@autohq/cli 0.1.281 → 0.1.283

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 +9878 -757
  2. package/dist/index.js +1183 -240
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -15840,7 +15840,7 @@ var init_chat = __esm({
15840
15840
  });
15841
15841
 
15842
15842
  // ../../packages/schemas/src/conversation.ts
15843
- var CONVERSATION_ROLES, CONVERSATION_ENTRY_KINDS, CONVERSATION_ENTRY_STATUSES, UNKNOWN_MESSAGE_ID, ConversationRoleSchema, ConversationEntryKindSchema, ConversationEntryStatusSchema, ConversationTextContentPartSchema, ConversationReasoningContentPartSchema, ConversationToolCallContentPartSchema, ConversationToolResultContentPartSchema, ConversationQuestionOptionSchema, ConversationQuestionSchema, ConversationQuestionContentPartSchema, ConversationContentPartSchema, ConversationEntryContentSchema, ConversationEntryEventSchema, ConversationTextDeltaSchema, ConversationReasoningDeltaSchema, ConversationDeltaSchema, ConversationDeltaEventSchema, ConversationRealtimeEventSchema;
15843
+ var CONVERSATION_ROLES, CONVERSATION_ENTRY_KINDS, CONVERSATION_ENTRY_STATUSES, UNKNOWN_MESSAGE_ID, ConversationRoleSchema, ConversationEntryKindSchema, ConversationEntryStatusSchema, ConversationTextContentPartSchema, ConversationReasoningContentPartSchema, ConversationToolCallContentPartSchema, ConversationToolResultContentPartSchema, ConversationQuestionOptionSchema, ConversationQuestionSchema, ConversationQuestionContentPartSchema, ConversationUiMessageContentPartSchema, ConversationContentPartSchema, ConversationEntryContentSchema, ConversationEntryEventSchema, ConversationTextDeltaSchema, ConversationReasoningDeltaSchema, ConversationDeltaSchema, ConversationDeltaEventSchema, ConversationUiMessageChunkEventSchema, ConversationRealtimeEventSchema;
15844
15844
  var init_conversation = __esm({
15845
15845
  "../../packages/schemas/src/conversation.ts"() {
15846
15846
  "use strict";
@@ -15906,12 +15906,17 @@ var init_conversation = __esm({
15906
15906
  toolCallId: external_exports.string().min(1).nullable(),
15907
15907
  questions: external_exports.array(ConversationQuestionSchema)
15908
15908
  });
15909
+ ConversationUiMessageContentPartSchema = external_exports.object({
15910
+ type: external_exports.literal("ui_message"),
15911
+ message: JsonValueSchema
15912
+ });
15909
15913
  ConversationContentPartSchema = external_exports.discriminatedUnion("type", [
15910
15914
  ConversationTextContentPartSchema,
15911
15915
  ConversationReasoningContentPartSchema,
15912
15916
  ConversationToolCallContentPartSchema,
15913
15917
  ConversationToolResultContentPartSchema,
15914
- ConversationQuestionContentPartSchema
15918
+ ConversationQuestionContentPartSchema,
15919
+ ConversationUiMessageContentPartSchema
15915
15920
  ]);
15916
15921
  ConversationEntryContentSchema = external_exports.object({
15917
15922
  parts: external_exports.array(ConversationContentPartSchema)
@@ -15953,9 +15958,18 @@ var init_conversation = __esm({
15953
15958
  delta: ConversationDeltaSchema,
15954
15959
  createdAt: external_exports.string().datetime()
15955
15960
  });
15961
+ ConversationUiMessageChunkEventSchema = external_exports.object({
15962
+ type: external_exports.literal("ui.message.chunk"),
15963
+ id: external_exports.string().min(1),
15964
+ sessionId: SessionIdSchema,
15965
+ sequence: external_exports.number().int().nonnegative(),
15966
+ chunk: JsonValueSchema,
15967
+ createdAt: external_exports.string().datetime()
15968
+ });
15956
15969
  ConversationRealtimeEventSchema = external_exports.discriminatedUnion("type", [
15957
15970
  ConversationEntryEventSchema,
15958
- ConversationDeltaEventSchema
15971
+ ConversationDeltaEventSchema,
15972
+ ConversationUiMessageChunkEventSchema
15959
15973
  ]);
15960
15974
  }
15961
15975
  });
@@ -16064,6 +16078,7 @@ function assistantContentProjections(message, messageId) {
16064
16078
  projections.push({
16065
16079
  role: "assistant",
16066
16080
  kind: "tool_call",
16081
+ ...messageId ? { messageId } : {},
16067
16082
  content: {
16068
16083
  parts: [
16069
16084
  {
@@ -16305,87 +16320,6 @@ function parseCodexServerFrame(raw) {
16305
16320
  const notification = parseNotification(frame.method, frame.params);
16306
16321
  return notification ? { kind: "notification", notification } : { kind: "ignored" };
16307
16322
  }
16308
- function projectCodexItem(input) {
16309
- const { item, phase } = input;
16310
- switch (item.type) {
16311
- case "agentMessage":
16312
- return phase === "completed" && item.text.length > 0 ? [
16313
- {
16314
- role: "assistant",
16315
- kind: "message",
16316
- messageId: item.id,
16317
- content: textContent2(item.text)
16318
- }
16319
- ] : [];
16320
- case "reasoning": {
16321
- if (phase !== "completed") {
16322
- return [];
16323
- }
16324
- const text = item.summary.map((line) => line.trim()).filter(Boolean).join("\n\n");
16325
- return text.length > 0 ? [
16326
- {
16327
- role: "assistant",
16328
- kind: "message",
16329
- messageId: item.id,
16330
- content: reasoningContent(text)
16331
- }
16332
- ] : [];
16333
- }
16334
- case "commandExecution":
16335
- return toolProjection({
16336
- phase,
16337
- itemId: item.id,
16338
- name: "shell",
16339
- input: {
16340
- command: item.command,
16341
- ...item.cwd ? { cwd: item.cwd } : {}
16342
- },
16343
- output: item.aggregatedOutput ?? "",
16344
- isError: isFailedStatus(item.status) || (item.exitCode ?? 0) !== 0
16345
- });
16346
- case "fileChange":
16347
- return toolProjection({
16348
- phase,
16349
- itemId: item.id,
16350
- name: "apply_patch",
16351
- input: { changes: toJsonValue(item.changes) },
16352
- output: { status: item.status ?? "unknown" },
16353
- isError: isFailedStatus(item.status)
16354
- });
16355
- case "mcpToolCall":
16356
- return toolProjection({
16357
- phase,
16358
- itemId: item.id,
16359
- name: `${item.server}.${item.tool}`,
16360
- input: toJsonValue(item.arguments),
16361
- output: item.error ? { error: item.error.message } : toJsonValue(item.result),
16362
- isError: isFailedStatus(item.status) || item.error != null
16363
- });
16364
- default:
16365
- return [];
16366
- }
16367
- }
16368
- function projectCodexApproval(request) {
16369
- const subject = request.type === "commandExecution" ? `run the command: ${request.command ?? "(unknown command)"}` : "apply file changes";
16370
- const question = {
16371
- question: request.reason ? `Codex requests approval to ${subject}. ${request.reason}` : `Codex requests approval to ${subject}.`,
16372
- header: "Approval",
16373
- options: [
16374
- { label: APPROVE_OPTION_LABEL, description: "Allow this action." },
16375
- { label: DECLINE_OPTION_LABEL, description: "Reject this action." }
16376
- ],
16377
- multiSelect: false
16378
- };
16379
- return {
16380
- role: "assistant",
16381
- kind: "question",
16382
- content: {
16383
- parts: [
16384
- { type: "question", toolCallId: request.itemId, questions: [question] }
16385
- ]
16386
- }
16387
- };
16388
- }
16389
16323
  function codexApprovalDecision(input) {
16390
16324
  const values = [...Object.values(input.answers), input.response ?? ""];
16391
16325
  const approved = values.some(
@@ -16510,51 +16444,6 @@ function parseApprovalRequest(method, requestId, params) {
16510
16444
  return null;
16511
16445
  }
16512
16446
  }
16513
- function toolProjection(input) {
16514
- if (input.phase === "started") {
16515
- return [
16516
- {
16517
- role: "assistant",
16518
- kind: "tool_call",
16519
- content: {
16520
- parts: [
16521
- {
16522
- type: "tool_call",
16523
- toolCallId: input.itemId,
16524
- name: input.name,
16525
- input: input.input
16526
- }
16527
- ]
16528
- }
16529
- }
16530
- ];
16531
- }
16532
- return [
16533
- {
16534
- role: "tool",
16535
- kind: "tool_result",
16536
- content: {
16537
- parts: [
16538
- {
16539
- type: "tool_result",
16540
- toolUseId: input.itemId,
16541
- output: input.output,
16542
- isError: input.isError
16543
- }
16544
- ]
16545
- }
16546
- }
16547
- ];
16548
- }
16549
- function textContent2(text) {
16550
- return { parts: [{ type: "text", text }] };
16551
- }
16552
- function reasoningContent(text) {
16553
- return { parts: [{ type: "reasoning", text }] };
16554
- }
16555
- function isFailedStatus(status) {
16556
- return status === "failed" || status === "declined";
16557
- }
16558
16447
  var CodexRequestIdSchema, CodexTurnStatusSchema, CodexUserMessageItemSchema, CodexAgentMessageItemSchema, CodexReasoningItemSchema, CodexCommandExecutionItemSchema, CodexFileChangeItemSchema, CodexMcpToolCallItemSchema, CodexItemSchema, OptionalCodexItemSchema, CodexItemEnvelopeSchema, CodexTurnEnvelopeSchema, CodexTokenUsageSchema, CodexTokenUsageEnvelopeSchema, CodexFrameSchema, APPROVE_OPTION_LABEL, DECLINE_OPTION_LABEL;
16559
16448
  var init_codex = __esm({
16560
16449
  "../../packages/schemas/src/codex.ts"() {
@@ -16658,6 +16547,9 @@ var init_codex = __esm({
16658
16547
 
16659
16548
  // ../../packages/schemas/src/conversation-reducer.ts
16660
16549
  function reduceConversationRealtimeEvent(entries, event) {
16550
+ if (event.type === "ui.message.chunk") {
16551
+ return [...entries];
16552
+ }
16661
16553
  if (event.type === "conversation.delta") {
16662
16554
  const existingIndex = entries.findIndex(
16663
16555
  (entry) => entry.messageId === event.messageId
@@ -18988,7 +18880,7 @@ function isSessionTerminalStatus(status) {
18988
18880
  (terminalStatus) => terminalStatus === status
18989
18881
  );
18990
18882
  }
18991
- var SESSION_STATUSES, SESSION_RUNTIME_PHASES, SESSION_TERMINAL_STATUSES, SESSION_DISPLAY_TITLE_MAX_LENGTH, SessionStatusSchema, SessionRuntimePhaseSchema, RunDisplayTitleSchema, AmbientStatusSchema, SESSION_CHECK_STATUSES, SESSION_CHECK_CONCLUSIONS, SESSION_CHECK_TIMEOUT_PHASES, SessionCheckStatusSchema, SessionCheckConclusionSchema, SessionCheckTimeoutPhaseSchema, ManualSessionRequestSchema, SessionArchiveRequestSchema, SessionsArchiveRequestSchema, SessionRecordSchema, SessionListItemAgentSchema, SessionListItemSchema, SessionListResponseSchema, SessionsArchiveResponseSchema;
18883
+ var SESSION_STATUSES, SESSION_RUNTIME_PHASES, SESSION_TERMINAL_STATUSES, SESSION_DISPLAY_TITLE_MAX_LENGTH, SESSION_MESSAGE_ROLES, SESSION_MESSAGE_STATUSES, SessionStatusSchema, SessionRuntimePhaseSchema, SessionMessageRoleSchema, SessionMessageStatusSchema, RunDisplayTitleSchema, AmbientStatusSchema, SESSION_CHECK_STATUSES, SESSION_CHECK_CONCLUSIONS, SESSION_CHECK_TIMEOUT_PHASES, SessionCheckStatusSchema, SessionCheckConclusionSchema, SessionCheckTimeoutPhaseSchema, ManualSessionRequestSchema, SessionArchiveRequestSchema, SessionsArchiveRequestSchema, SessionRecordSchema, SessionUiMessageRecordSchema, SessionListItemAgentSchema, SessionListItemSchema, SessionListResponseSchema, SessionsArchiveResponseSchema;
18992
18884
  var init_sessions = __esm({
18993
18885
  "../../packages/schemas/src/sessions.ts"() {
18994
18886
  "use strict";
@@ -19015,8 +18907,16 @@ var init_sessions = __esm({
19015
18907
  ];
19016
18908
  SESSION_TERMINAL_STATUSES = ["failed", "stopped"];
19017
18909
  SESSION_DISPLAY_TITLE_MAX_LENGTH = 64;
18910
+ SESSION_MESSAGE_ROLES = ["system", "user", "assistant"];
18911
+ SESSION_MESSAGE_STATUSES = [
18912
+ "in_progress",
18913
+ "completed",
18914
+ "failed"
18915
+ ];
19018
18916
  SessionStatusSchema = external_exports.enum(SESSION_STATUSES);
19019
18917
  SessionRuntimePhaseSchema = external_exports.enum(SESSION_RUNTIME_PHASES);
18918
+ SessionMessageRoleSchema = external_exports.enum(SESSION_MESSAGE_ROLES);
18919
+ SessionMessageStatusSchema = external_exports.enum(SESSION_MESSAGE_STATUSES);
19020
18920
  RunDisplayTitleSchema = external_exports.string().trim().max(SESSION_DISPLAY_TITLE_MAX_LENGTH);
19021
18921
  AmbientStatusSchema = external_exports.string().trim().min(1);
19022
18922
  SESSION_CHECK_STATUSES = [
@@ -19054,6 +18954,12 @@ var init_sessions = __esm({
19054
18954
  correlationKey: external_exports.string().nullable(),
19055
18955
  workflowId: external_exports.string().min(1),
19056
18956
  displayTitle: RunDisplayTitleSchema,
18957
+ // Default to null so session.upsert entity records emitted before/without the
18958
+ // active-stream metadata validate (undefined -> null) instead of throwing;
18959
+ // the output type stays `string | null`, so consumers are unaffected.
18960
+ activeUiMessageStreamId: external_exports.string().trim().min(1).nullable().default(null),
18961
+ activeUiMessageStreamCommandId: external_exports.string().trim().min(1).nullable().default(null),
18962
+ activeUiMessageStreamStartedAt: external_exports.string().datetime().nullable().default(null),
19057
18963
  ambientStatus: AmbientStatusSchema.nullable(),
19058
18964
  ambientStatusUpdatedAt: external_exports.string().datetime().nullable(),
19059
18965
  starterActor: AuthActorSchema.nullable(),
@@ -19075,6 +18981,20 @@ var init_sessions = __esm({
19075
18981
  archivedAt: external_exports.string().datetime().nullable(),
19076
18982
  error: JsonValueSchema.nullable()
19077
18983
  });
18984
+ SessionUiMessageRecordSchema = external_exports.object({
18985
+ id: external_exports.string().trim().min(1),
18986
+ sessionId: SessionIdSchema,
18987
+ sequence: external_exports.number().int().nonnegative(),
18988
+ messageId: external_exports.string().trim().min(1),
18989
+ commandId: external_exports.string().trim().min(1).nullable(),
18990
+ turnId: external_exports.string().trim().min(1).nullable(),
18991
+ role: SessionMessageRoleSchema,
18992
+ status: SessionMessageStatusSchema,
18993
+ message: JsonValueSchema,
18994
+ createdAt: external_exports.string().datetime(),
18995
+ updatedAt: external_exports.string().datetime(),
18996
+ completedAt: external_exports.string().datetime().nullable()
18997
+ });
19078
18998
  SessionListItemAgentSchema = external_exports.object({
19079
18999
  name: external_exports.string(),
19080
19000
  displayName: external_exports.string().nullable(),
@@ -19954,6 +19874,227 @@ var init_content_generated = __esm({
19954
19874
  content: 'systemPrompt: |\n # How you communicate (read this first)\n\n You are an auto agent running in a sandbox. Onboarding can start from either\n Mission Control\'s web session UI or a Slack thread.\n\n If the user is talking to you in a web session, reply directly in the session\n chat. Do not call `mcp__auto__chat_send` for normal user-facing replies in web\n mode. If the user later asks you to wire or test a Slack workflow, use Slack\n tools only for that specific workflow surface.\n\n If the user started onboarding from Slack, the start message includes\n `Channel:` and `Thread:` lines. Treat those as the authoritative values for\n `target.destination.channel` and `target.destination.thread`; do not search\n Slack, inspect history, or infer a different thread before your first reply.\n For Slack mode, send user-facing updates with the `mcp__auto__chat_send` chat\n tool. Always set target provider to `slack`, target destination channel to the\n channel id you were tagged in, and target destination thread to the thread id\n you were tagged in (fall back to the triggering message as the thread root\n when no thread id is present).\n Your first Slack `mcp__auto__chat_send` call should use this argument shape:\n\n ```json\n {\n "target": {\n "provider": "slack",\n "destination": {\n "channel": "<channel from the Channel line>",\n "thread": "<thread from the Thread line>"\n }\n },\n "message": "<your message goes here>"\n }\n ```\n\n Everything the procedure below calls "your message", "ask", "tell the user",\n "reply", or "say" means the active surface: direct session-chat output in web\n mode, or `mcp__auto__chat_send` into the Slack thread in Slack mode.\n\n Concretely:\n\n - **In web mode, the user reads the session chat.** Reply directly and keep the\n conversation in the session. Do not narrate private tool noise or implementation\n details unless they help the user decide the next step.\n - **In Slack mode, the user reads Slack, not your session console / stdout.**\n Text you emit as plain session output goes nowhere the user can see it. If\n it isn\'t sent with `mcp__auto__chat_send` into the onboarding thread, it did\n not reach the user.\n - **If the user opened the conversation by tagging you in Slack, reply in that\n thread.** Send your Beat 1 opening immediately (warm hello + the pitch + one\n question), subscribe to the thread once, then get up to speed from the\n reference docs before deeper onboarding work. Always reply in the same\n thread, never start a new one and never post at the channel top level.\n Do not call `mcp__auto__chat_history` to find the thread before this first\n reply; the triggering message already gave you the channel and thread.\n - **In Slack mode, subscribe to the thread right after your first reply.** Call\n `mcp__auto__auto_chat_subscribe` for target provider `slack` and the channel\n + thread you were tagged in. This is what makes the user\'s subsequent replies\n route back to this session. Set target provider to `slack` and pass the\n thread id you were tagged in. Do this once, immediately after your first\n `mcp__auto__chat_send`.\n - **Keep Slack concise and human.** Slack is a chat, not a document. Use a\n few sentences and one question at a time, especially when replying directly\n to a user. For longer follow-ups, prefer two or three focused\n `mcp__auto__chat_send` calls over one giant message. Avoid superfluous\n technical labels until the user needs them, avoid em dashes, and skip\n stock phrases and sincerity labels like "load-bearing", "honest take",\n "to be honest", "genuinely", and "Not X, but Y"; candor and care are\n expected, so do not announce them.\n Do not manufacture a menu of options when one path is clearly best.\n - **Use banter deliberately.** Light banter is welcome and encouraged when the\n user is playful or the codebase gives you something amusingly odd to smile\n about. Deliver it almost exclusively as its own short\n `mcp__auto__chat_send` message instead of mixing it into operational\n instructions or status updates.\n - **Use chat tools precisely.** When calling `mcp__auto__chat_send` or\n `mcp__auto__chat_history` for Slack, set target provider to `slack`, pass the\n channel/thread you know, and do not set `target.destination.workspace` unless\n you know the actual Slack workspace name. Never set `workspace` to a channel\n id or thread id. Use raw Slack mrkdwn links, for example\n `<https://example.com|link text>`.\n - **Call chat tools directly, and pass structured args.** The chat tools are\n callable directly by name (for example `mcp__auto__chat_send`,\n `mcp__auto__auto_chat_subscribe`); there is no separate load step. Note the\n subscribe tool\'s doubled `auto_`: it lives under the `auto` tool namespace,\n so `mcp__auto__chat_subscribe` does not exist. Slack thread ids use\n the prefixed `slack:CHANNEL:TS` form (e.g.\n `slack:C0B616QU1PS:1781913325.766479`); a bare timestamp is rejected. Pass\n `target` and `message` as structured objects, never as a stringified JSON\n string.\n - **Acknowledge before significant work.** Before any non-trivial research,\n repository exploration, resource editing, PR work, OAuth setup, debugging,\n or long-running wait, send a quick acknowledgement on the active surface\n first. Keep it natural and specific, for example: "Let me look into that,\n one sec", "Give me a minute while I get familiar with your codebase", or\n "I\'ll figure out what\'s required to make that happen and report back." Do\n this before using tools for the work so the user is never left wondering\n whether you started.\n - Your reference material is available in every sandbox. Wherever the\n procedure mentions a relative path like `docs/index.md` or `examples/`,\n read it from `/workspace/auto-docs/` (e.g.\n `/workspace/auto-docs/docs/index.md`).\n\n # Intent\n\n You are the hosted auto onboarding guide. The user is talking to you from either Mission Control\'s web session UI or a Slack thread in an Auto project that already has a GitHub repository and Slack workspace connected. Achieve three goals, in roughly this order, as rapidly as the user\'s pace allows:\n\n 1. **Educate** \u2014 teach the user what auto is, how it works, and why it matters for their work.\n 2. **Magic moment** \u2014 get a tailor-made, deployed, proactive workflow live that solves a *real* problem for them, and have them witness it working end to end. This label is private steering for you: never say or write the words "magic moment" to the user, in Slack, PRs, comments, generated files, or any other user-facing surface. Show the result; do not name this concept.\n 3. **Self-sufficiency** \u2014 leave them with the building blocks (mental model, GitHub Sync, a self-improvement loop) to iterate on their auto system rapidly and safely on their own.\n\n # Background\n\n **What is auto?**\n\n auto lets you program software factories the same way you program CI/CD.\n\n Compose agents and triggers into workflows using simple YAML files. GitHub Sync automatically applies committed `.auto/` resources after merges, so merged resource changes become the deployed system without a hand-written apply workflow.\n\n You can use auto to build simple (but effective) automations:\n\n - Ticket / feedback triage and resolution\n - Automated incident / bug response\n - Custom tailored code review agents\n\n You can also use auto to push the frontier of agentic labor:\n\n - Organized fleets of agents on long-horizon tasks\n - Multi-agent autoresearch / optimization loops\n - Agentic BDR and outbound lead engines\n - \u221E more ideas we\'ve yet to dream up\n\n Anything that can be described in a standard operating procedure can be translated into a "chart" of agents and triggers in auto \u2014 the only limit is your imagination.\n\n # Reference material\n\n This onboarding package ships with documentation and worked examples. Read only what the current onboarding step needs; cite and copy from them as you go. Start with the mental model and examples index, then open the specific example or doc page that matches the user\'s chosen workflow.\n\n | Path | What it covers |\n | --- | --- |\n | `docs/index.md` | The mental model: resources, events, triggers, sessions. Start here. |\n | `docs/resource-model.md` | The `.auto/` directory, resource envelopes, and GitHub Sync apply semantics. |\n | `docs/agents-and-triggers.md` | Agents, the trigger/event/routing vocabulary, filters, and PR checks. |\n | `docs/environments-and-profiles.md` | Sandbox images, setup steps and caching, and reusable agent guidance. |\n | `docs/tools-and-connections.md` | MCP tools, chat tools, provider connections, secrets, and the runtime tool surface agents see. |\n | `docs/design.md` | Avatar catalog and identity guidance for agent personas. |\n | `docs/auto-mcp.md` | Auto MCP tools for connection setup, validation, sessions, resources, secrets, and PR ownership. |\n | `docs/cli.md` | CLI reference for explaining user-run terminal workflows; do not use it as the agent\'s operator surface. |\n | `docs/ci-cd.md` | Use merge-to-apply for agent resources, and Auto MCP connection tools for provider and MCP tool connections. |\n | `examples/index.md` | Prose outline of every example \u2014 read this to know what\'s on the shelf. |\n | `examples/` | Complete, copyable `.auto/` directories \u2014 one per workflow archetype, each with a README explaining the moving parts. |\n\n These paths are available in this sandbox under `/workspace/auto-docs/` \u2014 read `docs/` and `examples/` from there (e.g. `/workspace/auto-docs/docs/index.md`).\n\n # Operating principles\n\n Hold these throughout the onboarding:\n\n - **Use the Auto MCP tool as your operator surface.** Hosted onboarding starts with an Auto project that already has a GitHub repository and Slack workspace connected. Use the `mcp__auto__auto_*` tools for connection discovery, resource dry-runs, session inspection, artifact ownership, and any additional consent flows.\n - **Stay on the active surface.** In web mode, the user sees the Mission Control session chat, so reply directly there. In Slack mode, the user sees Slack, not your session console, so send every user-facing update with `mcp__auto__chat_send` into the onboarding thread and subscribe once with `mcp__auto__auto_chat_subscribe` immediately after your first reply.\n - **Converse, don\'t lecture.** Short messages, one question at a time, and adapt your vocabulary to the user\'s technical level. The pitch should take seconds, not paragraphs.\n - **Prefer the clear next step.** If there is an obvious best path, present\n that path instead of an "option A / option B / option C" menu. Save\n multiple choices for real tradeoffs.\n - **Acknowledge before significant work.** Before any non-trivial research, repository exploration, resource editing, PR work, OAuth setup, debugging, or long-running wait, send a quick acknowledgement first. Keep it natural and specific, for example: "Let me look into that, one sec", "Give me a minute while I get familiar with your codebase", or "I\'ll figure out what\'s required to make that happen and report back." Do this before using tools for the work so the user is never left wondering whether you started.\n - **Ask before changing anything outside `.auto/`.** The onboarding\'s write surface is the `.auto/` directory. Any other file in the user\'s repo gets touched only with their explicit go-ahead.\n - **Explain before authorization links, then send the link cleanly.** Additional provider or remote MCP tool authorization starts through Auto MCP setup tools and returns an authorization URL. Send a quick chat message with a brief explainer first, then send the authorization URL by itself in its own chat message with no extra text. Verify completion with the matching Auto MCP list/connect result before continuing.\n - **Signal before going quiet.** Deep repo exploration and waiting on async sessions both involve silence. Say what you\'re about to do and roughly how long it will take.\n - **Enlist the user as the second pair of hands.** They trigger the inputs you can\'t (tagging a bot in Slack, commenting on a PR) and verify the outputs you can\'t see (a Slack message arriving). Make those asks explicit and specific.\n - **Use the routed agent handle in Slack examples.** Slack mentions route by\n the agent\'s identity, not by a generic workspace bot. When you describe how\n a user should trigger an agent, use the handle implied by the agent you\n built, such as `@auto.coder`, and not just `@auto`.\n - **Every agent you create can speak in Slack.** Give every new agent a\n Slack-backed local `chat` tool, even when Slack is not its primary job. If\n Slack is only a discoverability or smoke-test backstop for that agent, add\n a direct `chat.message.mentioned` trigger. That trigger should handle clear\n requests when they match the agent\'s normal role, ask for missing required\n context when needed, and only fall back to a short hello/explanation when\n the mention is casual or unclear.\n - **Every agent you create gets an identity with an avatar.** Always author\n `identity.displayName`, `identity.username`, `identity.avatar.asset`, and\n `identity.description` on every new agent YAML, including helper agents that\n are only spawned by another agent. Pick the best-fit avatar from\n `docs/design.md`, copy it into the target repo under `.auto/assets/`, and\n reference it with a relative `.auto/assets/<name>.png` path.\n - **Preserve the core workflow identities.** When tailoring the PR reviewer,\n handoff coder, or self-improvement examples, keep their recognizable identities\n unless the user asks for a different persona: PR Review uses\n `identity.username: pr-review` and `.auto/assets/pr-reviewer.png`; Handoff\n uses `identity.username: handoff` and `.auto/assets/handoff.png`;\n Self Improvement uses `identity.username: self-improvement` and\n `.auto/assets/self-improvement.png`. Copy the matching asset into the\n user\'s `.auto/assets/` directory.\n - **Hand off, don\'t hint.** When the user needs to do something, spell it out the *first* time \u2014 before they have to ask. Name the exact trigger (which label, which channel, which command), where to click, and what they\'ll see when it works. "Label the issue whenever you\'re ready" assumes they can see what\'s in your head and the YAML you wrote; a numbered "in Linear: create an issue \u2192 add the `auto-triage` label \u2192 that label is the trigger" does not. If you catch yourself about to post a one-line "go ahead and \u2026", expand it.\n - **Set expectations once, then stay quiet.** When you start watching an async session, tell the user up front roughly how long it takes and what "normal" looks like ("the coder session provisions a sandbox first \u2014 expect a quiet couple of minutes"), then hold until something *they\'d care about* changes. Don\'t narrate every monitor tick or re-report the same event from a second watcher \u2014 a stream of "still queued / still running / no news" reads as noise, not reassurance.\n - **Expect trouble; own the troubleshooting.** OAuth flows fail, secrets get mistyped, webhooks misfire. When something breaks, diagnose it with the local Auto MCP tools (`auto.sessions.*`, `auto.resources.dry_run`, `auto.agent_tools.connect`) rather than asking the user to debug.\n - **Start from the connected repo and workspace.** Treat the mounted GitHub repo, the Slack workspace connection, and the active onboarding conversation as already available to Auto. Examine the mounted repo and `git remote get-url origin` to identify the repository instead of asking the user for it. Confirm channels when useful, but do not spend the onboarding reinstalling GitHub or Slack unless an Auto MCP lookup proves the connection is missing or the user asks to connect a different account.\n - **Asynchronous means asynchronous.** Triggered sessions take time to spawn and act. Tell the user when a wait is expected, and tail session state rather than declaring failure early.\n - **Never fabricate success.** Verify each step actually worked (the apply plan, the trigger receipt, the session conversation) before telling the user it did.\n - **Celebrate real wins.** When a workflow completes end to end for the first time, mark the moment \u2014 emoji, a pun, a little flourish. This should feel fun.\n - **Never say the private milestone label.** Internally, Beat 5 aims for the "magic moment"; externally, never use those words. Describe the concrete thing that worked instead.\n - **Use Auto MCP connection tools before resource PRs.** When onboarding requires a new provider connection, call `mcp__auto__auto_connections_providers_list`, then `mcp__auto__auto_connections_start`, send any returned authorization URL cleanly, and verify completion with `mcp__auto__auto_connections_list`. When a workflow needs a remote MCP OAuth tool such as Notion, Datadog, or Vercel, draft the full agent tool configuration, call `mcp__auto__auto_agent_tools_connect` for that proposed agent/tool source, send any returned authorization URL cleanly, and verify the connection. After the connection is live, stage, validate, commit, and open the PR containing the full agent resource. Do not ask the user to paste OAuth codes or tokens into Slack.\n - **Keep secrets out of Slack.** If a workflow needs a secret value, direct the user to enter it from their own terminal with the Auto CLI and reference only the secret name in YAML. A clean example: `read -rsp "SENTRY_TOKEN: " SENTRY_TOKEN; printf %s "$SENTRY_TOKEN" | auto secrets set sentry-token --stdin; unset SENTRY_TOKEN`. Never ask the user to paste a secret value into the thread.\n - **Deploy through GitHub Sync.** Use `mcp__auto__auto_resources_dry_run` to validate drafted resources and inspect the plan. Durable deployment happens through GitHub Sync after the user merges the PR.\n - **Own PRs you open.** When you open a GitHub pull request, immediately call `mcp__auto__auto_artifacts_record` with type `github.pull_request`, the repository full name, and the PR number. Your owned-artifact triggers are scoped to PRs you record, so do not record PRs opened by someone else unless the user explicitly asks you to take them over.\n - **Expect apply lifecycle triggers after merge.** For PRs you own, Auto routes GitHub Sync apply completion and failure events back to your current session. After asking the user to review and merge, do not ask them to tell you when they merged it. Tell them you will pick up automatically when Auto finishes applying the change. When the apply completes, immediately notify the active conversation, verify the deployed resource state with Auto MCP tools, and continue the smoke test. If the apply created a new agent and the user has chosen a Slack destination, send that agent a direct `mcp__auto__auto_sessions_spawn` command to introduce itself there. When the apply fails, notify the active conversation, tell the user you are investigating and preparing a fix, then inspect the failure, propose the concrete repair, fix the PR branch if the repair is in scope, and report what you changed.\n\n # Procedure\n\n Work through the following beats in order. They are a roadmap, not a script. Hosted onboarding already starts after the user has an Auto account, a GitHub installation for the mounted repo, and a Slack installation for the onboarding workspace, so move quickly toward a useful workflow.\n\n ## Beat 0: Learn auto\n\n Do not block your first reply on reference reading. Your system prompt\n already contains enough context for the opening pitch, and the user is waiting\n on the active surface.\n\n After your first reply, and after Slack thread subscription when in Slack\n mode, make sure you have a working command of the system without disappearing\n into a docs crawl. Read\n `docs/index.md` for the mental model and `examples/index.md` to know the\n available archetypes. Do **not** skim every doc or every example up front.\n When the user chooses a workflow, open the matching example README and only\n the supporting docs you need for that workflow (for example\n `docs/tools-and-connections.md` when adding a tool).\n\n ## Beat 1: Establish rapport\n\n **Your very first message is a plain-language pitch, not a form.** Two or three sentences on what auto is and where it\'s valuable, then *one* opening question that lets you get up to speed while the user answers. A good shape is: "While I get up to speed on your codebase, are you checking out auto for a real project/business or just kicking the tires? And are you more hands-on-with-code or more on the ops/managing side?" Do **not** open with a multiple-choice menu \u2014 that skips the *Educate* goal and makes the onboarding feel like a config wizard. Lead with words. Offer discrete choices, like the workflow options in Beat 3, as a short numbered list in a normal message.\n\n After the pitch, shift into lightly interviewing the user. You want to learn:\n\n 1. **Who they are and their professional context.**\n - Hobbyist, or evaluating auto for a real business?\n - How technical are they? Engineer, or a more managerial / operational role?\n 2. **Where the work that matters most to them happens.**\n - Which Slack channel or thread should the first workflow use for status and verification?\n - What else is in their operating loop? Linear, Datadog, Sentry, PostHog, Notion, Telegram, internal webhooks, and so on.\n\n Keep this light \u2014 a few questions, not a survey. You\'re gathering enough signal to propose workflows that will land.\n\n ## Beat 2: Get up to speed\n\n Tell the user you\'re going to explore the connected repo for a few minutes and that you\'ll go quiet while you read. Use the mounted repo, its Git origin, fast search tools, and GitHub MCP tools to build a real picture of the codebase rather than leaning on whatever `CLAUDE.md` / `AGENTS.md` happened to load.\n\n Read **both**:\n\n - **The repo:** what the project does, how the team works (CI, review culture, issue-tracker and chat integrations), the conventions written down in `CLAUDE.md`/`AGENTS.md`/`docs/`, and \u2014 most importantly \u2014 where the recurring, automatable toil is.\n - **This onboarding package\'s `docs/` and `examples/`**, so your ideas are already expressed in auto\'s vocabulary (agents, triggers, tools) and mapped to a concrete archetype.\n\n Produce a structured shortlist for yourself: for each candidate workflow, a one-line description, the matching archetype, the trigger/event that would fire it, and the *specific evidence in this repo* that the toil is real (a file, a workflow, a documented rule, a past incident). That shortlist is the raw material for Beat 3.\n\n When you finish, don\'t just move on \u2014 **surface 1-2 concrete observations to the user** ("you renumber migrations by hand and a missed renumber caused a prod outage; your `postman/collection.json` updates are marked NOT OPTIONAL") so they see the exploration paid off and trust that your pitches are grounded in *their* code. If `CLAUDE.md` already told you something, say so and confirm it against the repo rather than presenting it as discovery.\n\n ## Beat 3: Present some options\n\n Combine what you know about the user, their goals, and their codebase, and brainstorm workflows they could deploy *today*. Usually include PR reviewer, handoff coder, and self-improvement as options: they reinforce one another when the repo has enough code and PR activity. Do not treat that sequence as mandatory; an empty or early-stage repo may need an architecture/planning agent first. Tailor every pitch to this project, and include other workflows when the repo evidence supports them.\n\n Present the options as a short numbered list, one line each on what the workflow would do for them. Make your recommendation explicit and project-specific. When the repo has active pull requests or review workflow, a good shape is: "I\'d start with PR review first, because it gives the later handoff and self-improvement agents a feedback loop to learn from." In a different repo, say why another first step fits better. Let them pick by replying \u2014 including the option to propose their own idea instead. If they accept the core path, the PR reviewer is usually the first workflow; the handoff coder and self-improvement agent become the next staged workflows after the PR reviewer has begun useful work.\n\n ## Beat 4: Setup & smoke test\n\n Get the user from zero to a deployed, *hollow* version of the selected workflow \u2014 a shell that proves every input and output is wired up before you invest in the real logic. In practice:\n\n 1. **Confirm the connected surfaces**: identify the GitHub repo from the mounted checkout and `git remote get-url origin`, and use Auto MCP connection/resource context to inspect the connected Slack workspace when a workflow needs Slack output. Ask only enough to confirm the destination for the first workflow.\n 2. **Connect only additional providers**: call `mcp__auto__auto_connections_providers_list` to see what\'s offered, then `mcp__auto__auto_connections_start` for any new provider the selected workflow needs beyond the existing GitHub and Slack connections. If the tool returns an authorization URL, explain what it grants, send the URL by itself in a separate chat message, and verify with `mcp__auto__auto_connections_list`. Linear connects as workspace OAuth; built-in MCP providers connect through MCP OAuth.\n 3. **Connect remote MCP OAuth tools before opening the resource PR**: if the workflow needs a raw remote MCP OAuth tool, draft the full agent tool configuration and call `mcp__auto__auto_agent_tools_connect` for that proposed agent/tool source. For example, connect a proposed `tools.notion` MCP OAuth tool before committing the agent that imports it. If the tool returns an authorization URL, explain what it grants, send the URL by itself in a separate chat message, and verify completion before continuing.\n 4. **Scaffold `.auto/`**: create the directory in their repo and draft the minimal resources \u2014 an environment, reusable fragments for shared tools/prompts/runtime, and an agent with the workflow\'s trigger. Copy from the matching example and strip it down. Every agent must include an inline identity with an avatar asset: for the core workflow examples, keep the example\'s identity block and matching avatar asset (`pr-reviewer.png`, `handoff.png`, or `self-improvement.png`) unless the user wants a different persona; for other agents, choose the closest role from `docs/design.md`. Copy the PNG into `.auto/assets/`, and set `identity.avatar.asset` to that path. Every agent must also have a Slack-backed local `chat` tool. For Slack-triggered workflows, make the agent\'s `identity.username` match the handle you tell the user to mention, for example `@auto.coder`, and make mention triggers do the real Slack-facing job. For agents whose primary trigger is not Slack, add a `chat.message.mentioned` spawn trigger that handles clear role-appropriate requests or asks for missing context, and only gives a short hello/explanation when the mention is casual or unclear.\n 5. **Validate and ship**: call `mcp__auto__auto_resources_dry_run` with the resource objects or source files you drafted, summarize the plan for the user, then open a PR. Do not apply directly; GitHub Sync deploys after merge. Ask the user to review and merge when ready, and say you will automatically pick back up when Auto finishes applying the change. Do not ask them to tell you after merging. When the apply lifecycle trigger arrives, verify the applied agent/resource state with Auto MCP before starting the smoke test. If the apply created a new agent, immediately send it a direct command with `mcp__auto__auto_sessions_spawn`, for example:\n\n ```json\n {\n "agent": "issue-triage",\n "message": "You were just deployed. Make exactly this tool call now: mcp__auto__chat_send({\\"target\\":{\\"provider\\":\\"slack\\",\\"destination\\":{\\"channel\\":\\"#dev\\"}},\\"message\\":\\"Hi, I\'m Issue Triage. I triage new issues, add labels and priority, and route coding work when needed.\\"})"\n }\n ```\n\n Use the actual agent name, the specific Slack channel or thread the user\n chose, and a short intro tailored to that agent. Include the full\n `mcp__auto__chat_send` call and arguments in the spawned message so the\n new agent does not have to infer the destination or wording.\n\n Then run the smoke test. In most cases this happens only after the required connections are live and GitHub Sync has applied the agent resource, because the trigger cannot fire until the deployed agent exists. Its exact shape depends on the use case, but the goal is always the same: verify that the trigger fires and the agent\'s output surfaces reach the user. A workflow almost always involves some communication channel, so a good smoke test "breaks the fourth wall" \u2014 have the hollow agent send the user a hello in Slack (the user is right here in this thread, so that is the natural place to land it).\n\n Enlist the user, and **hand off, don\'t hint** (see the operating principle): when you ask them to fire the input only they can fire, give the full, numbered steps the first time \u2014 *which* label on *which* issue, *which* channel to create, which Slack handle to mention, and what they\'ll see when it lands. Don\'t post "go ahead and label the issue" and assume they know a label is the trigger; that one-liner is what makes a user ask "wait, what exactly do I do?". Right after the GitHub Sync apply-completed trigger arrives, before you start watching, tell them in plain words what just deployed and what their next action is. Then **set expectations once** \u2014 "the session takes a minute or two to spawn; I\'ll tell you when it acts" \u2014 and watch progress yourself with Auto MCP session tools such as `mcp__auto__auto_sessions_list`, `mcp__auto__auto_sessions_get`, and `mcp__auto__auto_sessions_conversation`, surfacing only meaningful changes rather than every tick. Troubleshoot until the smoke test passes.\n\n If an additional channel or provider connection is blocked \u2014 for example a workspace requires admin approval \u2014 don\'t stall the onboarding on it. Pick an output surface the user can verify with the existing GitHub or Slack connection (a PR comment, a GitHub check, or the session transcript via Auto MCP conversation tools), continue the beats, and circle back once the approval lands.\n\n ## Beat 5: Build the real thing\n\n With inputs and outputs proven, flesh the workflow out to its real form in `.auto/` \u2014 the full agent system prompt, the real prompt, the filters and routing that make it production-shaped. Tell the user what you\'re changing, then validate it with `mcp__auto__auto_resources_dry_run`, open or update the PR, and let GitHub Sync deploy after merge.\n\n Test end to end: trigger the workflow for real, follow the session, and enlist the user again for out-of-band verification. Useful work means more than an intro message: the agent should review a PR, move a handoff forward, inspect real evidence, or otherwise exercise its actual job.\n\n If the first real workflow is a PR reviewer, offer to open a small follow-up PR that adds the next useful agent, usually the handoff coder when that fits the project. This tests the reviewer on a real `.auto/` resource change while also advancing the user\'s Auto system. Keep the test PR scoped and useful: avoid contrived README churn, validate the new agent resource, record PR ownership, and watch the PR-review session once the PR opens.\n\n If the user accepted the PR-review-first path, propose the handoff coder next once PR review has begun useful work. Build it the same way: hollow wiring first, then a small real handoff or existing PR to prove ownership, feedback routing, and status reporting.\n\n Then celebrate. This is the private milestone you have been steering toward \u2014 act like it. \u{1F389}\n\n ## Beat 6: Bring the user up to speed\n\n Only now, after the first real workflow has begun useful work, introduce the user to the Auto terminal UI. Ask them to run `auto` or `auto tui` from their repo.\n\n Walk the user through what you built: which agent files, environment fragments, identity, tools, and triggers exist, how an event becomes a session, and where each file lives in `.auto/`. Define terms as they appear: resources are declared platform objects; agents are reusable definitions; environments are sandbox setup; triggers map events into sessions; sessions are durable runs with transcript, tools, diagnostics, and artifacts.\n\n Give a short TUI tour tied to their live workflow: find the agent resource, open the session, inspect conversation/tool calls, attach if it is still running, and show manual resource edits. Durable changes should still go through `.auto/` and GitHub Sync.\n\n Then ask what they want to inspect or change before they review and merge the PR.\n\n ## Beat 7: Ship through GitHub Sync\n\n Make merges to their default branch the durable deployment mechanism for their auto system. Auto\'s GitHub Sync applies committed `.auto/` resources after merge.\n\n 1. Run `mcp__auto__auto_resources_dry_run` before opening the PR and summarize the plan in Slack.\n 2. Open a focused PR containing the `.auto/` resource changes. Use the GitHub MCP tools for PR work, then immediately record ownership with `mcp__auto__auto_artifacts_record`.\n 3. Ask the user to review and merge the PR when ready, and tell them Auto will route the apply result back to you automatically. Do not ask them to say "merged" or "done" afterward.\n 4. When the apply lifecycle trigger arrives, verify GitHub Sync applied the resources by inspecting Auto resource/session state rather than GitHub Actions logs. If the apply failed, tell the user promptly, diagnose the failure, and fix the PR branch when the repair is in scope.\n\n When the merge lands and sync has applied cleanly, congratulate them \u2014 their factory now ships from committed resource changes.\n\n ## Beat 8: Set up a self-improvement loop\n\n If the self-improvement agent is already installed, skip this beat except to recap how it can evolve after more sessions and PR feedback accumulate.\n\n Otherwise, once PR review and handoff have produced real traces, propose the self-improvement agent: it reviews PR feedback, read-only data sources, and Auto session history, then suggests high-leverage improvements to the app or the Auto system itself.\n\n If they\'re in, modify `examples/self-improvement/` to tailor it to their setup (their channel, their agents, their cadence). Since GitHub Sync is now the deployment path, open a PR, record ownership, and let them merge it. That\'s the new normal, and modeling it is the point.\n\n ## Beat 9: Conclusion\n\n Tell the user they\'re all set: a live workflow, GitHub Sync for their auto system, and a loop that helps it improve. Recap in two or three lines what now exists. Offer to help them build or optimize additional workflows \u2014 Beat 3\'s runner-up ideas are natural next candidates.\n\n After the conclusion has been sent and no immediate onboarding follow-up\n remains, call mcp__auto__auto_sessions_archive_current before finishing.'
19955
19875
  }
19956
19876
  ]
19877
+ },
19878
+ {
19879
+ version: "1.2.0",
19880
+ files: [
19881
+ {
19882
+ path: "agents/onboarding.yaml",
19883
+ content: `imports:
19884
+ - ../fragments/onboarding.yaml
19885
+ harness: claude-code
19886
+ environment:
19887
+ name: agent-runtime
19888
+ labels:
19889
+ purpose: agents
19890
+ image:
19891
+ kind: preset
19892
+ name: node24
19893
+ resources:
19894
+ memoryMB: 8192
19895
+ steps:
19896
+ - RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client redis-tools jq file && rm -rf /var/lib/apt/lists/*
19897
+ - RUN curl -fsSL https://temporal.download/cli.sh | sh && cp ~/.temporalio/bin/temporal /usr/local/bin/temporal
19898
+ - RUN npm install -g tsx
19899
+ name: onboarding
19900
+ labels:
19901
+ purpose: onboarding
19902
+ session:
19903
+ archiveAfterInactive:
19904
+ seconds: 86400
19905
+ identity:
19906
+ displayName: Auto Onboarding
19907
+ username: onboarding
19908
+ avatar:
19909
+ asset: .auto/assets/default.png
19910
+ description:
19911
+ Auto's onboarding guide - walks you from "what is this?" to your first
19912
+ deployed workflow in the active onboarding conversation.
19913
+ displayTitle: "Onboarding"
19914
+ initialPrompt: |
19915
+ Begin the onboarding now in this web session. Reply directly here with your
19916
+ Beat 1 opening pitch and one question. After the user has heard from you, get
19917
+ up to speed from the reference docs before deeper onboarding work.
19918
+ mounts:
19919
+ - kind: git
19920
+ repository: "{{ $repoFullName }}"
19921
+ mountPath: /workspace/auto
19922
+ ref: main
19923
+ depth: 1
19924
+ auth:
19925
+ kind: githubApp
19926
+ capabilities:
19927
+ contents: write
19928
+ pullRequests: write
19929
+ issues: write
19930
+ checks: read
19931
+ actions: read
19932
+ workflows: write
19933
+ workingDirectory: /workspace/auto
19934
+ tools:
19935
+ auto:
19936
+ kind: local
19937
+ implementation: auto
19938
+ github:
19939
+ kind: github
19940
+ tools:
19941
+ - create_pull_request
19942
+ - pull_request_read
19943
+ - update_pull_request
19944
+ - update_pull_request_branch
19945
+ - pull_request_review_write
19946
+ - add_comment_to_pending_review
19947
+ - add_reply_to_pull_request_comment
19948
+ - add_issue_comment
19949
+ - issue_read
19950
+ - issue_write
19951
+ - search_pull_requests
19952
+ - search_issues
19953
+ - search_code
19954
+ - get_file_contents
19955
+ - list_commits
19956
+ - create_branch
19957
+ - create_or_update_file
19958
+ - push_files
19959
+ - actions_get
19960
+ - actions_list
19961
+ - get_job_logs
19962
+ triggers:
19963
+ - events:
19964
+ - github.issue_comment.created
19965
+ - github.issue_comment.edited
19966
+ - github.pull_request_review.submitted
19967
+ - github.pull_request_review.edited
19968
+ - github.pull_request_review_comment.created
19969
+ - github.pull_request_review_comment.edited
19970
+ connection: "{{ $githubConnection }}"
19971
+ where:
19972
+ $.github.repository.fullName: "{{ $repoFullName }}"
19973
+ message: |
19974
+ A GitHub PR conversation update arrived for {{ $repoFullName }} PR #{{github.pullRequest.number}}.
19975
+
19976
+ Source URLs, when present:
19977
+ - issue comment: {{github.issueComment.htmlUrl}}
19978
+ - review: {{github.review.htmlUrl}}
19979
+ - review comment: {{github.reviewComment.htmlUrl}}
19980
+
19981
+ Read the update and decide whether it requires onboarding follow-up.
19982
+ Keep work on the existing PR branch and communicate in this web session.
19983
+ routing:
19984
+ kind: deliver
19985
+ routeBy:
19986
+ kind: ownedArtifact
19987
+ artifactType: github.pull_request
19988
+ onUnmatched: drop
19989
+ - event: github.check_run.completed
19990
+ connection: "{{ $githubConnection }}"
19991
+ where:
19992
+ $.github.repository.fullName: "{{ $repoFullName }}"
19993
+ $.github.checkRun.conclusion: failure
19994
+ $.github.checkRun.name:
19995
+ notIn:
19996
+ - All checks
19997
+ message: |
19998
+ Check {{github.checkRun.name}} failed on {{ $repoFullName }} PR #{{github.pullRequest.number}}.
19999
+
20000
+ Diagnose the failure, fix it on the existing PR branch when it is in
20001
+ scope, and update this web session.
20002
+
20003
+ Check session URL: {{github.checkRun.htmlUrl}}
20004
+ routing:
20005
+ kind: deliver
20006
+ routeBy:
20007
+ kind: ownedArtifact
20008
+ artifactType: github.pull_request
20009
+ onUnmatched: drop
20010
+ - event: github.check_run.completed
20011
+ connection: "{{ $githubConnection }}"
20012
+ where:
20013
+ $.github.repository.fullName: "{{ $repoFullName }}"
20014
+ $.github.checkRun.conclusion: success
20015
+ $.github.checkRun.name: All checks
20016
+ message: |
20017
+ Aggregate CI passed on {{ $repoFullName }} PR #{{github.pullRequest.number}}.
20018
+
20019
+ Inspect PR comments, reviews, and checks. If the PR is ready for the
20020
+ user to merge, say so in this web session; do not merge unless the user
20021
+ explicitly asks.
20022
+ routing:
20023
+ kind: deliver
20024
+ routeBy:
20025
+ kind: ownedArtifact
20026
+ artifactType: github.pull_request
20027
+ onUnmatched: drop
20028
+ - event: github.pull_request.merge_conflict
20029
+ connection: "{{ $githubConnection }}"
20030
+ where:
20031
+ $.github.repository.fullName: "{{ $repoFullName }}"
20032
+ message: |
20033
+ A merge conflict was detected on {{ $repoFullName }} PR #{{github.pullRequest.number}}.
20034
+
20035
+ Repair the existing PR branch with a normal follow-up commit if it is
20036
+ safe and scoped. Do not force-push or open a replacement PR.
20037
+ routing:
20038
+ kind: deliver
20039
+ routeBy:
20040
+ kind: ownedArtifact
20041
+ artifactType: github.pull_request
20042
+ onUnmatched: drop
20043
+ - event: auto.project_resource_apply.completed
20044
+ where:
20045
+ $.apply.auditAction: github_sync.apply
20046
+ message: |
20047
+ GitHub Sync applied project resources for an onboarding PR you own.
20048
+
20049
+ Apply operation: {{apply.operationId}}
20050
+ Created: {{apply.plan.counts.create}}
20051
+ Updated: {{apply.plan.counts.update}}
20052
+ Archived: {{apply.plan.counts.archive}}
20053
+ Unchanged: {{apply.plan.counts.unchanged}}
20054
+ Diagnostics: {{apply.plan.counts.diagnostics}}
20055
+
20056
+ Continue the onboarding flow in the web session. Inspect the deployed
20057
+ resource state with Auto MCP tools. If apply.plan.changedResources
20058
+ contains a newly created agent, spawn that agent to introduce itself in
20059
+ the session context or perform the next smoke-test step. Do not wait for
20060
+ the user to say they merged the PR or that the apply finished.
20061
+ routing:
20062
+ kind: deliver
20063
+ routeBy:
20064
+ kind: ownedArtifact
20065
+ artifactType: github.pull_request
20066
+ onUnmatched: drop
20067
+ - event: auto.project_resource_apply.failed
20068
+ where:
20069
+ $.apply.auditAction: github_sync.apply
20070
+ message: |
20071
+ GitHub Sync failed while applying project resources for an onboarding PR
20072
+ you own.
20073
+
20074
+ Apply operation: {{apply.operationId}}
20075
+ Error type: {{apply.error.name}}
20076
+ Error: {{apply.error.message}}
20077
+ Requested resources: {{apply.request.resources}}
20078
+ Requested deletes: {{apply.request.delete}}
20079
+
20080
+ Tell the user in the web session that Auto tried to apply the change and
20081
+ hit the error above. Then diagnose the failure, propose the concrete
20082
+ solution, repair the existing PR branch with a normal follow-up commit if
20083
+ the fix is in scope, and update the session with what changed. Do not ask
20084
+ the user to debug the apply locally.
20085
+ routing:
20086
+ kind: deliver
20087
+ routeBy:
20088
+ kind: ownedArtifact
20089
+ artifactType: github.pull_request
20090
+ onUnmatched: drop
20091
+ `
20092
+ },
20093
+ {
20094
+ path: "fragments/onboarding.yaml",
20095
+ content: "systemPrompt: |\n # How you communicate\n\n You are Auto's hosted onboarding guide. The user is talking to you in Mission\n Control's web session UI. Reply directly in the session chat. Do not use Slack\n or chat tools for onboarding conversation, and do not tell the user to move the\n conversation to another surface.\n\n Keep replies short, conversational, and specific. Ask one question at a time.\n Before non-trivial repository exploration, resource editing, PR work, OAuth\n setup, debugging, or waiting on an async session, acknowledge what you are about\n to do in the session first.\n\n # Intent\n\n Achieve three goals, in this order:\n\n 1. Educate the user on what Auto is and how resources, agents, triggers, tools,\n sessions, and GitHub Sync fit together.\n 2. Get a tailor-made proactive workflow live that solves a real problem for\n them, and verify it works end to end.\n 3. Leave them with a repeatable path for improving their Auto system through\n committed `.auto/` resources and GitHub Sync.\n\n Never claim a step worked until you have verified it with the relevant Auto,\n GitHub, or session state.\n\n # Reference material\n\n Reference docs and examples are available in the sandbox under\n `/workspace/auto-docs/`. Read only what the current onboarding step needs.\n\n Start with:\n\n - `/workspace/auto-docs/docs/index.md`\n - `/workspace/auto-docs/docs/resource-model.md`\n - `/workspace/auto-docs/docs/agents-and-triggers.md`\n - `/workspace/auto-docs/docs/tools-and-connections.md`\n - `/workspace/auto-docs/docs/ci-cd.md`\n - `/workspace/auto-docs/examples/index.md`\n\n # Operating principles\n\n Use the Auto MCP tool as your operator surface for connection discovery,\n resource dry-runs, session inspection, artifact ownership, and consent flows.\n Use the GitHub MCP tools and the mounted checkout for repository work.\n\n Treat the mounted repository and project provider connections as already\n available. Inspect the checkout and `git remote get-url origin` before asking\n the user for repository details.\n\n Ask before changing anything outside `.auto/`. The onboarding write surface is\n the `.auto/` directory unless the user explicitly approves another file.\n\n When a provider or remote MCP tool authorization is needed, explain why, start\n the Auto connection flow, give the authorization URL cleanly, and verify the\n connection completed before continuing. Never ask the user to paste secret\n values into the session chat.\n\n Deploy through GitHub Sync. Validate drafted resources with\n `mcp__auto__auto_resources_dry_run`, open a focused PR, call\n `mcp__auto__auto_artifacts_record` for the PR, and tell the user to merge when\n the PR is ready. The apply lifecycle trigger will return the result to you.\n\n Every agent you create should have a clear identity and avatar. Use the avatar\n catalog in `/workspace/auto-docs/docs/design.md`, copy the selected asset into\n `.auto/assets/`, and reference it from `identity.avatar.asset`.\n\n When the user needs to do something, spell out the exact action and what they\n should expect to see. Do not rely on vague prompts like \"try it when ready.\"\n\n # Onboarding beats\n\n Beat 1: Give a short pitch. Explain that Auto lets them compose agents and\n triggers into workflows using `.auto/` YAML, and that GitHub Sync applies\n merged resource changes. Ask what repetitive workflow or operational pain they\n want to automate first.\n\n Beat 2: Inspect the connected repository and the available Auto connections.\n Read the docs index and examples index. Summarize one recommended first\n workflow based on the repo and the user's answer.\n\n Beat 3: Draft the workflow under `.auto/`, including agent YAML, triggers,\n tools, identities, and assets. Use existing examples when they fit. Dry-run the\n resources before opening a PR.\n\n Beat 4: Open the PR, record ownership of the pull request artifact, and tell\n the user exactly what changed and what to review. Do not merge unless the user\n explicitly asks.\n\n Beat 5: After the user merges, handle the apply lifecycle event. Verify the\n resource state, then run or guide a smoke test that proves the workflow works.\n\n Beat 6: Recap what now exists and how the user can change it with normal PRs.\n Offer the next best improvement only after the first workflow is live and\n verified.\n\n When onboarding is complete and no immediate follow-up remains, call\n `mcp__auto__auto_sessions_archive_current`.\n"
20096
+ }
20097
+ ]
19957
20098
  }
19958
20099
  ],
19959
20100
  "@auto/pr-review": [
@@ -22999,7 +23140,7 @@ var init_package = __esm({
22999
23140
  "package.json"() {
23000
23141
  package_default = {
23001
23142
  name: "@autohq/cli",
23002
- version: "0.1.281",
23143
+ version: "0.1.283",
23003
23144
  license: "SEE LICENSE IN README.md",
23004
23145
  publishConfig: {
23005
23146
  access: "public"
@@ -23031,6 +23172,7 @@ var init_package = __esm({
23031
23172
  "@anthropic-ai/claude-agent-sdk": "^0.3.153",
23032
23173
  "@inkjs/ui": "^2.0.0",
23033
23174
  "@tanstack/react-query": "^5.100.14",
23175
+ ai: "^6.0.217",
23034
23176
  chalk: "^5.3.0",
23035
23177
  commander: "^14.0.2",
23036
23178
  ink: "^7",
@@ -23639,6 +23781,9 @@ function createConversationStreamWriter(input) {
23639
23781
  input.writeLine(formatConversationRealtimeEvent(event));
23640
23782
  return;
23641
23783
  }
23784
+ if (event.type === "ui.message.chunk") {
23785
+ return;
23786
+ }
23642
23787
  if (event.messageId && streamedMessageIds.has(event.messageId)) {
23643
23788
  streamedMessageIds.delete(event.messageId);
23644
23789
  input.writeChunk?.("\n");
@@ -23651,6 +23796,10 @@ function createConversationStreamWriter(input) {
23651
23796
  };
23652
23797
  }
23653
23798
  function formatConversationRealtimeEvent(event) {
23799
+ if (event.type === "ui.message.chunk") {
23800
+ const chunkType = typeof event.chunk === "object" && event.chunk !== null && !Array.isArray(event.chunk) && typeof event.chunk.type === "string" ? event.chunk.type : "unknown";
23801
+ return `[${event.sequence}] ui message chunk: ${chunkType}`;
23802
+ }
23654
23803
  if (event.type === "conversation.delta") {
23655
23804
  return `[${event.sequence}] assistant/message ${event.delta.type} delta: ${event.delta.text}`;
23656
23805
  }
@@ -23672,6 +23821,9 @@ function formatConversationEntry(entry) {
23672
23821
  (question) => `${question.question} [${question.options.map((option) => option.label).join(" | ")}]`
23673
23822
  ).join("; ");
23674
23823
  }
23824
+ if (part.type === "ui_message") {
23825
+ return JSON.stringify(part.message);
23826
+ }
23675
23827
  return `${part.isError ? "tool error" : "tool result"} ${part.toolUseId ?? ""}: ${JSON.stringify(part.output)}`;
23676
23828
  }).join("");
23677
23829
  return `[${entry.sequence}] ${entry.role}/${entry.kind}: ${content}`;
@@ -27422,6 +27574,10 @@ function ConversationView({
27422
27574
  sawLiveTurnActivity.current = true;
27423
27575
  return;
27424
27576
  }
27577
+ if (event.type === "ui.message.chunk") {
27578
+ sawLiveTurnActivity.current = true;
27579
+ return;
27580
+ }
27425
27581
  if (event.kind === "question") {
27426
27582
  pendingLocalCommand.current = null;
27427
27583
  updateAwaitingReply(false);
@@ -27938,6 +28094,9 @@ function streamedCharCount(event) {
27938
28094
  if (event.type === "conversation.delta") {
27939
28095
  return event.delta.text.length;
27940
28096
  }
28097
+ if (event.type !== "conversation.entry") {
28098
+ return 0;
28099
+ }
27941
28100
  if (event.kind === "tool_call" && event.status !== "in_progress") {
27942
28101
  return event.content.parts.filter((part) => part.type === "tool_call").reduce((total, part) => total + JSON.stringify(part.input).length, 0);
27943
28102
  }
@@ -32775,12 +32934,17 @@ var ConversationQuestionContentPartSchema2 = external_exports.object({
32775
32934
  toolCallId: external_exports.string().min(1).nullable(),
32776
32935
  questions: external_exports.array(ConversationQuestionSchema2)
32777
32936
  });
32937
+ var ConversationUiMessageContentPartSchema2 = external_exports.object({
32938
+ type: external_exports.literal("ui_message"),
32939
+ message: JsonValueSchema2
32940
+ });
32778
32941
  var ConversationContentPartSchema2 = external_exports.discriminatedUnion("type", [
32779
32942
  ConversationTextContentPartSchema2,
32780
32943
  ConversationReasoningContentPartSchema2,
32781
32944
  ConversationToolCallContentPartSchema2,
32782
32945
  ConversationToolResultContentPartSchema2,
32783
- ConversationQuestionContentPartSchema2
32946
+ ConversationQuestionContentPartSchema2,
32947
+ ConversationUiMessageContentPartSchema2
32784
32948
  ]);
32785
32949
  var ConversationEntryContentSchema2 = external_exports.object({
32786
32950
  parts: external_exports.array(ConversationContentPartSchema2)
@@ -32962,9 +33126,35 @@ var RuntimeBridgeOutputDeltaEnvelopeSchema = external_exports.object({
32962
33126
  delta: ConversationDeltaSchema2,
32963
33127
  createdAt: external_exports.string().datetime()
32964
33128
  }).strict();
33129
+ var RuntimeBridgeOutputUiMessageChunkEnvelopeSchema = external_exports.object({
33130
+ type: external_exports.literal("ui.message.chunk"),
33131
+ sessionId: SessionIdSchema2,
33132
+ runtimeId: RuntimeIdSchema2,
33133
+ bridgeLeaseId: RuntimeBridgeLeaseIdSchema2,
33134
+ outputSeq: external_exports.number().int().positive(),
33135
+ chunk: JsonValueSchema2,
33136
+ createdAt: external_exports.string().datetime()
33137
+ }).strict();
33138
+ var RuntimeBridgeOutputUiMessageCompletedEnvelopeSchema = external_exports.object({
33139
+ type: external_exports.literal("ui.message.completed"),
33140
+ sessionId: SessionIdSchema2,
33141
+ runtimeId: RuntimeIdSchema2,
33142
+ bridgeLeaseId: RuntimeBridgeLeaseIdSchema2,
33143
+ outputSeq: external_exports.number().int().positive(),
33144
+ messageId: external_exports.string().trim().min(1),
33145
+ role: external_exports.enum(["system", "user", "assistant"]),
33146
+ status: ConversationEntryStatusSchema2,
33147
+ message: JsonValueSchema2,
33148
+ turnStatus: external_exports.enum(["waiting_for_input", "completed", "failed"]).optional(),
33149
+ usage: RuntimeBridgeTurnUsageSchema.optional(),
33150
+ createdAt: external_exports.string().datetime(),
33151
+ completedAt: external_exports.string().datetime().nullable()
33152
+ }).strict();
32965
33153
  var RuntimeBridgeOutputEnvelopeSchema = external_exports.union([
32966
33154
  RuntimeBridgeOutputEntryEnvelopeSchema,
32967
- RuntimeBridgeOutputDeltaEnvelopeSchema
33155
+ RuntimeBridgeOutputDeltaEnvelopeSchema,
33156
+ RuntimeBridgeOutputUiMessageChunkEnvelopeSchema,
33157
+ RuntimeBridgeOutputUiMessageCompletedEnvelopeSchema
32968
33158
  ]);
32969
33159
  function legacyRuntimeTurnStatus(entry, sessionStatusAfter) {
32970
33160
  if (sessionStatusAfter === "failed" || entry.status === "failed") {
@@ -33444,17 +33634,27 @@ function outputLogContext(output, socketId) {
33444
33634
  kind: "kind" in output ? output.kind : void 0,
33445
33635
  output_status: "status" in output ? output.status : void 0,
33446
33636
  delta_type: output.type === "conversation.delta" ? output.delta.type : void 0,
33637
+ ui_chunk_type: output.type === "ui.message.chunk" ? jsonRecordString(output.chunk, "type") : void 0,
33447
33638
  message_id: "messageId" in output ? output.messageId : void 0
33448
33639
  };
33449
33640
  }
33450
33641
  function errorMessage(error51) {
33451
33642
  return error51 instanceof Error ? error51.message : String(error51);
33452
33643
  }
33644
+ function jsonRecordString(value, key) {
33645
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
33646
+ return void 0;
33647
+ }
33648
+ const field = value[key];
33649
+ return typeof field === "string" ? field : void 0;
33650
+ }
33453
33651
 
33454
33652
  // src/commands/agent-bridge/harness/claude-code/index.ts
33455
33653
  init_src();
33456
33654
 
33457
33655
  // src/commands/agent-bridge/harness/output-buffer.ts
33656
+ init_src();
33657
+ import { readUIMessageStream } from "ai";
33458
33658
  var AGENT_BRIDGE_OUTPUT_DELTA_FLUSH_MS = 50;
33459
33659
  var AgentBridgeOutputBuffer = class {
33460
33660
  constructor(input) {
@@ -33466,12 +33666,17 @@ var AgentBridgeOutputBuffer = class {
33466
33666
  pendingOutputs = /* @__PURE__ */ new Map();
33467
33667
  pendingDelta = null;
33468
33668
  deltaFlushTimer = null;
33669
+ activeUiMessageAssembler = null;
33469
33670
  drainBlocked = false;
33470
33671
  drainPromise = null;
33471
33672
  // ---------------------------------------------------------------------------
33472
33673
  // Public API
33473
33674
  // ---------------------------------------------------------------------------
33474
33675
  async emitProjection(context, projection) {
33676
+ if (projection.type === "ui_message_chunk") {
33677
+ await this.emitUiMessageChunk(context, projection);
33678
+ return;
33679
+ }
33475
33680
  if (projection.type === "delta") {
33476
33681
  await this.bufferDelta(context, projection.delta);
33477
33682
  return;
@@ -33483,6 +33688,75 @@ var AgentBridgeOutputBuffer = class {
33483
33688
  await this.flushPendingDelta({ force: true });
33484
33689
  await this.drainPendingOutputs({ force: true });
33485
33690
  }
33691
+ async emitUiMessageChunk(context, projection) {
33692
+ if (projection.chunk.type === "data-auto-question") {
33693
+ await this.flushPendingDelta();
33694
+ await this.emitLiveUiMessageChunk(context, projection.chunk);
33695
+ await this.enqueueProjectionAndDrain(context, {
33696
+ type: "entry",
33697
+ entry: {
33698
+ role: "assistant",
33699
+ kind: "question",
33700
+ ...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
33701
+ content: {
33702
+ parts: [questionPart(projection.chunk.data)]
33703
+ }
33704
+ }
33705
+ });
33706
+ return;
33707
+ }
33708
+ await this.flushPendingDelta();
33709
+ await this.emitLiveUiMessageChunk(context, projection.chunk);
33710
+ const completedMessage = await this.appendUiChunkToAssembler(
33711
+ projection.chunk
33712
+ );
33713
+ if (!completedMessage || !completedMessage.id.trim()) {
33714
+ if (isTerminalUiMessageChunk(projection.chunk)) {
33715
+ const statusText = terminalStatusText(projection);
33716
+ if (statusText) {
33717
+ await this.enqueueProjectionAndDrain(context, {
33718
+ type: "entry",
33719
+ entry: {
33720
+ role: "system",
33721
+ kind: "status",
33722
+ status: terminalEntryStatus(projection),
33723
+ ...projection.turnStatus ? { turnStatus: projection.turnStatus } : {},
33724
+ ...projection.usage ? { usage: projection.usage } : {},
33725
+ content: {
33726
+ parts: [{ type: "text", text: statusText }]
33727
+ }
33728
+ }
33729
+ });
33730
+ }
33731
+ }
33732
+ return;
33733
+ }
33734
+ this.outputSeq += 1;
33735
+ const completedAt = now2();
33736
+ const completedOutput = buildUiMessageCompletedOutputEnvelope({
33737
+ context,
33738
+ outputSeq: this.outputSeq,
33739
+ message: completedMessage,
33740
+ status: terminalEntryStatus(projection),
33741
+ turnStatus: projection.turnStatus,
33742
+ usage: projection.usage,
33743
+ createdAt: completedAt,
33744
+ completedAt
33745
+ });
33746
+ this.enqueueOutput(completedOutput);
33747
+ await this.drainPendingOutputs();
33748
+ }
33749
+ async emitLiveUiMessageChunk(context, chunk) {
33750
+ this.outputSeq += 1;
33751
+ const output = buildUiMessageChunkOutputEnvelope({
33752
+ context,
33753
+ outputSeq: this.outputSeq,
33754
+ chunk,
33755
+ createdAt: now2()
33756
+ });
33757
+ this.enqueueOutput(output);
33758
+ await this.drainPendingOutputs();
33759
+ }
33486
33760
  // ---------------------------------------------------------------------------
33487
33761
  // Transport emit
33488
33762
  // ---------------------------------------------------------------------------
@@ -33639,11 +33913,91 @@ var AgentBridgeOutputBuffer = class {
33639
33913
  kind: output && "kind" in output ? output.kind : void 0,
33640
33914
  output_status: output && "status" in output ? output.status : void 0,
33641
33915
  delta_type: output?.type === "conversation.delta" ? output.delta.type : void 0,
33916
+ ui_chunk_type: output?.type === "ui.message.chunk" ? jsonRecordString2(output.chunk, "type") : void 0,
33642
33917
  message_id: output && "messageId" in output ? output.messageId : void 0,
33643
33918
  ...fields
33644
33919
  };
33645
33920
  }
33921
+ async appendUiChunkToAssembler(chunk) {
33922
+ if (chunk.type === "start" || !this.activeUiMessageAssembler || this.activeUiMessageAssembler.done) {
33923
+ this.activeUiMessageAssembler = new UiMessageAssembler();
33924
+ }
33925
+ const completedMessage = await this.activeUiMessageAssembler.appendChunk(chunk);
33926
+ if (completedMessage) {
33927
+ this.activeUiMessageAssembler = null;
33928
+ }
33929
+ return completedMessage;
33930
+ }
33931
+ };
33932
+ var UiMessageAssembler = class {
33933
+ controller = null;
33934
+ latestMessage = null;
33935
+ closed = false;
33936
+ drained;
33937
+ constructor() {
33938
+ const stream = new ReadableStream({
33939
+ start: (controller) => {
33940
+ this.controller = controller;
33941
+ }
33942
+ });
33943
+ this.drained = (async () => {
33944
+ try {
33945
+ for await (const message of readUIMessageStream({
33946
+ stream,
33947
+ terminateOnError: false
33948
+ })) {
33949
+ this.latestMessage = message;
33950
+ }
33951
+ } finally {
33952
+ this.closed = true;
33953
+ }
33954
+ })();
33955
+ }
33956
+ get done() {
33957
+ return this.closed;
33958
+ }
33959
+ async appendChunk(chunk) {
33960
+ try {
33961
+ this.controller?.enqueue(chunk);
33962
+ } catch {
33963
+ this.closed = true;
33964
+ return null;
33965
+ }
33966
+ if (!isTerminalUiMessageChunk(chunk)) {
33967
+ return null;
33968
+ }
33969
+ this.closed = true;
33970
+ this.controller?.close();
33971
+ await this.drained;
33972
+ return this.latestMessage;
33973
+ }
33646
33974
  };
33975
+ function isTerminalUiMessageChunk(chunk) {
33976
+ return chunk.type === "finish" || chunk.type === "error" || chunk.type === "abort";
33977
+ }
33978
+ function questionPart(data) {
33979
+ const parsed = data;
33980
+ return {
33981
+ type: "question",
33982
+ toolCallId: parsed.toolCallId ?? null,
33983
+ questions: parsed.questions
33984
+ };
33985
+ }
33986
+ function terminalStatusText(projection) {
33987
+ if (projection.statusText) {
33988
+ return projection.statusText;
33989
+ }
33990
+ if (projection.chunk.type === "error") {
33991
+ return projection.chunk.errorText;
33992
+ }
33993
+ return null;
33994
+ }
33995
+ function terminalEntryStatus(projection) {
33996
+ if (projection.turnStatus === "failed" || projection.chunk.type === "error" || projection.chunk.type === "abort") {
33997
+ return "failed";
33998
+ }
33999
+ return "completed";
34000
+ }
33647
34001
  function canCoalesceDelta(pending, context, delta) {
33648
34002
  return pending.context.sessionId === context.sessionId && pending.context.runtimeId === context.runtimeId && pending.context.bridgeLeaseId === context.bridgeLeaseId && pending.delta.messageId === delta.messageId && pending.delta.partId === delta.partId && pending.delta.role === delta.role && pending.delta.kind === delta.kind && pending.delta.delta.type === delta.delta.type;
33649
34003
  }
@@ -33703,67 +34057,209 @@ function buildDeltaOutputEnvelope(input) {
33703
34057
  createdAt: input.createdAt
33704
34058
  });
33705
34059
  }
34060
+ function buildUiMessageChunkOutputEnvelope(input) {
34061
+ const { context } = input;
34062
+ return RuntimeBridgeOutputEnvelopeSchema.parse({
34063
+ type: "ui.message.chunk",
34064
+ sessionId: context.sessionId,
34065
+ runtimeId: context.runtimeId,
34066
+ bridgeLeaseId: context.bridgeLeaseId,
34067
+ outputSeq: input.outputSeq,
34068
+ chunk: toJsonValue(input.chunk),
34069
+ createdAt: input.createdAt
34070
+ });
34071
+ }
34072
+ function buildUiMessageCompletedOutputEnvelope(input) {
34073
+ const { context } = input;
34074
+ return RuntimeBridgeOutputEnvelopeSchema.parse({
34075
+ type: "ui.message.completed",
34076
+ sessionId: context.sessionId,
34077
+ runtimeId: context.runtimeId,
34078
+ bridgeLeaseId: context.bridgeLeaseId,
34079
+ outputSeq: input.outputSeq,
34080
+ messageId: input.message.id,
34081
+ role: input.message.role,
34082
+ status: input.status,
34083
+ message: toJsonValue(input.message),
34084
+ ...input.turnStatus ? { turnStatus: input.turnStatus } : {},
34085
+ ...input.usage ? { usage: input.usage } : {},
34086
+ createdAt: input.createdAt,
34087
+ completedAt: input.completedAt
34088
+ });
34089
+ }
33706
34090
  function isSuccessfulOutputAck(ack) {
33707
34091
  return ack.status === "persisted" || ack.status === "duplicate" || ack.status === "published";
33708
34092
  }
33709
34093
  function now2() {
33710
34094
  return (/* @__PURE__ */ new Date()).toISOString();
33711
34095
  }
34096
+ function jsonRecordString2(value, key) {
34097
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
34098
+ return void 0;
34099
+ }
34100
+ const field = value[key];
34101
+ return typeof field === "string" ? field : void 0;
34102
+ }
33712
34103
 
33713
34104
  // src/commands/agent-bridge/harness/claude-code/projector.ts
33714
34105
  init_src();
33715
34106
  var ClaudeCodeProjector = class {
33716
34107
  currentMessageId = null;
34108
+ // The turn's UI message id. Claude emits a `message_start` per assistant
34109
+ // message, but a tool-using turn spans several (message A with the tool call,
34110
+ // the tool result, then message B). We open the UI message once per turn and
34111
+ // bracket each message as a step (finish-step at message_stop), closing it
34112
+ // with the single terminal `finish` at the turn result — so the whole turn,
34113
+ // including paired tool input/output, assembles into one UIMessage. Mirrors
34114
+ // the Codex projector's `ensureResponseStarted`.
34115
+ activeResponseMessageId = null;
34116
+ activeParts = /* @__PURE__ */ new Map();
34117
+ activeToolInputs = /* @__PURE__ */ new Map();
34118
+ streamedMessageIds = /* @__PURE__ */ new Set();
33717
34119
  project(message) {
33718
34120
  if (message.type === "stream_event") {
33719
- if (message.event.type === "message_start") {
33720
- this.currentMessageId = anthropicMessageIdFromStreamStart(message) ?? UNKNOWN_MESSAGE_ID;
33721
- return [];
33722
- }
33723
- if (message.event.type === "message_stop") {
33724
- this.currentMessageId = null;
33725
- return [];
33726
- }
33727
- const delta = partialAssistantDeltaPayload(
33728
- message,
33729
- this.currentMessageId ?? UNKNOWN_MESSAGE_ID
33730
- );
33731
- return delta ? [{ type: "delta", delta }] : [];
34121
+ return this.projectStreamEvent(message);
33732
34122
  }
33733
34123
  const parsed = parseClaudeCodeStreamRecord(message);
33734
- const outputs = parsed.projections.map(
33735
- (entry) => ({
33736
- type: "entry",
33737
- // A question means the delivered turn is parked on AskUserQuestion
33738
- // waiting for an operator answer.
33739
- entry: entry.kind === "question" ? { ...entry, turnStatus: "waiting_for_input" } : entry
33740
- })
33741
- );
34124
+ const snapshotMessageId = assistantSnapshotMessageId(message);
34125
+ const suppressStreamed = snapshotMessageId !== null && this.streamedMessageIds.has(snapshotMessageId);
34126
+ const outputs = !suppressStreamed && message.type === "assistant" ? projectAssistantSnapshot(parsed.projections) : parsed.projections.flatMap((entry) => {
34127
+ if (suppressStreamed && (entry.kind === "message" || entry.kind === "tool_call")) {
34128
+ return [];
34129
+ }
34130
+ return conversationProjectionToUiChunks(entry);
34131
+ });
33742
34132
  if (parsed.result) {
34133
+ outputs.push(...this.endActiveParts(), ...this.finishActiveToolInputs());
34134
+ this.currentMessageId = null;
34135
+ this.activeResponseMessageId = null;
33743
34136
  outputs.push(projectClaudeCodeResult(parsed.result));
33744
34137
  }
33745
34138
  return outputs;
33746
34139
  }
34140
+ flushPendingAssistantMessages() {
34141
+ const outputs = [
34142
+ ...this.endActiveParts(),
34143
+ uiChunk({ type: "finish", finishReason: "stop" })
34144
+ ];
34145
+ this.currentMessageId = null;
34146
+ this.activeResponseMessageId = null;
34147
+ return outputs;
34148
+ }
34149
+ projectStreamEvent(message) {
34150
+ if (message.event.type === "message_start") {
34151
+ this.currentMessageId = anthropicMessageIdFromStreamStart(message) ?? UNKNOWN_MESSAGE_ID;
34152
+ this.streamedMessageIds.add(this.currentMessageId);
34153
+ this.activeParts.clear();
34154
+ this.activeToolInputs.clear();
34155
+ return this.ensureResponseStarted(this.currentMessageId);
34156
+ }
34157
+ if (message.event.type === "message_stop") {
34158
+ const outputs = [
34159
+ ...this.endActiveParts(),
34160
+ ...this.finishActiveToolInputs(),
34161
+ uiChunk({ type: "finish-step" })
34162
+ ];
34163
+ this.currentMessageId = null;
34164
+ return outputs;
34165
+ }
34166
+ if (message.event.type === "content_block_start") {
34167
+ return this.startToolInput(message);
34168
+ }
34169
+ if (message.event.type === "content_block_stop") {
34170
+ return [
34171
+ ...this.endActivePart(blockIndexId(message.event.index)),
34172
+ ...this.finishActiveToolInput(blockIndexId(message.event.index))
34173
+ ];
34174
+ }
34175
+ return partialAssistantDeltaProjections(
34176
+ message,
34177
+ this.activeParts,
34178
+ this.activeToolInputs
34179
+ );
34180
+ }
34181
+ // Open the turn's UI message exactly once. Subsequent messages in the same
34182
+ // turn reuse it (their content becomes additional steps), so the turn
34183
+ // assembles into a single UIMessage instead of the assembler resetting on
34184
+ // each `start` and dropping every message but the last.
34185
+ ensureResponseStarted(messageId) {
34186
+ if (this.activeResponseMessageId !== null) {
34187
+ return [];
34188
+ }
34189
+ this.activeResponseMessageId = messageId;
34190
+ return [uiChunk({ type: "start", messageId })];
34191
+ }
34192
+ endActiveParts() {
34193
+ const outputs = [...this.activeParts].map(([id]) => this.endActivePart(id));
34194
+ this.activeParts.clear();
34195
+ return outputs.flat();
34196
+ }
34197
+ endActivePart(id) {
34198
+ const type = this.activeParts.get(id);
34199
+ if (!type) {
34200
+ return [];
34201
+ }
34202
+ this.activeParts.delete(id);
34203
+ return [uiChunk({ type: `${type}-end`, id })];
34204
+ }
34205
+ startToolInput(message) {
34206
+ const started = toolInputStartFromEvent(message);
34207
+ if (!started) {
34208
+ return [];
34209
+ }
34210
+ this.activeToolInputs.set(started.index, {
34211
+ toolCallId: started.toolCallId,
34212
+ toolName: started.toolName,
34213
+ initialInput: started.input,
34214
+ inputText: ""
34215
+ });
34216
+ return [
34217
+ uiChunk({
34218
+ type: "tool-input-start",
34219
+ toolCallId: started.toolCallId,
34220
+ toolName: started.toolName
34221
+ })
34222
+ ];
34223
+ }
34224
+ finishActiveToolInput(index) {
34225
+ const active = this.activeToolInputs.get(index);
34226
+ if (!active) {
34227
+ return [];
34228
+ }
34229
+ this.activeToolInputs.delete(index);
34230
+ return [
34231
+ uiChunk({
34232
+ type: "tool-input-available",
34233
+ toolCallId: active.toolCallId,
34234
+ toolName: active.toolName,
34235
+ input: completeToolInput(active)
34236
+ })
34237
+ ];
34238
+ }
34239
+ finishActiveToolInputs() {
34240
+ return [...this.activeToolInputs.keys()].flatMap(
34241
+ (index) => this.finishActiveToolInput(index)
34242
+ );
34243
+ }
33747
34244
  };
33748
34245
  function projectClaudeCodeResult(result) {
33749
34246
  return {
33750
- type: "entry",
33751
- entry: {
33752
- role: "system",
33753
- kind: "status",
33754
- status: result.isError ? "failed" : "completed",
33755
- turnStatus: result.isError ? "failed" : "completed",
33756
- content: {
33757
- parts: [
33758
- {
33759
- type: "text",
33760
- text: result.isError ? `claude-code failed: ${result.errorMessage ?? "unknown error"}` : "claude-code completed"
33761
- }
33762
- ]
33763
- }
33764
- }
34247
+ type: "ui_message_chunk",
34248
+ chunk: {
34249
+ type: "finish",
34250
+ finishReason: result.isError ? "error" : "stop"
34251
+ },
34252
+ statusText: result.isError ? `claude-code failed: ${result.errorMessage ?? "unknown error"}` : "claude-code completed",
34253
+ turnStatus: result.isError ? "failed" : "completed"
33765
34254
  };
33766
34255
  }
34256
+ function assistantSnapshotMessageId(message) {
34257
+ if (message.type !== "assistant") {
34258
+ return null;
34259
+ }
34260
+ const id = message.message.id;
34261
+ return typeof id === "string" && id.trim() ? id : UNKNOWN_MESSAGE_ID;
34262
+ }
33767
34263
  function anthropicMessageIdFromStreamStart(message) {
33768
34264
  const event = message.event;
33769
34265
  if (event.type !== "message_start") {
@@ -33772,27 +34268,284 @@ function anthropicMessageIdFromStreamStart(message) {
33772
34268
  const id = event.message.id;
33773
34269
  return typeof id === "string" && id.trim() ? id : null;
33774
34270
  }
33775
- function partialAssistantDeltaPayload(message, messageId) {
34271
+ function partialAssistantDeltaProjections(message, activeParts, activeToolInputs) {
33776
34272
  const event = message.event;
33777
34273
  if (event.type !== "content_block_delta") {
33778
- return null;
34274
+ return [];
34275
+ }
34276
+ const toolInputDelta = toolInputDeltaFromDelta(event.delta);
34277
+ if (toolInputDelta) {
34278
+ const active = activeToolInputs.get(blockIndexId(event.index));
34279
+ if (!active) {
34280
+ return [];
34281
+ }
34282
+ active.inputText += toolInputDelta;
34283
+ return [
34284
+ uiChunk({
34285
+ type: "tool-input-delta",
34286
+ toolCallId: active.toolCallId,
34287
+ inputTextDelta: toolInputDelta
34288
+ })
34289
+ ];
34290
+ }
34291
+ const sourceChunk = sourceChunkFromDelta(event.delta, event.index);
34292
+ if (sourceChunk) {
34293
+ return [uiChunk(sourceChunk)];
33779
34294
  }
33780
34295
  const delta = event.delta;
33781
- const text = delta.type === "text_delta" ? delta.text : delta.type === "thinking_delta" ? delta.thinking : "";
33782
- if (text.length === 0) {
34296
+ const content = textContentFromDelta(delta);
34297
+ if (!content || content.text.length === 0) {
34298
+ return [];
34299
+ }
34300
+ const id = typeof event.index === "number" ? String(event.index) : "0";
34301
+ const outputs = [];
34302
+ if (!activeParts.has(id)) {
34303
+ activeParts.set(id, content.type);
34304
+ outputs.push(
34305
+ uiChunk({ type: `${content.type}-start`, id })
34306
+ );
34307
+ }
34308
+ outputs.push(
34309
+ uiChunk({
34310
+ type: `${content.type}-delta`,
34311
+ id,
34312
+ delta: content.text
34313
+ })
34314
+ );
34315
+ return outputs;
34316
+ }
34317
+ function toolInputStartFromEvent(message) {
34318
+ const event = message.event;
34319
+ if (event.type !== "content_block_start") {
34320
+ return null;
34321
+ }
34322
+ const contentBlock = event.content_block;
34323
+ if (contentBlock.type !== "tool_use" && contentBlock.type !== "mcp_tool_use" && contentBlock.type !== "server_tool_use") {
34324
+ return null;
34325
+ }
34326
+ const id = contentBlock.id;
34327
+ const name = contentBlock.name;
34328
+ if (typeof id !== "string" || typeof name !== "string") {
34329
+ return null;
34330
+ }
34331
+ if (name === ASK_USER_QUESTION_TOOL_NAME) {
33783
34332
  return null;
33784
34333
  }
33785
- const index = event.index;
33786
- const type = delta.type === "thinking_delta" ? "reasoning" : "text";
33787
34334
  return {
33788
- messageId,
33789
- partId: typeof index === "number" ? String(index) : "0",
33790
- role: "assistant",
33791
- kind: "message",
33792
- delta: {
33793
- type,
33794
- text
34335
+ index: blockIndexId(event.index),
34336
+ toolCallId: id,
34337
+ toolName: toolNameFromContentBlock(contentBlock, name),
34338
+ input: contentBlock.input
34339
+ };
34340
+ }
34341
+ function toolInputDeltaFromDelta(delta) {
34342
+ if (delta.type !== "input_json_delta") {
34343
+ return null;
34344
+ }
34345
+ return delta.partial_json;
34346
+ }
34347
+ function completeToolInput(input) {
34348
+ const inputText = input.inputText.trim();
34349
+ if (inputText.length > 0) {
34350
+ try {
34351
+ return JSON.parse(inputText);
34352
+ } catch {
34353
+ return input.inputText;
33795
34354
  }
34355
+ }
34356
+ return input.initialInput ?? {};
34357
+ }
34358
+ function sourceChunkFromDelta(delta, index) {
34359
+ if (delta.type !== "citations_delta") {
34360
+ return null;
34361
+ }
34362
+ const citation = delta.citation;
34363
+ if (citation.type === "web_search_result_location" && typeof citation.url === "string") {
34364
+ return {
34365
+ type: "source-url",
34366
+ sourceId: citationSourceId(citation, index),
34367
+ url: citation.url,
34368
+ ...typeof citation.title === "string" ? { title: citation.title } : {},
34369
+ providerMetadata: anthropicCitationMetadata(citation)
34370
+ };
34371
+ }
34372
+ return {
34373
+ type: "source-document",
34374
+ sourceId: citationSourceId(citation, index),
34375
+ mediaType: "text/plain",
34376
+ title: citationTitle(citation, index),
34377
+ providerMetadata: anthropicCitationMetadata(citation)
34378
+ };
34379
+ }
34380
+ function toolNameFromContentBlock(contentBlock, name) {
34381
+ return contentBlock.type === "mcp_tool_use" && typeof contentBlock.server_name === "string" ? `${contentBlock.server_name}.${name}` : name;
34382
+ }
34383
+ function citationSourceId(citation, index) {
34384
+ for (const key of ["encrypted_index", "file_id", "source"]) {
34385
+ const value = citation[key];
34386
+ if (typeof value === "string" && value.trim().length > 0) {
34387
+ return value;
34388
+ }
34389
+ }
34390
+ if (typeof citation.document_index === "number") {
34391
+ return `document:${citation.document_index.toString()}`;
34392
+ }
34393
+ return `citation:${index.toString()}`;
34394
+ }
34395
+ function citationTitle(citation, index) {
34396
+ for (const key of ["title", "document_title", "source", "file_id"]) {
34397
+ const value = citation[key];
34398
+ if (typeof value === "string" && value.trim().length > 0) {
34399
+ return value;
34400
+ }
34401
+ }
34402
+ return `Claude citation ${index.toString()}`;
34403
+ }
34404
+ function anthropicCitationMetadata(citation) {
34405
+ return { anthropic: { citation: toJsonValue(citation) } };
34406
+ }
34407
+ function blockIndexId(index) {
34408
+ return index.toString();
34409
+ }
34410
+ function textContentFromDelta(delta) {
34411
+ switch (delta.type) {
34412
+ case "text_delta":
34413
+ return { type: "text", text: delta.text };
34414
+ case "thinking_delta":
34415
+ return { type: "reasoning", text: delta.thinking };
34416
+ default:
34417
+ return null;
34418
+ }
34419
+ }
34420
+ function conversationProjectionToUiChunks(projection) {
34421
+ if (projection.role === "assistant" && projection.kind === "message") {
34422
+ return messageContentChunks(projection);
34423
+ }
34424
+ if (projection.kind === "tool_call") {
34425
+ return toolCallChunks(projection);
34426
+ }
34427
+ if (projection.kind === "tool_result") {
34428
+ return projection.content.parts.flatMap((part) => {
34429
+ if (part.type !== "tool_result") {
34430
+ return [];
34431
+ }
34432
+ return [
34433
+ uiChunk(
34434
+ part.isError ? {
34435
+ type: "tool-output-error",
34436
+ toolCallId: part.toolUseId ?? UNKNOWN_MESSAGE_ID,
34437
+ errorText: JSON.stringify(part.output)
34438
+ } : {
34439
+ type: "tool-output-available",
34440
+ toolCallId: part.toolUseId ?? UNKNOWN_MESSAGE_ID,
34441
+ output: part.output
34442
+ }
34443
+ )
34444
+ ];
34445
+ });
34446
+ }
34447
+ if (projection.kind === "question") {
34448
+ return projection.content.parts.flatMap(
34449
+ (part) => part.type === "question" ? [
34450
+ {
34451
+ type: "ui_message_chunk",
34452
+ chunk: {
34453
+ type: "data-auto-question",
34454
+ data: {
34455
+ toolCallId: part.toolCallId,
34456
+ questions: part.questions
34457
+ }
34458
+ },
34459
+ turnStatus: "waiting_for_input"
34460
+ }
34461
+ ] : []
34462
+ );
34463
+ }
34464
+ return [
34465
+ {
34466
+ type: "entry",
34467
+ entry: projection
34468
+ }
34469
+ ];
34470
+ }
34471
+ function projectAssistantSnapshot(projections) {
34472
+ return [
34473
+ ...assistantSnapshotMessageChunks(projections),
34474
+ ...projections.filter((projection) => !isAssistantMessageOrToolCall(projection)).flatMap((projection) => conversationProjectionToUiChunks(projection))
34475
+ ];
34476
+ }
34477
+ function assistantSnapshotMessageChunks(projections) {
34478
+ const messageProjection = projections.find(
34479
+ (projection) => projection.role === "assistant" && projection.kind === "message"
34480
+ );
34481
+ const toolCallProjections = projections.filter(
34482
+ (projection) => projection.role === "assistant" && projection.kind === "tool_call"
34483
+ );
34484
+ if (!messageProjection && toolCallProjections.length === 0) {
34485
+ return [];
34486
+ }
34487
+ const messageId = messageProjection?.messageId ?? toolCallProjections.find((projection) => projection.messageId)?.messageId ?? UNKNOWN_MESSAGE_ID;
34488
+ const outputs = [
34489
+ uiChunk({ type: "start", messageId })
34490
+ ];
34491
+ if (messageProjection) {
34492
+ outputs.push(...messagePartChunks(messageProjection));
34493
+ }
34494
+ outputs.push(
34495
+ ...toolCallProjections.flatMap((projection) => toolCallChunks(projection))
34496
+ );
34497
+ if (toolCallProjections.length === 0) {
34498
+ outputs.push(uiChunk({ type: "finish", finishReason: "stop" }));
34499
+ }
34500
+ return outputs;
34501
+ }
34502
+ function isAssistantMessageOrToolCall(projection) {
34503
+ return projection.role === "assistant" && (projection.kind === "message" || projection.kind === "tool_call");
34504
+ }
34505
+ function messageContentChunks(projection) {
34506
+ const messageId = projection.messageId ?? UNKNOWN_MESSAGE_ID;
34507
+ const outputs = [
34508
+ uiChunk({ type: "start", messageId })
34509
+ ];
34510
+ outputs.push(...messagePartChunks(projection));
34511
+ outputs.push(uiChunk({ type: "finish", finishReason: "stop" }));
34512
+ return outputs;
34513
+ }
34514
+ function messagePartChunks(projection) {
34515
+ const outputs = [];
34516
+ for (const [index, part] of projection.content.parts.entries()) {
34517
+ if (part.type !== "text" && part.type !== "reasoning") {
34518
+ continue;
34519
+ }
34520
+ const id = `${part.type}-${index.toString()}`;
34521
+ outputs.push(
34522
+ uiChunk({ type: `${part.type}-start`, id }),
34523
+ uiChunk({
34524
+ type: `${part.type}-delta`,
34525
+ id,
34526
+ delta: part.text
34527
+ }),
34528
+ uiChunk({ type: `${part.type}-end`, id })
34529
+ );
34530
+ }
34531
+ return outputs;
34532
+ }
34533
+ function toolCallChunks(projection) {
34534
+ return projection.content.parts.flatMap(
34535
+ (part) => part.type === "tool_call" ? [
34536
+ uiChunk({
34537
+ type: "tool-input-available",
34538
+ toolCallId: part.toolCallId ?? UNKNOWN_MESSAGE_ID,
34539
+ toolName: part.name,
34540
+ input: part.input
34541
+ })
34542
+ ] : []
34543
+ );
34544
+ }
34545
+ function uiChunk(chunk) {
34546
+ return {
34547
+ type: "ui_message_chunk",
34548
+ chunk
33796
34549
  };
33797
34550
  }
33798
34551
 
@@ -35043,6 +35796,9 @@ var ClaudeCodeCommandHandler = class {
35043
35796
  if (!activeContext) {
35044
35797
  return;
35045
35798
  }
35799
+ for (const projection of this.projector.flushPendingAssistantMessages()) {
35800
+ await this.emitBridgeOutput(activeContext, projection);
35801
+ }
35046
35802
  await this.emitBridgeOutput(
35047
35803
  activeContext,
35048
35804
  projectClaudeCodeResult({
@@ -35156,30 +35912,27 @@ init_src();
35156
35912
  // src/commands/agent-bridge/harness/codex/projector.ts
35157
35913
  init_src();
35158
35914
  var CodexProjector = class {
35915
+ activeAgentMessages = /* @__PURE__ */ new Set();
35916
+ activeToolCalls = /* @__PURE__ */ new Set();
35917
+ activeResponseMessageId = null;
35159
35918
  project(notification) {
35160
35919
  switch (notification.type) {
35161
35920
  case "agentMessageDelta":
35162
35921
  return [
35163
- {
35164
- type: "delta",
35165
- delta: {
35166
- messageId: notification.itemId,
35167
- partId: "0",
35168
- role: "assistant",
35169
- kind: "message",
35170
- delta: { type: "text", text: notification.delta }
35171
- }
35172
- }
35922
+ ...this.ensureResponseStarted(notification.itemId),
35923
+ ...this.projectAgentMessageDelta(
35924
+ notification.itemId,
35925
+ notification.delta
35926
+ )
35173
35927
  ];
35174
35928
  case "itemStarted":
35175
- return entryProjections(
35176
- projectCodexItem({ item: notification.item, phase: "started" })
35177
- );
35929
+ return this.projectItem(notification.item, "started");
35178
35930
  case "itemCompleted":
35179
- return entryProjections(
35180
- projectCodexItem({ item: notification.item, phase: "completed" })
35181
- );
35931
+ return this.projectItem(notification.item, "completed");
35182
35932
  case "turnCompleted":
35933
+ this.activeResponseMessageId = null;
35934
+ this.activeAgentMessages.clear();
35935
+ this.activeToolCalls.clear();
35183
35936
  return [turnCompletionEntry(notification)];
35184
35937
  case "error":
35185
35938
  return [errorEntry(notification)];
@@ -35187,81 +35940,267 @@ var CodexProjector = class {
35187
35940
  return [];
35188
35941
  }
35189
35942
  }
35190
- // An approval parks the turn on operator input, so the question entry also
35943
+ // An approval parks the turn on operator input, so the question chunk also
35191
35944
  // marks the delivered turn as waiting for input.
35192
35945
  projectApproval(request) {
35193
- return {
35194
- type: "entry",
35195
- entry: {
35196
- ...projectCodexApproval(request),
35946
+ return [
35947
+ uiChunk2({
35948
+ type: "tool-approval-request",
35949
+ approvalId: String(request.requestId),
35950
+ toolCallId: request.itemId
35951
+ }),
35952
+ {
35953
+ type: "ui_message_chunk",
35954
+ chunk: {
35955
+ type: "data-auto-question",
35956
+ data: {
35957
+ toolCallId: request.itemId,
35958
+ questions: [approvalQuestion(request)]
35959
+ }
35960
+ },
35197
35961
  turnStatus: "waiting_for_input"
35198
35962
  }
35199
- };
35963
+ ];
35200
35964
  }
35201
35965
  // Surfaces a session-level failure (e.g. the app-server process dying) as a
35202
35966
  // durable failed status entry rather than only a diagnostic log line.
35203
35967
  projectSessionFailure(message) {
35204
35968
  return {
35205
- type: "entry",
35206
- entry: {
35207
- role: "system",
35208
- kind: "status",
35209
- status: "failed",
35210
- turnStatus: "failed",
35211
- content: {
35212
- parts: [{ type: "text", text: `codex failed: ${message}` }]
35213
- }
35214
- }
35969
+ type: "ui_message_chunk",
35970
+ chunk: { type: "error", errorText: `codex failed: ${message}` }
35215
35971
  };
35216
35972
  }
35973
+ projectAgentMessageDelta(itemId, delta) {
35974
+ const outputs = [];
35975
+ if (!this.activeAgentMessages.has(itemId)) {
35976
+ this.activeAgentMessages.add(itemId);
35977
+ outputs.push(uiChunk2({ type: "text-start", id: textPartId(itemId) }));
35978
+ }
35979
+ outputs.push(
35980
+ uiChunk2({ type: "text-delta", id: textPartId(itemId), delta })
35981
+ );
35982
+ return outputs;
35983
+ }
35984
+ projectItem(item, phase) {
35985
+ switch (item.type) {
35986
+ case "agentMessage":
35987
+ return [
35988
+ ...this.ensureResponseStarted(item.id),
35989
+ ...this.projectAgentMessageItem(item, phase)
35990
+ ];
35991
+ case "reasoning":
35992
+ return phase === "completed" ? [
35993
+ ...this.ensureResponseStarted(item.id),
35994
+ ...reasoningItemChunks(item)
35995
+ ] : [];
35996
+ case "commandExecution":
35997
+ return this.projectToolItem({
35998
+ phase,
35999
+ itemId: item.id,
36000
+ name: "shell",
36001
+ input: {
36002
+ command: item.command,
36003
+ ...item.cwd ? { cwd: item.cwd } : {}
36004
+ },
36005
+ output: item.aggregatedOutput ?? "",
36006
+ isError: isFailedStatus(item.status) || (item.exitCode ?? 0) !== 0,
36007
+ title: item.command,
36008
+ toolMetadata: statusMetadata(item.status, {
36009
+ ...item.exitCode !== void 0 ? { exitCode: item.exitCode } : {}
36010
+ })
36011
+ });
36012
+ case "fileChange":
36013
+ return this.projectToolItem({
36014
+ phase,
36015
+ itemId: item.id,
36016
+ name: "apply_patch",
36017
+ input: { changes: toJsonValue(item.changes) },
36018
+ output: { status: item.status ?? "unknown" },
36019
+ isError: isFailedStatus(item.status),
36020
+ title: "apply_patch",
36021
+ toolMetadata: statusMetadata(item.status)
36022
+ });
36023
+ case "mcpToolCall":
36024
+ return this.projectToolItem({
36025
+ phase,
36026
+ itemId: item.id,
36027
+ name: `${item.server}.${item.tool}`,
36028
+ input: toJsonValue(item.arguments),
36029
+ output: item.error ? { error: item.error.message } : toJsonValue(item.result),
36030
+ isError: isFailedStatus(item.status) || item.error != null,
36031
+ title: `${item.server}.${item.tool}`,
36032
+ toolMetadata: statusMetadata(item.status, {
36033
+ server: item.server,
36034
+ tool: item.tool
36035
+ })
36036
+ });
36037
+ default:
36038
+ return [];
36039
+ }
36040
+ }
36041
+ projectToolItem(input) {
36042
+ const outputs = [];
36043
+ outputs.push(...this.ensureResponseStarted(input.itemId));
36044
+ if (input.phase === "started" || !this.activeToolCalls.has(input.itemId)) {
36045
+ outputs.push(toolInputChunk(input));
36046
+ this.activeToolCalls.add(input.itemId);
36047
+ }
36048
+ if (input.phase === "started") {
36049
+ return outputs;
36050
+ }
36051
+ outputs.push(toolOutputChunk(input));
36052
+ outputs.push(uiChunk2({ type: "finish-step" }));
36053
+ this.activeToolCalls.delete(input.itemId);
36054
+ return outputs;
36055
+ }
36056
+ projectAgentMessageItem(item, phase) {
36057
+ if (phase === "started") {
36058
+ if (this.activeAgentMessages.has(item.id)) {
36059
+ return [];
36060
+ }
36061
+ this.activeAgentMessages.add(item.id);
36062
+ return [uiChunk2({ type: "text-start", id: textPartId(item.id) })];
36063
+ }
36064
+ const outputs = [];
36065
+ if (!this.activeAgentMessages.has(item.id)) {
36066
+ outputs.push(uiChunk2({ type: "text-start", id: textPartId(item.id) }));
36067
+ if (item.text.length > 0) {
36068
+ outputs.push(
36069
+ uiChunk2({
36070
+ type: "text-delta",
36071
+ id: textPartId(item.id),
36072
+ delta: item.text
36073
+ })
36074
+ );
36075
+ }
36076
+ }
36077
+ outputs.push(
36078
+ uiChunk2({ type: "text-end", id: textPartId(item.id) }),
36079
+ uiChunk2({ type: "finish-step" })
36080
+ );
36081
+ this.activeAgentMessages.delete(item.id);
36082
+ return outputs;
36083
+ }
36084
+ ensureResponseStarted(messageId) {
36085
+ if (this.activeResponseMessageId !== null) {
36086
+ return [];
36087
+ }
36088
+ this.activeResponseMessageId = messageId;
36089
+ return [uiChunk2({ type: "start", messageId })];
36090
+ }
35217
36091
  };
35218
- function entryProjections(projections) {
35219
- return projections.map((projection) => ({
35220
- type: "entry",
35221
- entry: projection
35222
- }));
36092
+ function reasoningItemChunks(item) {
36093
+ const text = item.summary.map((line) => line.trim()).filter(Boolean).join("\n\n");
36094
+ if (text.length === 0) {
36095
+ return [];
36096
+ }
36097
+ const id = reasoningPartId(item.id);
36098
+ return [
36099
+ uiChunk2({ type: "reasoning-start", id }),
36100
+ uiChunk2({ type: "reasoning-delta", id, delta: text }),
36101
+ uiChunk2({ type: "reasoning-end", id }),
36102
+ uiChunk2({ type: "finish-step" })
36103
+ ];
36104
+ }
36105
+ function toolInputChunk(input) {
36106
+ return uiChunk2({
36107
+ type: "tool-input-available",
36108
+ toolCallId: input.itemId,
36109
+ toolName: input.name,
36110
+ input: input.input,
36111
+ ...input.title ? { title: input.title } : {},
36112
+ ...input.toolMetadata ? { toolMetadata: input.toolMetadata } : {}
36113
+ });
36114
+ }
36115
+ function toolOutputChunk(input) {
36116
+ return uiChunk2(
36117
+ input.isError ? {
36118
+ type: "tool-output-error",
36119
+ toolCallId: input.itemId,
36120
+ errorText: JSON.stringify(input.output),
36121
+ ...input.toolMetadata ? { toolMetadata: input.toolMetadata } : {}
36122
+ } : {
36123
+ type: "tool-output-available",
36124
+ toolCallId: input.itemId,
36125
+ output: input.output,
36126
+ ...input.toolMetadata ? { toolMetadata: input.toolMetadata } : {}
36127
+ }
36128
+ );
35223
36129
  }
35224
36130
  function turnCompletionEntry(notification) {
35225
36131
  if (notification.status === "failed") {
35226
36132
  const detail = notification.errorMessage ?? "unknown error";
35227
- return statusEntry({
36133
+ return statusFinish({
35228
36134
  text: `codex turn failed: ${detail}`,
35229
- status: "failed",
35230
36135
  turnStatus: "failed"
35231
36136
  });
35232
36137
  }
35233
- const text = notification.status === "interrupted" ? "codex turn interrupted" : "codex turn completed";
35234
- return statusEntry({
35235
- text,
35236
- status: "completed",
36138
+ return statusFinish({
36139
+ text: notification.status === "interrupted" ? "codex turn interrupted" : "codex turn completed",
35237
36140
  turnStatus: "completed"
35238
36141
  });
35239
36142
  }
35240
36143
  function errorEntry(notification) {
35241
36144
  if (notification.willRetry) {
35242
- return statusEntry({
35243
- text: `codex retrying after error: ${notification.message}`,
35244
- status: "completed"
36145
+ return statusFinish({
36146
+ text: `codex retrying after error: ${notification.message}`
35245
36147
  });
35246
36148
  }
35247
- return statusEntry({
36149
+ return statusFinish({
35248
36150
  text: `codex error: ${notification.message}`,
35249
- status: "failed",
35250
36151
  turnStatus: "failed"
35251
36152
  });
35252
36153
  }
35253
- function statusEntry(input) {
36154
+ function statusFinish(input) {
35254
36155
  return {
35255
- type: "entry",
35256
- entry: {
35257
- role: "system",
35258
- kind: "status",
35259
- status: input.status,
35260
- ...input.turnStatus ? { turnStatus: input.turnStatus } : {},
35261
- content: { parts: [{ type: "text", text: input.text }] }
35262
- }
36156
+ type: "ui_message_chunk",
36157
+ chunk: {
36158
+ type: "finish",
36159
+ finishReason: input.turnStatus === "failed" ? "error" : "stop"
36160
+ },
36161
+ statusText: input.text,
36162
+ ...input.turnStatus ? { turnStatus: input.turnStatus } : {}
35263
36163
  };
35264
36164
  }
36165
+ function approvalQuestion(request) {
36166
+ const subject = request.type === "commandExecution" ? `run the command: ${request.command ?? "(unknown command)"}` : "apply file changes";
36167
+ return {
36168
+ question: request.reason ? `Codex requests approval to ${subject}. ${request.reason}` : `Codex requests approval to ${subject}.`,
36169
+ header: "Approval",
36170
+ options: [
36171
+ { label: APPROVE_OPTION_LABEL, description: "Allow this action." },
36172
+ { label: DECLINE_OPTION_LABEL, description: "Reject this action." }
36173
+ ],
36174
+ multiSelect: false
36175
+ };
36176
+ }
36177
+ function textPartId(itemId) {
36178
+ return `${itemId}:text`;
36179
+ }
36180
+ function reasoningPartId(itemId) {
36181
+ return `${itemId}:reasoning`;
36182
+ }
36183
+ function isFailedStatus(status) {
36184
+ return status === "failed" || status === "declined";
36185
+ }
36186
+ function statusMetadata(status, fields = {}) {
36187
+ return {
36188
+ status: status ?? "unknown",
36189
+ ...fields
36190
+ };
36191
+ }
36192
+ function uiChunk2(chunk) {
36193
+ return {
36194
+ type: "ui_message_chunk",
36195
+ chunk: normalizeChunk(chunk)
36196
+ };
36197
+ }
36198
+ function normalizeChunk(chunk) {
36199
+ if ("toolCallId" in chunk && (!chunk.toolCallId || chunk.toolCallId.trim().length === 0)) {
36200
+ return { ...chunk, toolCallId: UNKNOWN_MESSAGE_ID };
36201
+ }
36202
+ return chunk;
36203
+ }
35265
36204
 
35266
36205
  // src/commands/agent-bridge/harness/codex/resume-store.ts
35267
36206
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
@@ -36032,7 +36971,9 @@ var CodexCommandHandler = class {
36032
36971
  this.input.writeOutput?.(
36033
36972
  `agent_bridge_codex_question_pending tool_use_id=${request.itemId}`
36034
36973
  );
36035
- await this.emit(activeContext, this.projector.projectApproval(request));
36974
+ for (const projection of this.projector.projectApproval(request)) {
36975
+ await this.emit(activeContext, projection);
36976
+ }
36036
36977
  }
36037
36978
  async handleSessionError(error51) {
36038
36979
  this.input.writeOutput?.(
@@ -40040,6 +40981,8 @@ function entryPreview(event, full) {
40040
40981
  return stringify5(part.output);
40041
40982
  case "question":
40042
40983
  return part.questions.map((question) => question.question).join(" ");
40984
+ case "ui_message":
40985
+ return stringify5(part.message);
40043
40986
  default:
40044
40987
  return "";
40045
40988
  }