@autohq/cli 0.1.583 → 0.1.585

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