@adhdev/daemon-core 0.9.82-rc.536 → 0.9.82-rc.538
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/chat/source-resolver.d.ts +21 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +13 -0
- package/dist/index.js +344 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -17
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-completion-synthesis.d.ts +14 -0
- package/dist/mesh/mesh-work-queue.d.ts +22 -0
- package/package.json +3 -3
- package/src/chat/source-resolver.ts +0 -0
- package/src/cli-adapters/cli-state-engine.ts +51 -1
- package/src/cli-adapters/provider-cli-adapter.ts +7 -0
- package/src/commands/chat-commands-read.ts +103 -7
- package/src/mesh/mesh-completion-synthesis.ts +85 -0
- package/src/mesh/mesh-event-forwarding.ts +96 -1
- package/src/mesh/mesh-queue-assignment.ts +7 -3
- package/src/mesh/mesh-reconcile-loop.ts +53 -2
- package/src/mesh/mesh-work-queue.ts +74 -4
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +108 -1
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +33 -1
|
@@ -41,6 +41,27 @@ export declare class ChatSourceRegistry {
|
|
|
41
41
|
private readonly records;
|
|
42
42
|
/** Snapshot of current state for diagnostics. Does not mutate. */
|
|
43
43
|
getState(key: ChatSourceSessionKey): ChatSourceState;
|
|
44
|
+
/**
|
|
45
|
+
* Opaque snapshot of a session's FULL record (state + lock + transitions),
|
|
46
|
+
* for callers that need to speculatively `observe()` and then roll back if
|
|
47
|
+
* the resulting decision is undesirable (STICKY-NATIVE hold: a trusted
|
|
48
|
+
* exact-identity session must not flip to PTY on a transient native gap).
|
|
49
|
+
* Returns undefined when the session has no record yet. The snapshot is a
|
|
50
|
+
* shallow copy — transitions array is copied so a later append does not
|
|
51
|
+
* mutate it.
|
|
52
|
+
*/
|
|
53
|
+
snapshotRecord(key: ChatSourceSessionKey): {
|
|
54
|
+
state: ChatSourceState;
|
|
55
|
+
lockedSince: number | undefined;
|
|
56
|
+
transitions: ChatSourceTransition[];
|
|
57
|
+
} | undefined;
|
|
58
|
+
/** Restore a previously snapshotted record, undoing a speculative observe.
|
|
59
|
+
* Passing undefined clears the key (it had no record when snapshotted). */
|
|
60
|
+
restoreRecord(key: ChatSourceSessionKey, snapshot: {
|
|
61
|
+
state: ChatSourceState;
|
|
62
|
+
lockedSince: number | undefined;
|
|
63
|
+
transitions: ChatSourceTransition[];
|
|
64
|
+
} | undefined): void;
|
|
44
65
|
/** Recent transitions, newest last. Empty array when nothing has happened. */
|
|
45
66
|
getTransitions(key: ChatSourceSessionKey): ReadonlyArray<ChatSourceTransition>;
|
|
46
67
|
/** Drop a session. Caller should invoke this when the session is destroyed
|
|
@@ -105,6 +105,7 @@ export declare class CliStateEngine {
|
|
|
105
105
|
* paint and made the engine type "1" repeatedly into the prompt.
|
|
106
106
|
*/
|
|
107
107
|
modalLostAt: number;
|
|
108
|
+
private modalLostRecheckTimer;
|
|
108
109
|
private approvalExitTimeout;
|
|
109
110
|
responseEpoch: number;
|
|
110
111
|
submitPendingUntil: number;
|
|
@@ -230,6 +231,18 @@ export declare class CliStateEngine {
|
|
|
230
231
|
finishResponse(): void;
|
|
231
232
|
private scheduleIdleFinish;
|
|
232
233
|
private cancelPendingIdleFinish;
|
|
234
|
+
/**
|
|
235
|
+
* Schedule one more settled evaluation while pinned to `waiting_approval`
|
|
236
|
+
* with no actionable modal. The settled FSM normally only re-runs on new
|
|
237
|
+
* PTY output; a provider whose modal cue lingers in a form detectStatus
|
|
238
|
+
* still matches (e.g. kimi's questionPattern hitting the user echo) but
|
|
239
|
+
* whose PTY has gone quiet would never get another evaluation, latching
|
|
240
|
+
* `waiting_approval` forever. This timer guarantees the modal-lost recovery
|
|
241
|
+
* in `applyWaitingApproval` is reached even against a silent PTY. It is a
|
|
242
|
+
* no-op once the FSM leaves `waiting_approval` (the re-evaluation itself
|
|
243
|
+
* takes the recovery branch and clears the state).
|
|
244
|
+
*/
|
|
245
|
+
private armModalLostRecheck;
|
|
233
246
|
private armApprovalExitTimeout;
|
|
234
247
|
private armIdleFinishCandidate;
|
|
235
248
|
private shouldDeferIdleTimeoutFinish;
|
package/dist/index.js
CHANGED
|
@@ -419,10 +419,10 @@ function readInjected(value) {
|
|
|
419
419
|
}
|
|
420
420
|
function getDaemonBuildInfo() {
|
|
421
421
|
if (cached) return cached;
|
|
422
|
-
const commit = readInjected(true ? "
|
|
423
|
-
const commitShort = readInjected(true ? "
|
|
424
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
425
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
422
|
+
const commit = readInjected(true ? "35ac849eca76162066561b27633e11827fa19a73" : void 0) ?? "unknown";
|
|
423
|
+
const commitShort = readInjected(true ? "35ac849e" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
424
|
+
const version = readInjected(true ? "0.9.82-rc.538" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
425
|
+
const builtAt = readInjected(true ? "2026-07-15T15:51:55.785Z" : void 0);
|
|
426
426
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
427
427
|
return cached;
|
|
428
428
|
}
|
|
@@ -5712,6 +5712,8 @@ __export(mesh_work_queue_exports, {
|
|
|
5712
5712
|
MESH_TASK_MODES: () => MESH_TASK_MODES,
|
|
5713
5713
|
MESH_TASK_PRIORITIES: () => MESH_TASK_PRIORITIES,
|
|
5714
5714
|
NOT_BEFORE_RELATIVE_THRESHOLD_MS: () => NOT_BEFORE_RELATIVE_THRESHOLD_MS,
|
|
5715
|
+
REDRIVE_RECLAIM_REASONS: () => REDRIVE_RECLAIM_REASONS,
|
|
5716
|
+
REDRIVE_SUPERSEDE_WINDOW_MS: () => REDRIVE_SUPERSEDE_WINDOW_MS,
|
|
5715
5717
|
__clearDirectDispatchesForTests: () => __clearDirectDispatchesForTests,
|
|
5716
5718
|
__clearMeshQueueForTests: () => __clearMeshQueueForTests,
|
|
5717
5719
|
__replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
|
|
@@ -6007,10 +6009,21 @@ function normalizeMeshCapabilityTags(value) {
|
|
|
6007
6009
|
return true;
|
|
6008
6010
|
});
|
|
6009
6011
|
}
|
|
6010
|
-
function
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6013
|
-
|
|
6012
|
+
function readNodeProviderTypes(policy) {
|
|
6013
|
+
const record = policy && typeof policy === "object" && !Array.isArray(policy) ? policy : {};
|
|
6014
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6015
|
+
const out = [];
|
|
6016
|
+
const push = (type) => {
|
|
6017
|
+
const trimmed = typeof type === "string" ? type.trim() : "";
|
|
6018
|
+
if (!trimmed || seen.has(trimmed)) return;
|
|
6019
|
+
seen.add(trimmed);
|
|
6020
|
+
out.push(trimmed);
|
|
6021
|
+
};
|
|
6022
|
+
for (const slot of normalizeNodeCapabilitySlots(record.slots)) push(slot.provider);
|
|
6023
|
+
if (Array.isArray(record.providerPriority)) {
|
|
6024
|
+
for (const type of record.providerPriority) push(type);
|
|
6025
|
+
}
|
|
6026
|
+
return out;
|
|
6014
6027
|
}
|
|
6015
6028
|
function readNodeOverride(node, key2) {
|
|
6016
6029
|
const overrides = node?.userOverrides;
|
|
@@ -6023,7 +6036,8 @@ function readNodeReporter(node, key2) {
|
|
|
6023
6036
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
6024
6037
|
}
|
|
6025
6038
|
function buildMeshNodeCapabilityTags(node, providerType) {
|
|
6026
|
-
const
|
|
6039
|
+
const pinnedProvider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : void 0;
|
|
6040
|
+
const providerTags = pinnedProvider ? [pinnedProvider] : readNodeProviderTypes(node?.policy);
|
|
6027
6041
|
const worktreeBranch = typeof node?.worktreeBranch === "string" && node.worktreeBranch.trim() ? node.worktreeBranch.trim() : null;
|
|
6028
6042
|
const os32 = readNodeOverride(node, "platform") ?? readNodeReporter(node, "platform") ?? process.platform;
|
|
6029
6043
|
const arch2 = readNodeOverride(node, "arch") ?? readNodeReporter(node, "arch") ?? process.arch;
|
|
@@ -6031,7 +6045,7 @@ function buildMeshNodeCapabilityTags(node, providerType) {
|
|
|
6031
6045
|
...Array.isArray(node?.capabilities) ? node.capabilities : [],
|
|
6032
6046
|
`os=${os32}`,
|
|
6033
6047
|
`arch=${arch2}`,
|
|
6034
|
-
...
|
|
6048
|
+
...providerTags.map((p) => `provider=${p}`),
|
|
6035
6049
|
// Worktree nodes automatically expose a "worktree=<branch>" tag so that
|
|
6036
6050
|
// mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
|
|
6037
6051
|
// only to the matching worktree node.
|
|
@@ -6534,7 +6548,7 @@ function recordMeshToolCall(opts) {
|
|
|
6534
6548
|
return { rateLimitExceeded: false, callsInWindow: 0, advisory: null };
|
|
6535
6549
|
}
|
|
6536
6550
|
}
|
|
6537
|
-
var import_crypto6, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, MAX_STRANDED_RECLAIMS;
|
|
6551
|
+
var import_crypto6, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, MESH_TASK_MODES, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS, LIVE_DEBUG_READONLY_FORBIDDEN, NEGATION_CUES, NEGATION_WINDOW_TOKENS, GIT_MUTATION_SUBCOMMANDS, GIT_STASH_READONLY_SUBCOMMANDS, DEPENDENCY_FAILURE_TERMINALS, MAX_STRANDED_RECLAIMS, REDRIVE_RECLAIM_REASONS, REDRIVE_SUPERSEDE_WINDOW_MS;
|
|
6538
6552
|
var init_mesh_work_queue = __esm({
|
|
6539
6553
|
"src/mesh/mesh-work-queue.ts"() {
|
|
6540
6554
|
"use strict";
|
|
@@ -6603,6 +6617,12 @@ var init_mesh_work_queue = __esm({
|
|
|
6603
6617
|
GIT_STASH_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "show"]);
|
|
6604
6618
|
DEPENDENCY_FAILURE_TERMINALS = /* @__PURE__ */ new Set(["failed", "cancelled"]);
|
|
6605
6619
|
MAX_STRANDED_RECLAIMS = 3;
|
|
6620
|
+
REDRIVE_RECLAIM_REASONS = /* @__PURE__ */ new Set([
|
|
6621
|
+
"delivered_no_turn_deadline",
|
|
6622
|
+
"reclaim_after_unknown_grace",
|
|
6623
|
+
"delivered_not_consumed_redrive"
|
|
6624
|
+
]);
|
|
6625
|
+
REDRIVE_SUPERSEDE_WINDOW_MS = 5 * 6e4;
|
|
6606
6626
|
}
|
|
6607
6627
|
});
|
|
6608
6628
|
|
|
@@ -16841,7 +16861,8 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
16841
16861
|
if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
|
|
16842
16862
|
if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
|
|
16843
16863
|
if (task.requiredTags?.length) {
|
|
16844
|
-
const
|
|
16864
|
+
const slotProviders = resolveNodeCapabilitySlots(node).map((s2) => s2.provider).filter(Boolean);
|
|
16865
|
+
const priorities = slotProviders.length ? slotProviders : normalizeProviderPriority2(node?.policy);
|
|
16845
16866
|
const providerCandidates = priorities.length ? priorities : [void 0];
|
|
16846
16867
|
return providerCandidates.some(
|
|
16847
16868
|
(p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
|
|
@@ -20430,6 +20451,52 @@ function stopStaleMeshWorker(components, args) {
|
|
|
20430
20451
|
LOG.warn("MeshQueue", `stopStaleMeshWorker error for ${sessionId}: ${e?.message || e}`);
|
|
20431
20452
|
}
|
|
20432
20453
|
}
|
|
20454
|
+
function supersedeRedriveReclaimForLateCompletion(components, meshId, row, completingSessionId, outcome, args) {
|
|
20455
|
+
if (!row.requeueReason || !REDRIVE_RECLAIM_REASONS.has(row.requeueReason)) return false;
|
|
20456
|
+
if (row.status === "completed" || row.status === "failed" || row.status === "cancelled") return false;
|
|
20457
|
+
const requeuedAtMs = Date.parse(row.requeuedAt ?? "");
|
|
20458
|
+
if (!Number.isFinite(requeuedAtMs)) return false;
|
|
20459
|
+
if (Date.now() - requeuedAtMs > REDRIVE_SUPERSEDE_WINDOW_MS) return false;
|
|
20460
|
+
const reDispatchedSessionId = row.assignedSessionId;
|
|
20461
|
+
if (row.status === "assigned" && reDispatchedSessionId && !sessionIdsEquivalent(reDispatchedSessionId, completingSessionId)) {
|
|
20462
|
+
stopStaleMeshWorker(components, {
|
|
20463
|
+
meshId,
|
|
20464
|
+
sessionId: reDispatchedSessionId,
|
|
20465
|
+
nodeId: row.assignedNodeId,
|
|
20466
|
+
providerType: row.assignedProviderType
|
|
20467
|
+
});
|
|
20468
|
+
}
|
|
20469
|
+
endTaskDispatchInFlight(meshId, row.id);
|
|
20470
|
+
updateTaskStatus(meshId, row.id, outcome === "completed" ? "completed" : "failed");
|
|
20471
|
+
if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
|
|
20472
|
+
try {
|
|
20473
|
+
appendLedgerEntry(meshId, {
|
|
20474
|
+
kind: outcome === "completed" ? "task_completed" : "task_failed",
|
|
20475
|
+
sessionId: completingSessionId,
|
|
20476
|
+
nodeId: readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId) || void 0,
|
|
20477
|
+
providerType: readNonEmptyString2(args.metadataEvent.providerType) || void 0,
|
|
20478
|
+
payload: {
|
|
20479
|
+
taskId: row.id,
|
|
20480
|
+
event: args.event,
|
|
20481
|
+
source: "redrive_late_completion_supersede",
|
|
20482
|
+
reclaimReason: row.requeueReason,
|
|
20483
|
+
reclaimAgeMs: Date.now() - requeuedAtMs,
|
|
20484
|
+
finalSummary: readNonEmptyString2(args.metadataEvent.finalSummary) || void 0
|
|
20485
|
+
}
|
|
20486
|
+
});
|
|
20487
|
+
} catch {
|
|
20488
|
+
}
|
|
20489
|
+
}
|
|
20490
|
+
LOG.warn("MeshQueue", `Late completion superseded re-drive for task ${row.id} on mesh ${meshId} (reclaimed '${row.requeueReason}' ${Math.round((Date.now() - requeuedAtMs) / 1e3)}s ago; completing session ${completingSessionId}) \u2192 flipped ${outcome}${row.status === "assigned" && reDispatchedSessionId && !sessionIdsEquivalent(reDispatchedSessionId, completingSessionId) ? `, stopped duplicate re-dispatch on ${reDispatchedSessionId}` : ""}`);
|
|
20491
|
+
traceMeshEventDrop("redrive_late_completion_supersede", {
|
|
20492
|
+
taskId: row.id,
|
|
20493
|
+
sessionId: completingSessionId,
|
|
20494
|
+
nodeId: row.assignedNodeId ?? args.nodeId,
|
|
20495
|
+
meshId,
|
|
20496
|
+
event: args.event
|
|
20497
|
+
}, `${row.requeueReason} ${Math.round((Date.now() - requeuedAtMs) / 1e3)}s \u2192 ${outcome}`);
|
|
20498
|
+
return true;
|
|
20499
|
+
}
|
|
20433
20500
|
function injectMeshSystemMessage(components, args) {
|
|
20434
20501
|
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
20435
20502
|
const eventNodeId = readNonEmptyString2(args.nodeId) || readNonEmptyString2(args.metadataEvent.meshNodeId);
|
|
@@ -20541,6 +20608,7 @@ function injectMeshSystemMessage(components, args) {
|
|
|
20541
20608
|
}
|
|
20542
20609
|
});
|
|
20543
20610
|
}
|
|
20611
|
+
} else if (strandedRow && supersedeRedriveReclaimForLateCompletion(components, args.meshId, strandedRow, sessionId, outcome, args)) {
|
|
20544
20612
|
}
|
|
20545
20613
|
} catch {
|
|
20546
20614
|
}
|
|
@@ -21867,6 +21935,50 @@ async function autoPruneStaleDirectDispatches(components, mesh, selfIds, localDa
|
|
|
21867
21935
|
LOG.info("MeshReconcile", `Auto-pruned ${result.prunedCount} orphaned direct dispatch record(s) for mesh ${mesh.id}`);
|
|
21868
21936
|
}
|
|
21869
21937
|
}
|
|
21938
|
+
async function pollAssignedTaskTerminalEvidence(components, mesh, row) {
|
|
21939
|
+
const sessionId = readNonEmptyString2(row.assignedSessionId);
|
|
21940
|
+
const nodeId = readNonEmptyString2(row.assignedNodeId);
|
|
21941
|
+
if (!sessionId || !nodeId) return null;
|
|
21942
|
+
const node = (mesh.nodes ?? []).find((n) => n.id === nodeId);
|
|
21943
|
+
const nodeDaemonId = readNonEmptyString2(node?.daemonId);
|
|
21944
|
+
const localDaemonId = readNonEmptyString2(components.statusInstanceId);
|
|
21945
|
+
const isLocalNode = !nodeDaemonId || daemonIdsEquivalent(nodeDaemonId, localDaemonId) || !!components.instanceManager.getInstance(sessionId);
|
|
21946
|
+
const providerType = readNonEmptyString2(row.assignedProviderType);
|
|
21947
|
+
const readArgs = {
|
|
21948
|
+
sessionId,
|
|
21949
|
+
targetSessionId: sessionId,
|
|
21950
|
+
tailLimit: 10,
|
|
21951
|
+
...node?.workspace ? { workspace: node.workspace } : {},
|
|
21952
|
+
...providerType ? { agentType: providerType, providerType } : {}
|
|
21953
|
+
};
|
|
21954
|
+
let payload = null;
|
|
21955
|
+
try {
|
|
21956
|
+
if (isLocalNode) {
|
|
21957
|
+
const result = await components.commandHandler?.handle("read_chat", readArgs);
|
|
21958
|
+
if (result && result.success === false) return null;
|
|
21959
|
+
payload = unwrapReadChatPayload(result);
|
|
21960
|
+
} else if (components.dispatchMeshCommand) {
|
|
21961
|
+
const result = await components.dispatchMeshCommand(nodeDaemonId, "read_chat", readArgs);
|
|
21962
|
+
payload = unwrapReadChatPayload(result);
|
|
21963
|
+
if (payload && payload.success === false) return null;
|
|
21964
|
+
} else {
|
|
21965
|
+
return null;
|
|
21966
|
+
}
|
|
21967
|
+
} catch {
|
|
21968
|
+
return null;
|
|
21969
|
+
}
|
|
21970
|
+
if (!payload) return null;
|
|
21971
|
+
if (readChatPayloadStatus(payload) !== "idle") return null;
|
|
21972
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
21973
|
+
const evidence = extractFinalAssistantSummaryEvidence(messages);
|
|
21974
|
+
if (!evidence.finalSummary) return null;
|
|
21975
|
+
const dispatchedAtMs = Date.parse(readNonEmptyString2(row.dispatchTimestamp));
|
|
21976
|
+
const transcriptAtMs = Date.parse(evidence.transcriptMessageAt ?? "");
|
|
21977
|
+
if (!(Number.isFinite(dispatchedAtMs) && Number.isFinite(transcriptAtMs) && transcriptAtMs >= dispatchedAtMs)) {
|
|
21978
|
+
return null;
|
|
21979
|
+
}
|
|
21980
|
+
return "completed";
|
|
21981
|
+
}
|
|
21870
21982
|
var init_mesh_completion_synthesis = __esm({
|
|
21871
21983
|
"src/mesh/mesh-completion-synthesis.ts"() {
|
|
21872
21984
|
"use strict";
|
|
@@ -22143,7 +22255,8 @@ function drainAndDeliverApprovalNudges(meshId, drainDaemonIds, localDaemonId, me
|
|
|
22143
22255
|
}
|
|
22144
22256
|
return delivered;
|
|
22145
22257
|
}
|
|
22146
|
-
function recoverStrandedAssignedDispatches(components,
|
|
22258
|
+
async function recoverStrandedAssignedDispatches(components, mesh, store) {
|
|
22259
|
+
const meshId = mesh.id;
|
|
22147
22260
|
const assigned = getQueue(meshId, { status: ["assigned"] });
|
|
22148
22261
|
if (!assigned.length) return;
|
|
22149
22262
|
const nowMs = Date.now();
|
|
@@ -22250,6 +22363,36 @@ function recoverStrandedAssignedDispatches(components, meshId, store) {
|
|
|
22250
22363
|
}
|
|
22251
22364
|
reclaimReason = "reclaim_after_unknown_grace";
|
|
22252
22365
|
}
|
|
22366
|
+
const terminalEvidence = await pollAssignedTaskTerminalEvidence(components, mesh, row);
|
|
22367
|
+
if (terminalEvidence) {
|
|
22368
|
+
deliveredNoTurnUnknownStreak.delete(streakKey);
|
|
22369
|
+
updateTaskStatus(meshId, row.id, terminalEvidence);
|
|
22370
|
+
if (!findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })) {
|
|
22371
|
+
try {
|
|
22372
|
+
appendLedgerEntry(meshId, {
|
|
22373
|
+
kind: terminalEvidence === "completed" ? "task_completed" : "task_failed",
|
|
22374
|
+
nodeId: row.assignedNodeId,
|
|
22375
|
+
sessionId: row.assignedSessionId,
|
|
22376
|
+
providerType: row.assignedProviderType,
|
|
22377
|
+
payload: {
|
|
22378
|
+
taskId: row.id,
|
|
22379
|
+
event: "agent:generating_completed",
|
|
22380
|
+
source: "redrive_deadline_transcript_evidence"
|
|
22381
|
+
}
|
|
22382
|
+
});
|
|
22383
|
+
} catch {
|
|
22384
|
+
}
|
|
22385
|
+
}
|
|
22386
|
+
LOG.warn("MeshReconcile", `Skipped delivered-no-turn re-drive for task ${row.id} on mesh ${meshId} (node=${row.assignedNodeId ?? "?"} session=${row.assignedSessionId ?? "?"}): worker transcript is idle with a final assistant message after dispatch \u2014 the completion event was lost/late, task is ${terminalEvidence}, NOT re-driving`);
|
|
22387
|
+
traceMeshEventDrop("redrive_deadline_transcript_completed", {
|
|
22388
|
+
taskId: row.id,
|
|
22389
|
+
sessionId: row.assignedSessionId,
|
|
22390
|
+
nodeId: row.assignedNodeId,
|
|
22391
|
+
meshId,
|
|
22392
|
+
event: "agent:generating_completed"
|
|
22393
|
+
}, `${reclaimReason} \u2192 transcript ${terminalEvidence}`);
|
|
22394
|
+
continue;
|
|
22395
|
+
}
|
|
22253
22396
|
const reclaimedLost = reclaimStrandedAssignedTask(meshId, row.id, {
|
|
22254
22397
|
reason: reclaimReason,
|
|
22255
22398
|
ageMs: nowMs - dispatchedAtMs
|
|
@@ -22376,7 +22519,7 @@ async function runMeshReconcileTick(components) {
|
|
|
22376
22519
|
const selfIds = resolveCoordinatorSelfIds(mesh, drainDaemonIds);
|
|
22377
22520
|
if (!daemonHostsMesh(mesh, selfIds)) continue;
|
|
22378
22521
|
try {
|
|
22379
|
-
recoverStrandedAssignedDispatches(components, mesh
|
|
22522
|
+
await recoverStrandedAssignedDispatches(components, mesh, store);
|
|
22380
22523
|
} catch (e) {
|
|
22381
22524
|
LOG.warn("MeshReconcile", `Assigned-stranded watchdog failed for mesh ${mesh.id}: ${e?.message || e}`);
|
|
22382
22525
|
}
|
|
@@ -24551,6 +24694,70 @@ function modalMatches(spec, input) {
|
|
|
24551
24694
|
if (buttonBlockApprovalCue(spec, text)) return true;
|
|
24552
24695
|
return false;
|
|
24553
24696
|
}
|
|
24697
|
+
function lastModalCueLine(spec, screenText) {
|
|
24698
|
+
if (!screenText) return -1;
|
|
24699
|
+
const lines = screenText.split("\n");
|
|
24700
|
+
const question = compile2(spec.questionPattern, spec.questionFlags ?? "i");
|
|
24701
|
+
const variants = (spec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
|
|
24702
|
+
const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
|
|
24703
|
+
const buttonRe = compile2(spec.buttonPattern, buttonFlags);
|
|
24704
|
+
let last = -1;
|
|
24705
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24706
|
+
const line = lines[i];
|
|
24707
|
+
question.lastIndex = 0;
|
|
24708
|
+
if (question.test(line)) {
|
|
24709
|
+
last = i;
|
|
24710
|
+
continue;
|
|
24711
|
+
}
|
|
24712
|
+
if (variants.some((re) => {
|
|
24713
|
+
re.lastIndex = 0;
|
|
24714
|
+
return re.test(line);
|
|
24715
|
+
})) {
|
|
24716
|
+
last = i;
|
|
24717
|
+
continue;
|
|
24718
|
+
}
|
|
24719
|
+
buttonRe.lastIndex = 0;
|
|
24720
|
+
if (buttonRe.test(line)) {
|
|
24721
|
+
last = i;
|
|
24722
|
+
continue;
|
|
24723
|
+
}
|
|
24724
|
+
}
|
|
24725
|
+
return last;
|
|
24726
|
+
}
|
|
24727
|
+
function modalSupersededBySettledPrompt(modalSpec, settledSpec, settled, input) {
|
|
24728
|
+
if (!settled || !settledSpec) return false;
|
|
24729
|
+
if (settledSpec.scope === "whole-screen") return false;
|
|
24730
|
+
const screenText = input.screenText ?? "";
|
|
24731
|
+
if (!screenText) return false;
|
|
24732
|
+
const modalLine = lastModalCueLine(modalSpec, screenText);
|
|
24733
|
+
if (modalLine < 0) return false;
|
|
24734
|
+
const lines = screenText.split("\n");
|
|
24735
|
+
const below = lines.slice(modalLine + 1);
|
|
24736
|
+
if (below.length === 0) return false;
|
|
24737
|
+
const belowText = below.join("\n");
|
|
24738
|
+
if (!settled.prompt.test(belowText)) return false;
|
|
24739
|
+
if (settled.footers.length > 0 && !settled.footers.every((f) => f.test(belowText))) return false;
|
|
24740
|
+
const question = compile2(modalSpec.questionPattern, modalSpec.questionFlags ?? "i");
|
|
24741
|
+
const variants = (modalSpec.questionVariants ?? []).map((v) => compile2(v.regex, v.flags ?? "i"));
|
|
24742
|
+
const buttonFlags = modalSpec.buttonFlags && modalSpec.buttonFlags.includes("m") ? modalSpec.buttonFlags : `${modalSpec.buttonFlags ?? ""}m`;
|
|
24743
|
+
const buttonRe = compile2(modalSpec.buttonPattern, buttonFlags);
|
|
24744
|
+
const isModalCueLine = (line) => {
|
|
24745
|
+
question.lastIndex = 0;
|
|
24746
|
+
if (question.test(line)) return true;
|
|
24747
|
+
if (variants.some((re) => {
|
|
24748
|
+
re.lastIndex = 0;
|
|
24749
|
+
return re.test(line);
|
|
24750
|
+
})) return true;
|
|
24751
|
+
buttonRe.lastIndex = 0;
|
|
24752
|
+
return buttonRe.test(line);
|
|
24753
|
+
};
|
|
24754
|
+
const settledPromptLineRe = compile2(settledSpec.regex, (settledSpec.flags ?? "m").includes("m") ? settledSpec.flags ?? "m" : `${settledSpec.flags ?? ""}m`);
|
|
24755
|
+
const isSettledLine = (line) => {
|
|
24756
|
+
settledPromptLineRe.lastIndex = 0;
|
|
24757
|
+
return settledPromptLineRe.test(line);
|
|
24758
|
+
};
|
|
24759
|
+
return below.some((line) => line.trim() !== "" && !isModalCueLine(line) && !isSettledLine(line));
|
|
24760
|
+
}
|
|
24554
24761
|
function evaluateGroup(group, spec, input, compiled) {
|
|
24555
24762
|
switch (group) {
|
|
24556
24763
|
case "spinner": {
|
|
@@ -24560,7 +24767,9 @@ function evaluateGroup(group, spec, input, compiled) {
|
|
|
24560
24767
|
}
|
|
24561
24768
|
case "modal": {
|
|
24562
24769
|
if (!spec.modal) return null;
|
|
24563
|
-
|
|
24770
|
+
if (!modalMatches(spec.modal, input)) return null;
|
|
24771
|
+
if (modalSupersededBySettledPrompt(spec.modal, spec.settledPrompt, compiled.settled, input)) return null;
|
|
24772
|
+
return "waiting_approval";
|
|
24564
24773
|
}
|
|
24565
24774
|
case "settled-prompt": {
|
|
24566
24775
|
if (!spec.settledPrompt || !compiled.settled) return null;
|
|
@@ -24618,6 +24827,21 @@ function compile3(re, flags) {
|
|
|
24618
24827
|
}
|
|
24619
24828
|
function findQuestionLineIndex(spec, lines) {
|
|
24620
24829
|
const primary = compile3(spec.questionPattern, spec.questionFlags ?? "i");
|
|
24830
|
+
const buttonFlags = spec.buttonFlags && spec.buttonFlags.includes("m") ? spec.buttonFlags : `${spec.buttonFlags ?? ""}m`;
|
|
24831
|
+
const buttonRe = compile3(spec.buttonPattern, buttonFlags);
|
|
24832
|
+
const isButtonLine = (line) => {
|
|
24833
|
+
buttonRe.lastIndex = 0;
|
|
24834
|
+
return buttonRe.test(line);
|
|
24835
|
+
};
|
|
24836
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
24837
|
+
if (primary.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: "primary" };
|
|
24838
|
+
}
|
|
24839
|
+
for (const variant of spec.questionVariants ?? []) {
|
|
24840
|
+
const re = compile3(variant.regex, variant.flags ?? "i");
|
|
24841
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
24842
|
+
if (re.test(lines[i]) && !isButtonLine(lines[i])) return { index: i, matchedSource: variant.label ?? "variant" };
|
|
24843
|
+
}
|
|
24844
|
+
}
|
|
24621
24845
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
24622
24846
|
if (primary.test(lines[i])) return { index: i, matchedSource: "primary" };
|
|
24623
24847
|
}
|
|
@@ -25347,6 +25571,7 @@ var init_cli_state_engine = __esm({
|
|
|
25347
25571
|
* paint and made the engine type "1" repeatedly into the prompt.
|
|
25348
25572
|
*/
|
|
25349
25573
|
modalLostAt = 0;
|
|
25574
|
+
modalLostRecheckTimer = null;
|
|
25350
25575
|
approvalExitTimeout = null;
|
|
25351
25576
|
// ── Response tracking ────────────────────────────
|
|
25352
25577
|
responseEpoch = 0;
|
|
@@ -25398,6 +25623,10 @@ var init_cli_state_engine = __esm({
|
|
|
25398
25623
|
setStatus(status, trigger) {
|
|
25399
25624
|
const prev = this.currentStatus;
|
|
25400
25625
|
if (prev === status) return;
|
|
25626
|
+
if (prev === "waiting_approval" && this.modalLostRecheckTimer) {
|
|
25627
|
+
clearTimeout(this.modalLostRecheckTimer);
|
|
25628
|
+
this.modalLostRecheckTimer = null;
|
|
25629
|
+
}
|
|
25401
25630
|
this.currentStatus = status;
|
|
25402
25631
|
this.statusHistory.push({ status, at: Date.now(), trigger });
|
|
25403
25632
|
if (this.statusHistory.length > 50) this.statusHistory.shift();
|
|
@@ -25558,6 +25787,10 @@ var init_cli_state_engine = __esm({
|
|
|
25558
25787
|
clearTimeout(this.approvalExitTimeout);
|
|
25559
25788
|
this.approvalExitTimeout = null;
|
|
25560
25789
|
}
|
|
25790
|
+
if (this.modalLostRecheckTimer) {
|
|
25791
|
+
clearTimeout(this.modalLostRecheckTimer);
|
|
25792
|
+
this.modalLostRecheckTimer = null;
|
|
25793
|
+
}
|
|
25561
25794
|
if (this.finishRetryTimer) {
|
|
25562
25795
|
clearTimeout(this.finishRetryTimer);
|
|
25563
25796
|
this.finishRetryTimer = null;
|
|
@@ -25869,7 +26102,7 @@ var init_cli_state_engine = __esm({
|
|
|
25869
26102
|
if (!inCooldown || modal) {
|
|
25870
26103
|
if (!modal) {
|
|
25871
26104
|
LOG.warn("CLI", `[${this.provider.type}] detectStatus=waiting_approval but parseApproval returned null; ignoring`);
|
|
25872
|
-
if (this.currentStatus === "waiting_approval"
|
|
26105
|
+
if (this.currentStatus === "waiting_approval") {
|
|
25873
26106
|
const lostAt = this.modalLostAt || Date.now();
|
|
25874
26107
|
if (!this.modalLostAt) this.modalLostAt = lostAt;
|
|
25875
26108
|
if (Date.now() - lostAt >= this.timeouts.approvalCooldown) {
|
|
@@ -25877,6 +26110,8 @@ var init_cli_state_engine = __esm({
|
|
|
25877
26110
|
this.modalLostAt = 0;
|
|
25878
26111
|
this.setStatus("generating", "approval_lost_modal");
|
|
25879
26112
|
this.callbacks.onStatusChange();
|
|
26113
|
+
} else {
|
|
26114
|
+
this.armModalLostRecheck();
|
|
25880
26115
|
}
|
|
25881
26116
|
}
|
|
25882
26117
|
return;
|
|
@@ -26138,6 +26373,25 @@ var init_cli_state_engine = __esm({
|
|
|
26138
26373
|
this.recordTrace("idle_finish_cancelled", { trigger: reason });
|
|
26139
26374
|
}
|
|
26140
26375
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
26376
|
+
/**
|
|
26377
|
+
* Schedule one more settled evaluation while pinned to `waiting_approval`
|
|
26378
|
+
* with no actionable modal. The settled FSM normally only re-runs on new
|
|
26379
|
+
* PTY output; a provider whose modal cue lingers in a form detectStatus
|
|
26380
|
+
* still matches (e.g. kimi's questionPattern hitting the user echo) but
|
|
26381
|
+
* whose PTY has gone quiet would never get another evaluation, latching
|
|
26382
|
+
* `waiting_approval` forever. This timer guarantees the modal-lost recovery
|
|
26383
|
+
* in `applyWaitingApproval` is reached even against a silent PTY. It is a
|
|
26384
|
+
* no-op once the FSM leaves `waiting_approval` (the re-evaluation itself
|
|
26385
|
+
* takes the recovery branch and clears the state).
|
|
26386
|
+
*/
|
|
26387
|
+
armModalLostRecheck() {
|
|
26388
|
+
if (this.modalLostRecheckTimer) return;
|
|
26389
|
+
this.modalLostRecheckTimer = setTimeout(() => {
|
|
26390
|
+
this.modalLostRecheckTimer = null;
|
|
26391
|
+
if (this.currentStatus !== "waiting_approval") return;
|
|
26392
|
+
this.evaluateSettled(this.transport.getSnapshot());
|
|
26393
|
+
}, this.timeouts.approvalCooldown);
|
|
26394
|
+
}
|
|
26141
26395
|
armApprovalExitTimeout() {
|
|
26142
26396
|
if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
|
|
26143
26397
|
this.approvalExitTimeout = setTimeout(() => {
|
|
@@ -26731,6 +26985,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
26731
26985
|
const staleSnapshotLooksActive = activeScreenPattern.test(lastSnapshot);
|
|
26732
26986
|
const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:Try\s+["“][^\n\r"”]+["”])?\s*(?:\n|\r|$)/.test(screenText) && !activeScreenPattern.test(screenText);
|
|
26733
26987
|
if (staleSnapshotLooksActive && currentScreenLooksIdle) return screenText;
|
|
26988
|
+
if (this.runDetectStatus(screenText) === "idle") return screenText;
|
|
26734
26989
|
if (currentSnapshot.length >= lastSnapshot.length) return screenText;
|
|
26735
26990
|
return `${screenText}
|
|
26736
26991
|
${lastSnapshot}`;
|
|
@@ -35979,6 +36234,33 @@ var ChatSourceRegistry = class {
|
|
|
35979
36234
|
getState(key2) {
|
|
35980
36235
|
return this.records.get(key2)?.state ?? INITIAL_CHAT_SOURCE_STATE;
|
|
35981
36236
|
}
|
|
36237
|
+
/**
|
|
36238
|
+
* Opaque snapshot of a session's FULL record (state + lock + transitions),
|
|
36239
|
+
* for callers that need to speculatively `observe()` and then roll back if
|
|
36240
|
+
* the resulting decision is undesirable (STICKY-NATIVE hold: a trusted
|
|
36241
|
+
* exact-identity session must not flip to PTY on a transient native gap).
|
|
36242
|
+
* Returns undefined when the session has no record yet. The snapshot is a
|
|
36243
|
+
* shallow copy — transitions array is copied so a later append does not
|
|
36244
|
+
* mutate it.
|
|
36245
|
+
*/
|
|
36246
|
+
snapshotRecord(key2) {
|
|
36247
|
+
const rec = this.records.get(key2);
|
|
36248
|
+
if (!rec) return void 0;
|
|
36249
|
+
return { state: rec.state, lockedSince: rec.lockedSince, transitions: [...rec.transitions] };
|
|
36250
|
+
}
|
|
36251
|
+
/** Restore a previously snapshotted record, undoing a speculative observe.
|
|
36252
|
+
* Passing undefined clears the key (it had no record when snapshotted). */
|
|
36253
|
+
restoreRecord(key2, snapshot) {
|
|
36254
|
+
if (!snapshot) {
|
|
36255
|
+
this.records.delete(key2);
|
|
36256
|
+
return;
|
|
36257
|
+
}
|
|
36258
|
+
this.records.set(key2, {
|
|
36259
|
+
state: snapshot.state,
|
|
36260
|
+
lockedSince: snapshot.lockedSince,
|
|
36261
|
+
transitions: [...snapshot.transitions]
|
|
36262
|
+
});
|
|
36263
|
+
}
|
|
35982
36264
|
/** Recent transitions, newest last. Empty array when nothing has happened. */
|
|
35983
36265
|
getTransitions(key2) {
|
|
35984
36266
|
return this.records.get(key2)?.transitions ?? [];
|
|
@@ -36402,7 +36684,52 @@ function decideCliReadChatSource(args) {
|
|
|
36402
36684
|
const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
|
|
36403
36685
|
const observation = buildObservationForCli(args, supportsNative);
|
|
36404
36686
|
const sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
|
|
36687
|
+
const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(sessionKey);
|
|
36688
|
+
const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(sessionKey);
|
|
36689
|
+
const eligibleForStickyHold = args.trustedExactNativeIdentity === true && (priorState.name === "NativeLocked" || priorState.name === "Recovering");
|
|
36405
36690
|
let decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
|
|
36691
|
+
if (eligibleForStickyHold && decision.selected === "pty-parser") {
|
|
36692
|
+
CHAT_SOURCE_REGISTRY.restoreRecord(sessionKey, priorSnapshot);
|
|
36693
|
+
const heldNativeMessages = observation.kind === "native_present" ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult) : [];
|
|
36694
|
+
const messageSource2 = buildCliMessageSourceProvenance({
|
|
36695
|
+
selected: "native-history",
|
|
36696
|
+
provider: args.providerType,
|
|
36697
|
+
nativeHandle: typeof args.nativeHistoryResult?.providerSessionId === "string" ? args.nativeHistoryResult.providerSessionId : void 0,
|
|
36698
|
+
sessionWorkspace: args.sessionWorkspace,
|
|
36699
|
+
intendedWorkspace: args.intendedWorkspace,
|
|
36700
|
+
transcriptWorkspace: void 0,
|
|
36701
|
+
fallbackReason: "native_history_transient_gap_held",
|
|
36702
|
+
nativeSource: "provider-native",
|
|
36703
|
+
sourcePath: typeof args.nativeHistoryResult?.sourcePath === "string" ? args.nativeHistoryResult.sourcePath : void 0,
|
|
36704
|
+
sourceMtimeMs: typeof args.nativeHistoryResult?.sourceMtimeMs === "number" ? args.nativeHistoryResult.sourceMtimeMs : void 0,
|
|
36705
|
+
nativeHistoryCoverage: void 0,
|
|
36706
|
+
partialReason: void 0,
|
|
36707
|
+
unavailableReason: observation.kind === "native_unavailable" ? observation.reason : void 0,
|
|
36708
|
+
nativeMessages: heldNativeMessages,
|
|
36709
|
+
ptyMessages: args.ptyMessages,
|
|
36710
|
+
returnedMessages: heldNativeMessages,
|
|
36711
|
+
safeMapping: args.safeMapping,
|
|
36712
|
+
freshEnough: true,
|
|
36713
|
+
ptyStatusApprovalOnly: true
|
|
36714
|
+
});
|
|
36715
|
+
return {
|
|
36716
|
+
decision: {
|
|
36717
|
+
selected: "native-history",
|
|
36718
|
+
nextState: priorState,
|
|
36719
|
+
transition: {
|
|
36720
|
+
fromState: priorState.name,
|
|
36721
|
+
toState: priorState.name,
|
|
36722
|
+
event: "NoOp",
|
|
36723
|
+
cause: decision.transition.cause,
|
|
36724
|
+
at: Date.now()
|
|
36725
|
+
},
|
|
36726
|
+
lockState: { locked: priorState.name === "NativeLocked" }
|
|
36727
|
+
},
|
|
36728
|
+
messageSource: messageSource2,
|
|
36729
|
+
nativeMessages: heldNativeMessages,
|
|
36730
|
+
nativeSelected: true
|
|
36731
|
+
};
|
|
36732
|
+
}
|
|
36406
36733
|
if (decision.selected === "pty-parser" && args.trustedExactNativeIdentity === true && args.safeMapping && args.ptyMessages.length === 0 && observation.kind === "native_present" && observation.coverage !== "partial" && observation.messages.length > 0) {
|
|
36407
36734
|
CHAT_SOURCE_REGISTRY.clear(sessionKey);
|
|
36408
36735
|
decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
|
|
@@ -36722,7 +37049,7 @@ function sessionSpawnEnvFromAdapter(h, targetSessionId) {
|
|
|
36722
37049
|
function readCliProviderNativeHistory(agentStr, args) {
|
|
36723
37050
|
const canBindFromLiveSession = !args.historySessionId && typeof args.sessionStartedAtMs === "number" && args.sessionStartedAtMs > 0 && typeof args.workspace === "string" && args.workspace.trim().length > 0;
|
|
36724
37051
|
const pinnedProviderSessionId = typeof args.pinnedProviderSessionId === "string" ? args.pinnedProviderSessionId.trim() : "";
|
|
36725
|
-
const effectiveHistorySessionId = args.historySessionId ||
|
|
37052
|
+
const effectiveHistorySessionId = args.historySessionId || pinnedProviderSessionId || "";
|
|
36726
37053
|
const workspaceLatestFallback = !effectiveHistorySessionId && !canBindFromLiveSession && !pinnedProviderSessionId && args.allowWorkspaceLatestFallback === true && typeof args.workspace === "string" && args.workspace.trim().length > 0;
|
|
36727
37054
|
if (!effectiveHistorySessionId && !canBindFromLiveSession && !workspaceLatestFallback) {
|
|
36728
37055
|
return {
|