tesseract-agent 0.7.0 → 0.7.1

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 (2) hide show
  1. package/dist-bundle/cli.js +690 -101
  2. package/package.json +1 -1
@@ -4475,14 +4475,14 @@ function promiseAllObject(promisesObj) {
4475
4475
  }
4476
4476
  function randomString(length = 10) {
4477
4477
  const chars = "abcdefghijklmnopqrstuvwxyz";
4478
- let str = "";
4478
+ let str2 = "";
4479
4479
  for (let i = 0; i < length; i++) {
4480
- str += chars[Math.floor(Math.random() * chars.length)];
4480
+ str2 += chars[Math.floor(Math.random() * chars.length)];
4481
4481
  }
4482
- return str;
4482
+ return str2;
4483
4483
  }
4484
- function esc(str) {
4485
- return JSON.stringify(str);
4484
+ function esc(str2) {
4485
+ return JSON.stringify(str2);
4486
4486
  }
4487
4487
  function slugify(input) {
4488
4488
  return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -4526,8 +4526,8 @@ function numKeys(data) {
4526
4526
  }
4527
4527
  return keyCount;
4528
4528
  }
4529
- function escapeRegex(str) {
4530
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4529
+ function escapeRegex(str2) {
4530
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4531
4531
  }
4532
4532
  function clone(inst, def, params) {
4533
4533
  const cl = new inst._zod.constr(def ?? inst._zod.def);
@@ -19467,6 +19467,12 @@ var init_ws_protocol = __esm({
19467
19467
  // co-viewers see the Mission card's live stage progress
19468
19468
  "agent:elicitation_complete",
19469
19469
  // M5 — co-viewers clear a resolved url-mode card
19470
+ "agent:framework_event",
19471
+ // full-visibility debug feed — co-viewers see the same framework events
19472
+ "agent:sources",
19473
+ // citation footnotes — co-viewers see the same sources under the reply
19474
+ "agent:task",
19475
+ // live subagent cards — co-viewers watch subagent progress too
19470
19476
  "chat:echoed",
19471
19477
  "chat:scheduled_fired"
19472
19478
  // cron — co-viewers see the synthetic "Scheduled" user turn
@@ -22871,6 +22877,72 @@ var init_kill_tree = __esm({
22871
22877
  }
22872
22878
  });
22873
22879
 
22880
+ // ../../packages/agent-core/dist/sources.js
22881
+ function str(v) {
22882
+ return typeof v === "string" ? v : "";
22883
+ }
22884
+ function extractSources(content) {
22885
+ if (!Array.isArray(content))
22886
+ return [];
22887
+ const byKey = /* @__PURE__ */ new Map();
22888
+ for (const block of content) {
22889
+ if (!block || typeof block !== "object")
22890
+ continue;
22891
+ const b = block;
22892
+ if (b.type !== "text" || !Array.isArray(b.citations))
22893
+ continue;
22894
+ for (const raw of b.citations) {
22895
+ if (!raw || typeof raw !== "object")
22896
+ continue;
22897
+ const c = raw;
22898
+ let url2;
22899
+ let title;
22900
+ let kind;
22901
+ switch (c.type) {
22902
+ case "web_search_result_location":
22903
+ url2 = str(c.url) || void 0;
22904
+ title = str(c.title) || url2 || "Web result";
22905
+ kind = "web";
22906
+ break;
22907
+ case "search_result_location": {
22908
+ const source = str(c.source);
22909
+ url2 = /^https?:\/\//.test(source) ? source : void 0;
22910
+ title = str(c.title) || source || "Search result";
22911
+ kind = "search";
22912
+ break;
22913
+ }
22914
+ case "char_location":
22915
+ case "page_location":
22916
+ case "content_block_location":
22917
+ title = str(c.document_title) || "Document";
22918
+ kind = "document";
22919
+ break;
22920
+ default:
22921
+ continue;
22922
+ }
22923
+ const key2 = (url2 || title).toLowerCase();
22924
+ const snippet = str(c.cited_text).replace(/\s+/g, " ").trim().slice(0, SNIPPET_MAX);
22925
+ const existing = byKey.get(key2);
22926
+ if (existing) {
22927
+ existing.count += 1;
22928
+ if (!existing.snippet && snippet)
22929
+ existing.snippet = snippet;
22930
+ } else if (byKey.size < SOURCES_MAX) {
22931
+ byKey.set(key2, { ...url2 ? { url: url2 } : {}, title, ...snippet ? { snippet } : {}, kind, count: 1 });
22932
+ }
22933
+ }
22934
+ }
22935
+ return [...byKey.values()];
22936
+ }
22937
+ var SNIPPET_MAX, SOURCES_MAX;
22938
+ var init_sources = __esm({
22939
+ "../../packages/agent-core/dist/sources.js"() {
22940
+ "use strict";
22941
+ SNIPPET_MAX = 220;
22942
+ SOURCES_MAX = 40;
22943
+ }
22944
+ });
22945
+
22874
22946
  // ../../packages/agent-core/dist/agent-manager.js
22875
22947
  import { query, createSdkMcpServer, startup } from "@anthropic-ai/claude-agent-sdk";
22876
22948
  import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile6 } from "node:fs/promises";
@@ -22990,8 +23062,176 @@ function extractHumanReadable(message) {
22990
23062
  }
22991
23063
  return parts.join("\n");
22992
23064
  }
22993
- function systemHeader(type, subtype) {
22994
- return `_${subtype ? `${type}/${subtype}` : type}_`;
23065
+ function traceFrame(message) {
23066
+ const type = message.type || "unknown";
23067
+ if (type === "stream_event") {
23068
+ const ev = message.event ?? {};
23069
+ const et = ev.type ?? "?";
23070
+ if (et === "content_block_delta" || et === "ping")
23071
+ return;
23072
+ const cb = ev.content_block ? ` ${ev.content_block.type ?? ""}${ev.content_block.name ? `:${ev.content_block.name}` : ""}` : "";
23073
+ log(`frame stream_event/${et}${cb}`);
23074
+ return;
23075
+ }
23076
+ const sub = typeof message.subtype === "string" ? `/${message.subtype}` : "";
23077
+ const parent = message.parent_tool_use_id ? " (subagent)" : "";
23078
+ log(`frame ${type}${sub}${parent}`);
23079
+ }
23080
+ function rawFramePreview(message, limit = 2e3) {
23081
+ let s;
23082
+ try {
23083
+ s = JSON.stringify(message, null, 2);
23084
+ } catch {
23085
+ s = String(message);
23086
+ }
23087
+ return s.length > limit ? `${s.slice(0, limit)}
23088
+ \u2026 (${s.length} bytes total)` : s;
23089
+ }
23090
+ function humanizeName(name) {
23091
+ const s = name.replace(/[_-]+/g, " ").trim();
23092
+ return s.charAt(0).toUpperCase() + s.slice(1);
23093
+ }
23094
+ function salientFields(m, max = 8) {
23095
+ const SKIP = /* @__PURE__ */ new Set(["type", "subtype", "uuid", "session_id", "parent_tool_use_id", "request_id"]);
23096
+ const out = [];
23097
+ for (const [k, v] of Object.entries(m)) {
23098
+ if (SKIP.has(k))
23099
+ continue;
23100
+ if (v === null || v === void 0)
23101
+ continue;
23102
+ let val;
23103
+ if (typeof v === "string")
23104
+ val = v.length > 200 ? `${v.slice(0, 200)}\u2026` : v;
23105
+ else if (typeof v === "number" || typeof v === "boolean")
23106
+ val = String(v);
23107
+ else if (Array.isArray(v))
23108
+ val = `[${v.length}]`;
23109
+ else
23110
+ continue;
23111
+ out.push({ label: humanizeName(k), value: val });
23112
+ if (out.length >= max)
23113
+ break;
23114
+ }
23115
+ return out;
23116
+ }
23117
+ function describeSystemSubtype(subtype, m) {
23118
+ if (subtype === "thinking_tokens") {
23119
+ const est = Number(m.estimated_tokens ?? 0);
23120
+ const delta = Number(m.estimated_tokens_delta ?? 0);
23121
+ return {
23122
+ category: "thinking",
23123
+ name: subtype,
23124
+ title: `Thinking \u2014 ~${est.toLocaleString("en-US")} tokens`,
23125
+ ...delta ? { fields: [{ label: "this step", value: `+${delta.toLocaleString("en-US")}` }] } : {},
23126
+ updateKey: "thinking_tokens"
23127
+ // one self-updating card per turn, not one per tick
23128
+ };
23129
+ }
23130
+ const category = SYSTEM_EVENT_CATEGORY[subtype];
23131
+ if (category) {
23132
+ const updateKey = category === "hook" ? `hook:${String(m.hook_name ?? m.hook_event_name ?? subtype)}` : category === "task" ? `task:${String(m.task_id ?? subtype)}` : category === "telemetry" ? `sys:${subtype}` : void 0;
23133
+ return { category, name: subtype, title: humanizeName(subtype), fields: salientFields(m), ...updateKey ? { updateKey } : {} };
23134
+ }
23135
+ const text3 = extractHumanReadable(m);
23136
+ return { category: "unknown", name: subtype, title: `system/${subtype}`, ...text3 ? { detail: text3 } : {}, raw: rawFramePreview(m) };
23137
+ }
23138
+ function sstr(v) {
23139
+ return typeof v === "string" ? v : "";
23140
+ }
23141
+ function prettifyModel(id) {
23142
+ if (!id)
23143
+ return "the model";
23144
+ const known = {
23145
+ "claude-opus-4-8": "Opus 4.8",
23146
+ "claude-sonnet-4-6": "Sonnet 4.6",
23147
+ "claude-haiku-4-5-20251001": "Haiku 4.5",
23148
+ "claude-fable-5": "Fable 5",
23149
+ "claude-sonnet-5": "Sonnet 5"
23150
+ };
23151
+ return known[id] ?? id.replace(/^claude-/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
23152
+ }
23153
+ function buildNotice(subtype, m) {
23154
+ switch (subtype) {
23155
+ case "notification": {
23156
+ const text3 = sstr(m.text) || "Notification";
23157
+ const priority = ["low", "medium", "high", "immediate"].includes(sstr(m.priority)) ? m.priority : "medium";
23158
+ const level = priority === "immediate" ? "error" : priority === "high" ? "warning" : priority === "low" ? "info" : "notice";
23159
+ return { category: "notification", level, priority, title: text3, toast: true, inline: true, ...typeof m.timeout_ms === "number" ? { timeoutMs: m.timeout_ms } : {} };
23160
+ }
23161
+ case "informational": {
23162
+ const content = sstr(m.content).trim();
23163
+ if (!content)
23164
+ return null;
23165
+ const level = ["info", "notice", "suggestion", "warning"].includes(sstr(m.level)) ? m.level : "info";
23166
+ const nl = content.indexOf("\n");
23167
+ const firstLine = nl >= 0 ? content.slice(0, nl) : content;
23168
+ const remainder = nl >= 0 ? content.slice(nl + 1).trim() : "";
23169
+ const truncated = firstLine.length > 160;
23170
+ const title = truncated ? `${firstLine.slice(0, 159).trimEnd()}\u2026` : firstLine;
23171
+ const body = truncated ? content : remainder;
23172
+ return { category: "info", level, title, ...body ? { body } : {}, ...sstr(m.tool_use_id) ? { toolUseId: sstr(m.tool_use_id) } : {}, inline: true, toast: level === "warning" };
23173
+ }
23174
+ case "plugin_install": {
23175
+ const status2 = sstr(m.status);
23176
+ if (status2 !== "installed" && status2 !== "failed" && status2 !== "completed")
23177
+ return null;
23178
+ const name = sstr(m.name) || "plugin";
23179
+ const failed = status2 === "failed";
23180
+ return { category: "plugin", level: failed ? "error" : "success", title: failed ? `Plugin \u201C${name}\u201D failed to install` : `Installed plugin \u201C${name}\u201D`, ...failed && sstr(m.error) ? { body: sstr(m.error) } : {}, toast: true, inline: true };
23181
+ }
23182
+ case "memory_recall": {
23183
+ const memories = Array.isArray(m.memories) ? m.memories : [];
23184
+ if (!memories.length)
23185
+ return null;
23186
+ const names = memories.map((x) => sstr(x.path).split(/[/\\]/).pop() || sstr(x.path)).filter(Boolean);
23187
+ return { category: "memory", level: "info", title: `Recalled ${memories.length} ${memories.length === 1 ? "memory" : "memories"}`, ...names.length ? { body: names.join(" \xB7 ") } : {}, toast: false, inline: true };
23188
+ }
23189
+ case "permission_denied": {
23190
+ const tool9 = sstr(m.tool_name) || "a tool";
23191
+ const reason = sstr(m.decision_reason) || sstr(m.message);
23192
+ return { category: "permission", level: "warning", title: `Blocked: ${tool9}`, ...reason ? { body: reason } : {}, ...sstr(m.tool_use_id) ? { toolUseId: sstr(m.tool_use_id) } : {}, toast: true, inline: true };
23193
+ }
23194
+ case "model_refusal_fallback": {
23195
+ const orig = sstr(m.original_model);
23196
+ const fb = sstr(m.fallback_model);
23197
+ const cat = sstr(m.api_refusal_category);
23198
+ const expl = sstr(m.api_refusal_explanation) || sstr(m.content);
23199
+ const oL = prettifyModel(orig);
23200
+ const fL = prettifyModel(fb);
23201
+ const reverted = sstr(m.direction) === "revert";
23202
+ return {
23203
+ category: "model",
23204
+ level: "warning",
23205
+ priority: "high",
23206
+ title: reverted ? `${oL} declined this request${cat ? ` (${cat})` : ""} \u2014 retried on ${fL}` : `${oL} declined this request${cat ? ` (${cat})` : ""} \u2014 now on ${fL}`,
23207
+ ...expl ? { body: expl } : {},
23208
+ ...!reverted && fb ? { setModel: fb } : {},
23209
+ toast: true,
23210
+ inline: true,
23211
+ actions: reverted ? [{ id: "pick", label: "Choose model\u2026", kind: "pick_model" }] : [
23212
+ ...orig ? [{ id: "back", label: `Switch back to ${oL}`, style: "primary", kind: "switch_model", data: { model: orig } }] : [],
23213
+ { id: "stay", label: `Stay on ${fL}`, kind: "dismiss" },
23214
+ { id: "pick", label: "Choose model\u2026", kind: "pick_model" }
23215
+ ]
23216
+ };
23217
+ }
23218
+ case "model_refusal_no_fallback": {
23219
+ const orig = sstr(m.original_model);
23220
+ const cat = sstr(m.api_refusal_category);
23221
+ const expl = sstr(m.api_refusal_explanation) || sstr(m.content);
23222
+ return {
23223
+ category: "model",
23224
+ level: "error",
23225
+ title: `${prettifyModel(orig)} declined this request${cat ? ` (${cat})` : ""}`,
23226
+ ...expl ? { body: expl } : {},
23227
+ toast: true,
23228
+ inline: true,
23229
+ actions: [{ id: "pick", label: "Try another model\u2026", style: "primary", kind: "pick_model" }]
23230
+ };
23231
+ }
23232
+ default:
23233
+ return null;
23234
+ }
22995
23235
  }
22996
23236
  function parseTodos(rawInput) {
22997
23237
  try {
@@ -23200,7 +23440,7 @@ MOBILE (${fw}):
23200
23440
  return "";
23201
23441
  }
23202
23442
  }
23203
- var cachedClaudeExe, cachedNodeExe, HUMAN_READABLE_FIELDS, TOOL_RESULT_CAP, MAX_RESULT_IMAGES, MAX_IMAGE_BASE64, PERMISSION_MODES, EDIT_PREVIEW_CAP, AsyncMessagePump, IDLE_REAP_MS, MODERATE_IDLE_MS, STEER_COALESCE_MS, STOP_HARD_KILL_AFTER_MS, AgentManager;
23443
+ var cachedClaudeExe, cachedNodeExe, HUMAN_READABLE_FIELDS, THINKING_THROTTLE_MS, SYSTEM_EVENT_CATEGORY, TOOL_RESULT_CAP, MAX_RESULT_IMAGES, MAX_IMAGE_BASE64, PERMISSION_MODES, EDIT_PREVIEW_CAP, AsyncMessagePump, IDLE_REAP_MS, MODERATE_IDLE_MS, STEER_COALESCE_MS, STOP_HARD_KILL_AFTER_MS, AgentManager;
23204
23444
  var init_agent_manager = __esm({
23205
23445
  "../../packages/agent-core/dist/agent-manager.js"() {
23206
23446
  "use strict";
@@ -23222,7 +23462,17 @@ var init_agent_manager = __esm({
23222
23462
  init_session_usage();
23223
23463
  init_kill_tree();
23224
23464
  init_log();
23465
+ init_sources();
23225
23466
  HUMAN_READABLE_FIELDS = ["message", "text", "content", "summary", "description", "output", "stdout", "error", "reason", "name"];
23467
+ THINKING_THROTTLE_MS = 2e3;
23468
+ SYSTEM_EVENT_CATEGORY = {
23469
+ hook_started: "hook",
23470
+ hook_progress: "hook",
23471
+ thinking_tokens: "thinking"
23472
+ // task_* / files_persisted / worker_shutting_down are handled explicitly now;
23473
+ // get_context_usage / get_session_cost / background_tasks are outbound control-requests
23474
+ // we never receive — both dropped from here rather than kept as dead entries.
23475
+ };
23226
23476
  TOOL_RESULT_CAP = 16e3;
23227
23477
  MAX_RESULT_IMAGES = 4;
23228
23478
  MAX_IMAGE_BASE64 = 15e5;
@@ -23315,6 +23565,9 @@ var init_agent_manager = __esm({
23315
23565
  /** M5 H6 — parked MCP elicitation prompts, resolved when the client replies via
23316
23566
  * chat:elicitation (→ respondToElicitation) or denied on abort. */
23317
23567
  pendingElicitations = /* @__PURE__ */ new Map();
23568
+ /** request_user_dialog prompts (model-fallback / usage-consent), resolved when the
23569
+ * client replies via chat:user_dialog (→ respondToUserDialog) or cancelled on abort. */
23570
+ pendingUserDialogs = /* @__PURE__ */ new Map();
23318
23571
  /** Skill-driven interactive UI requests (ask_form / present_choices), resolved when
23319
23572
  * the client replies via chat:ui_response (→ respondToUiRequest) or the owning
23320
23573
  * session is torn down (→ cancelled). Keyed by requestId; `sessionId` lets a
@@ -23622,6 +23875,159 @@ ${full.slice(-8e3)}`;
23622
23875
  entry.resolve({ behavior: "deny", message: decision.message || "Denied by user.", ...decision.interrupt ? { interrupt: true } : {} });
23623
23876
  }
23624
23877
  }
23878
+ /** AskUserQuestion: park a promise, raise the multiple-choice question card
23879
+ * (agent:permission_request, toolName='AskUserQuestion'), and resolve it from the
23880
+ * client's chat:permission reply — which lands in respondToPermission carrying
23881
+ * `updatedInput.answers`. Deliberately reuses pendingPermissions + the
23882
+ * PermissionResult shape so the existing answer round-trip (client card →
23883
+ * respondToPermission) is unchanged; the PreToolUse hook maps the resolved
23884
+ * PermissionResult into hookSpecificOutput. Driven from a PreToolUse hook rather
23885
+ * than canUseTool because hooks fire in EVERY permission mode — bypassPermissions
23886
+ * (the app default) shadows canUseTool but never hooks. Mirrors canUseTool's
23887
+ * abort/cleanup handling so an interrupted turn never leaks a parked card. */
23888
+ raiseQuestion(live, input, toolUseId, signal, agentId) {
23889
+ return new Promise((resolve10) => {
23890
+ const requestId = randomUUID2();
23891
+ const onAbort = () => {
23892
+ if (this.pendingPermissions.delete(requestId))
23893
+ resolve10({ behavior: "deny", message: "Interrupted.", interrupt: true });
23894
+ };
23895
+ const cleanup2 = () => signal.removeEventListener("abort", onAbort);
23896
+ if (signal.aborted) {
23897
+ resolve10({ behavior: "deny", message: "Interrupted.", interrupt: true });
23898
+ return;
23899
+ }
23900
+ signal.addEventListener("abort", onAbort, { once: true });
23901
+ this.pendingPermissions.set(requestId, { resolve: resolve10, cleanup: cleanup2, origin: live.origin });
23902
+ const willEvaluate = !!this.cheapLlm && Array.isArray(input.questions) && input.questions.length > 0;
23903
+ const evt = {
23904
+ type: "agent:permission_request",
23905
+ sessionId: live.sessionId,
23906
+ requestId,
23907
+ toolName: "AskUserQuestion",
23908
+ input,
23909
+ ...toolUseId ? { toolUseId } : {},
23910
+ ...agentId ? { agentId } : {},
23911
+ ...willEvaluate ? { recommendationsPending: true } : {}
23912
+ };
23913
+ if (!live.sessionId && live.clientRequestId)
23914
+ this.send({ ...evt, clientRequestId: live.clientRequestId }, live.origin);
23915
+ else
23916
+ this.send(evt, live.origin);
23917
+ if (willEvaluate)
23918
+ void this.evaluateOptions(live, requestId, input);
23919
+ });
23920
+ }
23921
+ /** Compile a compact context summary for the option-recommendation LLM: the project,
23922
+ * the user's current request, and what the assistant just said (why it's asking). */
23923
+ compileQuestionContext(live) {
23924
+ const parts = [];
23925
+ if (live.cwd)
23926
+ parts.push(`Project directory: ${live.cwd}`);
23927
+ if (live.lastPrompt)
23928
+ parts.push(`The user's current request:
23929
+ ${live.lastPrompt.slice(0, 1400)}`);
23930
+ if (Array.isArray(live.lastAssistantContent)) {
23931
+ const text3 = live.lastAssistantContent.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
23932
+ if (text3)
23933
+ parts.push(`What the assistant just said (leading to this question):
23934
+ ${text3.slice(0, 1600)}`);
23935
+ }
23936
+ return parts.join("\n\n") || "No additional context available beyond the question itself.";
23937
+ }
23938
+ /** Rate every option of an AskUserQuestion for the user's situation via the cheap LLM
23939
+ * (structured JSON, 5 levels), then emit agent:option_recommendations. Best-effort:
23940
+ * any failure (no model, bad JSON, card already answered) simply yields no badges. */
23941
+ async evaluateOptions(live, requestId, input) {
23942
+ const llm = this.cheapLlm;
23943
+ if (!llm)
23944
+ return;
23945
+ const questions = Array.isArray(input.questions) ? input.questions : [];
23946
+ if (!questions.length)
23947
+ return;
23948
+ try {
23949
+ const qText = questions.map((q, qi) => {
23950
+ const opts = Array.isArray(q.options) ? q.options : [];
23951
+ const lines = opts.map((o) => ` - "${String(o.label)}"${o.description ? `: ${String(o.description)}` : ""}`).join("\n");
23952
+ return `Question ${qi}: ${String(q.question)}
23953
+ Options:
23954
+ ${lines}`;
23955
+ }).join("\n\n");
23956
+ const system = `You advise a user who is answering multiple-choice questions posed by a coding agent mid-task. For EVERY option of EVERY question, rate how advisable it is FOR THIS USER'S SPECIFIC SITUATION, using exactly one of these five levels: "highly_recommended", "recommended", "neutral", "caution", "discouraged". Be decisive and honest \u2014 most questions have one or two clearly better choices; do not rate everything neutral. Ground each rating in the provided context. Respond with ONLY a JSON object, no prose: {"recommendations":[{"questionIndex":<int>,"optionLabel":"<verbatim label>","level":"<level>","reason":"<max 12 words>"}]}. Include every option.`;
23957
+ const prompt = `## Context
23958
+ ${this.compileQuestionContext(live)}
23959
+
23960
+ ## Questions and options
23961
+ ${qText}`;
23962
+ const out = await llm({ system, prompt, maxTokens: 1400 });
23963
+ if (!out)
23964
+ return;
23965
+ const parsed = JSON.parse(out.slice(out.indexOf("{"), out.lastIndexOf("}") + 1));
23966
+ const LEVELS = /* @__PURE__ */ new Set(["highly_recommended", "recommended", "neutral", "caution", "discouraged"]);
23967
+ const clean = (Array.isArray(parsed.recommendations) ? parsed.recommendations : []).map((r) => r).filter((r) => r && typeof r.optionLabel === "string" && typeof r.questionIndex === "number" && typeof r.level === "string" && LEVELS.has(r.level)).map((r) => ({ questionIndex: r.questionIndex, optionLabel: r.optionLabel, level: r.level, reason: typeof r.reason === "string" ? r.reason.slice(0, 140) : "" }));
23968
+ if (!clean.length || !this.pendingPermissions.has(requestId))
23969
+ return;
23970
+ const evt = { type: "agent:option_recommendations", sessionId: live.sessionId, requestId, recommendations: clean };
23971
+ if (!live.sessionId && live.clientRequestId)
23972
+ this.send({ ...evt, clientRequestId: live.clientRequestId }, live.origin);
23973
+ else
23974
+ this.send(evt, live.origin);
23975
+ } catch (err) {
23976
+ log("option recommendation eval failed:", err instanceof Error ? err.message : String(err));
23977
+ }
23978
+ }
23979
+ /** request_user_dialog: park a promise, raise the dialog card
23980
+ * (agent:user_dialog_request), and resolve it from the client's chat:user_dialog
23981
+ * reply (→ respondToUserDialog). Mirrors onElicitation: the CLI parks the flow until
23982
+ * we answer; an unanswered/aborted dialog resolves 'cancelled' so the CLI applies the
23983
+ * dialog's default behavior (e.g. the classic refusal message) rather than hanging. */
23984
+ raiseUserDialog(live, request, signal) {
23985
+ return new Promise((resolve10) => {
23986
+ const requestId = randomUUID2();
23987
+ log("user_dialog raised:", request.dialogKind, "requestId=" + requestId, "payloadKeys=" + Object.keys(request.payload ?? {}).join(","));
23988
+ const onAbort = () => {
23989
+ if (this.pendingUserDialogs.delete(requestId)) {
23990
+ log("user_dialog aborted (cancelled):", request.dialogKind, requestId);
23991
+ resolve10({ behavior: "cancelled" });
23992
+ }
23993
+ };
23994
+ const cleanup2 = () => signal.removeEventListener("abort", onAbort);
23995
+ if (signal.aborted) {
23996
+ log("user_dialog pre-aborted (cancelled):", request.dialogKind);
23997
+ resolve10({ behavior: "cancelled" });
23998
+ return;
23999
+ }
24000
+ signal.addEventListener("abort", onAbort, { once: true });
24001
+ this.pendingUserDialogs.set(requestId, { resolve: resolve10, cleanup: cleanup2, origin: live.origin });
24002
+ const evt = {
24003
+ type: "agent:user_dialog_request",
24004
+ sessionId: live.sessionId,
24005
+ requestId,
24006
+ dialogKind: request.dialogKind,
24007
+ payload: request.payload ?? {},
24008
+ ...request.toolUseID ? { toolUseId: request.toolUseID } : {}
24009
+ };
24010
+ if (!live.sessionId && live.clientRequestId)
24011
+ this.send({ ...evt, clientRequestId: live.clientRequestId }, live.origin);
24012
+ else
24013
+ this.send(evt, live.origin);
24014
+ });
24015
+ }
24016
+ /** Resolve a parked request_user_dialog (client replied via chat:user_dialog).
24017
+ * Driver-only, same origin guard as respondToPermission. */
24018
+ respondToUserDialog(requestId, decision, callerOrigin) {
24019
+ const entry = this.pendingUserDialogs.get(requestId);
24020
+ if (!entry) {
24021
+ log("user_dialog response for unknown/expired requestId:", requestId);
24022
+ return;
24023
+ }
24024
+ if (entry.origin && callerOrigin && entry.origin !== callerOrigin)
24025
+ return;
24026
+ this.pendingUserDialogs.delete(requestId);
24027
+ entry.cleanup();
24028
+ log("user_dialog answered:", requestId, decision.behavior === "completed" ? `result=${JSON.stringify(decision.result)}` : "cancelled");
24029
+ entry.resolve(decision.behavior === "completed" ? { behavior: "completed", result: decision.result } : { behavior: "cancelled" });
24030
+ }
23625
24031
  /** M5 H6 — resolve a parked MCP elicitation (client replied via chat:elicitation).
23626
24032
  * Driver-only, same origin guard as respondToPermission. */
23627
24033
  respondToElicitation(requestId, decision, callerOrigin) {
@@ -24306,6 +24712,10 @@ CRITICAL RULES:
24306
24712
  // resolve it when the client replies via chat:permission (respondToPermission),
24307
24713
  // or deny on abort so an interrupted turn never leaks a pending decision.
24308
24714
  canUseTool: (toolName, input, opts) => new Promise((resolve10) => {
24715
+ if (toolName === "AskUserQuestion") {
24716
+ resolve10({ behavior: "allow" });
24717
+ return;
24718
+ }
24309
24719
  const requestId = randomUUID2();
24310
24720
  const onAbort = () => {
24311
24721
  if (this.pendingPermissions.delete(requestId)) {
@@ -24374,12 +24784,38 @@ CRITICAL RULES:
24374
24784
  else
24375
24785
  this.send(evt, live.origin);
24376
24786
  }),
24787
+ // request_user_dialog — blocking model-fallback / usage-consent dialogs the CLI
24788
+ // asks the host to render. Without this the flow degrades to no-dialog (a model
24789
+ // refusal ends the turn with the classic error). We declare the kinds we render
24790
+ // (supportedDialogKinds); the CLI fails closed and never emits an undeclared kind.
24791
+ onUserDialog: (request, opts) => this.raiseUserDialog(live, request, opts.signal),
24792
+ supportedDialogKinds: ["refusal_fallback_prompt", "fable_overage_consent_prompt"],
24377
24793
  // Capture file edits for the client's "Review changes" (see pendingEdits
24378
24794
  // above). Bash-driven writes aren't visible here — only the editing tools.
24379
24795
  // Subagent tool calls (agent_id set) are skipped: their tool blocks aren't
24380
24796
  // in the visible stream, so an emitted edit would convert the wrong card.
24381
24797
  hooks: {
24382
24798
  PreToolUse: [{
24799
+ // AskUserQuestion is an INTERACTIVE tool with no server-side executor: its
24800
+ // "result" IS the user's selection. If it isn't surfaced and answered, the
24801
+ // tool_use dangles and the whole turn stalls (no result ever arrives).
24802
+ // canUseTool can't carry it: in bypassPermissions (the app default) the SDK
24803
+ // auto-approves every tool BEFORE canUseTool runs — it even logs
24804
+ // CLAUDE_SDK_CAN_USE_TOOL_SHADOWED — so the question card never shows. A
24805
+ // PreToolUse hook fires in EVERY permission mode and can return updatedInput,
24806
+ // so we raise the card here and bake the chosen answers back into the input.
24807
+ matcher: "AskUserQuestion",
24808
+ hooks: [async (input, _toolUseId, opts) => {
24809
+ if (input.hook_event_name !== "PreToolUse" || input.tool_name !== "AskUserQuestion")
24810
+ return {};
24811
+ const original = input.tool_input ?? {};
24812
+ const decision = await this.raiseQuestion(live, original, input.tool_use_id, opts.signal, input.agent_id);
24813
+ if (decision.behavior === "allow") {
24814
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: { ...original, ...decision.updatedInput ?? {} } } };
24815
+ }
24816
+ return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: decision.message || "The user dismissed the question without answering." } };
24817
+ }]
24818
+ }, {
24383
24819
  matcher: "Edit|Write|MultiEdit|NotebookEdit",
24384
24820
  hooks: [async (input) => {
24385
24821
  if (input.hook_event_name === "PreToolUse" && !input.agent_id) {
@@ -24458,10 +24894,14 @@ CRITICAL RULES:
24458
24894
 
24459
24895
  ${params.appendSystemPrompt}` : "") },
24460
24896
  includePartialMessages: true,
24461
- // Opt into AskUserQuestion option previews. Without a previewFormat the
24462
- // SDK omits the preview-field guidance from the tool prompt, so the
24463
- // model never emits previews; our card renders them as markdown.
24464
- toolConfig: { askUserQuestion: { previewFormat: "markdown" } },
24897
+ // Emit a predicted follow-up prompt after each turn (git/context-aware) — rendered
24898
+ // as a clickable suggestion chip. Off by default in the SDK, so opt in here.
24899
+ promptSuggestions: true,
24900
+ // Opt into AskUserQuestion option previews as self-contained HTML fragments —
24901
+ // the card renders them as live, sandboxed mockups (an actual styled button,
24902
+ // not its source). Without a previewFormat the SDK omits the preview guidance
24903
+ // and the model never emits previews.
24904
+ toolConfig: { askUserQuestion: { previewFormat: "html" } },
24465
24905
  // M7 — structured output: constrain the final result to a JSON Schema.
24466
24906
  ...params.jsonSchema ? { outputFormat: { type: "json_schema", schema: params.jsonSchema } } : {},
24467
24907
  // Customize panel: inline-key parity surfaces. These keep the SDK
@@ -24529,6 +24969,36 @@ ${params.appendSystemPrompt}` : "") },
24529
24969
  log("send callback threw (ignored):", err instanceof Error ? err.message : String(err));
24530
24970
  }
24531
24971
  };
24972
+ const sendEvent = (spec) => {
24973
+ emit({ type: "agent:framework_event", sessionId: live.sessionId, ...spec });
24974
+ };
24975
+ const flushPendingThinking = () => {
24976
+ if (live.pendingThinking) {
24977
+ sendEvent(live.pendingThinking);
24978
+ live.pendingThinking = void 0;
24979
+ live.lastThinkingEmitAt = Date.now();
24980
+ }
24981
+ };
24982
+ const emitNotice = (spec) => {
24983
+ flushPendingThinking();
24984
+ emit({ type: "agent:notice", sessionId: live.sessionId, noticeId: randomUUID2(), ...spec });
24985
+ };
24986
+ const emitEvent = (spec, parentId) => {
24987
+ const payload = { ...spec, ...parentId ? { parentToolUseId: parentId } : {} };
24988
+ if (spec.updateKey === "thinking_tokens") {
24989
+ const now2 = Date.now();
24990
+ if (now2 - (live.lastThinkingEmitAt ?? 0) >= THINKING_THROTTLE_MS) {
24991
+ live.lastThinkingEmitAt = now2;
24992
+ live.pendingThinking = void 0;
24993
+ sendEvent(payload);
24994
+ } else {
24995
+ live.pendingThinking = payload;
24996
+ }
24997
+ return;
24998
+ }
24999
+ flushPendingThinking();
25000
+ sendEvent(payload);
25001
+ };
24532
25002
  const refreshSessionUsage = (sessionId) => {
24533
25003
  void getSessionUsage(live.engineEnv).then((usage) => {
24534
25004
  if (usage && (usage.session || usage.weekly)) {
@@ -24544,6 +25014,7 @@ ${params.appendSystemPrompt}` : "") },
24544
25014
  live.trace = { turnId: crypto.randomUUID(), startedAt: Date.now(), frames: [] };
24545
25015
  if (live.trace.frames.length < 2e3)
24546
25016
  live.trace.frames.push(message);
25017
+ traceFrame(message);
24547
25018
  if (!live.sessionId && "session_id" in message && message.session_id) {
24548
25019
  const sid2 = message.session_id;
24549
25020
  live.sessionId = sid2;
@@ -24566,6 +25037,7 @@ ${params.appendSystemPrompt}` : "") },
24566
25037
  if (event.type === "content_block_start") {
24567
25038
  const cbt = event.content_block?.type;
24568
25039
  if ((cbt === "tool_use" || cbt === "server_tool_use") && event.content_block?.name) {
25040
+ flushPendingThinking();
24569
25041
  const isServerTool = cbt === "server_tool_use";
24570
25042
  const id = event.content_block.id ?? "";
24571
25043
  blockAcc.set(key2, { name: event.content_block.name, input: "", isServerTool, id });
@@ -24659,6 +25131,7 @@ ${params.appendSystemPrompt}` : "") },
24659
25131
  break;
24660
25132
  }
24661
25133
  case "result": {
25134
+ flushPendingThinking();
24662
25135
  const r = message;
24663
25136
  const turnSid = r.session_id || sid;
24664
25137
  this.narrator.endTurn(turnSid);
@@ -24695,6 +25168,9 @@ ${params.appendSystemPrompt}` : "") },
24695
25168
  }
24696
25169
  }
24697
25170
  }
25171
+ const sources = extractSources(live.lastAssistantContent);
25172
+ if (sources.length > 0)
25173
+ emit({ type: "agent:sources", sessionId: turnSid, messageId: turnMessageId, sources });
24698
25174
  const usage = r.usage;
24699
25175
  const continuable = r.stop_reason === "max_tokens";
24700
25176
  emit({
@@ -24742,14 +25218,14 @@ ${params.appendSystemPrompt}` : "") },
24742
25218
  }
24743
25219
  case "auth_status": {
24744
25220
  const m = message;
24745
- const lines = [systemHeader("auth_status")];
24746
- if (m.isAuthenticating)
24747
- lines.push("Authenticating\u2026");
24748
- if (m.error)
24749
- lines.push(`Error: ${m.error}`);
24750
- if (m.output?.length)
24751
- lines.push(m.output.join("\n"));
24752
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: lines.join("\n\n") });
25221
+ emitEvent({
25222
+ category: "auth",
25223
+ name: "auth_status",
25224
+ title: m.error ? "Authentication error" : m.isAuthenticating ? "Authenticating\u2026" : "Auth status",
25225
+ ...m.error ? { fields: [{ label: "Error", value: m.error }] } : {},
25226
+ ...m.output?.length ? { detail: m.output.join("\n") } : {},
25227
+ updateKey: "auth_status"
25228
+ });
24753
25229
  break;
24754
25230
  }
24755
25231
  case "rate_limit_event": {
@@ -24765,46 +25241,121 @@ ${params.appendSystemPrompt}` : "") },
24765
25241
  const attempt = m.attempt ?? "?";
24766
25242
  const max = m.max_retries ?? "?";
24767
25243
  const errStatus = m.error_status ? ` (HTTP ${m.error_status})` : "";
24768
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: `${systemHeader("system", "api_retry")}
24769
-
24770
- Retrying API call (${attempt}/${max})${errStatus}\u2026` });
25244
+ emitEvent({
25245
+ category: "retry",
25246
+ name: "api_retry",
25247
+ title: `Retrying API call (${attempt}/${max})${errStatus}`,
25248
+ ...m.error_status ? { fields: [{ label: "HTTP", value: String(m.error_status) }] } : {}
25249
+ });
24771
25250
  break;
24772
25251
  }
25252
+ // Subagent (Task tool) lifecycle → one live card per task_id (agent:task).
24773
25253
  case "task_started": {
24774
- const desc = m.description || "subagent task";
24775
- const taskId = typeof m.task_id === "string" ? m.task_id : void 0;
24776
- if (taskId)
24777
- live.taskIds.add(taskId);
24778
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "Task", summary: `**Task**: ${desc}`, ...taskId ? { taskId } : {} });
25254
+ const taskId = sstr(m.task_id);
25255
+ if (!taskId)
25256
+ break;
25257
+ live.taskIds.add(taskId);
25258
+ emit({
25259
+ type: "agent:task",
25260
+ sessionId: sid,
25261
+ taskId,
25262
+ phase: "started",
25263
+ status: "running",
25264
+ ...sstr(m.tool_use_id) ? { toolUseId: sstr(m.tool_use_id) } : {},
25265
+ ...parentToolUseId ? { parentToolUseId } : {},
25266
+ description: sstr(m.description) || "subagent task",
25267
+ ...sstr(m.subagent_type) ? { subagentType: sstr(m.subagent_type) } : {},
25268
+ ...sstr(m.workflow_name) ? { workflowName: sstr(m.workflow_name) } : {}
25269
+ });
25270
+ break;
25271
+ }
25272
+ case "task_progress": {
25273
+ const taskId = sstr(m.task_id);
25274
+ if (!taskId)
25275
+ break;
25276
+ const u = m.usage;
25277
+ emit({
25278
+ type: "agent:task",
25279
+ sessionId: sid,
25280
+ taskId,
25281
+ phase: "progress",
25282
+ status: "running",
25283
+ ...sstr(m.description) ? { description: sstr(m.description) } : {},
25284
+ ...sstr(m.subagent_type) ? { subagentType: sstr(m.subagent_type) } : {},
25285
+ ...sstr(m.last_tool_name) ? { lastToolName: sstr(m.last_tool_name) } : {},
25286
+ ...sstr(m.summary) ? { summary: sstr(m.summary) } : {},
25287
+ ...u ? { usage: { totalTokens: u.total_tokens ?? 0, toolUses: u.tool_uses ?? 0, durationMs: u.duration_ms ?? 0 } } : {}
25288
+ });
25289
+ break;
25290
+ }
25291
+ case "task_updated": {
25292
+ const taskId = sstr(m.task_id);
25293
+ if (!taskId)
25294
+ break;
25295
+ const patch = m.patch && typeof m.patch === "object" ? m.patch : {};
25296
+ const status2 = ["pending", "running", "completed", "failed", "killed", "paused"].includes(sstr(patch.status)) ? patch.status : void 0;
25297
+ emit({
25298
+ type: "agent:task",
25299
+ sessionId: sid,
25300
+ taskId,
25301
+ phase: "updated",
25302
+ ...status2 ? { status: status2 } : {},
25303
+ ...sstr(patch.description) ? { description: sstr(patch.description) } : {},
25304
+ ...sstr(patch.error) ? { error: sstr(patch.error) } : {}
25305
+ });
25306
+ if (status2 === "completed" || status2 === "failed" || status2 === "killed")
25307
+ live.taskIds.delete(taskId);
24779
25308
  break;
24780
25309
  }
24781
25310
  case "task_notification": {
24782
- const status2 = m.status || "completed";
24783
- const summary = m.summary || "";
24784
- const body = summary ? `**${status2}** \u2014 ${summary}` : `**${status2}**`;
24785
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: `${systemHeader("system", "task_notification")}
24786
-
24787
- ${body}` });
25311
+ const taskId = sstr(m.task_id);
25312
+ if (!taskId)
25313
+ break;
25314
+ const status2 = ["completed", "failed", "stopped"].includes(sstr(m.status)) ? m.status : "completed";
25315
+ const u = m.usage;
25316
+ emit({
25317
+ type: "agent:task",
25318
+ sessionId: sid,
25319
+ taskId,
25320
+ phase: "done",
25321
+ status: status2,
25322
+ ...sstr(m.summary) ? { summary: sstr(m.summary) } : {},
25323
+ ...u ? { usage: { totalTokens: u.total_tokens ?? 0, toolUses: u.tool_uses ?? 0, durationMs: u.duration_ms ?? 0 } } : {}
25324
+ });
25325
+ live.taskIds.delete(taskId);
24788
25326
  break;
24789
25327
  }
25328
+ // Slash-command output is user-requested content — a visible inline notice
25329
+ // (NOT the geek-only debug feed).
24790
25330
  case "local_command_output": {
24791
- const content = m.content || "";
24792
- if (content.trim()) {
24793
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: `${systemHeader("system", "local_command_output")}
24794
-
24795
- ${content}` });
25331
+ const content = sstr(m.content).trim();
25332
+ if (content)
25333
+ emitNotice({ category: "info", level: "info", title: "Command output", body: content, inline: true, toast: false });
25334
+ break;
25335
+ }
25336
+ // The session worker is stopping — surface it so an abrupt halt is explained.
25337
+ case "worker_shutting_down": {
25338
+ emitNotice({ category: "info", level: "warning", title: "Session worker is restarting", ...sstr(m.reason) ? { body: sstr(m.reason) } : {}, toast: true, inline: true });
25339
+ break;
25340
+ }
25341
+ // File persistence — only surface FAILURES (successes stay quiet in the log).
25342
+ case "files_persisted": {
25343
+ const failed = Array.isArray(m.failed) ? m.failed : [];
25344
+ if (failed.length) {
25345
+ emitNotice({ category: "info", level: "warning", title: `${failed.length} file${failed.length === 1 ? "" : "s"} failed to save`, body: failed.map((f) => `${sstr(f.filename)}: ${sstr(f.error)}`).join("\n"), toast: true, inline: true });
24796
25346
  }
24797
25347
  break;
24798
25348
  }
24799
25349
  case "hook_response": {
24800
- const stdout = m.stdout || "";
24801
- const stderr = m.stderr || "";
24802
- const out = stdout || stderr;
24803
- if (out.trim()) {
24804
- const hookName = m.hook_name || "hook";
24805
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: `${systemHeader("system", `hook_response/${hookName}`)}
24806
-
24807
- ${out}` });
25350
+ const hookName = sstr(m.hook_name) || "hook";
25351
+ const out = (sstr(m.stderr) || sstr(m.stdout) || sstr(m.output)).trim();
25352
+ const exit = typeof m.exit_code === "number" ? m.exit_code : void 0;
25353
+ const failed = sstr(m.outcome) === "error" || exit !== void 0 && exit !== 0;
25354
+ if (failed) {
25355
+ const body = [sstr(m.hook_event) ? `Event: ${sstr(m.hook_event)}` : "", exit !== void 0 ? `Exit ${exit}` : "", out].filter(Boolean).join("\n").slice(0, 800);
25356
+ emitNotice({ category: "hook", level: "warning", title: `Hook failed: ${hookName}`, ...body ? { body } : {}, toast: true, inline: true });
25357
+ } else if (out) {
25358
+ emitEvent({ category: "hook", name: "hook_response", title: `Hook response \u2014 ${hookName}`, detail: out, updateKey: `hook:${hookName}` });
24808
25359
  }
24809
25360
  break;
24810
25361
  }
@@ -24839,9 +25390,7 @@ ${out}` });
24839
25390
  // M4 H11 — compaction boundary: clear the "compacting" indicator + a notice.
24840
25391
  case "compact_boundary": {
24841
25392
  emit({ type: "agent:session_state", sessionId: sid, state: "running" });
24842
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: `${systemHeader("system", "compact_boundary")}
24843
-
24844
- Compacted earlier context.` });
25393
+ emitEvent({ category: "lifecycle", name: "compact_boundary", title: "Compacted earlier context", fields: salientFields(m) });
24845
25394
  break;
24846
25395
  }
24847
25396
  // M4 H11 — authoritative idle/running/requires_action. Never drains
@@ -24856,20 +25405,37 @@ Compacted earlier context.` });
24856
25405
  emit({ type: "agent:elicitation_complete", sessionId: sid, serverName: m.mcp_server_name ?? "", elicitationId: m.elicitation_id ?? "" });
