@koda-sl/baker-bridge 0.108.0 → 0.109.0-dev.557795bef
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -51
- package/dist/broker/baker-api.d.ts +23 -0
- package/dist/broker/baker-api.d.ts.map +1 -0
- package/dist/broker/baker-api.js +55 -0
- package/dist/broker/baker-api.js.map +1 -0
- package/dist/broker/broker-client.d.ts +10 -0
- package/dist/broker/broker-client.d.ts.map +1 -0
- package/dist/broker/broker-client.js +60 -0
- package/dist/broker/broker-client.js.map +1 -0
- package/dist/broker/broker-process.d.ts +10 -0
- package/dist/broker/broker-process.d.ts.map +1 -0
- package/dist/broker/broker-process.js +20 -0
- package/dist/broker/broker-process.js.map +1 -0
- package/dist/broker/broker-process.test.d.ts +2 -0
- package/dist/broker/broker-process.test.d.ts.map +1 -0
- package/dist/broker/broker-process.test.js +13 -0
- package/dist/broker/broker-process.test.js.map +1 -0
- package/dist/broker/ca.d.ts +38 -0
- package/dist/broker/ca.d.ts.map +1 -0
- package/dist/broker/ca.js +80 -0
- package/dist/broker/ca.js.map +1 -0
- package/dist/broker/ca.test.d.ts +2 -0
- package/dist/broker/ca.test.d.ts.map +1 -0
- package/dist/broker/ca.test.js +58 -0
- package/dist/broker/ca.test.js.map +1 -0
- package/dist/broker/credential-cache.d.ts +24 -0
- package/dist/broker/credential-cache.d.ts.map +1 -0
- package/dist/broker/credential-cache.js +76 -0
- package/dist/broker/credential-cache.js.map +1 -0
- package/dist/broker/credential-cache.test.d.ts +2 -0
- package/dist/broker/credential-cache.test.d.ts.map +1 -0
- package/dist/broker/credential-cache.test.js +136 -0
- package/dist/broker/credential-cache.test.js.map +1 -0
- package/dist/broker/credential-text.d.ts +45 -0
- package/dist/broker/credential-text.d.ts.map +1 -0
- package/dist/broker/credential-text.js +103 -0
- package/dist/broker/credential-text.js.map +1 -0
- package/dist/broker/credential-text.test.d.ts +2 -0
- package/dist/broker/credential-text.test.d.ts.map +1 -0
- package/dist/broker/credential-text.test.js +54 -0
- package/dist/broker/credential-text.test.js.map +1 -0
- package/dist/broker/env.d.ts +21 -0
- package/dist/broker/env.d.ts.map +1 -0
- package/dist/broker/env.js +20 -0
- package/dist/broker/env.js.map +1 -0
- package/dist/broker/main.d.ts +3 -0
- package/dist/broker/main.d.ts.map +1 -0
- package/dist/broker/main.js +57 -0
- package/dist/broker/main.js.map +1 -0
- package/dist/broker/proxy.d.ts +24 -0
- package/dist/broker/proxy.d.ts.map +1 -0
- package/dist/broker/proxy.integration.test.d.ts +2 -0
- package/dist/broker/proxy.integration.test.d.ts.map +1 -0
- package/dist/broker/proxy.integration.test.js +197 -0
- package/dist/broker/proxy.integration.test.js.map +1 -0
- package/dist/broker/proxy.js +147 -0
- package/dist/broker/proxy.js.map +1 -0
- package/dist/broker/rewrite.d.ts +44 -0
- package/dist/broker/rewrite.d.ts.map +1 -0
- package/dist/broker/rewrite.js +291 -0
- package/dist/broker/rewrite.js.map +1 -0
- package/dist/broker/rewrite.test.d.ts +2 -0
- package/dist/broker/rewrite.test.d.ts.map +1 -0
- package/dist/broker/rewrite.test.js +171 -0
- package/dist/broker/rewrite.test.js.map +1 -0
- package/dist/broker/socket-service.d.ts +18 -0
- package/dist/broker/socket-service.d.ts.map +1 -0
- package/dist/broker/socket-service.js +113 -0
- package/dist/broker/socket-service.js.map +1 -0
- package/dist/broker/socket-service.test.d.ts +2 -0
- package/dist/broker/socket-service.test.d.ts.map +1 -0
- package/dist/broker/socket-service.test.js +80 -0
- package/dist/broker/socket-service.test.js.map +1 -0
- package/dist/broker/supervisor-main.d.ts +3 -0
- package/dist/broker/supervisor-main.d.ts.map +1 -0
- package/dist/broker/supervisor-main.js +70 -0
- package/dist/broker/supervisor-main.js.map +1 -0
- package/dist/broker/supervisor.d.ts +25 -0
- package/dist/broker/supervisor.d.ts.map +1 -0
- package/dist/broker/supervisor.js +88 -0
- package/dist/broker/supervisor.js.map +1 -0
- package/dist/broker/supervisor.test.d.ts +2 -0
- package/dist/broker/supervisor.test.d.ts.map +1 -0
- package/dist/broker/supervisor.test.js +161 -0
- package/dist/broker/supervisor.test.js.map +1 -0
- package/dist/broker/token-bucket.d.ts +9 -0
- package/dist/broker/token-bucket.d.ts.map +1 -0
- package/dist/broker/token-bucket.js +18 -0
- package/dist/broker/token-bucket.js.map +1 -0
- package/dist/broker/usage-reporter.d.ts +18 -0
- package/dist/broker/usage-reporter.d.ts.map +1 -0
- package/dist/broker/usage-reporter.js +31 -0
- package/dist/broker/usage-reporter.js.map +1 -0
- package/dist/broker/usage-reporter.test.d.ts +2 -0
- package/dist/broker/usage-reporter.test.d.ts.map +1 -0
- package/dist/broker/usage-reporter.test.js +20 -0
- package/dist/broker/usage-reporter.test.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/hono/agent-session.d.ts +4 -0
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +28 -2
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +37 -0
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent-tasks.d.ts.map +1 -1
- package/dist/hono/agent-tasks.js +11 -7
- package/dist/hono/agent-tasks.js.map +1 -1
- package/dist/hono/agent-tasks.test.js +15 -0
- package/dist/hono/agent-tasks.test.js.map +1 -1
- package/dist/hono/agent.d.ts +2 -0
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +19 -2
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +8 -2
- package/dist/hono/agent.test.js.map +1 -1
- package/dist/hono/avatar-accent.d.ts +23 -5
- package/dist/hono/avatar-accent.d.ts.map +1 -1
- package/dist/hono/avatar-accent.js +54 -63
- package/dist/hono/avatar-accent.js.map +1 -1
- package/dist/hono/avatar-accent.test.js +64 -35
- package/dist/hono/avatar-accent.test.js.map +1 -1
- package/dist/hono/avatar-casting.d.ts +1 -0
- package/dist/hono/avatar-casting.d.ts.map +1 -1
- package/dist/hono/avatar-casting.js +18 -4
- package/dist/hono/avatar-casting.js.map +1 -1
- package/dist/hono/avatar-casting.test.js +5 -0
- package/dist/hono/avatar-casting.test.js.map +1 -1
- package/dist/hono/avatar-face-question.d.ts +23 -6
- package/dist/hono/avatar-face-question.d.ts.map +1 -1
- package/dist/hono/avatar-face-question.js +48 -59
- package/dist/hono/avatar-face-question.js.map +1 -1
- package/dist/hono/avatar-face-question.test.js +46 -101
- package/dist/hono/avatar-face-question.test.js.map +1 -1
- package/dist/hono/baker-ui-tool-schemas.test.js +30 -4
- package/dist/hono/baker-ui-tool-schemas.test.js.map +1 -1
- package/dist/hono/blocking-card.d.ts +12 -0
- package/dist/hono/blocking-card.d.ts.map +1 -0
- package/dist/hono/blocking-card.js +12 -0
- package/dist/hono/blocking-card.js.map +1 -0
- package/dist/hono/bridge-job.d.ts +68 -2
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +301 -56
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +489 -26
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/check-content.d.ts +49 -0
- package/dist/hono/check-content.d.ts.map +1 -0
- package/dist/hono/check-content.js +182 -0
- package/dist/hono/check-content.js.map +1 -0
- package/dist/hono/check-content.test.d.ts +2 -0
- package/dist/hono/check-content.test.d.ts.map +1 -0
- package/dist/hono/check-content.test.js +87 -0
- package/dist/hono/check-content.test.js.map +1 -0
- package/dist/hono/connection-input.d.ts +170 -12
- package/dist/hono/connection-input.d.ts.map +1 -1
- package/dist/hono/connection-input.js +166 -14
- package/dist/hono/connection-input.js.map +1 -1
- package/dist/hono/connection-input.test.js +112 -9
- package/dist/hono/connection-input.test.js.map +1 -1
- package/dist/hono/convex.d.ts +8 -0
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +8 -0
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/convex.test.js +4 -1
- package/dist/hono/convex.test.js.map +1 -1
- package/dist/hono/credential-input.d.ts +39 -0
- package/dist/hono/credential-input.d.ts.map +1 -0
- package/dist/hono/credential-input.js +78 -0
- package/dist/hono/credential-input.js.map +1 -0
- package/dist/hono/credential-scrub.d.ts +42 -0
- package/dist/hono/credential-scrub.d.ts.map +1 -0
- package/dist/hono/credential-scrub.js +173 -0
- package/dist/hono/credential-scrub.js.map +1 -0
- package/dist/hono/credential-scrub.test.d.ts +2 -0
- package/dist/hono/credential-scrub.test.d.ts.map +1 -0
- package/dist/hono/credential-scrub.test.js +130 -0
- package/dist/hono/credential-scrub.test.js.map +1 -0
- package/dist/hono/env.d.ts +1 -0
- package/dist/hono/env.d.ts.map +1 -1
- package/dist/hono/env.js +2 -0
- package/dist/hono/env.js.map +1 -1
- package/dist/hono/flow-crypto.d.ts +2 -1
- package/dist/hono/flow-crypto.d.ts.map +1 -1
- package/dist/hono/flow-crypto.js +50 -26
- package/dist/hono/flow-crypto.js.map +1 -1
- package/dist/hono/flow-crypto.test.js +32 -0
- package/dist/hono/flow-crypto.test.js.map +1 -1
- package/dist/hono/flow-input-card-fields.test.d.ts +10 -0
- package/dist/hono/flow-input-card-fields.test.d.ts.map +1 -0
- package/dist/hono/flow-input-card-fields.test.js +26 -0
- package/dist/hono/flow-input-card-fields.test.js.map +1 -0
- package/dist/hono/flow-input-fields.test.js +1 -1
- package/dist/hono/flow-input-fields.test.js.map +1 -1
- package/dist/hono/flow-input.d.ts +23 -2
- package/dist/hono/flow-input.d.ts.map +1 -1
- package/dist/hono/flow-input.js +63 -14
- package/dist/hono/flow-input.js.map +1 -1
- package/dist/hono/flow-input.test.js +67 -11
- package/dist/hono/flow-input.test.js.map +1 -1
- package/dist/hono/jev.d.ts +16 -0
- package/dist/hono/jev.d.ts.map +1 -0
- package/dist/hono/jev.js +60 -0
- package/dist/hono/jev.js.map +1 -0
- package/dist/hono/jev.test.d.ts +2 -0
- package/dist/hono/jev.test.d.ts.map +1 -0
- package/dist/hono/jev.test.js +15 -0
- package/dist/hono/jev.test.js.map +1 -0
- package/dist/hono/knowledge-content.d.ts.map +1 -1
- package/dist/hono/knowledge-content.js +4 -2
- package/dist/hono/knowledge-content.js.map +1 -1
- package/dist/hono/knowledge-content.test.js +6 -5
- package/dist/hono/knowledge-content.test.js.map +1 -1
- package/dist/hono/mcp-servers.d.ts +8 -1
- package/dist/hono/mcp-servers.d.ts.map +1 -1
- package/dist/hono/mcp-servers.js +17 -6
- package/dist/hono/mcp-servers.js.map +1 -1
- package/dist/hono/mcp-servers.test.js +25 -0
- package/dist/hono/mcp-servers.test.js.map +1 -1
- package/dist/hono/model-capabilities.d.ts.map +1 -1
- package/dist/hono/model-capabilities.js +20 -9
- package/dist/hono/model-capabilities.js.map +1 -1
- package/dist/hono/model-capabilities.test.js +16 -8
- package/dist/hono/model-capabilities.test.js.map +1 -1
- package/dist/hono/oom-priority.agent.test.d.ts +2 -0
- package/dist/hono/oom-priority.agent.test.d.ts.map +1 -0
- package/dist/hono/oom-priority.agent.test.js +138 -0
- package/dist/hono/oom-priority.agent.test.js.map +1 -0
- package/dist/hono/oom-priority.d.ts +39 -0
- package/dist/hono/oom-priority.d.ts.map +1 -0
- package/dist/hono/oom-priority.js +138 -0
- package/dist/hono/oom-priority.js.map +1 -0
- package/dist/hono/oom-priority.test.d.ts +2 -0
- package/dist/hono/oom-priority.test.d.ts.map +1 -0
- package/dist/hono/oom-priority.test.js +52 -0
- package/dist/hono/oom-priority.test.js.map +1 -0
- package/dist/hono/openrouter-session.d.ts.map +1 -1
- package/dist/hono/openrouter-session.js +12 -1
- package/dist/hono/openrouter-session.js.map +1 -1
- package/dist/hono/openrouter-session.test.js +9 -3
- package/dist/hono/openrouter-session.test.js.map +1 -1
- package/dist/hono/prompt-context.d.ts +8 -1
- package/dist/hono/prompt-context.d.ts.map +1 -1
- package/dist/hono/prompt-context.js +45 -7
- package/dist/hono/prompt-context.js.map +1 -1
- package/dist/hono/prompt-context.test.js +32 -1
- package/dist/hono/prompt-context.test.js.map +1 -1
- package/dist/hono/publish-recovery.d.ts +1 -1
- package/dist/hono/publish-recovery.d.ts.map +1 -1
- package/dist/hono/publish-recovery.js +9 -6
- package/dist/hono/publish-recovery.js.map +1 -1
- package/dist/hono/publish-recovery.test.js +11 -2
- package/dist/hono/publish-recovery.test.js.map +1 -1
- package/dist/hono/relay.d.ts +10 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +6 -0
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/sandbox-content.d.ts +2 -0
- package/dist/hono/sandbox-content.d.ts.map +1 -1
- package/dist/hono/sandbox-content.js +8 -4
- package/dist/hono/sandbox-content.js.map +1 -1
- package/dist/hono/sandbox-content.test.js +20 -1
- package/dist/hono/sandbox-content.test.js.map +1 -1
- package/dist/hono/server.d.ts.map +1 -1
- package/dist/hono/server.js +32 -0
- package/dist/hono/server.js.map +1 -1
- package/dist/hono/server.test.js +21 -1
- package/dist/hono/server.test.js.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +10 -1
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/tag-fields.d.ts.map +1 -1
- package/dist/hono/tag-fields.js +2 -0
- package/dist/hono/tag-fields.js.map +1 -1
- package/dist/hono/tag-input.d.ts +124 -3
- package/dist/hono/tag-input.d.ts.map +1 -1
- package/dist/hono/tag-input.js +27 -17
- package/dist/hono/tag-input.js.map +1 -1
- package/dist/hono/tag-input.test.js +19 -1
- package/dist/hono/tag-input.test.js.map +1 -1
- package/dist/hono/tool-mentions.d.ts.map +1 -1
- package/dist/hono/tool-mentions.js +1 -0
- package/dist/hono/tool-mentions.js.map +1 -1
- package/dist/hono/tool-mentions.test.js +5 -0
- package/dist/hono/tool-mentions.test.js.map +1 -1
- package/dist/hono/types.d.ts +8 -0
- package/dist/hono/types.d.ts.map +1 -1
- package/dist/hono/workspace-script.d.ts +10 -0
- package/dist/hono/workspace-script.d.ts.map +1 -0
- package/dist/hono/workspace-script.js +19 -0
- package/dist/hono/workspace-script.js.map +1 -0
- package/dist/hono/workspace-settle.d.ts +20 -0
- package/dist/hono/workspace-settle.d.ts.map +1 -0
- package/dist/hono/workspace-settle.js +50 -0
- package/dist/hono/workspace-settle.js.map +1 -0
- package/dist/review/digest.d.ts +8 -1
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +10 -43
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +11 -6
- package/dist/review/digest.test.js.map +1 -1
- package/dist/review/evidence-block.js +1 -1
- package/dist/review/evidence-block.js.map +1 -1
- package/dist/review/evidence-block.test.js +0 -10
- package/dist/review/evidence-block.test.js.map +1 -1
- package/dist/review/shell-policy.d.ts +6 -0
- package/dist/review/shell-policy.d.ts.map +1 -1
- package/dist/review/shell-policy.js +240 -72
- package/dist/review/shell-policy.js.map +1 -1
- package/dist/review/shell-policy.test.js +114 -0
- package/dist/review/shell-policy.test.js.map +1 -1
- package/package.json +9 -5
- package/dist/hono/latitude-telemetry.d.ts +0 -17
- package/dist/hono/latitude-telemetry.d.ts.map +0 -1
- package/dist/hono/latitude-telemetry.js +0 -52
- package/dist/hono/latitude-telemetry.js.map +0 -1
- package/dist/hono/latitude-telemetry.test.d.ts +0 -2
- package/dist/hono/latitude-telemetry.test.d.ts.map +0 -1
- package/dist/hono/latitude-telemetry.test.js +0 -18
- package/dist/hono/latitude-telemetry.test.js.map +0 -1
package/dist/hono/bridge-job.js
CHANGED
|
@@ -11,15 +11,18 @@ import { avatarAwaitingItsFace, avatarCreatedInAssistantMessage, avatarHandleFro
|
|
|
11
11
|
import { accentContradictsUser } from "./avatar-accent.js";
|
|
12
12
|
import { AVATAR_CASTING_TOOL_NAME, normalizeAvatarCastingToolInput, } from "./avatar-casting.js";
|
|
13
13
|
import { asksForAvatarFace, FACE_QUESTION_MESSAGE } from "./avatar-face-question.js";
|
|
14
|
-
import { CONNECTION_INPUT_TOOL_NAME, CONNECTION_PLATFORMS, normalizeConnectionInputToolInput, } from "./connection-input.js";
|
|
14
|
+
import { CONNECTION_INPUT_TOOL_NAME, CONNECTION_PLATFORMS, connectedButNotLoaded, normalizeConnectionInputToolInput, toolsConnected, } from "./connection-input.js";
|
|
15
15
|
import { messageContextTokens, pickContextWindow, streamEventContextUsage, turnContextUsage, } from "./context-usage.js";
|
|
16
|
-
import {
|
|
16
|
+
import { CREDENTIAL_INPUT_TOOL_NAME, normalizeCredentialInputToolInput, } from "./credential-input.js";
|
|
17
|
+
import { credentialScrubHooks } from "./credential-scrub.js";
|
|
18
|
+
import { agentStatedFlowMessage, FLOW_INPUT_TOOL_NAME, nodeTargetRefusals, normalizeFlowInputToolInput, readRawFlowTree, sanitizeFlowRequests, unknownFlowPrefillMessage, } from "./flow-input.js";
|
|
17
19
|
import { writesInventedLogo } from "./invented-logo.js";
|
|
18
|
-
import {
|
|
20
|
+
import { jevJudge } from "./jev.js";
|
|
19
21
|
import { composioScopesToInvalidate, failedCustomServers, reportMcpInvalid, serversConnectedWithNoTools, } from "./mcp-servers.js";
|
|
20
22
|
import { agentModelOverride, autoCompactWindowForModel, betasForModel, contextWindowForModel, } from "./model-capabilities.js";
|
|
23
|
+
import { agentShellPrefixEnv, killFirstWrapper } from "./oom-priority.js";
|
|
21
24
|
import { BEDROCK_SERVABLE_CAPABILITIES, buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, } from "./openrouter-session.js";
|
|
22
|
-
import { currentReferenceReminder } from "./prompt-context.js";
|
|
25
|
+
import { credentialReminder, currentReferenceReminder } from "./prompt-context.js";
|
|
23
26
|
import { bridgeRuntimeFiles } from "./runtime-files.js";
|
|
24
27
|
import { applyCurrentSenderAttribution, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
|
|
25
28
|
import { readSessionSkills } from "./session-skills.js";
|
|
@@ -29,6 +32,7 @@ import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TA
|
|
|
29
32
|
import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
|
|
30
33
|
import { recordTurnEvent } from "./turn-events.js";
|
|
31
34
|
import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
|
|
35
|
+
import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
|
|
32
36
|
export function parsePlanDecision(value) {
|
|
33
37
|
if (!value || typeof value !== "object") {
|
|
34
38
|
return null;
|
|
@@ -43,6 +47,35 @@ export function parsePlanDecision(value) {
|
|
|
43
47
|
}
|
|
44
48
|
return null;
|
|
45
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* How long a mid-turn MCP reload may hold the card's answer. Connecting a
|
|
52
|
+
* handful of HTTP servers takes seconds; past this the tools arrive next
|
|
53
|
+
* message instead, and the turn goes on.
|
|
54
|
+
*/
|
|
55
|
+
const MCP_RELOAD_TIMEOUT_MS = 30_000;
|
|
56
|
+
/** The card's resolution, with what the reload did: ready now, or which servers would not start. */
|
|
57
|
+
function withReload(resolution, reload) {
|
|
58
|
+
const missing = reload?.ready ? connectedButNotLoaded(resolution, reload.loaded) : [];
|
|
59
|
+
const failed = [...(reload?.failed ?? []), ...missing];
|
|
60
|
+
return {
|
|
61
|
+
...resolution,
|
|
62
|
+
toolsReady: reload?.ready === true && missing.length === 0,
|
|
63
|
+
...(failed.length > 0 ? { failedToLoad: failed } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** The promise's value, or `undefined` once `ms` pass without one. */
|
|
67
|
+
async function settleWithin(promise, ms) {
|
|
68
|
+
let timer;
|
|
69
|
+
const timedOut = new Promise((resolve) => {
|
|
70
|
+
timer = setTimeout(() => resolve(undefined), ms);
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
return await Promise.race([promise, timedOut]);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
46
79
|
/**
|
|
47
80
|
* The Agent SDK matches AskUserQuestion answers to their questions by the
|
|
48
81
|
* question TEXT (see `AskUserQuestionInput.answers`), but the dashboard submits
|
|
@@ -117,11 +150,10 @@ const BASE_DISALLOWED_TOOLS = [
|
|
|
117
150
|
//
|
|
118
151
|
// Since CLI 2.1.233 the CLI additionally withholds these from
|
|
119
152
|
// Sonnet-5-and-newer models — so on Opus 5.5 this list is now
|
|
120
|
-
// belt-and-braces. It is NOT redundant
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// production agent cannot see.
|
|
153
|
+
// belt-and-braces. It is NOT redundant: Haiku 4.5 predates that gate
|
|
154
|
+
// (read off the session's own `init` tool list on 2.1.239, it is offered
|
|
155
|
+
// all four and Opus 5 none), and any override naming it would hand that
|
|
156
|
+
// agent the backlog the production agent cannot see.
|
|
125
157
|
//
|
|
126
158
|
// `TodoWrite` is gone from both — it is absent from an SDK session's
|
|
127
159
|
// tool list on either model, so in-turn planning no longer has a tool
|
|
@@ -151,10 +183,11 @@ const BASE_DISALLOWED_TOOLS = [
|
|
|
151
183
|
// not asked to fund a research fleet. Ordinary fan-out is unaffected:
|
|
152
184
|
// `Task` stays, so the scaffold's subagents still run.
|
|
153
185
|
"Workflow",
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
186
|
+
// NOT listed: `SendMessage`. It is how the model follows up with a
|
|
187
|
+
// subagent it already ran, and the CLI tells it to on every Agent result.
|
|
188
|
+
// The continued subagent runs in the background, but it announces itself
|
|
189
|
+
// with `task_started` / `background_tasks_changed`, so the turn stays open
|
|
190
|
+
// until it finishes (`createTurnEndController`).
|
|
158
191
|
// A Session review may not change anything. Removing the definitions is
|
|
159
192
|
// the second of three layers — the key it carries is read-scoped and its
|
|
160
193
|
// Bash calls go through `reviewBashPolicy` — because each fails
|
|
@@ -189,6 +222,16 @@ export function reviewQueryOptions(job) {
|
|
|
189
222
|
? { outputFormat: { type: "json_schema", schema: job.review.outputSchema }, hooks: reviewOutputHooks() }
|
|
190
223
|
: {};
|
|
191
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Every job's tool results pass through the credential scrub, appended after
|
|
227
|
+
* any other PostToolUse hook so its rewrite is the one the SDK keeps.
|
|
228
|
+
*/
|
|
229
|
+
function withCredentialScrub(options) {
|
|
230
|
+
const scrub = credentialScrubHooks();
|
|
231
|
+
const hooks = { ...(options.hooks ?? {}) };
|
|
232
|
+
hooks.PostToolUse = [...(hooks.PostToolUse ?? []), ...(scrub.PostToolUse ?? [])];
|
|
233
|
+
return { ...options, hooks };
|
|
234
|
+
}
|
|
192
235
|
/**
|
|
193
236
|
* The review runs on Baker's own OpenRouter credential and its own read-only
|
|
194
237
|
* key, both replacing whatever the Runtime was started with — the client never
|
|
@@ -215,6 +258,12 @@ export const REVIEW_DEBUG_LOG = "/tmp/baker-review-cli.jsonl";
|
|
|
215
258
|
// briefly; model activity cancels it (the CLI clearly isn't idle). Turns that
|
|
216
259
|
// never ran a task skip the delay entirely.
|
|
217
260
|
const DEFAULT_TURN_END_SETTLE_MS = 1_000;
|
|
261
|
+
// When the last task settles after the clean result, the CLI wakes the model on
|
|
262
|
+
// it and streams nothing until the woken turn's first token — seconds, and 30+
|
|
263
|
+
// on a slow route. Ending input inside that silence strands the woken turn on a
|
|
264
|
+
// closed channel, and the CLI's exit at its result kills anything it backgrounds
|
|
265
|
+
// (dev, Oct 2026: a render). A settle that wakes nobody waits this long once.
|
|
266
|
+
const DEFAULT_TURN_END_WAKE_MS = 45_000;
|
|
218
267
|
// After input ends, a healthy idle CLI exits within seconds. If the stream is
|
|
219
268
|
// still open past this window with no messages arriving, the CLI is wedged —
|
|
220
269
|
// close the query so the turn can finish instead of hanging to the watchdog.
|
|
@@ -242,10 +291,10 @@ const UNATTENDED_PROMPT_MESSAGE = "This run is unattended — it was started by
|
|
|
242
291
|
* asked for in a text prompt renders a question nobody can answer. In each case the user
|
|
243
292
|
* is left waiting for something that was never going to come.
|
|
244
293
|
*/
|
|
245
|
-
function refuseMalformedCall(toolName, input, userText, priorUserText) {
|
|
294
|
+
async function refuseMalformedCall(toolName, input, judge, userText, priorUserText) {
|
|
246
295
|
if (toolName === "Bash") {
|
|
247
296
|
const command = typeof input?.command === "string" ? input.command : undefined;
|
|
248
|
-
return toolRunAsCommand(command) ?? accentContradictsUser(command, userText, priorUserText);
|
|
297
|
+
return toolRunAsCommand(command) ?? (await accentContradictsUser(command, userText, priorUserText, judge));
|
|
249
298
|
}
|
|
250
299
|
if (toolName === "Agent" || toolName === "Task") {
|
|
251
300
|
return blockingToolDelegated(typeof input?.prompt === "string" ? input.prompt : undefined);
|
|
@@ -253,11 +302,18 @@ function refuseMalformedCall(toolName, input, userText, priorUserText) {
|
|
|
253
302
|
if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
254
303
|
return writesInventedLogo(typeof input?.file_path === "string" ? input.file_path : undefined);
|
|
255
304
|
}
|
|
256
|
-
if (toolName === "AskUserQuestion" && asksForAvatarFace(input)) {
|
|
305
|
+
if (toolName === "AskUserQuestion" && (await asksForAvatarFace(input, judge))) {
|
|
257
306
|
return FACE_QUESTION_MESSAGE;
|
|
258
307
|
}
|
|
259
308
|
return null;
|
|
260
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Whether this Runtime has Credentials: the backend sets `BAKER_CREDENTIALS=1`
|
|
312
|
+
* alongside the credential broker's proxy env. Read per turn, not at import.
|
|
313
|
+
*/
|
|
314
|
+
function credentialsEnabled() {
|
|
315
|
+
return process.env.BAKER_CREDENTIALS === "1";
|
|
316
|
+
}
|
|
261
317
|
/** Blocking prompts: every tool that would leave a card the backend parks on. */
|
|
262
318
|
function deniedUnattendedPrompt(toolName) {
|
|
263
319
|
return (toolName === "AskUserQuestion" ||
|
|
@@ -265,6 +321,7 @@ function deniedUnattendedPrompt(toolName) {
|
|
|
265
321
|
toolName === TAG_INPUT_TOOL_NAME ||
|
|
266
322
|
toolName === FLOW_INPUT_TOOL_NAME ||
|
|
267
323
|
toolName === CONNECTION_INPUT_TOOL_NAME ||
|
|
324
|
+
toolName === CREDENTIAL_INPUT_TOOL_NAME ||
|
|
268
325
|
toolName === AVATAR_CASTING_TOOL_NAME);
|
|
269
326
|
}
|
|
270
327
|
export class StreamStallError extends Error {
|
|
@@ -341,8 +398,7 @@ function enrichImageError(message) {
|
|
|
341
398
|
return `${message} — likely a file whose extension lies about its format (e.g. PNG renamed .jpg) or a corrupted download. Run the image validator and check magic bytes with \`file <path>\`.`;
|
|
342
399
|
}
|
|
343
400
|
/**
|
|
344
|
-
* Which hooks a job runs. The client's agent runs the workspace's own
|
|
345
|
-
* traced to Latitude. A Session review runs none: every workspace hook is
|
|
401
|
+
* Which hooks a job runs. The client's agent runs the workspace's own. A Session review runs none: every workspace hook is
|
|
346
402
|
* written for the client's agent, and in the reviewer they speak about "this
|
|
347
403
|
* Session" with the review's read-scoped key. The SessionStart probes are
|
|
348
404
|
* refused on full-scope routes, and the reviewer filed its own "status unknown"
|
|
@@ -359,12 +415,11 @@ function hookSettingsForJob(job) {
|
|
|
359
415
|
if (job.review) {
|
|
360
416
|
return { disableAllHooks: true, permissions: { ask: ["Bash"] } };
|
|
361
417
|
}
|
|
362
|
-
|
|
363
|
-
return hooks ? { hooks } : {};
|
|
418
|
+
return {};
|
|
364
419
|
}
|
|
365
420
|
// Carry a Runtime-wide model override down to the fan-out workers.
|
|
366
421
|
//
|
|
367
|
-
// The shared subagents pin themselves to Sonnet 5 in their own frontmatter (see
|
|
422
|
+
// The shared subagents pin themselves to Sonnet 5.5 in their own frontmatter (see
|
|
368
423
|
// `DEFAULT_MODEL`). Since CLI 2.1.251 `CLAUDE_CODE_SUBAGENT_MODEL` is only the
|
|
369
424
|
// default beneath that pin; `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` (2.1.257) makes it
|
|
370
425
|
// win over the frontmatter and any per-spawn model again. Absent (production),
|
|
@@ -498,9 +553,9 @@ function sdkResultError(msg) {
|
|
|
498
553
|
// the whole turn; the deny THRESHOLD below still guards a one-off end-of-turn race.
|
|
499
554
|
// The trailing colon matters: the CLI always emits the envelope followed by the
|
|
500
555
|
// error that caused it ("…failed: Error: Stream closed", "…failed: AbortError: …").
|
|
501
|
-
// Prose that merely NAMES the failure quotes it without one, which is how
|
|
502
|
-
//
|
|
503
|
-
// block is an is_error tool_result, so a colon-less match counted a healthy
|
|
556
|
+
// Prose that merely NAMES the failure quotes it without one, which is how a
|
|
557
|
+
// hook explaining a block quotes it (the scaffold's former background-subagent
|
|
558
|
+
// block did) — and a hook block is an is_error tool_result, so a colon-less match counted a healthy
|
|
504
559
|
// blocked spawn as a deny and could trip the breaker on a healthy turn.
|
|
505
560
|
const CONTROL_CHANNEL_DENY_MARKER = "Tool permission request failed:";
|
|
506
561
|
// Hook failures are reported through the same is_error tool_result channel but
|
|
@@ -658,14 +713,37 @@ export function createStallDetector(input) {
|
|
|
658
713
|
timer.unref?.();
|
|
659
714
|
return {
|
|
660
715
|
observeMessage: (msg) => {
|
|
716
|
+
// A tool's progress says nothing about the model: a background task's
|
|
717
|
+
// heartbeat arrives while the model's stream is stalled.
|
|
718
|
+
if (msg.type === "tool_progress") {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
661
721
|
lastMessageAt = input.now();
|
|
662
722
|
trackPendingToolUses(pendingToolUses, msg);
|
|
663
723
|
awaitingModel = (msg.type === "user" || msg.type === "stream_event") && pendingToolUses.size === 0;
|
|
664
724
|
},
|
|
665
725
|
stalled: () => stalled,
|
|
726
|
+
hasPendingToolUses: () => pendingToolUses.size > 0,
|
|
666
727
|
dispose: () => clearInterval(timer),
|
|
667
728
|
};
|
|
668
729
|
}
|
|
730
|
+
// A query's cost and `modelUsage` are cumulative — on every result message and
|
|
731
|
+
// on the CLI's usage reply alike — so the latest reading replaces the last one
|
|
732
|
+
// rather than adding to it. It is folded into the turn totals when the query
|
|
733
|
+
// settles (see `runQueryOnce`). A spuriously empty later reading never wipes a
|
|
734
|
+
// real earlier one.
|
|
735
|
+
function recordCumulativeUsage(state, reading) {
|
|
736
|
+
const own = ownQueryUsage(reading, state.queryUsageBaseline);
|
|
737
|
+
state.observedCostUsd = own.costUsd;
|
|
738
|
+
const snapshot = queryModelUsage(own.modelUsage);
|
|
739
|
+
if (snapshot.size > 0) {
|
|
740
|
+
state.queryModelUsage = snapshot;
|
|
741
|
+
}
|
|
742
|
+
return own.costUsd;
|
|
743
|
+
}
|
|
744
|
+
// How long ending a turn waits on the CLI's usage reply before going ahead
|
|
745
|
+
// without it. Billing a turn late is recoverable; hanging one is not.
|
|
746
|
+
const USAGE_READING_TIMEOUT_MS = 5_000;
|
|
669
747
|
// Decides when the turn is really over and the CLI may be told to exit.
|
|
670
748
|
//
|
|
671
749
|
// The prompt rides a held-open input stream (see `createHeldOpenQuery` in
|
|
@@ -687,7 +765,10 @@ export function createStallDetector(input) {
|
|
|
687
765
|
// events must not, or the end would never re-arm. After input ends, an exit
|
|
688
766
|
// guard closes the query if the CLI neither speaks nor exits, so a wedged
|
|
689
767
|
// process cannot hang the turn until the session watchdog.
|
|
690
|
-
function createTurnEndController(job, query, closeQuery, timings, onTurnEvent
|
|
768
|
+
function createTurnEndController(job, query, closeQuery, timings, onTurnEvent,
|
|
769
|
+
// Runs once, before input ends on a turn that ran tasks: a task can settle
|
|
770
|
+
// without waking the model, so the last result may not include what it spent.
|
|
771
|
+
beforeEndInput) {
|
|
691
772
|
const inFlightTasks = new Set();
|
|
692
773
|
// The background_tasks_changed LEVEL signal, tracked separately from the
|
|
693
774
|
// edge-paired set above: a foreground command the CLI auto-backgrounds emits
|
|
@@ -700,15 +781,52 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
700
781
|
let cleanResultSeen = false;
|
|
701
782
|
let lastWasTaskLifecycle = false;
|
|
702
783
|
let inputEnded = false;
|
|
784
|
+
// Between deciding to end and ending, while the usage reading is in flight.
|
|
785
|
+
// Anything that would have cancelled a pending end — model activity, a task
|
|
786
|
+
// starting — is recorded here and abandons the end instead.
|
|
787
|
+
let ending;
|
|
788
|
+
// The query stopped being drained (a Stop, a crash): a reading answering
|
|
789
|
+
// after that has no turn left to end.
|
|
790
|
+
let disposed = false;
|
|
703
791
|
let settleTimer;
|
|
704
792
|
let exitGuard;
|
|
705
793
|
const endInput = () => {
|
|
706
794
|
settleTimer = undefined;
|
|
707
795
|
// Fire-time guard: a task that started during the settle window keeps the
|
|
708
796
|
// channel open; a later settle re-arms the end.
|
|
709
|
-
if (inputEnded || hasInFlightWork()) {
|
|
797
|
+
if (inputEnded || ending || hasInFlightWork()) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (sawAnyTask && beforeEndInput) {
|
|
801
|
+
const attempt = { interrupted: false };
|
|
802
|
+
ending = attempt;
|
|
803
|
+
let timeout;
|
|
804
|
+
const timedOut = new Promise((resolveTimeout) => {
|
|
805
|
+
timeout = setTimeout(resolveTimeout, USAGE_READING_TIMEOUT_MS);
|
|
806
|
+
timeout.unref?.();
|
|
807
|
+
});
|
|
808
|
+
void Promise.race([beforeEndInput().catch(() => undefined), timedOut]).finally(() => {
|
|
809
|
+
clearTimeout(timeout);
|
|
810
|
+
ending = undefined;
|
|
811
|
+
if (disposed) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
// Re-check at the moment input would end, as the fire-time guard above
|
|
815
|
+
// does: a wake or a task that landed during the reading keeps the
|
|
816
|
+
// channel open, and the woken turn's own result or settle ends it.
|
|
817
|
+
if (attempt.interrupted || hasInFlightWork()) {
|
|
818
|
+
if (cleanResultSeen && !hasInFlightWork()) {
|
|
819
|
+
scheduleEnd();
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
finishEndInput();
|
|
824
|
+
});
|
|
710
825
|
return;
|
|
711
826
|
}
|
|
827
|
+
finishEndInput();
|
|
828
|
+
};
|
|
829
|
+
const finishEndInput = () => {
|
|
712
830
|
inputEnded = true;
|
|
713
831
|
query.endInput?.();
|
|
714
832
|
onTurnEvent({
|
|
@@ -728,7 +846,7 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
728
846
|
}, timings.exitGuardMs);
|
|
729
847
|
exitGuard.unref?.();
|
|
730
848
|
};
|
|
731
|
-
const scheduleEnd = () => {
|
|
849
|
+
const scheduleEnd = (delayMs = timings.settleMs) => {
|
|
732
850
|
if (settleTimer) {
|
|
733
851
|
return;
|
|
734
852
|
}
|
|
@@ -740,7 +858,7 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
740
858
|
endInput();
|
|
741
859
|
return;
|
|
742
860
|
}
|
|
743
|
-
settleTimer = setTimeout(endInput,
|
|
861
|
+
settleTimer = setTimeout(endInput, delayMs);
|
|
744
862
|
settleTimer.unref?.();
|
|
745
863
|
};
|
|
746
864
|
return {
|
|
@@ -778,6 +896,9 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
778
896
|
clearTimeout(settleTimer);
|
|
779
897
|
settleTimer = undefined;
|
|
780
898
|
}
|
|
899
|
+
if (ending && (isModelActivity || hasInFlightWork())) {
|
|
900
|
+
ending.interrupted = true;
|
|
901
|
+
}
|
|
781
902
|
if (exitGuard) {
|
|
782
903
|
exitGuard.refresh();
|
|
783
904
|
}
|
|
@@ -803,10 +924,13 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
803
924
|
// second result) would strand the turn until the watchdog. An emptied
|
|
804
925
|
// background_tasks_changed level counts as a settle the same way.
|
|
805
926
|
if (lastWasTaskLifecycle && cleanResultSeen && !hasInFlightWork()) {
|
|
806
|
-
scheduleEnd();
|
|
927
|
+
scheduleEnd(timings.wakeMs);
|
|
807
928
|
}
|
|
808
929
|
},
|
|
930
|
+
hasInFlightWork,
|
|
931
|
+
ranTasks: () => sawAnyTask,
|
|
809
932
|
dispose: () => {
|
|
933
|
+
disposed = true;
|
|
810
934
|
if (settleTimer) {
|
|
811
935
|
clearTimeout(settleTimer);
|
|
812
936
|
settleTimer = undefined;
|
|
@@ -893,11 +1017,14 @@ export class BridgeJobExecutor {
|
|
|
893
1017
|
stallCheckIntervalMs;
|
|
894
1018
|
recordTurnEvent;
|
|
895
1019
|
turnEndSettleMs;
|
|
1020
|
+
turnEndWakeMs;
|
|
896
1021
|
turnEndExitGuardMs;
|
|
897
1022
|
now;
|
|
898
1023
|
readUsageBaseline;
|
|
899
1024
|
stalledCliExitMs;
|
|
900
1025
|
readFlowTree;
|
|
1026
|
+
settleWorkspace;
|
|
1027
|
+
judge;
|
|
901
1028
|
constructor(dependencies) {
|
|
902
1029
|
this.sdk = dependencies.sdk;
|
|
903
1030
|
this.relay = dependencies.relay;
|
|
@@ -910,14 +1037,17 @@ export class BridgeJobExecutor {
|
|
|
910
1037
|
this.stallCheckIntervalMs = dependencies.stallCheckIntervalMs ?? DEFAULT_STALL_CHECK_INTERVAL_MS;
|
|
911
1038
|
this.recordTurnEvent = dependencies.recordTurnEvent ?? recordTurnEvent;
|
|
912
1039
|
this.turnEndSettleMs = dependencies.turnEndSettleMs ?? DEFAULT_TURN_END_SETTLE_MS;
|
|
1040
|
+
this.turnEndWakeMs = dependencies.turnEndWakeMs ?? DEFAULT_TURN_END_WAKE_MS;
|
|
913
1041
|
this.turnEndExitGuardMs = dependencies.turnEndExitGuardMs ?? DEFAULT_TURN_END_EXIT_GUARD_MS;
|
|
914
1042
|
this.now = dependencies.now ?? Date.now;
|
|
915
1043
|
this.readUsageBaseline =
|
|
916
1044
|
dependencies.readUsageBaseline ??
|
|
917
1045
|
((sessionId) => readUsageBaseline(sessionId, process.env.CLAUDE_CONFIG_DIR || resolve(process.env.HOME || homedir(), ".claude")));
|
|
918
1046
|
this.stalledCliExitMs = dependencies.stalledCliExitMs ?? STALLED_CLI_EXIT_MS;
|
|
1047
|
+
this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
|
|
919
1048
|
this.readFlowTree =
|
|
920
1049
|
dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
|
|
1050
|
+
this.judge = dependencies.judge ?? jevJudge(process.env.OPENROUTER_API_KEY);
|
|
921
1051
|
}
|
|
922
1052
|
async execute(job, controls) {
|
|
923
1053
|
const state = createBridgeJobState();
|
|
@@ -990,7 +1120,11 @@ export class BridgeJobExecutor {
|
|
|
990
1120
|
const verbatim = verbatimSlashCommand(job.prompt) ??
|
|
991
1121
|
leadingSkillCommand(job.prompt, (await readSessionSkills(bridgeRuntimeFiles.projectRoot())) ?? []);
|
|
992
1122
|
let requestPrompt = verbatim ?? applyCurrentSenderAttribution(job.prompt, job.currentSender);
|
|
993
|
-
const
|
|
1123
|
+
const reminders = [
|
|
1124
|
+
verbatim === null ? currentReferenceReminder(job.currentReference) : undefined,
|
|
1125
|
+
credentialReminder(job.credentials ?? [], credentialsEnabled()),
|
|
1126
|
+
].filter((text) => text !== undefined);
|
|
1127
|
+
const reminder = reminders.length > 0 ? reminders.join("\n\n") : undefined;
|
|
994
1128
|
if (job.attachments && job.attachments.length > 0) {
|
|
995
1129
|
requestPrompt += await this.prepareAttachments(job.attachments, "message");
|
|
996
1130
|
}
|
|
@@ -1247,6 +1381,39 @@ export class BridgeJobExecutor {
|
|
|
1247
1381
|
return undefined;
|
|
1248
1382
|
}
|
|
1249
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Load what the user just connected into the running query, so its tools
|
|
1386
|
+
* answer in this turn instead of the next. Asks Convex for the whole set
|
|
1387
|
+
* afresh (the same set the next turn starts from, so the tool list changes
|
|
1388
|
+
* once, not twice) and hands it to the CLI with `baker_ui` kept — the SDK
|
|
1389
|
+
* drops any in-process server missing from the call, and `baker_ui` is the
|
|
1390
|
+
* server whose tool call is resolving right now. Never throws. A server the
|
|
1391
|
+
* CLI could not start is named, because it will not start next message
|
|
1392
|
+
* either; anything else that goes wrong here loads next message.
|
|
1393
|
+
*/
|
|
1394
|
+
async reloadMcpServers(job, query) {
|
|
1395
|
+
const notReady = { ready: false, loaded: [], failed: [] };
|
|
1396
|
+
if (!this.mcpServersProvider || !query.setMcpServers) {
|
|
1397
|
+
return notReady;
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
const servers = await this.mcpServersProvider(job.threadId, job.currentSender?.userId, { fresh: true });
|
|
1401
|
+
if (!servers) {
|
|
1402
|
+
return notReady;
|
|
1403
|
+
}
|
|
1404
|
+
const result = await settleWithin(query.setMcpServers({ ...servers, baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) }), MCP_RELOAD_TIMEOUT_MS);
|
|
1405
|
+
const failed = Object.keys(result?.errors ?? {});
|
|
1406
|
+
if (failed.length > 0) {
|
|
1407
|
+
console.error(`[mcp] reload after a connection could not start ${failed.join(", ")}`);
|
|
1408
|
+
}
|
|
1409
|
+
const loaded = Object.keys(servers).filter((name) => !failed.includes(name));
|
|
1410
|
+
return { ready: result !== undefined && failed.length === 0, loaded, failed };
|
|
1411
|
+
}
|
|
1412
|
+
catch (error) {
|
|
1413
|
+
console.error(`[mcp] reload after a connection failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1414
|
+
return notReady;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1250
1417
|
reportMcpFailures(job, msg) {
|
|
1251
1418
|
for (const scope of composioScopesToInvalidate(msg)) {
|
|
1252
1419
|
void reportMcpInvalid(job.threadId, { scope }, job.currentSender?.userId);
|
|
@@ -1266,7 +1433,7 @@ export class BridgeJobExecutor {
|
|
|
1266
1433
|
* handlers — except on an unattended turn, where there is nobody to answer and
|
|
1267
1434
|
* every one of them is refused up front (see `UNATTENDED_PROMPT_MESSAGE`).
|
|
1268
1435
|
*/
|
|
1269
|
-
routeToolUse(job, controls, toolName, input, opts) {
|
|
1436
|
+
async routeToolUse(job, controls, toolName, input, opts) {
|
|
1270
1437
|
const refusedShell = reviewShellRefusal(job, toolName, input);
|
|
1271
1438
|
if (refusedShell) {
|
|
1272
1439
|
return Promise.resolve({ behavior: "deny", message: refusedShell });
|
|
@@ -1282,9 +1449,9 @@ export class BridgeJobExecutor {
|
|
|
1282
1449
|
});
|
|
1283
1450
|
return Promise.resolve({ behavior: "deny", message: UNATTENDED_PROMPT_MESSAGE });
|
|
1284
1451
|
}
|
|
1285
|
-
const malformed = refuseMalformedCall(toolName, input, job.prompt, userTurnsBefore(job));
|
|
1452
|
+
const malformed = await refuseMalformedCall(toolName, input, this.judge, job.prompt, userTurnsBefore(job));
|
|
1286
1453
|
if (malformed)
|
|
1287
|
-
return
|
|
1454
|
+
return { behavior: "deny", message: malformed };
|
|
1288
1455
|
// Cast what is owed FIRST, whatever comes next — including another create. Checked
|
|
1289
1456
|
// the other way round, a second create (the agent retrying after a rejected flag,
|
|
1290
1457
|
// which is what happens in practice) would overwrite the handle and the first
|
|
@@ -1337,6 +1504,15 @@ export class BridgeJobExecutor {
|
|
|
1337
1504
|
if (toolName === CONNECTION_INPUT_TOOL_NAME) {
|
|
1338
1505
|
return this.handleConnectionInputRequest(job, controls, input, opts.toolUseID, opts.signal);
|
|
1339
1506
|
}
|
|
1507
|
+
if (toolName === CREDENTIAL_INPUT_TOOL_NAME) {
|
|
1508
|
+
if (!credentialsEnabled()) {
|
|
1509
|
+
return Promise.resolve({
|
|
1510
|
+
behavior: "deny",
|
|
1511
|
+
message: "Saved credentials are not available in this workspace. Ask the user to share what you need another way, and never ask them to paste a password or key into the chat.",
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
return this.handleCredentialInputRequest(job, controls, input, opts.toolUseID, opts.signal);
|
|
1515
|
+
}
|
|
1340
1516
|
if (toolName === AVATAR_CASTING_TOOL_NAME) {
|
|
1341
1517
|
return this.handleAvatarCastingRequest(job, controls, input, opts.toolUseID, opts.signal);
|
|
1342
1518
|
}
|
|
@@ -1375,6 +1551,10 @@ export class BridgeJobExecutor {
|
|
|
1375
1551
|
// warning to the user. Pinning it takes that threshold out of remote control.
|
|
1376
1552
|
// See `autoCompactWindowForModel` for the full chain and its ceiling.
|
|
1377
1553
|
...(autoCompactWindow !== undefined ? { autoCompactWindow } : {}),
|
|
1554
|
+
// Subagents, workflows and helper requests default to a 5-minute prompt
|
|
1555
|
+
// cache outside a Claude subscription, so a subagent waiting on a render
|
|
1556
|
+
// longer than that rewrites its whole prefix on the next call.
|
|
1557
|
+
subagentPromptCacheTtl: "1h",
|
|
1378
1558
|
// SDK rejects autoMemoryDirectory from project settings for security; must use settings tier. Gate on BAKER_CHAT_ID so local dev never writes into the monorepo.
|
|
1379
1559
|
...(process.env.BAKER_CHAT_ID
|
|
1380
1560
|
? {
|
|
@@ -1407,11 +1587,8 @@ export class BridgeJobExecutor {
|
|
|
1407
1587
|
settingSources: ["project"],
|
|
1408
1588
|
// `baker_ui` hosts in-process interactive dashboard tools (secure tag
|
|
1409
1589
|
// form). Always present; HTTP (Composio) servers merge alongside it.
|
|
1410
|
-
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer() },
|
|
1411
|
-
|
|
1412
|
-
// `settings` to the CLI whenever it is present, so an empty object still
|
|
1413
|
-
// becomes a `--settings {}` flag tier for the CLI to parse.
|
|
1414
|
-
...(Object.keys(settings).length > 0 ? { settings } : {}),
|
|
1590
|
+
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
|
|
1591
|
+
settings,
|
|
1415
1592
|
includePartialMessages: true,
|
|
1416
1593
|
// Only the workspace's own skills. Omitting the option is NOT "skills
|
|
1417
1594
|
// off" — the CLI's defaults still load its built-in developer skills
|
|
@@ -1420,8 +1597,11 @@ export class BridgeJobExecutor {
|
|
|
1420
1597
|
// the workspace could not be read; omit rather than allowlist nothing.
|
|
1421
1598
|
...(skills ? { skills } : {}),
|
|
1422
1599
|
disallowedTools: disallowedToolsFor(job),
|
|
1423
|
-
...reviewQueryOptions(job),
|
|
1424
|
-
|
|
1600
|
+
...withCredentialScrub(reviewQueryOptions(job)),
|
|
1601
|
+
// Never omitted: since SDK 0.3.286 an omitted mode starts a third-party-provider
|
|
1602
|
+
// session (OpenRouter) in auto mode, where a classifier rather than `canUseTool`
|
|
1603
|
+
// decides tool calls.
|
|
1604
|
+
permissionMode: job.planMode ? "plan" : "default",
|
|
1425
1605
|
canUseTool: (toolName, input, opts) => this.routeToolUse(job, controls, toolName, input, opts),
|
|
1426
1606
|
// No `fallbackModel`, and no server-side refusal `fallbacks` in the extra
|
|
1427
1607
|
// body: the CLI's refusal fallback is gated on a first-party base URL (see
|
|
@@ -1445,6 +1625,13 @@ export class BridgeJobExecutor {
|
|
|
1445
1625
|
// system-reminder repeatedly pushed the agent toward that ephemeral
|
|
1446
1626
|
// backlog instead of the product's own Tasks (`baker actions`).
|
|
1447
1627
|
CLAUDE_CODE_ENABLE_TASKS: "0",
|
|
1628
|
+
// Every Bash command runs through a wrapper that makes it, not the agent
|
|
1629
|
+
// or the credential broker, what the OOM killer takes first (oom-priority.ts).
|
|
1630
|
+
...agentShellPrefixEnv(killFirstWrapper(), process.env),
|
|
1631
|
+
// The CLI clamps any Bash timeout to 10 minutes unless told otherwise;
|
|
1632
|
+
// the workspace's `inject-timeouts` hook gives a render up to 30, and at
|
|
1633
|
+
// the clamp the render was moved to the background mid-repair.
|
|
1634
|
+
BASH_MAX_TIMEOUT_MS: String(30 * 60_000),
|
|
1448
1635
|
// `citty` writes ANSI escapes into `baker --help` unconditionally —
|
|
1449
1636
|
// they are never TTY-gated, so every help read the agent does inside
|
|
1450
1637
|
// the sandbox pays ~5-10% of its tokens on colour codes it can't see.
|
|
@@ -1466,6 +1653,7 @@ export class BridgeJobExecutor {
|
|
|
1466
1653
|
...attempt.resumeOptions,
|
|
1467
1654
|
};
|
|
1468
1655
|
const query = this.sdk.query({ prompt: attempt.prompt, reminder: attempt.reminder, options });
|
|
1656
|
+
controls.reloadMcpServers = () => this.reloadMcpServers(job, query);
|
|
1469
1657
|
let queryClosed = false;
|
|
1470
1658
|
const closeQuery = () => {
|
|
1471
1659
|
if (queryClosed) {
|
|
@@ -1487,6 +1675,7 @@ export class BridgeJobExecutor {
|
|
|
1487
1675
|
throw error;
|
|
1488
1676
|
}
|
|
1489
1677
|
finally {
|
|
1678
|
+
controls.reloadMcpServers = undefined;
|
|
1490
1679
|
controls.abortSignal.removeEventListener("abort", closeQuery);
|
|
1491
1680
|
// Release the held-open input if the turn-end policy never fired (the
|
|
1492
1681
|
// stream ended on its own, or an error path closed the query). Idempotent.
|
|
@@ -1510,7 +1699,19 @@ export class BridgeJobExecutor {
|
|
|
1510
1699
|
// in-progress session.
|
|
1511
1700
|
async drainQuery(job, controls, state, query, closeQuery) {
|
|
1512
1701
|
const stallDetector = this.createStallDetector(job, controls, closeQuery);
|
|
1513
|
-
|
|
1702
|
+
// What the query had spent when its turn ended, read from the CLI — set
|
|
1703
|
+
// only when a task ran, and higher than the last result's figure when a
|
|
1704
|
+
// task kept working after it.
|
|
1705
|
+
let settledCostUsd = 0;
|
|
1706
|
+
// A reading that answers after the query drained (past its timeout) belongs
|
|
1707
|
+
// to a query already folded into the turn — and a retry reuses `state`.
|
|
1708
|
+
let drained = false;
|
|
1709
|
+
const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, wakeMs: this.turnEndWakeMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent, async () => {
|
|
1710
|
+
const reading = await query.usage?.();
|
|
1711
|
+
if (reading && !drained) {
|
|
1712
|
+
settledCostUsd = recordCumulativeUsage(state, reading);
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1514
1715
|
let costUsd = 0;
|
|
1515
1716
|
// Errors thrown while HANDLING a message (control-channel breaker, error
|
|
1516
1717
|
// results) are real verdicts and must surface as-is; only errors from the
|
|
@@ -1520,6 +1721,7 @@ export class BridgeJobExecutor {
|
|
|
1520
1721
|
for await (const msg of query) {
|
|
1521
1722
|
stallDetector.observeMessage(msg);
|
|
1522
1723
|
turnEnd.observeMessage(msg);
|
|
1724
|
+
controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
|
|
1523
1725
|
try {
|
|
1524
1726
|
costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);
|
|
1525
1727
|
}
|
|
@@ -1542,6 +1744,8 @@ export class BridgeJobExecutor {
|
|
|
1542
1744
|
throw error;
|
|
1543
1745
|
}
|
|
1544
1746
|
finally {
|
|
1747
|
+
drained = true;
|
|
1748
|
+
controls.setWorkInFlight?.(false);
|
|
1545
1749
|
stallDetector.dispose();
|
|
1546
1750
|
turnEnd.dispose();
|
|
1547
1751
|
await this.openPendingCastingAtTurnEnd(job, controls);
|
|
@@ -1549,7 +1753,11 @@ export class BridgeJobExecutor {
|
|
|
1549
1753
|
if (stallDetector.stalled() && controls.isActive()) {
|
|
1550
1754
|
throw new StreamStallError();
|
|
1551
1755
|
}
|
|
1552
|
-
|
|
1756
|
+
// Not for a Session review: it runs with every hook off and may not change anything.
|
|
1757
|
+
if (turnEnd.ranTasks() && controls.isActive() && !job.review) {
|
|
1758
|
+
await this.settleWorkspace();
|
|
1759
|
+
}
|
|
1760
|
+
return Math.max(costUsd, settledCostUsd);
|
|
1553
1761
|
}
|
|
1554
1762
|
/**
|
|
1555
1763
|
* Open a casting the turn recorded but never reached.
|
|
@@ -1606,6 +1814,7 @@ export class BridgeJobExecutor {
|
|
|
1606
1814
|
controls.pendingTagInputs.size > 0 ||
|
|
1607
1815
|
controls.pendingFlowInputs.size > 0 ||
|
|
1608
1816
|
controls.pendingConnectionInputs.size > 0 ||
|
|
1817
|
+
controls.pendingCredentialInputs.size > 0 ||
|
|
1609
1818
|
controls.pendingAvatarCastings.size > 0,
|
|
1610
1819
|
onStall: () => {
|
|
1611
1820
|
console.error(`[BridgeJob] no stream data for ${this.stallTimeoutMs}ms while awaiting the model — closing query for retry (thread ${job.threadId})`);
|
|
@@ -1642,17 +1851,7 @@ export class BridgeJobExecutor {
|
|
|
1642
1851
|
}
|
|
1643
1852
|
trackMainRequestUsage(state, msg);
|
|
1644
1853
|
if (msg.type === "result") {
|
|
1645
|
-
const
|
|
1646
|
-
state.observedCostUsd = own.costUsd;
|
|
1647
|
-
// A query's result `modelUsage` is cumulative (like `total_cost_usd`, which
|
|
1648
|
-
// we overwrite above), so keep the latest snapshot rather than summing —
|
|
1649
|
-
// it is folded into the turn totals when the query settles (see
|
|
1650
|
-
// `runQueryOnce`). Guard against a spuriously empty later result wiping a
|
|
1651
|
-
// real earlier one.
|
|
1652
|
-
const snapshot = queryModelUsage(own.modelUsage);
|
|
1653
|
-
if (snapshot.size > 0) {
|
|
1654
|
-
state.queryModelUsage = snapshot;
|
|
1655
|
-
}
|
|
1854
|
+
const ownCostUsd = recordCumulativeUsage(state, { totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage });
|
|
1656
1855
|
await this.publishContextUsage(job, state, msg.modelUsage);
|
|
1657
1856
|
// What a Session review returns. The SDK enforces the JSON schema, so this
|
|
1658
1857
|
// either parses against the findings contract or the review failed to
|
|
@@ -1669,7 +1868,7 @@ export class BridgeJobExecutor {
|
|
|
1669
1868
|
await this.clearIfDirty(job, controls, state, "result_error");
|
|
1670
1869
|
throw resultError;
|
|
1671
1870
|
}
|
|
1672
|
-
return
|
|
1871
|
+
return ownCostUsd;
|
|
1673
1872
|
}
|
|
1674
1873
|
if (isUserVisibleSdkMessage(msg)) {
|
|
1675
1874
|
state.sawUserVisibleSdkMessage = true;
|
|
@@ -2044,6 +2243,16 @@ export class BridgeJobExecutor {
|
|
|
2044
2243
|
// Strip any secret values the model tried to prefill; their names still reach
|
|
2045
2244
|
// the user as requested fields. The backend strips again.
|
|
2046
2245
|
const sanitizedRequests = sanitizeFlowRequests(requests);
|
|
2246
|
+
// A key the card does not render is dropped there without a word, and the
|
|
2247
|
+
// person retypes what the agent already knew — refuse it before the card opens.
|
|
2248
|
+
const stated = agentStatedFlowMessage(sanitizedRequests);
|
|
2249
|
+
if (stated) {
|
|
2250
|
+
return { behavior: "deny", message: stated };
|
|
2251
|
+
}
|
|
2252
|
+
const unknownPrefill = unknownFlowPrefillMessage(sanitizedRequests);
|
|
2253
|
+
if (unknownPrefill) {
|
|
2254
|
+
return { behavior: "deny", message: unknownPrefill };
|
|
2255
|
+
}
|
|
2047
2256
|
const posted = await this.relay.flowInputRequest(target, { toolUseId, requests: sanitizedRequests });
|
|
2048
2257
|
if (!posted) {
|
|
2049
2258
|
console.error(`[request_flow_input] failed to post flow-input-request for ${toolUseId} — denying to avoid a hang`);
|
|
@@ -2094,7 +2303,7 @@ export class BridgeJobExecutor {
|
|
|
2094
2303
|
if (connections.length === 0) {
|
|
2095
2304
|
return {
|
|
2096
2305
|
behavior: "deny",
|
|
2097
|
-
message: `request_connection requires \`connections: [...]\` with at least one entry { platform, reason }
|
|
2306
|
+
message: `request_connection requires \`connections: [...]\` with at least one entry: { platform, reason } where platform is one of ${CONNECTION_PLATFORMS.join(", ")}; { server: { name, url, auth?, keyHeader?, scope? }, reason } for a remote MCP server; or { app, reason } for a managed app from \`baker mcp catalog\`. Retry with that shape.`,
|
|
2098
2307
|
};
|
|
2099
2308
|
}
|
|
2100
2309
|
const posted = await this.relay.connectionInputRequest(target, { toolUseId, connections });
|
|
@@ -2116,7 +2325,43 @@ export class BridgeJobExecutor {
|
|
|
2116
2325
|
return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
|
|
2117
2326
|
}
|
|
2118
2327
|
void this.relay.connectionInputResolved(target);
|
|
2119
|
-
|
|
2328
|
+
// Loaded before the tool answers, so the model's next request already
|
|
2329
|
+
// carries the new tools. Only when an app or server came through: a
|
|
2330
|
+
// platform adds no tool, and reloading for nothing rewrites the tool list.
|
|
2331
|
+
const resolution = toolsConnected(decision.value)
|
|
2332
|
+
? withReload(decision.value, await controls.reloadMcpServers?.())
|
|
2333
|
+
: decision.value;
|
|
2334
|
+
return { behavior: "allow", updatedInput: { connections, resolution } };
|
|
2335
|
+
}
|
|
2336
|
+
async handleCredentialInputRequest(job, controls, input, toolUseId, signal) {
|
|
2337
|
+
const target = { threadId: job.threadId, turnId: job.turnId };
|
|
2338
|
+
const request = normalizeCredentialInputToolInput(input);
|
|
2339
|
+
if (!request) {
|
|
2340
|
+
return {
|
|
2341
|
+
behavior: "deny",
|
|
2342
|
+
message: "request_credential requires { label, reason, allowedHosts: [...] } (and optionally a lowercase `name`). Retry with that shape.",
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
const posted = await this.relay.credentialInputRequest(target, { toolUseId, ...request });
|
|
2346
|
+
if (!posted) {
|
|
2347
|
+
console.error(`[request_credential] failed to post credential-input-request for ${toolUseId} — denying`);
|
|
2348
|
+
return {
|
|
2349
|
+
behavior: "deny",
|
|
2350
|
+
message: "The credential request could not be delivered to the dashboard — retry request_credential in a moment.",
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
const decision = await this.awaitPromptDecision({
|
|
2354
|
+
label: "request_credential",
|
|
2355
|
+
signal,
|
|
2356
|
+
controls,
|
|
2357
|
+
pending: controls.pendingCredentialInputs,
|
|
2358
|
+
toolUseId,
|
|
2359
|
+
});
|
|
2360
|
+
if (decision.kind === "unanswered") {
|
|
2361
|
+
return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
|
|
2362
|
+
}
|
|
2363
|
+
void this.relay.credentialInputResolved(target);
|
|
2364
|
+
return { behavior: "allow", updatedInput: { ...request, resolution: decision.value } };
|
|
2120
2365
|
}
|
|
2121
2366
|
async handlePlanReview(job, controls, input, toolUseId, signal) {
|
|
2122
2367
|
const target = { threadId: job.threadId, turnId: job.turnId };
|