@adhdev/daemon-standalone 1.0.28-rc.10 → 1.0.28-rc.12
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.js
CHANGED
|
@@ -33262,10 +33262,10 @@ var require_dist3 = __commonJS({
|
|
|
33262
33262
|
}
|
|
33263
33263
|
function getDaemonBuildInfo() {
|
|
33264
33264
|
if (cached2) return cached2;
|
|
33265
|
-
const commit = readInjected(true ? "
|
|
33266
|
-
const commitShort = readInjected(true ? "
|
|
33267
|
-
const version2 = readInjected(true ? "1.0.28-rc.
|
|
33268
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
33265
|
+
const commit = readInjected(true ? "6f38d1460a02018ef53a3ecdf306004005b44310" : void 0) ?? "unknown";
|
|
33266
|
+
const commitShort = readInjected(true ? "6f38d146" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
33267
|
+
const version2 = readInjected(true ? "1.0.28-rc.12" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
33268
|
+
const builtAt = readInjected(true ? "2026-07-26T08:24:40.824Z" : void 0);
|
|
33269
33269
|
cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
|
|
33270
33270
|
return cached2;
|
|
33271
33271
|
}
|
|
@@ -59929,6 +59929,11 @@ ${cleanBody}`;
|
|
|
59929
59929
|
return errs;
|
|
59930
59930
|
}
|
|
59931
59931
|
if ("cursor_above" in w && "changed" in w) return errs;
|
|
59932
|
+
if ("signal" in w) {
|
|
59933
|
+
if (typeof w.signal !== "string" || !w.signal.trim()) errs.push(`${path46}.signal must be a non-empty string`);
|
|
59934
|
+
if (w.equals !== void 0 && typeof w.equals !== "boolean") errs.push(`${path46}.equals must be a boolean`);
|
|
59935
|
+
return errs;
|
|
59936
|
+
}
|
|
59932
59937
|
if ("elapsed_ms" in w) {
|
|
59933
59938
|
if (typeof w.elapsed_ms !== "number") errs.push(`${path46}.elapsed_ms must be a number`);
|
|
59934
59939
|
return errs;
|
|
@@ -60242,6 +60247,49 @@ ${cleanBody}`;
|
|
|
60242
60247
|
"use strict";
|
|
60243
60248
|
}
|
|
60244
60249
|
});
|
|
60250
|
+
function unavailableSignalSnapshot(now, reason, profile) {
|
|
60251
|
+
return {
|
|
60252
|
+
kind: SIGNAL_SNAPSHOT_KIND,
|
|
60253
|
+
sampledAt: now,
|
|
60254
|
+
available: false,
|
|
60255
|
+
unavailableReason: reason,
|
|
60256
|
+
...profile ? { profile } : {},
|
|
60257
|
+
signals: { final_assistant_present: null, in_turn_progress: null, transcript_growing: null },
|
|
60258
|
+
detail: { msgCount: 0, sourceMtimeMs: 0, ageMs: null }
|
|
60259
|
+
};
|
|
60260
|
+
}
|
|
60261
|
+
function evaluateSignalLeaf(cond, snapshot) {
|
|
60262
|
+
const expected = cond.equals ?? true;
|
|
60263
|
+
if (!snapshot || !snapshot.available) {
|
|
60264
|
+
const why = !snapshot ? "no snapshot" : `unavailable(${snapshot.unavailableReason ?? "unknown"})`;
|
|
60265
|
+
return {
|
|
60266
|
+
value: null,
|
|
60267
|
+
shadowResult: null,
|
|
60268
|
+
detail: `signal ${cond.signal} ${why} \u2192 fail-open`
|
|
60269
|
+
};
|
|
60270
|
+
}
|
|
60271
|
+
const raw = Object.prototype.hasOwnProperty.call(snapshot.signals, cond.signal) ? snapshot.signals[cond.signal] : null;
|
|
60272
|
+
if (raw === null || raw === void 0) {
|
|
60273
|
+
return {
|
|
60274
|
+
value: null,
|
|
60275
|
+
shadowResult: null,
|
|
60276
|
+
detail: `signal ${cond.signal} unknown \u2192 fail-open`
|
|
60277
|
+
};
|
|
60278
|
+
}
|
|
60279
|
+
const shadowResult = raw === expected;
|
|
60280
|
+
return {
|
|
60281
|
+
value: raw,
|
|
60282
|
+
shadowResult,
|
|
60283
|
+
detail: `signal ${cond.signal}=${raw} expected=${expected} \u2192 ${shadowResult}`
|
|
60284
|
+
};
|
|
60285
|
+
}
|
|
60286
|
+
var SIGNAL_SNAPSHOT_KIND;
|
|
60287
|
+
var init_signal_envelope = __esm2({
|
|
60288
|
+
"src/providers/spec/signal-envelope.ts"() {
|
|
60289
|
+
"use strict";
|
|
60290
|
+
SIGNAL_SNAPSHOT_KIND = "adhdev:fsm/signal-snapshot@0";
|
|
60291
|
+
}
|
|
60292
|
+
});
|
|
60245
60293
|
var fsm_evaluator_exports = {};
|
|
60246
60294
|
__export2(fsm_evaluator_exports, {
|
|
60247
60295
|
evaluateConditionPreview: () => evaluateConditionPreview,
|
|
@@ -60274,24 +60322,74 @@ ${cleanBody}`;
|
|
|
60274
60322
|
function isNot(c) {
|
|
60275
60323
|
return "not" in c;
|
|
60276
60324
|
}
|
|
60277
|
-
function
|
|
60325
|
+
function isSignal(c) {
|
|
60326
|
+
return "signal" in c;
|
|
60327
|
+
}
|
|
60328
|
+
function containsSignal(c) {
|
|
60329
|
+
if (isSignal(c)) return true;
|
|
60330
|
+
if (isAll(c)) return c.all.some(containsSignal);
|
|
60331
|
+
if (isAny(c)) return c.any.some(containsSignal);
|
|
60332
|
+
if (isNot(c)) return containsSignal(c.not);
|
|
60333
|
+
return false;
|
|
60334
|
+
}
|
|
60335
|
+
function foldShadow(cond) {
|
|
60336
|
+
if (cond.kind === "signal") {
|
|
60337
|
+
const s2 = cond.signal;
|
|
60338
|
+
if (s2 && s2.shadowResult !== null && s2.shadowResult !== void 0) {
|
|
60339
|
+
return { value: s2.shadowResult, unknown: false };
|
|
60340
|
+
}
|
|
60341
|
+
return { value: true, unknown: true };
|
|
60342
|
+
}
|
|
60343
|
+
if (cond.kind === "not" && cond.children?.length) {
|
|
60344
|
+
const c = foldShadow(cond.children[0]);
|
|
60345
|
+
return { value: !c.value, unknown: c.unknown };
|
|
60346
|
+
}
|
|
60347
|
+
if (cond.kind === "all" && cond.children) {
|
|
60348
|
+
const kids = cond.children.map(foldShadow);
|
|
60349
|
+
if (kids.some((k) => !k.unknown && !k.value)) return { value: false, unknown: false };
|
|
60350
|
+
if (kids.some((k) => k.unknown)) return { value: true, unknown: true };
|
|
60351
|
+
return { value: true, unknown: false };
|
|
60352
|
+
}
|
|
60353
|
+
if (cond.kind === "any" && cond.children) {
|
|
60354
|
+
const kids = cond.children.map(foldShadow);
|
|
60355
|
+
if (kids.some((k) => !k.unknown && k.value)) return { value: true, unknown: false };
|
|
60356
|
+
if (kids.some((k) => k.unknown)) return { value: false, unknown: true };
|
|
60357
|
+
return { value: false, unknown: false };
|
|
60358
|
+
}
|
|
60359
|
+
return { value: cond.result, unknown: false };
|
|
60360
|
+
}
|
|
60361
|
+
function evalCond(cond, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId, signalSnapshot) {
|
|
60278
60362
|
if (isAll(cond)) {
|
|
60279
|
-
const children = cond.all.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
|
|
60363
|
+
const children = cond.all.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId, signalSnapshot));
|
|
60280
60364
|
const result = children.every((c) => c.result);
|
|
60281
60365
|
const remainingMs = result ? 0 : Math.max(0, ...children.filter((c) => !c.result).map((c) => c.remainingMs ?? 0));
|
|
60282
60366
|
return { kind: "all", result, detail: `all(${children.length})`, remainingMs, children };
|
|
60283
60367
|
}
|
|
60284
60368
|
if (isAny(cond)) {
|
|
60285
|
-
const children = cond.any.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId));
|
|
60369
|
+
const children = cond.any.map((c) => evalCond(c, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId, signalSnapshot));
|
|
60286
60370
|
const result = children.some((c) => c.result);
|
|
60287
60371
|
const pending = children.filter((c) => !c.result).map((c) => c.remainingMs ?? Infinity);
|
|
60288
60372
|
const remainingMs = result ? 0 : pending.length ? Math.min(...pending) : 0;
|
|
60289
60373
|
return { kind: "any", result, detail: `any(${children.length})`, remainingMs: Number.isFinite(remainingMs) ? remainingMs : 0, children };
|
|
60290
60374
|
}
|
|
60291
60375
|
if (isNot(cond)) {
|
|
60292
|
-
const child = evalCond(cond.not, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId);
|
|
60376
|
+
const child = evalCond(cond.not, sections, fullScreen, cursor, prevLines, clock, legacyTrace, stateId, signalSnapshot);
|
|
60293
60377
|
return { kind: "not", result: !child.result, detail: `not`, remainingMs: 0, children: [child] };
|
|
60294
60378
|
}
|
|
60379
|
+
if (isSignal(cond)) {
|
|
60380
|
+
const leaf = evaluateSignalLeaf(cond, signalSnapshot);
|
|
60381
|
+
return {
|
|
60382
|
+
kind: "signal",
|
|
60383
|
+
result: true,
|
|
60384
|
+
detail: `${leaf.detail} (stage0 shadow \u2014 pass-through)`,
|
|
60385
|
+
signal: {
|
|
60386
|
+
name: cond.signal,
|
|
60387
|
+
available: !!signalSnapshot?.available,
|
|
60388
|
+
value: leaf.value,
|
|
60389
|
+
shadowResult: leaf.shadowResult
|
|
60390
|
+
}
|
|
60391
|
+
};
|
|
60392
|
+
}
|
|
60295
60393
|
if (isElapsed(cond)) {
|
|
60296
60394
|
const age = clock.now - clock.stateEnteredAt;
|
|
60297
60395
|
const result = age >= cond.elapsed_ms;
|
|
@@ -60330,7 +60428,7 @@ ${cleanBody}`;
|
|
|
60330
60428
|
const from = Array.isArray(t.from) ? t.from.join("|") : t.from;
|
|
60331
60429
|
return t.label ?? `${from}\u2192${t.to}`;
|
|
60332
60430
|
}
|
|
60333
|
-
function evaluateFsm(spec, currentStateId, screenText, cursor, prevLines, clock) {
|
|
60431
|
+
function evaluateFsm(spec, currentStateId, screenText, cursor, prevLines, clock, signalSnapshot) {
|
|
60334
60432
|
const legacyTrace = [];
|
|
60335
60433
|
const lines = screenText.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
|
|
60336
60434
|
const cleanScreen = lines.join("\n");
|
|
@@ -60346,7 +60444,7 @@ ${cleanBody}`;
|
|
|
60346
60444
|
let cond;
|
|
60347
60445
|
let condResult = true;
|
|
60348
60446
|
if (t.when) {
|
|
60349
|
-
cond = evalCond(t.when, sections, cleanScreen, cursor, prevLines, clock, legacyTrace, `${currentStateId}\u2192${t.to}
|
|
60447
|
+
cond = evalCond(t.when, sections, cleanScreen, cursor, prevLines, clock, legacyTrace, `${currentStateId}\u2192${t.to}`, signalSnapshot);
|
|
60350
60448
|
condResult = cond.result;
|
|
60351
60449
|
}
|
|
60352
60450
|
const fires = holdSatisfied && condResult;
|
|
@@ -60361,6 +60459,14 @@ ${cleanBody}`;
|
|
|
60361
60459
|
fires,
|
|
60362
60460
|
priority: t.priority ?? 0
|
|
60363
60461
|
};
|
|
60462
|
+
if (t.when && containsSignal(t.when) && cond) {
|
|
60463
|
+
const folded = foldShadow(cond);
|
|
60464
|
+
te.shadow = {
|
|
60465
|
+
condResult: folded.value,
|
|
60466
|
+
fires: holdSatisfied && folded.value,
|
|
60467
|
+
unknown: folded.unknown
|
|
60468
|
+
};
|
|
60469
|
+
}
|
|
60364
60470
|
transitions.push(te);
|
|
60365
60471
|
if (fires && !fired) fired = te;
|
|
60366
60472
|
}
|
|
@@ -60383,6 +60489,7 @@ ${cleanBody}`;
|
|
|
60383
60489
|
"use strict";
|
|
60384
60490
|
init_evaluator();
|
|
60385
60491
|
init_fsm_types();
|
|
60492
|
+
init_signal_envelope();
|
|
60386
60493
|
WHOLE_SCREEN = -1;
|
|
60387
60494
|
}
|
|
60388
60495
|
});
|
|
@@ -78044,12 +78151,21 @@ ${body}
|
|
|
78044
78151
|
appendUpgradeLog(`Waiting for parent pid ${payload.parentPid} to exit`);
|
|
78045
78152
|
await waitForPidExit(payload.parentPid, 15e3);
|
|
78046
78153
|
}
|
|
78047
|
-
if (process.platform === "win32") {
|
|
78154
|
+
if (process.platform === "win32" || payload.killSessionHost === true) {
|
|
78048
78155
|
await stopSessionHostProcesses(sessionHostAppName);
|
|
78049
78156
|
} else {
|
|
78050
78157
|
appendUpgradeLog("POSIX \u2014 session-host left running (survives upgrade; sessions rebind on next boot)");
|
|
78051
78158
|
}
|
|
78052
78159
|
removeDaemonPidFile();
|
|
78160
|
+
if (payload.skipInstall) {
|
|
78161
|
+
appendUpgradeLog("Restart-only mode \u2014 package install skipped, re-spawning daemon");
|
|
78162
|
+
spawnDetachedDaemonRestart(restartArgv, payload.cwd);
|
|
78163
|
+
try {
|
|
78164
|
+
fs15.unlinkSync(getUpgradeFailureNoticePath());
|
|
78165
|
+
} catch {
|
|
78166
|
+
}
|
|
78167
|
+
return;
|
|
78168
|
+
}
|
|
78053
78169
|
const instanceDir = resolveInstanceDir();
|
|
78054
78170
|
const windowsInstallerLayout = resolveWindowsInstallerLayout({
|
|
78055
78171
|
homeDir: os14.homedir(),
|
|
@@ -78168,6 +78284,13 @@ ${body}
|
|
|
78168
78284
|
cleanupStaleGlobalInstallDirs(payload.packageName, installCommand.surface);
|
|
78169
78285
|
appendUpgradeLog("Post-install staging cleanup complete");
|
|
78170
78286
|
}
|
|
78287
|
+
spawnDetachedDaemonRestart(restartArgv, payload.cwd);
|
|
78288
|
+
try {
|
|
78289
|
+
fs15.unlinkSync(getUpgradeFailureNoticePath());
|
|
78290
|
+
} catch {
|
|
78291
|
+
}
|
|
78292
|
+
}
|
|
78293
|
+
function spawnDetachedDaemonRestart(restartArgv, cwd) {
|
|
78171
78294
|
if (restartArgv.length > 0) {
|
|
78172
78295
|
const env2 = { ...process.env };
|
|
78173
78296
|
delete env2[UPGRADE_HELPER_ENV];
|
|
@@ -78176,17 +78299,13 @@ ${body}
|
|
|
78176
78299
|
detached: true,
|
|
78177
78300
|
stdio: "ignore",
|
|
78178
78301
|
windowsHide: true,
|
|
78179
|
-
cwd:
|
|
78302
|
+
cwd: cwd || process.cwd(),
|
|
78180
78303
|
env: env2
|
|
78181
78304
|
});
|
|
78182
78305
|
child.unref();
|
|
78183
78306
|
} else {
|
|
78184
78307
|
appendUpgradeLog("No restart argv provided; upgrade completed without restart");
|
|
78185
78308
|
}
|
|
78186
|
-
try {
|
|
78187
|
-
fs15.unlinkSync(getUpgradeFailureNoticePath());
|
|
78188
|
-
} catch {
|
|
78189
|
-
}
|
|
78190
78309
|
}
|
|
78191
78310
|
async function maybeRunDaemonUpgradeHelperFromEnv2() {
|
|
78192
78311
|
const raw = process.env[UPGRADE_HELPER_ENV];
|
|
@@ -78277,6 +78396,41 @@ ${body}
|
|
|
78277
78396
|
return { success: false, error: e.message };
|
|
78278
78397
|
}
|
|
78279
78398
|
},
|
|
78399
|
+
// Restart-only path (mesh restart_daemon_node mode="restart"): re-spawn the
|
|
78400
|
+
// daemon WITHOUT the npm reinstall daemon_upgrade performs. Used to reset
|
|
78401
|
+
// daemon state (memory leaks, zombie sessions, wedged internals) when the
|
|
78402
|
+
// version is already correct — downtime drops to the detached re-spawn.
|
|
78403
|
+
// killSessionHost is an explicit opt-in hard refresh (see upgrade-helper).
|
|
78404
|
+
daemon_restart: async (ctx, args) => {
|
|
78405
|
+
LOG2.info("Restart", "Restart-only requested (no package reinstall)");
|
|
78406
|
+
try {
|
|
78407
|
+
const isStandalone = ctx.deps.packageName === "@adhdev/daemon-standalone" || process.argv[1]?.includes("daemon-standalone");
|
|
78408
|
+
const pkgName = isStandalone ? "@adhdev/daemon-standalone" : "adhdev";
|
|
78409
|
+
const killSessionHost = args?.killSessionHost === true;
|
|
78410
|
+
if (killSessionHost) {
|
|
78411
|
+
LOG2.warn("Restart", "killSessionHost requested \u2014 session-host will be stopped and ALL hosted sessions destroyed");
|
|
78412
|
+
}
|
|
78413
|
+
spawnDetachedDaemonUpgradeHelper({
|
|
78414
|
+
packageName: pkgName,
|
|
78415
|
+
targetVersion: "",
|
|
78416
|
+
parentPid: process.pid,
|
|
78417
|
+
restartArgv: process.argv.slice(1),
|
|
78418
|
+
cwd: process.cwd(),
|
|
78419
|
+
sessionHostAppName: process.env.ADHDEV_SESSION_HOST_NAME || "adhdev",
|
|
78420
|
+
skipInstall: true,
|
|
78421
|
+
killSessionHost
|
|
78422
|
+
});
|
|
78423
|
+
LOG2.info("Restart", "Scheduled detached restart-only helper");
|
|
78424
|
+
setTimeout(() => {
|
|
78425
|
+
LOG2.info("Restart", "Exiting daemon so detached helper can re-spawn it...");
|
|
78426
|
+
process.exit(0);
|
|
78427
|
+
}, 3e3);
|
|
78428
|
+
return { success: true, restarted: true, restarting: true, mode: "restart", killSessionHost };
|
|
78429
|
+
} catch (e) {
|
|
78430
|
+
LOG2.error("Restart", `Failed: ${e.message}`);
|
|
78431
|
+
return { success: false, error: e.message };
|
|
78432
|
+
}
|
|
78433
|
+
},
|
|
78280
78434
|
set_machine_nickname: async (_ctx, args) => {
|
|
78281
78435
|
const nickname = args?.nickname;
|
|
78282
78436
|
updateConfig({ machineNickname: nickname || null });
|
|
@@ -78754,6 +78908,91 @@ ${body}
|
|
|
78754
78908
|
init_provider_input_support();
|
|
78755
78909
|
init_hash();
|
|
78756
78910
|
init_transcript_evidence();
|
|
78911
|
+
init_logger();
|
|
78912
|
+
init_signal_envelope();
|
|
78913
|
+
var TranscriptSignalSource = class {
|
|
78914
|
+
constructor(opts) {
|
|
78915
|
+
this.opts = opts;
|
|
78916
|
+
}
|
|
78917
|
+
/** msgCount seen at the previous update — drives in_turn_progress. */
|
|
78918
|
+
prevMsgCount = -1;
|
|
78919
|
+
/** Fingerprint of the last LOGGED snapshot, so the shadow log fires on
|
|
78920
|
+
* change only (a quiet session must not spam one line per read). */
|
|
78921
|
+
lastLoggedFingerprint = "";
|
|
78922
|
+
/**
|
|
78923
|
+
* Normalize one daemon read into a SignalSnapshot and emit the Stage-0
|
|
78924
|
+
* shadow log when the observation changed. Pure w.r.t. the outside world:
|
|
78925
|
+
* the only side effect is the (change-gated) log line. Returns the
|
|
78926
|
+
* snapshot the caller should inject into the FSM driver.
|
|
78927
|
+
*/
|
|
78928
|
+
update(sample, now = Date.now()) {
|
|
78929
|
+
const snapshot = this.buildSnapshot(sample, now);
|
|
78930
|
+
this.logOnChange(snapshot);
|
|
78931
|
+
return snapshot;
|
|
78932
|
+
}
|
|
78933
|
+
/** Pure normalization — separated from update() so tests can assert the
|
|
78934
|
+
* envelope without touching the log path. */
|
|
78935
|
+
buildSnapshot(sample, now) {
|
|
78936
|
+
const profileRef = { class: this.opts.profile.class, timing: this.opts.profile.timing };
|
|
78937
|
+
if (this.opts.profile.class !== "native-source") {
|
|
78938
|
+
return unavailableSignalSnapshot(now, "no_native_source", profileRef);
|
|
78939
|
+
}
|
|
78940
|
+
if (sample.error) {
|
|
78941
|
+
return unavailableSignalSnapshot(now, "error", profileRef);
|
|
78942
|
+
}
|
|
78943
|
+
const probe = sample.probe;
|
|
78944
|
+
if (!probe || !Array.isArray(sample.messages)) {
|
|
78945
|
+
return unavailableSignalSnapshot(now, "unresolved", profileRef);
|
|
78946
|
+
}
|
|
78947
|
+
const msgCount = typeof probe.msgCount === "number" && Number.isFinite(probe.msgCount) ? probe.msgCount : sample.messages.length;
|
|
78948
|
+
const sourceMtimeMs = typeof probe.sourceMtimeMs === "number" && Number.isFinite(probe.sourceMtimeMs) ? probe.sourceMtimeMs : 0;
|
|
78949
|
+
const ageMs = sourceMtimeMs > 0 ? Math.max(0, now - sourceMtimeMs) : null;
|
|
78950
|
+
let turnStartedAt;
|
|
78951
|
+
try {
|
|
78952
|
+
turnStartedAt = this.opts.turnStartedAt?.();
|
|
78953
|
+
} catch {
|
|
78954
|
+
turnStartedAt = void 0;
|
|
78955
|
+
}
|
|
78956
|
+
let finalAssistant = null;
|
|
78957
|
+
try {
|
|
78958
|
+
finalAssistant = this.opts.finalAssistantPresent(sample.messages, turnStartedAt);
|
|
78959
|
+
} catch {
|
|
78960
|
+
finalAssistant = null;
|
|
78961
|
+
}
|
|
78962
|
+
const countAdvanced = this.prevMsgCount >= 0 && msgCount > this.prevMsgCount;
|
|
78963
|
+
const fresh = ageMs !== null && ageMs < this.opts.growthQuietMs;
|
|
78964
|
+
const inTurnProgress = countAdvanced || fresh;
|
|
78965
|
+
const transcriptGrowing = ageMs === null ? null : fresh;
|
|
78966
|
+
this.prevMsgCount = msgCount;
|
|
78967
|
+
return {
|
|
78968
|
+
kind: SIGNAL_SNAPSHOT_KIND,
|
|
78969
|
+
sampledAt: now,
|
|
78970
|
+
available: true,
|
|
78971
|
+
profile: profileRef,
|
|
78972
|
+
signals: {
|
|
78973
|
+
final_assistant_present: finalAssistant,
|
|
78974
|
+
in_turn_progress: inTurnProgress,
|
|
78975
|
+
transcript_growing: transcriptGrowing
|
|
78976
|
+
},
|
|
78977
|
+
detail: { msgCount, sourceMtimeMs, ageMs }
|
|
78978
|
+
};
|
|
78979
|
+
}
|
|
78980
|
+
/** Stage-0 shadow log: emit one line when the normalized observation
|
|
78981
|
+
* CHANGES. This log is the Stage 1-3 judgment input ("which signals were
|
|
78982
|
+
* actually observable, and when"), so it carries the full signal set —
|
|
78983
|
+
* but only on change, so a steady-state session stays quiet. */
|
|
78984
|
+
logOnChange(snapshot) {
|
|
78985
|
+
const s2 = snapshot.signals;
|
|
78986
|
+
const fingerprint = snapshot.available ? `1|fa=${s2.final_assistant_present}|tp=${s2.in_turn_progress}|tg=${s2.transcript_growing}|n=${snapshot.detail.msgCount}` : `0|${snapshot.unavailableReason ?? "unknown"}`;
|
|
78987
|
+
if (fingerprint === this.lastLoggedFingerprint) return;
|
|
78988
|
+
this.lastLoggedFingerprint = fingerprint;
|
|
78989
|
+
const age = snapshot.detail.ageMs === null ? "n/a" : `${snapshot.detail.ageMs}ms`;
|
|
78990
|
+
LOG2.info(
|
|
78991
|
+
"TranscriptSignalSource",
|
|
78992
|
+
`[${this.opts.label}] [shadow] signals available=${snapshot.available}` + (snapshot.available ? ` final_assistant_present=${s2.final_assistant_present} in_turn_progress=${s2.in_turn_progress} transcript_growing=${s2.transcript_growing} msgCount=${snapshot.detail.msgCount} age=${age}` : ` reason=${snapshot.unavailableReason ?? "unknown"}`)
|
|
78993
|
+
);
|
|
78994
|
+
}
|
|
78995
|
+
};
|
|
78757
78996
|
var fs222 = __toESM2(require("fs"));
|
|
78758
78997
|
var path26 = __toESM2(require("path"));
|
|
78759
78998
|
init_provider_cli_adapter();
|
|
@@ -79107,6 +79346,15 @@ ${body}
|
|
|
79107
79346
|
* transition — the rich pre-transition table that lastFsmEval only keeps
|
|
79108
79347
|
* for the single most recent evaluation. Separate from stateHistory. */
|
|
79109
79348
|
fsmSnapshotHistory = [];
|
|
79349
|
+
/** TX-FSM Stage 0 (shadow): the latest daemon-injected signal observation.
|
|
79350
|
+
* Read by evalFsmNow for the shadow verdict of `signal` conditions ONLY —
|
|
79351
|
+
* the Stage-0 pass-through in the evaluator means it can never alter
|
|
79352
|
+
* which transition fires. */
|
|
79353
|
+
signalObservation = null;
|
|
79354
|
+
/** Per-transition last-logged shadow divergence (`${from}→${to}` → whether
|
|
79355
|
+
* the shadow verdict currently disagrees with the real one), so the
|
|
79356
|
+
* shadow log emits on FLIP only, not every frame. */
|
|
79357
|
+
shadowDivergenceLast = /* @__PURE__ */ new Map();
|
|
79110
79358
|
subscribe(listener) {
|
|
79111
79359
|
this.listeners.add(listener);
|
|
79112
79360
|
return () => {
|
|
@@ -79179,6 +79427,16 @@ ${body}
|
|
|
79179
79427
|
updateMeta(meta3, replace = false) {
|
|
79180
79428
|
this.adapter.updateMeta(meta3, replace);
|
|
79181
79429
|
}
|
|
79430
|
+
/**
|
|
79431
|
+
* TX-FSM Stage 0 (shadow): receive the daemon-normalized signal
|
|
79432
|
+
* observation. The driver stores it verbatim — it does NOT poll, parse,
|
|
79433
|
+
* or read anything itself (generic-PTY-engine boundary). The observation
|
|
79434
|
+
* only feeds the shadow verdict of `signal` conditions; the evaluator's
|
|
79435
|
+
* Stage-0 pass-through guarantees it cannot change a transition.
|
|
79436
|
+
*/
|
|
79437
|
+
setSignalObservation(snapshot) {
|
|
79438
|
+
this.signalObservation = snapshot ?? null;
|
|
79439
|
+
}
|
|
79182
79440
|
snapshot() {
|
|
79183
79441
|
return this.adapter.snapshot();
|
|
79184
79442
|
}
|
|
@@ -79369,7 +79627,35 @@ ${body}
|
|
|
79369
79627
|
}
|
|
79370
79628
|
evalFsmNow(screen, cursor, now) {
|
|
79371
79629
|
const prev = this.prevScreenLines.length > 0 ? this.prevScreenLines : void 0;
|
|
79372
|
-
return evaluateFsm(this.spec, this.currentStateId, screen, cursor, prev, this.buildClock(now));
|
|
79630
|
+
return evaluateFsm(this.spec, this.currentStateId, screen, cursor, prev, this.buildClock(now), this.signalObservation);
|
|
79631
|
+
}
|
|
79632
|
+
/**
|
|
79633
|
+
* TX-FSM Stage 0 (shadow): compare each signal-guarded transition's
|
|
79634
|
+
* counterfactual verdict against the real (PTY-only) one and log on FLIP.
|
|
79635
|
+
* This is the "만약 적용했다면" half of the shadow log — the Stage 1-3
|
|
79636
|
+
* evidence for whether signal gating would have changed any verdict, and
|
|
79637
|
+
* in which direction. Read-only: runs after the evaluation is complete
|
|
79638
|
+
* and never feeds back into it.
|
|
79639
|
+
*/
|
|
79640
|
+
logShadowDivergence(ev) {
|
|
79641
|
+
for (const t of ev.transitions) {
|
|
79642
|
+
if (!t.shadow) continue;
|
|
79643
|
+
const key2 = `${this.currentStateId}\u2192${t.to}`;
|
|
79644
|
+
const diverges = t.shadow.fires !== t.fires;
|
|
79645
|
+
if ((this.shadowDivergenceLast.get(key2) ?? false) === diverges) continue;
|
|
79646
|
+
this.shadowDivergenceLast.set(key2, diverges);
|
|
79647
|
+
if (diverges) {
|
|
79648
|
+
LOG2.info(
|
|
79649
|
+
"FsmDriver",
|
|
79650
|
+
`[${this.specTag()}] [shadow] ${key2} (${t.label}): real fires=${t.fires} but signal-gated verdict would be fires=${t.shadow.fires} (shadowCond=${t.shadow.condResult}${t.shadow.unknown ? ", signal unknown/fail-open" : ""})`
|
|
79651
|
+
);
|
|
79652
|
+
} else {
|
|
79653
|
+
LOG2.info(
|
|
79654
|
+
"FsmDriver",
|
|
79655
|
+
`[${this.specTag()}] [shadow] ${key2} (${t.label}): shadow verdict realigned with real fires=${t.fires}`
|
|
79656
|
+
);
|
|
79657
|
+
}
|
|
79658
|
+
}
|
|
79373
79659
|
}
|
|
79374
79660
|
reevaluate(forceEmit = false) {
|
|
79375
79661
|
const now = Date.now();
|
|
@@ -79380,6 +79666,7 @@ ${body}
|
|
|
79380
79666
|
const ev = this.evalFsmNow(screen, cursor, now);
|
|
79381
79667
|
this.lastFsmEval = ev;
|
|
79382
79668
|
this.prevScreenLines = currentLines;
|
|
79669
|
+
this.logShadowDivergence(ev);
|
|
79383
79670
|
if (ev.fired) {
|
|
79384
79671
|
this.commitTransition(ev.fired, now, ev);
|
|
79385
79672
|
this.emitStateChanged(forceEmit);
|
|
@@ -82016,6 +82303,18 @@ ${body}
|
|
|
82016
82303
|
}
|
|
82017
82304
|
refreshProviderDefinition() {
|
|
82018
82305
|
}
|
|
82306
|
+
/**
|
|
82307
|
+
* TX-FSM Stage 0 (shadow): forward the daemon's normalized signal
|
|
82308
|
+
* observation into the FSM driver. Observation-only — failures here must
|
|
82309
|
+
* never break the adapter, and the driver treats the envelope as a pure
|
|
82310
|
+
* injected value (no reads cross the engine boundary).
|
|
82311
|
+
*/
|
|
82312
|
+
setSignalObservation(snapshot) {
|
|
82313
|
+
try {
|
|
82314
|
+
this.driver.setSignalObservation?.(snapshot);
|
|
82315
|
+
} catch {
|
|
82316
|
+
}
|
|
82317
|
+
}
|
|
82019
82318
|
handleEvent(ev) {
|
|
82020
82319
|
this.lastEvent = ev;
|
|
82021
82320
|
switch (ev.kind) {
|
|
@@ -83037,18 +83336,20 @@ ${body}
|
|
|
83037
83336
|
// MESH_WORKER_STALL_REFIRE_COOLDOWN_MS between successive emissions across
|
|
83038
83337
|
// anchor re-arms (the per-anchor guard only covers a single continuous stall).
|
|
83039
83338
|
meshStallLastFiredAt = -1;
|
|
83040
|
-
// KIMI-MESH-COMPLETION-EMIT (axis 1)
|
|
83041
|
-
//
|
|
83042
|
-
//
|
|
83043
|
-
//
|
|
83044
|
-
//
|
|
83045
|
-
//
|
|
83046
|
-
//
|
|
83047
|
-
//
|
|
83048
|
-
//
|
|
83049
|
-
// handling can lead to the worker being stopped mid-work → completion
|
|
83050
|
-
// emitted).
|
|
83051
|
-
|
|
83339
|
+
// KIMI-MESH-COMPLETION-EMIT (axis 1) — TX-FSM Stage 1: whether the stall
|
|
83340
|
+
// watchdog has consumed a usable native-transcript SIGNAL SNAPSHOT for the
|
|
83341
|
+
// current stall episode. A pure-PTY native-source worker running a long,
|
|
83342
|
+
// screen-quiet tool (no viewport bytes for minutes) looks stalled by the
|
|
83343
|
+
// lastOutputAt clock even though its transcript file is still growing.
|
|
83344
|
+
// Before firing the stall, the watchdog consults the shared
|
|
83345
|
+
// TranscriptSignalSource's in_turn_progress signal; if the transcript is
|
|
83346
|
+
// advancing, the "stall" is a false positive of PTY-render stasis, so we
|
|
83347
|
+
// re-arm the anchor instead of paging the coordinator (whose no_progress
|
|
83348
|
+
// handling can lead to the worker being stopped mid-work → completion
|
|
83349
|
+
// never emitted). The FIRST usable sample of an episode still re-arms
|
|
83350
|
+
// unconditionally (no episode-scoped baseline exists yet to prove stasis
|
|
83351
|
+
// against) — the historical `!prev → advanced` semantics, preserved.
|
|
83352
|
+
meshStallTranscriptSignalSampled = false;
|
|
83052
83353
|
// FALSE-IDLE continuity epoch: monotonically bumped on EVERY entry into a busy
|
|
83053
83354
|
// phase (→generating or →waiting_approval). The completedDebouncePending snapshots
|
|
83054
83355
|
// this value at arm time (busyEpochAtArm); the flush guard requires it UNCHANGED
|
|
@@ -83846,6 +84147,17 @@ ${body}
|
|
|
83846
84147
|
completedDebounceTimer = null;
|
|
83847
84148
|
completedDebouncePending = null;
|
|
83848
84149
|
lastExternalCompletionProbe = null;
|
|
84150
|
+
/** TX-FSM: lazily-created transcript signal normalizer. Fed ONLY by
|
|
84151
|
+
* transcript reads this instance already performs — it adds zero I/O.
|
|
84152
|
+
* Stage 0: its output was a pure shadow observation for the FSM driver.
|
|
84153
|
+
* Stage 1: the instance's own stall/growth-hold judgments consume the
|
|
84154
|
+
* normalized snapshot too (single source of truth). */
|
|
84155
|
+
transcriptSignalSource = null;
|
|
84156
|
+
/** TX-FSM Stage 1: the latest snapshot the source produced (set inside
|
|
84157
|
+
* publishTranscriptSignalObservation). Consumed the same tick by the
|
|
84158
|
+
* stall-path / growth-hold judgments via probeNativeTranscriptSignals —
|
|
84159
|
+
* never treated as fresh across ticks. */
|
|
84160
|
+
lastTranscriptSignalSnapshot = null;
|
|
83849
84161
|
/**
|
|
83850
84162
|
* The final assistant summary of the last completed turn, cached at
|
|
83851
84163
|
* completion-emit time. For a native-source provider (antigravity) whose
|
|
@@ -84011,6 +84323,7 @@ ${body}
|
|
|
84011
84323
|
});
|
|
84012
84324
|
if (restoredHistory.source !== "provider-native") {
|
|
84013
84325
|
this.lastExternalCompletionProbe = null;
|
|
84326
|
+
this.publishTranscriptSignalObservation(null);
|
|
84014
84327
|
return null;
|
|
84015
84328
|
}
|
|
84016
84329
|
this.lastExternalCompletionProbe = buildExternalTranscriptProbe(
|
|
@@ -84018,8 +84331,55 @@ ${body}
|
|
|
84018
84331
|
restoredHistory.sourcePath,
|
|
84019
84332
|
restoredHistory.sourceMtimeMs
|
|
84020
84333
|
);
|
|
84334
|
+
this.publishTranscriptSignalObservation(restoredHistory.messages);
|
|
84021
84335
|
return restoredHistory.messages;
|
|
84022
84336
|
}
|
|
84337
|
+
/**
|
|
84338
|
+
* TX-FSM: normalize the transcript read that JUST happened into a
|
|
84339
|
+
* SignalSnapshot. Stage 0 injected it into the FSM driver as a pure
|
|
84340
|
+
* shadow observation (daemon → SpecCliAdapter → FsmDriver); Stage 1
|
|
84341
|
+
* additionally caches it (lastTranscriptSignalSnapshot) so the instance's
|
|
84342
|
+
* OWN stall/growth-hold judgments consume the SAME normalized snapshot
|
|
84343
|
+
* instead of re-running private transcript scans. Fed ONLY by reads this
|
|
84344
|
+
* method's caller already performs — it adds zero I/O, so the getState()
|
|
84345
|
+
* zero-native-read invariant and the stall-path read cadence are
|
|
84346
|
+
* untouched. The source update runs regardless of the adapter hook so a
|
|
84347
|
+
* non-spec provider still produces the instance-side snapshot (the FSM
|
|
84348
|
+
* injection is simply skipped there). Fail-open end to end — an
|
|
84349
|
+
* unresolved transcript or any throw degrades to "no observation", never
|
|
84350
|
+
* to a wedge.
|
|
84351
|
+
*/
|
|
84352
|
+
publishTranscriptSignalObservation(messages, error48 = false) {
|
|
84353
|
+
try {
|
|
84354
|
+
if (!this.transcriptSignalSource) {
|
|
84355
|
+
this.transcriptSignalSource = new TranscriptSignalSource({
|
|
84356
|
+
label: this.type,
|
|
84357
|
+
// Choke point: class/timing come from the P0 profile
|
|
84358
|
+
// resolver, never from raw predicates or provider names.
|
|
84359
|
+
profile: resolveTranscriptAuthorityProfile(this.provider),
|
|
84360
|
+
turnStartedAt: () => {
|
|
84361
|
+
const t = this.adapter?.currentTurnStartedAt;
|
|
84362
|
+
if (typeof t === "number" && Number.isFinite(t)) return t;
|
|
84363
|
+
return this.meshTaskInjectedAt > 0 ? this.meshTaskInjectedAt : void 0;
|
|
84364
|
+
},
|
|
84365
|
+
// Reuse the exact completion machinery (I1) for the
|
|
84366
|
+
// final_assistant_present signal rather than duplicating
|
|
84367
|
+
// the message scan.
|
|
84368
|
+
finalAssistantPresent: (msgs, ts2) => this.completionHasFinalAssistantMessage(msgs, ts2),
|
|
84369
|
+
growthQuietMs: MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS
|
|
84370
|
+
});
|
|
84371
|
+
}
|
|
84372
|
+
const snapshot = this.transcriptSignalSource.update(
|
|
84373
|
+
{ messages, probe: this.lastExternalCompletionProbe, error: error48 }
|
|
84374
|
+
);
|
|
84375
|
+
this.lastTranscriptSignalSnapshot = snapshot;
|
|
84376
|
+
const adapter = this.adapter;
|
|
84377
|
+
if (typeof adapter?.setSignalObservation === "function") {
|
|
84378
|
+
adapter.setSignalObservation(snapshot);
|
|
84379
|
+
}
|
|
84380
|
+
} catch {
|
|
84381
|
+
}
|
|
84382
|
+
}
|
|
84023
84383
|
/**
|
|
84024
84384
|
* The content of the LAST visible assistant bubble in a message list, or ''
|
|
84025
84385
|
* when the tail is not an assistant reply. Skips trailing system/tool/activity
|
|
@@ -84509,17 +84869,17 @@ ${body}
|
|
|
84509
84869
|
const threshold = turnActive ? _CliProviderInstance.MESH_WORKER_STALL_TURN_THRESHOLD_MS : _CliProviderInstance.MESH_WORKER_STALL_IDLE_THRESHOLD_MS;
|
|
84510
84870
|
const stalledMs = now - this.meshStallAnchorAt;
|
|
84511
84871
|
if (stalledMs < threshold) return;
|
|
84512
|
-
const
|
|
84513
|
-
if (
|
|
84514
|
-
const
|
|
84515
|
-
|
|
84516
|
-
|
|
84517
|
-
if (
|
|
84872
|
+
const transcriptSignals = this.probeNativeTranscriptSignals();
|
|
84873
|
+
if (transcriptSignals?.snapshot?.available === true) {
|
|
84874
|
+
const firstSampleThisEpisode = !this.meshStallTranscriptSignalSampled;
|
|
84875
|
+
this.meshStallTranscriptSignalSampled = true;
|
|
84876
|
+
const signalDetail = transcriptSignals.snapshot.detail;
|
|
84877
|
+
if (firstSampleThisEpisode || transcriptSignals.snapshot.signals.in_turn_progress === true) {
|
|
84518
84878
|
if (this.isMeshWorkerSession()) {
|
|
84519
84879
|
traceMeshEventDrop(
|
|
84520
84880
|
"mesh_worker_stall_transcript_advancing",
|
|
84521
84881
|
this.meshTraceCtx("monitor:no_progress"),
|
|
84522
|
-
`msgCount=${
|
|
84882
|
+
`msgCount=${signalDetail.msgCount} sourceMtime=${signalDetail.sourceMtimeMs} (PTY quiet ${Math.round(stalledMs / 1e3)}s but transcript advancing)`
|
|
84523
84883
|
);
|
|
84524
84884
|
}
|
|
84525
84885
|
this.meshStallAnchorAt = now;
|
|
@@ -84527,7 +84887,7 @@ ${body}
|
|
|
84527
84887
|
return;
|
|
84528
84888
|
}
|
|
84529
84889
|
}
|
|
84530
|
-
if (this.tryReconcileTranscriptCompletionForStall(observedStatus)) {
|
|
84890
|
+
if (this.tryReconcileTranscriptCompletionForStall(observedStatus, transcriptSignals)) {
|
|
84531
84891
|
this.meshStallEmittedForAnchor = true;
|
|
84532
84892
|
return;
|
|
84533
84893
|
}
|
|
@@ -84567,38 +84927,45 @@ ${body}
|
|
|
84567
84927
|
this.meshStallEmittedForAnchor = false;
|
|
84568
84928
|
this.meshStallTurnActiveLast = void 0;
|
|
84569
84929
|
this.meshStallLastFiredAt = -1;
|
|
84570
|
-
this.
|
|
84930
|
+
this.meshStallTranscriptSignalSampled = false;
|
|
84571
84931
|
}
|
|
84932
|
+
/** The result of one native-transcript signal probe: the normalized
|
|
84933
|
+
* snapshot the shared TranscriptSignalSource produced from the read, and
|
|
84934
|
+
* the very messages it was normalized from (so a judgment site can pull
|
|
84935
|
+
* a payload — e.g. the final summary — from the SAME read with zero
|
|
84936
|
+
* added I/O). */
|
|
84572
84937
|
/**
|
|
84573
|
-
*
|
|
84574
|
-
*
|
|
84575
|
-
*
|
|
84576
|
-
*
|
|
84577
|
-
*
|
|
84578
|
-
*
|
|
84579
|
-
*
|
|
84580
|
-
*
|
|
84581
|
-
*
|
|
84938
|
+
* TX-FSM Stage 1 — the single native-transcript signal probe (replaces
|
|
84939
|
+
* the Stage-0 sampleNativeTranscriptProgress fingerprint sampler). For a
|
|
84940
|
+
* native-source provider (its authoritative history is an on-disk
|
|
84941
|
+
* transcript file, e.g. kimi's wire.jsonl), perform the read this
|
|
84942
|
+
* judgment point already owns — SAME cadence as before, one
|
|
84943
|
+
* readExternalCompletionMessages() per call, never more — and return the
|
|
84944
|
+
* NORMALIZED SignalSnapshot the shared TranscriptSignalSource produced
|
|
84945
|
+
* from it (publishTranscriptSignalObservation runs inside the read), plus
|
|
84946
|
+
* the messages that read returned. Judgment sites (the stall watchdog's
|
|
84947
|
+
* transcript-advancing axis, the completion growth-hold, the stall-path
|
|
84948
|
+
* completion rescue) consume the snapshot's SIGNALS instead of running
|
|
84949
|
+
* their own fingerprint/freshness/final-assistant scans — one source of
|
|
84950
|
+
* truth for "what does the transcript say right now".
|
|
84582
84951
|
*
|
|
84583
|
-
*
|
|
84584
|
-
*
|
|
84585
|
-
*
|
|
84586
|
-
* (
|
|
84952
|
+
* Class gating goes through resolveTranscriptAuthorityProfile ONLY.
|
|
84953
|
+
* Returns null for a non-native-source class (nothing to signal from) and
|
|
84954
|
+
* a null snapshot when the read threw — callers keep their fail-open
|
|
84955
|
+
* fallbacks ("couldn't tell" never blocks an idle verdict and never
|
|
84956
|
+
* fabricates a completion). Cheap enough for the stall path: it runs
|
|
84957
|
+
* only at the stall threshold (≥180s of PTY stasis) or during an armed
|
|
84958
|
+
* completion-debounce retry, never on the routine 5s tick.
|
|
84587
84959
|
*/
|
|
84588
|
-
|
|
84589
|
-
if (
|
|
84960
|
+
probeNativeTranscriptSignals() {
|
|
84961
|
+
if (resolveTranscriptAuthorityProfile(this.provider).class !== "native-source") return null;
|
|
84590
84962
|
let messages = null;
|
|
84591
84963
|
try {
|
|
84592
84964
|
messages = this.readExternalCompletionMessages();
|
|
84593
84965
|
} catch {
|
|
84594
|
-
return null;
|
|
84966
|
+
return { snapshot: null, messages: null };
|
|
84595
84967
|
}
|
|
84596
|
-
|
|
84597
|
-
if (!probe) return null;
|
|
84598
|
-
return {
|
|
84599
|
-
msgCount: typeof probe.msgCount === "number" ? probe.msgCount : Array.isArray(messages) ? messages.length : 0,
|
|
84600
|
-
sourceMtimeMs: typeof probe.sourceMtimeMs === "number" ? probe.sourceMtimeMs : 0
|
|
84601
|
-
};
|
|
84968
|
+
return { snapshot: this.lastTranscriptSignalSnapshot, messages };
|
|
84602
84969
|
}
|
|
84603
84970
|
/**
|
|
84604
84971
|
* KIMI-MESH-COMPLETION-EMIT (axis 2). Last-chance completion emit for a mesh
|
|
@@ -84687,8 +85054,22 @@ ${body}
|
|
|
84687
85054
|
* false for a daemon-owned provider and for a genuinely mid-turn / wedged
|
|
84688
85055
|
* worker with no in-turn final assistant, so a real stall still fires
|
|
84689
85056
|
* unchanged. Evidence bar is identical to flushMeshCompletionBeforeCleanup.
|
|
85057
|
+
*
|
|
85058
|
+
* TX-FSM Stage 1 (FIX 3 probe delegation): for a native-source class the
|
|
85059
|
+
* completion VERDICT is the shared TranscriptSignalSource's
|
|
85060
|
+
* final_assistant_present signal, normalized from the ONE transcript read
|
|
85061
|
+
* the stall watchdog already performed this tick (passed in as
|
|
85062
|
+
* `transcriptSignals`) — not a second private read + scan. The emit
|
|
85063
|
+
* payload (finalSummary) is extracted from the SAME messages the signal
|
|
85064
|
+
* was normalized from, with the SAME turn boundary the legacy path used,
|
|
85065
|
+
* so the extraction re-proves the turn scope the signal checked. Fail-open
|
|
85066
|
+
* both ways: no usable snapshot (read failed / unresolved / a pure-PTY
|
|
85067
|
+
* class, which has no native signal by construction) → the legacy
|
|
85068
|
+
* class-appropriate evidence path (completionFinalSummary), unchanged; and
|
|
85069
|
+
* a present-but-unextractable signal yields false rather than a
|
|
85070
|
+
* payload-less emit.
|
|
84690
85071
|
*/
|
|
84691
|
-
tryReconcileTranscriptCompletionForStall(observedStatus) {
|
|
85072
|
+
tryReconcileTranscriptCompletionForStall(observedStatus, transcriptSignals) {
|
|
84692
85073
|
const profile = resolveTranscriptAuthorityProfile(this.provider);
|
|
84693
85074
|
if (profile.class === "daemon-owned") return false;
|
|
84694
85075
|
if (observedStatus !== "idle") return false;
|
|
@@ -84706,10 +85087,19 @@ ${body}
|
|
|
84706
85087
|
parsedMessages = void 0;
|
|
84707
85088
|
}
|
|
84708
85089
|
let finalSummary;
|
|
84709
|
-
|
|
84710
|
-
|
|
84711
|
-
|
|
84712
|
-
finalSummary =
|
|
85090
|
+
const signalSnapshot = transcriptSignals?.snapshot;
|
|
85091
|
+
if (profile.class === "native-source" && signalSnapshot?.available === true) {
|
|
85092
|
+
if (signalSnapshot.signals.final_assistant_present !== true) return false;
|
|
85093
|
+
finalSummary = extractFinalSummaryFromMessagesAfter(
|
|
85094
|
+
Array.isArray(transcriptSignals?.messages) ? transcriptSignals.messages : [],
|
|
85095
|
+
turnStartedAt
|
|
85096
|
+
) || void 0;
|
|
85097
|
+
} else {
|
|
85098
|
+
try {
|
|
85099
|
+
finalSummary = this.completionFinalSummary(parsedMessages, turnStartedAt);
|
|
85100
|
+
} catch {
|
|
85101
|
+
finalSummary = void 0;
|
|
85102
|
+
}
|
|
84713
85103
|
}
|
|
84714
85104
|
if (!finalSummary) return false;
|
|
84715
85105
|
const diagnosticSource = profile.class === "pure-pty" ? "stall_pure_pty_transcript_completion" : "stall_native_source_transcript_completion";
|
|
@@ -84976,24 +85366,25 @@ ${body}
|
|
|
84976
85366
|
const isTranscriptEvidenceGate = block2.allowTimeout === true;
|
|
84977
85367
|
LOG2.debug("CLI", `[${this.type}] finalization block: reason=${blockReason} terminal=${block2.terminal} allowTimeout=${isTranscriptEvidenceGate} waitedMs=${waitedMs} maxWait=${COMPLETED_FINALIZATION_MAX_WAIT_MS}`);
|
|
84978
85368
|
if (blockReason === "missing_final_assistant" && block2.noExternalTranscriptSource === true) {
|
|
84979
|
-
let
|
|
85369
|
+
let growthSnapshot = null;
|
|
84980
85370
|
try {
|
|
84981
|
-
|
|
85371
|
+
growthSnapshot = this.probeNativeTranscriptSignals()?.snapshot ?? null;
|
|
84982
85372
|
} catch {
|
|
84983
|
-
|
|
85373
|
+
growthSnapshot = null;
|
|
84984
85374
|
}
|
|
84985
|
-
|
|
84986
|
-
|
|
85375
|
+
if (growthSnapshot?.available === true && growthSnapshot.signals.transcript_growing === true) {
|
|
85376
|
+
const growthDetail = growthSnapshot.detail;
|
|
85377
|
+
const mtimeAgeMs = growthDetail.ageMs ?? 0;
|
|
84987
85378
|
if (pending.loggedBlockReason !== "native_transcript_advancing") {
|
|
84988
|
-
LOG2.info("CLI", `[${this.type}] holding pending completed (native_transcript_advancing: msgCount=${
|
|
85379
|
+
LOG2.info("CLI", `[${this.type}] holding pending completed (native_transcript_advancing: msgCount=${growthDetail.msgCount} mtimeAge=${mtimeAgeMs}ms < ${MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS}ms) \u2014 transcript still growing, screen-idle verdict not trusted`);
|
|
84989
85380
|
if (this.isMeshWorkerSession()) {
|
|
84990
|
-
traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `native_transcript_advancing msgCount=${
|
|
85381
|
+
traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `native_transcript_advancing msgCount=${growthDetail.msgCount} mtimeAge=${mtimeAgeMs}ms`);
|
|
84991
85382
|
}
|
|
84992
85383
|
if (this.completionTraceOn()) this.recordCompletionGateTrace("hold", {
|
|
84993
85384
|
blockReason: "native_transcript_advancing",
|
|
84994
85385
|
latestVisibleStatus,
|
|
84995
|
-
msgCount:
|
|
84996
|
-
sourceMtimeAgeMs:
|
|
85386
|
+
msgCount: growthDetail.msgCount,
|
|
85387
|
+
sourceMtimeAgeMs: mtimeAgeMs,
|
|
84997
85388
|
growthQuietMs: MISSING_ASSISTANT_TRANSCRIPT_GROWTH_QUIET_MS,
|
|
84998
85389
|
waitedMs
|
|
84999
85390
|
});
|
|
@@ -95437,17 +95828,134 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
|
|
|
95437
95828
|
}
|
|
95438
95829
|
};
|
|
95439
95830
|
init_dist();
|
|
95831
|
+
init_logger();
|
|
95440
95832
|
var RESTART_BLOCKING_STATES = /* @__PURE__ */ new Set(["generating", "waiting_approval", "starting"]);
|
|
95441
|
-
|
|
95833
|
+
var DEFERRED_RESTART_DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
95834
|
+
var DEFERRED_RESTART_MAX_TIMEOUT_MS = 6 * 60 * 60 * 1e3;
|
|
95835
|
+
var DEFERRED_RESTART_POLL_MS = 1e4;
|
|
95836
|
+
function collectBlockingSessions(ctx, meshId) {
|
|
95837
|
+
const blocking = [];
|
|
95442
95838
|
const states = ctx.deps.instanceManager.collectAllStates();
|
|
95839
|
+
const consider = (state) => {
|
|
95840
|
+
const status = String(state?.status || "");
|
|
95841
|
+
if (!RESTART_BLOCKING_STATES.has(status)) return;
|
|
95842
|
+
blocking.push({
|
|
95843
|
+
instanceId: typeof state?.instanceId === "string" ? state.instanceId : null,
|
|
95844
|
+
status,
|
|
95845
|
+
// The coordinator marker is stamped by mesh_coordinator_launch and
|
|
95846
|
+
// re-stamped on restore (cli-manager.restoreHostedSessions). Ad-hoc
|
|
95847
|
+
// coordinator sessions have no marker and are NOT waived by selfOnly.
|
|
95848
|
+
selfCoordinator: !!meshId && state?.settings?.meshCoordinatorFor === meshId
|
|
95849
|
+
});
|
|
95850
|
+
};
|
|
95443
95851
|
for (const state of states) {
|
|
95444
|
-
|
|
95852
|
+
consider(state);
|
|
95445
95853
|
const childStates = "extensions" in state && Array.isArray(state.extensions) ? state.extensions : [];
|
|
95446
|
-
for (const child of childStates)
|
|
95447
|
-
|
|
95448
|
-
|
|
95854
|
+
for (const child of childStates) consider(child);
|
|
95855
|
+
}
|
|
95856
|
+
return blocking;
|
|
95857
|
+
}
|
|
95858
|
+
var pendingDeferredRestart = null;
|
|
95859
|
+
function deferredRestartInfo() {
|
|
95860
|
+
if (!pendingDeferredRestart) return null;
|
|
95861
|
+
return {
|
|
95862
|
+
meshId: pendingDeferredRestart.meshId,
|
|
95863
|
+
nodeId: pendingDeferredRestart.nodeId,
|
|
95864
|
+
mode: pendingDeferredRestart.mode,
|
|
95865
|
+
killSessionHost: pendingDeferredRestart.killSessionHost,
|
|
95866
|
+
scheduledAt: new Date(pendingDeferredRestart.scheduledAt).toISOString(),
|
|
95867
|
+
expiresAt: new Date(pendingDeferredRestart.expiresAt).toISOString(),
|
|
95868
|
+
runCondition: "executes automatically once no session is generating / waiting_approval / starting"
|
|
95869
|
+
};
|
|
95870
|
+
}
|
|
95871
|
+
function clearPendingDeferredRestart() {
|
|
95872
|
+
if (pendingDeferredRestart) clearInterval(pendingDeferredRestart.timer);
|
|
95873
|
+
pendingDeferredRestart = null;
|
|
95874
|
+
}
|
|
95875
|
+
function normalizeRestartMode(value) {
|
|
95876
|
+
return value === "restart" ? "restart" : "upgrade";
|
|
95877
|
+
}
|
|
95878
|
+
function restartWarnings(args) {
|
|
95879
|
+
const warnings = [];
|
|
95880
|
+
if (args.forced) {
|
|
95881
|
+
warnings.push("forced restart over active sessions \u2014 in-flight turns were interrupted and the in-memory pendingOutboundQueue is permanently lost (no persistence/restore path).");
|
|
95882
|
+
}
|
|
95883
|
+
if (args.killSessionHost) {
|
|
95884
|
+
warnings.push("killSessionHost: the session-host process was stopped \u2014 ALL hosted CLI sessions on this machine are destroyed (hard refresh).");
|
|
95885
|
+
}
|
|
95886
|
+
if (process.platform === "win32") {
|
|
95887
|
+
warnings.push("Windows: the daemon restart/upgrade path stops the session-host regardless of options \u2014 all hosted sessions terminate.");
|
|
95888
|
+
} else {
|
|
95889
|
+
warnings.push("POSIX: hosted PTY sessions survive a plain daemon restart and rebind on next boot (unless killSessionHost was set).");
|
|
95890
|
+
}
|
|
95891
|
+
return warnings;
|
|
95892
|
+
}
|
|
95893
|
+
async function executeRestart(ctx, args, opts) {
|
|
95894
|
+
const mode = normalizeRestartMode(args?.mode);
|
|
95895
|
+
const killSessionHost = args?.killSessionHost === true;
|
|
95896
|
+
const result = mode === "restart" ? await daemonLifecycleHandlers.daemon_restart({ deps: ctx.deps }, { killSessionHost }) : await daemonLifecycleHandlers.daemon_upgrade({ deps: ctx.deps }, args);
|
|
95897
|
+
const restarting = result?.restarting === true;
|
|
95898
|
+
return {
|
|
95899
|
+
...result,
|
|
95900
|
+
mode,
|
|
95901
|
+
restarted: restarting,
|
|
95902
|
+
...restarting ? {
|
|
95903
|
+
warnings: restartWarnings({ killSessionHost, forced: opts.forced }),
|
|
95904
|
+
// MCP IPC has no automatic reconnect/retry — tell the caller when to
|
|
95905
|
+
// talk to this daemon again.
|
|
95906
|
+
clientHint: "daemon restarting \u2014 retry your next call in ~10s"
|
|
95907
|
+
} : {}
|
|
95908
|
+
};
|
|
95909
|
+
}
|
|
95910
|
+
function scheduleDeferredRestart(ctx, args, meshId, nodeId) {
|
|
95911
|
+
clearPendingDeferredRestart();
|
|
95912
|
+
const requestedTimeout = typeof args?.timeoutMs === "number" && Number.isFinite(args.timeoutMs) ? args.timeoutMs : DEFERRED_RESTART_DEFAULT_TIMEOUT_MS;
|
|
95913
|
+
const timeoutMs = Math.min(Math.max(requestedTimeout, DEFERRED_RESTART_POLL_MS), DEFERRED_RESTART_MAX_TIMEOUT_MS);
|
|
95914
|
+
const record2 = {
|
|
95915
|
+
meshId,
|
|
95916
|
+
nodeId,
|
|
95917
|
+
mode: normalizeRestartMode(args?.mode),
|
|
95918
|
+
killSessionHost: args?.killSessionHost === true,
|
|
95919
|
+
scheduledAt: Date.now(),
|
|
95920
|
+
expiresAt: Date.now() + timeoutMs,
|
|
95921
|
+
timer: setInterval(() => {
|
|
95922
|
+
void deferredRestartTick(ctx);
|
|
95923
|
+
}, DEFERRED_RESTART_POLL_MS)
|
|
95924
|
+
};
|
|
95925
|
+
record2.timer.unref?.();
|
|
95926
|
+
pendingDeferredRestart = record2;
|
|
95927
|
+
LOG2.info("MeshRestart", `Deferred restart scheduled for node ${nodeId} (mode=${record2.mode}, expires in ${Math.round(timeoutMs / 6e4)}min)`);
|
|
95928
|
+
return {
|
|
95929
|
+
success: true,
|
|
95930
|
+
restarted: false,
|
|
95931
|
+
scheduled: true,
|
|
95932
|
+
code: "restart_scheduled_when_idle",
|
|
95933
|
+
reason: "Restart scheduled \u2014 it will execute automatically as soon as no session on this daemon is generating / waiting_approval / starting. Running at an idle point also avoids pendingOutboundQueue loss, making this the safest restart path.",
|
|
95934
|
+
deferredRestart: deferredRestartInfo()
|
|
95935
|
+
};
|
|
95936
|
+
}
|
|
95937
|
+
async function deferredRestartTick(ctx) {
|
|
95938
|
+
const record2 = pendingDeferredRestart;
|
|
95939
|
+
if (!record2) return;
|
|
95940
|
+
if (Date.now() >= record2.expiresAt) {
|
|
95941
|
+
LOG2.warn("MeshRestart", `Deferred restart for node ${record2.nodeId} expired without reaching an idle point; dropping the schedule`);
|
|
95942
|
+
clearPendingDeferredRestart();
|
|
95943
|
+
return;
|
|
95944
|
+
}
|
|
95945
|
+
const blocking = collectBlockingSessions(ctx, record2.meshId);
|
|
95946
|
+
if (blocking.length > 0) return;
|
|
95947
|
+
LOG2.info("MeshRestart", `Deferred restart for node ${record2.nodeId} executing \u2014 daemon is idle`);
|
|
95948
|
+
clearPendingDeferredRestart();
|
|
95949
|
+
try {
|
|
95950
|
+
await executeRestart(ctx, {
|
|
95951
|
+
meshId: record2.meshId,
|
|
95952
|
+
nodeId: record2.nodeId,
|
|
95953
|
+
mode: record2.mode,
|
|
95954
|
+
killSessionHost: record2.killSessionHost
|
|
95955
|
+
}, { forced: false });
|
|
95956
|
+
} catch (e) {
|
|
95957
|
+
LOG2.error("MeshRestart", `Deferred restart execution failed: ${e?.message || String(e)}`);
|
|
95449
95958
|
}
|
|
95450
|
-
return false;
|
|
95451
95959
|
}
|
|
95452
95960
|
var meshRestartHandlers = {
|
|
95453
95961
|
restart_daemon_node: async (ctx, args) => {
|
|
@@ -95468,16 +95976,43 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
|
|
|
95468
95976
|
});
|
|
95469
95977
|
return forwarded ?? { success: false, error: "no response from remote node" };
|
|
95470
95978
|
}
|
|
95471
|
-
if (
|
|
95979
|
+
if (args?.cancelWhenIdle === true) {
|
|
95980
|
+
const had = pendingDeferredRestart !== null;
|
|
95981
|
+
clearPendingDeferredRestart();
|
|
95982
|
+
return { success: true, restarted: false, cancelled: had, deferredRestart: null };
|
|
95983
|
+
}
|
|
95984
|
+
if (args?.whenIdleStatus === true) {
|
|
95985
|
+
return { success: true, restarted: false, deferredRestart: deferredRestartInfo() };
|
|
95986
|
+
}
|
|
95987
|
+
const blocking = collectBlockingSessions(ctx, meshId);
|
|
95988
|
+
if (blocking.length > 0) {
|
|
95989
|
+
if (args?.force === true) {
|
|
95990
|
+
LOG2.warn("MeshRestart", `force restart over ${blocking.length} blocking session(s): ${blocking.map((b) => `${b.instanceId || "?"}(${b.status})`).join(", ")} \u2014 pendingOutboundQueue will be lost`);
|
|
95991
|
+
return executeRestart(ctx, args, { forced: true });
|
|
95992
|
+
}
|
|
95993
|
+
const foreignBlocking = blocking.filter((b) => !b.selfCoordinator);
|
|
95994
|
+
if (args?.selfOnly === true && foreignBlocking.length === 0) {
|
|
95995
|
+
LOG2.info("MeshRestart", `selfOnly restart: waiving ${blocking.length} self-coordinator session(s) for mesh ${meshId}`);
|
|
95996
|
+
return executeRestart(ctx, args, { forced: false });
|
|
95997
|
+
}
|
|
95998
|
+
if (args?.whenIdle === true) {
|
|
95999
|
+
return scheduleDeferredRestart(ctx, args, meshId, nodeId);
|
|
96000
|
+
}
|
|
95472
96001
|
return {
|
|
95473
96002
|
success: false,
|
|
95474
96003
|
restarted: false,
|
|
95475
96004
|
code: "blocking_sessions",
|
|
95476
|
-
reason: "Daemon has an active session (generating / waiting_approval / starting); restart refused to avoid interrupting in-flight work. Retry when the node is idle."
|
|
96005
|
+
reason: "Daemon has an active session (generating / waiting_approval / starting); restart refused to avoid interrupting in-flight work. Retry when the node is idle.",
|
|
96006
|
+
blockingSessions: blocking,
|
|
96007
|
+
options: {
|
|
96008
|
+
selfOnly: "waive only this mesh's own coordinator session (settings.meshCoordinatorFor === meshId)",
|
|
96009
|
+
force: "bypass the idle-gate entirely \u2014 kills in-flight turns and loses the unpersisted pendingOutboundQueue",
|
|
96010
|
+
whenIdle: "schedule the restart to run automatically once the daemon goes idle (safest)"
|
|
96011
|
+
},
|
|
96012
|
+
deferredRestart: deferredRestartInfo()
|
|
95477
96013
|
};
|
|
95478
96014
|
}
|
|
95479
|
-
|
|
95480
|
-
return { ...result, restarted: result?.restarting === true };
|
|
96015
|
+
return executeRestart(ctx, args, { forced: false });
|
|
95481
96016
|
}
|
|
95482
96017
|
};
|
|
95483
96018
|
var medFamilyRegistry = new Map(
|