24857
25406
  break;
24858
25407
  }
24859
- // Low-value subtypes — don't render. Log so we notice if their semantics change.
24860
- case "hook_started":
24861
- case "hook_progress":
24862
- case "task_progress":
24863
- case "files_persisted":
25408
+ // Slash-command set changed (plugin loaded, .claude/commands edited) — push
25409
+ // the fresh list so the command palette updates without a restart.
25410
+ case "commands_changed": {
25411
+ const commands = (Array.isArray(m.commands) ? m.commands : []).flatMap((c) => {
25412
+ if (!c || typeof c !== "object")
25413
+ return [];
25414
+ const o = c;
25415
+ if (typeof o.name !== "string")
25416
+ return [];
25417
+ return [{ name: o.name, description: typeof o.description === "string" ? o.description : "", ...typeof o.hint === "string" ? { hint: o.hint } : {} }];
25418
+ });
25419
+ this.send({ type: "commands:list", commands }, live.origin);
24864
25420
  break;
25421
+ }
25422
+ // Subtypes that earn a real user-facing notice (toast + inline card + native
25423
+ // when unfocused) rather than the raw debug feed: model-fallback, blocked
25424
+ // tools, plugin installs, leveled info, memory recall.
25425
+ case "notification":
25426
+ case "informational":
25427
+ case "plugin_install":
25428
+ case "memory_recall":
25429
+ case "permission_denied":
25430
+ case "model_refusal_fallback":
25431
+ case "model_refusal_no_fallback": {
25432
+ const notice = buildNotice(subtype, m);
25433
+ if (notice)
25434
+ emitNotice(notice);
25435
+ break;
25436
+ }
24865
25437
  default: {
24866
- const text3 = extractHumanReadable(m);
24867
- const header = systemHeader("system", subtype);
24868
- const body = text3 ? `${header}
24869
-
24870
- ${text3}` : header;
24871
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: body });
24872
- log("best-effort surfaced unknown system subtype:", subtype ?? "?");
25438
+ emitEvent(describeSystemSubtype(subtype ?? "unknown", m), parentToolUseId);
24873
25439
  break;
