@adhdev/daemon-core 0.9.82-rc.187 → 0.9.82-rc.189

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 (42) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +1 -0
  2. package/dist/commands/cli-manager.d.ts +2 -1
  3. package/dist/commands/router.d.ts +5 -1
  4. package/dist/git/git-commands.d.ts +2 -0
  5. package/dist/git/git-types.d.ts +2 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +459 -38
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +458 -38
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/providers/cli-provider-instance.d.ts +4 -0
  12. package/dist/providers/contracts.d.ts +31 -0
  13. package/dist/providers/sdk/v1/types/common/index.d.ts +35 -1
  14. package/dist/providers/spec/adapter.d.ts +4 -0
  15. package/dist/providers/spec/driver.d.ts +10 -1
  16. package/dist/providers/spec/evaluator.d.ts +9 -1
  17. package/dist/providers/spec/schema.gen.d.ts +38 -0
  18. package/dist/providers/spec/types.d.ts +25 -0
  19. package/dist/repo-mesh-types.d.ts +6 -0
  20. package/package.json +1 -1
  21. package/src/boot/daemon-lifecycle.ts +2 -0
  22. package/src/commands/chat-commands.ts +26 -0
  23. package/src/commands/cli-manager.ts +52 -14
  24. package/src/commands/router.ts +35 -4
  25. package/src/git/git-commands.ts +20 -2
  26. package/src/git/git-status.ts +35 -6
  27. package/src/git/git-types.ts +2 -0
  28. package/src/index.ts +1 -1
  29. package/src/mesh/mesh-events.ts +7 -0
  30. package/src/providers/cli-provider-instance.ts +110 -9
  31. package/src/providers/contracts.d.ts +55 -0
  32. package/src/providers/contracts.ts +35 -0
  33. package/src/providers/provider-schema.ts +56 -1
  34. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +46 -0
  35. package/src/providers/sdk/v1/types/common/index.ts +19 -0
  36. package/src/providers/spec/adapter.ts +8 -0
  37. package/src/providers/spec/driver.ts +74 -2
  38. package/src/providers/spec/evaluator.ts +39 -3
  39. package/src/providers/spec/schema.gen.ts +28 -1
  40. package/src/providers/spec/schema.json +26 -2
  41. package/src/providers/spec/types.ts +25 -0
  42. package/src/repo-mesh-types.ts +6 -0
