tesseract-agent 0.7.0 → 0.7.2

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