@autohq/cli 0.1.583 → 0.1.584

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.
Files changed (3) hide show
  1. package/dist/agent-bridge.js +37635 -27103
  2. package/dist/index.js +699 -122
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16681,13 +16681,13 @@ function parseClaudeCodeStreamRecord(parsed) {
16681
16681
  }
16682
16682
  const isError = record2.is_error === true || record2.subtype === "error";
16683
16683
  const sdkErrorMessage = record2.errors?.map((error51) => error51.trim()).filter(Boolean).join("\n");
16684
- const errorMessage6 = isError ? record2.error ?? record2.result ?? (sdkErrorMessage || void 0) ?? "claude-code reported an error" : void 0;
16684
+ const errorMessage7 = isError ? record2.error ?? record2.result ?? (sdkErrorMessage || void 0) ?? "claude-code reported an error" : void 0;
16685
16685
  const usage = claudeCodeUsageFromRecord(record2);
16686
16686
  return {
16687
16687
  projections: [],
16688
16688
  result: {
16689
16689
  isError,
16690
- errorMessage: errorMessage6,
16690
+ errorMessage: errorMessage7,
16691
16691
  ...usage ? { usage } : {}
16692
16692
  }
16693
16693
  };
@@ -16705,11 +16705,11 @@ function isClaudeCodeInterruptMarker(record2) {
16705
16705
  (block) => block !== null && block.type === "text" && block.text.startsWith(CLAUDE_CODE_INTERRUPT_MARKER_PREFIX)
16706
16706
  );
16707
16707
  }
