fix(sessions_spawn): hard-fail invalid model overrides
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Gateway/CLI: add daemon runtime selection (Node recommended; Bun optional) and document WhatsApp/Baileys Bun WebSocket instability on reconnect.
|
- Gateway/CLI: add daemon runtime selection (Node recommended; Bun optional) and document WhatsApp/Baileys Bun WebSocket instability on reconnect.
|
||||||
|
- Sub-agents: allow `sessions_spawn` model overrides and error on invalid models. Thanks @azade-c for PR #298.
|
||||||
- Heartbeat: default interval 30m; clarified default prompt usage and HEARTBEAT.md template behavior.
|
- Heartbeat: default interval 30m; clarified default prompt usage and HEARTBEAT.md template behavior.
|
||||||
- Onboarding: write auth profiles to the multi-agent path (`~/.clawdbot/agents/main/agent/`) so the gateway finds credentials on first startup. Thanks @minghinmatthewlam for PR #327.
|
- Onboarding: write auth profiles to the multi-agent path (`~/.clawdbot/agents/main/agent/`) so the gateway finds credentials on first startup. Thanks @minghinmatthewlam for PR #327.
|
||||||
- Docs: add missing `ui:install` setup step in the README. Thanks @hugobarauna for PR #300.
|
- Docs: add missing `ui:install` setup step in the README. Thanks @hugobarauna for PR #300.
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ Spawn a sub-agent run in an isolated session and announce the result back to the
|
|||||||
Parameters:
|
Parameters:
|
||||||
- `task` (required)
|
- `task` (required)
|
||||||
- `label?` (optional; used for logs/UI)
|
- `label?` (optional; used for logs/UI)
|
||||||
|
- `model?` (optional; overrides the sub-agent model; invalid values error)
|
||||||
- `timeoutSeconds?` (default 0; 0 = fire-and-forget)
|
- `timeoutSeconds?` (default 0; 0 = fire-and-forget)
|
||||||
- `cleanup?` (`delete|keep`, default `delete`)
|
- `cleanup?` (`delete|keep`, default `delete`)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Use `sessions_spawn`:
|
|||||||
Tool params:
|
Tool params:
|
||||||
- `task` (required)
|
- `task` (required)
|
||||||
- `label?` (optional)
|
- `label?` (optional)
|
||||||
|
- `model?` (optional; overrides the sub-agent model; invalid values error)
|
||||||
- `timeoutSeconds?` (default `0`; `0` = fire-and-forget)
|
- `timeoutSeconds?` (default `0`; `0` = fire-and-forget)
|
||||||
- `cleanup?` (`delete|keep`, default `delete`)
|
- `cleanup?` (`delete|keep`, default `delete`)
|
||||||
|
|
||||||
|
|||||||
@@ -223,9 +223,7 @@ describe("subagents", () => {
|
|||||||
| undefined;
|
| undefined;
|
||||||
const message = params?.message ?? "";
|
const message = params?.message ?? "";
|
||||||
const reply =
|
const reply =
|
||||||
message === "Sub-agent announce step."
|
message === "Sub-agent announce step." ? "ANNOUNCE_SKIP" : "done";
|
||||||
? "ANNOUNCE_SKIP"
|
|
||||||
: "done";
|
|
||||||
replyByRunId.set(runId, reply);
|
replyByRunId.set(runId, reply);
|
||||||
return {
|
return {
|
||||||
runId,
|
runId,
|
||||||
@@ -278,4 +276,35 @@ describe("subagents", () => {
|
|||||||
model: "claude-haiku-4-5",
|
model: "claude-haiku-4-5",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sessions_spawn fails when model override is invalid", async () => {
|
||||||
|
callGatewayMock.mockReset();
|
||||||
|
const calls: Array<{ method?: string; params?: unknown }> = [];
|
||||||
|
|
||||||
|
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
||||||
|
const request = opts as { method?: string; params?: unknown };
|
||||||
|
calls.push(request);
|
||||||
|
if (request.method === "sessions.patch") {
|
||||||
|
throw new Error("invalid model: bad-model");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
const tool = createClawdbotTools({
|
||||||
|
agentSessionKey: "main",
|
||||||
|
agentProvider: "whatsapp",
|
||||||
|
}).find((candidate) => candidate.name === "sessions_spawn");
|
||||||
|
if (!tool) throw new Error("missing sessions_spawn tool");
|
||||||
|
|
||||||
|
const result = await tool.execute("call4", {
|
||||||
|
task: "do thing",
|
||||||
|
timeoutSeconds: 1,
|
||||||
|
model: "bad-model",
|
||||||
|
});
|
||||||
|
expect(result.details).toMatchObject({ status: "error" });
|
||||||
|
expect(
|
||||||
|
String((result.details as { error?: string }).error ?? ""),
|
||||||
|
).toContain("invalid model");
|
||||||
|
expect(calls.some((call) => call.method === "agent")).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -220,26 +220,38 @@ export function createSessionsSpawnTool(opts?: {
|
|||||||
parseAgentSessionKey(requesterInternalKey)?.agentId,
|
parseAgentSessionKey(requesterInternalKey)?.agentId,
|
||||||
);
|
);
|
||||||
const childSessionKey = `agent:${requesterAgentId}:subagent:${crypto.randomUUID()}`;
|
const childSessionKey = `agent:${requesterAgentId}:subagent:${crypto.randomUUID()}`;
|
||||||
const patchParams: { key: string; spawnedBy?: string; model?: string } = {
|
|
||||||
key: childSessionKey,
|
|
||||||
};
|
|
||||||
if (opts?.sandboxed === true) {
|
if (opts?.sandboxed === true) {
|
||||||
patchParams.spawnedBy = requesterInternalKey;
|
|
||||||
}
|
|
||||||
if (model) {
|
|
||||||
patchParams.model = model;
|
|
||||||
}
|
|
||||||
if (patchParams.spawnedBy || patchParams.model) {
|
|
||||||
try {
|
try {
|
||||||
await callGateway({
|
await callGateway({
|
||||||
method: "sessions.patch",
|
method: "sessions.patch",
|
||||||
params: patchParams,
|
params: { key: childSessionKey, spawnedBy: requesterInternalKey },
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// best-effort; scoping relies on this metadata but spawning still works without it
|
// best-effort; scoping relies on this metadata but spawning still works without it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (model) {
|
||||||
|
try {
|
||||||
|
await callGateway({
|
||||||
|
method: "sessions.patch",
|
||||||
|
params: { key: childSessionKey, model },
|
||||||
|
timeoutMs: 10_000,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const messageText =
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: typeof err === "string"
|
||||||
|
? err
|
||||||
|
: "error";
|
||||||
|
return jsonResult({
|
||||||
|
status: "error",
|
||||||
|
error: messageText,
|
||||||
|
childSessionKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const childSystemPrompt = buildSubagentSystemPrompt({
|
const childSystemPrompt = buildSubagentSystemPrompt({
|
||||||
requesterSessionKey,
|
requesterSessionKey,
|
||||||
requesterProvider: opts?.agentProvider,
|
requesterProvider: opts?.agentProvider,
|
||||||
|
|||||||
Reference in New Issue
Block a user