24874
25440
  }
24875
25441
  }
@@ -24924,24 +25490,40 @@ ${text3}` : header;
24924
25490
  }
24925
25491
  break;
24926
25492
  }
24927
- case "tool_progress":
24928
- case "prompt_suggestion":
25493
+ // A still-running tool's elapsed time → update its card's badge in place. Only
25494
+ // once it's been a few seconds (fast tools finish before the badge matters).
25495
+ case "tool_progress": {
25496
+ const tp = message;
25497
+ const elapsed = Math.round(Number(tp.elapsed_time_seconds ?? 0));
25498
+ if (tp.tool_use_id && elapsed >= 3)
25499
+ emit({ type: "agent:tool_use", sessionId: sid, toolName: "_progress_", summary: "", toolUseId: tp.tool_use_id, elapsedSeconds: elapsed, ...parentToolUseId ? { parentToolUseId } : {} });
25500
+ break;
25501
+ }
25502
+ // A predicted follow-up prompt → a clickable chip under the reply.
25503
+ case "prompt_suggestion": {
25504
+ const s = sstr(message.suggestion).trim();
25505
+ if (s)
25506
+ emit({ type: "agent:suggestion", sessionId: sid, suggestion: s });
24929
25507
  break;
25508
+ }
24930
25509
  default: {
24931
25510
  const m = message;
24932
25511
  const text3 = extractHumanReadable(m);
24933
- const header = systemHeader(m.type || "unknown");
24934
- const body = text3 ? `${header}
24935
-
24936
- ${text3}` : header;
24937
- emit({ type: "agent:tool_use", sessionId: sid, toolName: "_notice_", summary: body });
24938
- log("best-effort surfaced unknown SDK message type:", m.type || "?");
25512
+ emitEvent({
25513
+ category: "unknown",
25514
+ name: m.type || "unknown",
25515
+ title: m.type || "unknown frame",
25516
+ ...text3 ? { detail: text3 } : {},
25517
+ raw: rawFramePreview(m)
25518
+ }, parentToolUseId);
25519
+ log("unknown SDK message type surfaced with raw data:", m.type || "?");
24939
25520
  break;
24940
25521
  }
24941
25522
  }
24942
25523
  }
24943
25524
  } catch (err) {
24944
25525
  const sid = live.sessionId;
25526
+ live.pendingThinking = void 0;
24945
25527
  if (live.interrupted || live.abort.signal.aborted) {
24946
25528
  live.interrupted = false;
24947
25529
  this.clearStopWatchdog(live);
@@ -27132,26 +27714,26 @@ var require_build = __commonJS({
27132
27714
  throw new Error(`Unknown type of encoding '${enc}'`);
27133
27715
  }
27134
27716
  }
27135
- static FromString(str, enc = "utf8") {
27136
- if (!str) {
27717
+ static FromString(str2, enc = "utf8") {
27718
+ if (!str2) {
27137
27719
  return new ArrayBuffer(0);
27138
27720
  }
27139
27721
  switch (enc.toLowerCase()) {
27140
27722
  case "utf8":
27141
- return this.FromUtf8String(str);
27723
+ return this.FromUtf8String(str2);
27142
27724
  case "binary":
27143
- return this.FromBinary(str);
27725
+ return this.FromBinary(str2);
27144
27726
  case "hex":
27145
- return this.FromHex(str);
27727
+ return this.FromHex(str2);
27146
27728
  case "base64":
27147
- return this.FromBase64(str);
27729
+ return this.FromBase64(str2);
27148
27730
  case "base64url":
27149
- return this.FromBase64Url(str);
27731
+ return this.FromBase64Url(str2);
27150
27732
  case "utf16le":
27151
- return Utf16Converter.fromString(str, true);
27733
+ return Utf16Converter.fromString(str2, true);
27152
27734
  case "utf16":
27153
27735
  case "utf16be":
27154
- return Utf16Converter.fromString(str);
27736
+ return Utf16Converter.fromString(str2);
27155
27737
  default:
27156
27738
  throw new Error(`Unknown type of encoding '${enc}'`);
27157
27739
  }
@@ -27354,11 +27936,11 @@ var require_utils = __commonJS({
27354
27936
  function bufferToHexCodes(inputBuffer, inputOffset = 0, inputLength = inputBuffer.byteLength - inputOffset, insertSpace = false) {
27355
27937
  let result = "";
27356
27938
  for (const item of new Uint8Array(inputBuffer, inputOffset, inputLength)) {
27357
- const str = item.toString(16).toUpperCase();
27358
- if (str.length === 1) {
27939
+ const str2 = item.toString(16).toUpperCase();
27940
+ if (str2.length === 1) {
27359
27941
  result += "0";
27360
27942
  }
27361
- result += str;
27943
+ result += str2;
27362
27944
  if (insertSpace) {
27363
27945
  result += " ";
27364
27946
  }
@@ -27525,17 +28107,17 @@ var require_utils = __commonJS({
27525
28107
  return true;
27526
28108
  }
27527
28109
  function padNumber(inputNumber, fullLength) {
27528
- const str = inputNumber.toString(10);
27529
- if (fullLength < str.length) {
28110
+ const str2 = inputNumber.toString(10);
28111
+ if (fullLength < str2.length) {
27530
28112
  return "";
27531
28113
  }
27532
- const dif = fullLength - str.length;
28114
+ const dif = fullLength - str2.length;
27533
28115
  const padding = new Array(dif);
27534
28116
  for (let i = 0; i < dif; i++) {
27535
28117
  padding[i] = "0";
27536
28118
  }
27537
28119
  const paddingString = padding.join("");
27538
- return paddingString.concat(str);
28120
+ return paddingString.concat(str2);
27539
28121
  }
27540
28122
  var base64Template = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
27541
28123
  var base64UrlTemplate = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
@@ -27647,12 +28229,12 @@ var require_utils = __commonJS({
27647
28229
  }
27648
28230
  return resultString;
27649
28231
  }
27650
- function stringToArrayBuffer(str) {
27651
- const stringLength = str.length;
28232
+ function stringToArrayBuffer(str2) {
28233
+ const stringLength = str2.length;
27652
28234
  const resultBuffer = new ArrayBuffer(stringLength);
27653
28235
  const resultView = new Uint8Array(resultBuffer);
27654
28236
  for (let i = 0; i < stringLength; i++) {
27655
- resultView[i] = str.charCodeAt(i);
28237
+ resultView[i] = str2.charCodeAt(i);
27656
28238
  }
27657
28239
  return resultBuffer;
27658
28240
  }
@@ -30090,11 +30672,11 @@ ${values.join("\n")}` : `${blockName} :`;
30090
30672
  this.fromString(String.fromCharCode.apply(null, pvtsutils__namespace.BufferSourceConverter.toUint8Array(inputBuffer)));
