@autohq/cli 0.1.582 → 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 +37639 -27101
  2. package/dist/index.js +706 -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) {
@@ -19375,7 +19375,7 @@ var init_trigger_filters = __esm({
19375
19375
  });
19376
19376
 
19377
19377
  // ../../packages/schemas/src/tasks.ts
19378
- var TASK_STATUSES, TaskStatusSchema, TASK_COMPLETION_KINDS, TaskCompletionKindSchema, TASK_LINK_ROLES, TaskLinkRoleSchema, TASK_LINK_STATUSES, TaskLinkStatusSchema, TASK_LINK_TARGET_TYPES, TaskLinkTargetTypeSchema, TaskLinkTargetSchema, TaskOutcomeSchema, TaskArchiveRequestSchema, TasksArchiveRequestSchema, TaskArchiveFilterSchema, TaskReadModelSchema, TaskListResponseSchema;
19378
+ var TASK_STATUSES, TaskStatusSchema, TASK_COMPLETION_KINDS, TaskCompletionKindSchema, TASK_LINK_ROLES, TaskLinkRoleSchema, TASK_LINK_STATUSES, TaskLinkStatusSchema, TASK_LINK_TARGET_TYPES, TaskLinkTargetTypeSchema, TaskLinkTargetSchema, TaskOutcomeSchema, TaskPullRequestSchema, TaskArchiveRequestSchema, TasksArchiveRequestSchema, TaskArchiveFilterSchema, TaskReadModelSchema, TaskListResponseSchema;
19379
19379
  var init_tasks = __esm({
19380
19380
  "../../packages/schemas/src/tasks.ts"() {
19381
19381
  "use strict";
@@ -19413,6 +19413,11 @@ var init_tasks = __esm({
19413
19413
  "Target type cannot be linked to a task"
19414
19414
  );
19415
19415
  TaskOutcomeSchema = JsonObjectSchema;
19416
+ TaskPullRequestSchema = external_exports.object({
19417
+ externalId: external_exports.string(),
19418
+ repository: external_exports.string(),
19419
+ number: external_exports.number().int().positive()
19420
+ });
19416
19421
  TaskArchiveRequestSchema = external_exports.object({ archived: external_exports.boolean() }).strict();
19417
19422
  TasksArchiveRequestSchema = external_exports.object({
19418
19423
  task_ids: external_exports.array(external_exports.string().trim().min(1)).min(1).max(100),
@@ -19432,6 +19437,7 @@ var init_tasks = __esm({
19432
19437
  context: JsonObjectSchema.nullable(),
19433
19438
  createdBySessionId: external_exports.string().nullable(),
19434
19439
  requester: JsonObjectSchema.nullable(),
19440
+ pullRequests: external_exports.array(TaskPullRequestSchema).optional(),
19435
19441
  revision: external_exports.number().int().positive(),
19436
19442
  archivedAt: external_exports.string().datetime().nullable(),
19437
19443
  createdAt: external_exports.string().datetime(),
@@ -88600,7 +88606,7 @@ var init_package = __esm({
88600
88606
  "package.json"() {
88601
88607
  package_default = {
88602
88608
  name: "@autohq/cli",
88603
- version: "0.1.582",
88609
+ version: "0.1.584",
88604
88610
  license: "SEE LICENSE IN README.md",
88605
88611
  publishConfig: {
88606
88612
  access: "public"
@@ -92331,9 +92337,9 @@ var init_actions = __esm({
92331
92337
  });
92332
92338
 
92333
92339
  // src/commands/auth/pkce.ts
92334
- import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
92340
+ import { createHash as createHash6, randomBytes as randomBytes4 } from "crypto";
92335
92341
  function pkceVerifier() {
92336
- return randomBytes3(32).toString("base64url");
92342
+ return randomBytes4(32).toString("base64url");
92337
92343
  }
92338
92344
  function pkceChallenge(verifier) {
92339
92345
  return createHash6("sha256").update(verifier).digest("base64url");
@@ -95898,7 +95904,7 @@ function SessionsView({
95898
95904
  visibleSessions.map((session, vi) => {
95899
95905
  const i = scrollOffset + vi;
95900
95906
  const title = sessionDisplayTitle(session);
95901
- const err = session.status === "failed" ? errorMessage4(session) : null;
95907
+ const err = session.status === "failed" ? errorMessage5(session) : null;
95902
95908
  const isSelected = i === safeIndex;
95903
95909
  const isMarked = selectedSessionIds.has(session.id);
95904
95910
  return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
@@ -96036,7 +96042,7 @@ function truncateToWidth(value, width) {
96036
96042
  function agentNameForRun(session) {
96037
96043
  return session.snapshot.metadata.name;
96038
96044
  }
96039
- function errorMessage4(session) {
96045
+ function errorMessage5(session) {
96040
96046
  if (session.error == null) return null;
96041
96047
  if (typeof session.error === "string") return session.error;
96042
96048
  const msg = readField(session.error, "message");
@@ -97471,7 +97477,7 @@ function HomeView({
97471
97477
  if (inspectedResource) {
97472
97478
  sectionTitle = `Inspect ${inspectedResource.kind}/${inspectedResource.name}`;
97473
97479
  }
97474
- 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);
97475
97481
  const isRunMutationPending = triggerSession.isPending || archiveSessions.isPending || unarchiveSessions.isPending || stopSessions.isPending;
97476
97482
  const isConnectionMutationPending = startConnection.isPending || allowConnection.isPending || removeConnection.isPending || createGithubSyncBinding.isPending;
97477
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;
@@ -97884,7 +97890,7 @@ function runActionTargetLabel(sessionIds, markedRunCount) {
97884
97890
  function pluralizeRunCount(count) {
97885
97891
  return `${count} ${count === 1 ? "session" : "sessions"}`;
97886
97892
  }
97887
- function errorMessage5(error51) {
97893
+ function errorMessage6(error51) {
97888
97894
  if (error51 instanceof Error) return error51.message;
97889
97895
  return error51 ? String(error51) : null;
97890
97896
  }
@@ -100727,7 +100733,7 @@ function jsonRecordString(value, key) {
100727
100733
  init_src();
100728
100734
 
100729
100735
  // src/commands/agent-bridge/harness/agent-facing-auto-mcp.ts
100730
- import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
100736
+ import { randomBytes as randomBytes2 } from "crypto";
100731
100737
  import { lstatSync, readFileSync as readFileSync3, realpathSync, statSync } from "fs";
100732
100738
  import {
100733
100739
  createServer as createServer3
@@ -100745,7 +100751,17 @@ import {
100745
100751
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
100746
100752
 
100747
100753
  // src/commands/agent-bridge/harness/turn-bound-mcp.ts
100754
+ import { timingSafeEqual } from "crypto";
100748
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
+ }
100749
100765
 
100750
100766
  // src/commands/agent-bridge/harness/agent-facing-auto-mcp.ts
100751
100767
  var MAX_DRY_RUN_PATH_FILES = 100;
@@ -101215,7 +101231,7 @@ async function startAutoMcpProxy(input) {
101215
101231
  let pathsCompatible = false;
101216
101232
  const server = createServer3(async (request, response) => {
101217
101233
  try {
101218
- if (!requestHasCapability(request.headers, input.capability)) {
101234
+ if (!requestHasMcpCapability(request.headers, input.capability)) {
101219
101235
  writeResponse(
101220
101236
  response,
101221
101237
  401,
@@ -101421,15 +101437,6 @@ function exactActiveCommandId(activeTurnCommandIds) {
101421
101437
  const commandIds = activeTurnCommandIds();
101422
101438
  return commandIds.length === 1 ? commandIds[0] ?? null : null;
101423
101439
  }
101424
- function requestHasCapability(headers, expected) {
101425
- const received = headers[AGENT_BRIDGE_MCP_CAPABILITY_HEADER];
101426
- if (typeof received !== "string") {
101427
- return false;
101428
- }
101429
- const expectedBytes = Buffer.from(expected);
101430
- const receivedBytes = Buffer.from(received);
101431
- return expectedBytes.length === receivedBytes.length && timingSafeEqual(expectedBytes, receivedBytes);
101432
- }
101433
101440
  function readRequestBody(request) {
101434
101441
  return new Promise((resolveBody, reject) => {
101435
101442
  const chunks = [];
@@ -101462,6 +101469,384 @@ function isRecord2(value) {
101462
101469
  return typeof value === "object" && value !== null && !Array.isArray(value);
101463
101470
  }
101464
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
+
101465
101850
  // src/commands/agent-bridge/harness/liveness-ticker.ts
101466
101851
  var RUNTIME_LIVENESS_INTERVAL_MS = 2e4;
101467
101852
  function startRuntimeLivenessTicker(input) {
@@ -102591,7 +102976,7 @@ function isTerminalUiMessageChunk(chunk) {
102591
102976
  function legacyToolEntries(chunk) {
102592
102977
  switch (chunk.type) {
102593
102978
  case "tool-input-available":
102594
- if (chunk.toolName === ASK_USER_QUESTION_TOOL_NAME) {
102979
+ if (chunk.toolName === ASK_USER_QUESTION_TOOL_NAME || chunk.toolName === ASK_USER_TOOL_NAME) {
102595
102980
  return [];
102596
102981
  }
102597
102982
  return [
@@ -102915,6 +103300,111 @@ function jsonRecordString2(value, key) {
102915
103300
  return typeof field === "string" ? field : void 0;
102916
103301
  }
102917
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
+
102918
103408
  // src/commands/agent-bridge/harness/claude-code/projector.ts
102919
103409
  init_src();
102920
103410
  var ClaudeCodeProjector = class {
@@ -102933,6 +103423,7 @@ var ClaudeCodeProjector = class {
102933
103423
  activeResponseMessageId = null;
102934
103424
  activeParts = /* @__PURE__ */ new Map();
102935
103425
  activeToolInputs = /* @__PURE__ */ new Map();
103426
+ suppressedAskUserToolCallIds = /* @__PURE__ */ new Set();
102936
103427
  streamedMessageIds = /* @__PURE__ */ new Set();
102937
103428
  // The SDK's "[Request interrupted by user]" transcript marker was seen since
102938
103429
  // the last terminal result: the very next diagnostic-only
@@ -102952,6 +103443,7 @@ var ClaudeCodeProjector = class {
102952
103443
  bridge: context.bridgeInterruptSettling === true
102953
103444
  };
102954
103445
  const parsed = parseClaudeCodeStreamRecord(message);
103446
+ parsed.projections = this.suppressAskUserProjections(parsed.projections);
102955
103447
  const snapshotMessageId = assistantSnapshotMessageId(message);
102956
103448
  const suppressStreamed = snapshotMessageId !== null && this.streamedMessageIds.has(snapshotMessageId);
102957
103449
  if (message.type === "assistant" && !suppressStreamed) {
@@ -102984,6 +103476,7 @@ var ClaudeCodeProjector = class {
102984
103476
  );
102985
103477
  }
102986
103478
  outputs.push(projectClaudeCodeResult(parsed.result, { interrupted }));
103479
+ this.suppressedAskUserToolCallIds.clear();
102987
103480
  this.interruptMarkerSeen = false;
102988
103481
  }
102989
103482
  return outputs;
@@ -103068,6 +103561,11 @@ var ClaudeCodeProjector = class {
103068
103561
  return [uiChunk({ type: `${type}-end`, id })];
103069
103562
  }
103070
103563
  startToolInput(message) {
103564
+ const askUserToolCallId = askUserMcpToolCallId(message);
103565
+ if (askUserToolCallId) {
103566
+ this.suppressedAskUserToolCallIds.add(askUserToolCallId);
103567
+ return [];
103568
+ }
103071
103569
  const started = toolInputStartFromEvent(message);
103072
103570
  if (!started) {
103073
103571
  return [];
@@ -103086,6 +103584,32 @@ var ClaudeCodeProjector = class {
103086
103584
  })
103087
103585
  ];
103088
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
+ }
103089
103613
  finishActiveToolInput(index) {
103090
103614
  const active = this.activeToolInputs.get(index);
103091
103615
  if (!active) {
@@ -103282,6 +103806,17 @@ function toolInputStartFromEvent(message) {
103282
103806
  input: contentBlock.input
103283
103807
  };
103284
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
+ }
103285
103820
  function toolInputDeltaFromDelta(delta) {
103286
103821
  if (delta.type !== "input_json_delta") {
103287
103822
  return null;
@@ -103802,78 +104337,6 @@ function serverStatusList(servers, context) {
103802
104337
  return reported.map((server) => `${server.name}:${server.status}`).join(",");
103803
104338
  }
103804
104339
 
103805
- // src/commands/agent-bridge/harness/claude-code/options.ts
103806
- var CLAUDE_AGENT_SDK_CLIENT_APP = "auto-agent-bridge";
103807
- var CLAUDE_RUNTIME_PROCESS_ENV_KEYS = ["PATH", "HOME"];
103808
- var CLAUDE_CODE_EXECUTABLE_PATH = "/usr/local/bin/claude";
103809
- var CLAUDE_CODE_SHELL_PATH = "/bin/bash";
103810
- var CLAUDE_CODE_DEFAULT_MODEL = "claude-opus-4-8";
103811
- function claudeAgentOptions(config2, input = {}) {
103812
- return {
103813
- ...config2.cwd ? { cwd: config2.cwd } : {},
103814
- ...input.resume ? { resume: input.resume } : {},
103815
- env: claudeProcessEnv(
103816
- config2.env,
103817
- input.clientApp ?? CLAUDE_AGENT_SDK_CLIENT_APP
103818
- ),
103819
- includePartialMessages: true,
103820
- thinking: { type: "adaptive", display: "summarized" },
103821
- settings: { showThinkingSummaries: true },
103822
- model: config2.model?.id ?? CLAUDE_CODE_DEFAULT_MODEL,
103823
- ...config2.reasoningEffort ? { effort: config2.reasoningEffort } : {},
103824
- permissionMode: "bypassPermissions",
103825
- allowDangerouslySkipPermissions: true,
103826
- pathToClaudeCodeExecutable: claudeCodeExecutablePath(),
103827
- ...input.mcpServers ?? config2.mcpServers ? {
103828
- mcpServers: input.mcpServers ?? config2.mcpServers,
103829
- strictMcpConfig: true
103830
- } : {},
103831
- systemPrompt: {
103832
- type: "preset",
103833
- preset: "claude_code",
103834
- ...config2.systemPromptAppend ? { append: config2.systemPromptAppend } : {}
103835
- },
103836
- settingSources: [],
103837
- tools: {
103838
- type: "preset",
103839
- preset: "claude_code"
103840
- },
103841
- stderr: input.onStderrChunk ?? (() => {
103842
- })
103843
- };
103844
- }
103845
- function claudeProcessEnv(env, clientApp) {
103846
- const runtimeEnv = runtimeProcessEnv();
103847
- return {
103848
- // Claude Code only needs ambient shell basics from the bridge process.
103849
- // Dynamic Claude/MCP/session secrets arrive through encrypted bootstrap env.
103850
- ...runtimeEnv,
103851
- ...env,
103852
- // Claude Code auto-detects a shell when this is absent. Hosted sandboxes run
103853
- // Bash tool commands, so pin the shell snapshot source to bash instead of
103854
- // letting zsh profile functions leak into bash snapshots and inherit user
103855
- // strict-mode options such as `set -u`.
103856
- CLAUDE_CODE_SHELL: CLAUDE_CODE_SHELL_PATH,
103857
- CLAUDE_AGENT_SDK_CLIENT_APP: clientApp
103858
- };
103859
- }
103860
- function runtimeProcessEnv() {
103861
- const selected = {};
103862
- for (const key of CLAUDE_RUNTIME_PROCESS_ENV_KEYS) {
103863
- const value = process.env[key];
103864
- if (value !== void 0) {
103865
- selected[key] = value;
103866
- }
103867
- }
103868
- return selected;
103869
- }
103870
- function claudeCodeExecutablePath() {
103871
- if (process.env.AUTO_AGENT_BRIDGE_TEST_CLAUDE_COMMAND === "1" && process.env.AUTO_CLAUDE_CODE_COMMAND?.trim()) {
103872
- return process.env.AUTO_CLAUDE_CODE_COMMAND.trim();
103873
- }
103874
- return CLAUDE_CODE_EXECUTABLE_PATH;
103875
- }
103876
-
103877
104340
  // src/commands/agent-bridge/harness/claude-code/stderr.ts
103878
104341
  var STDERR_LINE_MAX_CHARS = 400;
103879
104342
  var STDERR_FORWARD_MAX_LINES = 200;
@@ -105049,6 +105512,10 @@ var ClaudeCodeCommandHandler = class {
105049
105512
  mcpServers: input.claude.mcpServers,
105050
105513
  writeOutput: input.writeOutput
105051
105514
  });
105515
+ this.askUserServer = new AskUserServer({
105516
+ onQuestionPending: (question) => this.handleAskUserQuestion(question),
105517
+ writeOutput: input.writeOutput
105518
+ });
105052
105519
  this.outputBuffer = new AgentBridgeOutputBuffer(input);
105053
105520
  this.projector = new ClaudeCodeProjector({
105054
105521
  writeOutput: input.writeOutput
@@ -105061,6 +105528,7 @@ var ClaudeCodeCommandHandler = class {
105061
105528
  claudeConfig;
105062
105529
  claudeMcpServers;
105063
105530
  autoMcpShim;
105531
+ askUserServer;
105064
105532
  // Selection-change restarts happen in-process, so prefer the live SDK id even
105065
105533
  // when the file resume store is stale or temporarily unreadable.
105066
105534
  lastObservedAgentId = null;
@@ -105096,7 +105564,13 @@ var ClaudeCodeCommandHandler = class {
105096
105564
  return this.outputBuffer.pendingOutputStats();
105097
105565
  }
105098
105566
  async prepare() {
105099
- 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
+ );
105100
105574
  await this.ensureAgentSession().prepare();
105101
105575
  }
105102
105576
  shutdown() {
@@ -105105,6 +105579,7 @@ var ClaudeCodeCommandHandler = class {
105105
105579
  this.agentSession?.close();
105106
105580
  this.agentSession = null;
105107
105581
  this.autoMcpShim.close();
105582
+ this.askUserServer.close();
105108
105583
  this.settlePendingQuestions("Runtime is shutting down");
105109
105584
  }
105110
105585
  async handleCommand(rawDelivery) {
@@ -105164,6 +105639,7 @@ var ClaudeCodeCommandHandler = class {
105164
105639
  }
105165
105640
  if (delivery.kind === "interrupt") {
105166
105641
  this.injectedCommands.add(delivery.commandId);
105642
+ this.askUserServer.quarantineAll("turn interrupted");
105167
105643
  await this.agentSession?.interruptActiveTurn();
105168
105644
  this.input.runtimeLogger?.info(
105169
105645
  "agent_bridge_claude_command_interrupt_ready",
@@ -105372,6 +105848,17 @@ var ClaudeCodeCommandHandler = class {
105372
105848
  );
105373
105849
  return;
105374
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
+ }
105375
105862
  const message = answerFallbackMessage(answer);
105376
105863
  await this.emitRequiredBridgeOutput(activeContext, {
105377
105864
  type: "entry",
@@ -105402,11 +105889,28 @@ var ClaudeCodeCommandHandler = class {
105402
105889
  }
105403
105890
  this.pendingQuestions.clear();
105404
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
+ }
105405
105906
  // Permission callback for the Claude Agent SDK session. The sessions with
105406
105907
  // bypassPermissions, so regular tools are auto-approved and only the
105407
105908
  // AskUserQuestion tool reaches this callback for a real decision: it parks
105408
105909
  // the SDK on a promise that an `answer` bridge command later resolves.
105409
105910
  canUseTool = (toolName, toolInput, options) => {
105911
+ if (toolName === CLAUDE_ASK_USER_TOOL_NAME) {
105912
+ return Promise.resolve({ behavior: "allow", updatedInput: toolInput });
105913
+ }
105410
105914
  if (toolName !== ASK_USER_QUESTION_TOOL_NAME) {
105411
105915
  return Promise.resolve({ behavior: "allow", updatedInput: toolInput });
105412
105916
  }
@@ -105485,6 +105989,9 @@ var ClaudeCodeCommandHandler = class {
105485
105989
  }
105486
105990
  this.persistAgentId(activeContext, message);
105487
105991
  this.trackReadState(activeContext, message);
105992
+ if (message.type === "result") {
105993
+ this.askUserServer.quarantineAll("turn ended");
105994
+ }
105488
105995
  let projections;
105489
105996
  try {
105490
105997
  projections = this.projector.project(message, {
@@ -105504,6 +106011,7 @@ var ClaudeCodeCommandHandler = class {
105504
106011
  }
105505
106012
  }
105506
106013
  async handleAgentError(error51) {
106014
+ this.askUserServer.quarantineAll("harness session failed");
105507
106015
  this.input.writeOutput?.(
105508
106016
  `agent_bridge_claude_sdk_failed error=${error51 instanceof Error ? error51.message : String(error51)}`
105509
106017
  );
@@ -105623,6 +106131,7 @@ var ClaudeCodeCommandHandler = class {
105623
106131
  onMessage: (message, meta3) => this.handleAgentMessage(message, meta3),
105624
106132
  onError: (error51) => this.handleAgentError(error51),
105625
106133
  onExit: () => {
106134
+ this.askUserServer.quarantineAll("harness child exited");
105626
106135
  if (this.agentSession === session) {
105627
106136
  this.agentSession = null;
105628
106137
  }
@@ -105651,6 +106160,7 @@ var ClaudeCodeCommandHandler = class {
105651
106160
  this.requireResumeForNextAgentSession = this.lastObservedAgentId !== null;
105652
106161
  this.agentSession?.close();
105653
106162
  this.agentSession = null;
106163
+ this.askUserServer.quarantineAll("harness session restarted");
105654
106164
  this.settlePendingQuestions("The runtime restarted to change model");
105655
106165
  this.input.writeOutput?.(
105656
106166
  `agent_bridge_claude_model_selection_changed provider=${next.model?.provider ?? ""} model=${next.model?.id ?? ""} reasoning_effort=${next.reasoningEffort ?? ""}`
@@ -105844,6 +106354,9 @@ var CodexProjector = class {
105844
106354
  toolMetadata: statusMetadata(item.status)
105845
106355
  });
105846
106356
  case "mcpToolCall":
106357
+ if (isAskUserMcpTool(item.server, item.tool)) {
106358
+ return [];
106359
+ }
105847
106360
  return this.projectToolItem({
105848
106361
  phase,
105849
106362
  itemId: item.id,
@@ -106187,6 +106700,9 @@ function renderCodexConfigToml(config2) {
106187
106700
  lines.push("");
106188
106701
  lines.push(`[mcp_servers.${tomlKey(name)}]`);
106189
106702
  lines.push(`url = ${tomlString(server.url)}`);
106703
+ lines.push(
106704
+ `tool_timeout_sec = ${validatedMcpToolTimeoutSeconds(HARNESS_MCP_TOOL_TIMEOUT_SECONDS)}`
106705
+ );
106190
106706
  const persistedHeaders = Object.fromEntries(
106191
106707
  Object.entries(server.headers ?? {}).filter(
106192
106708
  ([name2]) => name2 !== AGENT_BRIDGE_MCP_CAPABILITY_HEADER
@@ -106200,20 +106716,30 @@ function renderCodexConfigToml(config2) {
106200
106716
  `;
106201
106717
  }
106202
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
+ }
106203
106725
  const servers = Object.fromEntries(
106204
- Object.entries(mcpServers ?? {}).flatMap(([name, server]) => {
106726
+ Object.entries(mcpServers ?? {}).map(([name, server]) => {
106205
106727
  const capability = server.headers?.[AGENT_BRIDGE_MCP_CAPABILITY_HEADER];
106206
- return capability ? [
106207
- [
106208
- name,
106209
- {
106210
- 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 ? {
106211
106736
  http_headers: {
106212
- [AGENT_BRIDGE_MCP_CAPABILITY_HEADER]: capability
106737
+ ...server.headers,
106738
+ ...capability ? { [AGENT_BRIDGE_MCP_CAPABILITY_HEADER]: capability } : {}
106213
106739
  }
106214
- }
106215
- ]
106216
- ] : [];
106740
+ } : {}
106741
+ }
106742
+ ];
106217
106743
  })
106218
106744
  );
106219
106745
  return Object.keys(servers).length > 0 ? { mcp_servers: servers } : void 0;
@@ -106807,7 +107333,7 @@ var CodexAgentBridgeSessionImpl = class {
106807
107333
  return;
106808
107334
  } catch (error51) {
106809
107335
  this.input.writeOutput?.(
106810
- `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)}`
106811
107337
  );
106812
107338
  }
106813
107339
  }
@@ -106885,7 +107411,7 @@ var CodexAgentBridgeSessionImpl = class {
106885
107411
  this.activeCommandIds = previousCommandIds;
106886
107412
  this.publishActiveTurnCommandIds();
106887
107413
  this.input.writeOutput?.(
106888
- `agent_bridge_codex_steer_failed error=${errorMessage2(error51)}`
107414
+ `agent_bridge_codex_steer_failed error=${errorMessage3(error51)}`
106889
107415
  );
106890
107416
  }
106891
107417
  if (this.activeTurnId) {
@@ -106899,7 +107425,7 @@ var CodexAgentBridgeSessionImpl = class {
106899
107425
  this.input.writeOutput?.("agent_bridge_codex_interrupted");
106900
107426
  } catch (error51) {
106901
107427
  this.input.writeOutput?.(
106902
- `agent_bridge_codex_interrupt_failed error=${errorMessage2(error51)}`
107428
+ `agent_bridge_codex_interrupt_failed error=${errorMessage3(error51)}`
106903
107429
  );
106904
107430
  }
106905
107431
  }
@@ -106974,7 +107500,7 @@ var CodexAgentBridgeSessionImpl = class {
106974
107500
  message = parseCodexServerFrame(JSON.parse(line));
106975
107501
  } catch (error51) {
106976
107502
  this.input.writeOutput?.(
106977
- `agent_bridge_codex_parse_failed error=${errorMessage2(error51)}`
107503
+ `agent_bridge_codex_parse_failed error=${errorMessage3(error51)}`
106978
107504
  );
106979
107505
  return;
106980
107506
  }
@@ -107169,7 +107695,7 @@ var CodexAgentBridgeSessionImpl = class {
107169
107695
  );
107170
107696
  } catch (error51) {
107171
107697
  this.input.writeOutput?.(
107172
- `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)}`
107173
107699
  );
107174
107700
  this.turnRetry = null;
107175
107701
  this.clearPendingRetryableError();
@@ -107384,7 +107910,7 @@ function threadIdFromResult(result) {
107384
107910
  const id = thread.id;
107385
107911
  return typeof id === "string" && id.length > 0 ? id : null;
107386
107912
  }
107387
- function errorMessage2(error51) {
107913
+ function errorMessage3(error51) {
107388
107914
  return error51 instanceof Error ? error51.message : String(error51);
107389
107915
  }
107390
107916
  function toErrnoException(error51) {
@@ -107410,6 +107936,11 @@ var CodexCommandHandler = class {
107410
107936
  mcpServers: input.codex.mcpServers,
107411
107937
  writeOutput: input.writeOutput
107412
107938
  });
107939
+ this.askUserServer = new AskUserServer({
107940
+ onQuestionPending: (question) => this.handleAskUserQuestion(question),
107941
+ onQuestionSettled: ({ id }) => this.pendingQuestions.delete(id),
107942
+ writeOutput: input.writeOutput
107943
+ });
107413
107944
  this.outputBuffer = new AgentBridgeOutputBuffer(input);
107414
107945
  }
107415
107946
  input;
@@ -107417,10 +107948,12 @@ var CodexCommandHandler = class {
107417
107948
  session = null;
107418
107949
  codexConfig;
107419
107950
  autoMcpShim;
107951
+ askUserServer;
107420
107952
  injectedCommands = /* @__PURE__ */ new Set();
107421
107953
  // itemId -> JSON-RPC request id of the parked approval request, so an `answer`
107422
107954
  // command keyed by toolCallId (= itemId) can resolve the right server request.
107423
107955
  pendingApprovals = /* @__PURE__ */ new Map();
107956
+ pendingQuestions = /* @__PURE__ */ new Set();
107424
107957
  outputBuffer;
107425
107958
  projector = new CodexProjector();
107426
107959
  livenessTicker = null;
@@ -107442,9 +107975,23 @@ var CodexCommandHandler = class {
107442
107975
  return this.outputBuffer.pendingOutputStats();
107443
107976
  }
107444
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();
107445
107985
  this.codexConfig = {
107446
107986
  ...this.codexConfig,
107447
- 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
+ }
107448
107995
  };
107449
107996
  await this.ensureSession().prepare();
107450
107997
  }
@@ -107454,7 +108001,9 @@ var CodexCommandHandler = class {
107454
108001
  this.session?.close();
107455
108002
  this.session = null;
107456
108003
  this.autoMcpShim.close();
108004
+ this.askUserServer.close();
107457
108005
  this.pendingApprovals.clear();
108006
+ this.pendingQuestions.clear();
107458
108007
  }
107459
108008
  async handleCommand(rawDelivery) {
107460
108009
  const delivery = RuntimeBridgeCommandDeliverySchema.parse(rawDelivery);
@@ -107471,6 +108020,7 @@ var CodexCommandHandler = class {
107471
108020
  }
107472
108021
  if (delivery.kind === "interrupt") {
107473
108022
  this.injectedCommands.add(delivery.commandId);
108023
+ this.askUserServer.quarantineAll("turn interrupted");
107474
108024
  await this.session?.interruptActiveTurn();
107475
108025
  return commandAck({ delivery, socketId, status: "injected" });
107476
108026
  }
@@ -107514,7 +108064,7 @@ var CodexCommandHandler = class {
107514
108064
  delivery,
107515
108065
  socketId,
107516
108066
  status: "failed",
107517
- error: errorMessage3(error51)
108067
+ error: errorMessage4(error51)
107518
108068
  });
107519
108069
  }
107520
108070
  return commandAck({ delivery, socketId, status: "injected" });
@@ -107547,6 +108097,11 @@ var CodexCommandHandler = class {
107547
108097
  this.input.writeOutput?.(
107548
108098
  `agent_bridge_codex_question_answered tool_use_id=${payload.data.toolCallId} decision=${decision}`
107549
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
+ );
107550
108105
  } else {
107551
108106
  const message = answerFallbackMessage2(payload.data);
107552
108107
  await this.emitUserMessageEntry(
@@ -107564,7 +108119,7 @@ var CodexCommandHandler = class {
107564
108119
  delivery,
107565
108120
  socketId,
107566
108121
  status: "failed",
107567
- error: errorMessage3(error51)
108122
+ error: errorMessage4(error51)
107568
108123
  });
107569
108124
  }
107570
108125
  return commandAck({ delivery, socketId, status: "injected" });
@@ -107592,6 +108147,9 @@ var CodexCommandHandler = class {
107592
108147
  if (notification.type === "tokenUsage") {
107593
108148
  return;
107594
108149
  }
108150
+ if (notification.type === "turnCompleted") {
108151
+ this.askUserServer.quarantineAll("turn ended");
108152
+ }
107595
108153
  for (const projection of this.projector.project(notification)) {
107596
108154
  await this.emit(activeContext, projection);
107597
108155
  }
@@ -107609,9 +108167,25 @@ var CodexCommandHandler = class {
107609
108167
  await this.emit(activeContext, projection);
107610
108168
  }
107611
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
+ }
107612
108185
  async handleSessionError(error51) {
108186
+ this.askUserServer.quarantineAll("harness session failed");
107613
108187
  this.input.writeOutput?.(
107614
- `agent_bridge_codex_session_failed error=${errorMessage3(error51)}`
108188
+ `agent_bridge_codex_session_failed error=${errorMessage4(error51)}`
107615
108189
  );
107616
108190
  const activeContext = this.context;
107617
108191
  if (!activeContext) {
@@ -107619,7 +108193,7 @@ var CodexCommandHandler = class {
107619
108193
  }
107620
108194
  await this.emit(
107621
108195
  activeContext,
107622
- this.projector.projectSessionFailure(errorMessage3(error51))
108196
+ this.projector.projectSessionFailure(errorMessage4(error51))
107623
108197
  );
107624
108198
  }
107625
108199
  async emitLivenessBeat() {
@@ -107644,10 +108218,16 @@ var CodexCommandHandler = class {
107644
108218
  await this.outputBuffer.emitProjection(activeContext, projection);
107645
108219
  } catch (error51) {
107646
108220
  this.input.writeOutput?.(
107647
- `agent_bridge_output_emit_failed error=${errorMessage3(error51)}`
108221
+ `agent_bridge_output_emit_failed error=${errorMessage4(error51)}`
107648
108222
  );
107649
108223
  }
107650
108224
  }
108225
+ async emitRequired(activeContext, projection) {
108226
+ await this.outputBuffer.emitProjection(activeContext, projection, {
108227
+ discardOnFailure: true,
108228
+ requireDrain: true
108229
+ });
108230
+ }
107651
108231
  ensureSession() {
107652
108232
  if (this.session) {
107653
108233
  return this.session;
@@ -107664,6 +108244,7 @@ var CodexCommandHandler = class {
107664
108244
  onUsageTurnChanged: (turnId) => this.emitUsageTurn(turnId),
107665
108245
  onError: (error51) => this.handleSessionError(error51),
107666
108246
  onExit: () => {
108247
+ this.askUserServer.quarantineAll("harness child exited");
107667
108248
  if (this.session === session) {
107668
108249
  this.session = null;
107669
108250
  }
@@ -107691,7 +108272,9 @@ var CodexCommandHandler = class {
107691
108272
  this.skipResumeForNextSession = true;
107692
108273
  this.session?.close();
107693
108274
  this.session = null;
108275
+ this.askUserServer.quarantineAll("harness session restarted");
107694
108276
  this.pendingApprovals.clear();
108277
+ this.pendingQuestions.clear();
107695
108278
  this.input.writeOutput?.(
107696
108279
  `agent_bridge_codex_model_selection_changed provider=${next.model?.provider ?? ""} model=${next.model?.id ?? ""} reasoning_effort=${next.reasoningEffort ?? ""}`
107697
108280
  );
@@ -107706,7 +108289,7 @@ var CodexCommandHandler = class {
107706
108289
  return store.read(activeContext.sessionId) ?? void 0;
107707
108290
  } catch (error51) {
107708
108291
  this.input.writeOutput?.(
107709
- `agent_bridge_codex_thread_read_failed error=${errorMessage3(error51)}`
108292
+ `agent_bridge_codex_thread_read_failed error=${errorMessage4(error51)}`
107710
108293
  );
107711
108294
  return void 0;
107712
108295
  }
@@ -107721,7 +108304,7 @@ var CodexCommandHandler = class {
107721
108304
  store.write({ sessionId: activeContext.sessionId, threadId });
107722
108305
  } catch (error51) {
107723
108306
  this.input.writeOutput?.(
107724
- `agent_bridge_codex_thread_persist_failed error=${errorMessage3(error51)}`
108307
+ `agent_bridge_codex_thread_persist_failed error=${errorMessage4(error51)}`
107725
108308
  );
107726
108309
  }
107727
108310
  }
@@ -107763,7 +108346,7 @@ function deliveryMode2(delivery) {
107763
108346
  }
107764
108347
  return "interrupt";
107765
108348
  }
107766
- function errorMessage3(error51) {
108349
+ function errorMessage4(error51) {
107767
108350
  return error51 instanceof Error ? error51.message : String(error51);
107768
108351
  }
107769
108352