@koda-sl/baker-bridge 0.110.0-dev.6a86a7961 → 0.110.0-dev.6beff6a4b
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 +108 -65
- 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 +22 -10
- 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/attachments.d.ts +6 -1
- package/dist/hono/attachments.d.ts.map +1 -1
- package/dist/hono/attachments.js +9 -4
- package/dist/hono/attachments.js.map +1 -1
- package/dist/hono/attachments.test.js +5 -0
- package/dist/hono/attachments.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 +78 -13
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +333 -103
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +571 -190
- 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 +177 -12
- package/dist/hono/connection-input.d.ts.map +1 -1
- package/dist/hono/connection-input.js +167 -14
- package/dist/hono/connection-input.js.map +1 -1
- package/dist/hono/connection-input.test.js +112 -10
- 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 +64 -14
- package/dist/hono/flow-input.js.map +1 -1
- package/dist/hono/flow-input.test.js +86 -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.d.ts +0 -1
- package/dist/hono/session-continuity.d.ts.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/session-continuity.js +2 -5
- package/dist/hono/session-continuity.js.map +1 -1
- package/dist/hono/tag-fields.d.ts.map +1 -1
- package/dist/hono/tag-fields.js +4 -0
- package/dist/hono/tag-fields.js.map +1 -1
- package/dist/hono/tag-input.d.ts +125 -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 +6 -2
- package/dist/hono/tool-mentions.test.js.map +1 -1
- package/dist/hono/types.d.ts +15 -6
- 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 +15 -2
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +90 -59
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +94 -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 +10 -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/main-sync.d.ts +0 -46
- package/dist/hono/main-sync.d.ts.map +0 -1
- package/dist/hono/main-sync.js +0 -422
- package/dist/hono/main-sync.js.map +0 -1
- package/dist/hono/main-sync.test.d.ts +0 -2
- package/dist/hono/main-sync.test.d.ts.map +0 -1
- package/dist/hono/main-sync.test.js +0 -288
- package/dist/hono/main-sync.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,15 +896,21 @@ 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
|
}
|
|
784
905
|
},
|
|
785
|
-
maybeEndAfter: (msg) => {
|
|
906
|
+
maybeEndAfter: (msg, refused) => {
|
|
786
907
|
if (inputEnded) {
|
|
787
908
|
return;
|
|
788
909
|
}
|
|
789
|
-
|
|
910
|
+
// A refusal's error result is not thrown (the refusal already decided how
|
|
911
|
+
// the turn ends), so it has to end input like a clean one or the CLI idles
|
|
912
|
+
// on stdin until the session watchdog.
|
|
913
|
+
if (msg.type === "result" && (!msg.is_error || refused)) {
|
|
790
914
|
cleanResultSeen = true;
|
|
791
915
|
if (hasInFlightWork()) {
|
|
792
916
|
console.error(`[BridgeJob] result received with ${inFlightTasks.size} task(s) and ${backgroundTaskCount} background command(s) in flight — holding the turn open (thread ${job.threadId})`);
|
|
@@ -800,10 +924,13 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
|
|
|
800
924
|
// second result) would strand the turn until the watchdog. An emptied
|
|
801
925
|
// background_tasks_changed level counts as a settle the same way.
|
|
802
926
|
if (lastWasTaskLifecycle && cleanResultSeen && !hasInFlightWork()) {
|
|
803
|
-
scheduleEnd();
|
|
927
|
+
scheduleEnd(timings.wakeMs);
|
|
804
928
|
}
|
|
805
929
|
},
|
|
930
|
+
hasInFlightWork,
|
|
931
|
+
ranTasks: () => sawAnyTask,
|
|
806
932
|
dispose: () => {
|
|
933
|
+
disposed = true;
|
|
807
934
|
if (settleTimer) {
|
|
808
935
|
clearTimeout(settleTimer);
|
|
809
936
|
settleTimer = undefined;
|
|
@@ -890,12 +1017,14 @@ export class BridgeJobExecutor {
|
|
|
890
1017
|
stallCheckIntervalMs;
|
|
891
1018
|
recordTurnEvent;
|
|
892
1019
|
turnEndSettleMs;
|
|
1020
|
+
turnEndWakeMs;
|
|
893
1021
|
turnEndExitGuardMs;
|
|
894
1022
|
now;
|
|
895
1023
|
readUsageBaseline;
|
|
896
1024
|
stalledCliExitMs;
|
|
897
1025
|
readFlowTree;
|
|
898
|
-
|
|
1026
|
+
settleWorkspace;
|
|
1027
|
+
judge;
|
|
899
1028
|
constructor(dependencies) {
|
|
900
1029
|
this.sdk = dependencies.sdk;
|
|
901
1030
|
this.relay = dependencies.relay;
|
|
@@ -908,15 +1037,17 @@ export class BridgeJobExecutor {
|
|
|
908
1037
|
this.stallCheckIntervalMs = dependencies.stallCheckIntervalMs ?? DEFAULT_STALL_CHECK_INTERVAL_MS;
|
|
909
1038
|
this.recordTurnEvent = dependencies.recordTurnEvent ?? recordTurnEvent;
|
|
910
1039
|
this.turnEndSettleMs = dependencies.turnEndSettleMs ?? DEFAULT_TURN_END_SETTLE_MS;
|
|
1040
|
+
this.turnEndWakeMs = dependencies.turnEndWakeMs ?? DEFAULT_TURN_END_WAKE_MS;
|
|
911
1041
|
this.turnEndExitGuardMs = dependencies.turnEndExitGuardMs ?? DEFAULT_TURN_END_EXIT_GUARD_MS;
|
|
912
1042
|
this.now = dependencies.now ?? Date.now;
|
|
913
1043
|
this.readUsageBaseline =
|
|
914
1044
|
dependencies.readUsageBaseline ??
|
|
915
1045
|
((sessionId) => readUsageBaseline(sessionId, process.env.CLAUDE_CONFIG_DIR || resolve(process.env.HOME || homedir(), ".claude")));
|
|
916
1046
|
this.stalledCliExitMs = dependencies.stalledCliExitMs ?? STALLED_CLI_EXIT_MS;
|
|
1047
|
+
this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
|
|
917
1048
|
this.readFlowTree =
|
|
918
1049
|
dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
|
|
919
|
-
this.
|
|
1050
|
+
this.judge = dependencies.judge ?? jevJudge(process.env.OPENROUTER_API_KEY);
|
|
920
1051
|
}
|
|
921
1052
|
async execute(job, controls) {
|
|
922
1053
|
const state = createBridgeJobState();
|
|
@@ -929,10 +1060,12 @@ export class BridgeJobExecutor {
|
|
|
929
1060
|
return { kind: "cancelled", costUsd: 0, model: state.model };
|
|
930
1061
|
}
|
|
931
1062
|
const loaded = await this.loadTrustedBoundary(job, controls);
|
|
1063
|
+
if (loaded?.kind !== "clean") {
|
|
1064
|
+
request = await this.withContinuedAttachments(job, request);
|
|
1065
|
+
}
|
|
932
1066
|
attempt = prepareSessionAttempt({
|
|
933
1067
|
requestPrompt: request.prompt,
|
|
934
1068
|
reminder: request.reminder,
|
|
935
|
-
recoveryReminder: request.recoveryReminder,
|
|
936
1069
|
history: job.history,
|
|
937
1070
|
loaded,
|
|
938
1071
|
leadingCommand: request.leadingCommand,
|
|
@@ -963,12 +1096,6 @@ export class BridgeJobExecutor {
|
|
|
963
1096
|
await this.clearIfDirty(job, controls, state, classifyDirtyReason(error, controls));
|
|
964
1097
|
return this.errorOutcome(job, controls, state, this.resultWithObservedCost(result, state), error);
|
|
965
1098
|
}
|
|
966
|
-
finally {
|
|
967
|
-
// A review only reads the finished Session's workspace.
|
|
968
|
-
if (!job.review) {
|
|
969
|
-
await this.mainSync?.afterTurn();
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
1099
|
}
|
|
973
1100
|
async loadTrustedBoundary(job, controls) {
|
|
974
1101
|
const loaded = await this.sessionStore.load(job.threadId);
|
|
@@ -990,34 +1117,21 @@ export class BridgeJobExecutor {
|
|
|
990
1117
|
// skill) must stay the leading token, so they skip speaker attribution and
|
|
991
1118
|
// reference context — otherwise the prepended lines push the command off the
|
|
992
1119
|
// front and it lands as ordinary message text the model just echoes.
|
|
993
|
-
const
|
|
994
|
-
const verbatim = bridgeCommand ??
|
|
1120
|
+
const verbatim = verbatimSlashCommand(job.prompt) ??
|
|
995
1121
|
leadingSkillCommand(job.prompt, (await readSessionSkills(bridgeRuntimeFiles.projectRoot())) ?? []);
|
|
996
1122
|
let requestPrompt = verbatim ?? applyCurrentSenderAttribution(job.prompt, job.currentSender);
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
? ((await this.mainSync?.beforeTurn(job.threadId, job.publishedElsewhere ?? [], {
|
|
1003
|
-
withNote: verbatim === null,
|
|
1004
|
-
})) ?? {})
|
|
1005
|
-
: {};
|
|
1006
|
-
const viewing = verbatim === null ? currentReferenceReminder(job.currentReference) : undefined;
|
|
1007
|
-
const reminder = joinReminders(notes.resumed, viewing);
|
|
1008
|
-
const recoveryReminder = joinReminders(notes.recovered, viewing);
|
|
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;
|
|
1009
1128
|
if (job.attachments && job.attachments.length > 0) {
|
|
1010
|
-
requestPrompt += await this.prepareAttachments(job.attachments);
|
|
1129
|
+
requestPrompt += await this.prepareAttachments(job.attachments, "message");
|
|
1011
1130
|
}
|
|
1012
1131
|
if (!controls.isActive()) {
|
|
1013
1132
|
return null;
|
|
1014
1133
|
}
|
|
1015
|
-
return {
|
|
1016
|
-
prompt: requestPrompt,
|
|
1017
|
-
...(reminder ? { reminder } : {}),
|
|
1018
|
-
...(recoveryReminder ? { recoveryReminder } : {}),
|
|
1019
|
-
leadingCommand: verbatim !== null,
|
|
1020
|
-
};
|
|
1134
|
+
return { prompt: requestPrompt, ...(reminder ? { reminder } : {}), leadingCommand: verbatim !== null };
|
|
1021
1135
|
}
|
|
1022
1136
|
/**
|
|
1023
1137
|
* The reviewer's prompt: the brief Convex wrote, with the evidence block this
|
|
@@ -1216,6 +1330,18 @@ export class BridgeJobExecutor {
|
|
|
1216
1330
|
});
|
|
1217
1331
|
}
|
|
1218
1332
|
}
|
|
1333
|
+
/**
|
|
1334
|
+
* The files of the request a late reply continues, for a turn that does not
|
|
1335
|
+
* resume the Session that received them with the request. Its history names
|
|
1336
|
+
* them but cannot open them.
|
|
1337
|
+
*/
|
|
1338
|
+
async withContinuedAttachments(job, request) {
|
|
1339
|
+
if (!job.continuedAttachments?.length || request.leadingCommand) {
|
|
1340
|
+
return request;
|
|
1341
|
+
}
|
|
1342
|
+
const suffix = await this.prepareAttachments(job.continuedAttachments, "continued_request");
|
|
1343
|
+
return { ...request, prompt: request.prompt + suffix };
|
|
1344
|
+
}
|
|
1219
1345
|
async invalidResumeFallbackAttempt(job, controls, request, attempt, state, error) {
|
|
1220
1346
|
if (!attempt.usedBoundary ||
|
|
1221
1347
|
state.sawUserVisibleSdkMessage ||
|
|
@@ -1224,6 +1350,7 @@ export class BridgeJobExecutor {
|
|
|
1224
1350
|
return null;
|
|
1225
1351
|
}
|
|
1226
1352
|
await this.sessionStore.clear(job.threadId, { runToken: controls.runToken, reason: "sdk_error" });
|
|
1353
|
+
const rebuilt = await this.withContinuedAttachments(job, request);
|
|
1227
1354
|
void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydrated_invalid_resume", {
|
|
1228
1355
|
historyEntryCount: job.history?.entries.length ?? 0,
|
|
1229
1356
|
omittedEntryCount: job.history?.omittedEntryCount ?? 0,
|
|
@@ -1232,12 +1359,12 @@ export class BridgeJobExecutor {
|
|
|
1232
1359
|
});
|
|
1233
1360
|
// A leading command stays bare here too: a history preamble in front of it
|
|
1234
1361
|
// would turn it into plain text (see `prepareSessionAttempt`).
|
|
1235
|
-
if (
|
|
1236
|
-
return { prompt:
|
|
1362
|
+
if (rebuilt.leadingCommand) {
|
|
1363
|
+
return { prompt: rebuilt.prompt, attemptedRehydration: false, resumeOptions: {} };
|
|
1237
1364
|
}
|
|
1238
1365
|
return {
|
|
1239
|
-
prompt: buildRehydratedPrompt(
|
|
1240
|
-
...(
|
|
1366
|
+
prompt: buildRehydratedPrompt(rebuilt.prompt, job.history),
|
|
1367
|
+
...(rebuilt.reminder ? { reminder: rebuilt.reminder } : {}),
|
|
1241
1368
|
attemptedRehydration: true,
|
|
1242
1369
|
resumeOptions: {},
|
|
1243
1370
|
};
|
|
@@ -1254,6 +1381,39 @@ export class BridgeJobExecutor {
|
|
|
1254
1381
|
return undefined;
|
|
1255
1382
|
}
|
|
1256
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
|
+
}
|
|
1257
1417
|
reportMcpFailures(job, msg) {
|
|
1258
1418
|
for (const scope of composioScopesToInvalidate(msg)) {
|
|
1259
1419
|
void reportMcpInvalid(job.threadId, { scope }, job.currentSender?.userId);
|
|
@@ -1273,7 +1433,7 @@ export class BridgeJobExecutor {
|
|
|
1273
1433
|
* handlers — except on an unattended turn, where there is nobody to answer and
|
|
1274
1434
|
* every one of them is refused up front (see `UNATTENDED_PROMPT_MESSAGE`).
|
|
1275
1435
|
*/
|
|
1276
|
-
routeToolUse(job, controls, toolName, input, opts) {
|
|
1436
|
+
async routeToolUse(job, controls, toolName, input, opts) {
|
|
1277
1437
|
const refusedShell = reviewShellRefusal(job, toolName, input);
|
|
1278
1438
|
if (refusedShell) {
|
|
1279
1439
|
return Promise.resolve({ behavior: "deny", message: refusedShell });
|
|
@@ -1289,14 +1449,9 @@ export class BridgeJobExecutor {
|
|
|
1289
1449
|
});
|
|
1290
1450
|
return Promise.resolve({ behavior: "deny", message: UNATTENDED_PROMPT_MESSAGE });
|
|
1291
1451
|
}
|
|
1292
|
-
const malformed = refuseMalformedCall(toolName, input, job.prompt, userTurnsBefore(job));
|
|
1452
|
+
const malformed = await refuseMalformedCall(toolName, input, this.judge, job.prompt, userTurnsBefore(job));
|
|
1293
1453
|
if (malformed)
|
|
1294
|
-
return
|
|
1295
|
-
const clash = toolName === "Write" && typeof input?.file_path === "string"
|
|
1296
|
-
? (this.mainSync?.refuseWrite(job.threadId, input.file_path) ?? null)
|
|
1297
|
-
: null;
|
|
1298
|
-
if (clash)
|
|
1299
|
-
return Promise.resolve({ behavior: "deny", message: clash });
|
|
1454
|
+
return { behavior: "deny", message: malformed };
|
|
1300
1455
|
// Cast what is owed FIRST, whatever comes next — including another create. Checked
|
|
1301
1456
|
// the other way round, a second create (the agent retrying after a rejected flag,
|
|
1302
1457
|
// which is what happens in practice) would overwrite the handle and the first
|
|
@@ -1349,6 +1504,15 @@ export class BridgeJobExecutor {
|
|
|
1349
1504
|
if (toolName === CONNECTION_INPUT_TOOL_NAME) {
|
|
1350
1505
|
return this.handleConnectionInputRequest(job, controls, input, opts.toolUseID, opts.signal);
|
|
1351
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
|
+
}
|
|
1352
1516
|
if (toolName === AVATAR_CASTING_TOOL_NAME) {
|
|
1353
1517
|
return this.handleAvatarCastingRequest(job, controls, input, opts.toolUseID, opts.signal);
|
|
1354
1518
|
}
|
|
@@ -1387,6 +1551,10 @@ export class BridgeJobExecutor {
|
|
|
1387
1551
|
// warning to the user. Pinning it takes that threshold out of remote control.
|
|
1388
1552
|
// See `autoCompactWindowForModel` for the full chain and its ceiling.
|
|
1389
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",
|
|
1390
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.
|
|
1391
1559
|
...(process.env.BAKER_CHAT_ID
|
|
1392
1560
|
? {
|
|
@@ -1411,19 +1579,16 @@ export class BridgeJobExecutor {
|
|
|
1411
1579
|
// MUST stay a single constant. The resolved effort is rendered into the
|
|
1412
1580
|
// prompt, so changing it between requests invalidates every cache
|
|
1413
1581
|
// breakpoint above it — on a session carrying hundreds of thousands of
|
|
1414
|
-
// tokens that re-write costs far more than any per-turn tuning saves.
|
|
1415
|
-
//
|
|
1416
|
-
//
|
|
1582
|
+
// tokens that re-write costs far more than any per-turn tuning saves.
|
|
1583
|
+
// Vary it per message only once the route accepts per-message effort
|
|
1584
|
+
// (see `BEDROCK_SERVABLE_CAPABILITIES`); prose is no substitute.
|
|
1417
1585
|
effort: "medium",
|
|
1418
1586
|
cwd: process.cwd(),
|
|
1419
1587
|
settingSources: ["project"],
|
|
1420
1588
|
// `baker_ui` hosts in-process interactive dashboard tools (secure tag
|
|
1421
1589
|
// form). Always present; HTTP (Composio) servers merge alongside it.
|
|
1422
|
-
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer() },
|
|
1423
|
-
|
|
1424
|
-
// `settings` to the CLI whenever it is present, so an empty object still
|
|
1425
|
-
// becomes a `--settings {}` flag tier for the CLI to parse.
|
|
1426
|
-
...(Object.keys(settings).length > 0 ? { settings } : {}),
|
|
1590
|
+
mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
|
|
1591
|
+
settings,
|
|
1427
1592
|
includePartialMessages: true,
|
|
1428
1593
|
// Only the workspace's own skills. Omitting the option is NOT "skills
|
|
1429
1594
|
// off" — the CLI's defaults still load its built-in developer skills
|
|
@@ -1432,8 +1597,11 @@ export class BridgeJobExecutor {
|
|
|
1432
1597
|
// the workspace could not be read; omit rather than allowlist nothing.
|
|
1433
1598
|
...(skills ? { skills } : {}),
|
|
1434
1599
|
disallowedTools: disallowedToolsFor(job),
|
|
1435
|
-
...reviewQueryOptions(job),
|
|
1436
|
-
|
|
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",
|
|
1437
1605
|
canUseTool: (toolName, input, opts) => this.routeToolUse(job, controls, toolName, input, opts),
|
|
1438
1606
|
// No `fallbackModel`, and no server-side refusal `fallbacks` in the extra
|
|
1439
1607
|
// body: the CLI's refusal fallback is gated on a first-party base URL (see
|
|
@@ -1457,6 +1625,13 @@ export class BridgeJobExecutor {
|
|
|
1457
1625
|
// system-reminder repeatedly pushed the agent toward that ephemeral
|
|
1458
1626
|
// backlog instead of the product's own Tasks (`baker actions`).
|
|
1459
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),
|
|
1460
1635
|
// `citty` writes ANSI escapes into `baker --help` unconditionally —
|
|
1461
1636
|
// they are never TTY-gated, so every help read the agent does inside
|
|
1462
1637
|
// the sandbox pays ~5-10% of its tokens on colour codes it can't see.
|
|
@@ -1478,6 +1653,7 @@ export class BridgeJobExecutor {
|
|
|
1478
1653
|
...attempt.resumeOptions,
|
|
1479
1654
|
};
|
|
1480
1655
|
const query = this.sdk.query({ prompt: attempt.prompt, reminder: attempt.reminder, options });
|
|
1656
|
+
controls.reloadMcpServers = () => this.reloadMcpServers(job, query);
|
|
1481
1657
|
let queryClosed = false;
|
|
1482
1658
|
const closeQuery = () => {
|
|
1483
1659
|
if (queryClosed) {
|
|
@@ -1499,6 +1675,7 @@ export class BridgeJobExecutor {
|
|
|
1499
1675
|
throw error;
|
|
1500
1676
|
}
|
|
1501
1677
|
finally {
|
|
1678
|
+
controls.reloadMcpServers = undefined;
|
|
1502
1679
|
controls.abortSignal.removeEventListener("abort", closeQuery);
|
|
1503
1680
|
// Release the held-open input if the turn-end policy never fired (the
|
|
1504
1681
|
// stream ended on its own, or an error path closed the query). Idempotent.
|
|
@@ -1522,7 +1699,19 @@ export class BridgeJobExecutor {
|
|
|
1522
1699
|
// in-progress session.
|
|
1523
1700
|
async drainQuery(job, controls, state, query, closeQuery) {
|
|
1524
1701
|
const stallDetector = this.createStallDetector(job, controls, closeQuery);
|
|
1525
|
-
|
|
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
|
+
});
|
|
1526
1715
|
let costUsd = 0;
|
|
1527
1716
|
// Errors thrown while HANDLING a message (control-channel breaker, error
|
|
1528
1717
|
// results) are real verdicts and must surface as-is; only errors from the
|
|
@@ -1532,6 +1721,7 @@ export class BridgeJobExecutor {
|
|
|
1532
1721
|
for await (const msg of query) {
|
|
1533
1722
|
stallDetector.observeMessage(msg);
|
|
1534
1723
|
turnEnd.observeMessage(msg);
|
|
1724
|
+
controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
|
|
1535
1725
|
try {
|
|
1536
1726
|
costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);
|
|
1537
1727
|
}
|
|
@@ -1539,7 +1729,7 @@ export class BridgeJobExecutor {
|
|
|
1539
1729
|
consumeError = true;
|
|
1540
1730
|
throw error;
|
|
1541
1731
|
}
|
|
1542
|
-
turnEnd.maybeEndAfter(msg);
|
|
1732
|
+
turnEnd.maybeEndAfter(msg, state.refusedWithoutFallback === true);
|
|
1543
1733
|
if (!controls.isActive()) {
|
|
1544
1734
|
closeQuery();
|
|
1545
1735
|
break;
|
|
@@ -1554,6 +1744,8 @@ export class BridgeJobExecutor {
|
|
|
1554
1744
|
throw error;
|
|
1555
1745
|
}
|
|
1556
1746
|
finally {
|
|
1747
|
+
drained = true;
|
|
1748
|
+
controls.setWorkInFlight?.(false);
|
|
1557
1749
|
stallDetector.dispose();
|
|
1558
1750
|
turnEnd.dispose();
|
|
1559
1751
|
await this.openPendingCastingAtTurnEnd(job, controls);
|
|
@@ -1561,7 +1753,11 @@ export class BridgeJobExecutor {
|
|
|
1561
1753
|
if (stallDetector.stalled() && controls.isActive()) {
|
|
1562
1754
|
throw new StreamStallError();
|
|
1563
1755
|
}
|
|
1564
|
-
|
|
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);
|
|
1565
1761
|
}
|
|
1566
1762
|
/**
|
|
1567
1763
|
* Open a casting the turn recorded but never reached.
|
|
@@ -1618,6 +1814,7 @@ export class BridgeJobExecutor {
|
|
|
1618
1814
|
controls.pendingTagInputs.size > 0 ||
|
|
1619
1815
|
controls.pendingFlowInputs.size > 0 ||
|
|
1620
1816
|
controls.pendingConnectionInputs.size > 0 ||
|
|
1817
|
+
controls.pendingCredentialInputs.size > 0 ||
|
|
1621
1818
|
controls.pendingAvatarCastings.size > 0,
|
|
1622
1819
|
onStall: () => {
|
|
1623
1820
|
console.error(`[BridgeJob] no stream data for ${this.stallTimeoutMs}ms while awaiting the model — closing query for retry (thread ${job.threadId})`);
|
|
@@ -1654,17 +1851,7 @@ export class BridgeJobExecutor {
|
|
|
1654
1851
|
}
|
|
1655
1852
|
trackMainRequestUsage(state, msg);
|
|
1656
1853
|
if (msg.type === "result") {
|
|
1657
|
-
const
|
|
1658
|
-
state.observedCostUsd = own.costUsd;
|
|
1659
|
-
// A query's result `modelUsage` is cumulative (like `total_cost_usd`, which
|
|
1660
|
-
// we overwrite above), so keep the latest snapshot rather than summing —
|
|
1661
|
-
// it is folded into the turn totals when the query settles (see
|
|
1662
|
-
// `runQueryOnce`). Guard against a spuriously empty later result wiping a
|
|
1663
|
-
// real earlier one.
|
|
1664
|
-
const snapshot = queryModelUsage(own.modelUsage);
|
|
1665
|
-
if (snapshot.size > 0) {
|
|
1666
|
-
state.queryModelUsage = snapshot;
|
|
1667
|
-
}
|
|
1854
|
+
const ownCostUsd = recordCumulativeUsage(state, { totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage });
|
|
1668
1855
|
await this.publishContextUsage(job, state, msg.modelUsage);
|
|
1669
1856
|
// What a Session review returns. The SDK enforces the JSON schema, so this
|
|
1670
1857
|
// either parses against the findings contract or the review failed to
|
|
@@ -1681,7 +1868,7 @@ export class BridgeJobExecutor {
|
|
|
1681
1868
|
await this.clearIfDirty(job, controls, state, "result_error");
|
|
1682
1869
|
throw resultError;
|
|
1683
1870
|
}
|
|
1684
|
-
return
|
|
1871
|
+
return ownCostUsd;
|
|
1685
1872
|
}
|
|
1686
1873
|
if (isUserVisibleSdkMessage(msg)) {
|
|
1687
1874
|
state.sawUserVisibleSdkMessage = true;
|
|
@@ -2056,6 +2243,16 @@ export class BridgeJobExecutor {
|
|
|
2056
2243
|
// Strip any secret values the model tried to prefill; their names still reach
|
|
2057
2244
|
// the user as requested fields. The backend strips again.
|
|
2058
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
|
+
}
|
|
2059
2256
|
const posted = await this.relay.flowInputRequest(target, { toolUseId, requests: sanitizedRequests });
|
|
2060
2257
|
if (!posted) {
|
|
2061
2258
|
console.error(`[request_flow_input] failed to post flow-input-request for ${toolUseId} — denying to avoid a hang`);
|
|
@@ -2106,7 +2303,7 @@ export class BridgeJobExecutor {
|
|
|
2106
2303
|
if (connections.length === 0) {
|
|
2107
2304
|
return {
|
|
2108
2305
|
behavior: "deny",
|
|
2109
|
-
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.`,
|
|
2110
2307
|
};
|
|
2111
2308
|
}
|
|
2112
2309
|
const posted = await this.relay.connectionInputRequest(target, { toolUseId, connections });
|
|
@@ -2128,7 +2325,43 @@ export class BridgeJobExecutor {
|
|
|
2128
2325
|
return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
|
|
2129
2326
|
}
|
|
2130
2327
|
void this.relay.connectionInputResolved(target);
|
|
2131
|
-
|
|
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 } };
|
|
2132
2365
|
}
|
|
2133
2366
|
async handlePlanReview(job, controls, input, toolUseId, signal) {
|
|
2134
2367
|
const target = { threadId: job.threadId, turnId: job.turnId };
|
|
@@ -2294,7 +2527,4 @@ function userTurnsBefore(job) {
|
|
|
2294
2527
|
.trim();
|
|
2295
2528
|
return said || undefined;
|
|
2296
2529
|
}
|
|
2297
|
-
function joinReminders(...blocks) {
|
|
2298
|
-
return blocks.filter((block) => block !== undefined).join("\n\n") || undefined;
|
|
2299
|
-
}
|
|
2300
2530
|
//# sourceMappingURL=bridge-job.js.map
|