16708
- function isClaudeCodeEdeDiagnosticOnlyError(errorMessage6) {
16709
- if (!errorMessage6) {
16708
+ function isClaudeCodeEdeDiagnosticOnlyError(errorMessage7) {
16709
+ if (!errorMessage7) {
16710
16710
  return false;
16711
16711
  }
16712
- const lines = errorMessage6.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
16712
+ const lines = errorMessage7.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
16713
16713
  return lines.length > 0 && lines.every((line) => line.startsWith(CLAUDE_CODE_EDE_DIAGNOSTIC_PREFIX));
16714
16714
  }
16715
16715
  function isClaudeCodePermissionDenialText(text) {
@@ -88606,7 +88606,7 @@ var init_package = __esm({
88606
88606
  "package.json"() {
88607
88607
  package_default = {
88608
88608
  name: "@autohq/cli",
88609
- version: "0.1.583",
88609
+ version: "0.1.584",
88610
88610
  license: "SEE LICENSE IN README.md",
88611
88611
  publishConfig: {
88612
88612
  access: "public"
@@ -92337,9 +92337,9 @@ var init_actions = __esm({
92337
92337
  });
92338
92338
 
92339
92339
  // src/commands/auth/pkce.ts
92340
- import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
92340
+ import { createHash as createHash6, randomBytes as randomBytes4 } from "crypto";
92341
92341
  function pkceVerifier() {
92342
- return randomBytes3(32).toString("base64url");
92342
+ return randomBytes4(32).toString("base64url");
92343
92343
  }
92344
92344
  function pkceChallenge(verifier) {
92345
92345
  return createHash6("sha256").update(verifier).digest("base64url");
@@ -95904,7 +95904,7 @@ function SessionsView({
95904
95904
  visibleSessions.map((session, vi) => {
95905
95905
  const i = scrollOffset + vi;
95906
95906
  const title = sessionDisplayTitle(session);
95907
- const err = session.status === "failed" ? errorMessage4(session) : null;
95907
+ const err = session.status === "failed" ? errorMessage5(session) : null;
95908
95908
  const isSelected = i === safeIndex;
95909
95909
  const isMarked = selectedSessionIds.has(session.id);
95910
95910
  return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
@@ -96042,7 +96042,7 @@ function truncateToWidth(value, width) {
96042
96042
  function agentNameForRun(session) {
96043
96043
  return session.snapshot.metadata.name;
96044
96044
  }
96045
- function errorMessage4(session) {
96045
+ function errorMessage5(session) {
96046
96046
  if (session.error == null) return null;
96047
96047
  if (typeof session.error === "string") return session.error;
96048
96048
  const msg = readField(session.error, "message");
@@ -97477,7 +97477,7 @@ function HomeView({
97477
97477
  if (inspectedResource) {
97478
97478
  sectionTitle = `Inspect ${inspectedResource.kind}/${inspectedResource.name}`;
97479
97479
  }
97480
- const runError = errorMessage5(triggerSession.error) ?? errorMessage5(archiveSessions.error) ?? errorMessage5(unarchiveSessions.error) ?? errorMessage5(stopSessions.error);
97480
+ const runError = errorMessage6(triggerSession.error) ?? errorMessage6(archiveSessions.error) ?? errorMessage6(unarchiveSessions.error) ?? errorMessage6(stopSessions.error);
97481
97481
  const isRunMutationPending = triggerSession.isPending || archiveSessions.isPending || unarchiveSessions.isPending || stopSessions.isPending;
97482
97482
  const isConnectionMutationPending = startConnection.isPending || allowConnection.isPending || removeConnection.isPending || createGithubSyncBinding.isPending;
97483
97483
  const serviceAccountStatus = createServiceAccount2.error instanceof Error ? createServiceAccount2.error.message : rotateServiceAccount2.error instanceof Error ? rotateServiceAccount2.error.message : removeServiceAccount2.error instanceof Error ? removeServiceAccount2.error.message : serviceAccountNotice;
@@ -97890,7 +97890,7 @@ function runActionTargetLabel(sessionIds, markedRunCount) {
97890
97890
  function pluralizeRunCount(count) {
97891
97891
  return `${count} ${count === 1 ? "session" : "sessions"}`;
97892
97892
  }
97893
- function errorMessage5(error51) {
97893
+ function errorMessage6(error51) {
97894
97894
  if (error51 instanceof Error) return error51.message;
97895
97895
  return error51 ? String(error51) : null;
97896
97896
  }
@@ -100733,7 +100733,7 @@ function jsonRecordString(value, key) {
100733
100733
  init_src();
100734
100734
 
100735
100735
  // src/commands/agent-bridge/harness/agent-facing-auto-mcp.ts
100736
- import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
100736
+ import { randomBytes as randomBytes2 } from "crypto";
100737
100737
  import { lstatSync, readFileSync as readFileSync3, realpathSync, statSync } from "fs";
100738
100738
  import {
100739
100739
  createServer as createServer3
@@ -100751,7 +100751,17 @@ import {
100751
100751
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
100752
100752
 
100753
100753
  // src/commands/agent-bridge/harness/turn-bound-mcp.ts
100754
+ import { timingSafeEqual } from "crypto";
100754
100755
  var AGENT_BRIDGE_MCP_CAPABILITY_HEADER = "x-auto-agent-bridge-mcp-capability";
100756
+ function requestHasMcpCapability(headers, expected) {
100757
+ const received = headers[AGENT_BRIDGE_MCP_CAPABILITY_HEADER];
100758
+ if (typeof received !== "string") {
100759
+ return false;
100760
+ }
100761
+ const expectedBytes = Buffer.from(expected);
100762
+ const receivedBytes = Buffer.from(received);
100763
+ return expectedBytes.length === receivedBytes.length && timingSafeEqual(expectedBytes, receivedBytes);
100764
+ }
100755
100765
 
100756
100766
  // src/commands/agent-bridge/harness/agent-facing-auto-mcp.ts
100757
100767
  var MAX_DRY_RUN_PATH_FILES = 100;
@@ -101221,7 +101231,7 @@ async function startAutoMcpProxy(input) {
101221
101231
  let pathsCompatible = false;
101222
101232
  const server = createServer3(async (request, response) => {
101223
101233
  try {
101224
- if (!requestHasCapability(request.headers, input.capability)) {
101234
+ if (!requestHasMcpCapability(request.headers, input.capability)) {
101225
101235
  writeResponse(
101226
101236
  response,
101227
101237
  401,
@@ -101427,15 +101437,6 @@ function exactActiveCommandId(activeTurnCommandIds) {
101427
101437
  const commandIds = activeTurnCommandIds();
101428
101438
  return commandIds.length === 1 ? commandIds[0] ?? null : null;
101429
101439
  }
101430
- function requestHasCapability(headers, expected) {
101431
- const received = headers[AGENT_BRIDGE_MCP_CAPABILITY_HEADER];
101432
- if (typeof received !== "string") {
101433
- return false;
101434
- }
101435
- const expectedBytes = Buffer.from(expected);
101436
- const receivedBytes = Buffer.from(received);
101437
- return expectedBytes.length === receivedBytes.length && timingSafeEqual(expectedBytes, receivedBytes);
101438
- }
101439
101440
  function readRequestBody(request) {
101440
101441
  return new Promise((resolveBody, reject) => {
101441
101442
  const chunks = [];
@@ -101468,6 +101469,384 @@ function isRecord2(value) {
101468
101469
  return typeof value === "object" && value !== null && !Array.isArray(value);
101469
101470
  }
101470
101471
 
101472
+ // src/commands/agent-bridge/harness/ask-user-projection.ts
101473
+ init_src();
101474
+ var ASK_USER_MCP_SERVER_NAME = "user";
101475
+ var ASK_USER_MCP_TOOL_NAME = "ask_question";
101476
+ var ASK_USER_TOOL_NAME = `${ASK_USER_MCP_SERVER_NAME}.${ASK_USER_MCP_TOOL_NAME}`;
101477
+ var CLAUDE_ASK_USER_TOOL_NAME = `mcp__${ASK_USER_MCP_SERVER_NAME}__${ASK_USER_MCP_TOOL_NAME}`;
101478
+ function isAskUserMcpTool(server, tool) {
101479
+ return server === ASK_USER_MCP_SERVER_NAME && tool === ASK_USER_MCP_TOOL_NAME;
101480
+ }
101481
+ function projectAskUserQuestion(input) {
101482
+ return [
101483
+ {
101484
+ type: "entry",
101485
+ entry: {
101486
+ role: "assistant",
101487
+ kind: "question",
101488
+ turnStatus: "waiting_for_input",
101489
+ content: {
101490
+ parts: [
101491
+ {
101492
+ type: "question",
101493
+ toolCallId: input.id,
101494
+ questions: input.questions
101495
+ }
101496
+ ]
101497
+ }
101498
+ }
101499
+ },
101500
+ {
101501
+ type: "ui_message_chunk",
101502
+ chunk: {
101503
+ type: "tool-input-available",
101504
+ toolCallId: input.id,
101505
+ toolName: ASK_USER_TOOL_NAME,
101506
+ input: { questions: input.questions }
101507
+ },
101508
+ turnStatus: "waiting_for_input"
101509
+ }
101510
+ ];
101511
+ }
101512
+ function projectAskUserAnswer(answer) {
101513
+ return {
101514
+ type: "ui_message_chunk",
101515
+ chunk: {
101516
+ type: "tool-output-available",
101517
+ toolCallId: answer.toolCallId || UNKNOWN_MESSAGE_ID,
101518
+ output: toJsonValue({
101519
+ answers: answer.answers,
101520
+ ...answer.response ? { response: answer.response } : {}
101521
+ })
101522
+ }
101523
+ };
101524
+ }
101525
+
101526
+ // src/commands/agent-bridge/harness/ask-user-server.ts
101527
+ init_src();
101528
+ init_zod();
101529
+ import { randomBytes as randomBytes3, randomUUID } from "crypto";
101530
+ import {
101531
+ createServer as createServer4
101532
+ } from "http";
101533
+ import { Server as McpProtocolServer } from "@modelcontextprotocol/sdk/server/index.js";
101534
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
101535
+ import {
101536
+ CallToolRequestSchema as CallToolRequestSchema2,
101537
+ ListToolsRequestSchema as ListToolsRequestSchema2
101538
+ } from "@modelcontextprotocol/sdk/types.js";
101539
+ var ASK_USER_PROGRESS_INTERVAL_MS = 3e4;
101540
+ var ASK_USER_CALL_DEADLINE_MS = 84e4;
101541
+ var ASK_USER_FALLBACK_MESSAGE = "The question remains pending for the user. Continue this turn; the answer will arrive as a user message.";
101542
+ var AuthoredQuestionSchema = ConversationQuestionSchema.extend({
101543
+ question: external_exports.string().trim().min(1).max(1024),
101544
+ header: external_exports.string().trim().min(1).max(64),
101545
+ options: external_exports.array(
101546
+ external_exports.object({
101547
+ label: external_exports.string().trim().min(1).max(128),
101548
+ description: external_exports.string().trim().max(512)
101549
+ }).strict()
101550
+ ).min(2).max(6)
101551
+ }).strict();
101552
+ var AskUserQuestionInputSchema2 = external_exports.object({
101553
+ questions: external_exports.array(AuthoredQuestionSchema).min(1).max(4)
101554
+ }).strict();
101555
+ var AskUserQuestionRegistry = class {
101556
+ pending = /* @__PURE__ */ new Map();
101557
+ deadlineMs;
101558
+ progressIntervalMs;
101559
+ constructor(options = {}) {
101560
+ this.deadlineMs = positiveDuration(
101561
+ options.deadlineMs ?? ASK_USER_CALL_DEADLINE_MS,
101562
+ "ask-user deadline"
101563
+ );
101564
+ this.progressIntervalMs = positiveDuration(
101565
+ options.progressIntervalMs ?? ASK_USER_PROGRESS_INTERVAL_MS,
101566
+ "ask-user progress interval"
101567
+ );
101568
+ }
101569
+ get size() {
101570
+ return this.pending.size;
101571
+ }
101572
+ has(id) {
101573
+ return this.pending.has(id);
101574
+ }
101575
+ park(input) {
101576
+ const { questions } = AskUserQuestionInputSchema2.parse({
101577
+ questions: input.questions
101578
+ });
101579
+ const id = `q_${randomUUID()}`;
101580
+ let resolveWait = () => {
101581
+ };
101582
+ const wait = new Promise((resolve6) => {
101583
+ resolveWait = resolve6;
101584
+ });
101585
+ const progress = setInterval(() => {
101586
+ try {
101587
+ void Promise.resolve(input.onProgress?.()).catch(() => {
101588
+ });
101589
+ } catch {
101590
+ }
101591
+ }, this.progressIntervalMs);
101592
+ const deadline = setTimeout(() => {
101593
+ this.settle(id, {
101594
+ kind: "fallback",
101595
+ message: ASK_USER_FALLBACK_MESSAGE,
101596
+ reason: "deadline"
101597
+ });
101598
+ }, this.deadlineMs);
101599
+ this.pending.set(id, {
101600
+ id,
101601
+ questions,
101602
+ deadline,
101603
+ progress,
101604
+ resolve: resolveWait,
101605
+ onSettled: input.onSettled
101606
+ });
101607
+ return { id, wait };
101608
+ }
101609
+ answer(id, answer) {
101610
+ if (answer.toolCallId !== id) {
101611
+ return false;
101612
+ }
101613
+ return this.settle(id, { kind: "answered", answer });
101614
+ }
101615
+ quarantine(id, reason) {
101616
+ return this.settle(id, {
101617
+ kind: "fallback",
101618
+ message: ASK_USER_FALLBACK_MESSAGE,
101619
+ reason
101620
+ });
101621
+ }
101622
+ quarantineAll(reason) {
101623
+ let settled = 0;
101624
+ for (const id of [...this.pending.keys()]) {
101625
+ if (this.quarantine(id, reason)) {
101626
+ settled += 1;
101627
+ }
101628
+ }
101629
+ return settled;
101630
+ }
101631
+ settle(id, settlement) {
101632
+ const pending = this.pending.get(id);
101633
+ if (!pending || !this.pending.delete(id)) {
101634
+ return false;
101635
+ }
101636
+ clearTimeout(pending.deadline);
101637
+ clearInterval(pending.progress);
101638
+ pending.resolve(settlement);
101639
+ try {
101640
+ pending.onSettled?.(settlement);
101641
+ } catch {
101642
+ }
101643
+ return true;
101644
+ }
101645
+ };
101646
+ var AskUserServer = class {
101647
+ constructor(options) {
101648
+ this.options = options;
101649
+ this.registry = new AskUserQuestionRegistry(options);
101650
+ }
101651
+ options;
101652
+ registry;
101653
+ path = `/mcp/${randomBytes3(24).toString("base64url")}`;
101654
+ capability = randomBytes3(32).toString("base64url");
101655
+ server = null;
101656
+ prepared = null;
101657
+ prepare() {
101658
+ this.prepared ??= this.prepareOnce();
101659
+ return this.prepared;
101660
+ }
101661
+ answer(answer) {
101662
+ return this.registry.answer(answer.toolCallId, answer);
101663
+ }
101664
+ quarantineAll(reason) {
101665
+ return this.registry.quarantineAll(reason);
101666
+ }
101667
+ close() {
101668
+ this.quarantineAll("harness shutdown");
101669
+ this.server?.close();
101670
+ this.server = null;
101671
+ }
101672
+ async prepareOnce() {
101673
+ const server = createServer4((request, response) => {
101674
+ void this.handleRequest(request, response);
101675
+ });
101676
+ this.server = server;
101677
+ await new Promise((resolve6, reject) => {
101678
+ server.once("error", reject);
101679
+ server.listen(0, "127.0.0.1", () => {
101680
+ server.off("error", reject);
101681
+ resolve6();
101682
+ });
101683
+ });
101684
+ const address = server.address();
101685
+ if (!address || typeof address === "string") {
101686
+ server.close();
101687
+ throw new Error("ask-user MCP server did not receive a TCP address");
101688
+ }
101689
+ server.unref();
101690
+ return {
101691
+ url: `http://127.0.0.1:${address.port}${this.path}`,
101692
+ headers: {
101693
+ [AGENT_BRIDGE_MCP_CAPABILITY_HEADER]: this.capability
101694
+ }
101695
+ };
101696
+ }
101697
+ async handleRequest(request, response) {
101698
+ if (request.url !== this.path || request.method !== "POST") {
101699
+ response.writeHead(request.url === this.path ? 405 : 404).end();
101700
+ return;
101701
+ }
101702
+ if (!requestHasMcpCapability(request.headers, this.capability)) {
101703
+ response.writeHead(401, { "content-type": "application/json" }).end(JSON.stringify({ error: "Invalid ask-user MCP capability" }));
101704
+ return;
101705
+ }
101706
+ try {
101707
+ await this.handleMcpRequest(request, response);
101708
+ } catch (error51) {
101709
+ this.options.writeOutput?.(
101710
+ `agent_bridge_ask_user_http_error error=${errorMessage2(error51)}`
101711
+ );
101712
+ if (!response.headersSent) {
101713
+ response.writeHead(500, { "content-type": "application/json" });
101714
+ response.end(
101715
+ JSON.stringify({
101716
+ jsonrpc: "2.0",
101717
+ id: null,
101718
+ error: { code: -32603, message: "Ask-user server failed" }
101719
+ })
101720
+ );
101721
+ }
101722
+ }
101723
+ }
101724
+ async handleMcpRequest(request, response) {
101725
+ const body = await readJsonRequest(request);
101726
+ this.options.writeOutput?.(
101727
+ `agent_bridge_ask_user_mcp_request method=${typeof body.method === "string" ? body.method : "unknown"}`
101728
+ );
101729
+ const server = this.createMcpServer();
101730
+ const transport = new StreamableHTTPServerTransport({
101731
+ sessionIdGenerator: void 0,
101732
+ // Codex 0.145.0 requires ordinary initialize/list responses as JSON. A
101733
+ // parked tool call keeps SSE enabled so progress notifications can flow
101734
+ // over the held response without extending the hard client deadline.
101735
+ enableJsonResponse: body.method !== "tools/call"
101736
+ });
101737
+ response.once("close", () => {
101738
+ void transport.close();
101739
+ void server.close();
101740
+ });
101741
+ await server.connect(transport);
101742
+ await transport.handleRequest(request, response, body);
101743
+ }
101744
+ createMcpServer() {
101745
+ const server = new McpProtocolServer(
101746
+ { name: "auto-ask-user", version: "1.0.0" },
101747
+ { capabilities: { tools: {} } }
101748
+ );
101749
+ server.setRequestHandler(ListToolsRequestSchema2, () => ({
101750
+ tools: [
101751
+ {
101752
+ name: ASK_USER_MCP_TOOL_NAME,
101753
+ title: ASK_USER_TOOL_NAME,
101754
+ description: "Ask the user one to four blocking questions and wait for their answer.",
101755
+ inputSchema: external_exports.toJSONSchema(AskUserQuestionInputSchema2)
101756
+ }
101757
+ ]
101758
+ }));
101759
+ server.setRequestHandler(
101760
+ CallToolRequestSchema2,
101761
+ async (request, extra) => {
101762
+ if (request.params.name !== ASK_USER_MCP_TOOL_NAME) {
101763
+ throw new Error(`Unknown ask-user tool: ${request.params.name}`);
101764
+ }
101765
+ const { questions } = AskUserQuestionInputSchema2.parse(
101766
+ request.params.arguments
101767
+ );
101768
+ let questionId = "";
101769
+ const parked = this.registry.park({
101770
+ questions,
101771
+ onProgress: async () => {
101772
+ const progressToken = extra._meta?.progressToken;
101773
+ if (progressToken === void 0) {
101774
+ return;
101775
+ }
101776
+ await extra.sendNotification({
101777
+ method: "notifications/progress",
101778
+ params: {
101779
+ progressToken,
101780
+ progress: Date.now(),
101781
+ message: "Waiting for the user to answer"
101782
+ }
101783
+ });
101784
+ },
101785
+ onSettled: (settlement2) => {
101786
+ this.options.onQuestionSettled?.({ id: questionId, settlement: settlement2 });
101787
+ }
101788
+ });
101789
+ questionId = parked.id;
101790
+ try {
101791
+ await this.options.onQuestionPending({
101792
+ id: parked.id,
101793
+ questions
101794
+ });
101795
+ } catch (error51) {
101796
+ this.registry.quarantine(parked.id, "question projection failed");
101797
+ throw error51;
101798
+ }
101799
+ const settlement = await parked.wait;
101800
+ return settlementResult(settlement);
101801
+ }
101802
+ );
101803
+ return server;
101804
+ }
101805
+ };
101806
+ function settlementResult(settlement) {
101807
+ if (settlement.kind === "fallback") {
101808
+ return {
101809
+ content: [{ type: "text", text: settlement.message }]
101810
+ };
101811
+ }
101812
+ const answer = settlement.answer;
101813
+ const lines = Object.entries(answer.answers).map(
101814
+ ([question, value]) => `- ${question}: ${value}`
101815
+ );
101816
+ return {
101817
+ content: [
101818
+ {
101819
+ type: "text",
101820
+ text: [
101821
+ "The user answered your questions:",
101822
+ ...lines,
101823
+ ...answer.response ? [`The user also responded: ${answer.response}`] : []
101824
+ ].join("\n")
101825
+ }
101826
+ ]
101827
+ };
101828
+ }
101829
+ function positiveDuration(value, label) {
101830
+ if (!Number.isSafeInteger(value) || value < 1) {
101831
+ throw new Error(`${label} must be a positive safe integer`);
101832
+ }
101833
+ return value;
101834
+ }
101835
+ function errorMessage2(error51) {
101836
+ return error51 instanceof Error ? error51.message : String(error51);
101837
+ }
101838
+ async function readJsonRequest(request) {
101839
+ const chunks = [];
101840
+ for await (const chunk of request) {
101841
+ chunks.push(Buffer.from(chunk));
101842
+ }
101843
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
101844
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
101845
+ throw new Error("ask-user MCP request body must be a JSON object");
101846
+ }
101847
+ return parsed;
101848
+ }
101849
+
101471
101850
  // src/commands/agent-bridge/harness/liveness-ticker.ts
101472
101851
  var RUNTIME_LIVENESS_INTERVAL_MS = 2e4;
101473
101852
  function startRuntimeLivenessTicker(input) {
@@ -102597,7 +102976,7 @@ function isTerminalUiMessageChunk(chunk) {
102597
102976
  function legacyToolEntries(chunk) {
102598
102977
  switch (chunk.type) {
102599
102978
  case "tool-input-available":
102600
- if (chunk.toolName === ASK_USER_QUESTION_TOOL_NAME) {
102979
+ if (chunk.toolName === ASK_USER_QUESTION_TOOL_NAME || chunk.toolName === ASK_USER_TOOL_NAME) {
102601
102980
  return [];
102602
102981
  }
102603
102982
  return [
@@ -102921,6 +103300,111 @@ function jsonRecordString2(value, key) {
102921
103300
  return typeof field === "string" ? field : void 0;
102922
103301
  }
102923
103302
 
103303
+ // src/commands/agent-bridge/harness/mcp-timeout.ts
103304
+ var HARNESS_MCP_TOOL_TIMEOUT_SECONDS = 900;
103305
+ var MAX_HARNESS_MCP_TOOL_TIMEOUT_SECONDS = 3600;
103306
+ function validatedMcpToolTimeoutSeconds(value) {
103307
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_HARNESS_MCP_TOOL_TIMEOUT_SECONDS) {
103308
+ throw new Error(
103309
+ `MCP tool timeout must be a safe integer from 1 to ${MAX_HARNESS_MCP_TOOL_TIMEOUT_SECONDS} seconds`
103310
+ );
103311
+ }
103312
+ return value;
103313
+ }
103314
+ function validatedMcpToolTimeoutMilliseconds(value) {
103315
+ return validatedMcpToolTimeoutSeconds(value) * 1e3;
103316
+ }
103317
+
103318
+ // src/commands/agent-bridge/harness/claude-code/options.ts
103319
+ var CLAUDE_AGENT_SDK_CLIENT_APP = "auto-agent-bridge";
103320
+ var CLAUDE_RUNTIME_PROCESS_ENV_KEYS = ["PATH", "HOME"];
103321
+ var CLAUDE_CODE_EXECUTABLE_PATH = "/usr/local/bin/claude";
103322
+ var CLAUDE_CODE_SHELL_PATH = "/bin/bash";
103323
+ var CLAUDE_CODE_DEFAULT_MODEL = "claude-opus-4-8";
103324
+ function claudeAgentOptions(config2, input = {}) {
103325
+ return {
103326
+ ...config2.cwd ? { cwd: config2.cwd } : {},
103327
+ ...input.resume ? { resume: input.resume } : {},
103328
+ env: claudeProcessEnv(
103329
+ config2.env,
103330
+ input.clientApp ?? CLAUDE_AGENT_SDK_CLIENT_APP
103331
+ ),
103332
+ includePartialMessages: true,
103333
+ thinking: { type: "adaptive", display: "summarized" },
103334
+ settings: { showThinkingSummaries: true },
103335
+ model: config2.model?.id ?? CLAUDE_CODE_DEFAULT_MODEL,
103336
+ ...config2.reasoningEffort ? { effort: config2.reasoningEffort } : {},
103337
+ permissionMode: "bypassPermissions",
103338
+ allowDangerouslySkipPermissions: true,
103339
+ pathToClaudeCodeExecutable: claudeCodeExecutablePath(),
103340
+ ...input.mcpServers ?? config2.mcpServers ? {
103341
+ mcpServers: input.mcpServers ?? config2.mcpServers,
103342
+ strictMcpConfig: true
103343
+ } : {},
103344
+ systemPrompt: {
103345
+ type: "preset",
103346
+ preset: "claude_code",
103347
+ ...config2.systemPromptAppend ? { append: config2.systemPromptAppend } : {}
103348
+ },
103349
+ settingSources: [],
103350
+ tools: {
103351
+ type: "preset",
103352
+ preset: "claude_code"
103353
+ },
103354
+ stderr: input.onStderrChunk ?? (() => {
103355
+ })
103356
+ };
103357
+ }
103358
+ function withClaudeAskUserMcpServer(mcpServers, url2, headers) {
103359
+ if (mcpServers?.[ASK_USER_MCP_SERVER_NAME]) {
103360
+ throw new Error(
103361
+ `MCP server name ${ASK_USER_MCP_SERVER_NAME} is reserved by the harness`
103362
+ );
103363
+ }
103364
+ return {
103365
+ ...mcpServers,
103366
+ [ASK_USER_MCP_SERVER_NAME]: {
103367
+ type: "http",
103368
+ url: url2,
103369
+ headers,
103370
+ timeout: validatedMcpToolTimeoutMilliseconds(
103371
+ HARNESS_MCP_TOOL_TIMEOUT_SECONDS
103372
+ )
103373
+ }
103374
+ };
103375
+ }
103376
+ function claudeProcessEnv(env, clientApp) {
103377
+ const runtimeEnv = runtimeProcessEnv();
103378
+ return {
103379
+ // Claude Code only needs ambient shell basics from the bridge process.
103380
+ // Dynamic Claude/MCP/session secrets arrive through encrypted bootstrap env.
103381
+ ...runtimeEnv,
103382
+ ...env,
103383
+ // Claude Code auto-detects a shell when this is absent. Hosted sandboxes run
103384
+ // Bash tool commands, so pin the shell snapshot source to bash instead of
103385
+ // letting zsh profile functions leak into bash snapshots and inherit user
103386
+ // strict-mode options such as `set -u`.
103387
+ CLAUDE_CODE_SHELL: CLAUDE_CODE_SHELL_PATH,
103388
+ CLAUDE_AGENT_SDK_CLIENT_APP: clientApp
103389
+ };
103390
+ }
103391
+ function runtimeProcessEnv() {
103392
+ const selected = {};
103393
+ for (const key of CLAUDE_RUNTIME_PROCESS_ENV_KEYS) {
103394
+ const value = process.env[key];
103395
+ if (value !== void 0) {
103396
+ selected[key] = value;
103397
+ }
103398
+ }
103399
+ return selected;
103400
+ }
103401
+ function claudeCodeExecutablePath() {
103402
+ if (process.env.AUTO_AGENT_BRIDGE_TEST_CLAUDE_COMMAND === "1" && process.env.AUTO_CLAUDE_CODE_COMMAND?.trim()) {
103403
+ return process.env.AUTO_CLAUDE_CODE_COMMAND.trim();
103404
+ }
103405
+ return CLAUDE_CODE_EXECUTABLE_PATH;
103406
+ }
103407
+
102924
103408
  // src/commands/agent-bridge/harness/claude-code/projector.ts
102925
103409
  init_src();
102926
103410
  var ClaudeCodeProjector = class {
@@ -102939,6 +103423,7 @@ var ClaudeCodeProjector = class {
102939
103423
  activeResponseMessageId = null;
102940
103424
  activeParts = /* @__PURE__ */ new Map();
102941
103425
  activeToolInputs = /* @__PURE__ */ new Map();
103426
+ suppressedAskUserToolCallIds = /* @__PURE__ */ new Set();
102942
103427
  streamedMessageIds = /* @__PURE__ */ new Set();
102943
103428
  // The SDK's "[Request interrupted by user]" transcript marker was seen since
102944
103429
  // the last terminal result: the very next diagnostic-only
@@ -102958,6 +103443,7 @@ var ClaudeCodeProjector = class {
102958
103443
  bridge: context.bridgeInterruptSettling === true
102959
103444
  };
102960
103445
  const parsed = parseClaudeCodeStreamRecord(message);
103446
+ parsed.projections = this.suppressAskUserProjections(parsed.projections);
102961
103447
  const snapshotMessageId = assistantSnapshotMessageId(message);
102962
103448
  const suppressStreamed = snapshotMessageId !== null && this.streamedMessageIds.has(snapshotMessageId);
102963
103449
  if (message.type === "assistant" && !suppressStreamed) {
@@ -102990,6 +103476,7 @@ var ClaudeCodeProjector = class {
102990
103476
  );
102991
103477
  }
102992
103478
  outputs.push(projectClaudeCodeResult(parsed.result, { interrupted }));
103479
+ this.suppressedAskUserToolCallIds.clear();
102993
103480
  this.interruptMarkerSeen = false;
102994
103481
  }
102995
103482
  return outputs;
@@ -103074,6 +103561,11 @@ var ClaudeCodeProjector = class {
103074
103561
  return [uiChunk({ type: `${type}-end`, id })];
103075
103562
  }
103076
103563
  startToolInput(message) {
103564
+ const askUserToolCallId = askUserMcpToolCallId(message);
103565
+ if (askUserToolCallId) {
103566
+ this.suppressedAskUserToolCallIds.add(askUserToolCallId);
103567
+ return [];
103568
+ }
103077
103569
  const started = toolInputStartFromEvent(message);
103078
103570
  if (!started) {
103079
103571
  return [];
@@ -103092,6 +103584,32 @@ var ClaudeCodeProjector = class {
103092
103584
  })
103093
103585
  ];
103094
103586
  }
103587
+ suppressAskUserProjections(projections) {
103588
+ return projections.flatMap((projection) => {
103589
+ if (projection.kind === "tool_call") {
103590
+ const askUserParts = projection.content.parts.filter(
103591
+ (part) => part.type === "tool_call" && (part.name === ASK_USER_TOOL_NAME || part.name === CLAUDE_ASK_USER_TOOL_NAME)
103592
+ );
103593
+ for (const part of askUserParts) {
103594
+ if (part.type === "tool_call" && part.toolCallId) {
103595
+ this.suppressedAskUserToolCallIds.add(part.toolCallId);
103596
+ }
103597
+ }
103598
+ if (askUserParts.length > 0) {
103599
+ return [];
103600
+ }
103601
+ }
103602
+ if (projection.kind === "tool_result") {
103603
+ const suppressed = projection.content.parts.some(
103604
+ (part) => part.type === "tool_result" && part.toolUseId !== null && this.suppressedAskUserToolCallIds.has(part.toolUseId)
103605
+ );
103606
+ if (suppressed) {
103607
+ return [];
103608
+ }
103609
+ }
103610
+ return [projection];
103611
+ });
103612
+ }
103095
103613
  finishActiveToolInput(index) {
103096
103614
  const active = this.activeToolInputs.get(index);
103097
103615
  if (!active) {
@@ -103288,6 +103806,17 @@ function toolInputStartFromEvent(message) {
103288
103806
  input: contentBlock.input
103289
103807
  };
103290
103808
  }
103809
+ function askUserMcpToolCallId(message) {
103810
+ const event = message.event;
103811
+ if (event.type !== "content_block_start") {
103812
+ return null;
103813
+ }
103814
+ const block = event.content_block;
103815
+ if (block.type !== "mcp_tool_use" || typeof block.id !== "string" || typeof block.server_name !== "string" || typeof block.name !== "string" || !isAskUserMcpTool(block.server_name, block.name)) {
103816
+ return null;
103817
+ }
103818
+ return block.id;
103819
+ }
103291
103820
  function toolInputDeltaFromDelta(delta) {
103292
103821
  if (delta.type !== "input_json_delta") {
103293
103822
  return null;
@@ -103808,78 +104337,6 @@ function serverStatusList(servers, context) {
103808
104337
  return reported.map((server) => `${server.name}:${server.status}`).join(",");
103809
104338
  }
103810
104339
 
103811
- // src/commands/agent-bridge/harness/claude-code/options.ts
103812
- var CLAUDE_AGENT_SDK_CLIENT_APP = "auto-agent-bridge";
103813
- var CLAUDE_RUNTIME_PROCESS_ENV_KEYS = ["PATH", "HOME"];
103814
- var CLAUDE_CODE_EXECUTABLE_PATH = "/usr/local/bin/claude";
103815
- var CLAUDE_CODE_SHELL_PATH = "/bin/bash";
103816
- var CLAUDE_CODE_DEFAULT_MODEL = "claude-opus-4-8";
103817
- function claudeAgentOptions(config2, input = {}) {
103818
- return {
103819
- ...config2.cwd ? { cwd: config2.cwd } : {},
103820
- ...input.resume ? { resume: input.resume } : {},
103821
- env: claudeProcessEnv(
103822
- config2.env,
103823
- input.clientApp ?? CLAUDE_AGENT_SDK_CLIENT_APP
103824
- ),
103825
- includePartialMessages: true,
103826
- thinking: { type: "adaptive", display: "summarized" },
103827
- settings: { showThinkingSummaries: true },
103828
- model: config2.model?.id ?? CLAUDE_CODE_DEFAULT_MODEL,
103829
- ...config2.reasoningEffort ? { effort: config2.reasoningEffort } : {},
103830
- permissionMode: "bypassPermissions",
103831
- allowDangerouslySkipPermissions: true,
103832
- pathToClaudeCodeExecutable: claudeCodeExecutablePath(),
103833
- ...input.mcpServers ?? config2.mcpServers ? {
103834
- mcpServers: input.mcpServers ?? config2.mcpServers,
103835
- strictMcpConfig: true
103836
- } : {},
103837
- systemPrompt: {
103838
- type: "preset",
103839
- preset: "claude_code",
103840
- ...config2.systemPromptAppend ? { append: config2.systemPromptAppend } : {}
103841
- },
103842
- settingSources: [],
103843
- tools: {
103844
- type: "preset",
103845
- preset: "claude_code"
103846
- },
103847
- stderr: input.onStderrChunk ?? (() => {
103848
- })
103849
- };
103850
- }
103851
- function claudeProcessEnv(env, clientApp) {
103852
- const runtimeEnv = runtimeProcessEnv();
103853
- return {
103854
- // Claude Code only needs ambient shell basics from the bridge process.
103855
- // Dynamic Claude/MCP/session secrets arrive through encrypted bootstrap env.
103856
- ...runtimeEnv,
103857
- ...env,
103858
- // Claude Code auto-detects a shell when this is absent. Hosted sandboxes run
103859
- // Bash tool commands, so pin the shell snapshot source to bash instead of
103860
- // letting zsh profile functions leak into bash snapshots and inherit user
103861
- // strict-mode options such as `set -u`.
103862
- CLAUDE_CODE_SHELL: CLAUDE_CODE_SHELL_PATH,
103863
- CLAUDE_AGENT_SDK_CLIENT_APP: clientApp
103864
- };
103865
- }
103866
- function runtimeProcessEnv() {
103867
- const selected = {};
103868
- for (const key of CLAUDE_RUNTIME_PROCESS_ENV_KEYS) {
103869
- const value = process.env[key];
103870
- if (value !== void 0) {
103871
- selected[key] = value;
103872
- }
103873
- }
103874
- return selected;
103875
- }
103876
- function claudeCodeExecutablePath() {
103877
- if (process.env.AUTO_AGENT_BRIDGE_TEST_CLAUDE_COMMAND === "1" && process.env.AUTO_CLAUDE_CODE_COMMAND?.trim()) {
103878
- return process.env.AUTO_CLAUDE_CODE_COMMAND.trim();
103879
- }
103880
- return CLAUDE_CODE_EXECUTABLE_PATH;
103881
- }
103882
-
103883
104340
  // src/commands/agent-bridge/harness/claude-code/stderr.ts
103884
104341
  var STDERR_LINE_MAX_CHARS = 400;
103885
104342
  var STDERR_FORWARD_MAX_LINES = 200;
@@ -105055,6 +105512,10 @@ var ClaudeCodeCommandHandler = class {
105055
105512
  mcpServers: input.claude.mcpServers,
105056
105513
  writeOutput: input.writeOutput
105057
105514
  });
105515
+ this.askUserServer = new AskUserServer({
105516
+ onQuestionPending: (question) => this.handleAskUserQuestion(question),
105517
+ writeOutput: input.writeOutput
105518
+ });
105058
105519
  this.outputBuffer = new AgentBridgeOutputBuffer(input);
105059
105520
  this.projector = new ClaudeCodeProjector({
105060
105521
  writeOutput: input.writeOutput
@@ -105067,6 +105528,7 @@ var ClaudeCodeCommandHandler = class {
105067
105528
  claudeConfig;
105068
105529
  claudeMcpServers;
105069
105530
  autoMcpShim;
105531
+ askUserServer;
105070
105532
  // Selection-change restarts happen in-process, so prefer the live SDK id even
105071
105533
  // when the file resume store is stale or temporarily unreadable.
105072
105534
  lastObservedAgentId = null;
@@ -105102,7 +105564,13 @@ var ClaudeCodeCommandHandler = class {
105102
105564
  return this.outputBuffer.pendingOutputStats();
105103
105565
  }
105104
105566
  async prepare() {
105105
- this.claudeMcpServers = await this.autoMcpShim.prepareClaude();
105567
+ const mcpServers = await this.autoMcpShim.prepareClaude();
105568
+ const askUserEndpoint = await this.askUserServer.prepare();
105569
+ this.claudeMcpServers = withClaudeAskUserMcpServer(
105570
+ mcpServers,
105571
+ askUserEndpoint.url,
105572
+ askUserEndpoint.headers
105573
+ );
105106
105574
  await this.ensureAgentSession().prepare();
105107
105575
  }
105108
105576
  shutdown() {
@@ -105111,6 +105579,7 @@ var ClaudeCodeCommandHandler = class {
105111
105579
  this.agentSession?.close();
105112
105580
  this.agentSession = null;
105113
105581
  this.autoMcpShim.close();
105582
+ this.askUserServer.close();
105114
105583
  this.settlePendingQuestions("Runtime is shutting down");
105115
105584
  }
105116
105585
  async handleCommand(rawDelivery) {
@@ -105170,6 +105639,7 @@ var ClaudeCodeCommandHandler = class {
105170
105639
  }
105171
105640
  if (delivery.kind === "interrupt") {
105172
105641
  this.injectedCommands.add(delivery.commandId);
105642
+ this.askUserServer.quarantineAll("turn interrupted");
105173
105643
  await this.agentSession?.interruptActiveTurn();
105174
105644
  this.input.runtimeLogger?.info(
105175
105645
  "agent_bridge_claude_command_interrupt_ready",
@@ -105378,6 +105848,17 @@ var ClaudeCodeCommandHandler = class {
105378
105848
  );
105379
105849
  return;
105380
105850
  }
105851
+ if (this.askUserServer.answer(answer)) {
105852
+ this.agentSession?.noteActiveTurnCommand(delivery.commandId);
105853
+ await this.emitRequiredBridgeOutput(
105854
+ activeContext,
105855
+ projectAskUserAnswer(answer)
105856
+ );
105857
+ this.input.writeOutput?.(
105858
+ `agent_bridge_question_answered tool_use_id=${answer.toolCallId}`
105859
+ );
105860
+ return;
105861
+ }
105381
105862
  const message = answerFallbackMessage(answer);
105382
105863
  await this.emitRequiredBridgeOutput(activeContext, {
105383
105864
  type: "entry",
@@ -105408,11 +105889,28 @@ var ClaudeCodeCommandHandler = class {
105408
105889
  }
105409
105890
  this.pendingQuestions.clear();
105410
105891
  }
105892
+ async handleAskUserQuestion(input) {
105893
+ const activeContext = this.context;
105894
+ if (!activeContext) {
105895
+ throw new Error(
105896
+ "Cannot publish an ask-user question without bridge context"
105897
+ );
105898
+ }
105899
+ this.input.writeOutput?.(
105900
+ `agent_bridge_question_pending tool_use_id=${input.id}`
105901
+ );
105902
+ for (const projection of projectAskUserQuestion(input)) {
105903
+ await this.emitRequiredBridgeOutput(activeContext, projection);
105904
+ }
105905
+ }
105411
105906
  // Permission callback for the Claude Agent SDK session. The sessions with
105412
105907
  // bypassPermissions, so regular tools are auto-approved and only the
105413
105908
  // AskUserQuestion tool reaches this callback for a real decision: it parks
105414
105909
  // the SDK on a promise that an `answer` bridge command later resolves.
105415
105910
  canUseTool = (toolName, toolInput, options) => {
105911
+ if (toolName === CLAUDE_ASK_USER_TOOL_NAME) {
105912
+ return Promise.resolve({ behavior: "allow", updatedInput: toolInput });
105913
+ }
105416
105914
  if (toolName !== ASK_USER_QUESTION_TOOL_NAME) {
105417
105915
  return Promise.resolve({ behavior: "allow", updatedInput: toolInput });
105418
105916
  }
@@ -105491,6 +105989,9 @@ var ClaudeCodeCommandHandler = class {
105491
105989
  }
105492
105990
  this.persistAgentId(activeContext, message);
105493
105991
  this.trackReadState(activeContext, message);
105992
+ if (message.type === "result") {
105993
+ this.askUserServer.quarantineAll("turn ended");
105994
+ }
105494
105995
  let projections;
105495
105996
  try {
105496
105997
  projections = this.projector.project(message, {
@@ -105510,6 +106011,7 @@ var ClaudeCodeCommandHandler = class {
105510
106011
  }
105511
106012
  }
105512
106013
  async handleAgentError(error51) {
106014
+ this.askUserServer.quarantineAll("harness session failed");
105513
106015
  this.input.writeOutput?.(
105514
106016
  `agent_bridge_claude_sdk_failed error=${error51 instanceof Error ? error51.message : String(error51)}`
105515
106017
  );
@@ -105629,6 +106131,7 @@ var ClaudeCodeCommandHandler = class {
105629
106131
  onMessage: (message, meta3) => this.handleAgentMessage(message, meta3),
105630
106132
  onError: (error51) => this.handleAgentError(error51),
105631
106133
  onExit: () => {
106134
+ this.askUserServer.quarantineAll("harness child exited");
105632
106135
  if (this.agentSession === session) {
105633
106136
  this.agentSession = null;
105634
106137
  }
@@ -105657,6 +106160,7 @@ var ClaudeCodeCommandHandler = class {
105657
106160
  this.requireResumeForNextAgentSession = this.lastObservedAgentId !== null;
105658
106161
  this.agentSession?.close();
105659
106162
  this.agentSession = null;
106163
+ this.askUserServer.quarantineAll("harness session restarted");
105660
106164
  this.settlePendingQuestions("The runtime restarted to change model");
105661
106165
  this.input.writeOutput?.(
105662
106166
  `agent_bridge_claude_model_selection_changed provider=${next.model?.provider ?? ""} model=${next.model?.id ?? ""} reasoning_effort=${next.reasoningEffort ?? ""}`
@@ -105850,6 +106354,9 @@ var CodexProjector = class {
105850
106354
  toolMetadata: statusMetadata(item.status)
105851
106355
  });
105852
106356
  case "mcpToolCall":
106357
+ if (isAskUserMcpTool(item.server, item.tool)) {
106358
+ return [];
106359
+ }
105853
106360
  return this.projectToolItem({
105854
106361
  phase,
105855
106362
  itemId: item.id,
@@ -106193,6 +106700,9 @@ function renderCodexConfigToml(config2) {
106193
106700
  lines.push("");
106194
106701
  lines.push(`[mcp_servers.${tomlKey(name)}]`);
106195
106702
  lines.push(`url = ${tomlString(server.url)}`);
106703
+ lines.push(
106704
+ `tool_timeout_sec = ${validatedMcpToolTimeoutSeconds(HARNESS_MCP_TOOL_TIMEOUT_SECONDS)}`
106705
+ );
106196
106706
  const persistedHeaders = Object.fromEntries(
106197
106707
  Object.entries(server.headers ?? {}).filter(
106198
106708
  ([name2]) => name2 !== AGENT_BRIDGE_MCP_CAPABILITY_HEADER
@@ -106206,20 +106716,30 @@ function renderCodexConfigToml(config2) {
106206
106716
  `;
106207
106717
  }
106208
106718
  function turnOnlyMcpConfig(mcpServers) {
106719
+ const hasTurnOnlyCapability = Object.values(mcpServers ?? {}).some(
106720
+ (server) => server.headers?.[AGENT_BRIDGE_MCP_CAPABILITY_HEADER] !== void 0
106721
+ );
106722
+ if (!hasTurnOnlyCapability) {
106723
+ return void 0;
106724
+ }
106209
106725
  const servers = Object.fromEntries(
106210
- Object.entries(mcpServers ?? {}).flatMap(([name, server]) => {
106726
+ Object.entries(mcpServers ?? {}).map(([name, server]) => {
106211
106727
  const capability = server.headers?.[AGENT_BRIDGE_MCP_CAPABILITY_HEADER];
106212
- return capability ? [
106213
- [
106214
- name,
106215
- {
106216
- url: server.url,
106728
+ return [
106729
+ name,
106730
+ {
106731
+ url: server.url,
106732
+ tool_timeout_sec: validatedMcpToolTimeoutSeconds(
106733
+ HARNESS_MCP_TOOL_TIMEOUT_SECONDS
106734
+ ),
106735
+ ...server.headers ? {
106217
106736
  http_headers: {
106218
- [AGENT_BRIDGE_MCP_CAPABILITY_HEADER]: capability
106737
+ ...server.headers,
106738
+ ...capability ? { [AGENT_BRIDGE_MCP_CAPABILITY_HEADER]: capability } : {}
106219
106739
  }
106220
- }
106221
- ]
106222
- ] : [];
106740
+ } : {}
106741
+ }
106742
+ ];
106223
106743
  })
106224
106744
  );
106225
106745
  return Object.keys(servers).length > 0 ? { mcp_servers: servers } : void 0;
@@ -106813,7 +107333,7 @@ var CodexAgentBridgeSessionImpl = class {
106813
107333
  return;
106814
107334
  } catch (error51) {
106815
107335
  this.input.writeOutput?.(
106816
- `agent_bridge_codex_resume_fallback thread_id=${this.input.resumeThreadId} error=${errorMessage2(error51)}`
107336
+ `agent_bridge_codex_resume_fallback thread_id=${this.input.resumeThreadId} error=${errorMessage3(error51)}`
106817
107337
  );
106818
107338
  }
106819
107339
  }
@@ -106891,7 +107411,7 @@ var CodexAgentBridgeSessionImpl = class {
106891
107411
  this.activeCommandIds = previousCommandIds;
106892
107412
  this.publishActiveTurnCommandIds();
106893
107413
  this.input.writeOutput?.(
106894
- `agent_bridge_codex_steer_failed error=${errorMessage2(error51)}`
107414
+ `agent_bridge_codex_steer_failed error=${errorMessage3(error51)}`
106895
107415
  );
106896
107416
  }
106897
107417
  if (this.activeTurnId) {
@@ -106905,7 +107425,7 @@ var CodexAgentBridgeSessionImpl = class {
106905
107425
  this.input.writeOutput?.("agent_bridge_codex_interrupted");
106906
107426
  } catch (error51) {
106907
107427
  this.input.writeOutput?.(
106908
- `agent_bridge_codex_interrupt_failed error=${errorMessage2(error51)}`
107428
+ `agent_bridge_codex_interrupt_failed error=${errorMessage3(error51)}`
106909
107429
  );
106910
107430
  }
106911
107431
  }
@@ -106980,7 +107500,7 @@ var CodexAgentBridgeSessionImpl = class {
106980
107500
  message = parseCodexServerFrame(JSON.parse(line));
106981
107501
  } catch (error51) {
106982
107502
  this.input.writeOutput?.(
106983
- `agent_bridge_codex_parse_failed error=${errorMessage2(error51)}`
107503
+ `agent_bridge_codex_parse_failed error=${errorMessage3(error51)}`
106984
107504
  );
106985
107505
  return;
106986
107506
  }
@@ -107175,7 +107695,7 @@ var CodexAgentBridgeSessionImpl = class {
107175
107695
  );
107176
107696
  } catch (error51) {
107177
107697
  this.input.writeOutput?.(
107178
- `agent_bridge_codex_turn_retry_failed attempt=${input.attempt} error=${errorMessage2(error51)}`
107698
+ `agent_bridge_codex_turn_retry_failed attempt=${input.attempt} error=${errorMessage3(error51)}`
107179
107699
  );
107180
107700
  this.turnRetry = null;
107181
107701
  this.clearPendingRetryableError();
@@ -107390,7 +107910,7 @@ function threadIdFromResult(result) {
107390
107910
  const id = thread.id;
107391
107911
  return typeof id === "string" && id.length > 0 ? id : null;
107392
107912
  }
107393
- function errorMessage2(error51) {
107913
+ function errorMessage3(error51) {
107394
107914
  return error51 instanceof Error ? error51.message : String(error51);
107395
107915
  }
107396
107916
  function toErrnoException(error51) {
@@ -107416,6 +107936,11 @@ var CodexCommandHandler = class {
107416
107936
  mcpServers: input.codex.mcpServers,
107417
107937
  writeOutput: input.writeOutput
107418
107938
  });
107939
+ this.askUserServer = new AskUserServer({
107940
+ onQuestionPending: (question) => this.handleAskUserQuestion(question),
107941
+ onQuestionSettled: ({ id }) => this.pendingQuestions.delete(id),
107942
+ writeOutput: input.writeOutput
107943
+ });
107419
107944
  this.outputBuffer = new AgentBridgeOutputBuffer(input);
107420
107945
  }
107421
107946
  input;
@@ -107423,10 +107948,12 @@ var CodexCommandHandler = class {
107423
107948
  session = null;
107424
107949
  codexConfig;
107425
107950
  autoMcpShim;
107951
+ askUserServer;
107426
107952
  injectedCommands = /* @__PURE__ */ new Set();
107427
107953
  // itemId -> JSON-RPC request id of the parked approval request, so an `answer`
107428
107954
  // command keyed by toolCallId (= itemId) can resolve the right server request.
107429
107955
  pendingApprovals = /* @__PURE__ */ new Map();
107956
+ pendingQuestions = /* @__PURE__ */ new Set();
107430
107957
  outputBuffer;
107431
107958
  projector = new CodexProjector();
107432
107959
  livenessTicker = null;
@@ -107448,9 +107975,23 @@ var CodexCommandHandler = class {
107448
107975
  return this.outputBuffer.pendingOutputStats();
107449
107976
  }
107450
107977
  async prepare() {
107978
+ const mcpServers = await this.autoMcpShim.prepare();
107979
+ if (mcpServers?.[ASK_USER_MCP_SERVER_NAME]) {
107980
+ throw new Error(
107981
+ `MCP server name ${ASK_USER_MCP_SERVER_NAME} is reserved by the harness`
107982
+ );
107983
+ }
107984
+ const askUserEndpoint = await this.askUserServer.prepare();
107451
107985
  this.codexConfig = {
107452
107986
  ...this.codexConfig,
107453
- mcpServers: await this.autoMcpShim.prepare()
107987
+ mcpServers: {
107988
+ ...mcpServers,
107989
+ [ASK_USER_MCP_SERVER_NAME]: {
107990
+ type: "http",
107991
+ url: askUserEndpoint.url,
107992
+ headers: askUserEndpoint.headers
107993
+ }
107994
+ }
107454
107995
  };
107455
107996
  await this.ensureSession().prepare();
107456
107997
  }
@@ -107460,7 +108001,9 @@ var CodexCommandHandler = class {
107460
108001
  this.session?.close();
107461
108002
  this.session = null;
107462
108003
  this.autoMcpShim.close();
108004
+ this.askUserServer.close();
107463
108005
  this.pendingApprovals.clear();
108006
+ this.pendingQuestions.clear();
107464
108007
  }
107465
108008
  async handleCommand(rawDelivery) {
107466
108009
  const delivery = RuntimeBridgeCommandDeliverySchema.parse(rawDelivery);
@@ -107477,6 +108020,7 @@ var CodexCommandHandler = class {
107477
108020
  }
107478
108021
  if (delivery.kind === "interrupt") {
107479
108022
  this.injectedCommands.add(delivery.commandId);
108023
+ this.askUserServer.quarantineAll("turn interrupted");
107480
108024
  await this.session?.interruptActiveTurn();
107481
108025
  return commandAck({ delivery, socketId, status: "injected" });
107482
108026
  }
@@ -107520,7 +108064,7 @@ var CodexCommandHandler = class {
107520
108064
  delivery,
107521
108065
  socketId,
107522
108066
  status: "failed",
107523
- error: errorMessage3(error51)
108067
+ error: errorMessage4(error51)
107524
108068
  });
107525
108069
  }
107526
108070
  return commandAck({ delivery, socketId, status: "injected" });
@@ -107553,6 +108097,11 @@ var CodexCommandHandler = class {
107553
108097
  this.input.writeOutput?.(
107554
108098
  `agent_bridge_codex_question_answered tool_use_id=${payload.data.toolCallId} decision=${decision}`
107555
108099
  );
108100
+ } else if (this.pendingQuestions.has(payload.data.toolCallId) && this.askUserServer.answer(payload.data)) {
108101
+ await this.emit(activeContext, projectAskUserAnswer(payload.data));
108102
+ this.input.writeOutput?.(
108103
+ `agent_bridge_codex_question_answered tool_use_id=${payload.data.toolCallId} decision=answered`
108104
+ );
107556
108105
  } else {
107557
108106
  const message = answerFallbackMessage2(payload.data);
107558
108107
  await this.emitUserMessageEntry(
@@ -107570,7 +108119,7 @@ var CodexCommandHandler = class {
107570
108119
  delivery,
107571
108120
  socketId,
107572
108121
  status: "failed",
107573
- error: errorMessage3(error51)
108122
+ error: errorMessage4(error51)
107574
108123
  });
107575
108124
  }
107576
108125
  return commandAck({ delivery, socketId, status: "injected" });
@@ -107598,6 +108147,9 @@ var CodexCommandHandler = class {
107598
108147
  if (notification.type === "tokenUsage") {
107599
108148
  return;
107600
108149
  }
108150
+ if (notification.type === "turnCompleted") {
108151
+ this.askUserServer.quarantineAll("turn ended");
108152
+ }
107601
108153
  for (const projection of this.projector.project(notification)) {
107602
108154
  await this.emit(activeContext, projection);
107603
108155
  }
@@ -107615,9 +108167,25 @@ var CodexCommandHandler = class {
107615
108167
  await this.emit(activeContext, projection);
107616
108168
  }
107617
108169
  }
108170
+ async handleAskUserQuestion(input) {
108171
+ const activeContext = this.context;
108172
+ if (!activeContext) {
108173
+ throw new Error(
108174
+ "Cannot publish an ask-user question without bridge context"
108175
+ );
108176
+ }
108177
+ this.pendingQuestions.add(input.id);
108178
+ this.input.writeOutput?.(
108179
+ `agent_bridge_codex_question_pending tool_use_id=${input.id}`
108180
+ );
108181
+ for (const projection of projectAskUserQuestion(input)) {
108182
+ await this.emitRequired(activeContext, projection);
108183
+ }
108184
+ }
107618
108185
  async handleSessionError(error51) {
108186
+ this.askUserServer.quarantineAll("harness session failed");
107619
108187
  this.input.writeOutput?.(
107620
- `agent_bridge_codex_session_failed error=${errorMessage3(error51)}`
108188
+ `agent_bridge_codex_session_failed error=${errorMessage4(error51)}`
107621
108189
  );
107622
108190
  const activeContext = this.context;
107623
108191
  if (!activeContext) {
@@ -107625,7 +108193,7 @@ var CodexCommandHandler = class {
107625
108193
  }
107626
108194
  await this.emit(
107627
108195
  activeContext,
107628
- this.projector.projectSessionFailure(errorMessage3(error51))
108196
+ this.projector.projectSessionFailure(errorMessage4(error51))
107629
108197
  );
107630
108198
  }
107631
108199
  async emitLivenessBeat() {
@@ -107650,10 +108218,16 @@ var CodexCommandHandler = class {
107650
108218
  await this.outputBuffer.emitProjection(activeContext, projection);
107651
108219
  } catch (error51) {
107652
108220
  this.input.writeOutput?.(
107653
- `agent_bridge_output_emit_failed error=${errorMessage3(error51)}`
108221
+ `agent_bridge_output_emit_failed error=${errorMessage4(error51)}`
107654
108222
  );
107655
108223
  }
107656
108224
  }
108225
+ async emitRequired(activeContext, projection) {
108226
+ await this.outputBuffer.emitProjection(activeContext, projection, {
108227
+ discardOnFailure: true,
108228
+ requireDrain: true
108229
+ });
108230
+ }
107657
108231
  ensureSession() {
107658
108232
  if (this.session) {
107659
108233
  return this.session;
@@ -107670,6 +108244,7 @@ var CodexCommandHandler = class {
107670
108244
  onUsageTurnChanged: (turnId) => this.emitUsageTurn(turnId),
107671
108245
  onError: (error51) => this.handleSessionError(error51),
107672
108246
  onExit: () => {
108247
+ this.askUserServer.quarantineAll("harness child exited");
107673
108248
  if (this.session === session) {
107674
108249
  this.session = null;
107675
108250
  }
@@ -107697,7 +108272,9 @@ var CodexCommandHandler = class {
107697
108272
  this.skipResumeForNextSession = true;
107698
108273
  this.session?.close();
107699
108274
  this.session = null;
108275
+ this.askUserServer.quarantineAll("harness session restarted");
107700
108276
  this.pendingApprovals.clear();
108277
+ this.pendingQuestions.clear();
107701
108278
  this.input.writeOutput?.(
107702
108279
  `agent_bridge_codex_model_selection_changed provider=${next.model?.provider ?? ""} model=${next.model?.id ?? ""} reasoning_effort=${next.reasoningEffort ?? ""}`
107703
108280
  );
@@ -107712,7 +108289,7 @@ var CodexCommandHandler = class {
107712
108289
  return store.read(activeContext.sessionId) ?? void 0;
107713
108290
  } catch (error51) {
107714
108291
  this.input.writeOutput?.(
107715
- `agent_bridge_codex_thread_read_failed error=${errorMessage3(error51)}`
108292
+ `agent_bridge_codex_thread_read_failed error=${errorMessage4(error51)}`
107716
108293
  );
107717
108294
  return void 0;
107718
108295
  }
@@ -107727,7 +108304,7 @@ var CodexCommandHandler = class {
107727
108304
  store.write({ sessionId: activeContext.sessionId, threadId });
107728
108305
  } catch (error51) {
107729
108306
  this.input.writeOutput?.(
107730
- `agent_bridge_codex_thread_persist_failed error=${errorMessage3(error51)}`
108307
+ `agent_bridge_codex_thread_persist_failed error=${errorMessage4(error51)}`
107731
108308
  );
107732
108309
  }
107733
108310
  }
@@ -107769,7 +108346,7 @@ function deliveryMode2(delivery) {
107769
108346
  }
107770
108347
  return "interrupt";
107771
108348
  }
107772
- function errorMessage3(error51) {
108349
+ function errorMessage4(error51) {
107773
108350
  return error51 instanceof Error ? error51.message : String(error51);
107774
108351
  }
107775
108352