30091
30673
  }
30092
30674
  toBuffer() {
30093
- const str = this.toString();
30094
- const buffer = new ArrayBuffer(str.length);
30675
+ const str2 = this.toString();
30676
+ const buffer = new ArrayBuffer(str2.length);
30095
30677
  const view = new Uint8Array(buffer);
30096
- for (let i = 0; i < str.length; i++)
30097
- view[i] = str.charCodeAt(i);
30678
+ for (let i = 0; i < str2.length; i++)
30679
+ view[i] = str2.charCodeAt(i);
30098
30680
  return buffer;
30099
30681
  }
30100
30682
  fromDate(inputDate) {
@@ -45761,6 +46343,7 @@ async function createClaudeCodeBackend(send2, onBrowserRequest, loadPluginRuntim
45761
46343
  stopTask: (sessionId, taskId, callerOrigin) => manager2.stopTask(sessionId, taskId, callerOrigin),
45762
46344
  respondToElicitation: (requestId, decision, callerOrigin) => manager2.respondToElicitation(requestId, decision, callerOrigin),
45763
46345
  respondToUiRequest: (requestId, response, callerOrigin) => manager2.respondToUiRequest(requestId, response, callerOrigin),
46346
+ respondToUserDialog: (requestId, decision, callerOrigin) => manager2.respondToUserDialog(requestId, decision, callerOrigin),
45764
46347
  mcpServerStatus: (sessionId) => manager2.mcpServerStatus(sessionId),
45765
46348
  reconnectMcpServer: (sessionId, name) => manager2.reconnectMcpServer(sessionId, name),
45766
46349
  toggleMcpServer: (sessionId, name, enabled) => manager2.toggleMcpServer(sessionId, name, enabled),
@@ -53507,18 +54090,18 @@ var unifyPaths = (paths_) => {
53507
54090
  return paths.map(normalizePathToUnix);
53508
54091
  };
53509
54092
  var toUnix = (string4) => {
53510
- let str = string4.replace(BACK_SLASH_RE, SLASH);
54093
+ let str2 = string4.replace(BACK_SLASH_RE, SLASH);
53511
54094
  let prepend = false;
53512
- if (str.startsWith(SLASH_SLASH)) {
54095
+ if (str2.startsWith(SLASH_SLASH)) {
53513
54096
  prepend = true;
53514
54097
  }
53515
- while (str.match(DOUBLE_SLASH_RE)) {
53516
- str = str.replace(DOUBLE_SLASH_RE, SLASH);
54098
+ while (str2.match(DOUBLE_SLASH_RE)) {
54099
+ str2 = str2.replace(DOUBLE_SLASH_RE, SLASH);
53517
54100
  }
53518
54101
  if (prepend) {
53519
- str = SLASH + str;
54102
+ str2 = SLASH + str2;
53520
54103
  }
53521
- return str;
54104
+ return str2;
53522
54105
  };
53523
54106
  var normalizePathToUnix = (path5) => toUnix(sysPath2.normalize(toUnix(path5)));
53524
54107
  var normalizeIgnored = (cwd = "") => (path5) => {
@@ -56069,6 +56652,9 @@ async function handleInboundAgent(msg, driverClientId) {
56069
56652
  case "chat:ui_response":
56070
56653
  b.respondToUiRequest?.(msg.requestId, msg.response, origin);
56071
56654
  break;
56655
+ case "chat:user_dialog":
56656
+ b.respondToUserDialog?.(msg.requestId, { behavior: msg.behavior, result: msg.result }, origin);
56657
+ break;
56072
56658
  case "agent:mcp_reconnect":
56073
56659
  void b.reconnectMcpServer?.(msg.sessionId, msg.name);
56074
56660
  break;
@@ -56836,6 +57422,9 @@ async function startLocalServer(opts) {
56836
57422
  case "chat:ui_response":
56837
57423
  b.respondToUiRequest?.(msg.requestId, msg.response, loopbackOrigin);
56838
57424
  break;
57425
+ case "chat:user_dialog":
57426
+ b.respondToUserDialog?.(msg.requestId, { behavior: msg.behavior, result: msg.result }, loopbackOrigin);
57427
+ break;
56839
57428
  case "agent:mcp_reconnect":
56840
57429
  void b.reconnectMcpServer?.(msg.sessionId, msg.name);
56841
57430
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tesseract-agent",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "license": "UNLICENSED",
5
5
  "publishConfig": {
6
6
  "access": "public",