@autohq/cli 0.1.287 → 0.1.289
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/dist/agent-bridge.js +146 -57
- package/dist/index.js +147 -58
- package/package.json +1 -1
package/dist/agent-bridge.js
CHANGED
|
@@ -23384,7 +23384,7 @@ Object.assign(lookup, {
|
|
|
23384
23384
|
// package.json
|
|
23385
23385
|
var package_default = {
|
|
23386
23386
|
name: "@autohq/cli",
|
|
23387
|
-
version: "0.1.
|
|
23387
|
+
version: "0.1.289",
|
|
23388
23388
|
license: "SEE LICENSE IN README.md",
|
|
23389
23389
|
publishConfig: {
|
|
23390
23390
|
access: "public"
|
|
@@ -28362,6 +28362,9 @@ var ProjectTemplateSubscriptionSchema = external_exports.object({
|
|
|
28362
28362
|
});
|
|
28363
28363
|
|
|
28364
28364
|
// ../../packages/schemas/src/temporal.ts
|
|
28365
|
+
var SessionCheckTimeoutWorkflowInputSchema = external_exports.object({
|
|
28366
|
+
checkId: external_exports.string().trim().min(1)
|
|
28367
|
+
});
|
|
28365
28368
|
var ResourceReconciliationScopeSchema = external_exports.object({
|
|
28366
28369
|
organizationId: external_exports.string().trim().min(1),
|
|
28367
28370
|
projectId: external_exports.string().trim().min(1).nullable()
|
|
@@ -36725,25 +36728,11 @@ var AgentBridgeOutputBuffer = class {
|
|
|
36725
36728
|
);
|
|
36726
36729
|
if (!completedMessage || !completedMessage.id.trim()) {
|
|
36727
36730
|
if (isTerminalUiMessageChunk(projection.chunk)) {
|
|
36728
|
-
|
|
36729
|
-
if (statusText) {
|
|
36730
|
-
await this.enqueueProjectionAndDrain(context, {
|
|
36731
|
-
type: "entry",
|
|
36732
|
-
entry: {
|
|
36733
|
-
role: "system",
|
|
36734
|
-
kind: "status",
|
|
36735
|
-
status: terminalEntryStatus(projection),
|
|
36736
|
-
...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
|
|
36737
|
-
...projection.usage ? { usage: projection.usage } : {},
|
|
36738
|
-
content: {
|
|
36739
|
-
parts: [{ type: "text", text: statusText }]
|
|
36740
|
-
}
|
|
36741
|
-
}
|
|
36742
|
-
});
|
|
36743
|
-
}
|
|
36731
|
+
await this.emitTerminalStatusEntry(context, projection);
|
|
36744
36732
|
}
|
|
36745
36733
|
return;
|
|
36746
36734
|
}
|
|
36735
|
+
const turnFailed = projection.turnStatus === "failed";
|
|
36747
36736
|
this.outputSeq += 1;
|
|
36748
36737
|
const completedAt = now2();
|
|
36749
36738
|
const completedOutput = buildUiMessageCompletedOutputEnvelope({
|
|
@@ -36751,13 +36740,35 @@ var AgentBridgeOutputBuffer = class {
|
|
|
36751
36740
|
outputSeq: this.outputSeq,
|
|
36752
36741
|
message: completedMessage,
|
|
36753
36742
|
status: terminalEntryStatus(projection),
|
|
36754
|
-
turnStatus: projection.turnStatus,
|
|
36755
|
-
usage: projection.usage,
|
|
36743
|
+
turnStatus: turnFailed ? void 0 : projection.turnStatus,
|
|
36744
|
+
usage: turnFailed ? void 0 : projection.usage,
|
|
36756
36745
|
createdAt: completedAt,
|
|
36757
36746
|
completedAt
|
|
36758
36747
|
});
|
|
36759
36748
|
this.enqueueOutput(completedOutput);
|
|
36760
36749
|
await this.drainPendingOutputs();
|
|
36750
|
+
if (turnFailed) {
|
|
36751
|
+
await this.emitTerminalStatusEntry(context, projection);
|
|
36752
|
+
}
|
|
36753
|
+
}
|
|
36754
|
+
async emitTerminalStatusEntry(context, projection) {
|
|
36755
|
+
const statusText = terminalStatusText(projection);
|
|
36756
|
+
if (!statusText) {
|
|
36757
|
+
return;
|
|
36758
|
+
}
|
|
36759
|
+
await this.enqueueProjectionAndDrain(context, {
|
|
36760
|
+
type: "entry",
|
|
36761
|
+
entry: {
|
|
36762
|
+
role: "system",
|
|
36763
|
+
kind: "status",
|
|
36764
|
+
status: terminalEntryStatus(projection),
|
|
36765
|
+
...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
|
|
36766
|
+
...projection.usage ? { usage: projection.usage } : {},
|
|
36767
|
+
content: {
|
|
36768
|
+
parts: [{ type: "text", text: statusText }]
|
|
36769
|
+
}
|
|
36770
|
+
}
|
|
36771
|
+
});
|
|
36761
36772
|
}
|
|
36762
36773
|
async emitLiveUiMessageChunk(context, chunk) {
|
|
36763
36774
|
this.outputSeq += 1;
|
|
@@ -57340,6 +57351,12 @@ var claudeAgentBridgeRuntime = {
|
|
|
57340
57351
|
var claudeAgentSdkRuntime = {
|
|
57341
57352
|
startup: (input) => aj$(input)
|
|
57342
57353
|
};
|
|
57354
|
+
var ClaudeMcpRegistryEmptyError = class extends Error {
|
|
57355
|
+
constructor(detail) {
|
|
57356
|
+
super(`MCP tool registry came up empty at session start: ${detail}`);
|
|
57357
|
+
this.name = "ClaudeMcpRegistryEmptyError";
|
|
57358
|
+
}
|
|
57359
|
+
};
|
|
57343
57360
|
var ClaudeAgentBridgeSessionImpl = class {
|
|
57344
57361
|
input;
|
|
57345
57362
|
inputQueue;
|
|
@@ -57729,17 +57746,20 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57729
57746
|
}
|
|
57730
57747
|
// Why this gate is correct without a provider-backed test (it relies on real
|
|
57731
57748
|
// SDK mcpServerStatus() timing that fakes cannot prove):
|
|
57732
|
-
// - Safe-degrade: pollMcpRegistration never rejects — on
|
|
57733
|
-
// status-read failure, or a closed session it resolves, so
|
|
57734
|
-
//
|
|
57735
|
-
//
|
|
57749
|
+
// - Safe-degrade on uncertainty: pollMcpRegistration never rejects — on
|
|
57750
|
+
// timeout, a status-read failure, or a closed session it resolves, so an
|
|
57751
|
+
// *unconfirmed* registry still degrades to today's immediate injection.
|
|
57752
|
+
// The one fail-closed exit is the confirmed-empty registry: every
|
|
57753
|
+
// configured server settled and none is serving tools, which is exactly
|
|
57754
|
+
// the state that produced silent toolless runs in production (FRA-3458).
|
|
57736
57755
|
// - Provider-path validation is deferred deliberately: the failure is the
|
|
57737
57756
|
// intermittent live-prod race on the chief-of-staff singleton (a real
|
|
57738
57757
|
// runtime restart dispatching turn-1 while real mcp__* servers reconnect),
|
|
57739
57758
|
// which cannot be reproduced deterministically without live credentials
|
|
57740
57759
|
// and a forced restart racing reconnect.
|
|
57741
57760
|
// - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
|
|
57742
|
-
// _ready/_timeout diagnostics on the next real restart, not a CI
|
|
57761
|
+
// _ready/_timeout/_empty diagnostics on the next real restart, not a CI
|
|
57762
|
+
// fake.
|
|
57743
57763
|
//
|
|
57744
57764
|
// Block until the freshly-warmed query's MCP servers have left `pending`, so
|
|
57745
57765
|
// the first turn's prompt is built with the mcp__* tools registered. Memoized
|
|
@@ -57763,15 +57783,22 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57763
57783
|
promise: this.pollMcpRegistration(query)
|
|
57764
57784
|
};
|
|
57765
57785
|
}
|
|
57766
|
-
await this.mcpRegistrationGate.promise;
|
|
57786
|
+
const result = await this.mcpRegistrationGate.promise;
|
|
57787
|
+
if (result.outcome !== "empty") {
|
|
57788
|
+
return;
|
|
57789
|
+
}
|
|
57790
|
+
this.close();
|
|
57791
|
+
throw new ClaudeMcpRegistryEmptyError(result.detail);
|
|
57767
57792
|
}
|
|
57768
57793
|
// Poll the live query's MCP server status until no configured server is still
|
|
57769
57794
|
// `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
|
|
57770
57795
|
// (connected/failed/needs-auth/disabled) all count as settled, so a server
|
|
57771
57796
|
// that cannot connect in this runtime (e.g. an interactively-authed server in
|
|
57772
|
-
// a headless sandbox) never holds the gate to the timeout.
|
|
57797
|
+
// a headless sandbox) never holds the gate to the timeout. A settled state
|
|
57798
|
+
// where no configured server is serving tools resolves "empty" — the caller
|
|
57799
|
+
// fails the send rather than injecting a toolless turn. Never rejects:
|
|
57773
57800
|
// status-read failure, timeout, or a closed session all resolve so the caller
|
|
57774
|
-
// degrades to immediate injection.
|
|
57801
|
+
// degrades to immediate injection when the registry state is uncertain.
|
|
57775
57802
|
async pollMcpRegistration(query) {
|
|
57776
57803
|
const configuredNames = Object.keys(this.options.mcpServers ?? {});
|
|
57777
57804
|
const startedAt = Date.now();
|
|
@@ -57781,7 +57808,7 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57781
57808
|
);
|
|
57782
57809
|
while (true) {
|
|
57783
57810
|
if (this.state.kind === "closed") {
|
|
57784
|
-
return;
|
|
57811
|
+
return { outcome: "closed" };
|
|
57785
57812
|
}
|
|
57786
57813
|
let statuses;
|
|
57787
57814
|
try {
|
|
@@ -57790,19 +57817,26 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57790
57817
|
this.input.writeOutput?.(
|
|
57791
57818
|
`agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
|
|
57792
57819
|
);
|
|
57793
|
-
return;
|
|
57820
|
+
return { outcome: "status_failed" };
|
|
57794
57821
|
}
|
|
57795
57822
|
if (mcpServersSettled(statuses, configuredNames)) {
|
|
57823
|
+
if (!mcpRegistryHasTools(statuses, configuredNames)) {
|
|
57824
|
+
const detail = mcpStatusDetailList(statuses);
|
|
57825
|
+
this.input.writeOutput?.(
|
|
57826
|
+
`agent_bridge_claude_mcp_gate_empty duration_ms=${Date.now() - startedAt} servers=${detail}`
|
|
57827
|
+
);
|
|
57828
|
+
return { outcome: "empty", detail };
|
|
57829
|
+
}
|
|
57796
57830
|
this.input.writeOutput?.(
|
|
57797
57831
|
`agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
57798
57832
|
);
|
|
57799
|
-
return;
|
|
57833
|
+
return { outcome: "ready" };
|
|
57800
57834
|
}
|
|
57801
57835
|
if (Date.now() >= deadline) {
|
|
57802
57836
|
this.input.writeOutput?.(
|
|
57803
57837
|
`agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
57804
57838
|
);
|
|
57805
|
-
return;
|
|
57839
|
+
return { outcome: "timeout" };
|
|
57806
57840
|
}
|
|
57807
57841
|
await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
|
|
57808
57842
|
}
|
|
@@ -57945,9 +57979,20 @@ function mcpServersSettled(statuses, configuredNames) {
|
|
|
57945
57979
|
return status !== void 0 && status !== "pending";
|
|
57946
57980
|
});
|
|
57947
57981
|
}
|
|
57982
|
+
function mcpRegistryHasTools(statuses, configuredNames) {
|
|
57983
|
+
const configured = new Set(configuredNames);
|
|
57984
|
+
return statuses.some(
|
|
57985
|
+
(server) => configured.has(server.name) && server.status === "connected" && (server.tools === void 0 || server.tools.length > 0)
|
|
57986
|
+
);
|
|
57987
|
+
}
|
|
57948
57988
|
function mcpStatusList(statuses) {
|
|
57949
57989
|
return statuses.map((server) => `${server.name}:${server.status}`).join(",");
|
|
57950
57990
|
}
|
|
57991
|
+
function mcpStatusDetailList(statuses) {
|
|
57992
|
+
return statuses.map(
|
|
57993
|
+
(server) => server.error ? `${server.name}:${server.status}(${server.error})` : `${server.name}:${server.status}`
|
|
57994
|
+
).join(",");
|
|
57995
|
+
}
|
|
57951
57996
|
function delay(ms) {
|
|
57952
57997
|
return new Promise((resolve2) => {
|
|
57953
57998
|
const timer = setTimeout(resolve2, ms);
|
|
@@ -58055,6 +58100,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58055
58100
|
agentSession = null;
|
|
58056
58101
|
persistedAgentId = null;
|
|
58057
58102
|
injectedCommands = /* @__PURE__ */ new Set();
|
|
58103
|
+
// Message commands whose injection has started but not settled. A fresh SDK
|
|
58104
|
+
// warm inside sendMessage can outlast the bridge's command-ack window, so a
|
|
58105
|
+
// redelivery may arrive while the first attempt is still in flight; acking it
|
|
58106
|
+
// "duplicate" then would let the worker accept a command whose injection
|
|
58107
|
+
// later fails, silently stalling the turn (FRA-3458). The duplicate branch
|
|
58108
|
+
// awaits the in-flight outcome here and relays it instead.
|
|
58109
|
+
inFlightMessageCommands = /* @__PURE__ */ new Map();
|
|
58058
58110
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
58059
58111
|
outputBuffer;
|
|
58060
58112
|
projector = new ClaudeCodeProjector();
|
|
@@ -58102,13 +58154,25 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58102
58154
|
});
|
|
58103
58155
|
}
|
|
58104
58156
|
if (this.injectedCommands.has(delivery.commandId)) {
|
|
58157
|
+
const inFlight = this.inFlightMessageCommands.get(delivery.commandId);
|
|
58158
|
+
const outcome2 = inFlight ? await inFlight : { status: "injected" };
|
|
58105
58159
|
this.input.runtimeLogger?.info(
|
|
58106
58160
|
"agent_bridge_claude_command_duplicate",
|
|
58107
58161
|
commandLogContext(delivery, {
|
|
58108
58162
|
socket_id: socketId,
|
|
58109
|
-
duration_ms: Date.now() - startedAt
|
|
58163
|
+
duration_ms: Date.now() - startedAt,
|
|
58164
|
+
awaited_in_flight: Boolean(inFlight),
|
|
58165
|
+
in_flight_status: outcome2.status
|
|
58110
58166
|
})
|
|
58111
58167
|
);
|
|
58168
|
+
if (outcome2.status === "failed") {
|
|
58169
|
+
return commandAck({
|
|
58170
|
+
delivery,
|
|
58171
|
+
socketId,
|
|
58172
|
+
status: "failed",
|
|
58173
|
+
error: outcome2.error
|
|
58174
|
+
});
|
|
58175
|
+
}
|
|
58112
58176
|
return commandAck({
|
|
58113
58177
|
delivery,
|
|
58114
58178
|
socketId,
|
|
@@ -58159,6 +58223,53 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58159
58223
|
});
|
|
58160
58224
|
}
|
|
58161
58225
|
this.injectedCommands.add(delivery.commandId);
|
|
58226
|
+
const injection = this.injectMessageCommand(
|
|
58227
|
+
activeContext,
|
|
58228
|
+
delivery,
|
|
58229
|
+
message,
|
|
58230
|
+
socketId
|
|
58231
|
+
);
|
|
58232
|
+
this.inFlightMessageCommands.set(delivery.commandId, injection);
|
|
58233
|
+
let outcome;
|
|
58234
|
+
try {
|
|
58235
|
+
outcome = await injection;
|
|
58236
|
+
} finally {
|
|
58237
|
+
this.inFlightMessageCommands.delete(delivery.commandId);
|
|
58238
|
+
}
|
|
58239
|
+
if (outcome.status === "failed") {
|
|
58240
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
58241
|
+
this.input.runtimeLogger?.warn(
|
|
58242
|
+
"agent_bridge_claude_command_failed",
|
|
58243
|
+
commandLogContext(delivery, {
|
|
58244
|
+
socket_id: socketId,
|
|
58245
|
+
duration_ms: Date.now() - startedAt,
|
|
58246
|
+
error: outcome.error
|
|
58247
|
+
})
|
|
58248
|
+
);
|
|
58249
|
+
return commandAck({
|
|
58250
|
+
delivery,
|
|
58251
|
+
socketId,
|
|
58252
|
+
status: "failed",
|
|
58253
|
+
error: outcome.error
|
|
58254
|
+
});
|
|
58255
|
+
}
|
|
58256
|
+
this.input.runtimeLogger?.info(
|
|
58257
|
+
"agent_bridge_claude_command_injected",
|
|
58258
|
+
commandLogContext(delivery, {
|
|
58259
|
+
socket_id: socketId,
|
|
58260
|
+
duration_ms: Date.now() - startedAt
|
|
58261
|
+
})
|
|
58262
|
+
);
|
|
58263
|
+
return commandAck({
|
|
58264
|
+
delivery,
|
|
58265
|
+
socketId,
|
|
58266
|
+
status: "injected"
|
|
58267
|
+
});
|
|
58268
|
+
}
|
|
58269
|
+
// Runs one message command's injection to a terminal outcome. Never rejects:
|
|
58270
|
+
// the returned promise is stored in the in-flight map and awaited by both the
|
|
58271
|
+
// original delivery and any redeliveries, so failures travel as values.
|
|
58272
|
+
async injectMessageCommand(activeContext, delivery, message, socketId) {
|
|
58162
58273
|
try {
|
|
58163
58274
|
const emitStartedAt = Date.now();
|
|
58164
58275
|
this.input.runtimeLogger?.info(
|
|
@@ -58204,35 +58315,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58204
58315
|
duration_ms: Date.now() - sendStartedAt
|
|
58205
58316
|
})
|
|
58206
58317
|
);
|
|
58318
|
+
return { status: "injected" };
|
|
58207
58319
|
} catch (error51) {
|
|
58208
|
-
|
|
58209
|
-
this.input.runtimeLogger?.warn(
|
|
58210
|
-
"agent_bridge_claude_command_failed",
|
|
58211
|
-
commandLogContext(delivery, {
|
|
58212
|
-
socket_id: socketId,
|
|
58213
|
-
duration_ms: Date.now() - startedAt,
|
|
58214
|
-
error: error51 instanceof Error ? error51.message : String(error51)
|
|
58215
|
-
})
|
|
58216
|
-
);
|
|
58217
|
-
return commandAck({
|
|
58218
|
-
delivery,
|
|
58219
|
-
socketId,
|
|
58320
|
+
return {
|
|
58220
58321
|
status: "failed",
|
|
58221
58322
|
error: error51 instanceof Error ? error51.message : String(error51)
|
|
58222
|
-
}
|
|
58323
|
+
};
|
|
58223
58324
|
}
|
|
58224
|
-
this.input.runtimeLogger?.info(
|
|
58225
|
-
"agent_bridge_claude_command_injected",
|
|
58226
|
-
commandLogContext(delivery, {
|
|
58227
|
-
socket_id: socketId,
|
|
58228
|
-
duration_ms: Date.now() - startedAt
|
|
58229
|
-
})
|
|
58230
|
-
);
|
|
58231
|
-
return commandAck({
|
|
58232
|
-
delivery,
|
|
58233
|
-
socketId,
|
|
58234
|
-
status: "injected"
|
|
58235
|
-
});
|
|
58236
58325
|
}
|
|
58237
58326
|
// -----------------------------------------------------------------------------
|
|
58238
58327
|
// Question answering
|
package/dist/index.js
CHANGED
|
@@ -20265,11 +20265,14 @@ var init_templates = __esm({
|
|
|
20265
20265
|
});
|
|
20266
20266
|
|
|
20267
20267
|
// ../../packages/schemas/src/temporal.ts
|
|
20268
|
-
var ResourceReconciliationScopeSchema;
|
|
20268
|
+
var SessionCheckTimeoutWorkflowInputSchema, ResourceReconciliationScopeSchema;
|
|
20269
20269
|
var init_temporal = __esm({
|
|
20270
20270
|
"../../packages/schemas/src/temporal.ts"() {
|
|
20271
20271
|
"use strict";
|
|
20272
20272
|
init_zod();
|
|
20273
|
+
SessionCheckTimeoutWorkflowInputSchema = external_exports.object({
|
|
20274
|
+
checkId: external_exports.string().trim().min(1)
|
|
20275
|
+
});
|
|
20273
20276
|
ResourceReconciliationScopeSchema = external_exports.object({
|
|
20274
20277
|
organizationId: external_exports.string().trim().min(1),
|
|
20275
20278
|
projectId: external_exports.string().trim().min(1).nullable()
|
|
@@ -23170,7 +23173,7 @@ var init_package = __esm({
|
|
|
23170
23173
|
"package.json"() {
|
|
23171
23174
|
package_default = {
|
|
23172
23175
|
name: "@autohq/cli",
|
|
23173
|
-
version: "0.1.
|
|
23176
|
+
version: "0.1.289",
|
|
23174
23177
|
license: "SEE LICENSE IN README.md",
|
|
23175
23178
|
publishConfig: {
|
|
23176
23179
|
access: "public"
|
|
@@ -33742,25 +33745,11 @@ var AgentBridgeOutputBuffer = class {
|
|
|
33742
33745
|
);
|
|
33743
33746
|
if (!completedMessage || !completedMessage.id.trim()) {
|
|
33744
33747
|
if (isTerminalUiMessageChunk(projection.chunk)) {
|
|
33745
|
-
|
|
33746
|
-
if (statusText) {
|
|
33747
|
-
await this.enqueueProjectionAndDrain(context, {
|
|
33748
|
-
type: "entry",
|
|
33749
|
-
entry: {
|
|
33750
|
-
role: "system",
|
|
33751
|
-
kind: "status",
|
|
33752
|
-
status: terminalEntryStatus(projection),
|
|
33753
|
-
...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
|
|
33754
|
-
...projection.usage ? { usage: projection.usage } : {},
|
|
33755
|
-
content: {
|
|
33756
|
-
parts: [{ type: "text", text: statusText }]
|
|
33757
|
-
}
|
|
33758
|
-
}
|
|
33759
|
-
});
|
|
33760
|
-
}
|
|
33748
|
+
await this.emitTerminalStatusEntry(context, projection);
|
|
33761
33749
|
}
|
|
33762
33750
|
return;
|
|
33763
33751
|
}
|
|
33752
|
+
const turnFailed = projection.turnStatus === "failed";
|
|
33764
33753
|
this.outputSeq += 1;
|
|
33765
33754
|
const completedAt = now2();
|
|
33766
33755
|
const completedOutput = buildUiMessageCompletedOutputEnvelope({
|
|
@@ -33768,13 +33757,35 @@ var AgentBridgeOutputBuffer = class {
|
|
|
33768
33757
|
outputSeq: this.outputSeq,
|
|
33769
33758
|
message: completedMessage,
|
|
33770
33759
|
status: terminalEntryStatus(projection),
|
|
33771
|
-
turnStatus: projection.turnStatus,
|
|
33772
|
-
usage: projection.usage,
|
|
33760
|
+
turnStatus: turnFailed ? void 0 : projection.turnStatus,
|
|
33761
|
+
usage: turnFailed ? void 0 : projection.usage,
|
|
33773
33762
|
createdAt: completedAt,
|
|
33774
33763
|
completedAt
|
|
33775
33764
|
});
|
|
33776
33765
|
this.enqueueOutput(completedOutput);
|
|
33777
33766
|
await this.drainPendingOutputs();
|
|
33767
|
+
if (turnFailed) {
|
|
33768
|
+
await this.emitTerminalStatusEntry(context, projection);
|
|
33769
|
+
}
|
|
33770
|
+
}
|
|
33771
|
+
async emitTerminalStatusEntry(context, projection) {
|
|
33772
|
+
const statusText = terminalStatusText(projection);
|
|
33773
|
+
if (!statusText) {
|
|
33774
|
+
return;
|
|
33775
|
+
}
|
|
33776
|
+
await this.enqueueProjectionAndDrain(context, {
|
|
33777
|
+
type: "entry",
|
|
33778
|
+
entry: {
|
|
33779
|
+
role: "system",
|
|
33780
|
+
kind: "status",
|
|
33781
|
+
status: terminalEntryStatus(projection),
|
|
33782
|
+
...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
|
|
33783
|
+
...projection.usage ? { usage: projection.usage } : {},
|
|
33784
|
+
content: {
|
|
33785
|
+
parts: [{ type: "text", text: statusText }]
|
|
33786
|
+
}
|
|
33787
|
+
}
|
|
33788
|
+
});
|
|
33778
33789
|
}
|
|
33779
33790
|
async emitLiveUiMessageChunk(context, chunk) {
|
|
33780
33791
|
this.outputSeq += 1;
|
|
@@ -34786,6 +34797,12 @@ var claudeAgentBridgeRuntime = {
|
|
|
34786
34797
|
var claudeAgentSdkRuntime = {
|
|
34787
34798
|
startup: (input) => startupClaudeAgent(input)
|
|
34788
34799
|
};
|
|
34800
|
+
var ClaudeMcpRegistryEmptyError = class extends Error {
|
|
34801
|
+
constructor(detail) {
|
|
34802
|
+
super(`MCP tool registry came up empty at session start: ${detail}`);
|
|
34803
|
+
this.name = "ClaudeMcpRegistryEmptyError";
|
|
34804
|
+
}
|
|
34805
|
+
};
|
|
34789
34806
|
var ClaudeAgentBridgeSessionImpl = class {
|
|
34790
34807
|
input;
|
|
34791
34808
|
inputQueue;
|
|
@@ -35175,17 +35192,20 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35175
35192
|
}
|
|
35176
35193
|
// Why this gate is correct without a provider-backed test (it relies on real
|
|
35177
35194
|
// SDK mcpServerStatus() timing that fakes cannot prove):
|
|
35178
|
-
// - Safe-degrade: pollMcpRegistration never rejects — on
|
|
35179
|
-
// status-read failure, or a closed session it resolves, so
|
|
35180
|
-
//
|
|
35181
|
-
//
|
|
35195
|
+
// - Safe-degrade on uncertainty: pollMcpRegistration never rejects — on
|
|
35196
|
+
// timeout, a status-read failure, or a closed session it resolves, so an
|
|
35197
|
+
// *unconfirmed* registry still degrades to today's immediate injection.
|
|
35198
|
+
// The one fail-closed exit is the confirmed-empty registry: every
|
|
35199
|
+
// configured server settled and none is serving tools, which is exactly
|
|
35200
|
+
// the state that produced silent toolless runs in production (FRA-3458).
|
|
35182
35201
|
// - Provider-path validation is deferred deliberately: the failure is the
|
|
35183
35202
|
// intermittent live-prod race on the chief-of-staff singleton (a real
|
|
35184
35203
|
// runtime restart dispatching turn-1 while real mcp__* servers reconnect),
|
|
35185
35204
|
// which cannot be reproduced deterministically without live credentials
|
|
35186
35205
|
// and a forced restart racing reconnect.
|
|
35187
35206
|
// - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
|
|
35188
|
-
// _ready/_timeout diagnostics on the next real restart, not a CI
|
|
35207
|
+
// _ready/_timeout/_empty diagnostics on the next real restart, not a CI
|
|
35208
|
+
// fake.
|
|
35189
35209
|
//
|
|
35190
35210
|
// Block until the freshly-warmed query's MCP servers have left `pending`, so
|
|
35191
35211
|
// the first turn's prompt is built with the mcp__* tools registered. Memoized
|
|
@@ -35209,15 +35229,22 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35209
35229
|
promise: this.pollMcpRegistration(query)
|
|
35210
35230
|
};
|
|
35211
35231
|
}
|
|
35212
|
-
await this.mcpRegistrationGate.promise;
|
|
35232
|
+
const result = await this.mcpRegistrationGate.promise;
|
|
35233
|
+
if (result.outcome !== "empty") {
|
|
35234
|
+
return;
|
|
35235
|
+
}
|
|
35236
|
+
this.close();
|
|
35237
|
+
throw new ClaudeMcpRegistryEmptyError(result.detail);
|
|
35213
35238
|
}
|
|
35214
35239
|
// Poll the live query's MCP server status until no configured server is still
|
|
35215
35240
|
// `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
|
|
35216
35241
|
// (connected/failed/needs-auth/disabled) all count as settled, so a server
|
|
35217
35242
|
// that cannot connect in this runtime (e.g. an interactively-authed server in
|
|
35218
|
-
// a headless sandbox) never holds the gate to the timeout.
|
|
35243
|
+
// a headless sandbox) never holds the gate to the timeout. A settled state
|
|
35244
|
+
// where no configured server is serving tools resolves "empty" — the caller
|
|
35245
|
+
// fails the send rather than injecting a toolless turn. Never rejects:
|
|
35219
35246
|
// status-read failure, timeout, or a closed session all resolve so the caller
|
|
35220
|
-
// degrades to immediate injection.
|
|
35247
|
+
// degrades to immediate injection when the registry state is uncertain.
|
|
35221
35248
|
async pollMcpRegistration(query) {
|
|
35222
35249
|
const configuredNames = Object.keys(this.options.mcpServers ?? {});
|
|
35223
35250
|
const startedAt = Date.now();
|
|
@@ -35227,7 +35254,7 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35227
35254
|
);
|
|
35228
35255
|
while (true) {
|
|
35229
35256
|
if (this.state.kind === "closed") {
|
|
35230
|
-
return;
|
|
35257
|
+
return { outcome: "closed" };
|
|
35231
35258
|
}
|
|
35232
35259
|
let statuses;
|
|
35233
35260
|
try {
|
|
@@ -35236,19 +35263,26 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35236
35263
|
this.input.writeOutput?.(
|
|
35237
35264
|
`agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
|
|
35238
35265
|
);
|
|
35239
|
-
return;
|
|
35266
|
+
return { outcome: "status_failed" };
|
|
35240
35267
|
}
|
|
35241
35268
|
if (mcpServersSettled(statuses, configuredNames)) {
|
|
35269
|
+
if (!mcpRegistryHasTools(statuses, configuredNames)) {
|
|
35270
|
+
const detail = mcpStatusDetailList(statuses);
|
|
35271
|
+
this.input.writeOutput?.(
|
|
35272
|
+
`agent_bridge_claude_mcp_gate_empty duration_ms=${Date.now() - startedAt} servers=${detail}`
|
|
35273
|
+
);
|
|
35274
|
+
return { outcome: "empty", detail };
|
|
35275
|
+
}
|
|
35242
35276
|
this.input.writeOutput?.(
|
|
35243
35277
|
`agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
35244
35278
|
);
|
|
35245
|
-
return;
|
|
35279
|
+
return { outcome: "ready" };
|
|
35246
35280
|
}
|
|
35247
35281
|
if (Date.now() >= deadline) {
|
|
35248
35282
|
this.input.writeOutput?.(
|
|
35249
35283
|
`agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
35250
35284
|
);
|
|
35251
|
-
return;
|
|
35285
|
+
return { outcome: "timeout" };
|
|
35252
35286
|
}
|
|
35253
35287
|
await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
|
|
35254
35288
|
}
|
|
@@ -35391,9 +35425,20 @@ function mcpServersSettled(statuses, configuredNames) {
|
|
|
35391
35425
|
return status !== void 0 && status !== "pending";
|
|
35392
35426
|
});
|
|
35393
35427
|
}
|
|
35428
|
+
function mcpRegistryHasTools(statuses, configuredNames) {
|
|
35429
|
+
const configured = new Set(configuredNames);
|
|
35430
|
+
return statuses.some(
|
|
35431
|
+
(server) => configured.has(server.name) && server.status === "connected" && (server.tools === void 0 || server.tools.length > 0)
|
|
35432
|
+
);
|
|
35433
|
+
}
|
|
35394
35434
|
function mcpStatusList(statuses) {
|
|
35395
35435
|
return statuses.map((server) => `${server.name}:${server.status}`).join(",");
|
|
35396
35436
|
}
|
|
35437
|
+
function mcpStatusDetailList(statuses) {
|
|
35438
|
+
return statuses.map(
|
|
35439
|
+
(server) => server.error ? `${server.name}:${server.status}(${server.error})` : `${server.name}:${server.status}`
|
|
35440
|
+
).join(",");
|
|
35441
|
+
}
|
|
35397
35442
|
function delay(ms) {
|
|
35398
35443
|
return new Promise((resolve4) => {
|
|
35399
35444
|
const timer = setTimeout(resolve4, ms);
|
|
@@ -35501,6 +35546,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35501
35546
|
agentSession = null;
|
|
35502
35547
|
persistedAgentId = null;
|
|
35503
35548
|
injectedCommands = /* @__PURE__ */ new Set();
|
|
35549
|
+
// Message commands whose injection has started but not settled. A fresh SDK
|
|
35550
|
+
// warm inside sendMessage can outlast the bridge's command-ack window, so a
|
|
35551
|
+
// redelivery may arrive while the first attempt is still in flight; acking it
|
|
35552
|
+
// "duplicate" then would let the worker accept a command whose injection
|
|
35553
|
+
// later fails, silently stalling the turn (FRA-3458). The duplicate branch
|
|
35554
|
+
// awaits the in-flight outcome here and relays it instead.
|
|
35555
|
+
inFlightMessageCommands = /* @__PURE__ */ new Map();
|
|
35504
35556
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
35505
35557
|
outputBuffer;
|
|
35506
35558
|
projector = new ClaudeCodeProjector();
|
|
@@ -35548,13 +35600,25 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35548
35600
|
});
|
|
35549
35601
|
}
|
|
35550
35602
|
if (this.injectedCommands.has(delivery.commandId)) {
|
|
35603
|
+
const inFlight = this.inFlightMessageCommands.get(delivery.commandId);
|
|
35604
|
+
const outcome2 = inFlight ? await inFlight : { status: "injected" };
|
|
35551
35605
|
this.input.runtimeLogger?.info(
|
|
35552
35606
|
"agent_bridge_claude_command_duplicate",
|
|
35553
35607
|
commandLogContext(delivery, {
|
|
35554
35608
|
socket_id: socketId,
|
|
35555
|
-
duration_ms: Date.now() - startedAt
|
|
35609
|
+
duration_ms: Date.now() - startedAt,
|
|
35610
|
+
awaited_in_flight: Boolean(inFlight),
|
|
35611
|
+
in_flight_status: outcome2.status
|
|
35556
35612
|
})
|
|
35557
35613
|
);
|
|
35614
|
+
if (outcome2.status === "failed") {
|
|
35615
|
+
return commandAck({
|
|
35616
|
+
delivery,
|
|
35617
|
+
socketId,
|
|
35618
|
+
status: "failed",
|
|
35619
|
+
error: outcome2.error
|
|
35620
|
+
});
|
|
35621
|
+
}
|
|
35558
35622
|
return commandAck({
|
|
35559
35623
|
delivery,
|
|
35560
35624
|
socketId,
|
|
@@ -35605,6 +35669,53 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35605
35669
|
});
|
|
35606
35670
|
}
|
|
35607
35671
|
this.injectedCommands.add(delivery.commandId);
|
|
35672
|
+
const injection = this.injectMessageCommand(
|
|
35673
|
+
activeContext,
|
|
35674
|
+
delivery,
|
|
35675
|
+
message,
|
|
35676
|
+
socketId
|
|
35677
|
+
);
|
|
35678
|
+
this.inFlightMessageCommands.set(delivery.commandId, injection);
|
|
35679
|
+
let outcome;
|
|
35680
|
+
try {
|
|
35681
|
+
outcome = await injection;
|
|
35682
|
+
} finally {
|
|
35683
|
+
this.inFlightMessageCommands.delete(delivery.commandId);
|
|
35684
|
+
}
|
|
35685
|
+
if (outcome.status === "failed") {
|
|
35686
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
35687
|
+
this.input.runtimeLogger?.warn(
|
|
35688
|
+
"agent_bridge_claude_command_failed",
|
|
35689
|
+
commandLogContext(delivery, {
|
|
35690
|
+
socket_id: socketId,
|
|
35691
|
+
duration_ms: Date.now() - startedAt,
|
|
35692
|
+
error: outcome.error
|
|
35693
|
+
})
|
|
35694
|
+
);
|
|
35695
|
+
return commandAck({
|
|
35696
|
+
delivery,
|
|
35697
|
+
socketId,
|
|
35698
|
+
status: "failed",
|
|
35699
|
+
error: outcome.error
|
|
35700
|
+
});
|
|
35701
|
+
}
|
|
35702
|
+
this.input.runtimeLogger?.info(
|
|
35703
|
+
"agent_bridge_claude_command_injected",
|
|
35704
|
+
commandLogContext(delivery, {
|
|
35705
|
+
socket_id: socketId,
|
|
35706
|
+
duration_ms: Date.now() - startedAt
|
|
35707
|
+
})
|
|
35708
|
+
);
|
|
35709
|
+
return commandAck({
|
|
35710
|
+
delivery,
|
|
35711
|
+
socketId,
|
|
35712
|
+
status: "injected"
|
|
35713
|
+
});
|
|
35714
|
+
}
|
|
35715
|
+
// Runs one message command's injection to a terminal outcome. Never rejects:
|
|
35716
|
+
// the returned promise is stored in the in-flight map and awaited by both the
|
|
35717
|
+
// original delivery and any redeliveries, so failures travel as values.
|
|
35718
|
+
async injectMessageCommand(activeContext, delivery, message, socketId) {
|
|
35608
35719
|
try {
|
|
35609
35720
|
const emitStartedAt = Date.now();
|
|
35610
35721
|
this.input.runtimeLogger?.info(
|
|
@@ -35650,35 +35761,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35650
35761
|
duration_ms: Date.now() - sendStartedAt
|
|
35651
35762
|
})
|
|
35652
35763
|
);
|
|
35764
|
+
return { status: "injected" };
|
|
35653
35765
|
} catch (error51) {
|
|
35654
|
-
|
|
35655
|
-
this.input.runtimeLogger?.warn(
|
|
35656
|
-
"agent_bridge_claude_command_failed",
|
|
35657
|
-
commandLogContext(delivery, {
|
|
35658
|
-
socket_id: socketId,
|
|
35659
|
-
duration_ms: Date.now() - startedAt,
|
|
35660
|
-
error: error51 instanceof Error ? error51.message : String(error51)
|
|
35661
|
-
})
|
|
35662
|
-
);
|
|
35663
|
-
return commandAck({
|
|
35664
|
-
delivery,
|
|
35665
|
-
socketId,
|
|
35766
|
+
return {
|
|
35666
35767
|
status: "failed",
|
|
35667
35768
|
error: error51 instanceof Error ? error51.message : String(error51)
|
|
35668
|
-
}
|
|
35769
|
+
};
|
|
35669
35770
|
}
|
|
35670
|
-
this.input.runtimeLogger?.info(
|
|
35671
|
-
"agent_bridge_claude_command_injected",
|
|
35672
|
-
commandLogContext(delivery, {
|
|
35673
|
-
socket_id: socketId,
|
|
35674
|
-
duration_ms: Date.now() - startedAt
|
|
35675
|
-
})
|
|
35676
|
-
);
|
|
35677
|
-
return commandAck({
|
|
35678
|
-
delivery,
|
|
35679
|
-
socketId,
|
|
35680
|
-
status: "injected"
|
|
35681
|
-
});
|
|
35682
35771
|
}
|
|
35683
35772
|
// -----------------------------------------------------------------------------
|
|
35684
35773
|
// Question answering
|