package/dist/index.mjs CHANGED
@@ -270,6 +270,8 @@ async function getGitRepoStatus(workspace, options = {}) {
270
270
  if (includeSubmodules) {
271
271
  submodules = await getSubmoduleStatuses(repo, options);
272
272
  }
273
+ const submoduleDirty = (submodules || []).some((submodule) => submodule.dirty || submodule.outOfSync || !!submodule.error);
274
+ const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0 || parsed.conflictFiles.length > 0 || stashCount > 0 || submoduleDirty;
273
275
  return {
274
276
  workspace: repo.workspace,
275
277
  repoRoot: repo.repoRoot,
@@ -288,6 +290,7 @@ async function getGitRepoStatus(workspace, options = {}) {
288
290
  untracked: parsed.untracked,
289
291
  deleted: parsed.deleted,
290
292
  renamed: parsed.renamed,
293
+ dirty,
291
294
  hasConflicts: parsed.conflictFiles.length > 0,
292
295
  conflictFiles: parsed.conflictFiles,
293
296
  stashCount,
@@ -461,6 +464,7 @@ function emptyStatus(workspace, lastCheckedAt, error) {
461
464
  untracked: 0,
462
465
  deleted: 0,
463
466
  renamed: 0,
467
+ dirty: false,
464
468
  hasConflicts: false,
465
469
  conflictFiles: [],
466
470
  stashCount: 0,
@@ -473,17 +477,33 @@ async function getSubmoduleStatuses(repo, options) {
473
477
  if (!repo.repoRoot) return [];
474
478
  try {
475
479
  const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
476
- return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
480
+ const submodules = parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
481
+ await Promise.all(submodules.map((submodule) => enrichSubmoduleWorktreeStatus(repo, submodule, options)));
482
+ return submodules;
477
483
  } catch {
478
484
  return [];
479
485
  }
480
486
  }
487
+ async function enrichSubmoduleWorktreeStatus(repo, submodule, options) {
488
+ try {
489
+ const result = await runGit(repo, ["status", "--porcelain=v2", "--branch"], {
490
+ ...options,
491
+ cwd: submodule.repoPath
492
+ });
493
+ const parsed = parsePorcelainV2Status(result.stdout);
494
+ const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0 || parsed.conflictFiles.length > 0;
495
+ submodule.dirty = submodule.dirty || dirty;
496
+ } catch (error) {
497
+ submodule.dirty = true;
498
+ submodule.error = formatGitError(error);
499
+ }
500
+ }
481
501
  function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
482
502
  const submodules = [];
483
503
  const ignoreSet = new Set(ignorePaths || []);
484
504
  for (const line of output.split("\n")) {
485
505
  if (!line.trim()) continue;
486
- const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
506
+ const match = line.match(/^([\-+U\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
487
507
  if (!match) continue;
488
508
  const prefix = match[1];
489
509
  const commit = match[2];
@@ -493,8 +513,8 @@ function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
493
513
  path: path40,
494
514
  commit,
495
515
  repoPath: repoRoot + "/" + path40,
496
- dirty: prefix === "+",
497
- outOfSync: prefix === "-",
516
+ dirty: prefix === "U",
517
+ outOfSync: prefix === "-" || prefix === "+",
498
518
  lastCheckedAt: Date.now()
499
519
  });
500
520
  }
@@ -5584,6 +5604,8 @@ function handleMeshForwardEvent(components, payload) {
5584
5604
  const nodeId = readNonEmptyString2(payload.nodeId);
5585
5605
  const workspace = readNonEmptyString2(payload.workspace);
5586
5606
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
5607
+ const relayModalMessage = readNonEmptyString2(payload.modalMessage);
5608
+ const relayModalButtons = Array.isArray(payload.modalButtons) ? payload.modalButtons.filter((b) => typeof b === "string" && b.trim().length > 0) : null;
5587
5609
  return injectMeshSystemMessage(components, {
5588
5610
  meshId,
5589
5611
  nodeId,
@@ -5601,6 +5623,8 @@ function handleMeshForwardEvent(components, payload) {
5601
5623
  startedAt: readNonEmptyString2(payload.startedAt),
5602
5624
  completedAt: readNonEmptyString2(payload.completedAt),
5603
5625
  retryOfJobId: readNonEmptyString2(payload.retryOfJobId),
5626
+ ...relayModalMessage ? { modalMessage: relayModalMessage } : {},
5627
+ ...relayModalButtons && relayModalButtons.length > 0 ? { modalButtons: relayModalButtons } : {},
5604
5628
  ...payload.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? { result: payload.result } : {},
5605
5629
  ...payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? { completionDiagnostic: payload.completionDiagnostic } : {},
5606
5630
  ...payload.workerResult && typeof payload.workerResult === "object" && !Array.isArray(payload.workerResult) ? { workerResult: payload.workerResult } : {},
@@ -6150,6 +6174,52 @@ var init_provider_schema = __esm({
6150
6174
  properties: { mode: { const: "env_var" }, name: { type: "string" } }
6151
6175
  }
6152
6176
  ]
6177
+ },
6178
+ delegatedWorkerIsolation: {
6179
+ description: "Provider-declared launch isolation for coordinator-spawned worker sessions. Keeps worker-only sessions from inheriting coordinator MCP/tools/config.",
6180
+ type: "object",
6181
+ additionalProperties: false,
6182
+ properties: {
6183
+ env: {
6184
+ type: "object",
6185
+ additionalProperties: false,
6186
+ properties: {
6187
+ unset: {
6188
+ type: "array",
6189
+ items: { type: "string", minLength: 1 }
6190
+ }
6191
+ }
6192
+ },
6193
+ args: {
6194
+ type: "array",
6195
+ items: {
6196
+ oneOf: [
6197
+ {
6198
+ type: "object",
6199
+ additionalProperties: false,
6200
+ required: ["mode", "flag"],
6201
+ properties: {
6202
+ mode: { const: "empty_mcp_config" },
6203
+ flag: { type: "string", minLength: 1 },
6204
+ strictFlag: { type: "string", minLength: 1 }
6205
+ }
6206
+ },
6207
+ {
6208
+ type: "object",
6209
+ additionalProperties: false,
6210
+ required: ["mode", "flag", "key", "value"],
6211
+ properties: {
6212
+ mode: { const: "config_override" },
6213
+ flag: { type: "string", minLength: 1 },
6214
+ key: { type: "string", minLength: 1 },
6215
+ value: { type: "string", minLength: 1 },
6216
+ dedupeKey: { type: "string", minLength: 1 }
6217
+ }
6218
+ }
6219
+ ]
6220
+ }
6221
+ }
6222
+ }
6153
6223
  }
6154
6224
  }
6155
6225
  },
@@ -12025,7 +12095,14 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
12025
12095
  switch (command) {
12026
12096
  case "git_status": {
12027
12097
  if (!services.getStatus) return serviceNotImplemented(command);
12028
- const status = await runService(() => services.getStatus({ workspace, refreshUpstream: optionalBoolean(args?.refreshUpstream) }));
12098
+ const submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths) ? args.submoduleIgnorePaths.filter((value) => typeof value === "string" && value.trim().length > 0) : void 0;
12099
+ const statusParams = { workspace };
12100
+ const refreshUpstream = optionalBoolean(args?.refreshUpstream);
12101
+ const includeSubmodules = optionalBoolean(args?.includeSubmodules);
12102
+ if (refreshUpstream !== void 0) statusParams.refreshUpstream = refreshUpstream;
12103
+ if (includeSubmodules !== void 0) statusParams.includeSubmodules = includeSubmodules;
12104
+ if (submoduleIgnorePaths && submoduleIgnorePaths.length > 0) statusParams.submoduleIgnorePaths = submoduleIgnorePaths;
12105
+ const status = await runService(() => services.getStatus(statusParams));
12029
12106
  return "success" in status ? status : { success: true, status };
12030
12107
  }
12031
12108
  case "git_diff_summary": {
@@ -12158,6 +12235,14 @@ async function gitCheckpoint(workspace, message, includeUntracked) {
12158
12235
  if (statusResult.hasConflicts) {
12159
12236
  throw new GitCommandError("conflict", "Repository has conflicts \u2014 resolve before checkpointing");
12160
12237
  }
12238
+ const dirtySubmodules = (statusResult.submodules || []).filter((submodule) => submodule.dirty);
12239
+ if (dirtySubmodules.length > 0) {
12240
+ const paths = dirtySubmodules.map((submodule) => submodule.path).join(", ");
12241
+ throw new GitCommandError(
12242
+ "dirty_index_required",
12243
+ `Repository has dirty submodules that must be checkpointed first: ${paths}. Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.`
12244
+ );
12245
+ }
12161
12246
  const addArgs = includeUntracked ? ["-A"] : ["-u"];
12162
12247
  await runGit(repo, ["add", ...addArgs], { cwd: repoRoot });
12163
12248
  const fullMsg = `adhdev: checkpoint ${message}`;
@@ -21479,6 +21564,14 @@ function hasVisibleAssistantMessage(messages) {
21479
21564
  return String(message.content || "").trim().length > 0;
21480
21565
  });
21481
21566
  }
21567
+ function hasFinalVisibleAssistantMessage(messages) {
21568
+ if (!Array.isArray(messages)) return false;
21569
+ const visible = filterUserFacingChatMessages(messages);
21570
+ const last = visible[visible.length - 1];
21571
+ const role = typeof last?.role === "string" ? last.role.trim().toLowerCase() : "";
21572
+ const content = last ? flattenContent(last.content).trim() : "";
21573
+ return (role === "assistant" || role === "model") && content.length > 0;
21574
+ }
21482
21575
  function shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus) {
21483
21576
  if (!isGeneratingLikeStatus(parsedStatus)) return false;
21484
21577
  if (hasNonEmptyModalButtons(activeModal)) return false;
@@ -22333,6 +22426,18 @@ async function handleReadChat(h, args) {
22333
22426
  });
22334
22427
  }
22335
22428
  }
22429
+ if (isGeneratingLikeStatus(selectedStatus) && selectedTranscriptAuthority === "provider" && !hasNonEmptyModalButtons(activeModal) && hasFinalVisibleAssistantMessage(selectedMessages)) {
22430
+ selectedStatus = "idle";
22431
+ selectedMessages = finalizeStreamingMessagesWhenIdle(selectedMessages, selectedStatus);
22432
+ messageSource = {
22433
+ ...messageSource,
22434
+ statusReconciled: {
22435
+ from: returnedStatus,
22436
+ to: "idle",
22437
+ reason: "provider_native_final_assistant"
22438
+ }
22439
+ };
22440
+ }
22336
22441
  LOG.debug("Command", `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || "")} adapterStatus=${String(adapterStatus.status || "")} parsedStatus=${String(parsedRecord.status || "")} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
22337
22442
  return buildReadChatCommandResult({
22338
22443
  messages: selectedMessages,
@@ -25549,6 +25654,13 @@ var TerminalAdapter = class {
25549
25654
  snapshot() {
25550
25655
  return this.lastScreen || this.computeScreen();
25551
25656
  }
25657
+ getCursorPosition() {
25658
+ const buf = this.term.buffer.active;
25659
+ return {
25660
+ row: Math.max(0, buf.cursorY ?? 0),
25661
+ col: Math.max(0, buf.cursorX ?? 0)
25662
+ };
25663
+ }
25552
25664
  kill() {
25553
25665
  this.stopTimers();
25554
25666
  try {
@@ -25653,14 +25765,33 @@ function compileLinePattern(ref) {
25653
25765
  const flags = (ref.flags ?? "m").replace(/g/g, "");
25654
25766
  return new RegExp(ref.pattern, flags);
25655
25767
  }
25656
- function matchState(state, sections, fullScreen, trace) {
25768
+ function matchState(state, sections, fullScreen, trace, cursor) {
25657
25769
  const haystack = sectionText(sections, state.when.section, fullScreen);
25658
25770
  const re = compileRegex(state.when);
25659
25771
  if (!re.test(haystack)) {
25660
25772
  trace.push({ kind: "state_skip", text: `state[${state.id}] when ${state.when.section ?? "*"}~/${state.when.regex}/ no match` });
25661
25773
  return { matched: false, title: null };
25662
25774
  }
25663
- trace.push({ kind: "state_match", text: `state[${state.id}] matched via ${state.when.section ?? "*"}~/${state.when.regex}/` });
25775
+ if (cursor !== void 0) {
25776
+ const w = state.when;
25777
+ if (w.cursor_row_min !== void 0 && cursor.row < w.cursor_row_min) {
25778
+ trace.push({ kind: "state_skip", text: `state[${state.id}] cursor row ${cursor.row} < cursor_row_min ${w.cursor_row_min}` });
25779
+ return { matched: false, title: null };
25780
+ }
25781
+ if (w.cursor_row_max !== void 0 && cursor.row > w.cursor_row_max) {
25782
+ trace.push({ kind: "state_skip", text: `state[${state.id}] cursor row ${cursor.row} > cursor_row_max ${w.cursor_row_max}` });
25783
+ return { matched: false, title: null };
25784
+ }
25785
+ if (w.cursor_col_min !== void 0 && cursor.col < w.cursor_col_min) {
25786
+ trace.push({ kind: "state_skip", text: `state[${state.id}] cursor col ${cursor.col} < cursor_col_min ${w.cursor_col_min}` });
25787
+ return { matched: false, title: null };
25788
+ }
25789
+ if (w.cursor_col_max !== void 0 && cursor.col > w.cursor_col_max) {
25790
+ trace.push({ kind: "state_skip", text: `state[${state.id}] cursor col ${cursor.col} > cursor_col_max ${w.cursor_col_max}` });
25791
+ return { matched: false, title: null };
25792
+ }
25793
+ }
25794
+ trace.push({ kind: "state_match", text: `state[${state.id}] matched via ${state.when.section ?? "*"}~/${state.when.regex}/${cursor !== void 0 ? ` cursor=(${cursor.row},${cursor.col})` : ""}` });
25664
25795
  let title = null;
25665
25796
  if (state.extract_title) {
25666
25797
  const titleHay = sectionText(sections, state.extract_title.section, fullScreen);
@@ -25718,17 +25849,20 @@ function extractModal(state, sections, fullScreen, title, trace) {
25718
25849
  trace.push({ kind: "modal", text: `modal_buttons matched ${buttons.length} choices` });
25719
25850
  return { title, buttons };
25720
25851
  }
25721
- function evaluate(spec, screenText) {
25852
+ function evaluate(spec, screenText, cursor) {
25722
25853
  const trace = [];
25723
25854
  const lines = screenText.split("\n");
25724
25855
  const sections = resolveSections(spec, lines);
25725
25856
  for (const s of sections) {
25726
25857
  trace.push({ kind: "section", text: `section[${s.id}] lines [${s.fromLine}, ${s.toLine}) (${s.toLine - s.fromLine} lines)` });
25727
25858
  }
25859
+ if (cursor !== void 0) {
25860
+ trace.push({ kind: "section", text: `cursor (${cursor.row}, ${cursor.col})` });
25861
+ }
25728
25862
  let activeState = null;
25729
25863
  let modal = null;
25730
25864
  for (const st of spec.states) {
25731
- const { matched, title } = matchState(st, sections, screenText, trace);
25865
+ const { matched, title } = matchState(st, sections, screenText, trace, cursor);
25732
25866
  if (!matched) continue;
25733
25867
  const extractedModal = extractModal(st, sections, screenText, title, trace);
25734
25868
  if (st.modal_buttons && !extractedModal) {
@@ -25919,7 +26053,18 @@ var SCHEMA = {
25919
26053
  "additionalProperties": false,
25920
26054
  "properties": {
25921
26055
  "busy_hold_ms": { "type": "integer", "minimum": 0 },
25922
- "startup_grace_ms": { "type": "integer", "minimum": 0 }
26056
+ "startup_grace_ms": { "type": "integer", "minimum": 0 },
26057
+ "completion_idle_after": {
26058
+ "type": "object",
26059
+ "additionalProperties": false,
26060
+ "required": ["regex", "hold_ms"],
26061
+ "properties": {
26062
+ "section": { "type": "string", "minLength": 1 },
26063
+ "regex": { "type": "string", "minLength": 1 },
26064
+ "flags": { "type": "string" },
26065
+ "hold_ms": { "type": "integer", "minimum": 0 }
26066
+ }
26067
+ }
25923
26068
  }
25924
26069
  }
25925
26070
  },
@@ -25985,6 +26130,22 @@ var SCHEMA = {
25985
26130
  "flags": {
25986
26131
  "type": "string",
25987
26132
  "default": "i"
26133
+ },
26134
+ "cursor_row_min": {
26135
+ "type": "integer",
26136
+ "minimum": 0
26137
+ },
26138
+ "cursor_row_max": {
26139
+ "type": "integer",
26140
+ "minimum": 0
26141
+ },
26142
+ "cursor_col_min": {
26143
+ "type": "integer",
26144
+ "minimum": 0
26145
+ },
26146
+ "cursor_col_max": {
26147
+ "type": "integer",
26148
+ "minimum": 0
25988
26149
  }
25989
26150
  }
25990
26151
  },
@@ -26399,6 +26560,30 @@ function resolveSubmitDelayMs(specBeforeSubmit, text) {
26399
26560
  const spec = typeof specBeforeSubmit === "number" && specBeforeSubmit > 0 ? specBeforeSubmit : 0;
26400
26561
  return Math.max(spec, SUBMIT_DELAY_FLOOR_MS + linesBonus);
26401
26562
  }
26563
+ function matchesCompletionIdleRule(spec, ev, screen) {
26564
+ const rule = spec.debounce?.completion_idle_after;
26565
+ if (!rule?.regex) return null;
26566
+ const haystack = rule.section ? ev.sections.find((section) => section.id === rule.section)?.text ?? "" : screen;
26567
+ if (!haystack) return null;
26568
+ try {
26569
+ const regex = new RegExp(rule.regex, rule.flags || "");
26570
+ const match = haystack.match(regex);
26571
+ return match?.[0] || null;
26572
+ } catch {
26573
+ return null;
26574
+ }
26575
+ }
26576
+ function matchesCompletionIdleTargetState(spec, ev, screen) {
26577
+ const target = spec.states.find((state) => state.id === spec.default_state) ?? spec.states.find((state) => state.id === "idle");
26578
+ if (!target?.when?.regex) return false;
26579
+ const haystack = target.when.section ? ev.sections.find((section) => section.id === target.when.section)?.text ?? "" : screen;
26580
+ if (!haystack) return false;
26581
+ try {
26582
+ return new RegExp(target.when.regex, target.when.flags || "i").test(haystack);
26583
+ } catch {
26584
+ return false;
26585
+ }
26586
+ }
26402
26587
  var SpecDriver = class {
26403
26588
  constructor(opts) {
26404
26589
  this.opts = opts;
@@ -26438,6 +26623,8 @@ var SpecDriver = class {
26438
26623
  * because the evaluator already moved past busy by the time the hold
26439
26624
  * kicks in. */
26440
26625
  lastBusyState = null;
26626
+ completionIdleFirstSeenAt = 0;
26627
+ completionIdleKey = "";
26441
26628
  /** Timer that re-runs evaluate() once the hold window expires. Needed
26442
26629
  * because the PTY stops emitting once the agent finishes; without an
26443
26630
  * explicit wake-up there's nothing to trigger the busy → idle
@@ -26488,6 +26675,9 @@ var SpecDriver = class {
26488
26675
  snapshot() {
26489
26676
  return this.adapter.snapshot();
26490
26677
  }
26678
+ getCursorPosition() {
26679
+ return this.adapter.getCursorPosition();
26680
+ }
26491
26681
  shutdown() {
26492
26682
  for (const t of this.delegateTimers.values()) clearTimeout(t);
26493
26683
  this.delegateTimers.clear();
@@ -26552,7 +26742,8 @@ var SpecDriver = class {
26552
26742
  }
26553
26743
  reevaluate(forceEmit = false) {
26554
26744
  const screen = this.adapter.snapshot();
26555
- const ev = evaluate(this.spec, screen);
26745
+ const cursor = this.adapter.getCursorPosition();
26746
+ const ev = evaluate(this.spec, screen, cursor);
26556
26747
  let evState = ev.state;
26557
26748
  const busyHoldMs = this.spec.debounce?.busy_hold_ms ?? BUSY_HOLD_MS;
26558
26749
  if (this.currentStateId === "busy" && evState.id === "idle") {
@@ -26561,10 +26752,40 @@ var SpecDriver = class {
26561
26752
  evState = this.lastBusyState ?? evState;
26562
26753
  }
26563
26754
  }
26755
+ const completionIdleRule = this.spec.debounce?.completion_idle_after;
26756
+ let busyWakeMs = busyHoldMs;
26757
+ if (evState.id === "busy" && completionIdleRule) {
26758
+ const completionKey = matchesCompletionIdleRule(this.spec, ev, screen);
26759
+ if (completionKey) {
26760
+ const now = Date.now();
26761
+ if (completionKey !== this.completionIdleKey) {
26762
+ this.completionIdleKey = completionKey;
26763
+ this.completionIdleFirstSeenAt = now;
26764
+ }
26765
+ const holdMs = Math.max(0, completionIdleRule.hold_ms || 0);
26766
+ const ageMs = now - this.completionIdleFirstSeenAt;
26767
+ if (ageMs >= holdMs) {
26768
+ if (matchesCompletionIdleTargetState(this.spec, ev, screen)) {
26769
+ const idle = this.spec.states.find((state) => state.id === this.spec.default_state) ?? this.spec.states.find((state) => state.id === "idle");
26770
+ evState = idle ? { id: idle.id, label: idle.label, title: null } : { id: "idle", label: "Ready", title: null };
26771
+ } else {
26772
+ busyWakeMs = Math.min(busyWakeMs, 1e3);
26773
+ }
26774
+ } else {
26775
+ busyWakeMs = Math.min(busyWakeMs, Math.max(holdMs - ageMs, 0));
26776
+ }
26777
+ } else {
26778
+ this.completionIdleKey = "";
26779
+ this.completionIdleFirstSeenAt = 0;
26780
+ }
26781
+ } else if (evState.id !== "busy") {
26782
+ this.completionIdleKey = "";
26783
+ this.completionIdleFirstSeenAt = 0;
26784
+ }
26564
26785
  if (evState.id === "busy") {
26565
26786
  this.lastBusyAt = Date.now();
26566
26787
  this.lastBusyState = evState;
26567
- this.scheduleBusyExpiry(busyHoldMs);
26788
+ this.scheduleBusyExpiry(busyWakeMs);
26568
26789
  }
26569
26790
  const changed = forceEmit || evState.id !== this.currentStateId || !shallowSameModal(ev, this.currentEval) || !shallowSameControls(ev, this.currentEval);
26570
26791
  if (this.pickerInProgress) this.tryAdvancePicker(screen);
@@ -27467,6 +27688,8 @@ var CliProviderInstance = class {
27467
27688
  historyWriter;
27468
27689
  runtimeMessages = [];
27469
27690
  lastPersistedHistoryMessages = [];
27691
+ lastAcknowledgedUserInputAt = 0;
27692
+ externalBusyIdleFingerprint = "";
27470
27693
  lastNativeSourceCanonicalCheckAt = 0;
27471
27694
  lastNativeSourceCanonicalCacheKey = void 0;
27472
27695
  cachedSqliteDb = null;
@@ -27595,7 +27818,11 @@ var CliProviderInstance = class {
27595
27818
  typeof adapterStatus?.providerSessionId === "string" ? adapterStatus.providerSessionId : ""
27596
27819
  );
27597
27820
  const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
27598
- const visibleStatus = parseErrorMessage || parsedStatus?.status === "error" ? "error" : autoApproveActive ? "generating" : adapterStatus.status;
27821
+ let visibleStatus = parseErrorMessage || parsedStatus?.status === "error" ? "error" : autoApproveActive ? "generating" : adapterStatus.status;
27822
+ const externalNativeFinal = this.getExternalNativeFinalReconciliation(parsedStatus?.messages, adapterStatus);
27823
+ if (externalNativeFinal && isCliGeneratingLikeStatus(visibleStatus)) {
27824
+ visibleStatus = "idle";
27825
+ }
27599
27826
  const runtime = this.adapter.getRuntimeMetadata();
27600
27827
  this.maybeAppendRuntimeRecoveryMessage(runtime);
27601
27828
  let parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
@@ -27757,7 +27984,22 @@ var CliProviderInstance = class {
27757
27984
  };
27758
27985
  }
27759
27986
  updateSettings(newSettings) {
27760
- this.settings = { ...newSettings };
27987
+ const runtimeMeshSettings = {};
27988
+ for (const key of [
27989
+ "meshNodeFor",
27990
+ "meshNodeId",
27991
+ "meshActiveTaskId",
27992
+ "meshCoordinatorFor",
27993
+ "meshCoordinatorDaemonId",
27994
+ "meshCoordinatorNodeId",
27995
+ "spawnedSessionVisibility",
27996
+ "launchedByCoordinator"
27997
+ ]) {
27998
+ if (this.settings[key] !== void 0 && newSettings[key] === void 0) {
27999
+ runtimeMeshSettings[key] = this.settings[key];
28000
+ }
28001
+ }
28002
+ this.settings = { ...newSettings, ...runtimeMeshSettings };
27761
28003
  this.adapter.updateRuntimeSettings?.(this.settings);
27762
28004
  this.monitor.updateConfig({
27763
28005
  approvalAlert: this.settings.approvalAlert !== false,
@@ -27848,6 +28090,8 @@ var CliProviderInstance = class {
27848
28090
  const content = typeof input === "string" ? input.trim() : buildCliStructuredInputPrompt(input).trim();
27849
28091
  if (!content) return;
27850
28092
  const receivedAt = Date.now();
28093
+ this.lastAcknowledgedUserInputAt = receivedAt;
28094
+ this.externalBusyIdleFingerprint = "";
27851
28095
  const dedupKey = `user_input_ack:${crypto4.createHash("sha256").update(`${this.instanceId}:${content}:${receivedAt}`).digest("hex").slice(0, 24)}`;
27852
28096
  this.appendRuntimeMessage(buildChatMessage({
27853
28097
  role: "user",
@@ -27996,6 +28240,50 @@ var CliProviderInstance = class {
27996
28240
  const evidence = this.completionFinalAssistantEvidence(parsedMessages);
27997
28241
  return extractFinalSummaryFromMessages(evidence.messages);
27998
28242
  }
28243
+ externalNativeFinalFingerprint(evidence) {
28244
+ const messages = Array.isArray(evidence.messages) ? evidence.messages : [];
28245
+ const visibleMessages = messages.filter((message) => isUserFacingChatMessage(message));
28246
+ const lastVisible = visibleMessages[visibleMessages.length - 1];
28247
+ const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
28248
+ const receivedAt = lastVisible ? getMessageTime(lastVisible) : 0;
28249
+ const probe = this.lastExternalCompletionProbe;
28250
+ return crypto4.createHash("sha256").update([
28251
+ this.type,
28252
+ this.providerSessionId || "",
28253
+ probe?.sourcePath || "",
28254
+ String(probe?.sourceMtimeMs || 0),
28255
+ String(receivedAt || 0),
28256
+ content.slice(-500)
28257
+ ].join("\0")).digest("hex").slice(0, 24);
28258
+ }
28259
+ getExternalNativeFinalReconciliation(parsedMessages, adapterStatus) {
28260
+ const rawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
28261
+ if (!isCliGeneratingLikeStatus(rawStatus)) return null;
28262
+ if (hasNonEmptyCliModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal)) return null;
28263
+ const evidence = this.completionFinalAssistantEvidence(parsedMessages);
28264
+ if (evidence.source !== "external-native" || !evidence.present) return null;
28265
+ const messages = Array.isArray(evidence.messages) ? evidence.messages : [];
28266
+ const visibleMessages = messages.filter((message) => isUserFacingChatMessage(message));
28267
+ const lastVisible = visibleMessages[visibleMessages.length - 1];
28268
+ const lastMessageAt = lastVisible ? getMessageTime(lastVisible) : 0;
28269
+ const sourceMtimeMs = Number(this.lastExternalCompletionProbe?.sourceMtimeMs || 0);
28270
+ const minEvidenceAt = Math.max(
28271
+ this.startedAt > 0 ? this.startedAt - 5e3 : 0,
28272
+ this.generatingStartedAt > 0 ? this.generatingStartedAt - 5e3 : 0,
28273
+ this.lastAcknowledgedUserInputAt > 0 ? this.lastAcknowledgedUserInputAt - 1e3 : 0
28274
+ );
28275
+ if (minEvidenceAt > 0 && lastMessageAt > 0 && lastMessageAt < minEvidenceAt && sourceMtimeMs < minEvidenceAt) {
28276
+ return null;
28277
+ }
28278
+ const finalSummary = extractFinalSummaryFromMessages(evidence.messages);
28279
+ if (!finalSummary) return null;
28280
+ const fingerprint = this.externalNativeFinalFingerprint(evidence);
28281
+ if (fingerprint === this.externalBusyIdleFingerprint) {
28282
+ return { fingerprint, finalSummary, evidence };
28283
+ }
28284
+ this.externalBusyIdleFingerprint = fingerprint;
28285
+ return { fingerprint, finalSummary, evidence };
28286
+ }
27999
28287
  buildCompletedFinalizationDiagnostic(args) {
28000
28288
  let parsed = null;
28001
28289
  let parseError;
@@ -28066,17 +28354,18 @@ var CliProviderInstance = class {
28066
28354
  if (typeof adapterAny?.responseBuffer === "string" && adapterAny.responseBuffer.trim()) return false;
28067
28355
  return true;
28068
28356
  }
28069
- getCompletedFinalizationBlock(latestVisibleStatus, pending) {
28357
+ getCompletedFinalizationBlock(latestVisibleStatus, pending, opts) {
28070
28358
  if (latestVisibleStatus !== "idle") return { reason: `status:${latestVisibleStatus}`, terminal: true };
28071
28359
  const adapterAny = this.adapter;
28072
28360
  const approvalResolvedIdle = pending.previousStatus === "waiting_approval";
28073
- if (!approvalResolvedIdle) {
28361
+ const externalNativeFinal = opts?.externalNativeFinal || null;
28362
+ if (!approvalResolvedIdle && !externalNativeFinal) {
28074
28363
  if (adapterAny?.isWaitingForResponse === true) return { reason: "adapter_waiting_for_response", terminal: true };
28075
28364
  if (adapterAny?.currentTurnScope) return { reason: "adapter_turn_scope_active", terminal: true };
28076
28365
  if (this.hasAdapterPendingResponse()) return { reason: "adapter_pending_response", terminal: true };
28077
28366
  }
28078
28367
  const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
28079
- if (typeof partial === "string" && partial.trim()) return { reason: "partial_response_pending", terminal: true };
28368
+ if (!externalNativeFinal && typeof partial === "string" && partial.trim()) return { reason: "partial_response_pending", terminal: true };
28080
28369
  let parsed;
28081
28370
  try {
28082
28371
  parsed = this.adapter.getScriptParsedStatus();
@@ -28086,6 +28375,7 @@ var CliProviderInstance = class {
28086
28375
  const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
28087
28376
  if (parsedStatus !== "idle") {
28088
28377
  const adapterStatus = this.adapter.getStatus({ allowParse: false });
28378
+ if (externalNativeFinal && isCliGeneratingLikeStatus(parsedStatus)) return null;
28089
28379
  if (this.shouldSuppressStaleParsedBusyStatus(parsed, adapterStatus)) return null;
28090
28380
  return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
28091
28381
  }
@@ -28138,14 +28428,15 @@ var CliProviderInstance = class {
28138
28428
  }
28139
28429
  const latestStatus = this.adapter.getStatus({ allowParse: false });
28140
28430
  const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
28141
- const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
28431
+ const externalNativeFinal = this.getExternalNativeFinalReconciliation(void 0, latestStatus);
28432
+ const latestVisibleStatus = externalNativeFinal && isCliGeneratingLikeStatus(latestStatus.status) ? "idle" : latestAutoApproveActive ? "generating" : latestStatus.status;
28142
28433
  if (latestVisibleStatus !== "idle") {
28143
28434
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
28144
28435
  this.completedDebouncePending = null;
28145
28436
  this.completedDebounceTimer = null;
28146
28437
  return;
28147
28438
  }
28148
- const block2 = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
28439
+ const block2 = this.getCompletedFinalizationBlock(latestVisibleStatus, pending, { externalNativeFinal });
28149
28440
  if (block2) {
28150
28441
  const blockReason = block2.reason;
28151
28442
  const waitedMs = Date.now() - pending.firstObservedAt;
@@ -28186,7 +28477,18 @@ var CliProviderInstance = class {
28186
28477
  chatTitle: pending.chatTitle,
28187
28478
  duration: pending.duration,
28188
28479
  timestamp: pending.timestamp,
28189
- finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages)
28480
+ finalSummary: externalNativeFinal?.finalSummary || this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
28481
+ ...externalNativeFinal ? {
28482
+ completionDiagnostic: {
28483
+ providerType: this.type,
28484
+ sessionId: this.instanceId,
28485
+ providerSessionId: this.providerSessionId || null,
28486
+ reconciliationReason: "external_native_final_assistant_while_adapter_busy",
28487
+ finalAssistantPresent: true,
28488
+ finalAssistantEvidenceSource: externalNativeFinal.evidence.source,
28489
+ externalFinalFingerprint: externalNativeFinal.fingerprint
28490
+ }
28491
+ } : {}
28190
28492
  });
28191
28493
  this.completedDebouncePending = null;
28192
28494
  this.completedDebounceTimer = null;
@@ -28242,7 +28544,8 @@ var CliProviderInstance = class {
28242
28544
  const parsedStatus = null;
28243
28545
  const rawStatus = adapterStatus.status;
28244
28546
  const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, now);
28245
- const newStatus = autoApproveActive ? "generating" : rawStatus;
28547
+ const externalNativeFinal = this.getExternalNativeFinalReconciliation(void 0, adapterStatus);
28548
+ const newStatus = externalNativeFinal && isCliGeneratingLikeStatus(rawStatus) ? "idle" : autoApproveActive ? "generating" : rawStatus;
28246
28549
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
28247
28550
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
28248
28551
  const partial = this.adapter.getPartialResponse();
@@ -30248,15 +30551,29 @@ function colorize(color, text) {
30248
30551
  const fn = chalkApi?.[color];
30249
30552
  return typeof fn === "function" ? fn(text) : text;
30250
30553
  }
30251
- var COORDINATOR_DELEGATED_ENV_UNSETS = {
30252
- ADHDEV_INLINE_MESH: "",
30253
- ADHDEV_MCP_TRANSPORT: "",
30254
- ADHDEV_MESH_ID: "",
30255
- HERMES_EPHEMERAL_SYSTEM_PROMPT: ""
30256
- };
30554
+ var DEFAULT_COORDINATOR_DELEGATED_ENV_UNSETS = [
30555
+ "ADHDEV_INLINE_MESH",
30556
+ "ADHDEV_MCP_TRANSPORT",
30557
+ "ADHDEV_MESH_ID",
30558
+ "HERMES_EPHEMERAL_SYSTEM_PROMPT"
30559
+ ];
30257
30560
  function hasCliArg(args, flag) {
30258
30561
  return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
30259
30562
  }
30563
+ function hasConfigOverride(args, key) {
30564
+ for (let index = 0; index < args.length; index += 1) {
30565
+ const arg = args[index];
30566
+ const next = args[index + 1];
30567
+ if ((arg === "-c" || arg === "--config") && typeof next === "string") {
30568
+ if (next === key || next.startsWith(`${key}=`) || next.startsWith(`${key}.`)) return true;
30569
+ }
30570
+ if (arg.startsWith("--config=")) {
30571
+ const value = arg.slice("--config=".length);
30572
+ if (value === key || value.startsWith(`${key}=`) || value.startsWith(`${key}.`)) return true;
30573
+ }
30574
+ }
30575
+ return false;
30576
+ }
30260
30577
  function ensureEmptyDelegatedMcpConfig(workspace) {
30261
30578
  const baseDir = path23.join(os17.tmpdir(), "adhdev-delegated-agent-empty-mcp");
30262
30579
  mkdirSync11(baseDir, { recursive: true });
@@ -30266,11 +30583,30 @@ function ensureEmptyDelegatedMcpConfig(workspace) {
30266
30583
  return filePath;
30267
30584
  }
30268
30585
  function buildCoordinatorDelegatedCliLaunchOptions(input) {
30269
- const cliType = String(input.cliType || "").trim();
30270
30586
  const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
30271
- const env = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
30272
- if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
30273
- cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
30587
+ const env = { ...input.env || {} };
30588
+ const envUnsets = new Set(DEFAULT_COORDINATOR_DELEGATED_ENV_UNSETS);
30589
+ for (const key of input.isolation?.env?.unset || []) {
30590
+ if (typeof key === "string" && key.trim()) envUnsets.add(key.trim());
30591
+ }
30592
+ for (const key of envUnsets) env[key] = "";
30593
+ for (const rule of input.isolation?.args || []) {
30594
+ if (!rule || typeof rule !== "object") continue;
30595
+ if (rule.mode === "empty_mcp_config") {
30596
+ if (rule.flag && !hasCliArg(cliArgs, rule.flag)) {
30597
+ cliArgs.unshift(rule.flag, ensureEmptyDelegatedMcpConfig(input.workspace));
30598
+ }
30599
+ if (rule.strictFlag && !hasCliArg(cliArgs, rule.strictFlag)) {
30600
+ cliArgs.unshift(rule.strictFlag);
30601
+ }
30602
+ continue;
30603
+ }
30604
+ if (rule.mode === "config_override") {
30605
+ const key = String(rule.dedupeKey || rule.key || "").trim();
30606
+ const flag = String(rule.flag || "").trim();
30607
+ if (!key || !flag || hasConfigOverride(cliArgs, key)) continue;
30608
+ cliArgs.unshift(flag, `${rule.key}=${rule.value}`);
30609
+ }
30274
30610
  }
30275
30611
  return { cliArgs, env };
30276
30612
  }
@@ -30933,22 +31269,25 @@ Run 'adhdev doctor' for detailed diagnostics.`
30933
31269
  const dir = resolved.path;
30934
31270
  const launchSource = resolved.source;
30935
31271
  if (!cliType) throw new Error("cliType required");
31272
+ const providerType = this.providerLoader.resolveAlias(cliType);
31273
+ const provLookup = this.providerLoader.getMeta(providerType);
30936
31274
  const settingsOverride = args?.settings && typeof args.settings === "object" ? args.settings : void 0;
30937
31275
  const delegatedLaunch = settingsOverride?.launchedByCoordinator === true ? buildCoordinatorDelegatedCliLaunchOptions({
30938
31276
  cliType,
30939
31277
  workspace: dir,
30940
31278
  cliArgs: args?.cliArgs,
30941
- env: args?.env
31279
+ env: args?.env,
31280
+ isolation: provLookup?.meshCoordinator?.delegatedWorkerIsolation
30942
31281
  }) : null;
30943
- const provLookup = this.providerLoader.getMeta(this.providerLoader.resolveAlias(cliType));
30944
- const provTrust = provLookup?._sourceTrust;
31282
+ const provMeta = provLookup;
31283
+ const provTrust = provMeta?._sourceTrust;
30945
31284
  if (provTrust === "external-untrusted" && args?.confirmExternalUntrusted !== true) {
30946
31285
  return {
30947
31286
  success: false,
30948
31287
  error: "untrusted_external_provider",
30949
31288
  provider: {
30950
31289
  type: provLookup?.type ?? cliType,
30951
- sourceName: provLookup?._sourceName ?? null,
31290
+ sourceName: provMeta?._sourceName ?? null,
30952
31291
  trust: provTrust
30953
31292
  },
30954
31293
  hint: "Resend launch_cli with confirmExternalUntrusted=true after the user explicitly approves running JavaScript from this 3rd-party source."
@@ -31420,7 +31759,10 @@ function validateMeshCoordinator(raw, errors) {
31420
31759
  if (meshCoordinator.reason !== void 0 && (typeof meshCoordinator.reason !== "string" || !meshCoordinator.reason.trim())) {
31421
31760
  errors.push("meshCoordinator.reason must be a non-empty string when provided");
31422
31761
  }
31423
- const mcpConfig = meshCoordinator.mcpConfig;
31762
+ validateMeshCoordinatorMcpConfig(meshCoordinator.mcpConfig, errors);
31763
+ validateMeshCoordinatorDelegatedWorkerIsolation(meshCoordinator.delegatedWorkerIsolation, errors);
31764
+ }
31765
+ function validateMeshCoordinatorMcpConfig(mcpConfig, errors) {
31424
31766
  if (mcpConfig === void 0) return;
31425
31767
  if (!mcpConfig || typeof mcpConfig !== "object" || Array.isArray(mcpConfig)) {
31426
31768
  errors.push("meshCoordinator.mcpConfig must be an object");
@@ -31461,6 +31803,56 @@ function validateMeshCoordinator(raw, errors) {
31461
31803
  }
31462
31804
  }
31463
31805
  }
31806
+ function validateMeshCoordinatorDelegatedWorkerIsolation(raw, errors) {
31807
+ if (raw === void 0) return;
31808
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
31809
+ errors.push("meshCoordinator.delegatedWorkerIsolation must be an object");
31810
+ return;
31811
+ }
31812
+ const isolation = raw;
31813
+ const env = isolation.env;
31814
+ if (env !== void 0) {
31815
+ if (!env || typeof env !== "object" || Array.isArray(env)) {
31816
+ errors.push("meshCoordinator.delegatedWorkerIsolation.env must be an object");
31817
+ } else {
31818
+ const unset = env.unset;
31819
+ if (unset !== void 0 && (!Array.isArray(unset) || unset.some((key) => typeof key !== "string" || !key.trim()))) {
31820
+ errors.push("meshCoordinator.delegatedWorkerIsolation.env.unset must be an array of non-empty strings");
31821
+ }
31822
+ }
31823
+ }
31824
+ const args = isolation.args;
31825
+ if (args === void 0) return;
31826
+ if (!Array.isArray(args)) {
31827
+ errors.push("meshCoordinator.delegatedWorkerIsolation.args must be an array");
31828
+ return;
31829
+ }
31830
+ for (const [index, rule] of args.entries()) {
31831
+ const prefix = `meshCoordinator.delegatedWorkerIsolation.args[${index}]`;
31832
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
31833
+ errors.push(`${prefix} must be an object`);
31834
+ continue;
31835
+ }
31836
+ const item = rule;
31837
+ const mode = item.mode;
31838
+ if (mode !== "empty_mcp_config" && mode !== "config_override") {
31839
+ errors.push(`${prefix}.mode must be one of: empty_mcp_config, config_override`);
31840
+ continue;
31841
+ }
31842
+ for (const key of mode === "empty_mcp_config" ? ["flag"] : ["flag", "key", "value"]) {
31843
+ const value = item[key];
31844
+ if (typeof value !== "string" || !value.trim()) {
31845
+ errors.push(`${prefix}.${key} must be a non-empty string`);
31846
+ }
31847
+ }
31848
+ for (const key of ["strictFlag", "dedupeKey"]) {
31849
+ const value = item[key];
31850
+ if (value !== void 0 && (typeof value !== "string" || !value.trim())) {
31851
+ errors.push(`${prefix}.${key} must be a non-empty string when provided`);
31852
+ }
31853
+ }
31854
+ }
31855
+ }
31464
31856
  function validateControl(control, errors) {
31465
31857
  if (!control || typeof control !== "object") {
31466
31858
  errors.push("controls: each control must be an object");
@@ -37122,9 +37514,14 @@ function readCachedInlineMeshActiveSessionDetails(node) {
37122
37514
  node?.provider_type
37123
37515
  ),
37124
37516
  state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
37517
+ chatStatus: readStringValue(fallbackSession.chatStatus, fallbackSession.chat_status),
37125
37518
  lifecycle: readStringValue(fallbackSession.lifecycle),
37126
37519
  title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
37127
37520
  workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
37521
+ role: readStringValue(fallbackSession.role) ?? null,
37522
+ isSelfCoordinator: fallbackSession.isSelfCoordinator === true || fallbackSession.is_self_coordinator === true,
37523
+ createdAt: readStringValue(fallbackSession.createdAt, fallbackSession.created_at) ?? null,
37524
+ startedAt: readStringValue(fallbackSession.startedAt, fallbackSession.started_at) ?? null,
37128
37525
  lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
37129
37526
  recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
37130
37527
  isCached: true
@@ -37265,15 +37662,26 @@ async function hydrateInlineMeshDirectTruth(args) {
37265
37662
  };
37266
37663
  }
37267
37664
  function summarizeMeshSessionRecord(record) {
37665
+ const meta = readObjectRecord(record?.meta);
37666
+ const isSelfCoordinator = Boolean(readStringValue(meta.meshCoordinatorFor));
37667
+ const chatStatus = readStringValue(record?.chatStatus, record?.activeChat?.status, meta.chatStatus, meta.sessionStatus);
37668
+ const state = readLiveMeshSessionState(record);
37669
+ const statusNote = isSelfCoordinator && (!chatStatus || chatStatus === "idle" || state === "idle") ? "Coordinator self status is sampled from the session host and may read idle while the coordinator is generating this response." : null;
37268
37670
  return {
37269
37671
  sessionId: readStringValue(record?.sessionId) || "unknown",
37270
37672
  providerType: readStringValue(record?.providerType),
37271
- state: readLiveMeshSessionState(record),
37673
+ state,
37674
+ chatStatus,
37272
37675
  lifecycle: readStringValue(record?.lifecycle),
37273
37676
  surfaceKind: getSessionHostSurfaceKind(record),
37274
- recoveryState: readStringValue(record?.meta?.runtimeRecoveryState) ?? null,
37677
+ recoveryState: readStringValue(meta.runtimeRecoveryState) ?? null,
37275
37678
  workspace: readStringValue(record?.workspace) ?? null,
37276
37679
  title: readStringValue(record?.displayName, record?.workspaceLabel) ?? null,
37680
+ role: isSelfCoordinator ? "coordinator" : readStringValue(meta.meshRole, meta.role) ?? null,
37681
+ isSelfCoordinator,
37682
+ statusNote,
37683
+ createdAt: toIsoTimestamp(record?.createdAt ?? record?.created_at),
37684
+ startedAt: toIsoTimestamp(record?.startedAt ?? record?.started_at ?? record?.spawnedAtMs ?? record?.spawned_at_ms),
37277
37685
  lastActivityAt: toIsoTimestamp(record?.updatedAt ?? record?.lastActivityAt ?? record?.last_activity_at),
37278
37686
  isCached: false
37279
37687
  };
@@ -38297,6 +38705,15 @@ var DaemonCommandRouter = class {
38297
38705
  this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
38298
38706
  return next;
38299
38707
  }
38708
+ getCachedInlineMeshNodes() {
38709
+ const nodes = [];
38710
+ for (const mesh of this.inlineMeshCache.values()) {
38711
+ if (Array.isArray(mesh?.nodes)) {
38712
+ nodes.push(...mesh.nodes);
38713
+ }
38714
+ }
38715
+ return nodes;
38716
+ }
38300
38717
  getCachedInlineMesh(meshId, inlineMesh) {
38301
38718
  if (inlineMesh && typeof inlineMesh === "object") {
38302
38719
  return this.warmInlineMeshCache(meshId, inlineMesh);
@@ -38345,6 +38762,7 @@ var DaemonCommandRouter = class {
38345
38762
  }
38346
38763
  invalidateAggregateMeshStatus(meshId) {
38347
38764
  this.aggregateMeshStatusCache.delete(meshId);
38765
+ this.deps.onMeshStateChange?.(meshId);
38348
38766
  }
38349
38767
  async requireMeshHostMutationOwner(meshId, inlineMesh, operation) {
38350
38768
  const meshRecord = await this.getMeshForCommand(meshId, inlineMesh, { preferInline: true });
@@ -49837,6 +50255,7 @@ async function initDaemonComponents(config) {
49837
50255
  },
49838
50256
  onIdeConnected: () => poller?.start(),
49839
50257
  onStatusChange: config.onStatusChange,
50258
+ onMeshStateChange: config.onMeshStateChange,
49840
50259
  onPostChatCommand: config.onPostChatCommand,
49841
50260
  sessionHostControl: config.sessionHostControl,
49842
50261
  statusInstanceId: config.statusInstanceId,
@@ -50429,6 +50848,7 @@ export {
50429
50848
  probeCdpPort,
50430
50849
  queuePendingMeshCoordinatorEvent,
50431
50850
  readSession3 as readAntigravityCliSession,
50851
+ readCachedInlineMeshActiveSessionDetails,
50432
50852
  readChatHistory,
50433
50853
  readSession as readClaudeCliSession,
50434
50854
  readSession2 as readCodexCliSession,