@autohq/cli 0.1.287 → 0.1.288
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 +120 -39
- package/dist/index.js +121 -40
- 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.288",
|
|
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()
|
|
@@ -57340,6 +57343,12 @@ var claudeAgentBridgeRuntime = {
|
|
|
57340
57343
|
var claudeAgentSdkRuntime = {
|
|
57341
57344
|
startup: (input) => aj$(input)
|
|
57342
57345
|
};
|
|
57346
|
+
var ClaudeMcpRegistryEmptyError = class extends Error {
|
|
57347
|
+
constructor(detail) {
|
|
57348
|
+
super(`MCP tool registry came up empty at session start: ${detail}`);
|
|
57349
|
+
this.name = "ClaudeMcpRegistryEmptyError";
|
|
57350
|
+
}
|
|
57351
|
+
};
|
|
57343
57352
|
var ClaudeAgentBridgeSessionImpl = class {
|
|
57344
57353
|
input;
|
|
57345
57354
|
inputQueue;
|
|
@@ -57729,17 +57738,20 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57729
57738
|
}
|
|
57730
57739
|
// Why this gate is correct without a provider-backed test (it relies on real
|
|
57731
57740
|
// 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
|
-
//
|
|
57741
|
+
// - Safe-degrade on uncertainty: pollMcpRegistration never rejects — on
|
|
57742
|
+
// timeout, a status-read failure, or a closed session it resolves, so an
|
|
57743
|
+
// *unconfirmed* registry still degrades to today's immediate injection.
|
|
57744
|
+
// The one fail-closed exit is the confirmed-empty registry: every
|
|
57745
|
+
// configured server settled and none is serving tools, which is exactly
|
|
57746
|
+
// the state that produced silent toolless runs in production (FRA-3458).
|
|
57736
57747
|
// - Provider-path validation is deferred deliberately: the failure is the
|
|
57737
57748
|
// intermittent live-prod race on the chief-of-staff singleton (a real
|
|
57738
57749
|
// runtime restart dispatching turn-1 while real mcp__* servers reconnect),
|
|
57739
57750
|
// which cannot be reproduced deterministically without live credentials
|
|
57740
57751
|
// and a forced restart racing reconnect.
|
|
57741
57752
|
// - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
|
|
57742
|
-
// _ready/_timeout diagnostics on the next real restart, not a CI
|
|
57753
|
+
// _ready/_timeout/_empty diagnostics on the next real restart, not a CI
|
|
57754
|
+
// fake.
|
|
57743
57755
|
//
|
|
57744
57756
|
// Block until the freshly-warmed query's MCP servers have left `pending`, so
|
|
57745
57757
|
// the first turn's prompt is built with the mcp__* tools registered. Memoized
|
|
@@ -57763,15 +57775,22 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57763
57775
|
promise: this.pollMcpRegistration(query)
|
|
57764
57776
|
};
|
|
57765
57777
|
}
|
|
57766
|
-
await this.mcpRegistrationGate.promise;
|
|
57778
|
+
const result = await this.mcpRegistrationGate.promise;
|
|
57779
|
+
if (result.outcome !== "empty") {
|
|
57780
|
+
return;
|
|
57781
|
+
}
|
|
57782
|
+
this.close();
|
|
57783
|
+
throw new ClaudeMcpRegistryEmptyError(result.detail);
|
|
57767
57784
|
}
|
|
57768
57785
|
// Poll the live query's MCP server status until no configured server is still
|
|
57769
57786
|
// `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
|
|
57770
57787
|
// (connected/failed/needs-auth/disabled) all count as settled, so a server
|
|
57771
57788
|
// that cannot connect in this runtime (e.g. an interactively-authed server in
|
|
57772
|
-
// a headless sandbox) never holds the gate to the timeout.
|
|
57789
|
+
// a headless sandbox) never holds the gate to the timeout. A settled state
|
|
57790
|
+
// where no configured server is serving tools resolves "empty" — the caller
|
|
57791
|
+
// fails the send rather than injecting a toolless turn. Never rejects:
|
|
57773
57792
|
// status-read failure, timeout, or a closed session all resolve so the caller
|
|
57774
|
-
// degrades to immediate injection.
|
|
57793
|
+
// degrades to immediate injection when the registry state is uncertain.
|
|
57775
57794
|
async pollMcpRegistration(query) {
|
|
57776
57795
|
const configuredNames = Object.keys(this.options.mcpServers ?? {});
|
|
57777
57796
|
const startedAt = Date.now();
|
|
@@ -57781,7 +57800,7 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57781
57800
|
);
|
|
57782
57801
|
while (true) {
|
|
57783
57802
|
if (this.state.kind === "closed") {
|
|
57784
|
-
return;
|
|
57803
|
+
return { outcome: "closed" };
|
|
57785
57804
|
}
|
|
57786
57805
|
let statuses;
|
|
57787
57806
|
try {
|
|
@@ -57790,19 +57809,26 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
57790
57809
|
this.input.writeOutput?.(
|
|
57791
57810
|
`agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
|
|
57792
57811
|
);
|
|
57793
|
-
return;
|
|
57812
|
+
return { outcome: "status_failed" };
|
|
57794
57813
|
}
|
|
57795
57814
|
if (mcpServersSettled(statuses, configuredNames)) {
|
|
57815
|
+
if (!mcpRegistryHasTools(statuses, configuredNames)) {
|
|
57816
|
+
const detail = mcpStatusDetailList(statuses);
|
|
57817
|
+
this.input.writeOutput?.(
|
|
57818
|
+
`agent_bridge_claude_mcp_gate_empty duration_ms=${Date.now() - startedAt} servers=${detail}`
|
|
57819
|
+
);
|
|
57820
|
+
return { outcome: "empty", detail };
|
|
57821
|
+
}
|
|
57796
57822
|
this.input.writeOutput?.(
|
|
57797
57823
|
`agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
57798
57824
|
);
|
|
57799
|
-
return;
|
|
57825
|
+
return { outcome: "ready" };
|
|
57800
57826
|
}
|
|
57801
57827
|
if (Date.now() >= deadline) {
|
|
57802
57828
|
this.input.writeOutput?.(
|
|
57803
57829
|
`agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
57804
57830
|
);
|
|
57805
|
-
return;
|
|
57831
|
+
return { outcome: "timeout" };
|
|
57806
57832
|
}
|
|
57807
57833
|
await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
|
|
57808
57834
|
}
|
|
@@ -57945,9 +57971,20 @@ function mcpServersSettled(statuses, configuredNames) {
|
|
|
57945
57971
|
return status !== void 0 && status !== "pending";
|
|
57946
57972
|
});
|
|
57947
57973
|
}
|
|
57974
|
+
function mcpRegistryHasTools(statuses, configuredNames) {
|
|
57975
|
+
const configured = new Set(configuredNames);
|
|
57976
|
+
return statuses.some(
|
|
57977
|
+
(server) => configured.has(server.name) && server.status === "connected" && (server.tools === void 0 || server.tools.length > 0)
|
|
57978
|
+
);
|
|
57979
|
+
}
|
|
57948
57980
|
function mcpStatusList(statuses) {
|
|
57949
57981
|
return statuses.map((server) => `${server.name}:${server.status}`).join(",");
|
|
57950
57982
|
}
|
|
57983
|
+
function mcpStatusDetailList(statuses) {
|
|
57984
|
+
return statuses.map(
|
|
57985
|
+
(server) => server.error ? `${server.name}:${server.status}(${server.error})` : `${server.name}:${server.status}`
|
|
57986
|
+
).join(",");
|
|
57987
|
+
}
|
|
57951
57988
|
function delay(ms) {
|
|
57952
57989
|
return new Promise((resolve2) => {
|
|
57953
57990
|
const timer = setTimeout(resolve2, ms);
|
|
@@ -58055,6 +58092,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58055
58092
|
agentSession = null;
|
|
58056
58093
|
persistedAgentId = null;
|
|
58057
58094
|
injectedCommands = /* @__PURE__ */ new Set();
|
|
58095
|
+
// Message commands whose injection has started but not settled. A fresh SDK
|
|
58096
|
+
// warm inside sendMessage can outlast the bridge's command-ack window, so a
|
|
58097
|
+
// redelivery may arrive while the first attempt is still in flight; acking it
|
|
58098
|
+
// "duplicate" then would let the worker accept a command whose injection
|
|
58099
|
+
// later fails, silently stalling the turn (FRA-3458). The duplicate branch
|
|
58100
|
+
// awaits the in-flight outcome here and relays it instead.
|
|
58101
|
+
inFlightMessageCommands = /* @__PURE__ */ new Map();
|
|
58058
58102
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
58059
58103
|
outputBuffer;
|
|
58060
58104
|
projector = new ClaudeCodeProjector();
|
|
@@ -58102,13 +58146,25 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58102
58146
|
});
|
|
58103
58147
|
}
|
|
58104
58148
|
if (this.injectedCommands.has(delivery.commandId)) {
|
|
58149
|
+
const inFlight = this.inFlightMessageCommands.get(delivery.commandId);
|
|
58150
|
+
const outcome2 = inFlight ? await inFlight : { status: "injected" };
|
|
58105
58151
|
this.input.runtimeLogger?.info(
|
|
58106
58152
|
"agent_bridge_claude_command_duplicate",
|
|
58107
58153
|
commandLogContext(delivery, {
|
|
58108
58154
|
socket_id: socketId,
|
|
58109
|
-
duration_ms: Date.now() - startedAt
|
|
58155
|
+
duration_ms: Date.now() - startedAt,
|
|
58156
|
+
awaited_in_flight: Boolean(inFlight),
|
|
58157
|
+
in_flight_status: outcome2.status
|
|
58110
58158
|
})
|
|
58111
58159
|
);
|
|
58160
|
+
if (outcome2.status === "failed") {
|
|
58161
|
+
return commandAck({
|
|
58162
|
+
delivery,
|
|
58163
|
+
socketId,
|
|
58164
|
+
status: "failed",
|
|
58165
|
+
error: outcome2.error
|
|
58166
|
+
});
|
|
58167
|
+
}
|
|
58112
58168
|
return commandAck({
|
|
58113
58169
|
delivery,
|
|
58114
58170
|
socketId,
|
|
@@ -58159,6 +58215,53 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58159
58215
|
});
|
|
58160
58216
|
}
|
|
58161
58217
|
this.injectedCommands.add(delivery.commandId);
|
|
58218
|
+
const injection = this.injectMessageCommand(
|
|
58219
|
+
activeContext,
|
|
58220
|
+
delivery,
|
|
58221
|
+
message,
|
|
58222
|
+
socketId
|
|
58223
|
+
);
|
|
58224
|
+
this.inFlightMessageCommands.set(delivery.commandId, injection);
|
|
58225
|
+
let outcome;
|
|
58226
|
+
try {
|
|
58227
|
+
outcome = await injection;
|
|
58228
|
+
} finally {
|
|
58229
|
+
this.inFlightMessageCommands.delete(delivery.commandId);
|
|
58230
|
+
}
|
|
58231
|
+
if (outcome.status === "failed") {
|
|
58232
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
58233
|
+
this.input.runtimeLogger?.warn(
|
|
58234
|
+
"agent_bridge_claude_command_failed",
|
|
58235
|
+
commandLogContext(delivery, {
|
|
58236
|
+
socket_id: socketId,
|
|
58237
|
+
duration_ms: Date.now() - startedAt,
|
|
58238
|
+
error: outcome.error
|
|
58239
|
+
})
|
|
58240
|
+
);
|
|
58241
|
+
return commandAck({
|
|
58242
|
+
delivery,
|
|
58243
|
+
socketId,
|
|
58244
|
+
status: "failed",
|
|
58245
|
+
error: outcome.error
|
|
58246
|
+
});
|
|
58247
|
+
}
|
|
58248
|
+
this.input.runtimeLogger?.info(
|
|
58249
|
+
"agent_bridge_claude_command_injected",
|
|
58250
|
+
commandLogContext(delivery, {
|
|
58251
|
+
socket_id: socketId,
|
|
58252
|
+
duration_ms: Date.now() - startedAt
|
|
58253
|
+
})
|
|
58254
|
+
);
|
|
58255
|
+
return commandAck({
|
|
58256
|
+
delivery,
|
|
58257
|
+
socketId,
|
|
58258
|
+
status: "injected"
|
|
58259
|
+
});
|
|
58260
|
+
}
|
|
58261
|
+
// Runs one message command's injection to a terminal outcome. Never rejects:
|
|
58262
|
+
// the returned promise is stored in the in-flight map and awaited by both the
|
|
58263
|
+
// original delivery and any redeliveries, so failures travel as values.
|
|
58264
|
+
async injectMessageCommand(activeContext, delivery, message, socketId) {
|
|
58162
58265
|
try {
|
|
58163
58266
|
const emitStartedAt = Date.now();
|
|
58164
58267
|
this.input.runtimeLogger?.info(
|
|
@@ -58204,35 +58307,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
58204
58307
|
duration_ms: Date.now() - sendStartedAt
|
|
58205
58308
|
})
|
|
58206
58309
|
);
|
|
58310
|
+
return { status: "injected" };
|
|
58207
58311
|
} 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,
|
|
58312
|
+
return {
|
|
58220
58313
|
status: "failed",
|
|
58221
58314
|
error: error51 instanceof Error ? error51.message : String(error51)
|
|
58222
|
-
}
|
|
58315
|
+
};
|
|
58223
58316
|
}
|
|
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
58317
|
}
|
|
58237
58318
|
// -----------------------------------------------------------------------------
|
|
58238
58319
|
// 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.288",
|
|
23174
23177
|
license: "SEE LICENSE IN README.md",
|
|
23175
23178
|
publishConfig: {
|
|
23176
23179
|
access: "public"
|
|
@@ -34786,6 +34789,12 @@ var claudeAgentBridgeRuntime = {
|
|
|
34786
34789
|
var claudeAgentSdkRuntime = {
|
|
34787
34790
|
startup: (input) => startupClaudeAgent(input)
|
|
34788
34791
|
};
|
|
34792
|
+
var ClaudeMcpRegistryEmptyError = class extends Error {
|
|
34793
|
+
constructor(detail) {
|
|
34794
|
+
super(`MCP tool registry came up empty at session start: ${detail}`);
|
|
34795
|
+
this.name = "ClaudeMcpRegistryEmptyError";
|
|
34796
|
+
}
|
|
34797
|
+
};
|
|
34789
34798
|
var ClaudeAgentBridgeSessionImpl = class {
|
|
34790
34799
|
input;
|
|
34791
34800
|
inputQueue;
|
|
@@ -35175,17 +35184,20 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35175
35184
|
}
|
|
35176
35185
|
// Why this gate is correct without a provider-backed test (it relies on real
|
|
35177
35186
|
// 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
|
-
//
|
|
35187
|
+
// - Safe-degrade on uncertainty: pollMcpRegistration never rejects — on
|
|
35188
|
+
// timeout, a status-read failure, or a closed session it resolves, so an
|
|
35189
|
+
// *unconfirmed* registry still degrades to today's immediate injection.
|
|
35190
|
+
// The one fail-closed exit is the confirmed-empty registry: every
|
|
35191
|
+
// configured server settled and none is serving tools, which is exactly
|
|
35192
|
+
// the state that produced silent toolless runs in production (FRA-3458).
|
|
35182
35193
|
// - Provider-path validation is deferred deliberately: the failure is the
|
|
35183
35194
|
// intermittent live-prod race on the chief-of-staff singleton (a real
|
|
35184
35195
|
// runtime restart dispatching turn-1 while real mcp__* servers reconnect),
|
|
35185
35196
|
// which cannot be reproduced deterministically without live credentials
|
|
35186
35197
|
// and a forced restart racing reconnect.
|
|
35187
35198
|
// - Live confirmation comes from the agent_bridge_claude_mcp_gate_started/
|
|
35188
|
-
// _ready/_timeout diagnostics on the next real restart, not a CI
|
|
35199
|
+
// _ready/_timeout/_empty diagnostics on the next real restart, not a CI
|
|
35200
|
+
// fake.
|
|
35189
35201
|
//
|
|
35190
35202
|
// Block until the freshly-warmed query's MCP servers have left `pending`, so
|
|
35191
35203
|
// the first turn's prompt is built with the mcp__* tools registered. Memoized
|
|
@@ -35209,15 +35221,22 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35209
35221
|
promise: this.pollMcpRegistration(query)
|
|
35210
35222
|
};
|
|
35211
35223
|
}
|
|
35212
|
-
await this.mcpRegistrationGate.promise;
|
|
35224
|
+
const result = await this.mcpRegistrationGate.promise;
|
|
35225
|
+
if (result.outcome !== "empty") {
|
|
35226
|
+
return;
|
|
35227
|
+
}
|
|
35228
|
+
this.close();
|
|
35229
|
+
throw new ClaudeMcpRegistryEmptyError(result.detail);
|
|
35213
35230
|
}
|
|
35214
35231
|
// Poll the live query's MCP server status until no configured server is still
|
|
35215
35232
|
// `pending`, bounded by CLAUDE_MCP_REGISTRATION_TIMEOUT_MS. Terminal states
|
|
35216
35233
|
// (connected/failed/needs-auth/disabled) all count as settled, so a server
|
|
35217
35234
|
// that cannot connect in this runtime (e.g. an interactively-authed server in
|
|
35218
|
-
// a headless sandbox) never holds the gate to the timeout.
|
|
35235
|
+
// a headless sandbox) never holds the gate to the timeout. A settled state
|
|
35236
|
+
// where no configured server is serving tools resolves "empty" — the caller
|
|
35237
|
+
// fails the send rather than injecting a toolless turn. Never rejects:
|
|
35219
35238
|
// status-read failure, timeout, or a closed session all resolve so the caller
|
|
35220
|
-
// degrades to immediate injection.
|
|
35239
|
+
// degrades to immediate injection when the registry state is uncertain.
|
|
35221
35240
|
async pollMcpRegistration(query) {
|
|
35222
35241
|
const configuredNames = Object.keys(this.options.mcpServers ?? {});
|
|
35223
35242
|
const startedAt = Date.now();
|
|
@@ -35227,7 +35246,7 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35227
35246
|
);
|
|
35228
35247
|
while (true) {
|
|
35229
35248
|
if (this.state.kind === "closed") {
|
|
35230
|
-
return;
|
|
35249
|
+
return { outcome: "closed" };
|
|
35231
35250
|
}
|
|
35232
35251
|
let statuses;
|
|
35233
35252
|
try {
|
|
@@ -35236,19 +35255,26 @@ var ClaudeAgentBridgeSessionImpl = class {
|
|
|
35236
35255
|
this.input.writeOutput?.(
|
|
35237
35256
|
`agent_bridge_claude_mcp_gate_status_failed duration_ms=${Date.now() - startedAt} error=${error51 instanceof Error ? error51.message : String(error51)}`
|
|
35238
35257
|
);
|
|
35239
|
-
return;
|
|
35258
|
+
return { outcome: "status_failed" };
|
|
35240
35259
|
}
|
|
35241
35260
|
if (mcpServersSettled(statuses, configuredNames)) {
|
|
35261
|
+
if (!mcpRegistryHasTools(statuses, configuredNames)) {
|
|
35262
|
+
const detail = mcpStatusDetailList(statuses);
|
|
35263
|
+
this.input.writeOutput?.(
|
|
35264
|
+
`agent_bridge_claude_mcp_gate_empty duration_ms=${Date.now() - startedAt} servers=${detail}`
|
|
35265
|
+
);
|
|
35266
|
+
return { outcome: "empty", detail };
|
|
35267
|
+
}
|
|
35242
35268
|
this.input.writeOutput?.(
|
|
35243
35269
|
`agent_bridge_claude_mcp_gate_ready duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
35244
35270
|
);
|
|
35245
|
-
return;
|
|
35271
|
+
return { outcome: "ready" };
|
|
35246
35272
|
}
|
|
35247
35273
|
if (Date.now() >= deadline) {
|
|
35248
35274
|
this.input.writeOutput?.(
|
|
35249
35275
|
`agent_bridge_claude_mcp_gate_timeout duration_ms=${Date.now() - startedAt} servers=${mcpStatusList(statuses)}`
|
|
35250
35276
|
);
|
|
35251
|
-
return;
|
|
35277
|
+
return { outcome: "timeout" };
|
|
35252
35278
|
}
|
|
35253
35279
|
await delay(CLAUDE_MCP_REGISTRATION_POLL_INTERVAL_MS);
|
|
35254
35280
|
}
|
|
@@ -35391,9 +35417,20 @@ function mcpServersSettled(statuses, configuredNames) {
|
|
|
35391
35417
|
return status !== void 0 && status !== "pending";
|
|
35392
35418
|
});
|
|
35393
35419
|
}
|
|
35420
|
+
function mcpRegistryHasTools(statuses, configuredNames) {
|
|
35421
|
+
const configured = new Set(configuredNames);
|
|
35422
|
+
return statuses.some(
|
|
35423
|
+
(server) => configured.has(server.name) && server.status === "connected" && (server.tools === void 0 || server.tools.length > 0)
|
|
35424
|
+
);
|
|
35425
|
+
}
|
|
35394
35426
|
function mcpStatusList(statuses) {
|
|
35395
35427
|
return statuses.map((server) => `${server.name}:${server.status}`).join(",");
|
|
35396
35428
|
}
|
|
35429
|
+
function mcpStatusDetailList(statuses) {
|
|
35430
|
+
return statuses.map(
|
|
35431
|
+
(server) => server.error ? `${server.name}:${server.status}(${server.error})` : `${server.name}:${server.status}`
|
|
35432
|
+
).join(",");
|
|
35433
|
+
}
|
|
35397
35434
|
function delay(ms) {
|
|
35398
35435
|
return new Promise((resolve4) => {
|
|
35399
35436
|
const timer = setTimeout(resolve4, ms);
|
|
@@ -35501,6 +35538,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35501
35538
|
agentSession = null;
|
|
35502
35539
|
persistedAgentId = null;
|
|
35503
35540
|
injectedCommands = /* @__PURE__ */ new Set();
|
|
35541
|
+
// Message commands whose injection has started but not settled. A fresh SDK
|
|
35542
|
+
// warm inside sendMessage can outlast the bridge's command-ack window, so a
|
|
35543
|
+
// redelivery may arrive while the first attempt is still in flight; acking it
|
|
35544
|
+
// "duplicate" then would let the worker accept a command whose injection
|
|
35545
|
+
// later fails, silently stalling the turn (FRA-3458). The duplicate branch
|
|
35546
|
+
// awaits the in-flight outcome here and relays it instead.
|
|
35547
|
+
inFlightMessageCommands = /* @__PURE__ */ new Map();
|
|
35504
35548
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
35505
35549
|
outputBuffer;
|
|
35506
35550
|
projector = new ClaudeCodeProjector();
|
|
@@ -35548,13 +35592,25 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35548
35592
|
});
|
|
35549
35593
|
}
|
|
35550
35594
|
if (this.injectedCommands.has(delivery.commandId)) {
|
|
35595
|
+
const inFlight = this.inFlightMessageCommands.get(delivery.commandId);
|
|
35596
|
+
const outcome2 = inFlight ? await inFlight : { status: "injected" };
|
|
35551
35597
|
this.input.runtimeLogger?.info(
|
|
35552
35598
|
"agent_bridge_claude_command_duplicate",
|
|
35553
35599
|
commandLogContext(delivery, {
|
|
35554
35600
|
socket_id: socketId,
|
|
35555
|
-
duration_ms: Date.now() - startedAt
|
|
35601
|
+
duration_ms: Date.now() - startedAt,
|
|
35602
|
+
awaited_in_flight: Boolean(inFlight),
|
|
35603
|
+
in_flight_status: outcome2.status
|
|
35556
35604
|
})
|
|
35557
35605
|
);
|
|
35606
|
+
if (outcome2.status === "failed") {
|
|
35607
|
+
return commandAck({
|
|
35608
|
+
delivery,
|
|
35609
|
+
socketId,
|
|
35610
|
+
status: "failed",
|
|
35611
|
+
error: outcome2.error
|
|
35612
|
+
});
|
|
35613
|
+
}
|
|
35558
35614
|
return commandAck({
|
|
35559
35615
|
delivery,
|
|
35560
35616
|
socketId,
|
|
@@ -35605,6 +35661,53 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35605
35661
|
});
|
|
35606
35662
|
}
|
|
35607
35663
|
this.injectedCommands.add(delivery.commandId);
|
|
35664
|
+
const injection = this.injectMessageCommand(
|
|
35665
|
+
activeContext,
|
|
35666
|
+
delivery,
|
|
35667
|
+
message,
|
|
35668
|
+
socketId
|
|
35669
|
+
);
|
|
35670
|
+
this.inFlightMessageCommands.set(delivery.commandId, injection);
|
|
35671
|
+
let outcome;
|
|
35672
|
+
try {
|
|
35673
|
+
outcome = await injection;
|
|
35674
|
+
} finally {
|
|
35675
|
+
this.inFlightMessageCommands.delete(delivery.commandId);
|
|
35676
|
+
}
|
|
35677
|
+
if (outcome.status === "failed") {
|
|
35678
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
35679
|
+
this.input.runtimeLogger?.warn(
|
|
35680
|
+
"agent_bridge_claude_command_failed",
|
|
35681
|
+
commandLogContext(delivery, {
|
|
35682
|
+
socket_id: socketId,
|
|
35683
|
+
duration_ms: Date.now() - startedAt,
|
|
35684
|
+
error: outcome.error
|
|
35685
|
+
})
|
|
35686
|
+
);
|
|
35687
|
+
return commandAck({
|
|
35688
|
+
delivery,
|
|
35689
|
+
socketId,
|
|
35690
|
+
status: "failed",
|
|
35691
|
+
error: outcome.error
|
|
35692
|
+
});
|
|
35693
|
+
}
|
|
35694
|
+
this.input.runtimeLogger?.info(
|
|
35695
|
+
"agent_bridge_claude_command_injected",
|
|
35696
|
+
commandLogContext(delivery, {
|
|
35697
|
+
socket_id: socketId,
|
|
35698
|
+
duration_ms: Date.now() - startedAt
|
|
35699
|
+
})
|
|
35700
|
+
);
|
|
35701
|
+
return commandAck({
|
|
35702
|
+
delivery,
|
|
35703
|
+
socketId,
|
|
35704
|
+
status: "injected"
|
|
35705
|
+
});
|
|
35706
|
+
}
|
|
35707
|
+
// Runs one message command's injection to a terminal outcome. Never rejects:
|
|
35708
|
+
// the returned promise is stored in the in-flight map and awaited by both the
|
|
35709
|
+
// original delivery and any redeliveries, so failures travel as values.
|
|
35710
|
+
async injectMessageCommand(activeContext, delivery, message, socketId) {
|
|
35608
35711
|
try {
|
|
35609
35712
|
const emitStartedAt = Date.now();
|
|
35610
35713
|
this.input.runtimeLogger?.info(
|
|
@@ -35650,35 +35753,13 @@ var ClaudeCodeCommandHandler = class {
|
|
|
35650
35753
|
duration_ms: Date.now() - sendStartedAt
|
|
35651
35754
|
})
|
|
35652
35755
|
);
|
|
35756
|
+
return { status: "injected" };
|
|
35653
35757
|
} 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,
|
|
35758
|
+
return {
|
|
35666
35759
|
status: "failed",
|
|
35667
35760
|
error: error51 instanceof Error ? error51.message : String(error51)
|
|
35668
|
-
}
|
|
35761
|
+
};
|
|
35669
35762
|
}
|
|
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
35763
|
}
|
|
35683
35764
|
// -----------------------------------------------------------------------------
|
|
35684
35765
|
// Question answering
|