@adhdev/daemon-core 0.9.82-rc.360 → 0.9.82-rc.361

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.
package/dist/index.mjs CHANGED
@@ -311,10 +311,10 @@ function readInjected(value) {
311
311
  }
312
312
  function getDaemonBuildInfo() {
313
313
  if (cached) return cached;
314
- const commit = readInjected(true ? "ea4a65e3d2be71c4aba09dbb000df08d2111759e" : void 0) ?? "unknown";
315
- const commitShort = readInjected(true ? "ea4a65e3" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
- const version = readInjected(true ? "0.9.82-rc.360" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
- const builtAt = readInjected(true ? "2026-06-23T08:23:05.522Z" : void 0);
314
+ const commit = readInjected(true ? "4c8bb331f13862335f134d8a1b97fa17eaca01e0" : void 0) ?? "unknown";
315
+ const commitShort = readInjected(true ? "4c8bb331" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
+ const version = readInjected(true ? "0.9.82-rc.361" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
+ const builtAt = readInjected(true ? "2026-06-23T11:56:37.654Z" : void 0);
318
318
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
319
319
  return cached;
320
320
  }
@@ -14161,7 +14161,7 @@ function forwardUnresolvedDelegateEvent(components, routing, event) {
14161
14161
  }
14162
14162
  function ackUnresolvedDelegateForwardByFingerprint(coordinatorDaemonId, eventName, payload) {
14163
14163
  const match = peekUnresolvedDelegateForwards().find(
14164
- (entry) => entry.coordinatorDaemonId === coordinatorDaemonId && readNonEmptyString2(entry.payload.event) === eventName && readNonEmptyString2(entry.payload.targetSessionId || entry.payload.sessionId || entry.payload.instanceId) === readNonEmptyString2(payload.targetSessionId || payload.sessionId || payload.instanceId) && readNonEmptyString2(entry.payload.workspace) === readNonEmptyString2(payload.workspace)
14164
+ (entry) => daemonIdsEquivalent(entry.coordinatorDaemonId, coordinatorDaemonId) && readNonEmptyString2(entry.payload.event) === eventName && readNonEmptyString2(entry.payload.targetSessionId || entry.payload.sessionId || entry.payload.instanceId) === readNonEmptyString2(payload.targetSessionId || payload.sessionId || payload.instanceId) && readNonEmptyString2(entry.payload.workspace) === readNonEmptyString2(payload.workspace)
14165
14165
  );
14166
14166
  if (match) ackUnresolvedDelegateForward(match.id);
14167
14167
  }
@@ -15821,1010 +15821,1599 @@ var init_external_sources = __esm({
15821
15821
  }
15822
15822
  });
15823
15823
 
15824
- // src/cli-adapters/pty-transport.ts
15825
- var pty_transport_exports = {};
15826
- __export(pty_transport_exports, {
15827
- NodePtyTransportFactory: () => NodePtyTransportFactory
15828
- });
15829
- import * as os13 from "os";
15830
- function loadNodePty() {
15831
- if (cachedPty !== void 0) return cachedPty;
15832
- try {
15833
- cachedPty = __require("node-pty");
15834
- ensureNodePtySpawnHelperPermissions();
15835
- } catch {
15836
- cachedPty = null;
15837
- }
15838
- return cachedPty;
15824
+ // src/providers/spec/fsm-types.ts
15825
+ function isV4Spec(raw) {
15826
+ return !!raw && typeof raw === "object" && raw.$schema === "adhdev:cli/spec@4";
15839
15827
  }
15840
- var cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory;
15841
- var init_pty_transport = __esm({
15842
- "src/cli-adapters/pty-transport.ts"() {
15843
- "use strict";
15844
- init_spawn_env();
15845
- init_resolve_executable();
15846
- NodePtyRuntimeTransport = class {
15847
- constructor(handle) {
15848
- this.handle = handle;
15849
- }
15850
- ready = Promise.resolve();
15851
- terminalQueriesHandled = false;
15852
- get pid() {
15853
- return this.handle.pid;
15854
- }
15855
- write(data) {
15856
- this.handle.write(data);
15857
- }
15858
- resize(cols, rows) {
15859
- this.handle.resize(cols, rows);
15860
- }
15861
- kill() {
15862
- this.handle.kill();
15863
- }
15864
- getMetadata() {
15865
- return null;
15866
- }
15867
- onData(callback) {
15868
- this.handle.onData(callback);
15869
- }
15870
- onExit(callback) {
15871
- this.handle.onExit(callback);
15872
- }
15873
- };
15874
- NodePtyTransportFactory = class {
15875
- spawn(command, args, options) {
15876
- const pty = loadNodePty();
15877
- if (!pty) throw new Error("node-pty is not installed");
15878
- let cwd = options.cwd;
15879
- if (cwd) {
15880
- try {
15881
- const fs32 = __require("fs");
15882
- const stat2 = fs32.statSync(cwd);
15883
- if (!stat2.isDirectory()) cwd = os13.homedir();
15884
- } catch {
15885
- cwd = os13.homedir();
15886
- }
15887
- }
15888
- const handle = pty.spawn(resolveWin32Executable(command), args, {
15889
- name: "xterm-256color",
15890
- cols: options.cols,
15891
- rows: options.rows,
15892
- cwd,
15893
- env: options.env
15894
- });
15895
- return new NodePtyRuntimeTransport(handle);
15896
- }
15897
- };
15898
- }
15899
- });
15900
-
15901
- // src/providers/sdk/v1/builders/cli/visible-region.ts
15902
- function compile(re, flags) {
15903
- try {
15904
- return new RegExp(re, flags ?? "");
15905
- } catch (e) {
15906
- throw new Error(`Invalid visible-region anchor regex /${re}/${flags ?? ""}: ${e.message}`);
15907
- }
15828
+ function initialState(spec) {
15829
+ return spec.states.find((s2) => s2.initial) ?? spec.states[0];
15908
15830
  }
15909
- function findAllMatches(re, text) {
15910
- const results = [];
15911
- const iter = new RegExp(re.source, re.flags.replace("g", "") + "g");
15912
- let m;
15913
- while ((m = iter.exec(text)) !== null) {
15914
- results.push(m);
15915
- if (m[0].length === 0) iter.lastIndex += 1;
15916
- }
15917
- return results;
15831
+ function stateById(spec, id) {
15832
+ return spec.states.find((s2) => s2.id === id);
15918
15833
  }
15919
- function applyVisibleRegion(spec, text) {
15920
- if (!text) return text;
15921
- switch (spec.scope) {
15922
- case "screen":
15923
- case "buffer":
15924
- return text;
15925
- case "tail": {
15926
- const limit = spec.tailChars ?? 4e3;
15927
- if (text.length <= limit) return text;
15928
- return text.slice(-limit);
15929
- }
15930
- case "between-anchors": {
15931
- const anchors = spec.anchors;
15932
- if (!anchors?.top && !anchors?.bottom) return text;
15933
- const selectLast = (spec.selectAnchor ?? "first") === "last";
15934
- let topEnd = 0;
15935
- if (anchors.top) {
15936
- const topRe = compile(anchors.top.pattern, anchors.top.flags);
15937
- const topMatches = findAllMatches(topRe, text);
15938
- if (topMatches.length === 0) {
15939
- return text;
15940
- }
15941
- const chosen = selectLast ? topMatches[topMatches.length - 1] : topMatches[0];
15942
- topEnd = chosen.index + chosen[0].length;
15943
- }
15944
- let bottomStart = text.length;
15945
- if (anchors.bottom) {
15946
- const bottomRe = compile(anchors.bottom.pattern, anchors.bottom.flags);
15947
- const searchFrom = topEnd;
15948
- const suffix = text.slice(searchFrom);
15949
- const bottomMatches = findAllMatches(bottomRe, suffix);
15950
- if (bottomMatches.length === 0) {
15951
- return text;
15952
- }
15953
- const chosen = selectLast ? bottomMatches[bottomMatches.length - 1] : bottomMatches[0];
15954
- bottomStart = searchFrom + chosen.index;
15955
- }
15956
- if (topEnd >= bottomStart) {
15957
- return text;
15958
- }
15959
- return text.slice(topEnd, bottomStart);
15960
- }
15961
- default:
15962
- return text;
15963
- }
15834
+ function outgoingTransitions(spec, stateId) {
15835
+ const matches = spec.transitions.filter((t) => {
15836
+ if (t.from === "*") return true;
15837
+ if (Array.isArray(t.from)) return t.from.includes(stateId);
15838
+ return t.from === stateId;
15839
+ });
15840
+ return matches.map((t, i) => ({ t, i })).sort((a, b) => (b.t.priority ?? 0) - (a.t.priority ?? 0) || a.i - b.i).map((x) => x.t);
15964
15841
  }
15965
- var init_visible_region = __esm({
15966
- "src/providers/sdk/v1/builders/cli/visible-region.ts"() {
15842
+ function statusForState(state) {
15843
+ if (state.status) return state.status;
15844
+ if (state.modal) return "approval";
15845
+ if (state.id === "busy" || state.id === "generating") return "generating";
15846
+ return "idle";
15847
+ }
15848
+ function modalKindForState(state) {
15849
+ if (state.modal_kind) return state.modal_kind;
15850
+ if (state.modal) return "approval";
15851
+ return null;
15852
+ }
15853
+ var init_fsm_types = __esm({
15854
+ "src/providers/spec/fsm-types.ts"() {
15967
15855
  "use strict";
15968
15856
  }
15969
15857
  });
15970
15858
 
15971
- // src/providers/sdk/v1/builders/cli/detect-status.ts
15972
- function compile2(re, flags) {
15859
+ // src/providers/spec/fsm-loader.ts
15860
+ var fsm_loader_exports = {};
15861
+ __export(fsm_loader_exports, {
15862
+ loadFsmSpec: () => loadFsmSpec,
15863
+ validateFsmSpec: () => validateFsmSpec
15864
+ });
15865
+ import * as fs10 from "fs";
15866
+ function loadFsmSpec(sourcePath) {
15867
+ let raw;
15973
15868
  try {
15974
- return new RegExp(re, flags ?? "");
15975
- } catch (e) {
15976
- throw new Error(`Invalid regex /${re}/${flags ?? ""}: ${e.message}`);
15869
+ raw = JSON.parse(fs10.readFileSync(sourcePath, "utf8"));
15870
+ } catch (err) {
15871
+ return { ok: false, errors: [`Failed to read/parse spec: ${err.message}`], sourcePath };
15977
15872
  }
15873
+ const errors = validateFsmSpec(raw);
15874
+ if (errors.length) return { ok: false, errors, sourcePath };
15875
+ return { ok: true, spec: raw, sourcePath };
15978
15876
  }
15979
- function takeTail(text, lines) {
15980
- if (!text) return "";
15981
- const split = text.split("\n");
15982
- if (split.length <= lines) return text;
15983
- return split.slice(-lines).join("\n");
15984
- }
15985
- function scopeText(input, scope, windowLines) {
15986
- const lines = windowLines && windowLines > 0 ? windowLines : 8;
15987
- switch (scope) {
15988
- case "whole-screen":
15989
- return input.screenText ?? "";
15990
- case "recent-buffer":
15991
- return input.tail ?? "";
15992
- case "last-n-lines":
15993
- case "live-frame-tail":
15994
- case void 0:
15995
- default:
15996
- return takeTail(input.screenText ?? "", lines);
15877
+ function validateFsmSpec(raw) {
15878
+ const errs = [];
15879
+ if (!isV4Spec(raw)) return ['$schema must be "adhdev:cli/spec@4"'];
15880
+ const spec = raw;
15881
+ if (!spec.id) errs.push("id is required");
15882
+ if (!spec.binary) errs.push("binary is required");
15883
+ if (!spec.send_message?.submit_key) errs.push("send_message.submit_key is required");
15884
+ if (spec.pre_launch_trust !== void 0) {
15885
+ const t = spec.pre_launch_trust;
15886
+ if (!t || typeof t !== "object" || Array.isArray(t)) {
15887
+ errs.push("pre_launch_trust must be an object");
15888
+ } else {
15889
+ if (typeof t.settings_path !== "string" || !t.settings_path) errs.push("pre_launch_trust.settings_path is required");
15890
+ if (typeof t.key !== "string" || !t.key) errs.push("pre_launch_trust.key is required");
15891
+ }
15997
15892
  }
15998
- }
15999
- function compileSpinnerMatchers(spec) {
16000
- return spec.patterns.map((p) => compile2(p.regex, p.flags ?? "i"));
16001
- }
16002
- function compileSettledPromptMatchers(spec) {
16003
- const prompt = compile2(spec.regex, spec.flags ?? "m");
16004
- const footers = (spec.withFooter ?? []).map((f) => {
16005
- if (f.kind === "regex") {
16006
- const re = compile2(f.pattern, f.flags ?? "i");
16007
- return { test: (s2) => re.test(s2) };
15893
+ if (spec.refocus_when_stalled_ms !== void 0) {
15894
+ if (typeof spec.refocus_when_stalled_ms !== "number" || !(spec.refocus_when_stalled_ms > 0)) {
15895
+ errs.push("refocus_when_stalled_ms must be a positive number");
15896
+ } else if (!Array.isArray(spec.send_on_spawn) || spec.send_on_spawn.length === 0) {
15897
+ errs.push("refocus_when_stalled_ms requires send_on_spawn (the wake sequence to re-inject)");
16008
15898
  }
16009
- const needle = f.pattern.toLowerCase();
16010
- return { test: (s2) => s2.toLowerCase().includes(needle) };
16011
- });
16012
- return { prompt, footers };
16013
- }
16014
- function extractButtonLabels(spec, text) {
16015
- if (!text) return [];
16016
- const flags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
16017
- const buttonRe = compile2(spec.buttonPattern, flags);
16018
- const labelGroup = Number.isInteger(spec.buttonLabelGroup) && (spec.buttonLabelGroup ?? 0) > 0 ? spec.buttonLabelGroup : 1;
16019
- const out = [];
16020
- for (const line of text.split("\n")) {
16021
- buttonRe.lastIndex = 0;
16022
- const m = buttonRe.exec(line);
16023
- if (!m) continue;
16024
- const captured = m[labelGroup] ?? (labelGroup === 1 && m.length > 2 ? m[m.length - 1] : void 0);
16025
- if (captured && captured.trim()) out.push(captured.trim());
16026
15899
  }
16027
- return out;
16028
- }
16029
- function buttonBlockApprovalCue(spec, text) {
16030
- const labels = extractButtonLabels(spec, text);
16031
- if (labels.length < 2) return false;
16032
- if (pickApprovalButton(labels).index < 0) return false;
16033
- return hasNegativeApprovalOption(labels);
16034
- }
16035
- function modalMatches(spec, input) {
16036
- const text = input.screenText ?? "";
16037
- const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
16038
- if (question.test(text)) return true;
16039
- for (const variant of spec.questionVariants ?? []) {
16040
- const re = compile2(variant.regex, variant.flags ?? "i");
16041
- if (re.test(text)) return true;
15900
+ if (!Array.isArray(spec.states) || spec.states.length === 0) {
15901
+ errs.push("states[] must be a non-empty array");
15902
+ return errs;
16042
15903
  }
16043
- if (buttonBlockApprovalCue(spec, text)) return true;
16044
- return false;
16045
- }
16046
- function evaluateGroup(group, spec, input, compiled) {
16047
- switch (group) {
16048
- case "spinner": {
16049
- if (!spec.spinner || !compiled.spinner) return null;
16050
- const text = scopeText(input, spec.spinner.scope, spec.spinner.scopeWindowLines);
16051
- return compiled.spinner.some((re) => re.test(text)) ? "generating" : null;
16052
- }
16053
- case "modal": {
16054
- if (!spec.modal) return null;
16055
- return modalMatches(spec.modal, input) ? "waiting_approval" : null;
15904
+ if (!Array.isArray(spec.transitions)) {
15905
+ errs.push("transitions[] must be an array");
15906
+ return errs;
15907
+ }
15908
+ const ids = /* @__PURE__ */ new Set();
15909
+ let initialCount = 0;
15910
+ for (const [i, s2] of spec.states.entries()) {
15911
+ if (!s2.id) {
15912
+ errs.push(`states[${i}].id is required`);
15913
+ continue;
16056
15914
  }
16057
- case "settled-prompt": {
16058
- if (!spec.settledPrompt || !compiled.settled) return null;
16059
- const text = scopeText(input, spec.settledPrompt.scope, spec.settledPrompt.scopeWindowLines);
16060
- if (!compiled.settled.prompt.test(text)) return null;
16061
- if (compiled.settled.footers.length === 0) return "idle";
16062
- return compiled.settled.footers.every((f) => f.test(text)) ? "idle" : null;
15915
+ if (ids.has(s2.id)) errs.push(`states[${i}].id "${s2.id}" is duplicated`);
15916
+ ids.add(s2.id);
15917
+ if (!s2.label) errs.push(`states[${i}].label is required`);
15918
+ if (s2.initial) initialCount += 1;
15919
+ if (s2.status && !["idle", "generating", "approval"].includes(s2.status)) {
15920
+ errs.push(`states[${i}].status "${s2.status}" must be idle|generating|approval`);
16063
15921
  }
16064
- // Groups declared in the catalog but not yet implemented as builder steps
16065
- // return null so they are no-ops in dispatch — declaring them in `order`
16066
- // is forward-compatible. Phase 2 Week 8+ will wire them.
16067
- case "cue-ordering":
16068
- case "error-detection":
16069
- case "approval-stitching":
16070
- return null;
16071
- default:
16072
- return null;
16073
15922
  }
16074
- }
16075
- function buildDetectStatusFromTui(spec) {
16076
- const compiledSpinner = spec.spinner ? compileSpinnerMatchers(spec.spinner) : null;
16077
- const compiledSettled = spec.settledPrompt ? compileSettledPromptMatchers(spec.settledPrompt) : null;
16078
- const compiled = { spinner: compiledSpinner, settled: compiledSettled };
16079
- const order = spec.dispatchOrder?.order && spec.dispatchOrder.order.length > 0 ? spec.dispatchOrder.order : DEFAULT_ORDER;
16080
- return function detectStatus(input) {
16081
- const effectiveInput = spec.visibleRegion ? {
16082
- ...input,
16083
- screenText: applyVisibleRegion(spec.visibleRegion, input.screenText ?? ""),
16084
- tail: applyVisibleRegion(spec.visibleRegion, input.tail)
16085
- } : input;
16086
- for (const group of order) {
16087
- const verdict = evaluateGroup(group, spec, effectiveInput, compiled);
16088
- if (verdict !== null) return verdict;
15923
+ if (initialCount === 0) errs.push("exactly one state must have initial:true (none found)");
15924
+ if (initialCount > 1) errs.push(`exactly one state must have initial:true (${initialCount} found)`);
15925
+ const sectionIds = new Set(Object.keys(spec.sections ?? {}));
15926
+ for (const [i, t] of spec.transitions.entries()) {
15927
+ const froms = t.from === "*" ? [] : Array.isArray(t.from) ? t.from : [t.from];
15928
+ for (const f of froms) {
15929
+ if (!ids.has(f)) errs.push(`transitions[${i}].from references unknown state "${f}"`);
16089
15930
  }
16090
- return null;
16091
- };
16092
- }
16093
- var DEFAULT_ORDER;
16094
- var init_detect_status = __esm({
16095
- "src/providers/sdk/v1/builders/cli/detect-status.ts"() {
16096
- "use strict";
16097
- init_visible_region();
16098
- init_approval_utils();
16099
- DEFAULT_ORDER = ["spinner", "modal", "settled-prompt"];
15931
+ if (t.from !== "*" && froms.length === 0) errs.push(`transitions[${i}].from is required`);
15932
+ if (!t.to) errs.push(`transitions[${i}].to is required`);
15933
+ else if (!ids.has(t.to)) errs.push(`transitions[${i}].to references unknown state "${t.to}"`);
15934
+ if (t.when) errs.push(...validateCondition(t.when, sectionIds, `transitions[${i}].when`));
16100
15935
  }
16101
- });
16102
-
16103
- // src/providers/sdk/v1/builders/cli/parse-approval.ts
16104
- function compile3(re, flags) {
16105
- try {
16106
- return new RegExp(re, flags);
16107
- } catch (e) {
16108
- throw new Error(`Invalid regex /${re}/${flags ?? ""}: ${e.message}`);
15936
+ for (const [i, s2] of spec.states.entries()) {
15937
+ const sec = s2.extract?.title?.section;
15938
+ if (sec && !sectionIds.has(sec)) errs.push(`states[${i}].extract.title.section "${sec}" unknown`);
15939
+ const bsec = s2.extract?.buttons?.section;
15940
+ if (bsec && !sectionIds.has(bsec)) errs.push(`states[${i}].extract.buttons.section "${bsec}" unknown`);
16109
15941
  }
15942
+ return errs;
16110
15943
  }
16111
- function findQuestionLineIndex(spec, lines) {
16112
- const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
16113
- for (let i = lines.length - 1; i >= 0; i -= 1) {
16114
- if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
16115
- }
16116
- for (const variant of spec.questionVariants ?? []) {
16117
- const re = compile3(variant.regex, variant.flags ?? "i");
16118
- for (let i = lines.length - 1; i >= 0; i -= 1) {
16119
- if (re.test(lines[i])) return { index: i, matchedSource: variant.label ?? "variant" };
16120
- }
15944
+ function validateCondition(c, sectionIds, path42) {
15945
+ const errs = [];
15946
+ const w = c;
15947
+ if ("all" in w) {
15948
+ w.all.forEach((x, i) => errs.push(...validateCondition(x, sectionIds, `${path42}.all[${i}]`)));
15949
+ return errs;
16121
15950
  }
16122
- return null;
16123
- }
16124
- function scopeLines(spec, lines, questionIndex) {
16125
- const scope = spec.scope ?? "between-last-two-separators";
16126
- if (scope === "whole-screen") {
16127
- return { start: 0, end: lines.length };
15951
+ if ("any" in w) {
15952
+ w.any.forEach((x, i) => errs.push(...validateCondition(x, sectionIds, `${path42}.any[${i}]`)));
15953
+ return errs;
16128
15954
  }
16129
- if (scope === "window-around-question") {
16130
- const window = spec.scopeWindowLines ?? 16;
16131
- return {
16132
- start: Math.max(0, questionIndex - 2),
16133
- end: Math.min(lines.length, questionIndex + window)
16134
- };
15955
+ if ("not" in w) {
15956
+ errs.push(...validateCondition(w.not, sectionIds, `${path42}.not`));
15957
+ return errs;
16135
15958
  }
16136
- let lastSep = -1;
16137
- let prevSep = -1;
16138
- for (let i = lines.length - 1; i >= 0; i -= 1) {
16139
- if (SEPARATOR_RE.test(lines[i].trim())) {
16140
- if (lastSep < 0) lastSep = i;
16141
- else if (prevSep < 0) {
16142
- prevSep = i;
16143
- break;
16144
- }
15959
+ if ("matches" in w) {
15960
+ if (w.section && !sectionIds.has(w.section)) errs.push(`${path42}.section "${w.section}" unknown`);
15961
+ try {
15962
+ new RegExp(w.matches, w.flags ?? "i");
15963
+ } catch (e) {
15964
+ errs.push(`${path42}.matches invalid regex: ${e.message}`);
16145
15965
  }
15966
+ return errs;
16146
15967
  }
16147
- if (lastSep >= 0 && prevSep >= 0) {
16148
- return { start: prevSep, end: lastSep + 1 };
16149
- }
16150
- return {
16151
- start: Math.max(0, questionIndex - 4),
16152
- end: Math.min(lines.length, questionIndex + 16)
16153
- };
16154
- }
16155
- function extractButtons(spec, lines, windowStart, windowEnd) {
16156
- const buttonRe = compile3(spec.buttonPattern, spec.buttonFlags ?? "m");
16157
- const out = [];
16158
- const labelGroup = Number.isInteger(spec.buttonLabelGroup) && (spec.buttonLabelGroup ?? 0) > 0 ? spec.buttonLabelGroup : 1;
16159
- let i = windowStart;
16160
- while (i < windowEnd) {
16161
- const line = lines[i];
16162
- const m = buttonRe.exec(line);
16163
- const captured = m?.[labelGroup] ?? (labelGroup === 1 && m && m.length > 2 ? m[m.length - 1] : void 0);
16164
- if (m && captured) {
16165
- let label = captured.trim();
16166
- if (spec.continuationLines) {
16167
- let j = i + 1;
16168
- while (j < windowEnd) {
16169
- const next = lines[j];
16170
- if (!next.trim()) break;
16171
- if (buttonRe.test(next)) break;
16172
- if (!/^\s+/.test(next)) break;
16173
- label += " " + next.trim();
16174
- j += 1;
16175
- }
16176
- i = j;
16177
- } else {
16178
- i += 1;
16179
- }
16180
- out.push(label);
16181
- } else {
16182
- i += 1;
16183
- }
15968
+ if ("cursor_above" in w && "changed" in w) return errs;
15969
+ if ("elapsed_ms" in w) {
15970
+ if (typeof w.elapsed_ms !== "number") errs.push(`${path42}.elapsed_ms must be a number`);
15971
+ return errs;
16184
15972
  }
16185
- return out;
16186
- }
16187
- function buildMessage(spec, lines, questionIndex, windowStart, windowEnd) {
16188
- const messageParts = [];
16189
- if (spec.contextHeader) {
16190
- const ctxRe = compile3(spec.contextHeader.regex, (spec.contextHeader.flags ?? "i") + "m");
16191
- const haystack = lines.slice(windowStart, windowEnd).join("\n");
16192
- const m = ctxRe.exec(haystack);
16193
- if (m) messageParts.push(m[1] ? m[1].trim() : m[0].trim());
15973
+ if ("stable_ms" in w) {
15974
+ if (typeof w.stable_ms !== "number") errs.push(`${path42}.stable_ms must be a number`);
15975
+ return errs;
16194
15976
  }
16195
- messageParts.push(lines[questionIndex].trim());
16196
- return messageParts.filter(Boolean).join(" \u2014 ");
15977
+ errs.push(`${path42} is not a recognized condition`);
15978
+ return errs;
16197
15979
  }
16198
- function extractInlineButtons(spec, lines, windowStart, windowEnd) {
16199
- if (!spec.inlineButtonPattern) return [];
16200
- const declaredFlags = spec.inlineButtonFlags ?? "gi";
16201
- const flags = declaredFlags.includes("g") ? declaredFlags : declaredFlags + "g";
16202
- const re = compile3(spec.inlineButtonPattern, flags);
16203
- const out = [];
16204
- for (let i = windowStart; i < windowEnd; i += 1) {
16205
- let m;
16206
- re.lastIndex = 0;
16207
- while ((m = re.exec(lines[i])) !== null) {
16208
- const label = (m[1] ?? m[0]).trim();
16209
- if (label && !out.includes(label)) out.push(label);
16210
- if (m.index === re.lastIndex) re.lastIndex += 1;
16211
- }
15980
+ var init_fsm_loader = __esm({
15981
+ "src/providers/spec/fsm-loader.ts"() {
15982
+ "use strict";
15983
+ init_fsm_types();
16212
15984
  }
16213
- return out;
15985
+ });
15986
+
15987
+ // src/providers/spec/evaluator.ts
15988
+ var evaluator_exports = {};
15989
+ __export(evaluator_exports, {
15990
+ evaluateCondition: () => evaluateCondition,
15991
+ extractButtonsFromRule: () => extractButtonsFromRule,
15992
+ extractTitle: () => extractTitle,
15993
+ lastContiguousNumberedBlock: () => lastContiguousNumberedBlock,
15994
+ resolveSections: () => resolveSections,
15995
+ sectionText: () => sectionText
15996
+ });
15997
+ function resolveSize(size, total) {
15998
+ if (size === void 0) return 0;
15999
+ if (typeof size === "number") return Math.max(0, Math.min(total, size));
16000
+ const m = /^(\d+(?:\.\d+)?)%$/.exec(size);
16001
+ if (!m) return 0;
16002
+ const pct = Number(m[1]);
16003
+ return Math.max(0, Math.min(total, Math.round(total * pct / 100)));
16214
16004
  }
16215
- function buildParseApprovalFromTui(spec, visibleRegion) {
16216
- const minButtons = spec.minButtons ?? 2;
16217
- return function parseApproval(input) {
16218
- const rawText = input.screenText ?? input.buffer ?? "";
16219
- if (!rawText) return null;
16220
- const text = visibleRegion ? applyVisibleRegion(visibleRegion, rawText) : rawText;
16221
- const lines = text.split("\n");
16222
- const question = findQuestionLineIndex(spec, lines);
16223
- if (!question) return null;
16224
- const { start, end } = scopeLines(spec, lines, question.index);
16225
- if (question.index < start || question.index >= end) return null;
16226
- let buttons = extractButtons(spec, lines, question.index + 1, end);
16227
- if (buttons.length < minButtons && spec.inlineButtonPattern) {
16228
- buttons = extractInlineButtons(spec, lines, question.index, end);
16005
+ function resolveSections(sectionsObj, lines) {
16006
+ const total = lines.length;
16007
+ const anchored = /* @__PURE__ */ new Map();
16008
+ const sectionEntries = Object.entries(sectionsObj);
16009
+ for (const [id, sec] of sectionEntries) {
16010
+ let from = 0;
16011
+ let to = total;
16012
+ if (sec.anchor !== void 0) {
16013
+ try {
16014
+ const anchorPatterns = Array.isArray(sec.anchor) ? sec.anchor : [sec.anchor];
16015
+ const sharedCtx = Array.isArray(sec.anchor_context) ? null : sec.anchor_context ?? null;
16016
+ const ctxList = Array.isArray(sec.anchor_context) ? sec.anchor_context : anchorPatterns.map(() => sharedCtx);
16017
+ const candidates = anchorPatterns.map((pattern, k) => {
16018
+ const ctx = ctxList[k] ?? null;
16019
+ return {
16020
+ re: new RegExp(pattern, sec.anchor_flags ?? ""),
16021
+ prevRe: ctx?.prev !== void 0 ? new RegExp(ctx.prev, ctx.prev_flags ?? "") : null,
16022
+ nextRe: ctx?.next !== void 0 ? new RegExp(ctx.next, ctx.next_flags ?? "") : null
16023
+ };
16024
+ });
16025
+ const matchesCandidate = (c, i) => c.re.test(lines[i]) && (c.prevRe === null || i > 0 && c.prevRe.test(lines[i - 1])) && (c.nextRe === null || i < total - 1 && c.nextRe.test(lines[i + 1]));
16026
+ let idx = -1;
16027
+ for (const c of candidates) {
16028
+ let candIdx = -1;
16029
+ if (sec.anchor_last) {
16030
+ for (let i = total - 1; i >= 0; i--) {
16031
+ if (matchesCandidate(c, i)) {
16032
+ candIdx = i;
16033
+ break;
16034
+ }
16035
+ }
16036
+ } else {
16037
+ for (let i = 0; i < total; i++) {
16038
+ if (matchesCandidate(c, i)) {
16039
+ candIdx = i;
16040
+ break;
16041
+ }
16042
+ }
16043
+ }
16044
+ if (candIdx !== -1 && (idx === -1 || candIdx < idx)) idx = candIdx;
16045
+ }
16046
+ if (idx !== -1) {
16047
+ from = idx;
16048
+ to = total;
16049
+ if (sec.until_regex !== void 0) {
16050
+ try {
16051
+ const ure = new RegExp(sec.until_regex, sec.until_regex_flags ?? "");
16052
+ const end = lines.findIndex((l, i) => i > idx && ure.test(l));
16053
+ if (end !== -1) to = end;
16054
+ } catch {
16055
+ }
16056
+ } else if (sec.lines !== void 0) {
16057
+ to = Math.min(total, from + sec.lines);
16058
+ }
16059
+ }
16060
+ } catch {
16061
+ }
16062
+ } else if (sec.from_top !== void 0) {
16063
+ from = resolveSize(sec.from_top, total);
16064
+ to = total;
16065
+ } else if (sec.from_bottom !== void 0) {
16066
+ const sz = resolveSize(sec.from_bottom, total);
16067
+ from = total - sz;
16068
+ to = total;
16229
16069
  }
16230
- if (buttons.length < minButtons) return null;
16231
- const message = buildMessage(spec, lines, question.index, start, end);
16232
- return { message, buttons };
16233
- };
16234
- }
16235
- var SEPARATOR_RE;
16236
- var init_parse_approval = __esm({
16237
- "src/providers/sdk/v1/builders/cli/parse-approval.ts"() {
16238
- "use strict";
16239
- init_visible_region();
16240
- SEPARATOR_RE = /^(?:─|━|═|━){10,}\s*$/;
16070
+ anchored.set(id, { fromLine: from, toLine: to });
16241
16071
  }
16242
- });
16243
-
16244
- // src/providers/sdk/v1/builders/cli/parse-session.ts
16245
- import * as crypto3 from "crypto";
16246
- function stripAnsi2(text) {
16247
- return String(text || "").replace(/\x1b\[(\d*)C/g, (_m, n) => " ".repeat(Math.max(1, Number(n) || 1))).replace(/\x1b\[\d*D/g, "").replace(ANSI_RE, "").replace(OSC_RE, "").replace(/\x1b[P^_X][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b(?:[@-Z\\-_])/g, "");
16072
+ const resolved = [];
16073
+ for (const [id, sec] of sectionEntries) {
16074
+ let { fromLine, toLine } = anchored.get(id);
16075
+ if (sec.until !== void 0) {
16076
+ if (sec.until.startsWith("^")) {
16077
+ try {
16078
+ const ure = new RegExp(sec.until);
16079
+ const end = lines.findIndex((l, i) => i > fromLine && ure.test(l));
16080
+ if (end !== -1) toLine = end;
16081
+ } catch {
16082
+ }
16083
+ } else {
16084
+ const target = anchored.get(sec.until);
16085
+ if (target) toLine = target.fromLine;
16086
+ }
16087
+ }
16088
+ if (toLine < fromLine) toLine = fromLine;
16089
+ const text = lines.slice(fromLine, toLine).join("\n");
16090
+ resolved.push({ id, fromLine, toLine, text });
16091
+ }
16092
+ return resolved;
16248
16093
  }
16249
- function splitLines(text) {
16250
- return stripAnsi2(text).replace(//g, "").split(/\r?\n/).map((l) => l.replace(/\s+$/, ""));
16094
+ function sectionText(sections, sectionId, fullScreen) {
16095
+ if (!sectionId) return fullScreen;
16096
+ const found = sections.find((s2) => s2.id === sectionId);
16097
+ return found ? found.text : "";
16251
16098
  }
16252
- function pickInputText(input, scope) {
16253
- if (!input) return "";
16254
- if (scope === "screen") return String(input.screenText || input.screen?.text || "");
16255
- if (scope === "tail") return String(input.tail || input.recentBuffer || input.buffer || "");
16256
- return String(input.buffer || input.rawBuffer || input.screenText || "");
16099
+ function isRegexCondition(c) {
16100
+ return "matches" in c;
16257
16101
  }
16258
- function stableHash(value) {
16259
- return crypto3.createHash("sha1").update(String(value || "")).digest("hex").slice(0, 12);
16102
+ function isChangedCondition(c) {
16103
+ return "cursor_above" in c && "changed" in c;
16260
16104
  }
16261
- function normalizeMessageIdentity(messages, status) {
16262
- const list = Array.isArray(messages) ? messages : [];
16263
- let turnIndex = -1;
16264
- return list.map((message, index) => {
16265
- const role = message?.role || "assistant";
16266
- const kind = message?.kind || "standard";
16267
- const content = typeof message?.content === "string" ? message.content : "";
16268
- if (role === "user" || turnIndex < 0) turnIndex += 1;
16269
- const seed = [role, kind, "", index, content].join("\n");
16270
- const providerUnitKey = `v2-pty:${role}:${kind}:${index}:${stableHash(seed)}`;
16271
- const bubbleId = `bubble:${providerUnitKey}`;
16272
- const turnKey = `turn:${turnIndex}`;
16273
- const isStreamingTail = status === "generating" && role === "assistant" && index === list.length - 1;
16274
- return {
16275
- ...message,
16276
- providerUnitKey,
16277
- bubbleId,
16278
- sequence: index,
16279
- _turnKey: turnKey,
16280
- bubbleState: isStreamingTail ? "streaming" : "final"
16281
- };
16282
- });
16105
+ function isAllCondition(c) {
16106
+ return "all" in c;
16283
16107
  }
16284
- function buildParseSessionFromTui(spec) {
16285
- if (!spec.transcriptPty) {
16286
- throw new Error("buildParseSessionFromTui: spec.transcriptPty is required");
16108
+ function isAnyCondition(c) {
16109
+ return "any" in c;
16110
+ }
16111
+ function evaluateCondition(cond, sections, fullScreen, cursor, prevLines, trace, stateId) {
16112
+ if (isAllCondition(cond)) {
16113
+ for (const child of cond.all) {
16114
+ if (!evaluateCondition(child, sections, fullScreen, cursor, prevLines, trace, stateId)) {
16115
+ return false;
16116
+ }
16117
+ }
16118
+ return true;
16287
16119
  }
16288
- const assistantRe = new RegExp(spec.transcriptPty.assistantPrefix.regex, spec.transcriptPty.assistantPrefix.flags || "");
16289
- const userRe = spec.transcriptPty.userPrefix ? new RegExp(spec.transcriptPty.userPrefix.regex, spec.transcriptPty.userPrefix.flags || "") : null;
16290
- const toolRe = spec.transcriptPty.toolPrefix ? new RegExp(spec.transcriptPty.toolPrefix.regex, spec.transcriptPty.toolPrefix.flags || "") : null;
16291
- const toolSkip = spec.transcriptPty.toolPrefix?.skip ?? false;
16292
- const chromeRes = (spec.transcriptPty.chromePatterns || []).map((p) => new RegExp(p.regex, p.flags || ""));
16293
- const requireIndentForContinuation = spec.transcriptPty.continuationLine?.indented ?? false;
16294
- const stripLeadingChrome = spec.transcriptPty.stripLeadingChrome ?? true;
16295
- const scope = spec.transcriptPty.scope ?? "buffer";
16296
- const detectStatus = spec.spinner || spec.settledPrompt || spec.modal || spec.dispatchOrder ? buildDetectStatusFromTui({
16297
- spinner: spec.spinner,
16298
- settledPrompt: spec.settledPrompt,
16299
- modal: spec.modal,
16300
- dispatchOrder: spec.dispatchOrder
16301
- }) : () => null;
16302
- const parseApproval = spec.modal ? buildParseApprovalFromTui(spec.modal) : () => null;
16303
- const sessionIdRe = spec.sessionIdExtraction ? new RegExp(
16304
- spec.sessionIdExtraction.regex,
16305
- spec.sessionIdExtraction.flags ?? "i"
16306
- ) : null;
16307
- const sessionIdScope = spec.sessionIdExtraction?.scope ?? "tail";
16308
- return function parseSession(input) {
16309
- const status = detectStatus(input) ?? "idle";
16310
- const modal = parseApproval(input);
16311
- const text = pickInputText(input, scope);
16312
- const lines = splitLines(text);
16313
- const messages = [];
16314
- let seenFirstRoleLine = !stripLeadingChrome;
16315
- for (const raw of lines) {
16316
- const line = raw;
16317
- if (line.trim() === "") {
16318
- continue;
16120
+ if (isAnyCondition(cond)) {
16121
+ for (const child of cond.any) {
16122
+ if (evaluateCondition(child, sections, fullScreen, cursor, prevLines, trace, stateId)) {
16123
+ return true;
16319
16124
  }
16320
- let isChrome = false;
16321
- for (const cre of chromeRes) {
16322
- if (cre.test(line)) {
16323
- isChrome = true;
16324
- break;
16325
- }
16125
+ }
16126
+ return false;
16127
+ }
16128
+ if (isChangedCondition(cond)) {
16129
+ if (!cursor || !prevLines || prevLines.length === 0) return false;
16130
+ const curLines = fullScreen.split("\n");
16131
+ const startRow = Math.max(0, cursor.row - cond.cursor_above);
16132
+ const endRow = cursor.row;
16133
+ const currentSlice = curLines.slice(startRow, endRow).join("\n");
16134
+ const prevSlice = prevLines.slice(startRow, endRow).join("\n");
16135
+ const didChange = currentSlice !== prevSlice;
16136
+ const result = cond.changed ? didChange : !didChange;
16137
+ const stableSuffix = cond.stable_ms != null ? ` stable_ms=${cond.stable_ms}` : "";
16138
+ trace.push({
16139
+ kind: result ? "state_match" : "state_skip",
16140
+ text: `state[${stateId}] changed cond cursor_above=${cond.cursor_above} rows[${startRow},${endRow}) changed=${didChange} expected=${cond.changed}${stableSuffix} result=${result}`
16141
+ });
16142
+ return result;
16143
+ }
16144
+ if (isRegexCondition(cond)) {
16145
+ const haystack = sectionText(sections, cond.section, fullScreen);
16146
+ let matched = false;
16147
+ try {
16148
+ const re = new RegExp(cond.matches, cond.flags ?? "i");
16149
+ matched = re.test(haystack);
16150
+ } catch {
16151
+ matched = false;
16152
+ }
16153
+ if (!matched) {
16154
+ trace.push({ kind: "state_skip", text: `state[${stateId}] regex cond ${cond.section ?? "*"}~/${cond.matches}/ no match` });
16155
+ return false;
16156
+ }
16157
+ if (cursor !== void 0) {
16158
+ if (cond.cursor_row_min !== void 0 && cursor.row < cond.cursor_row_min) {
16159
+ trace.push({ kind: "state_skip", text: `state[${stateId}] cursor row ${cursor.row} < cursor_row_min ${cond.cursor_row_min}` });
16160
+ return false;
16326
16161
  }
16327
- if (isChrome) continue;
16328
- const userMatch = userRe ? line.match(userRe) : null;
16329
- const toolMatch = toolRe ? line.match(toolRe) : null;
16330
- const assistMatch = line.match(assistantRe);
16331
- if (userMatch) {
16332
- seenFirstRoleLine = true;
16333
- const content = (userMatch[1] ?? userMatch[0]).trim();
16334
- if (content) messages.push({ role: "user", kind: "standard", content });
16335
- continue;
16162
+ if (cond.cursor_row_max !== void 0 && cursor.row > cond.cursor_row_max) {
16163
+ trace.push({ kind: "state_skip", text: `state[${stateId}] cursor row ${cursor.row} > cursor_row_max ${cond.cursor_row_max}` });
16164
+ return false;
16336
16165
  }
16337
- if (toolMatch) {
16338
- seenFirstRoleLine = true;
16339
- if (toolSkip) continue;
16340
- const content = (toolMatch[1] ?? toolMatch[0]).trim();
16341
- if (content) messages.push({ role: "assistant", kind: "tool", content });
16342
- continue;
16166
+ if (cond.cursor_col_min !== void 0 && cursor.col < cond.cursor_col_min) {
16167
+ trace.push({ kind: "state_skip", text: `state[${stateId}] cursor col ${cursor.col} < cursor_col_min ${cond.cursor_col_min}` });
16168
+ return false;
16343
16169
  }
16344
- if (assistMatch) {
16345
- seenFirstRoleLine = true;
16346
- const content = (assistMatch[1] ?? assistMatch[0]).trim();
16347
- if (content) messages.push({ role: "assistant", kind: "standard", content });
16348
- continue;
16170
+ if (cond.cursor_col_max !== void 0 && cursor.col > cond.cursor_col_max) {
16171
+ trace.push({ kind: "state_skip", text: `state[${stateId}] cursor col ${cursor.col} > cursor_col_max ${cond.cursor_col_max}` });
16172
+ return false;
16349
16173
  }
16350
- if (!seenFirstRoleLine) continue;
16351
- if (requireIndentForContinuation && !/^\s/.test(line)) continue;
16352
- const last = messages[messages.length - 1];
16353
- if (!last) continue;
16354
- const cont = line.replace(/^\s+/, "");
16355
- if (cont) last.content = last.content ? `${last.content}
16356
- ${cont}` : cont;
16357
16174
  }
16358
- const result = {
16359
- status,
16360
- messages,
16361
- activeModal: modal,
16362
- modal,
16363
- parsedStatus: status
16364
- };
16365
- if (sessionIdRe) {
16366
- const haystack = stripAnsi2(pickInputText(input, sessionIdScope));
16367
- const match = haystack.match(sessionIdRe);
16368
- const captured = match?.[1]?.trim();
16369
- if (captured) result.providerSessionId = captured;
16175
+ trace.push({ kind: "state_match", text: `state[${stateId}] regex cond ${cond.section ?? "*"}~/${cond.matches}/ matched${cursor !== void 0 ? ` cursor=(${cursor.row},${cursor.col})` : ""}` });
16176
+ return true;
16177
+ }
16178
+ return false;
16179
+ }
16180
+ function extractTitle(rule, sections, fullScreen) {
16181
+ const hay = sectionText(sections, rule.section, fullScreen);
16182
+ if (!hay) return null;
16183
+ if (rule.first_line) {
16184
+ const lines = hay.split("\n");
16185
+ for (const line of lines) {
16186
+ const stripped = line.trim();
16187
+ if (stripped && !/^[─╌═─\s]+$/.test(stripped)) {
16188
+ return stripped;
16189
+ }
16370
16190
  }
16371
- return result;
16372
- };
16191
+ return null;
16192
+ }
16193
+ if (rule.regex) {
16194
+ try {
16195
+ const re = new RegExp(rule.regex, rule.flags ?? "i");
16196
+ const m = re.exec(hay);
16197
+ if (m) return (m[1] ?? m[0]).trim();
16198
+ } catch {
16199
+ }
16200
+ }
16201
+ return null;
16373
16202
  }
16374
- var ANSI_RE, OSC_RE;
16375
- var init_parse_session = __esm({
16376
- "src/providers/sdk/v1/builders/cli/parse-session.ts"() {
16203
+ function compilePattern(ref) {
16204
+ const flags = ref.flags ?? "gm";
16205
+ return new RegExp(ref.pattern, flags.includes("g") ? flags : flags + "g");
16206
+ }
16207
+ function compileLinePattern(ref) {
16208
+ const flags = (ref.flags ?? "m").replace(/g/g, "");
16209
+ return new RegExp(ref.pattern, flags);
16210
+ }
16211
+ function extractButtonsFromRule(rule, hay) {
16212
+ const keyTemplate = rule.key_for_index;
16213
+ const continuationLines = rule.continuation_lines ?? false;
16214
+ const buttons = [];
16215
+ if (continuationLines) {
16216
+ const re = compileLinePattern(rule);
16217
+ const lines = hay.split("\n");
16218
+ for (let i = 0; i < lines.length; i += 1) {
16219
+ const m = re.exec(lines[i]);
16220
+ if (!m) continue;
16221
+ const idx = Number(m[1]);
16222
+ let label = String(m[2] ?? "").trim();
16223
+ if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
16224
+ const current = hasCursorMarker(lines[i]);
16225
+ let j = i + 1;
16226
+ while (j < lines.length) {
16227
+ const next = lines[j];
16228
+ if (!next.trim()) break;
16229
+ if (re.test(next)) break;
16230
+ if (!/^\s+/.test(next)) break;
16231
+ label += " " + next.trim();
16232
+ j += 1;
16233
+ }
16234
+ const key = keyTemplate.replace(/\{index\}/g, String(idx));
16235
+ buttons.push({ index: idx, label, key, current });
16236
+ i = j - 1;
16237
+ }
16238
+ } else {
16239
+ const re = compilePattern(rule);
16240
+ let m;
16241
+ while ((m = re.exec(hay)) !== null) {
16242
+ const idx = Number(m[1]);
16243
+ const label = String(m[2] ?? "").trim();
16244
+ if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
16245
+ const key = keyTemplate.replace(/\{index\}/g, String(idx));
16246
+ buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
16247
+ }
16248
+ }
16249
+ const block2 = lastContiguousNumberedBlock(buttons);
16250
+ block2.sort((a, b) => a.index - b.index);
16251
+ return block2;
16252
+ }
16253
+ function lastContiguousNumberedBlock(entries) {
16254
+ if (entries.length <= 1) return entries.slice();
16255
+ let start = entries.length - 1;
16256
+ for (let i = entries.length - 1; i > 0; i -= 1) {
16257
+ if (entries[i - 1].index === entries[i].index - 1) start = i - 1;
16258
+ else break;
16259
+ }
16260
+ return entries.slice(start);
16261
+ }
16262
+ function hasCursorMarker(text) {
16263
+ return /^\s*[❯›>]/.test(text);
16264
+ }
16265
+ var init_evaluator = __esm({
16266
+ "src/providers/spec/evaluator.ts"() {
16377
16267
  "use strict";
16378
- init_detect_status();
16379
- init_parse_approval();
16380
- ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
16381
- OSC_RE = /\x1b\][^\x07\x1b\n]*(?:\x07|\x1b\\|(?=\n|$))/g;
16382
16268
  }
16383
16269
  });
16384
16270
 
16385
- // src/cli-adapters/cli-script-runner.ts
16386
- function summarizeInput(input) {
16387
- const screenText = typeof input?.screenText === "string" ? input.screenText : "";
16388
- const rawBuffer = typeof input?.rawBuffer === "string" ? input.rawBuffer : "";
16389
- const tail = typeof input?.tail === "string" ? input.tail : "";
16390
- return {
16391
- screenTextLen: screenText.length,
16392
- rawBufferLen: rawBuffer.length,
16393
- tailLen: tail.length,
16394
- isWaitingForResponse: typeof input?.isWaitingForResponse === "boolean" ? input.isWaitingForResponse : void 0,
16395
- screenTextHead: screenText ? screenText.slice(0, 200) : void 0
16396
- };
16271
+ // src/providers/spec/fsm-evaluator.ts
16272
+ var fsm_evaluator_exports = {};
16273
+ __export(fsm_evaluator_exports, {
16274
+ evaluateConditionPreview: () => evaluateConditionPreview,
16275
+ evaluateFsm: () => evaluateFsm
16276
+ });
16277
+ function regionKey(cursorAbove) {
16278
+ return cursorAbove && cursorAbove > 0 ? cursorAbove : WHOLE_SCREEN;
16397
16279
  }
16398
- function summarizeResult(result) {
16399
- try {
16400
- const json = JSON.stringify(result);
16401
- return json && json.length > 400 ? `${json.slice(0, 400)}\u2026[truncated ${json.length - 400}]` : json ?? "undefined";
16402
- } catch (e) {
16403
- return `<unserializable: ${e?.message || e}>`;
16404
- }
16280
+ function isRegex(c) {
16281
+ return "matches" in c;
16405
16282
  }
16406
- var TRACE_RING_CAPACITY, DEFAULT_SCRIPT_CALL_BUDGET_MS, BUDGET_WARN_THROTTLE_MS, CliScriptRunner;
16407
- var init_cli_script_runner = __esm({
16408
- "src/cli-adapters/cli-script-runner.ts"() {
16409
- "use strict";
16410
- init_logger();
16411
- init_provider_cli_shared();
16412
- init_detect_status();
16413
- init_parse_approval();
16414
- init_parse_session();
16415
- TRACE_RING_CAPACITY = 64;
16416
- DEFAULT_SCRIPT_CALL_BUDGET_MS = 50;
16417
- BUDGET_WARN_THROTTLE_MS = 3e4;
16418
- CliScriptRunner = class {
16419
- scripts = {};
16420
- scriptState = null;
16421
- _parseErrorMessage = null;
16422
- cliType;
16423
- sdk = {};
16424
- invocationTrace = [];
16425
- /** Per-invocation wall-clock budget (ms). Configurable via setScriptCallBudget. */
16426
- scriptCallBudgetMs = DEFAULT_SCRIPT_CALL_BUDGET_MS;
16427
- /** Last WARN emit time per scriptName, used to throttle repeated budget violations. */
16428
- lastBudgetWarnAt = /* @__PURE__ */ new Map();
16429
- constructor(cliType) {
16430
- this.cliType = cliType;
16283
+ function isChanged(c) {
16284
+ return "cursor_above" in c && "changed" in c;
16285
+ }
16286
+ function isElapsed(c) {
16287
+ return "elapsed_ms" in c;
16288
+ }
16289
+ function isStable(c) {
16290
+ return "stable_ms" in c;
16291
+ }
16292
+ function isAll(c) {
16293
+ return "all" in c;
16294
+ }
16295
+ function isAny(c) {
16296
+ return "any" in c;
16297
+ }
16298
+ function isNot(c) {
16299
+ return "not" in c;
16300
+ }
16301
+ function evalCond(cond, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId) {
16302
+ if (isAll(cond)) {
16303
+ const children = cond.all.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
16304
+ const result = children.every((c) => c.result);
16305
+ const remainingMs = result ? 0 : Math.max(0, ...children.filter((c) => !c.result).map((c) => c.remainingMs ?? 0));
16306
+ return { kind: "all", result, detail: `all(${children.length})`, remainingMs, children };
16307
+ }
16308
+ if (isAny(cond)) {
16309
+ const children = cond.any.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
16310
+ const result = children.some((c) => c.result);
16311
+ const pending = children.filter((c) => !c.result).map((c) => c.remainingMs ?? Infinity);
16312
+ const remainingMs = result ? 0 : pending.length ? Math.min(...pending) : 0;
16313
+ return { kind: "any", result, detail: `any(${children.length})`, remainingMs: Number.isFinite(remainingMs) ? remainingMs : 0, children };
16314
+ }
16315
+ if (isNot(cond)) {
16316
+ const child = evalCond(cond.not, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId);
16317
+ return { kind: "not", result: !child.result, detail: `not`, remainingMs: 0, children: [child] };
16318
+ }
16319
+ if (isElapsed(cond)) {
16320
+ const age = clock.now - clock.stateEnteredAt;
16321
+ const result = age >= cond.elapsed_ms;
16322
+ const remainingMs = result ? 0 : cond.elapsed_ms - age;
16323
+ return { kind: "elapsed", result, detail: `elapsed ${age}ms / ${cond.elapsed_ms}ms`, remainingMs };
16324
+ }
16325
+ if (isStable(cond)) {
16326
+ const key = regionKey(cond.cursor_above);
16327
+ const lastChanged = clock.regionLastChangedAt.get(key) ?? clock.stateEnteredAt;
16328
+ const stableFor = clock.now - lastChanged;
16329
+ const result = stableFor >= cond.stable_ms;
16330
+ const remainingMs = result ? 0 : cond.stable_ms - stableFor;
16331
+ const where = key === WHOLE_SCREEN ? "screen" : `cursor_above=${cond.cursor_above}`;
16332
+ return { kind: "stable", result, detail: `stable ${where} ${stableFor}ms / ${cond.stable_ms}ms`, remainingMs };
16333
+ }
16334
+ if (isRegex(cond) || isChanged(cond)) {
16335
+ const result = evaluateCondition(cond, sections, fullScreen, cursor, prevLines, legacyTrace, stateId);
16336
+ const kind = isRegex(cond) ? "regex" : "changed";
16337
+ const detail = isRegex(cond) ? `${cond.section ?? "*"}~/${cond.matches}/` : `cursor_above=${cond.cursor_above} changed=${cond.changed}`;
16338
+ let matchedText;
16339
+ if (result && isRegex(cond)) {
16340
+ try {
16341
+ const hay = sectionText(sections, cond.section, fullScreen);
16342
+ const re = new RegExp(cond.matches, cond.flags ?? "i");
16343
+ const m = re.exec(hay);
16344
+ if (m && m[0]) matchedText = m[0].replace(/\s+/g, " ").trim().slice(0, 160);
16345
+ } catch {
16431
16346
  }
16432
- /** Returns the most-recent script invocation traces (oldest → newest). */
16433
- getInvocationTrace() {
16434
- return this.invocationTrace.slice();
16435
- }
16436
- /** Clear the trace ring — used by tests and after PTY reset. */
16437
- clearInvocationTrace() {
16438
- this.invocationTrace = [];
16439
- }
16440
- /**
16441
- * Configure the wall-clock budget (ms) applied to every script invocation.
16442
- *
16443
- * Out-of-range or non-finite values are clamped to [1, 5000] and the
16444
- * default (50ms) is used as a fallback. The budget is enforced per-call,
16445
- * not aggregated it does not abort a runaway script (Node CJS cannot
16446
- * interrupt synchronous code without a worker thread). Instead, an
16447
- * exceeded budget flags the trace entry with `timedOut: true` and emits
16448
- * a throttled WARN naming the script and elapsed time so an operator
16449
- * can identify which provider is hanging the settle loop.
16450
- */
16451
- setScriptCallBudget(ms) {
16452
- if (typeof ms !== "number" || !Number.isFinite(ms)) {
16453
- this.scriptCallBudgetMs = DEFAULT_SCRIPT_CALL_BUDGET_MS;
16454
- return;
16455
- }
16456
- const clamped = Math.max(1, Math.min(5e3, Math.floor(ms)));
16457
- this.scriptCallBudgetMs = clamped;
16458
- }
16459
- /** Test/debug accessor — current effective budget in ms. */
16460
- getScriptCallBudgetMs() {
16461
- return this.scriptCallBudgetMs;
16462
- }
16463
- recordTrace(entry) {
16464
- this.invocationTrace.push(entry);
16465
- if (this.invocationTrace.length > TRACE_RING_CAPACITY) {
16466
- this.invocationTrace.splice(0, this.invocationTrace.length - TRACE_RING_CAPACITY);
16467
- }
16468
- }
16469
- // ─── Script lifecycle ─────────────────────────────
16470
- setScripts(scripts, providerTui) {
16471
- this.sdk = this.buildSdk(providerTui);
16472
- const tui = providerTui;
16473
- const enriched = { ...scripts };
16474
- if (typeof enriched.detectStatus !== "function" && this.sdk.declarativeDetectStatus) {
16475
- enriched.detectStatus = this.sdk.declarativeDetectStatus;
16476
- }
16477
- if (typeof enriched.parseApproval !== "function" && this.sdk.declarativeParseApproval) {
16478
- enriched.parseApproval = this.sdk.declarativeParseApproval;
16479
- }
16480
- if (typeof enriched.parseSession !== "function" && tui?.transcriptPty) {
16481
- try {
16482
- const synth = buildParseSessionFromTui({
16483
- spinner: tui.spinner,
16484
- settledPrompt: tui.settledPrompt,
16485
- modal: tui.modal,
16486
- dispatchOrder: tui.dispatchOrder,
16487
- transcriptPty: tui.transcriptPty
16488
- });
16489
- enriched.parseSession = ((input) => {
16490
- const out = synth(input);
16491
- return {
16492
- ...out,
16493
- messages: normalizeMessageIdentity(out.messages, out.status ?? "idle")
16494
- };
16495
- });
16496
- } catch (e) {
16497
- LOG.warn("CLI", `[${this.cliType}] buildParseSessionFromTui failed: ${e?.message || e}`);
16498
- }
16499
- }
16500
- this.scripts = enriched;
16501
- this._parseErrorMessage = null;
16502
- this.scriptState = typeof enriched.createState === "function" ? enriched.createState() ?? null : null;
16503
- }
16504
- buildSdk(providerTui) {
16505
- const tui = providerTui;
16506
- if (!tui) return {};
16507
- const sdk = {};
16508
- if (tui.spinner || tui.settledPrompt || tui.modal || tui.dispatchOrder) {
16509
- try {
16510
- sdk.declarativeDetectStatus = buildDetectStatusFromTui({
16511
- spinner: tui.spinner,
16512
- settledPrompt: tui.settledPrompt,
16513
- modal: tui.modal,
16514
- dispatchOrder: tui.dispatchOrder
16515
- });
16516
- } catch (e) {
16517
- LOG.warn("CLI", `[${this.cliType}] buildDetectStatusFromTui failed: ${e?.message || e}`);
16518
- }
16519
- }
16520
- if (tui.modal) {
16521
- try {
16522
- sdk.declarativeParseApproval = buildParseApprovalFromTui(tui.modal);
16523
- } catch (e) {
16524
- LOG.warn("CLI", `[${this.cliType}] buildParseApprovalFromTui failed: ${e?.message || e}`);
16525
- }
16526
- }
16527
- return sdk;
16528
- }
16529
- /** Reset per-session state — called when the PTY process exits. */
16530
- resetSessionState() {
16531
- this.scriptState = null;
16532
- this.invocationTrace = [];
16533
- this.lastBudgetWarnAt.clear();
16534
- }
16535
- // ─── Script access (for reflection and test patching) ────────────────────
16536
- /** Returns the live scripts object. Direct property assignment on this object
16537
- * patches individual scripts without replacing others (used in tests). */
16538
- get cliScripts() {
16539
- return this.scripts;
16347
+ }
16348
+ return matchedText ? { kind, result, detail, matchedText } : { kind, result, detail };
16349
+ }
16350
+ return { kind: "all", result: false, detail: "unknown condition" };
16351
+ }
16352
+ function fromLabel(t) {
16353
+ const from = Array.isArray(t.from) ? t.from.join("|") : t.from;
16354
+ return t.label ?? `${from}\u2192${t.to}`;
16355
+ }
16356
+ function evaluateFsm(spec, currentStateId, screenText, cursor, prevLines, clock) {
16357
+ const legacyTrace = [];
16358
+ const lines = screenText.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
16359
+ const cleanScreen = lines.join("\n");
16360
+ const sections = resolveSections(spec.sections ?? {}, lines);
16361
+ const outgoing = outgoingTransitions(spec, currentStateId);
16362
+ const transitions = [];
16363
+ let fired = null;
16364
+ for (const t of outgoing) {
16365
+ const holdMs = t.min_hold_ms ?? 0;
16366
+ const heldFor = clock.now - clock.stateEnteredAt;
16367
+ const holdSatisfied = heldFor >= holdMs;
16368
+ const holdRemainingMs = holdSatisfied ? 0 : holdMs - heldFor;
16369
+ let cond;
16370
+ let condResult = true;
16371
+ if (t.when) {
16372
+ cond = evalCond(t.when, sections, cleanScreen, cursor, prevLines, clock, legacyTrace, `${currentStateId}\u2192${t.to}`);
16373
+ condResult = cond.result;
16374
+ }
16375
+ const fires = holdSatisfied && condResult;
16376
+ const te = {
16377
+ to: t.to,
16378
+ label: fromLabel(t),
16379
+ eligible: true,
16380
+ holdSatisfied,
16381
+ holdRemainingMs,
16382
+ condResult,
16383
+ cond,
16384
+ fires,
16385
+ priority: t.priority ?? 0
16386
+ };
16387
+ transitions.push(te);
16388
+ if (fires && !fired) fired = te;
16389
+ }
16390
+ if (fired && !stateById(spec, fired.to)) {
16391
+ legacyTrace.push({ kind: "state_skip", text: `transition target ${fired.to} not found` });
16392
+ fired = null;
16393
+ }
16394
+ return { sections, transitions, fired, trace: legacyTrace };
16395
+ }
16396
+ function evaluateConditionPreview(cond, sections, screenText, cursor) {
16397
+ const lines = screenText.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
16398
+ const cleanScreen = lines.join("\n");
16399
+ const resolved = resolveSections(sections ?? {}, lines);
16400
+ const clock = { now: 0, stateEnteredAt: 0, regionLastChangedAt: /* @__PURE__ */ new Map() };
16401
+ return evalCond(cond, resolved, cleanScreen, cursor, void 0, clock, [], "preview");
16402
+ }
16403
+ var WHOLE_SCREEN;
16404
+ var init_fsm_evaluator = __esm({
16405
+ "src/providers/spec/fsm-evaluator.ts"() {
16406
+ "use strict";
16407
+ init_evaluator();
16408
+ init_fsm_types();
16409
+ WHOLE_SCREEN = -1;
16410
+ }
16411
+ });
16412
+
16413
+ // src/cli-adapters/pty-transport.ts
16414
+ var pty_transport_exports = {};
16415
+ __export(pty_transport_exports, {
16416
+ NodePtyTransportFactory: () => NodePtyTransportFactory
16417
+ });
16418
+ import * as os13 from "os";
16419
+ function loadNodePty() {
16420
+ if (cachedPty !== void 0) return cachedPty;
16421
+ try {
16422
+ cachedPty = __require("node-pty");
16423
+ ensureNodePtySpawnHelperPermissions();
16424
+ } catch {
16425
+ cachedPty = null;
16426
+ }
16427
+ return cachedPty;
16428
+ }
16429
+ var cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory;
16430
+ var init_pty_transport = __esm({
16431
+ "src/cli-adapters/pty-transport.ts"() {
16432
+ "use strict";
16433
+ init_spawn_env();
16434
+ init_resolve_executable();
16435
+ NodePtyRuntimeTransport = class {
16436
+ constructor(handle) {
16437
+ this.handle = handle;
16540
16438
  }
16541
- // ─── Capability checks ────────────────────────────
16542
- hasDetectStatus() {
16543
- return typeof this.scripts.detectStatus === "function";
16439
+ ready = Promise.resolve();
16440
+ terminalQueriesHandled = false;
16441
+ get pid() {
16442
+ return this.handle.pid;
16544
16443
  }
16545
- hasParseSession() {
16546
- return typeof this.scripts.parseSession === "function";
16444
+ write(data) {
16445
+ this.handle.write(data);
16547
16446
  }
16548
- getScriptNames() {
16549
- return listCliScriptNames(this.scripts);
16447
+ resize(cols, rows) {
16448
+ this.handle.resize(cols, rows);
16550
16449
  }
16551
- // ─── Error state ──────────────────────────────────
16552
- get parseErrorMessage() {
16553
- return this._parseErrorMessage;
16450
+ kill() {
16451
+ this.handle.kill();
16554
16452
  }
16555
- clearParseError() {
16556
- this._parseErrorMessage = null;
16453
+ getMetadata() {
16454
+ return null;
16557
16455
  }
16558
- // ─── Script invocation ────────────────────────────
16559
- detectStatus(input) {
16560
- if (!this.scripts.detectStatus) return null;
16561
- try {
16562
- return this.invoke("detectStatus", this.scripts.detectStatus, input);
16563
- } catch (e) {
16564
- LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e?.message || e}`);
16565
- return null;
16566
- }
16456
+ onData(callback) {
16457
+ this.handle.onData(callback);
16567
16458
  }
16568
- parseApproval(input) {
16569
- if (!this.scripts.parseApproval) return null;
16570
- try {
16571
- return this.invoke(
16572
- "parseApproval",
16573
- this.scripts.parseApproval,
16574
- input
16575
- );
16576
- } catch (e) {
16577
- LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e?.message || e}`);
16578
- return null;
16579
- }
16459
+ onExit(callback) {
16460
+ this.handle.onExit(callback);
16580
16461
  }
16581
- parseSession(input) {
16582
- if (!this.scripts.parseSession) {
16583
- this._parseErrorMessage = `${this.cliType} parseSession unavailable`;
16584
- return null;
16585
- }
16586
- try {
16587
- const result = this.invoke("parseSession", this.scripts.parseSession, input);
16588
- this._parseErrorMessage = null;
16589
- return result && typeof result === "object" ? result : null;
16590
- } catch (e) {
16591
- this._parseErrorMessage = e?.message || String(e);
16592
- LOG.warn("CLI", `[${this.cliType}] parseSession error: ${this._parseErrorMessage}`);
16593
- return null;
16462
+ };
16463
+ NodePtyTransportFactory = class {
16464
+ spawn(command, args, options) {
16465
+ const pty = loadNodePty();
16466
+ if (!pty) throw new Error("node-pty is not installed");
16467
+ let cwd = options.cwd;
16468
+ if (cwd) {
16469
+ try {
16470
+ const fs32 = __require("fs");
16471
+ const stat2 = fs32.statSync(cwd);
16472
+ if (!stat2.isDirectory()) cwd = os13.homedir();
16473
+ } catch {
16474
+ cwd = os13.homedir();
16475
+ }
16594
16476
  }
16477
+ const handle = pty.spawn(resolveWin32Executable(command), args, {
16478
+ name: "xterm-256color",
16479
+ cols: options.cols,
16480
+ rows: options.rows,
16481
+ cwd,
16482
+ env: options.env
16483
+ });
16484
+ return new NodePtyRuntimeTransport(handle);
16595
16485
  }
16596
- /**
16597
- * Invoke an arbitrary named script (e.g. setModel, openModelPicker).
16598
- * Throws if the script is not available.
16599
- */
16600
- invokeByName(name, input) {
16601
- const fn = this.scripts[name];
16602
- if (typeof fn !== "function") {
16603
- throw new Error(`CLI script '${name}' not available`);
16486
+ };
16487
+ }
16488
+ });
16489
+
16490
+ // src/providers/sdk/v1/builders/cli/visible-region.ts
16491
+ function compile(re, flags) {
16492
+ try {
16493
+ return new RegExp(re, flags ?? "");
16494
+ } catch (e) {
16495
+ throw new Error(`Invalid visible-region anchor regex /${re}/${flags ?? ""}: ${e.message}`);
16496
+ }
16497
+ }
16498
+ function findAllMatches(re, text) {
16499
+ const results = [];
16500
+ const iter = new RegExp(re.source, re.flags.replace("g", "") + "g");
16501
+ let m;
16502
+ while ((m = iter.exec(text)) !== null) {
16503
+ results.push(m);
16504
+ if (m[0].length === 0) iter.lastIndex += 1;
16505
+ }
16506
+ return results;
16507
+ }
16508
+ function applyVisibleRegion(spec, text) {
16509
+ if (!text) return text;
16510
+ switch (spec.scope) {
16511
+ case "screen":
16512
+ case "buffer":
16513
+ return text;
16514
+ case "tail": {
16515
+ const limit = spec.tailChars ?? 4e3;
16516
+ if (text.length <= limit) return text;
16517
+ return text.slice(-limit);
16518
+ }
16519
+ case "between-anchors": {
16520
+ const anchors = spec.anchors;
16521
+ if (!anchors?.top && !anchors?.bottom) return text;
16522
+ const selectLast = (spec.selectAnchor ?? "first") === "last";
16523
+ let topEnd = 0;
16524
+ if (anchors.top) {
16525
+ const topRe = compile(anchors.top.pattern, anchors.top.flags);
16526
+ const topMatches = findAllMatches(topRe, text);
16527
+ if (topMatches.length === 0) {
16528
+ return text;
16604
16529
  }
16605
- return this.invoke(name, fn, input);
16530
+ const chosen = selectLast ? topMatches[topMatches.length - 1] : topMatches[0];
16531
+ topEnd = chosen.index + chosen[0].length;
16606
16532
  }
16607
- // ─── Internal ─────────────────────────────────────
16608
- invoke(scriptName, fn, input) {
16609
- const arity = fn.length;
16610
- const startedAt = Date.now();
16611
- const startedHr = typeof process !== "undefined" && typeof process.hrtime === "function" ? process.hrtime.bigint() : null;
16612
- let result;
16613
- try {
16614
- if (arity >= 3) {
16615
- result = fn(this.scriptState, input, this.sdk);
16616
- } else if (arity === 2) {
16617
- result = fn(this.scriptState, input);
16618
- } else {
16619
- result = fn(input);
16620
- }
16621
- const elapsedUs = startedHr ? Number((process.hrtime.bigint() - startedHr) / 1000n) : 0;
16622
- const timedOut = this.checkBudget(scriptName, elapsedUs);
16623
- this.recordTrace({
16624
- at: startedAt,
16625
- scriptName,
16626
- arity,
16627
- inputSummary: summarizeInput(input),
16628
- ok: true,
16629
- elapsedUs,
16630
- resultSummary: summarizeResult(result),
16631
- ...timedOut ? { timedOut: true } : {}
16632
- });
16633
- return result;
16634
- } catch (e) {
16635
- const elapsedUs = startedHr ? Number((process.hrtime.bigint() - startedHr) / 1000n) : 0;
16636
- const timedOut = this.checkBudget(scriptName, elapsedUs);
16637
- this.recordTrace({
16638
- at: startedAt,
16639
- scriptName,
16640
- arity,
16641
- inputSummary: summarizeInput(input),
16642
- ok: false,
16643
- elapsedUs,
16644
- error: e?.message ? String(e.message).slice(0, 400) : String(e).slice(0, 400),
16645
- ...timedOut ? { timedOut: true } : {}
16646
- });
16647
- throw e;
16533
+ let bottomStart = text.length;
16534
+ if (anchors.bottom) {
16535
+ const bottomRe = compile(anchors.bottom.pattern, anchors.bottom.flags);
16536
+ const searchFrom = topEnd;
16537
+ const suffix = text.slice(searchFrom);
16538
+ const bottomMatches = findAllMatches(bottomRe, suffix);
16539
+ if (bottomMatches.length === 0) {
16540
+ return text;
16648
16541
  }
16542
+ const chosen = selectLast ? bottomMatches[bottomMatches.length - 1] : bottomMatches[0];
16543
+ bottomStart = searchFrom + chosen.index;
16649
16544
  }
16650
- /**
16651
- * Returns true when `elapsedUs` exceeded the configured budget. On the
16652
- * first violation per script (or after the throttle window expires) we
16653
- * emit a single WARN so the operator learns which provider is slow.
16654
- *
16655
- * We deliberately throttle per-scriptName so a chronically-slow
16656
- * detectStatus doesn't spam the log on every PTY frame settle.
16657
- */
16658
- checkBudget(scriptName, elapsedUs) {
16659
- const budgetUs = this.scriptCallBudgetMs * 1e3;
16660
- if (elapsedUs <= budgetUs) return false;
16661
- const now = Date.now();
16662
- const last = this.lastBudgetWarnAt.get(scriptName) ?? 0;
16663
- if (now - last >= BUDGET_WARN_THROTTLE_MS) {
16664
- this.lastBudgetWarnAt.set(scriptName, now);
16665
- LOG.warn(
16666
- "CLI",
16667
- `[${this.cliType}] script ${scriptName} took ${elapsedUs}us, budget ${budgetUs}us`
16668
- );
16669
- }
16670
- return true;
16545
+ if (topEnd >= bottomStart) {
16546
+ return text;
16671
16547
  }
16672
- };
16548
+ return text.slice(topEnd, bottomStart);
16549
+ }
16550
+ default:
16551
+ return text;
16552
+ }
16553
+ }
16554
+ var init_visible_region = __esm({
16555
+ "src/providers/sdk/v1/builders/cli/visible-region.ts"() {
16556
+ "use strict";
16673
16557
  }
16674
16558
  });
16675
16559
 
16676
- // src/cli-adapters/provider-cli-parse.ts
16677
- function sliceFromOffset(text, start) {
16560
+ // src/providers/sdk/v1/builders/cli/detect-status.ts
16561
+ function compile2(re, flags) {
16562
+ try {
16563
+ return new RegExp(re, flags ?? "");
16564
+ } catch (e) {
16565
+ throw new Error(`Invalid regex /${re}/${flags ?? ""}: ${e.message}`);
16566
+ }
16567
+ }
16568
+ function takeTail(text, lines) {
16678
16569
  if (!text) return "";
16679
- if (!Number.isFinite(start) || start <= 0) return text;
16680
- if (start >= text.length) return "";
16681
- return text.slice(start);
16570
+ const split = text.split("\n");
16571
+ if (split.length <= lines) return text;
16572
+ return split.slice(-lines).join("\n");
16682
16573
  }
16683
- function normalizeCliParsedMessages(parsedMessages, _options) {
16684
- return Array.isArray(parsedMessages) ? parsedMessages : [];
16574
+ function scopeText(input, scope, windowLines) {
16575
+ const lines = windowLines && windowLines > 0 ? windowLines : 8;
16576
+ switch (scope) {
16577
+ case "whole-screen":
16578
+ return input.screenText ?? "";
16579
+ case "recent-buffer":
16580
+ return input.tail ?? "";
16581
+ case "last-n-lines":
16582
+ case "live-frame-tail":
16583
+ case void 0:
16584
+ default:
16585
+ return takeTail(input.screenText ?? "", lines);
16586
+ }
16685
16587
  }
16686
- function buildCliParseInput(options) {
16687
- const {
16688
- accumulatedBuffer,
16689
- accumulatedRawBuffer,
16690
- recentOutputBuffer,
16691
- terminalScreenText,
16692
- workingDir,
16693
- providerSessionId,
16694
- historySessionId,
16695
- baseMessages,
16696
- partialResponse,
16697
- isWaitingForResponse,
16698
- scope,
16699
- runtimeSettings,
16700
- spawnAt
16701
- } = options;
16702
- const buffer = scope ? sliceFromOffset(accumulatedBuffer, scope.bufferStart) : accumulatedBuffer;
16703
- const rawBuffer = scope ? sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart) : accumulatedRawBuffer;
16704
- const screenText = terminalScreenText;
16705
- const recentBuffer = buffer.slice(-1e3) || recentOutputBuffer;
16706
- return {
16707
- buffer,
16708
- rawBuffer,
16709
- recentBuffer,
16710
- screenText,
16711
- workspace: workingDir,
16712
- workingDir,
16713
- providerSessionId,
16714
- historySessionId,
16715
- screen: buildCliScreenSnapshot(screenText),
16716
- bufferScreen: buildCliScreenSnapshot(buffer),
16717
- recentScreen: buildCliScreenSnapshot(recentBuffer),
16718
- messages: [...baseMessages],
16719
- partialResponse,
16720
- isWaitingForResponse,
16721
- promptText: scope?.prompt || "",
16722
- settings: { ...runtimeSettings },
16723
- ...typeof spawnAt === "number" && spawnAt > 0 ? { spawnAt } : {}
16724
- };
16588
+ function compileSpinnerMatchers(spec) {
16589
+ return spec.patterns.map((p) => compile2(p.regex, p.flags ?? "i"));
16725
16590
  }
16726
- function summarizeCliTraceText(text, max = 800) {
16727
- const value = sanitizeTerminalText(String(text || ""));
16728
- if (value.length <= max) return value;
16729
- return `\u2026${value.slice(-max)}`;
16591
+ function compileSettledPromptMatchers(spec) {
16592
+ const prompt = compile2(spec.regex, spec.flags ?? "m");
16593
+ const footers = (spec.withFooter ?? []).map((f) => {
16594
+ if (f.kind === "regex") {
16595
+ const re = compile2(f.pattern, f.flags ?? "i");
16596
+ return { test: (s2) => re.test(s2) };
16597
+ }
16598
+ const needle = f.pattern.toLowerCase();
16599
+ return { test: (s2) => s2.toLowerCase().includes(needle) };
16600
+ });
16601
+ return { prompt, footers };
16730
16602
  }
16731
- var init_provider_cli_parse = __esm({
16732
- "src/cli-adapters/provider-cli-parse.ts"() {
16733
- "use strict";
16734
- init_provider_cli_shared();
16735
- }
16736
- });
16603
+ function extractButtonLabels(spec, text) {
16604
+ if (!text) return [];
16605
+ const flags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
16606
+ const buttonRe = compile2(spec.buttonPattern, flags);
16607
+ const labelGroup = Number.isInteger(spec.buttonLabelGroup) && (spec.buttonLabelGroup ?? 0) > 0 ? spec.buttonLabelGroup : 1;
16608
+ const out = [];
16609
+ for (const line of text.split("\n")) {
16610
+ buttonRe.lastIndex = 0;
16611
+ const m = buttonRe.exec(line);
16612
+ if (!m) continue;
16613
+ const captured = m[labelGroup] ?? (labelGroup === 1 && m.length > 2 ? m[m.length - 1] : void 0);
16614
+ if (captured && captured.trim()) out.push(captured.trim());
16615
+ }
16616
+ return out;
16617
+ }
16618
+ function buttonBlockApprovalCue(spec, text) {
16619
+ const labels = extractButtonLabels(spec, text);
16620
+ if (labels.length < 2) return false;
16621
+ if (pickApprovalButton(labels).index < 0) return false;
16622
+ return hasNegativeApprovalOption(labels);
16623
+ }
16624
+ function modalMatches(spec, input) {
16625
+ const text = input.screenText ?? "";
16626
+ const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
16627
+ if (question.test(text)) return true;
16628
+ for (const variant of spec.questionVariants ?? []) {
16629
+ const re = compile2(variant.regex, variant.flags ?? "i");
16630
+ if (re.test(text)) return true;
16631
+ }
16632
+ if (buttonBlockApprovalCue(spec, text)) return true;
16633
+ return false;
16634
+ }
16635
+ function evaluateGroup(group, spec, input, compiled) {
16636
+ switch (group) {
16637
+ case "spinner": {
16638
+ if (!spec.spinner || !compiled.spinner) return null;
16639
+ const text = scopeText(input, spec.spinner.scope, spec.spinner.scopeWindowLines);
16640
+ return compiled.spinner.some((re) => re.test(text)) ? "generating" : null;
16641
+ }
16642
+ case "modal": {
16643
+ if (!spec.modal) return null;
16644
+ return modalMatches(spec.modal, input) ? "waiting_approval" : null;
16645
+ }
16646
+ case "settled-prompt": {
16647
+ if (!spec.settledPrompt || !compiled.settled) return null;
16648
+ const text = scopeText(input, spec.settledPrompt.scope, spec.settledPrompt.scopeWindowLines);
16649
+ if (!compiled.settled.prompt.test(text)) return null;
16650
+ if (compiled.settled.footers.length === 0) return "idle";
16651
+ return compiled.settled.footers.every((f) => f.test(text)) ? "idle" : null;
16652
+ }
16653
+ // Groups declared in the catalog but not yet implemented as builder steps
16654
+ // return null so they are no-ops in dispatch — declaring them in `order`
16655
+ // is forward-compatible. Phase 2 Week 8+ will wire them.
16656
+ case "cue-ordering":
16657
+ case "error-detection":
16658
+ case "approval-stitching":
16659
+ return null;
16660
+ default:
16661
+ return null;
16662
+ }
16663
+ }
16664
+ function buildDetectStatusFromTui(spec) {
16665
+ const compiledSpinner = spec.spinner ? compileSpinnerMatchers(spec.spinner) : null;
16666
+ const compiledSettled = spec.settledPrompt ? compileSettledPromptMatchers(spec.settledPrompt) : null;
16667
+ const compiled = { spinner: compiledSpinner, settled: compiledSettled };
16668
+ const order = spec.dispatchOrder?.order && spec.dispatchOrder.order.length > 0 ? spec.dispatchOrder.order : DEFAULT_ORDER;
16669
+ return function detectStatus(input) {
16670
+ const effectiveInput = spec.visibleRegion ? {
16671
+ ...input,
16672
+ screenText: applyVisibleRegion(spec.visibleRegion, input.screenText ?? ""),
16673
+ tail: applyVisibleRegion(spec.visibleRegion, input.tail)
16674
+ } : input;
16675
+ for (const group of order) {
16676
+ const verdict = evaluateGroup(group, spec, effectiveInput, compiled);
16677
+ if (verdict !== null) return verdict;
16678
+ }
16679
+ return null;
16680
+ };
16681
+ }
16682
+ var DEFAULT_ORDER;
16683
+ var init_detect_status = __esm({
16684
+ "src/providers/sdk/v1/builders/cli/detect-status.ts"() {
16685
+ "use strict";
16686
+ init_visible_region();
16687
+ init_approval_utils();
16688
+ DEFAULT_ORDER = ["spinner", "modal", "settled-prompt"];
16689
+ }
16690
+ });
16737
16691
 
16738
- // src/cli-adapters/cli-state-engine.ts
16739
- var SCRIPT_STATUS_DEBOUNCE_MS, MAX_FINISH_RETRIES, FINISH_RETRY_DELAY_MS, MAX_TRACE_ENTRIES, APPROVAL_EXIT_TIMEOUT_MS, IDLE_CONFIRMATION_GRACE_MS, CliStateEngine;
16740
- var init_cli_state_engine = __esm({
16741
- "src/cli-adapters/cli-state-engine.ts"() {
16692
+ // src/providers/sdk/v1/builders/cli/parse-approval.ts
16693
+ function compile3(re, flags) {
16694
+ try {
16695
+ return new RegExp(re, flags);
16696
+ } catch (e) {
16697
+ throw new Error(`Invalid regex /${re}/${flags ?? ""}: ${e.message}`);
16698
+ }
16699
+ }
16700
+ function findQuestionLineIndex(spec, lines) {
16701
+ const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
16702
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
16703
+ if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
16704
+ }
16705
+ for (const variant of spec.questionVariants ?? []) {
16706
+ const re = compile3(variant.regex, variant.flags ?? "i");
16707
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
16708
+ if (re.test(lines[i])) return { index: i, matchedSource: variant.label ?? "variant" };
16709
+ }
16710
+ }
16711
+ return null;
16712
+ }
16713
+ function scopeLines(spec, lines, questionIndex) {
16714
+ const scope = spec.scope ?? "between-last-two-separators";
16715
+ if (scope === "whole-screen") {
16716
+ return { start: 0, end: lines.length };
16717
+ }
16718
+ if (scope === "window-around-question") {
16719
+ const window = spec.scopeWindowLines ?? 16;
16720
+ return {
16721
+ start: Math.max(0, questionIndex - 2),
16722
+ end: Math.min(lines.length, questionIndex + window)
16723
+ };
16724
+ }
16725
+ let lastSep = -1;
16726
+ let prevSep = -1;
16727
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
16728
+ if (SEPARATOR_RE.test(lines[i].trim())) {
16729
+ if (lastSep < 0) lastSep = i;
16730
+ else if (prevSep < 0) {
16731
+ prevSep = i;
16732
+ break;
16733
+ }
16734
+ }
16735
+ }
16736
+ if (lastSep >= 0 && prevSep >= 0) {
16737
+ return { start: prevSep, end: lastSep + 1 };
16738
+ }
16739
+ return {
16740
+ start: Math.max(0, questionIndex - 4),
16741
+ end: Math.min(lines.length, questionIndex + 16)
16742
+ };
16743
+ }
16744
+ function extractButtons(spec, lines, windowStart, windowEnd) {
16745
+ const buttonRe = compile3(spec.buttonPattern, spec.buttonFlags ?? "m");
16746
+ const out = [];
16747
+ const labelGroup = Number.isInteger(spec.buttonLabelGroup) && (spec.buttonLabelGroup ?? 0) > 0 ? spec.buttonLabelGroup : 1;
16748
+ let i = windowStart;
16749
+ while (i < windowEnd) {
16750
+ const line = lines[i];
16751
+ const m = buttonRe.exec(line);
16752
+ const captured = m?.[labelGroup] ?? (labelGroup === 1 && m && m.length > 2 ? m[m.length - 1] : void 0);
16753
+ if (m && captured) {
16754
+ let label = captured.trim();
16755
+ if (spec.continuationLines) {
16756
+ let j = i + 1;
16757
+ while (j < windowEnd) {
16758
+ const next = lines[j];
16759
+ if (!next.trim()) break;
16760
+ if (buttonRe.test(next)) break;
16761
+ if (!/^\s+/.test(next)) break;
16762
+ label += " " + next.trim();
16763
+ j += 1;
16764
+ }
16765
+ i = j;
16766
+ } else {
16767
+ i += 1;
16768
+ }
16769
+ out.push(label);
16770
+ } else {
16771
+ i += 1;
16772
+ }
16773
+ }
16774
+ return out;
16775
+ }
16776
+ function buildMessage(spec, lines, questionIndex, windowStart, windowEnd) {
16777
+ const messageParts = [];
16778
+ if (spec.contextHeader) {
16779
+ const ctxRe = compile3(spec.contextHeader.regex, (spec.contextHeader.flags ?? "i") + "m");
16780
+ const haystack = lines.slice(windowStart, windowEnd).join("\n");
16781
+ const m = ctxRe.exec(haystack);
16782
+ if (m) messageParts.push(m[1] ? m[1].trim() : m[0].trim());
16783
+ }
16784
+ messageParts.push(lines[questionIndex].trim());
16785
+ return messageParts.filter(Boolean).join(" \u2014 ");
16786
+ }
16787
+ function extractInlineButtons(spec, lines, windowStart, windowEnd) {
16788
+ if (!spec.inlineButtonPattern) return [];
16789
+ const declaredFlags = spec.inlineButtonFlags ?? "gi";
16790
+ const flags = declaredFlags.includes("g") ? declaredFlags : declaredFlags + "g";
16791
+ const re = compile3(spec.inlineButtonPattern, flags);
16792
+ const out = [];
16793
+ for (let i = windowStart; i < windowEnd; i += 1) {
16794
+ let m;
16795
+ re.lastIndex = 0;
16796
+ while ((m = re.exec(lines[i])) !== null) {
16797
+ const label = (m[1] ?? m[0]).trim();
16798
+ if (label && !out.includes(label)) out.push(label);
16799
+ if (m.index === re.lastIndex) re.lastIndex += 1;
16800
+ }
16801
+ }
16802
+ return out;
16803
+ }
16804
+ function buildParseApprovalFromTui(spec, visibleRegion) {
16805
+ const minButtons = spec.minButtons ?? 2;
16806
+ return function parseApproval(input) {
16807
+ const rawText = input.screenText ?? input.buffer ?? "";
16808
+ if (!rawText) return null;
16809
+ const text = visibleRegion ? applyVisibleRegion(visibleRegion, rawText) : rawText;
16810
+ const lines = text.split("\n");
16811
+ const question = findQuestionLineIndex(spec, lines);
16812
+ if (!question) return null;
16813
+ const { start, end } = scopeLines(spec, lines, question.index);
16814
+ if (question.index < start || question.index >= end) return null;
16815
+ let buttons = extractButtons(spec, lines, question.index + 1, end);
16816
+ if (buttons.length < minButtons && spec.inlineButtonPattern) {
16817
+ buttons = extractInlineButtons(spec, lines, question.index, end);
16818
+ }
16819
+ if (buttons.length < minButtons) return null;
16820
+ const message = buildMessage(spec, lines, question.index, start, end);
16821
+ return { message, buttons };
16822
+ };
16823
+ }
16824
+ var SEPARATOR_RE;
16825
+ var init_parse_approval = __esm({
16826
+ "src/providers/sdk/v1/builders/cli/parse-approval.ts"() {
16827
+ "use strict";
16828
+ init_visible_region();
16829
+ SEPARATOR_RE = /^(?:─|━|═|━){10,}\s*$/;
16830
+ }
16831
+ });
16832
+
16833
+ // src/providers/sdk/v1/builders/cli/parse-session.ts
16834
+ import * as crypto3 from "crypto";
16835
+ function stripAnsi2(text) {
16836
+ return String(text || "").replace(/\x1b\[(\d*)C/g, (_m, n) => " ".repeat(Math.max(1, Number(n) || 1))).replace(/\x1b\[\d*D/g, "").replace(ANSI_RE, "").replace(OSC_RE, "").replace(/\x1b[P^_X][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b(?:[@-Z\\-_])/g, "");
16837
+ }
16838
+ function splitLines(text) {
16839
+ return stripAnsi2(text).replace(//g, "").split(/\r?\n/).map((l) => l.replace(/\s+$/, ""));
16840
+ }
16841
+ function pickInputText(input, scope) {
16842
+ if (!input) return "";
16843
+ if (scope === "screen") return String(input.screenText || input.screen?.text || "");
16844
+ if (scope === "tail") return String(input.tail || input.recentBuffer || input.buffer || "");
16845
+ return String(input.buffer || input.rawBuffer || input.screenText || "");
16846
+ }
16847
+ function stableHash(value) {
16848
+ return crypto3.createHash("sha1").update(String(value || "")).digest("hex").slice(0, 12);
16849
+ }
16850
+ function normalizeMessageIdentity(messages, status) {
16851
+ const list = Array.isArray(messages) ? messages : [];
16852
+ let turnIndex = -1;
16853
+ return list.map((message, index) => {
16854
+ const role = message?.role || "assistant";
16855
+ const kind = message?.kind || "standard";
16856
+ const content = typeof message?.content === "string" ? message.content : "";
16857
+ if (role === "user" || turnIndex < 0) turnIndex += 1;
16858
+ const seed = [role, kind, "", index, content].join("\n");
16859
+ const providerUnitKey = `v2-pty:${role}:${kind}:${index}:${stableHash(seed)}`;
16860
+ const bubbleId = `bubble:${providerUnitKey}`;
16861
+ const turnKey = `turn:${turnIndex}`;
16862
+ const isStreamingTail = status === "generating" && role === "assistant" && index === list.length - 1;
16863
+ return {
16864
+ ...message,
16865
+ providerUnitKey,
16866
+ bubbleId,
16867
+ sequence: index,
16868
+ _turnKey: turnKey,
16869
+ bubbleState: isStreamingTail ? "streaming" : "final"
16870
+ };
16871
+ });
16872
+ }
16873
+ function buildParseSessionFromTui(spec) {
16874
+ if (!spec.transcriptPty) {
16875
+ throw new Error("buildParseSessionFromTui: spec.transcriptPty is required");
16876
+ }
16877
+ const assistantRe = new RegExp(spec.transcriptPty.assistantPrefix.regex, spec.transcriptPty.assistantPrefix.flags || "");
16878
+ const userRe = spec.transcriptPty.userPrefix ? new RegExp(spec.transcriptPty.userPrefix.regex, spec.transcriptPty.userPrefix.flags || "") : null;
16879
+ const toolRe = spec.transcriptPty.toolPrefix ? new RegExp(spec.transcriptPty.toolPrefix.regex, spec.transcriptPty.toolPrefix.flags || "") : null;
16880
+ const toolSkip = spec.transcriptPty.toolPrefix?.skip ?? false;
16881
+ const chromeRes = (spec.transcriptPty.chromePatterns || []).map((p) => new RegExp(p.regex, p.flags || ""));
16882
+ const requireIndentForContinuation = spec.transcriptPty.continuationLine?.indented ?? false;
16883
+ const stripLeadingChrome = spec.transcriptPty.stripLeadingChrome ?? true;
16884
+ const scope = spec.transcriptPty.scope ?? "buffer";
16885
+ const detectStatus = spec.spinner || spec.settledPrompt || spec.modal || spec.dispatchOrder ? buildDetectStatusFromTui({
16886
+ spinner: spec.spinner,
16887
+ settledPrompt: spec.settledPrompt,
16888
+ modal: spec.modal,
16889
+ dispatchOrder: spec.dispatchOrder
16890
+ }) : () => null;
16891
+ const parseApproval = spec.modal ? buildParseApprovalFromTui(spec.modal) : () => null;
16892
+ const sessionIdRe = spec.sessionIdExtraction ? new RegExp(
16893
+ spec.sessionIdExtraction.regex,
16894
+ spec.sessionIdExtraction.flags ?? "i"
16895
+ ) : null;
16896
+ const sessionIdScope = spec.sessionIdExtraction?.scope ?? "tail";
16897
+ return function parseSession(input) {
16898
+ const status = detectStatus(input) ?? "idle";
16899
+ const modal = parseApproval(input);
16900
+ const text = pickInputText(input, scope);
16901
+ const lines = splitLines(text);
16902
+ const messages = [];
16903
+ let seenFirstRoleLine = !stripLeadingChrome;
16904
+ for (const raw of lines) {
16905
+ const line = raw;
16906
+ if (line.trim() === "") {
16907
+ continue;
16908
+ }
16909
+ let isChrome = false;
16910
+ for (const cre of chromeRes) {
16911
+ if (cre.test(line)) {
16912
+ isChrome = true;
16913
+ break;
16914
+ }
16915
+ }
16916
+ if (isChrome) continue;
16917
+ const userMatch = userRe ? line.match(userRe) : null;
16918
+ const toolMatch = toolRe ? line.match(toolRe) : null;
16919
+ const assistMatch = line.match(assistantRe);
16920
+ if (userMatch) {
16921
+ seenFirstRoleLine = true;
16922
+ const content = (userMatch[1] ?? userMatch[0]).trim();
16923
+ if (content) messages.push({ role: "user", kind: "standard", content });
16924
+ continue;
16925
+ }
16926
+ if (toolMatch) {
16927
+ seenFirstRoleLine = true;
16928
+ if (toolSkip) continue;
16929
+ const content = (toolMatch[1] ?? toolMatch[0]).trim();
16930
+ if (content) messages.push({ role: "assistant", kind: "tool", content });
16931
+ continue;
16932
+ }
16933
+ if (assistMatch) {
16934
+ seenFirstRoleLine = true;
16935
+ const content = (assistMatch[1] ?? assistMatch[0]).trim();
16936
+ if (content) messages.push({ role: "assistant", kind: "standard", content });
16937
+ continue;
16938
+ }
16939
+ if (!seenFirstRoleLine) continue;
16940
+ if (requireIndentForContinuation && !/^\s/.test(line)) continue;
16941
+ const last = messages[messages.length - 1];
16942
+ if (!last) continue;
16943
+ const cont = line.replace(/^\s+/, "");
16944
+ if (cont) last.content = last.content ? `${last.content}
16945
+ ${cont}` : cont;
16946
+ }
16947
+ const result = {
16948
+ status,
16949
+ messages,
16950
+ activeModal: modal,
16951
+ modal,
16952
+ parsedStatus: status
16953
+ };
16954
+ if (sessionIdRe) {
16955
+ const haystack = stripAnsi2(pickInputText(input, sessionIdScope));
16956
+ const match = haystack.match(sessionIdRe);
16957
+ const captured = match?.[1]?.trim();
16958
+ if (captured) result.providerSessionId = captured;
16959
+ }
16960
+ return result;
16961
+ };
16962
+ }
16963
+ var ANSI_RE, OSC_RE;
16964
+ var init_parse_session = __esm({
16965
+ "src/providers/sdk/v1/builders/cli/parse-session.ts"() {
16966
+ "use strict";
16967
+ init_detect_status();
16968
+ init_parse_approval();
16969
+ ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
16970
+ OSC_RE = /\x1b\][^\x07\x1b\n]*(?:\x07|\x1b\\|(?=\n|$))/g;
16971
+ }
16972
+ });
16973
+
16974
+ // src/cli-adapters/cli-script-runner.ts
16975
+ function summarizeInput(input) {
16976
+ const screenText = typeof input?.screenText === "string" ? input.screenText : "";
16977
+ const rawBuffer = typeof input?.rawBuffer === "string" ? input.rawBuffer : "";
16978
+ const tail = typeof input?.tail === "string" ? input.tail : "";
16979
+ return {
16980
+ screenTextLen: screenText.length,
16981
+ rawBufferLen: rawBuffer.length,
16982
+ tailLen: tail.length,
16983
+ isWaitingForResponse: typeof input?.isWaitingForResponse === "boolean" ? input.isWaitingForResponse : void 0,
16984
+ screenTextHead: screenText ? screenText.slice(0, 200) : void 0
16985
+ };
16986
+ }
16987
+ function summarizeResult(result) {
16988
+ try {
16989
+ const json = JSON.stringify(result);
16990
+ return json && json.length > 400 ? `${json.slice(0, 400)}\u2026[truncated ${json.length - 400}]` : json ?? "undefined";
16991
+ } catch (e) {
16992
+ return `<unserializable: ${e?.message || e}>`;
16993
+ }
16994
+ }
16995
+ var TRACE_RING_CAPACITY, DEFAULT_SCRIPT_CALL_BUDGET_MS, BUDGET_WARN_THROTTLE_MS, CliScriptRunner;
16996
+ var init_cli_script_runner = __esm({
16997
+ "src/cli-adapters/cli-script-runner.ts"() {
16742
16998
  "use strict";
16743
16999
  init_logger();
16744
- init_provider_cli_parse();
16745
17000
  init_provider_cli_shared();
16746
- SCRIPT_STATUS_DEBOUNCE_MS = 3e3;
16747
- MAX_FINISH_RETRIES = 2;
16748
- FINISH_RETRY_DELAY_MS = 300;
16749
- MAX_TRACE_ENTRIES = 250;
16750
- APPROVAL_EXIT_TIMEOUT_MS = 6e4;
16751
- IDLE_CONFIRMATION_GRACE_MS = 2e3;
16752
- CliStateEngine = class {
16753
- constructor(provider, runner, transport, callbacks, timeouts) {
16754
- this.provider = provider;
16755
- this.runner = runner;
16756
- this.transport = transport;
16757
- this.callbacks = callbacks;
16758
- this.timeouts = timeouts;
17001
+ init_detect_status();
17002
+ init_parse_approval();
17003
+ init_parse_session();
17004
+ TRACE_RING_CAPACITY = 64;
17005
+ DEFAULT_SCRIPT_CALL_BUDGET_MS = 50;
17006
+ BUDGET_WARN_THROTTLE_MS = 3e4;
17007
+ CliScriptRunner = class {
17008
+ scripts = {};
17009
+ scriptState = null;
17010
+ _parseErrorMessage = null;
17011
+ cliType;
17012
+ sdk = {};
17013
+ invocationTrace = [];
17014
+ /** Per-invocation wall-clock budget (ms). Configurable via setScriptCallBudget. */
17015
+ scriptCallBudgetMs = DEFAULT_SCRIPT_CALL_BUDGET_MS;
17016
+ /** Last WARN emit time per scriptName, used to throttle repeated budget violations. */
17017
+ lastBudgetWarnAt = /* @__PURE__ */ new Map();
17018
+ constructor(cliType) {
17019
+ this.cliType = cliType;
17020
+ }
17021
+ /** Returns the most-recent script invocation traces (oldest → newest). */
17022
+ getInvocationTrace() {
17023
+ return this.invocationTrace.slice();
17024
+ }
17025
+ /** Clear the trace ring — used by tests and after PTY reset. */
17026
+ clearInvocationTrace() {
17027
+ this.invocationTrace = [];
16759
17028
  }
16760
- // ── Status ───────────────────────────────────────
16761
- currentStatus = "starting";
16762
- isWaitingForResponse = false;
16763
- currentTurnScope = null;
16764
- activeModal = null;
16765
- // ── Approval ─────────────────────────────────────
16766
- lastApprovalResolvedAt = 0;
16767
- lastResolvedModalMessage = "";
16768
17029
  /**
16769
- * Monotonic counter bumped every time the FSM *enters* waiting_approval
16770
- * with a freshly captured modal (see `applyWaitingApproval`). It is the
16771
- * single discriminator between "the same approval re-observed across TUI
16772
- * paint flaps" and "a genuinely new, distinct approval".
16773
- *
16774
- * The message-equality cooldown below (`lastResolvedModalMessage`) cannot
16775
- * tell these apart on its own: claude-cli routinely presents consecutive
16776
- * approvals whose modal message text is identical (e.g. two back-to-back
16777
- * Bash-command prompts). When that second approval arrived inside
16778
- * `approvalCooldown`, the message-equality guard silently swallowed the
16779
- * key write and the approval stuck forever — fatal under auto-approval.
17030
+ * Configure the wall-clock budget (ms) applied to every script invocation.
16780
17031
  *
16781
- * `approvalEntrySeq` increments on every fresh entry; `lastResolvedEntrySeq`
16782
- * records which entry the cooldown belongs to. We only short-circuit the
16783
- * write when we are still resolving *that same* entry a new entry (new
16784
- * seq) is always a real, distinct approval and must be written.
16785
- */
16786
- approvalEntrySeq = 0;
16787
- lastResolvedEntrySeq = -1;
16788
- /**
16789
- * When the engine previously held a modal but the latest parse failed
16790
- * to extract one, we record the timestamp here and only drop the modal
16791
- * after the configured `approvalCooldown` to avoid flapping between
16792
- * waiting_approval and generating on every Claude TUI redraw — that
16793
- * flapping is what fed auto-approve a fresh modal signature on each
16794
- * paint and made the engine type "1" repeatedly into the prompt.
17032
+ * Out-of-range or non-finite values are clamped to [1, 5000] and the
17033
+ * default (50ms) is used as a fallback. The budget is enforced per-call,
17034
+ * not aggregated it does not abort a runaway script (Node CJS cannot
17035
+ * interrupt synchronous code without a worker thread). Instead, an
17036
+ * exceeded budget flags the trace entry with `timedOut: true` and emits
17037
+ * a throttled WARN naming the script and elapsed time so an operator
17038
+ * can identify which provider is hanging the settle loop.
16795
17039
  */
16796
- modalLostAt = 0;
16797
- approvalExitTimeout = null;
16798
- // ── Response tracking ────────────────────────────
16799
- responseEpoch = 0;
16800
- submitPendingUntil = 0;
16801
- responseSettleIgnoreUntil = 0;
16802
- submitRetryUsed = false;
16803
- submitRetryPromptSnippet = "";
16804
- finishRetryCount = 0;
16805
- providerErrorMessage = null;
16806
- providerErrorReason = null;
16807
- // ── Timers ───────────────────────────────────────
16808
- settleTimer = null;
16809
- idleTimeout = null;
16810
- finishRetryTimer = null;
16811
- providerErrorRetryTimer = null;
16812
- providerErrorRetryKey = "";
16813
- // ── Debounce ─────────────────────────────────────
16814
- pendingScriptStatus = null;
16815
- pendingScriptStatusSince = 0;
16816
- pendingScriptStatusTimer = null;
16817
- // ── Idle candidate ───────────────────────────────
16818
- idleFinishCandidate = null;
16819
- // ── Idle confirmation grace ──────────────────────
16820
- /**
16821
- * `finishResponse` produces the `generating → idle` transition that
16822
- * coordinators interpret as "task complete". Some providers (antigravity-
16823
- * cli observed in the wild) briefly paint a screen that looks like an
16824
- * idle prompt between tool result frames while still actively running,
16825
- * which fired `response_finished` and broke completion semantics.
16826
- * We defer the actual idle transition by IDLE_CONFIRMATION_GRACE_MS and
16827
- * cancel it if the scripted detection re-detects generating during that
17040
+ setScriptCallBudget(ms) {
17041
+ if (typeof ms !== "number" || !Number.isFinite(ms)) {
17042
+ this.scriptCallBudgetMs = DEFAULT_SCRIPT_CALL_BUDGET_MS;
17043
+ return;
17044
+ }
17045
+ const clamped = Math.max(1, Math.min(5e3, Math.floor(ms)));
17046
+ this.scriptCallBudgetMs = clamped;
17047
+ }
17048
+ /** Test/debug accessor — current effective budget in ms. */
17049
+ getScriptCallBudgetMs() {
17050
+ return this.scriptCallBudgetMs;
17051
+ }
17052
+ recordTrace(entry) {
17053
+ this.invocationTrace.push(entry);
17054
+ if (this.invocationTrace.length > TRACE_RING_CAPACITY) {
17055
+ this.invocationTrace.splice(0, this.invocationTrace.length - TRACE_RING_CAPACITY);
17056
+ }
17057
+ }
17058
+ // ─── Script lifecycle ─────────────────────────────
17059
+ setScripts(scripts, providerTui) {
17060
+ this.sdk = this.buildSdk(providerTui);
17061
+ const tui = providerTui;
17062
+ const enriched = { ...scripts };
17063
+ if (typeof enriched.detectStatus !== "function" && this.sdk.declarativeDetectStatus) {
17064
+ enriched.detectStatus = this.sdk.declarativeDetectStatus;
17065
+ }
17066
+ if (typeof enriched.parseApproval !== "function" && this.sdk.declarativeParseApproval) {
17067
+ enriched.parseApproval = this.sdk.declarativeParseApproval;
17068
+ }
17069
+ if (typeof enriched.parseSession !== "function" && tui?.transcriptPty) {
17070
+ try {
17071
+ const synth = buildParseSessionFromTui({
17072
+ spinner: tui.spinner,
17073
+ settledPrompt: tui.settledPrompt,
17074
+ modal: tui.modal,
17075
+ dispatchOrder: tui.dispatchOrder,
17076
+ transcriptPty: tui.transcriptPty
17077
+ });
17078
+ enriched.parseSession = ((input) => {
17079
+ const out = synth(input);
17080
+ return {
17081
+ ...out,
17082
+ messages: normalizeMessageIdentity(out.messages, out.status ?? "idle")
17083
+ };
17084
+ });
17085
+ } catch (e) {
17086
+ LOG.warn("CLI", `[${this.cliType}] buildParseSessionFromTui failed: ${e?.message || e}`);
17087
+ }
17088
+ }
17089
+ this.scripts = enriched;
17090
+ this._parseErrorMessage = null;
17091
+ this.scriptState = typeof enriched.createState === "function" ? enriched.createState() ?? null : null;
17092
+ }
17093
+ buildSdk(providerTui) {
17094
+ const tui = providerTui;
17095
+ if (!tui) return {};
17096
+ const sdk = {};
17097
+ if (tui.spinner || tui.settledPrompt || tui.modal || tui.dispatchOrder) {
17098
+ try {
17099
+ sdk.declarativeDetectStatus = buildDetectStatusFromTui({
17100
+ spinner: tui.spinner,
17101
+ settledPrompt: tui.settledPrompt,
17102
+ modal: tui.modal,
17103
+ dispatchOrder: tui.dispatchOrder
17104
+ });
17105
+ } catch (e) {
17106
+ LOG.warn("CLI", `[${this.cliType}] buildDetectStatusFromTui failed: ${e?.message || e}`);
17107
+ }
17108
+ }
17109
+ if (tui.modal) {
17110
+ try {
17111
+ sdk.declarativeParseApproval = buildParseApprovalFromTui(tui.modal);
17112
+ } catch (e) {
17113
+ LOG.warn("CLI", `[${this.cliType}] buildParseApprovalFromTui failed: ${e?.message || e}`);
17114
+ }
17115
+ }
17116
+ return sdk;
17117
+ }
17118
+ /** Reset per-session state — called when the PTY process exits. */
17119
+ resetSessionState() {
17120
+ this.scriptState = null;
17121
+ this.invocationTrace = [];
17122
+ this.lastBudgetWarnAt.clear();
17123
+ }
17124
+ // ─── Script access (for reflection and test patching) ────────────────────
17125
+ /** Returns the live scripts object. Direct property assignment on this object
17126
+ * patches individual scripts without replacing others (used in tests). */
17127
+ get cliScripts() {
17128
+ return this.scripts;
17129
+ }
17130
+ // ─── Capability checks ────────────────────────────
17131
+ hasDetectStatus() {
17132
+ return typeof this.scripts.detectStatus === "function";
17133
+ }
17134
+ hasParseSession() {
17135
+ return typeof this.scripts.parseSession === "function";
17136
+ }
17137
+ getScriptNames() {
17138
+ return listCliScriptNames(this.scripts);
17139
+ }
17140
+ // ─── Error state ──────────────────────────────────
17141
+ get parseErrorMessage() {
17142
+ return this._parseErrorMessage;
17143
+ }
17144
+ clearParseError() {
17145
+ this._parseErrorMessage = null;
17146
+ }
17147
+ // ─── Script invocation ────────────────────────────
17148
+ detectStatus(input) {
17149
+ if (!this.scripts.detectStatus) return null;
17150
+ try {
17151
+ return this.invoke("detectStatus", this.scripts.detectStatus, input);
17152
+ } catch (e) {
17153
+ LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e?.message || e}`);
17154
+ return null;
17155
+ }
17156
+ }
17157
+ parseApproval(input) {
17158
+ if (!this.scripts.parseApproval) return null;
17159
+ try {
17160
+ return this.invoke(
17161
+ "parseApproval",
17162
+ this.scripts.parseApproval,
17163
+ input
17164
+ );
17165
+ } catch (e) {
17166
+ LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e?.message || e}`);
17167
+ return null;
17168
+ }
17169
+ }
17170
+ parseSession(input) {
17171
+ if (!this.scripts.parseSession) {
17172
+ this._parseErrorMessage = `${this.cliType} parseSession unavailable`;
17173
+ return null;
17174
+ }
17175
+ try {
17176
+ const result = this.invoke("parseSession", this.scripts.parseSession, input);
17177
+ this._parseErrorMessage = null;
17178
+ return result && typeof result === "object" ? result : null;
17179
+ } catch (e) {
17180
+ this._parseErrorMessage = e?.message || String(e);
17181
+ LOG.warn("CLI", `[${this.cliType}] parseSession error: ${this._parseErrorMessage}`);
17182
+ return null;
17183
+ }
17184
+ }
17185
+ /**
17186
+ * Invoke an arbitrary named script (e.g. setModel, openModelPicker).
17187
+ * Throws if the script is not available.
17188
+ */
17189
+ invokeByName(name, input) {
17190
+ const fn = this.scripts[name];
17191
+ if (typeof fn !== "function") {
17192
+ throw new Error(`CLI script '${name}' not available`);
17193
+ }
17194
+ return this.invoke(name, fn, input);
17195
+ }
17196
+ // ─── Internal ─────────────────────────────────────
17197
+ invoke(scriptName, fn, input) {
17198
+ const arity = fn.length;
17199
+ const startedAt = Date.now();
17200
+ const startedHr = typeof process !== "undefined" && typeof process.hrtime === "function" ? process.hrtime.bigint() : null;
17201
+ let result;
17202
+ try {
17203
+ if (arity >= 3) {
17204
+ result = fn(this.scriptState, input, this.sdk);
17205
+ } else if (arity === 2) {
17206
+ result = fn(this.scriptState, input);
17207
+ } else {
17208
+ result = fn(input);
17209
+ }
17210
+ const elapsedUs = startedHr ? Number((process.hrtime.bigint() - startedHr) / 1000n) : 0;
17211
+ const timedOut = this.checkBudget(scriptName, elapsedUs);
17212
+ this.recordTrace({
17213
+ at: startedAt,
17214
+ scriptName,
17215
+ arity,
17216
+ inputSummary: summarizeInput(input),
17217
+ ok: true,
17218
+ elapsedUs,
17219
+ resultSummary: summarizeResult(result),
17220
+ ...timedOut ? { timedOut: true } : {}
17221
+ });
17222
+ return result;
17223
+ } catch (e) {
17224
+ const elapsedUs = startedHr ? Number((process.hrtime.bigint() - startedHr) / 1000n) : 0;
17225
+ const timedOut = this.checkBudget(scriptName, elapsedUs);
17226
+ this.recordTrace({
17227
+ at: startedAt,
17228
+ scriptName,
17229
+ arity,
17230
+ inputSummary: summarizeInput(input),
17231
+ ok: false,
17232
+ elapsedUs,
17233
+ error: e?.message ? String(e.message).slice(0, 400) : String(e).slice(0, 400),
17234
+ ...timedOut ? { timedOut: true } : {}
17235
+ });
17236
+ throw e;
17237
+ }
17238
+ }
17239
+ /**
17240
+ * Returns true when `elapsedUs` exceeded the configured budget. On the
17241
+ * first violation per script (or after the throttle window expires) we
17242
+ * emit a single WARN so the operator learns which provider is slow.
17243
+ *
17244
+ * We deliberately throttle per-scriptName so a chronically-slow
17245
+ * detectStatus doesn't spam the log on every PTY frame settle.
17246
+ */
17247
+ checkBudget(scriptName, elapsedUs) {
17248
+ const budgetUs = this.scriptCallBudgetMs * 1e3;
17249
+ if (elapsedUs <= budgetUs) return false;
17250
+ const now = Date.now();
17251
+ const last = this.lastBudgetWarnAt.get(scriptName) ?? 0;
17252
+ if (now - last >= BUDGET_WARN_THROTTLE_MS) {
17253
+ this.lastBudgetWarnAt.set(scriptName, now);
17254
+ LOG.warn(
17255
+ "CLI",
17256
+ `[${this.cliType}] script ${scriptName} took ${elapsedUs}us, budget ${budgetUs}us`
17257
+ );
17258
+ }
17259
+ return true;
17260
+ }
17261
+ };
17262
+ }
17263
+ });
17264
+
17265
+ // src/cli-adapters/provider-cli-parse.ts
17266
+ function sliceFromOffset(text, start) {
17267
+ if (!text) return "";
17268
+ if (!Number.isFinite(start) || start <= 0) return text;
17269
+ if (start >= text.length) return "";
17270
+ return text.slice(start);
17271
+ }
17272
+ function normalizeCliParsedMessages(parsedMessages, _options) {
17273
+ return Array.isArray(parsedMessages) ? parsedMessages : [];
17274
+ }
17275
+ function buildCliParseInput(options) {
17276
+ const {
17277
+ accumulatedBuffer,
17278
+ accumulatedRawBuffer,
17279
+ recentOutputBuffer,
17280
+ terminalScreenText,
17281
+ workingDir,
17282
+ providerSessionId,
17283
+ historySessionId,
17284
+ baseMessages,
17285
+ partialResponse,
17286
+ isWaitingForResponse,
17287
+ scope,
17288
+ runtimeSettings,
17289
+ spawnAt
17290
+ } = options;
17291
+ const buffer = scope ? sliceFromOffset(accumulatedBuffer, scope.bufferStart) : accumulatedBuffer;
17292
+ const rawBuffer = scope ? sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart) : accumulatedRawBuffer;
17293
+ const screenText = terminalScreenText;
17294
+ const recentBuffer = buffer.slice(-1e3) || recentOutputBuffer;
17295
+ return {
17296
+ buffer,
17297
+ rawBuffer,
17298
+ recentBuffer,
17299
+ screenText,
17300
+ workspace: workingDir,
17301
+ workingDir,
17302
+ providerSessionId,
17303
+ historySessionId,
17304
+ screen: buildCliScreenSnapshot(screenText),
17305
+ bufferScreen: buildCliScreenSnapshot(buffer),
17306
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
17307
+ messages: [...baseMessages],
17308
+ partialResponse,
17309
+ isWaitingForResponse,
17310
+ promptText: scope?.prompt || "",
17311
+ settings: { ...runtimeSettings },
17312
+ ...typeof spawnAt === "number" && spawnAt > 0 ? { spawnAt } : {}
17313
+ };
17314
+ }
17315
+ function summarizeCliTraceText(text, max = 800) {
17316
+ const value = sanitizeTerminalText(String(text || ""));
17317
+ if (value.length <= max) return value;
17318
+ return `\u2026${value.slice(-max)}`;
17319
+ }
17320
+ var init_provider_cli_parse = __esm({
17321
+ "src/cli-adapters/provider-cli-parse.ts"() {
17322
+ "use strict";
17323
+ init_provider_cli_shared();
17324
+ }
17325
+ });
17326
+
17327
+ // src/cli-adapters/cli-state-engine.ts
17328
+ var SCRIPT_STATUS_DEBOUNCE_MS, MAX_FINISH_RETRIES, FINISH_RETRY_DELAY_MS, MAX_TRACE_ENTRIES, APPROVAL_EXIT_TIMEOUT_MS, IDLE_CONFIRMATION_GRACE_MS, CliStateEngine;
17329
+ var init_cli_state_engine = __esm({
17330
+ "src/cli-adapters/cli-state-engine.ts"() {
17331
+ "use strict";
17332
+ init_logger();
17333
+ init_provider_cli_parse();
17334
+ init_provider_cli_shared();
17335
+ SCRIPT_STATUS_DEBOUNCE_MS = 3e3;
17336
+ MAX_FINISH_RETRIES = 2;
17337
+ FINISH_RETRY_DELAY_MS = 300;
17338
+ MAX_TRACE_ENTRIES = 250;
17339
+ APPROVAL_EXIT_TIMEOUT_MS = 6e4;
17340
+ IDLE_CONFIRMATION_GRACE_MS = 2e3;
17341
+ CliStateEngine = class {
17342
+ constructor(provider, runner, transport, callbacks, timeouts) {
17343
+ this.provider = provider;
17344
+ this.runner = runner;
17345
+ this.transport = transport;
17346
+ this.callbacks = callbacks;
17347
+ this.timeouts = timeouts;
17348
+ }
17349
+ // ── Status ───────────────────────────────────────
17350
+ currentStatus = "starting";
17351
+ isWaitingForResponse = false;
17352
+ currentTurnScope = null;
17353
+ activeModal = null;
17354
+ // ── Approval ─────────────────────────────────────
17355
+ lastApprovalResolvedAt = 0;
17356
+ lastResolvedModalMessage = "";
17357
+ /**
17358
+ * Monotonic counter bumped every time the FSM *enters* waiting_approval
17359
+ * with a freshly captured modal (see `applyWaitingApproval`). It is the
17360
+ * single discriminator between "the same approval re-observed across TUI
17361
+ * paint flaps" and "a genuinely new, distinct approval".
17362
+ *
17363
+ * The message-equality cooldown below (`lastResolvedModalMessage`) cannot
17364
+ * tell these apart on its own: claude-cli routinely presents consecutive
17365
+ * approvals whose modal message text is identical (e.g. two back-to-back
17366
+ * Bash-command prompts). When that second approval arrived inside
17367
+ * `approvalCooldown`, the message-equality guard silently swallowed the
17368
+ * key write and the approval stuck forever — fatal under auto-approval.
17369
+ *
17370
+ * `approvalEntrySeq` increments on every fresh entry; `lastResolvedEntrySeq`
17371
+ * records which entry the cooldown belongs to. We only short-circuit the
17372
+ * write when we are still resolving *that same* entry — a new entry (new
17373
+ * seq) is always a real, distinct approval and must be written.
17374
+ */
17375
+ approvalEntrySeq = 0;
17376
+ lastResolvedEntrySeq = -1;
17377
+ /**
17378
+ * When the engine previously held a modal but the latest parse failed
17379
+ * to extract one, we record the timestamp here and only drop the modal
17380
+ * after the configured `approvalCooldown` to avoid flapping between
17381
+ * waiting_approval and generating on every Claude TUI redraw — that
17382
+ * flapping is what fed auto-approve a fresh modal signature on each
17383
+ * paint and made the engine type "1" repeatedly into the prompt.
17384
+ */
17385
+ modalLostAt = 0;
17386
+ approvalExitTimeout = null;
17387
+ // ── Response tracking ────────────────────────────
17388
+ responseEpoch = 0;
17389
+ submitPendingUntil = 0;
17390
+ responseSettleIgnoreUntil = 0;
17391
+ submitRetryUsed = false;
17392
+ submitRetryPromptSnippet = "";
17393
+ finishRetryCount = 0;
17394
+ providerErrorMessage = null;
17395
+ providerErrorReason = null;
17396
+ // ── Timers ───────────────────────────────────────
17397
+ settleTimer = null;
17398
+ idleTimeout = null;
17399
+ finishRetryTimer = null;
17400
+ providerErrorRetryTimer = null;
17401
+ providerErrorRetryKey = "";
17402
+ // ── Debounce ─────────────────────────────────────
17403
+ pendingScriptStatus = null;
17404
+ pendingScriptStatusSince = 0;
17405
+ pendingScriptStatusTimer = null;
17406
+ // ── Idle candidate ───────────────────────────────
17407
+ idleFinishCandidate = null;
17408
+ // ── Idle confirmation grace ──────────────────────
17409
+ /**
17410
+ * `finishResponse` produces the `generating → idle` transition that
17411
+ * coordinators interpret as "task complete". Some providers (antigravity-
17412
+ * cli observed in the wild) briefly paint a screen that looks like an
17413
+ * idle prompt between tool result frames while still actively running,
17414
+ * which fired `response_finished` and broke completion semantics.
17415
+ * We defer the actual idle transition by IDLE_CONFIRMATION_GRACE_MS and
17416
+ * cancel it if the scripted detection re-detects generating during that
16828
17417
  * window — a true completion stays idle for many seconds, so a 2-second
16829
17418
  * grace is sufficient to filter the paint blip.
16830
17419
  */
@@ -19529,595 +20118,6 @@ ${lastSnapshot}`;
19529
20118
  }
19530
20119
  });
19531
20120
 
19532
- // src/providers/spec/evaluator.ts
19533
- var evaluator_exports = {};
19534
- __export(evaluator_exports, {
19535
- evaluateCondition: () => evaluateCondition,
19536
- extractButtonsFromRule: () => extractButtonsFromRule,
19537
- extractTitle: () => extractTitle,
19538
- lastContiguousNumberedBlock: () => lastContiguousNumberedBlock,
19539
- resolveSections: () => resolveSections,
19540
- sectionText: () => sectionText
19541
- });
19542
- function resolveSize(size, total) {
19543
- if (size === void 0) return 0;
19544
- if (typeof size === "number") return Math.max(0, Math.min(total, size));
19545
- const m = /^(\d+(?:\.\d+)?)%$/.exec(size);
19546
- if (!m) return 0;
19547
- const pct = Number(m[1]);
19548
- return Math.max(0, Math.min(total, Math.round(total * pct / 100)));
19549
- }
19550
- function resolveSections(sectionsObj, lines) {
19551
- const total = lines.length;
19552
- const anchored = /* @__PURE__ */ new Map();
19553
- const sectionEntries = Object.entries(sectionsObj);
19554
- for (const [id, sec] of sectionEntries) {
19555
- let from = 0;
19556
- let to = total;
19557
- if (sec.anchor !== void 0) {
19558
- try {
19559
- const anchorPatterns = Array.isArray(sec.anchor) ? sec.anchor : [sec.anchor];
19560
- const sharedCtx = Array.isArray(sec.anchor_context) ? null : sec.anchor_context ?? null;
19561
- const ctxList = Array.isArray(sec.anchor_context) ? sec.anchor_context : anchorPatterns.map(() => sharedCtx);
19562
- const candidates = anchorPatterns.map((pattern, k) => {
19563
- const ctx = ctxList[k] ?? null;
19564
- return {
19565
- re: new RegExp(pattern, sec.anchor_flags ?? ""),
19566
- prevRe: ctx?.prev !== void 0 ? new RegExp(ctx.prev, ctx.prev_flags ?? "") : null,
19567
- nextRe: ctx?.next !== void 0 ? new RegExp(ctx.next, ctx.next_flags ?? "") : null
19568
- };
19569
- });
19570
- const matchesCandidate = (c, i) => c.re.test(lines[i]) && (c.prevRe === null || i > 0 && c.prevRe.test(lines[i - 1])) && (c.nextRe === null || i < total - 1 && c.nextRe.test(lines[i + 1]));
19571
- let idx = -1;
19572
- for (const c of candidates) {
19573
- let candIdx = -1;
19574
- if (sec.anchor_last) {
19575
- for (let i = total - 1; i >= 0; i--) {
19576
- if (matchesCandidate(c, i)) {
19577
- candIdx = i;
19578
- break;
19579
- }
19580
- }
19581
- } else {
19582
- for (let i = 0; i < total; i++) {
19583
- if (matchesCandidate(c, i)) {
19584
- candIdx = i;
19585
- break;
19586
- }
19587
- }
19588
- }
19589
- if (candIdx !== -1 && (idx === -1 || candIdx < idx)) idx = candIdx;
19590
- }
19591
- if (idx !== -1) {
19592
- from = idx;
19593
- to = total;
19594
- if (sec.until_regex !== void 0) {
19595
- try {
19596
- const ure = new RegExp(sec.until_regex, sec.until_regex_flags ?? "");
19597
- const end = lines.findIndex((l, i) => i > idx && ure.test(l));
19598
- if (end !== -1) to = end;
19599
- } catch {
19600
- }
19601
- } else if (sec.lines !== void 0) {
19602
- to = Math.min(total, from + sec.lines);
19603
- }
19604
- }
19605
- } catch {
19606
- }
19607
- } else if (sec.from_top !== void 0) {
19608
- from = resolveSize(sec.from_top, total);
19609
- to = total;
19610
- } else if (sec.from_bottom !== void 0) {
19611
- const sz = resolveSize(sec.from_bottom, total);
19612
- from = total - sz;
19613
- to = total;
19614
- }
19615
- anchored.set(id, { fromLine: from, toLine: to });
19616
- }
19617
- const resolved = [];
19618
- for (const [id, sec] of sectionEntries) {
19619
- let { fromLine, toLine } = anchored.get(id);
19620
- if (sec.until !== void 0) {
19621
- if (sec.until.startsWith("^")) {
19622
- try {
19623
- const ure = new RegExp(sec.until);
19624
- const end = lines.findIndex((l, i) => i > fromLine && ure.test(l));
19625
- if (end !== -1) toLine = end;
19626
- } catch {
19627
- }
19628
- } else {
19629
- const target = anchored.get(sec.until);
19630
- if (target) toLine = target.fromLine;
19631
- }
19632
- }
19633
- if (toLine < fromLine) toLine = fromLine;
19634
- const text = lines.slice(fromLine, toLine).join("\n");
19635
- resolved.push({ id, fromLine, toLine, text });
19636
- }
19637
- return resolved;
19638
- }
19639
- function sectionText(sections, sectionId, fullScreen) {
19640
- if (!sectionId) return fullScreen;
19641
- const found = sections.find((s2) => s2.id === sectionId);
19642
- return found ? found.text : "";
19643
- }
19644
- function isRegexCondition(c) {
19645
- return "matches" in c;
19646
- }
19647
- function isChangedCondition(c) {
19648
- return "cursor_above" in c && "changed" in c;
19649
- }
19650
- function isAllCondition(c) {
19651
- return "all" in c;
19652
- }
19653
- function isAnyCondition(c) {
19654
- return "any" in c;
19655
- }
19656
- function evaluateCondition(cond, sections, fullScreen, cursor, prevLines, trace, stateId) {
19657
- if (isAllCondition(cond)) {
19658
- for (const child of cond.all) {
19659
- if (!evaluateCondition(child, sections, fullScreen, cursor, prevLines, trace, stateId)) {
19660
- return false;
19661
- }
19662
- }
19663
- return true;
19664
- }
19665
- if (isAnyCondition(cond)) {
19666
- for (const child of cond.any) {
19667
- if (evaluateCondition(child, sections, fullScreen, cursor, prevLines, trace, stateId)) {
19668
- return true;
19669
- }
19670
- }
19671
- return false;
19672
- }
19673
- if (isChangedCondition(cond)) {
19674
- if (!cursor || !prevLines || prevLines.length === 0) return false;
19675
- const curLines = fullScreen.split("\n");
19676
- const startRow = Math.max(0, cursor.row - cond.cursor_above);
19677
- const endRow = cursor.row;
19678
- const currentSlice = curLines.slice(startRow, endRow).join("\n");
19679
- const prevSlice = prevLines.slice(startRow, endRow).join("\n");
19680
- const didChange = currentSlice !== prevSlice;
19681
- const result = cond.changed ? didChange : !didChange;
19682
- const stableSuffix = cond.stable_ms != null ? ` stable_ms=${cond.stable_ms}` : "";
19683
- trace.push({
19684
- kind: result ? "state_match" : "state_skip",
19685
- text: `state[${stateId}] changed cond cursor_above=${cond.cursor_above} rows[${startRow},${endRow}) changed=${didChange} expected=${cond.changed}${stableSuffix} result=${result}`
19686
- });
19687
- return result;
19688
- }
19689
- if (isRegexCondition(cond)) {
19690
- const haystack = sectionText(sections, cond.section, fullScreen);
19691
- let matched = false;
19692
- try {
19693
- const re = new RegExp(cond.matches, cond.flags ?? "i");
19694
- matched = re.test(haystack);
19695
- } catch {
19696
- matched = false;
19697
- }
19698
- if (!matched) {
19699
- trace.push({ kind: "state_skip", text: `state[${stateId}] regex cond ${cond.section ?? "*"}~/${cond.matches}/ no match` });
19700
- return false;
19701
- }
19702
- if (cursor !== void 0) {
19703
- if (cond.cursor_row_min !== void 0 && cursor.row < cond.cursor_row_min) {
19704
- trace.push({ kind: "state_skip", text: `state[${stateId}] cursor row ${cursor.row} < cursor_row_min ${cond.cursor_row_min}` });
19705
- return false;
19706
- }
19707
- if (cond.cursor_row_max !== void 0 && cursor.row > cond.cursor_row_max) {
19708
- trace.push({ kind: "state_skip", text: `state[${stateId}] cursor row ${cursor.row} > cursor_row_max ${cond.cursor_row_max}` });
19709
- return false;
19710
- }
19711
- if (cond.cursor_col_min !== void 0 && cursor.col < cond.cursor_col_min) {
19712
- trace.push({ kind: "state_skip", text: `state[${stateId}] cursor col ${cursor.col} < cursor_col_min ${cond.cursor_col_min}` });
19713
- return false;
19714
- }
19715
- if (cond.cursor_col_max !== void 0 && cursor.col > cond.cursor_col_max) {
19716
- trace.push({ kind: "state_skip", text: `state[${stateId}] cursor col ${cursor.col} > cursor_col_max ${cond.cursor_col_max}` });
19717
- return false;
19718
- }
19719
- }
19720
- trace.push({ kind: "state_match", text: `state[${stateId}] regex cond ${cond.section ?? "*"}~/${cond.matches}/ matched${cursor !== void 0 ? ` cursor=(${cursor.row},${cursor.col})` : ""}` });
19721
- return true;
19722
- }
19723
- return false;
19724
- }
19725
- function extractTitle(rule, sections, fullScreen) {
19726
- const hay = sectionText(sections, rule.section, fullScreen);
19727
- if (!hay) return null;
19728
- if (rule.first_line) {
19729
- const lines = hay.split("\n");
19730
- for (const line of lines) {
19731
- const stripped = line.trim();
19732
- if (stripped && !/^[─╌═─\s]+$/.test(stripped)) {
19733
- return stripped;
19734
- }
19735
- }
19736
- return null;
19737
- }
19738
- if (rule.regex) {
19739
- try {
19740
- const re = new RegExp(rule.regex, rule.flags ?? "i");
19741
- const m = re.exec(hay);
19742
- if (m) return (m[1] ?? m[0]).trim();
19743
- } catch {
19744
- }
19745
- }
19746
- return null;
19747
- }
19748
- function compilePattern(ref) {
19749
- const flags = ref.flags ?? "gm";
19750
- return new RegExp(ref.pattern, flags.includes("g") ? flags : flags + "g");
19751
- }
19752
- function compileLinePattern(ref) {
19753
- const flags = (ref.flags ?? "m").replace(/g/g, "");
19754
- return new RegExp(ref.pattern, flags);
19755
- }
19756
- function extractButtonsFromRule(rule, hay) {
19757
- const keyTemplate = rule.key_for_index;
19758
- const continuationLines = rule.continuation_lines ?? false;
19759
- const buttons = [];
19760
- if (continuationLines) {
19761
- const re = compileLinePattern(rule);
19762
- const lines = hay.split("\n");
19763
- for (let i = 0; i < lines.length; i += 1) {
19764
- const m = re.exec(lines[i]);
19765
- if (!m) continue;
19766
- const idx = Number(m[1]);
19767
- let label = String(m[2] ?? "").trim();
19768
- if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19769
- const current = hasCursorMarker(lines[i]);
19770
- let j = i + 1;
19771
- while (j < lines.length) {
19772
- const next = lines[j];
19773
- if (!next.trim()) break;
19774
- if (re.test(next)) break;
19775
- if (!/^\s+/.test(next)) break;
19776
- label += " " + next.trim();
19777
- j += 1;
19778
- }
19779
- const key = keyTemplate.replace(/\{index\}/g, String(idx));
19780
- buttons.push({ index: idx, label, key, current });
19781
- i = j - 1;
19782
- }
19783
- } else {
19784
- const re = compilePattern(rule);
19785
- let m;
19786
- while ((m = re.exec(hay)) !== null) {
19787
- const idx = Number(m[1]);
19788
- const label = String(m[2] ?? "").trim();
19789
- if (!Number.isFinite(idx) || idx <= 0 || !label) continue;
19790
- const key = keyTemplate.replace(/\{index\}/g, String(idx));
19791
- buttons.push({ index: idx, label, key, current: hasCursorMarker(m[0]) });
19792
- }
19793
- }
19794
- const block2 = lastContiguousNumberedBlock(buttons);
19795
- block2.sort((a, b) => a.index - b.index);
19796
- return block2;
19797
- }
19798
- function lastContiguousNumberedBlock(entries) {
19799
- if (entries.length <= 1) return entries.slice();
19800
- let start = entries.length - 1;
19801
- for (let i = entries.length - 1; i > 0; i -= 1) {
19802
- if (entries[i - 1].index === entries[i].index - 1) start = i - 1;
19803
- else break;
19804
- }
19805
- return entries.slice(start);
19806
- }
19807
- function hasCursorMarker(text) {
19808
- return /^\s*[❯›>]/.test(text);
19809
- }
19810
- var init_evaluator = __esm({
19811
- "src/providers/spec/evaluator.ts"() {
19812
- "use strict";
19813
- }
19814
- });
19815
-
19816
- // src/providers/spec/fsm-types.ts
19817
- function isV4Spec(raw) {
19818
- return !!raw && typeof raw === "object" && raw.$schema === "adhdev:cli/spec@4";
19819
- }
19820
- function initialState(spec) {
19821
- return spec.states.find((s2) => s2.initial) ?? spec.states[0];
19822
- }
19823
- function stateById(spec, id) {
19824
- return spec.states.find((s2) => s2.id === id);
19825
- }
19826
- function outgoingTransitions(spec, stateId) {
19827
- const matches = spec.transitions.filter((t) => {
19828
- if (t.from === "*") return true;
19829
- if (Array.isArray(t.from)) return t.from.includes(stateId);
19830
- return t.from === stateId;
19831
- });
19832
- return matches.map((t, i) => ({ t, i })).sort((a, b) => (b.t.priority ?? 0) - (a.t.priority ?? 0) || a.i - b.i).map((x) => x.t);
19833
- }
19834
- function statusForState(state) {
19835
- if (state.status) return state.status;
19836
- if (state.modal) return "approval";
19837
- if (state.id === "busy" || state.id === "generating") return "generating";
19838
- return "idle";
19839
- }
19840
- function modalKindForState(state) {
19841
- if (state.modal_kind) return state.modal_kind;
19842
- if (state.modal) return "approval";
19843
- return null;
19844
- }
19845
- var init_fsm_types = __esm({
19846
- "src/providers/spec/fsm-types.ts"() {
19847
- "use strict";
19848
- }
19849
- });
19850
-
19851
- // src/providers/spec/fsm-evaluator.ts
19852
- var fsm_evaluator_exports = {};
19853
- __export(fsm_evaluator_exports, {
19854
- evaluateConditionPreview: () => evaluateConditionPreview,
19855
- evaluateFsm: () => evaluateFsm
19856
- });
19857
- function regionKey(cursorAbove) {
19858
- return cursorAbove && cursorAbove > 0 ? cursorAbove : WHOLE_SCREEN;
19859
- }
19860
- function isRegex(c) {
19861
- return "matches" in c;
19862
- }
19863
- function isChanged(c) {
19864
- return "cursor_above" in c && "changed" in c;
19865
- }
19866
- function isElapsed(c) {
19867
- return "elapsed_ms" in c;
19868
- }
19869
- function isStable(c) {
19870
- return "stable_ms" in c;
19871
- }
19872
- function isAll(c) {
19873
- return "all" in c;
19874
- }
19875
- function isAny(c) {
19876
- return "any" in c;
19877
- }
19878
- function isNot(c) {
19879
- return "not" in c;
19880
- }
19881
- function evalCond(cond, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId) {
19882
- if (isAll(cond)) {
19883
- const children = cond.all.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
19884
- const result = children.every((c) => c.result);
19885
- const remainingMs = result ? 0 : Math.max(0, ...children.filter((c) => !c.result).map((c) => c.remainingMs ?? 0));
19886
- return { kind: "all", result, detail: `all(${children.length})`, remainingMs, children };
19887
- }
19888
- if (isAny(cond)) {
19889
- const children = cond.any.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
19890
- const result = children.some((c) => c.result);
19891
- const pending = children.filter((c) => !c.result).map((c) => c.remainingMs ?? Infinity);
19892
- const remainingMs = result ? 0 : pending.length ? Math.min(...pending) : 0;
19893
- return { kind: "any", result, detail: `any(${children.length})`, remainingMs: Number.isFinite(remainingMs) ? remainingMs : 0, children };
19894
- }
19895
- if (isNot(cond)) {
19896
- const child = evalCond(cond.not, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId);
19897
- return { kind: "not", result: !child.result, detail: `not`, remainingMs: 0, children: [child] };
19898
- }
19899
- if (isElapsed(cond)) {
19900
- const age = clock.now - clock.stateEnteredAt;
19901
- const result = age >= cond.elapsed_ms;
19902
- const remainingMs = result ? 0 : cond.elapsed_ms - age;
19903
- return { kind: "elapsed", result, detail: `elapsed ${age}ms / ${cond.elapsed_ms}ms`, remainingMs };
19904
- }
19905
- if (isStable(cond)) {
19906
- const key = regionKey(cond.cursor_above);
19907
- const lastChanged = clock.regionLastChangedAt.get(key) ?? clock.stateEnteredAt;
19908
- const stableFor = clock.now - lastChanged;
19909
- const result = stableFor >= cond.stable_ms;
19910
- const remainingMs = result ? 0 : cond.stable_ms - stableFor;
19911
- const where = key === WHOLE_SCREEN ? "screen" : `cursor_above=${cond.cursor_above}`;
19912
- return { kind: "stable", result, detail: `stable ${where} ${stableFor}ms / ${cond.stable_ms}ms`, remainingMs };
19913
- }
19914
- if (isRegex(cond) || isChanged(cond)) {
19915
- const result = evaluateCondition(cond, sections, fullScreen, cursor, prevLines, legacyTrace, stateId);
19916
- const kind = isRegex(cond) ? "regex" : "changed";
19917
- const detail = isRegex(cond) ? `${cond.section ?? "*"}~/${cond.matches}/` : `cursor_above=${cond.cursor_above} changed=${cond.changed}`;
19918
- let matchedText;
19919
- if (result && isRegex(cond)) {
19920
- try {
19921
- const hay = sectionText(sections, cond.section, fullScreen);
19922
- const re = new RegExp(cond.matches, cond.flags ?? "i");
19923
- const m = re.exec(hay);
19924
- if (m && m[0]) matchedText = m[0].replace(/\s+/g, " ").trim().slice(0, 160);
19925
- } catch {
19926
- }
19927
- }
19928
- return matchedText ? { kind, result, detail, matchedText } : { kind, result, detail };
19929
- }
19930
- return { kind: "all", result: false, detail: "unknown condition" };
19931
- }
19932
- function fromLabel(t) {
19933
- const from = Array.isArray(t.from) ? t.from.join("|") : t.from;
19934
- return t.label ?? `${from}\u2192${t.to}`;
19935
- }
19936
- function evaluateFsm(spec, currentStateId, screenText, cursor, prevLines, clock) {
19937
- const legacyTrace = [];
19938
- const lines = screenText.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
19939
- const cleanScreen = lines.join("\n");
19940
- const sections = resolveSections(spec.sections ?? {}, lines);
19941
- const outgoing = outgoingTransitions(spec, currentStateId);
19942
- const transitions = [];
19943
- let fired = null;
19944
- for (const t of outgoing) {
19945
- const holdMs = t.min_hold_ms ?? 0;
19946
- const heldFor = clock.now - clock.stateEnteredAt;
19947
- const holdSatisfied = heldFor >= holdMs;
19948
- const holdRemainingMs = holdSatisfied ? 0 : holdMs - heldFor;
19949
- let cond;
19950
- let condResult = true;
19951
- if (t.when) {
19952
- cond = evalCond(t.when, sections, cleanScreen, cursor, prevLines, clock, legacyTrace, `${currentStateId}\u2192${t.to}`);
19953
- condResult = cond.result;
19954
- }
19955
- const fires = holdSatisfied && condResult;
19956
- const te = {
19957
- to: t.to,
19958
- label: fromLabel(t),
19959
- eligible: true,
19960
- holdSatisfied,
19961
- holdRemainingMs,
19962
- condResult,
19963
- cond,
19964
- fires,
19965
- priority: t.priority ?? 0
19966
- };
19967
- transitions.push(te);
19968
- if (fires && !fired) fired = te;
19969
- }
19970
- if (fired && !stateById(spec, fired.to)) {
19971
- legacyTrace.push({ kind: "state_skip", text: `transition target ${fired.to} not found` });
19972
- fired = null;
19973
- }
19974
- return { sections, transitions, fired, trace: legacyTrace };
19975
- }
19976
- function evaluateConditionPreview(cond, sections, screenText, cursor) {
19977
- const lines = screenText.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
19978
- const cleanScreen = lines.join("\n");
19979
- const resolved = resolveSections(sections ?? {}, lines);
19980
- const clock = { now: 0, stateEnteredAt: 0, regionLastChangedAt: /* @__PURE__ */ new Map() };
19981
- return evalCond(cond, resolved, cleanScreen, cursor, void 0, clock, [], "preview");
19982
- }
19983
- var WHOLE_SCREEN;
19984
- var init_fsm_evaluator = __esm({
19985
- "src/providers/spec/fsm-evaluator.ts"() {
19986
- "use strict";
19987
- init_evaluator();
19988
- init_fsm_types();
19989
- WHOLE_SCREEN = -1;
19990
- }
19991
- });
19992
-
19993
- // src/providers/spec/fsm-loader.ts
19994
- var fsm_loader_exports = {};
19995
- __export(fsm_loader_exports, {
19996
- loadFsmSpec: () => loadFsmSpec,
19997
- validateFsmSpec: () => validateFsmSpec
19998
- });
19999
- import * as fs10 from "fs";
20000
- function loadFsmSpec(sourcePath) {
20001
- let raw;
20002
- try {
20003
- raw = JSON.parse(fs10.readFileSync(sourcePath, "utf8"));
20004
- } catch (err) {
20005
- return { ok: false, errors: [`Failed to read/parse spec: ${err.message}`], sourcePath };
20006
- }
20007
- const errors = validateFsmSpec(raw);
20008
- if (errors.length) return { ok: false, errors, sourcePath };
20009
- return { ok: true, spec: raw, sourcePath };
20010
- }
20011
- function validateFsmSpec(raw) {
20012
- const errs = [];
20013
- if (!isV4Spec(raw)) return ['$schema must be "adhdev:cli/spec@4"'];
20014
- const spec = raw;
20015
- if (!spec.id) errs.push("id is required");
20016
- if (!spec.binary) errs.push("binary is required");
20017
- if (!spec.send_message?.submit_key) errs.push("send_message.submit_key is required");
20018
- if (spec.pre_launch_trust !== void 0) {
20019
- const t = spec.pre_launch_trust;
20020
- if (!t || typeof t !== "object" || Array.isArray(t)) {
20021
- errs.push("pre_launch_trust must be an object");
20022
- } else {
20023
- if (typeof t.settings_path !== "string" || !t.settings_path) errs.push("pre_launch_trust.settings_path is required");
20024
- if (typeof t.key !== "string" || !t.key) errs.push("pre_launch_trust.key is required");
20025
- }
20026
- }
20027
- if (spec.refocus_when_stalled_ms !== void 0) {
20028
- if (typeof spec.refocus_when_stalled_ms !== "number" || !(spec.refocus_when_stalled_ms > 0)) {
20029
- errs.push("refocus_when_stalled_ms must be a positive number");
20030
- } else if (!Array.isArray(spec.send_on_spawn) || spec.send_on_spawn.length === 0) {
20031
- errs.push("refocus_when_stalled_ms requires send_on_spawn (the wake sequence to re-inject)");
20032
- }
20033
- }
20034
- if (!Array.isArray(spec.states) || spec.states.length === 0) {
20035
- errs.push("states[] must be a non-empty array");
20036
- return errs;
20037
- }
20038
- if (!Array.isArray(spec.transitions)) {
20039
- errs.push("transitions[] must be an array");
20040
- return errs;
20041
- }
20042
- const ids = /* @__PURE__ */ new Set();
20043
- let initialCount = 0;
20044
- for (const [i, s2] of spec.states.entries()) {
20045
- if (!s2.id) {
20046
- errs.push(`states[${i}].id is required`);
20047
- continue;
20048
- }
20049
- if (ids.has(s2.id)) errs.push(`states[${i}].id "${s2.id}" is duplicated`);
20050
- ids.add(s2.id);
20051
- if (!s2.label) errs.push(`states[${i}].label is required`);
20052
- if (s2.initial) initialCount += 1;
20053
- if (s2.status && !["idle", "generating", "approval"].includes(s2.status)) {
20054
- errs.push(`states[${i}].status "${s2.status}" must be idle|generating|approval`);
20055
- }
20056
- }
20057
- if (initialCount === 0) errs.push("exactly one state must have initial:true (none found)");
20058
- if (initialCount > 1) errs.push(`exactly one state must have initial:true (${initialCount} found)`);
20059
- const sectionIds = new Set(Object.keys(spec.sections ?? {}));
20060
- for (const [i, t] of spec.transitions.entries()) {
20061
- const froms = t.from === "*" ? [] : Array.isArray(t.from) ? t.from : [t.from];
20062
- for (const f of froms) {
20063
- if (!ids.has(f)) errs.push(`transitions[${i}].from references unknown state "${f}"`);
20064
- }
20065
- if (t.from !== "*" && froms.length === 0) errs.push(`transitions[${i}].from is required`);
20066
- if (!t.to) errs.push(`transitions[${i}].to is required`);
20067
- else if (!ids.has(t.to)) errs.push(`transitions[${i}].to references unknown state "${t.to}"`);
20068
- if (t.when) errs.push(...validateCondition(t.when, sectionIds, `transitions[${i}].when`));
20069
- }
20070
- for (const [i, s2] of spec.states.entries()) {
20071
- const sec = s2.extract?.title?.section;
20072
- if (sec && !sectionIds.has(sec)) errs.push(`states[${i}].extract.title.section "${sec}" unknown`);
20073
- const bsec = s2.extract?.buttons?.section;
20074
- if (bsec && !sectionIds.has(bsec)) errs.push(`states[${i}].extract.buttons.section "${bsec}" unknown`);
20075
- }
20076
- return errs;
20077
- }
20078
- function validateCondition(c, sectionIds, path42) {
20079
- const errs = [];
20080
- const w = c;
20081
- if ("all" in w) {
20082
- w.all.forEach((x, i) => errs.push(...validateCondition(x, sectionIds, `${path42}.all[${i}]`)));
20083
- return errs;
20084
- }
20085
- if ("any" in w) {
20086
- w.any.forEach((x, i) => errs.push(...validateCondition(x, sectionIds, `${path42}.any[${i}]`)));
20087
- return errs;
20088
- }
20089
- if ("not" in w) {
20090
- errs.push(...validateCondition(w.not, sectionIds, `${path42}.not`));
20091
- return errs;
20092
- }
20093
- if ("matches" in w) {
20094
- if (w.section && !sectionIds.has(w.section)) errs.push(`${path42}.section "${w.section}" unknown`);
20095
- try {
20096
- new RegExp(w.matches, w.flags ?? "i");
20097
- } catch (e) {
20098
- errs.push(`${path42}.matches invalid regex: ${e.message}`);
20099
- }
20100
- return errs;
20101
- }
20102
- if ("cursor_above" in w && "changed" in w) return errs;
20103
- if ("elapsed_ms" in w) {
20104
- if (typeof w.elapsed_ms !== "number") errs.push(`${path42}.elapsed_ms must be a number`);
20105
- return errs;
20106
- }
20107
- if ("stable_ms" in w) {
20108
- if (typeof w.stable_ms !== "number") errs.push(`${path42}.stable_ms must be a number`);
20109
- return errs;
20110
- }
20111
- errs.push(`${path42} is not a recognized condition`);
20112
- return errs;
20113
- }
20114
- var init_fsm_loader = __esm({
20115
- "src/providers/spec/fsm-loader.ts"() {
20116
- "use strict";
20117
- init_fsm_types();
20118
- }
20119
- });
20120
-
20121
20121
  // src/providers/sdk/v1/sandbox/require-whitelist.ts
20122
20122
  var require_whitelist_exports = {};
20123
20123
  __export(require_whitelist_exports, {
@@ -32685,6 +32685,503 @@ ${formatManifestValidationIssues2(validation.issues)}`,
32685
32685
  }
32686
32686
  };
32687
32687
 
32688
+ // src/commands/low-family/session-host.ts
32689
+ function toHostedCliRuntimeDescriptor(record) {
32690
+ if (!record || typeof record !== "object") return null;
32691
+ const runtimeId = typeof record.sessionId === "string" ? record.sessionId : "";
32692
+ const cliType = typeof record.providerType === "string" ? record.providerType : "";
32693
+ const workspace = typeof record.workspace === "string" ? record.workspace : "";
32694
+ if (!runtimeId || !cliType || !workspace) return null;
32695
+ return {
32696
+ runtimeId,
32697
+ runtimeKey: typeof record.runtimeKey === "string" ? record.runtimeKey : void 0,
32698
+ displayName: typeof record.displayName === "string" ? record.displayName : void 0,
32699
+ workspaceLabel: typeof record.workspaceLabel === "string" ? record.workspaceLabel : void 0,
32700
+ lifecycle: typeof record.lifecycle === "string" ? record.lifecycle : void 0,
32701
+ recoveryState: typeof record.meta?.runtimeRecoveryState === "string" ? String(record.meta.runtimeRecoveryState) : null,
32702
+ cliType,
32703
+ workspace,
32704
+ cliArgs: Array.isArray(record.meta?.cliArgs) ? record.meta.cliArgs : [],
32705
+ providerSessionId: typeof record.meta?.providerSessionId === "string" ? String(record.meta.providerSessionId) : void 0
32706
+ };
32707
+ }
32708
+ function getWriteConflictOwnerClientId(error) {
32709
+ const message = typeof error === "string" ? error : error instanceof Error ? error.message : "";
32710
+ const match = /^Write owned by\s+(.+)$/.exec(message.trim());
32711
+ return match?.[1]?.trim() || void 0;
32712
+ }
32713
+ function summarizeSessionHostRecord(result) {
32714
+ if (!result || typeof result !== "object") return {};
32715
+ const record = result;
32716
+ return {
32717
+ runtimeKey: typeof record.runtimeKey === "string" ? record.runtimeKey : void 0,
32718
+ lifecycle: typeof record.lifecycle === "string" ? record.lifecycle : void 0,
32719
+ surfaceKind: getSessionHostSurfaceKind(record),
32720
+ attachedClientCount: Array.isArray(record.attachedClients) ? record.attachedClients.length : void 0,
32721
+ hasWriteOwner: !!record.writeOwner,
32722
+ writeOwnerClientId: typeof record.writeOwner?.clientId === "string" ? record.writeOwner.clientId : void 0
32723
+ };
32724
+ }
32725
+ function summarizeSessionHostRecords(result) {
32726
+ const records = Array.isArray(result) ? result : [];
32727
+ const groups = partitionSessionHostRecords(records);
32728
+ return {
32729
+ sessionCount: records.length,
32730
+ liveRuntimeCount: groups.liveRuntimes.length,
32731
+ recoverySnapshotCount: groups.recoverySnapshots.length,
32732
+ inactiveRecordCount: groups.inactiveRecords.length
32733
+ };
32734
+ }
32735
+ function summarizeSessionHostDiagnostics(result) {
32736
+ const diagnostics = result && typeof result === "object" ? result : {};
32737
+ const sessions = Array.isArray(diagnostics.sessions) ? diagnostics.sessions : [];
32738
+ return {
32739
+ runtimeCount: typeof diagnostics.runtimeCount === "number" ? diagnostics.runtimeCount : void 0,
32740
+ ...summarizeSessionHostRecords(sessions)
32741
+ };
32742
+ }
32743
+ function summarizeSessionHostPruneResult(result) {
32744
+ const value = result && typeof result === "object" ? result : {};
32745
+ return {
32746
+ duplicateGroupCount: typeof value.duplicateGroupCount === "number" ? value.duplicateGroupCount : void 0,
32747
+ prunedCount: Array.isArray(value.prunedSessionIds) ? value.prunedSessionIds.length : void 0,
32748
+ keptCount: Array.isArray(value.keptSessionIds) ? value.keptSessionIds.length : void 0
32749
+ };
32750
+ }
32751
+ async function traceSessionHostAction(action, args, run, summarizeResult2) {
32752
+ const interactionId = typeof args?._interactionId === "string" ? args._interactionId : void 0;
32753
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : void 0;
32754
+ const requestedPayload = { action };
32755
+ if (sessionId) requestedPayload.sessionId = sessionId;
32756
+ if (typeof args?.clientId === "string") requestedPayload.clientId = args.clientId;
32757
+ if (typeof args?.signal === "string") requestedPayload.signal = args.signal;
32758
+ if (typeof args?.providerType === "string") requestedPayload.providerType = args.providerType;
32759
+ if (typeof args?.workspace === "string") requestedPayload.workspace = args.workspace;
32760
+ if (typeof args?.dryRun === "boolean") requestedPayload.dryRun = args.dryRun;
32761
+ recordDebugTrace({
32762
+ interactionId,
32763
+ category: "session_host",
32764
+ stage: "action_requested",
32765
+ level: "info",
32766
+ sessionId,
32767
+ payload: requestedPayload
32768
+ });
32769
+ try {
32770
+ const result = await run();
32771
+ recordDebugTrace({
32772
+ interactionId,
32773
+ category: "session_host",
32774
+ stage: "action_result",
32775
+ level: "info",
32776
+ sessionId,
32777
+ payload: {
32778
+ ...requestedPayload,
32779
+ success: true,
32780
+ ...summarizeResult2 ? summarizeResult2(result) : {}
32781
+ }
32782
+ });
32783
+ return result;
32784
+ } catch (error) {
32785
+ recordDebugTrace({
32786
+ interactionId,
32787
+ category: "session_host",
32788
+ stage: "action_failed",
32789
+ level: "error",
32790
+ sessionId,
32791
+ payload: {
32792
+ ...requestedPayload,
32793
+ error: error?.message || String(error),
32794
+ failureKind: getWriteConflictOwnerClientId(error) ? "write_conflict" : "request_failed",
32795
+ conflictOwnerClientId: getWriteConflictOwnerClientId(error)
32796
+ }
32797
+ });
32798
+ throw error;
32799
+ }
32800
+ }
32801
+ var sessionHostHandlers = {
32802
+ session_host_get_diagnostics: async (ctx, args) => {
32803
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32804
+ const diagnostics = await traceSessionHostAction("session_host_get_diagnostics", args, () => ctx.deps.sessionHostControl.getDiagnostics({
32805
+ includeSessions: args?.includeSessions !== false,
32806
+ limit: Number(args?.limit) || void 0
32807
+ }), (result) => ({
32808
+ includeSessions: args?.includeSessions !== false,
32809
+ limit: Number(args?.limit) || void 0,
32810
+ ...summarizeSessionHostDiagnostics(result)
32811
+ }));
32812
+ return { success: true, diagnostics };
32813
+ },
32814
+ session_host_list_sessions: async (ctx, args) => {
32815
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32816
+ const sessions = await traceSessionHostAction("session_host_list_sessions", args, () => ctx.deps.sessionHostControl.listSessions(), (records) => summarizeSessionHostRecords(records));
32817
+ return { success: true, sessions };
32818
+ },
32819
+ session_host_stop_session: async (ctx, args) => {
32820
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32821
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32822
+ if (!sessionId) return { success: false, error: "sessionId required" };
32823
+ const record = await traceSessionHostAction("session_host_stop_session", args, () => ctx.deps.sessionHostControl.stopSession(sessionId), (result) => summarizeSessionHostRecord(result));
32824
+ return { success: true, record };
32825
+ },
32826
+ session_host_resume_session: async (ctx, args) => {
32827
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32828
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32829
+ if (!sessionId) return { success: false, error: "sessionId required" };
32830
+ const record = await traceSessionHostAction("session_host_resume_session", args, async () => {
32831
+ const nextRecord = await ctx.deps.sessionHostControl.resumeSession(sessionId);
32832
+ const hosted = toHostedCliRuntimeDescriptor(nextRecord);
32833
+ if (hosted) {
32834
+ await ctx.deps.cliManager.restoreHostedSessions([hosted]);
32835
+ }
32836
+ return nextRecord;
32837
+ }, (result) => ({
32838
+ ...summarizeSessionHostRecord(result),
32839
+ restoredHostedSession: !!toHostedCliRuntimeDescriptor(result)
32840
+ }));
32841
+ return { success: true, record };
32842
+ },
32843
+ session_host_restart_session: async (ctx, args) => {
32844
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32845
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32846
+ if (!sessionId) return { success: false, error: "sessionId required" };
32847
+ const record = await traceSessionHostAction("session_host_restart_session", args, async () => {
32848
+ const nextRecord = await ctx.deps.sessionHostControl.restartSession(sessionId);
32849
+ const hosted = toHostedCliRuntimeDescriptor(nextRecord);
32850
+ if (hosted) {
32851
+ await ctx.deps.cliManager.restoreHostedSessions([hosted]);
32852
+ }
32853
+ return nextRecord;
32854
+ }, (result) => ({
32855
+ ...summarizeSessionHostRecord(result),
32856
+ restoredHostedSession: !!toHostedCliRuntimeDescriptor(result)
32857
+ }));
32858
+ return { success: true, record };
32859
+ },
32860
+ session_host_send_signal: async (ctx, args) => {
32861
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32862
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32863
+ const signal = typeof args?.signal === "string" ? args.signal : "";
32864
+ if (!sessionId) return { success: false, error: "sessionId required" };
32865
+ if (!signal) return { success: false, error: "signal required" };
32866
+ const record = await traceSessionHostAction("session_host_send_signal", args, () => ctx.deps.sessionHostControl.sendSignal(sessionId, signal), (result) => summarizeSessionHostRecord(result));
32867
+ return { success: true, record };
32868
+ },
32869
+ session_host_force_detach_client: async (ctx, args) => {
32870
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32871
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32872
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
32873
+ if (!sessionId) return { success: false, error: "sessionId required" };
32874
+ if (!clientId) return { success: false, error: "clientId required" };
32875
+ const record = await traceSessionHostAction("session_host_force_detach_client", args, () => ctx.deps.sessionHostControl.forceDetachClient(sessionId, clientId), (result) => summarizeSessionHostRecord(result));
32876
+ return { success: true, record };
32877
+ },
32878
+ session_host_prune_duplicate_sessions: async (ctx, args) => {
32879
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32880
+ const result = await traceSessionHostAction("session_host_prune_duplicate_sessions", args, () => ctx.deps.sessionHostControl.pruneDuplicateSessions({
32881
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
32882
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
32883
+ dryRun: args?.dryRun === true
32884
+ }), (value) => summarizeSessionHostPruneResult(value));
32885
+ return { success: true, result };
32886
+ },
32887
+ session_host_acquire_write: async (ctx, args) => {
32888
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32889
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32890
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
32891
+ const ownerType = args?.ownerType === "agent" ? "agent" : "user";
32892
+ if (!sessionId) return { success: false, error: "sessionId required" };
32893
+ if (!clientId) return { success: false, error: "clientId required" };
32894
+ const record = await traceSessionHostAction("session_host_acquire_write", args, () => ctx.deps.sessionHostControl.acquireWrite({
32895
+ sessionId,
32896
+ clientId,
32897
+ ownerType,
32898
+ force: args?.force !== false
32899
+ }), (result) => ({
32900
+ ...summarizeSessionHostRecord(result),
32901
+ ownerType
32902
+ }));
32903
+ return { success: true, record };
32904
+ },
32905
+ session_host_release_write: async (ctx, args) => {
32906
+ if (!ctx.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
32907
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
32908
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
32909
+ if (!sessionId) return { success: false, error: "sessionId required" };
32910
+ if (!clientId) return { success: false, error: "clientId required" };
32911
+ const record = await traceSessionHostAction("session_host_release_write", args, () => ctx.deps.sessionHostControl.releaseWrite({
32912
+ sessionId,
32913
+ clientId
32914
+ }), (result) => summarizeSessionHostRecord(result));
32915
+ return { success: true, record };
32916
+ }
32917
+ };
32918
+
32919
+ // src/commands/low-family/spec-providerdev.ts
32920
+ function resolveSpecPathInProviders(specPath, fsm, pathm, osm) {
32921
+ let rootReal;
32922
+ try {
32923
+ rootReal = fsm.realpathSync(pathm.join(osm.homedir(), ".adhdev", "providers"));
32924
+ } catch (e) {
32925
+ return { ok: false, error: `providers root unavailable: ${e.message}` };
32926
+ }
32927
+ const resolved = pathm.resolve(specPath);
32928
+ const base = pathm.basename(resolved);
32929
+ if (!/^[\w.-]+\.json$/.test(base)) {
32930
+ return { ok: false, error: "refused: spec file must be a *.json basename" };
32931
+ }
32932
+ let parentReal;
32933
+ try {
32934
+ parentReal = fsm.realpathSync(pathm.dirname(resolved));
32935
+ } catch (e) {
32936
+ return { ok: false, error: `spec directory not found: ${e.message}` };
32937
+ }
32938
+ if (parentReal !== rootReal && !parentReal.startsWith(rootReal + pathm.sep)) {
32939
+ return { ok: false, error: "refused: spec path must be under the providers root" };
32940
+ }
32941
+ const safe = pathm.join(parentReal, base);
32942
+ try {
32943
+ const st = fsm.lstatSync(safe);
32944
+ if (st.isSymbolicLink()) return { ok: false, error: "refused: spec path is a symlink" };
32945
+ } catch {
32946
+ }
32947
+ return { ok: true, path: safe };
32948
+ }
32949
+ var specProviderDevHandlers = {
32950
+ get_spec_debug: async (ctx, args) => {
32951
+ const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
32952
+ if (!sessionId) return { success: false, error: "targetSessionId required" };
32953
+ const target = ctx.deps.sessionRegistry.get(sessionId);
32954
+ if (!target) return { success: false, error: "Session not found", sessionId };
32955
+ const adapterObj = ctx.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
32956
+ const snapshot = adapterObj ? typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : typeof adapterObj.getDebugState === "function" ? adapterObj.getDebugState() : null : null;
32957
+ return {
32958
+ success: true,
32959
+ sessionId,
32960
+ providerType: target.providerType,
32961
+ isSpecProvider: snapshot !== null,
32962
+ snapshot
32963
+ };
32964
+ },
32965
+ // ── Spec source read/write for the debug panel's live editor.
32966
+ // Lets the dashboard load a session's spec.json, edit it, and save it back —
32967
+ // the driver's fs.watch picks up the change and hot-reloads the FSM with no
32968
+ // restart. Writes are confined to files under ~/.adhdev/providers.
32969
+ get_spec_source: async (ctx, args) => {
32970
+ const fsm = await import("fs");
32971
+ const pathm = await import("path");
32972
+ const osm = await import("os");
32973
+ const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
32974
+ let specPath = typeof args?.specPath === "string" ? args.specPath : "";
32975
+ if (!specPath && sessionId) {
32976
+ const target = ctx.deps.sessionRegistry.get(sessionId);
32977
+ const adapterObj = target ? ctx.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter : null;
32978
+ const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
32979
+ specPath = snap?.specPath ?? "";
32980
+ }
32981
+ if (!specPath) return { success: false, error: "specPath or resolvable targetSessionId required" };
32982
+ const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
32983
+ if (!safe.ok) return { success: false, error: safe.error, specPath };
32984
+ try {
32985
+ const content = fsm.readFileSync(safe.path, "utf8");
32986
+ return { success: true, specPath: safe.path, content };
32987
+ } catch (e) {
32988
+ return { success: false, error: `read failed: ${e.message}`, specPath };
32989
+ }
32990
+ },
32991
+ write_spec_source: async (_ctx, args) => {
32992
+ const fsm = await import("fs");
32993
+ const pathm = await import("path");
32994
+ const osm = await import("os");
32995
+ const specPath = typeof args?.specPath === "string" ? args.specPath : "";
32996
+ const content = typeof args?.content === "string" ? args.content : "";
32997
+ if (!specPath) return { success: false, error: "specPath required" };
32998
+ if (!content) return { success: false, error: "content required" };
32999
+ const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
33000
+ if (!safe.ok) return { success: false, error: safe.error };
33001
+ let parsed;
33002
+ try {
33003
+ parsed = JSON.parse(content);
33004
+ } catch (e) {
33005
+ return { success: false, error: `invalid JSON: ${e.message}` };
33006
+ }
33007
+ if (parsed?.$schema === "adhdev:cli/spec@4") {
33008
+ const { validateFsmSpec: validateFsmSpec2 } = await Promise.resolve().then(() => (init_fsm_loader(), fsm_loader_exports));
33009
+ const errs = validateFsmSpec2(parsed);
33010
+ if (errs.length) return { success: false, error: "spec invalid", validationErrors: errs };
33011
+ }
33012
+ try {
33013
+ fsm.writeFileSync(safe.path, content, "utf8");
33014
+ return { success: true, specPath: safe.path };
33015
+ } catch (e) {
33016
+ return { success: false, error: `write failed: ${e.message}` };
33017
+ }
33018
+ },
33019
+ // ── Validate an in-progress spec (string or object) without writing. The form
33020
+ // builder calls this on every change so Save can stay disabled while there
33021
+ // are structural / reference / regex errors.
33022
+ validate_spec: async (_ctx, args) => {
33023
+ let parsed = args?.spec;
33024
+ if (typeof args?.content === "string") {
33025
+ try {
33026
+ parsed = JSON.parse(args.content);
33027
+ } catch (e) {
33028
+ return { success: true, valid: false, errors: [`invalid JSON: ${e.message}`] };
33029
+ }
33030
+ }
33031
+ if (!parsed || typeof parsed !== "object") {
33032
+ return { success: true, valid: false, errors: ["spec must be an object or content string"] };
33033
+ }
33034
+ const schema = parsed.$schema;
33035
+ if (schema === "adhdev:cli/spec@4") {
33036
+ const { validateFsmSpec: validateFsmSpec2 } = await Promise.resolve().then(() => (init_fsm_loader(), fsm_loader_exports));
33037
+ const errors = validateFsmSpec2(parsed);
33038
+ return { success: true, valid: errors.length === 0, errors };
33039
+ }
33040
+ return { success: true, valid: false, errors: [`unsupported $schema "${schema}" \u2014 form builder is v4-only`] };
33041
+ },
33042
+ // ── Evaluate a single condition against a live session's current screen —
33043
+ // powers the editor's "does this match right now?" preview. Returns the
33044
+ // recursive match tree.
33045
+ eval_condition_preview: async (ctx, args) => {
33046
+ const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
33047
+ if (!sessionId) return { success: false, error: "targetSessionId required" };
33048
+ if (!args?.condition || typeof args.condition !== "object") return { success: false, error: "condition required" };
33049
+ const target = ctx.deps.sessionRegistry.get(sessionId);
33050
+ if (!target) return { success: false, error: "Session not found", sessionId };
33051
+ const adapterObj = ctx.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
33052
+ const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
33053
+ if (!snap?.screen) return { success: false, error: "no live screen for session" };
33054
+ let sectionsDef;
33055
+ try {
33056
+ const fsm2 = await import("fs");
33057
+ if (snap.specPath) {
33058
+ const raw = JSON.parse(fsm2.readFileSync(snap.specPath, "utf8"));
33059
+ sectionsDef = raw?.sections;
33060
+ }
33061
+ } catch {
33062
+ }
33063
+ const { evaluateConditionPreview: evaluateConditionPreview2 } = await Promise.resolve().then(() => (init_fsm_evaluator(), fsm_evaluator_exports));
33064
+ try {
33065
+ const result = evaluateConditionPreview2(
33066
+ args.condition,
33067
+ sectionsDef,
33068
+ snap.screen,
33069
+ snap.cursorPosition ?? void 0
33070
+ );
33071
+ return { success: true, result, sections: snap.sections ?? null };
33072
+ } catch (e) {
33073
+ return { success: false, error: `eval failed: ${e.message}` };
33074
+ }
33075
+ },
33076
+ // ── Resolve a sections map against a live session's screen — the section
33077
+ // editor's "test" button. Returns, for each section id, the line range + the
33078
+ // text it captures. Accepts an in-progress sections map so it previews
33079
+ // unsaved edits.
33080
+ resolve_section_preview: async (ctx, args) => {
33081
+ const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
33082
+ if (!sessionId) return { success: false, error: "targetSessionId required" };
33083
+ if (!args?.sections || typeof args.sections !== "object") return { success: false, error: "sections map required" };
33084
+ const target = ctx.deps.sessionRegistry.get(sessionId);
33085
+ if (!target) return { success: false, error: "Session not found", sessionId };
33086
+ const adapterObj = ctx.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
33087
+ const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
33088
+ if (!snap?.screen) return { success: false, error: "no live screen for session" };
33089
+ const { resolveSections: resolveSections2 } = await Promise.resolve().then(() => (init_evaluator(), evaluator_exports));
33090
+ try {
33091
+ const lines = String(snap.screen).split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
33092
+ const resolved = resolveSections2(args.sections, lines);
33093
+ return {
33094
+ success: true,
33095
+ screenLineCount: lines.length,
33096
+ sections: resolved.map((s2) => ({ id: s2.id, fromLine: s2.fromLine, toLine: s2.toLine, text: s2.text }))
33097
+ };
33098
+ } catch (e) {
33099
+ return { success: false, error: `resolve failed: ${e.message}` };
33100
+ }
33101
+ }
33102
+ };
33103
+
33104
+ // src/commands/low-family/refine-config.ts
33105
+ init_change_impact_config();
33106
+ var refineConfigHandlers = {
33107
+ get_mesh_refine_config_schema: async (_ctx, _args) => {
33108
+ return {
33109
+ success: true,
33110
+ schema: MESH_REFINE_CONFIG_SCHEMA,
33111
+ locations: MESH_REFINE_CONFIG_LOCATIONS,
33112
+ worktreeBootstrap: {
33113
+ schema: MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
33114
+ locations: MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
33115
+ sourceOfTruth: "repo worktree bootstrap config",
33116
+ runBehavior: "When present and enabled, clone_mesh_node runs commands after submodule initialization and records status on the worktree node."
33117
+ },
33118
+ sourceOfTruth: "repo mesh/refine config",
33119
+ heuristicRole: "suggestions_only_not_execution_path"
33120
+ };
33121
+ },
33122
+ validate_mesh_refine_config: async (_ctx, args) => {
33123
+ const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
33124
+ const mesh = args?.inlineMesh || {};
33125
+ const loaded = args?.config !== void 0 ? { config: args.config, source: "inline", sourceType: "mesh_policy" } : loadMeshRefineConfig(mesh, workspace);
33126
+ const validation = loaded.config ? validateMeshRefineConfig(loaded.config, loaded.source) : { valid: false, errors: [loaded.error || "repo mesh/refine config unavailable"], commands: [], rejectedCommands: [] };
33127
+ return { success: validation.valid, ...loaded, ...validation };
33128
+ },
33129
+ suggest_mesh_refine_config: async (_ctx, args) => {
33130
+ const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
33131
+ const mesh = args?.inlineMesh || {};
33132
+ return {
33133
+ success: true,
33134
+ ...suggestMeshRefineConfig(mesh, workspace),
33135
+ note: "Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config."
33136
+ };
33137
+ },
33138
+ get_mesh_change_impact_config_schema: async (_ctx, _args) => {
33139
+ return {
33140
+ success: true,
33141
+ schema: CHANGE_IMPACT_CONFIG_SCHEMA,
33142
+ locations: CHANGE_IMPACT_CONFIG_LOCATIONS,
33143
+ sourceOfTruth: "repo change-impact config",
33144
+ heuristicRole: "suggestions_only_not_execution_path",
33145
+ note: "Declarative config only \u2014 JSON/YAML are parsed but never executed. Defines which package/file changes require a daemon rebuild/restart vs. a web-only redeploy vs. nothing."
33146
+ };
33147
+ },
33148
+ validate_mesh_change_impact_config: async (_ctx, args) => {
33149
+ const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
33150
+ if (args?.config !== void 0) {
33151
+ const validation = validateChangeImpactConfig(args.config, "inline");
33152
+ return { success: validation.valid, source: "inline", sourceType: "mesh_policy", ...validation };
33153
+ }
33154
+ const loaded = loadChangeImpactConfig(workspace);
33155
+ if (loaded.sourceType === "repo_file") {
33156
+ const validation = validateChangeImpactConfig(loaded.config, loaded.source);
33157
+ return { success: validation.valid, ...loaded, ...validation };
33158
+ }
33159
+ return {
33160
+ success: false,
33161
+ ...loaded,
33162
+ valid: false,
33163
+ errors: [loaded.error || "repo change-impact config unavailable"]
33164
+ };
33165
+ },
33166
+ suggest_mesh_change_impact_config: async (_ctx, args) => {
33167
+ const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
33168
+ return {
33169
+ success: true,
33170
+ ...suggestChangeImpactConfig(workspace),
33171
+ note: "Suggestions are heuristic scaffold only; the draft must be reviewed and saved into repo change-impact config before it takes effect. Nothing is executed."
33172
+ };
33173
+ }
33174
+ };
33175
+
33176
+ // src/commands/low-family/index.ts
33177
+ var lowFamilyRegistry = new Map(
33178
+ Object.entries({
33179
+ ...sessionHostHandlers,
33180
+ ...specProviderDevHandlers,
33181
+ ...refineConfigHandlers
33182
+ })
33183
+ );
33184
+
32688
33185
  // src/commands/cli-manager.ts
32689
33186
  init_provider_cli_adapter();
32690
33187
  init_cli_detector();
@@ -36220,7 +36717,13 @@ var CliProviderInstance = class _CliProviderInstance {
36220
36717
  this.settings = {
36221
36718
  ...this.settings,
36222
36719
  meshNodeFor: assignment.meshId,
36223
- ...assignment.nodeId ? { meshNodeId: assignment.nodeId } : {},
36720
+ // WTCLAIM (A): track the bound node id under BOTH the active marker
36721
+ // (meshNodeId, cleared on detach) and a sticky marker (meshLastNodeId,
36722
+ // preserved across detach). The sticky marker lets a detached but still
36723
+ // coordinator-owned session be re-picked ONLY for the SAME node it served
36724
+ // — never auto-adopted for a sibling node (e.g. a cloned worktree) that
36725
+ // shares this daemon. See isMeshOwnedDelegateSession's post-detach gate.
36726
+ ...assignment.nodeId ? { meshNodeId: assignment.nodeId, meshLastNodeId: assignment.nodeId } : {},
36224
36727
  ...assignment.taskId ? { meshActiveTaskId: assignment.taskId } : {},
36225
36728
  ...assignment.coordinatorDaemonId ? { meshCoordinatorDaemonId: assignment.coordinatorDaemonId } : {},
36226
36729
  // Session-level routing anchor: the originating coordinator session, so this
@@ -36239,9 +36742,9 @@ var CliProviderInstance = class _CliProviderInstance {
36239
36742
  if (!this.settings.meshNodeFor && !this.settings.meshActiveTaskId && !this.settings.meshNodeId) return;
36240
36743
  const { meshNodeFor, meshNodeId, meshActiveTaskId, ...rest } = this.settings;
36241
36744
  void meshNodeFor;
36242
- void meshNodeId;
36243
36745
  void meshActiveTaskId;
36244
- this.settings = rest;
36746
+ const lastNodeId = typeof meshNodeId === "string" && meshNodeId.trim() ? meshNodeId.trim() : typeof rest.meshLastNodeId === "string" && rest.meshLastNodeId.trim() ? rest.meshLastNodeId.trim() : void 0;
36747
+ this.settings = lastNodeId ? { ...rest, meshLastNodeId: lastNodeId } : rest;
36245
36748
  this.adapter.updateRuntimeSettings?.(this.settings);
36246
36749
  }
36247
36750
  /**
@@ -38917,6 +39420,10 @@ function hasCompletedStartingLaunch(adapter) {
38917
39420
  if (hasNonEmptyModalButtons2(adapterStatus?.activeModal ?? adapterStatus?.modal ?? parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
38918
39421
  return hasFinalAssistantMessage(parsedStatus?.messages);
38919
39422
  }
39423
+ function normalizeDirForCompare(dir) {
39424
+ if (typeof dir !== "string") return "";
39425
+ return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
39426
+ }
38920
39427
  function shouldSuppressStaleParsedBusyStatus(adapterStatus, parsedStatus, adapter) {
38921
39428
  const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
38922
39429
  if (!BUSY_AGENT_STATUSES.has(parsedRawStatus)) return false;
@@ -39665,6 +40172,33 @@ Run 'adhdev doctor' for detailed diagnostics.`
39665
40172
  const adapter = this.adapters.get(ik);
39666
40173
  return adapter ? { adapter, key: ik } : null;
39667
40174
  }
40175
+ /**
40176
+ * WTCLAIM (B): resolve the adapter for a mesh dispatch that named a node
40177
+ * (meshContext.nodeId) but carried no explicit session. Matches by the
40178
+ * instance's bound mesh node id (settings.meshNodeId, falling back to the
40179
+ * sticky meshLastNodeId) first, then by the node workspace (workingDir).
40180
+ *
40181
+ * Unlike findAdapter's step-2 fuzzy fallback, this NEVER degrades to a
40182
+ * provider-only first-match: on a daemon hosting BOTH a base node and a
40183
+ * cloned worktree node (same daemonId), that fuzzy match could land a
40184
+ * worktree-targeted task on the base session. Returns null when no session
40185
+ * is bound to this node so the caller fails closed.
40186
+ */
40187
+ findMeshNodeAdapter(agentType, nodeId, dir) {
40188
+ const instanceManager = this.deps.getInstanceManager();
40189
+ const targetDir = normalizeDirForCompare(dir);
40190
+ let workspaceMatch = null;
40191
+ for (const [k, a] of this.adapters) {
40192
+ if (a.cliType !== agentType) continue;
40193
+ const settings = instanceManager?.getInstance(k)?.getState?.()?.settings;
40194
+ const boundNodeId = typeof settings?.meshNodeId === "string" && settings.meshNodeId.trim() ? settings.meshNodeId.trim() : typeof settings?.meshLastNodeId === "string" ? settings.meshLastNodeId.trim() : "";
40195
+ if (boundNodeId && boundNodeId === nodeId) return { adapter: a, key: k };
40196
+ if (!workspaceMatch && targetDir && normalizeDirForCompare(a.workingDir) === targetDir) {
40197
+ workspaceMatch = { adapter: a, key: k };
40198
+ }
40199
+ }
40200
+ return workspaceMatch;
40201
+ }
39668
40202
  // ─── CLI command handling ────────────────────────────
39669
40203
  async handleCliCommand(cmd, args) {
39670
40204
  switch (cmd) {
@@ -39850,10 +40384,22 @@ Run 'adhdev doctor' for detailed diagnostics.`
39850
40384
  const agentType = args?.agentType || args?.cliType;
39851
40385
  const action = args?.action;
39852
40386
  if (!agentType || !action) throw new Error("agentType and action required");
39853
- const found = this.findAdapter(agentType, {
39854
- dir: args?.dir,
39855
- instanceKey: args?.targetSessionId
39856
- });
40387
+ const meshScopeNodeId = (() => {
40388
+ const mc = args?.meshContext;
40389
+ return mc && typeof mc === "object" && typeof mc.nodeId === "string" ? mc.nodeId.trim() : "";
40390
+ })();
40391
+ let found;
40392
+ if (meshScopeNodeId && !args?.targetSessionId) {
40393
+ found = this.findMeshNodeAdapter(agentType, meshScopeNodeId, args?.dir);
40394
+ if (!found) {
40395
+ throw new Error(`No mesh worker session bound to node '${meshScopeNodeId}' for agent '${agentType}' on this daemon; refusing provider-only fuzzy match to avoid cross-node dispatch`);
40396
+ }
40397
+ } else {
40398
+ found = this.findAdapter(agentType, {
40399
+ dir: args?.dir,
40400
+ instanceKey: args?.targetSessionId
40401
+ });
40402
+ }
39857
40403
  if (!found) throw new Error(`CLI agent not running: ${agentType}`);
39858
40404
  const { adapter, key } = found;
39859
40405
  if (action === "send_chat") {
@@ -43747,7 +44293,6 @@ init_workspaces();
43747
44293
  init_recent_activity();
43748
44294
  init_cli_detector();
43749
44295
  init_git_status();
43750
- init_change_impact_config();
43751
44296
  init_dist();
43752
44297
  init_logger();
43753
44298
 
@@ -45090,7 +45635,7 @@ function buildMeshNodeMachineIdentity(node, opts) {
45090
45635
  const machineName = readMeshNodeDisplayMachineName(node);
45091
45636
  const coordinatorHostname = readStringValue(opts.coordinatorHostname);
45092
45637
  const machineIdMatches = Boolean(opts.localMachineId && machineId && opts.localMachineId === machineId);
45093
- const daemonIdMatches = Boolean(opts.localDaemonId && daemonId && opts.localDaemonId === daemonId);
45638
+ const daemonIdMatches = Boolean(opts.localDaemonId && daemonId && daemonIdsEquivalent(opts.localDaemonId, daemonId));
45094
45639
  const hostnameMatches = Boolean(
45095
45640
  normalizeMeshHostname(hostname3) && normalizeMeshHostname(coordinatorHostname) && normalizeMeshHostname(hostname3) === normalizeMeshHostname(coordinatorHostname)
45096
45641
  );
@@ -45805,10 +46350,10 @@ async function hydrateInlineMeshDirectTruth(args) {
45805
46350
  const isSelfNode = Boolean(
45806
46351
  nodeId && selectedCoordinatorNodeId && nodeId === selectedCoordinatorNodeId
45807
46352
  ) || Boolean(
45808
- daemonId && (daemonId === args.localMachineId || daemonId === args.statusInstanceId)
46353
+ daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId))
45809
46354
  ) || Boolean(args.meshSource !== "local_config" && nodeIndex === 0);
45810
46355
  const isSelfDaemonNode = Boolean(
45811
- daemonId && (daemonId === args.localMachineId || daemonId === args.statusInstanceId)
46356
+ daemonId && (daemonIdsEquivalent(daemonId, args.localMachineId) || daemonIdsEquivalent(daemonId, args.statusInstanceId))
45812
46357
  );
45813
46358
  if ((isSelfNode || isSelfDaemonNode) && isDeadLocalWorktreeNode(node)) {
45814
46359
  deadNodeIds.push(nodeId);
@@ -47147,97 +47692,6 @@ function normalizeCommandArgsWithInteractionId(args) {
47147
47692
  }
47148
47693
  return base;
47149
47694
  }
47150
- function resolveSpecPathInProviders(specPath, fsm, pathm, osm) {
47151
- let rootReal;
47152
- try {
47153
- rootReal = fsm.realpathSync(pathm.join(osm.homedir(), ".adhdev", "providers"));
47154
- } catch (e) {
47155
- return { ok: false, error: `providers root unavailable: ${e.message}` };
47156
- }
47157
- const resolved = pathm.resolve(specPath);
47158
- const base = pathm.basename(resolved);
47159
- if (!/^[\w.-]+\.json$/.test(base)) {
47160
- return { ok: false, error: "refused: spec file must be a *.json basename" };
47161
- }
47162
- let parentReal;
47163
- try {
47164
- parentReal = fsm.realpathSync(pathm.dirname(resolved));
47165
- } catch (e) {
47166
- return { ok: false, error: `spec directory not found: ${e.message}` };
47167
- }
47168
- if (parentReal !== rootReal && !parentReal.startsWith(rootReal + pathm.sep)) {
47169
- return { ok: false, error: "refused: spec path must be under the providers root" };
47170
- }
47171
- const safe = pathm.join(parentReal, base);
47172
- try {
47173
- const st = fsm.lstatSync(safe);
47174
- if (st.isSymbolicLink()) return { ok: false, error: "refused: spec path is a symlink" };
47175
- } catch {
47176
- }
47177
- return { ok: true, path: safe };
47178
- }
47179
- function toHostedCliRuntimeDescriptor(record) {
47180
- if (!record || typeof record !== "object") return null;
47181
- const runtimeId = typeof record.sessionId === "string" ? record.sessionId : "";
47182
- const cliType = typeof record.providerType === "string" ? record.providerType : "";
47183
- const workspace = typeof record.workspace === "string" ? record.workspace : "";
47184
- if (!runtimeId || !cliType || !workspace) return null;
47185
- return {
47186
- runtimeId,
47187
- runtimeKey: typeof record.runtimeKey === "string" ? record.runtimeKey : void 0,
47188
- displayName: typeof record.displayName === "string" ? record.displayName : void 0,
47189
- workspaceLabel: typeof record.workspaceLabel === "string" ? record.workspaceLabel : void 0,
47190
- lifecycle: typeof record.lifecycle === "string" ? record.lifecycle : void 0,
47191
- recoveryState: typeof record.meta?.runtimeRecoveryState === "string" ? String(record.meta.runtimeRecoveryState) : null,
47192
- cliType,
47193
- workspace,
47194
- cliArgs: Array.isArray(record.meta?.cliArgs) ? record.meta.cliArgs : [],
47195
- providerSessionId: typeof record.meta?.providerSessionId === "string" ? String(record.meta.providerSessionId) : void 0
47196
- };
47197
- }
47198
- function getWriteConflictOwnerClientId(error) {
47199
- const message = typeof error === "string" ? error : error instanceof Error ? error.message : "";
47200
- const match = /^Write owned by\s+(.+)$/.exec(message.trim());
47201
- return match?.[1]?.trim() || void 0;
47202
- }
47203
- function summarizeSessionHostRecord(result) {
47204
- if (!result || typeof result !== "object") return {};
47205
- const record = result;
47206
- return {
47207
- runtimeKey: typeof record.runtimeKey === "string" ? record.runtimeKey : void 0,
47208
- lifecycle: typeof record.lifecycle === "string" ? record.lifecycle : void 0,
47209
- surfaceKind: getSessionHostSurfaceKind(record),
47210
- attachedClientCount: Array.isArray(record.attachedClients) ? record.attachedClients.length : void 0,
47211
- hasWriteOwner: !!record.writeOwner,
47212
- writeOwnerClientId: typeof record.writeOwner?.clientId === "string" ? record.writeOwner.clientId : void 0
47213
- };
47214
- }
47215
- function summarizeSessionHostRecords(result) {
47216
- const records = Array.isArray(result) ? result : [];
47217
- const groups = partitionSessionHostRecords(records);
47218
- return {
47219
- sessionCount: records.length,
47220
- liveRuntimeCount: groups.liveRuntimes.length,
47221
- recoverySnapshotCount: groups.recoverySnapshots.length,
47222
- inactiveRecordCount: groups.inactiveRecords.length
47223
- };
47224
- }
47225
- function summarizeSessionHostDiagnostics(result) {
47226
- const diagnostics = result && typeof result === "object" ? result : {};
47227
- const sessions = Array.isArray(diagnostics.sessions) ? diagnostics.sessions : [];
47228
- return {
47229
- runtimeCount: typeof diagnostics.runtimeCount === "number" ? diagnostics.runtimeCount : void 0,
47230
- ...summarizeSessionHostRecords(sessions)
47231
- };
47232
- }
47233
- function summarizeSessionHostPruneResult(result) {
47234
- const value = result && typeof result === "object" ? result : {};
47235
- return {
47236
- duplicateGroupCount: typeof value.duplicateGroupCount === "number" ? value.duplicateGroupCount : void 0,
47237
- prunedCount: Array.isArray(value.prunedSessionIds) ? value.prunedSessionIds.length : void 0,
47238
- keptCount: Array.isArray(value.keptSessionIds) ? value.keptSessionIds.length : void 0
47239
- };
47240
- }
47241
47695
  function normalizeStandaloneHostCommandUrl(hostAddress) {
47242
47696
  const raw = hostAddress.trim();
47243
47697
  if (!raw) throw new Error("hostAddress required");
@@ -48087,56 +48541,6 @@ var DaemonCommandRouter = class {
48087
48541
  ...errors.length ? { errors } : {}
48088
48542
  };
48089
48543
  }
48090
- async traceSessionHostAction(action, args, run, summarizeResult2) {
48091
- const interactionId = typeof args?._interactionId === "string" ? args._interactionId : void 0;
48092
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : void 0;
48093
- const requestedPayload = { action };
48094
- if (sessionId) requestedPayload.sessionId = sessionId;
48095
- if (typeof args?.clientId === "string") requestedPayload.clientId = args.clientId;
48096
- if (typeof args?.signal === "string") requestedPayload.signal = args.signal;
48097
- if (typeof args?.providerType === "string") requestedPayload.providerType = args.providerType;
48098
- if (typeof args?.workspace === "string") requestedPayload.workspace = args.workspace;
48099
- if (typeof args?.dryRun === "boolean") requestedPayload.dryRun = args.dryRun;
48100
- recordDebugTrace({
48101
- interactionId,
48102
- category: "session_host",
48103
- stage: "action_requested",
48104
- level: "info",
48105
- sessionId,
48106
- payload: requestedPayload
48107
- });
48108
- try {
48109
- const result = await run();
48110
- recordDebugTrace({
48111
- interactionId,
48112
- category: "session_host",
48113
- stage: "action_result",
48114
- level: "info",
48115
- sessionId,
48116
- payload: {
48117
- ...requestedPayload,
48118
- success: true,
48119
- ...summarizeResult2 ? summarizeResult2(result) : {}
48120
- }
48121
- });
48122
- return result;
48123
- } catch (error) {
48124
- recordDebugTrace({
48125
- interactionId,
48126
- category: "session_host",
48127
- stage: "action_failed",
48128
- level: "error",
48129
- sessionId,
48130
- payload: {
48131
- ...requestedPayload,
48132
- error: error?.message || String(error),
48133
- failureKind: getWriteConflictOwnerClientId(error) ? "write_conflict" : "request_failed",
48134
- conflictOwnerClientId: getWriteConflictOwnerClientId(error)
48135
- }
48136
- });
48137
- throw error;
48138
- }
48139
- }
48140
48544
  /**
48141
48545
  * Unified command routing.
48142
48546
  * Returns result for all commands:
@@ -49511,6 +49915,10 @@ ${hintLines.join("\n")}` : "",
49511
49915
  }
49512
49916
  }
49513
49917
  }
49918
+ const lowFamilyHandler = lowFamilyRegistry.get(cmd);
49919
+ if (lowFamilyHandler) {
49920
+ return await lowFamilyHandler({ deps: this.deps }, args);
49921
+ }
49514
49922
  switch (cmd) {
49515
49923
  // ─── CLI / ACP commands ───
49516
49924
  case "mesh_forward_event": {
@@ -49638,121 +50046,6 @@ ${hintLines.join("\n")}` : "",
49638
50046
  const trace = getRecentDebugTrace({ interactionId, category, limit: count }).filter((entry) => !sinceTs || entry.ts > sinceTs);
49639
50047
  return { success: true, trace, count: trace.length };
49640
50048
  }
49641
- case "session_host_get_diagnostics": {
49642
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49643
- const diagnostics = await this.traceSessionHostAction("session_host_get_diagnostics", args, () => this.deps.sessionHostControl.getDiagnostics({
49644
- includeSessions: args?.includeSessions !== false,
49645
- limit: Number(args?.limit) || void 0
49646
- }), (result) => ({
49647
- includeSessions: args?.includeSessions !== false,
49648
- limit: Number(args?.limit) || void 0,
49649
- ...summarizeSessionHostDiagnostics(result)
49650
- }));
49651
- return { success: true, diagnostics };
49652
- }
49653
- case "session_host_list_sessions": {
49654
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49655
- const sessions = await this.traceSessionHostAction("session_host_list_sessions", args, () => this.deps.sessionHostControl.listSessions(), (records) => summarizeSessionHostRecords(records));
49656
- return { success: true, sessions };
49657
- }
49658
- case "session_host_stop_session": {
49659
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49660
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49661
- if (!sessionId) return { success: false, error: "sessionId required" };
49662
- const record = await this.traceSessionHostAction("session_host_stop_session", args, () => this.deps.sessionHostControl.stopSession(sessionId), (result) => summarizeSessionHostRecord(result));
49663
- return { success: true, record };
49664
- }
49665
- case "session_host_resume_session": {
49666
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49667
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49668
- if (!sessionId) return { success: false, error: "sessionId required" };
49669
- const record = await this.traceSessionHostAction("session_host_resume_session", args, async () => {
49670
- const nextRecord = await this.deps.sessionHostControl.resumeSession(sessionId);
49671
- const hosted = toHostedCliRuntimeDescriptor(nextRecord);
49672
- if (hosted) {
49673
- await this.deps.cliManager.restoreHostedSessions([hosted]);
49674
- }
49675
- return nextRecord;
49676
- }, (result) => ({
49677
- ...summarizeSessionHostRecord(result),
49678
- restoredHostedSession: !!toHostedCliRuntimeDescriptor(result)
49679
- }));
49680
- return { success: true, record };
49681
- }
49682
- case "session_host_restart_session": {
49683
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49684
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49685
- if (!sessionId) return { success: false, error: "sessionId required" };
49686
- const record = await this.traceSessionHostAction("session_host_restart_session", args, async () => {
49687
- const nextRecord = await this.deps.sessionHostControl.restartSession(sessionId);
49688
- const hosted = toHostedCliRuntimeDescriptor(nextRecord);
49689
- if (hosted) {
49690
- await this.deps.cliManager.restoreHostedSessions([hosted]);
49691
- }
49692
- return nextRecord;
49693
- }, (result) => ({
49694
- ...summarizeSessionHostRecord(result),
49695
- restoredHostedSession: !!toHostedCliRuntimeDescriptor(result)
49696
- }));
49697
- return { success: true, record };
49698
- }
49699
- case "session_host_send_signal": {
49700
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49701
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49702
- const signal = typeof args?.signal === "string" ? args.signal : "";
49703
- if (!sessionId) return { success: false, error: "sessionId required" };
49704
- if (!signal) return { success: false, error: "signal required" };
49705
- const record = await this.traceSessionHostAction("session_host_send_signal", args, () => this.deps.sessionHostControl.sendSignal(sessionId, signal), (result) => summarizeSessionHostRecord(result));
49706
- return { success: true, record };
49707
- }
49708
- case "session_host_force_detach_client": {
49709
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49710
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49711
- const clientId = typeof args?.clientId === "string" ? args.clientId : "";
49712
- if (!sessionId) return { success: false, error: "sessionId required" };
49713
- if (!clientId) return { success: false, error: "clientId required" };
49714
- const record = await this.traceSessionHostAction("session_host_force_detach_client", args, () => this.deps.sessionHostControl.forceDetachClient(sessionId, clientId), (result) => summarizeSessionHostRecord(result));
49715
- return { success: true, record };
49716
- }
49717
- case "session_host_prune_duplicate_sessions": {
49718
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49719
- const result = await this.traceSessionHostAction("session_host_prune_duplicate_sessions", args, () => this.deps.sessionHostControl.pruneDuplicateSessions({
49720
- providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
49721
- workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
49722
- dryRun: args?.dryRun === true
49723
- }), (value) => summarizeSessionHostPruneResult(value));
49724
- return { success: true, result };
49725
- }
49726
- case "session_host_acquire_write": {
49727
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49728
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49729
- const clientId = typeof args?.clientId === "string" ? args.clientId : "";
49730
- const ownerType = args?.ownerType === "agent" ? "agent" : "user";
49731
- if (!sessionId) return { success: false, error: "sessionId required" };
49732
- if (!clientId) return { success: false, error: "clientId required" };
49733
- const record = await this.traceSessionHostAction("session_host_acquire_write", args, () => this.deps.sessionHostControl.acquireWrite({
49734
- sessionId,
49735
- clientId,
49736
- ownerType,
49737
- force: args?.force !== false
49738
- }), (result) => ({
49739
- ...summarizeSessionHostRecord(result),
49740
- ownerType
49741
- }));
49742
- return { success: true, record };
49743
- }
49744
- case "session_host_release_write": {
49745
- if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
49746
- const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
49747
- const clientId = typeof args?.clientId === "string" ? args.clientId : "";
49748
- if (!sessionId) return { success: false, error: "sessionId required" };
49749
- if (!clientId) return { success: false, error: "clientId required" };
49750
- const record = await this.traceSessionHostAction("session_host_release_write", args, () => this.deps.sessionHostControl.releaseWrite({
49751
- sessionId,
49752
- clientId
49753
- }), (result) => summarizeSessionHostRecord(result));
49754
- return { success: true, record };
49755
- }
49756
50049
  case "list_saved_sessions": {
49757
50050
  const providerType = typeof args?.providerType === "string" ? args.providerType.trim() : typeof args?.agentType === "string" ? args.agentType.trim() : "";
49758
50051
  const kind = args?.kind === "acp" ? "acp" : "cli";
@@ -50008,166 +50301,6 @@ ${hintLines.join("\n")}` : "",
50008
50301
  } : null
50009
50302
  };
50010
50303
  }
50011
- case "get_spec_debug": {
50012
- const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
50013
- if (!sessionId) return { success: false, error: "targetSessionId required" };
50014
- const target = this.deps.sessionRegistry.get(sessionId);
50015
- if (!target) return { success: false, error: "Session not found", sessionId };
50016
- const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
50017
- const snapshot = adapterObj ? typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : typeof adapterObj.getDebugState === "function" ? adapterObj.getDebugState() : null : null;
50018
- return {
50019
- success: true,
50020
- sessionId,
50021
- providerType: target.providerType,
50022
- isSpecProvider: snapshot !== null,
50023
- snapshot
50024
- };
50025
- }
50026
- // ── Spec source read/write for the debug panel's live editor.
50027
- // Lets the dashboard load a session's spec.json, edit it, and
50028
- // save it back — the driver's fs.watch picks up the change and
50029
- // hot-reloads the FSM with no restart. Writes are confined to
50030
- // files under ~/.adhdev/providers to avoid arbitrary fs access.
50031
- case "get_spec_source": {
50032
- const fsm = await import("fs");
50033
- const pathm = await import("path");
50034
- const osm = await import("os");
50035
- const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
50036
- let specPath = typeof args?.specPath === "string" ? args.specPath : "";
50037
- if (!specPath && sessionId) {
50038
- const target = this.deps.sessionRegistry.get(sessionId);
50039
- const adapterObj = target ? this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter : null;
50040
- const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
50041
- specPath = snap?.specPath ?? "";
50042
- }
50043
- if (!specPath) return { success: false, error: "specPath or resolvable targetSessionId required" };
50044
- const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
50045
- if (!safe.ok) return { success: false, error: safe.error, specPath };
50046
- try {
50047
- const content = fsm.readFileSync(safe.path, "utf8");
50048
- return { success: true, specPath: safe.path, content };
50049
- } catch (e) {
50050
- return { success: false, error: `read failed: ${e.message}`, specPath };
50051
- }
50052
- }
50053
- case "write_spec_source": {
50054
- const fsm = await import("fs");
50055
- const pathm = await import("path");
50056
- const osm = await import("os");
50057
- const specPath = typeof args?.specPath === "string" ? args.specPath : "";
50058
- const content = typeof args?.content === "string" ? args.content : "";
50059
- if (!specPath) return { success: false, error: "specPath required" };
50060
- if (!content) return { success: false, error: "content required" };
50061
- const safe = resolveSpecPathInProviders(specPath, fsm, pathm, osm);
50062
- if (!safe.ok) return { success: false, error: safe.error };
50063
- let parsed;
50064
- try {
50065
- parsed = JSON.parse(content);
50066
- } catch (e) {
50067
- return { success: false, error: `invalid JSON: ${e.message}` };
50068
- }
50069
- if (parsed?.$schema === "adhdev:cli/spec@4") {
50070
- const { validateFsmSpec: validateFsmSpec2 } = await Promise.resolve().then(() => (init_fsm_loader(), fsm_loader_exports));
50071
- const errs = validateFsmSpec2(parsed);
50072
- if (errs.length) return { success: false, error: "spec invalid", validationErrors: errs };
50073
- }
50074
- try {
50075
- fsm.writeFileSync(safe.path, content, "utf8");
50076
- return { success: true, specPath: safe.path };
50077
- } catch (e) {
50078
- return { success: false, error: `write failed: ${e.message}` };
50079
- }
50080
- }
50081
- // ── Validate an in-progress spec (string or object) without writing.
50082
- // The form builder calls this on every change so Save can stay
50083
- // disabled while there are structural / reference / regex errors.
50084
- case "validate_spec": {
50085
- let parsed = args?.spec;
50086
- if (typeof args?.content === "string") {
50087
- try {
50088
- parsed = JSON.parse(args.content);
50089
- } catch (e) {
50090
- return { success: true, valid: false, errors: [`invalid JSON: ${e.message}`] };
50091
- }
50092
- }
50093
- if (!parsed || typeof parsed !== "object") {
50094
- return { success: true, valid: false, errors: ["spec must be an object or content string"] };
50095
- }
50096
- const schema = parsed.$schema;
50097
- if (schema === "adhdev:cli/spec@4") {
50098
- const { validateFsmSpec: validateFsmSpec2 } = await Promise.resolve().then(() => (init_fsm_loader(), fsm_loader_exports));
50099
- const errors = validateFsmSpec2(parsed);
50100
- return { success: true, valid: errors.length === 0, errors };
50101
- }
50102
- return { success: true, valid: false, errors: [`unsupported $schema "${schema}" \u2014 form builder is v4-only`] };
50103
- }
50104
- // ── Evaluate a single condition against a live session's current
50105
- // screen — powers the editor's "does this match right now?"
50106
- // preview. Returns the recursive match tree.
50107
- case "eval_condition_preview": {
50108
- const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
50109
- if (!sessionId) return { success: false, error: "targetSessionId required" };
50110
- if (!args?.condition || typeof args.condition !== "object") return { success: false, error: "condition required" };
50111
- const target = this.deps.sessionRegistry.get(sessionId);
50112
- if (!target) return { success: false, error: "Session not found", sessionId };
50113
- const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
50114
- const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
50115
- if (!snap?.screen) return { success: false, error: "no live screen for session" };
50116
- let sectionsDef;
50117
- try {
50118
- const fsm2 = await import("fs");
50119
- if (snap.specPath) {
50120
- const raw = JSON.parse(fsm2.readFileSync(snap.specPath, "utf8"));
50121
- sectionsDef = raw?.sections;
50122
- }
50123
- } catch {
50124
- }
50125
- const { evaluateConditionPreview: evaluateConditionPreview2 } = await Promise.resolve().then(() => (init_fsm_evaluator(), fsm_evaluator_exports));
50126
- try {
50127
- const result = evaluateConditionPreview2(
50128
- args.condition,
50129
- sectionsDef,
50130
- snap.screen,
50131
- snap.cursorPosition ?? void 0
50132
- );
50133
- return { success: true, result, sections: snap.sections ?? null };
50134
- } catch (e) {
50135
- return { success: false, error: `eval failed: ${e.message}` };
50136
- }
50137
- }
50138
- // ── Resolve a sections map against a live session's screen — the
50139
- // section editor's "test" button. Returns, for each section id,
50140
- // the line range + the text it captures, so the author can SEE
50141
- // whether a from_top/until/anchor definition carves the screen
50142
- // the way they intend. Accepts an in-progress sections map so it
50143
- // previews unsaved edits.
50144
- case "resolve_section_preview": {
50145
- const sessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : typeof args?.sessionId === "string" ? args.sessionId.trim() : "";
50146
- if (!sessionId) return { success: false, error: "targetSessionId required" };
50147
- if (!args?.sections || typeof args.sections !== "object") return { success: false, error: "sections map required" };
50148
- const target = this.deps.sessionRegistry.get(sessionId);
50149
- if (!target) return { success: false, error: "Session not found", sessionId };
50150
- const adapterObj = this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter;
50151
- const snap = adapterObj && typeof adapterObj.getDebugSnapshot === "function" ? adapterObj.getDebugSnapshot() : null;
50152
- if (!snap?.screen) return { success: false, error: "no live screen for session" };
50153
- const { resolveSections: resolveSections2 } = await Promise.resolve().then(() => (init_evaluator(), evaluator_exports));
50154
- try {
50155
- const lines = String(snap.screen).split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
50156
- const resolved = resolveSections2(args.sections, lines);
50157
- return {
50158
- success: true,
50159
- screenLineCount: lines.length,
50160
- sections: resolved.map((s2) => ({ id: s2.id, fromLine: s2.fromLine, toLine: s2.toLine, text: s2.text }))
50161
- };
50162
- } catch (e) {
50163
- return { success: false, error: `resolve failed: ${e.message}` };
50164
- }
50165
- }
50166
- // ── User-level coordinator-prompt files (~/.adhdev/coordinator-prompts/).
50167
- // These live on this daemon's filesystem and never sync to the
50168
- // cloud / other daemons — they're per-machine config. The
50169
- // Settings page in the dashboard reads/writes via these two
50170
- // commands instead of going through fs from the browser.
50171
50304
  case "list_coordinator_prompts": {
50172
50305
  const fs32 = await import("fs");
50173
50306
  const path42 = await import("path");
@@ -50888,73 +51021,6 @@ ${hintLines.join("\n")}` : "",
50888
51021
  return { success: false, error: e.message };
50889
51022
  }
50890
51023
  }
50891
- case "get_mesh_refine_config_schema": {
50892
- return {
50893
- success: true,
50894
- schema: MESH_REFINE_CONFIG_SCHEMA,
50895
- locations: MESH_REFINE_CONFIG_LOCATIONS,
50896
- worktreeBootstrap: {
50897
- schema: MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
50898
- locations: MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
50899
- sourceOfTruth: "repo worktree bootstrap config",
50900
- runBehavior: "When present and enabled, clone_mesh_node runs commands after submodule initialization and records status on the worktree node."
50901
- },
50902
- sourceOfTruth: "repo mesh/refine config",
50903
- heuristicRole: "suggestions_only_not_execution_path"
50904
- };
50905
- }
50906
- case "validate_mesh_refine_config": {
50907
- const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
50908
- const mesh = args?.inlineMesh || {};
50909
- const loaded = args?.config !== void 0 ? { config: args.config, source: "inline", sourceType: "mesh_policy" } : loadMeshRefineConfig(mesh, workspace);
50910
- const validation = loaded.config ? validateMeshRefineConfig(loaded.config, loaded.source) : { valid: false, errors: [loaded.error || "repo mesh/refine config unavailable"], commands: [], rejectedCommands: [] };
50911
- return { success: validation.valid, ...loaded, ...validation };
50912
- }
50913
- case "suggest_mesh_refine_config": {
50914
- const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
50915
- const mesh = args?.inlineMesh || {};
50916
- return {
50917
- success: true,
50918
- ...suggestMeshRefineConfig(mesh, workspace),
50919
- note: "Suggestions are heuristic scaffold only; Refinery will not execute them until saved into repo mesh/refine config."
50920
- };
50921
- }
50922
- case "get_mesh_change_impact_config_schema": {
50923
- return {
50924
- success: true,
50925
- schema: CHANGE_IMPACT_CONFIG_SCHEMA,
50926
- locations: CHANGE_IMPACT_CONFIG_LOCATIONS,
50927
- sourceOfTruth: "repo change-impact config",
50928
- heuristicRole: "suggestions_only_not_execution_path",
50929
- note: "Declarative config only \u2014 JSON/YAML are parsed but never executed. Defines which package/file changes require a daemon rebuild/restart vs. a web-only redeploy vs. nothing."
50930
- };
50931
- }
50932
- case "validate_mesh_change_impact_config": {
50933
- const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
50934
- if (args?.config !== void 0) {
50935
- const validation = validateChangeImpactConfig(args.config, "inline");
50936
- return { success: validation.valid, source: "inline", sourceType: "mesh_policy", ...validation };
50937
- }
50938
- const loaded = loadChangeImpactConfig(workspace);
50939
- if (loaded.sourceType === "repo_file") {
50940
- const validation = validateChangeImpactConfig(loaded.config, loaded.source);
50941
- return { success: validation.valid, ...loaded, ...validation };
50942
- }
50943
- return {
50944
- success: false,
50945
- ...loaded,
50946
- valid: false,
50947
- errors: [loaded.error || "repo change-impact config unavailable"]
50948
- };
50949
- }
50950
- case "suggest_mesh_change_impact_config": {
50951
- const workspace = typeof args?.workspace === "string" ? args.workspace : process.cwd();
50952
- return {
50953
- success: true,
50954
- ...suggestChangeImpactConfig(workspace),
50955
- note: "Suggestions are heuristic scaffold only; the draft must be reviewed and saved into repo change-impact config before it takes effect. Nothing is executed."
50956
- };
50957
- }
50958
51024
  case "mesh_init": {
50959
51025
  const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : process.cwd();
50960
51026
  const mesh = args?.inlineMesh || {};
@@ -52157,7 +52223,7 @@ ${ptyResult.output.slice(-2e3)}`);
52157
52223
  const isSelfNode = Boolean(
52158
52224
  nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId
52159
52225
  ) || Boolean(
52160
- daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId)
52226
+ daemonId && (daemonIdsEquivalent(daemonId, localMachineId) || daemonIdsEquivalent(daemonId, this.deps.statusInstanceId))
52161
52227
  ) || Boolean(meshRecord?.inline && nodeIndex === 0) || sparseConfiguredCoordinatorNode;
52162
52228
  const machineIdentity = buildMeshNodeMachineIdentity(node, {
52163
52229
  localMachineId,