@adhdev/daemon-core 0.9.82-rc.34 → 0.9.82-rc.341
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/boot/daemon-lifecycle.d.ts +8 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +46 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +198 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +116 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +53 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +14 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +308 -1
- package/dist/commands/upgrade-helper.d.ts +12 -0
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +77 -1
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +11 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +23 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +52 -13
- package/dist/index.js +42012 -17098
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41683 -16890
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +46 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +174 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +169 -0
- package/dist/mesh/mesh-events-pending.d.ts +45 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +66 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-init.d.ts +86 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +116 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +7 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +418 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/mesh/mesh-work-queue.d.ts +208 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +216 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/chat-message-normalization.d.ts +12 -0
- package/dist/providers/cli-provider-instance.d.ts +76 -3
- package/dist/providers/contracts.d.ts +139 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +13 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +69 -0
- package/dist/providers/spec/cli-adapter.d.ts +217 -0
- package/dist/providers/spec/evaluator.d.ts +23 -0
- package/dist/providers/spec/fsm-driver.d.ts +306 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +144 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +180 -0
- package/dist/providers/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +295 -13
- package/dist/sessions/registry.d.ts +6 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +59 -2
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +26 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +55 -12
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +47 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1215 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +774 -1141
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +100 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +90 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +53 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands.ts +1823 -62
- package/src/commands/cli-manager.ts +286 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +7433 -1317
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +81 -30
- package/src/config/chat-history.ts +483 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +388 -6
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/cli-detector.ts +28 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +55 -14
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +574 -43
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +8 -1
- package/src/git/index.ts +16 -0
- package/src/index.ts +146 -12
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +220 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +234 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +623 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +2144 -0
- package/src/mesh/mesh-events-pending.ts +508 -0
- package/src/mesh/mesh-events-stale.ts +290 -0
- package/src/mesh/mesh-events-utils.ts +300 -0
- package/src/mesh/mesh-events.ts +30 -1035
- package/src/mesh/mesh-fast-forward.ts +839 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-init.ts +260 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +589 -102
- package/src/mesh/mesh-missions.ts +248 -0
- package/src/mesh/mesh-reconcile-loop.ts +822 -0
- package/src/mesh/mesh-refine-batch.ts +197 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +1626 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/mesh/mesh-work-queue.ts +888 -141
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +423 -0
- package/src/mesh/worktree-bootstrap-config.ts +331 -0
- package/src/providers/acp-provider-instance.ts +25 -9
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +45 -5
- package/src/providers/cli-provider-instance.ts +1038 -99
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +150 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +670 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +168 -0
- package/src/providers/spec/cli-adapter.ts +986 -0
- package/src/providers/spec/evaluator.ts +345 -0
- package/src/providers/spec/fsm-driver.ts +1015 -0
- package/src/providers/spec/fsm-evaluator.ts +255 -0
- package/src/providers/spec/fsm-loader.ts +120 -0
- package/src/providers/spec/fsm-types.ts +238 -0
- package/src/providers/spec/native-history-executor.ts +1136 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +226 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +378 -14
- package/src/sessions/registry.ts +6 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +64 -1
- package/src/status/builders.ts +26 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +95 -26
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { appendLedgerEntry, buildTaskCompletionEvidence, readLedgerEntries } from './mesh-ledger.js';
|
|
2
|
+
import type { MeshLedgerKind } from './mesh-ledger.js';
|
|
3
|
+
import { updateDirectDispatchStatus, cleanupTerminalDirectDispatches } from './mesh-work-queue.js';
|
|
4
|
+
import { markSessionDeliveriesTerminal } from './mesh-delivery-policy.js';
|
|
5
|
+
import { queuePendingMeshCoordinatorEvent } from './mesh-events-pending.js';
|
|
6
|
+
import { readNonEmptyString, readRecord, resolveEventSessionId, readWorkerResultMetadata } from './mesh-events-utils.js';
|
|
7
|
+
import { meshNodeIdMatches, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Stale direct-dispatch detection & transcript reconciliation
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export function findRecentTerminalLedgerEvidence(args: {
|
|
14
|
+
meshId: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
nodeId?: string;
|
|
17
|
+
}): { id: string; kind: MeshLedgerKind; payload: Record<string, unknown>; timestamp: string } | null {
|
|
18
|
+
if (!args.sessionId && !args.nodeId) return null;
|
|
19
|
+
// Tail-limit: 200 entries gives a wide enough window to catch terminal events for active
|
|
20
|
+
// sessions while avoiding a full O(n) scan. If a terminal is older than 200 entries,
|
|
21
|
+
// the MeshRuntimeStore fingerprint dedup will still block duplicate processing downstream.
|
|
22
|
+
const entries = readLedgerEntries(args.meshId, { tail: 200 });
|
|
23
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
24
|
+
const entry = entries[i];
|
|
25
|
+
if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
|
|
26
|
+
if (args.sessionId && entry.sessionId === args.sessionId) {
|
|
27
|
+
return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
|
|
28
|
+
}
|
|
29
|
+
// Normalized node-id match (P4): a ledger entry may store its node id as `nodeId`
|
|
30
|
+
// (runtime form) or `node_id` (DB column form leaked onto the object). A raw `===`
|
|
31
|
+
// against args.nodeId drops the entry when the entry's stored form differs from the
|
|
32
|
+
// form the caller passes, so a valid terminal completion goes unfound. meshNodeIdMatches
|
|
33
|
+
// normalizes the entry across all 3 forms before comparing. (The entry's typed shape
|
|
34
|
+
// omits the open index signature MeshNodeIdentified declares, hence the cast.)
|
|
35
|
+
if (!args.sessionId && args.nodeId && meshNodeIdMatches(entry as unknown as MeshNodeIdentified, args.nodeId)) {
|
|
36
|
+
return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Returns true when a task_dispatched entry for the given session appears AFTER the terminal
|
|
43
|
+
// entry (identified by terminalId) in ledger order. Positional (append) order is used rather
|
|
44
|
+
// than timestamp comparison because both entries may share the same millisecond.
|
|
45
|
+
export function hasDispatchAfterTerminal(meshId: string, sessionId: string, terminalId: string): boolean {
|
|
46
|
+
// 200-entry window matches findRecentTerminalLedgerEvidence for consistency.
|
|
47
|
+
const entries = readLedgerEntries(meshId, { tail: 200 });
|
|
48
|
+
let pastTerminal = false;
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (!pastTerminal) {
|
|
51
|
+
if (entry.id === terminalId) pastTerminal = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (entry.kind === 'task_dispatched' && entry.sessionId === sessionId) return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function hasUnterminalDirectDispatchLedgerEntry(meshId: string, sessionId: string): boolean {
|
|
60
|
+
// Some dispatch paths can persist task_dispatched before the direct-dispatch DB row is
|
|
61
|
+
// available. Recover routing from ledger order so coordinator self-targets still emit
|
|
62
|
+
// task_completed and pendingCoordinatorEvents.
|
|
63
|
+
const entries = readLedgerEntries(meshId, { tail: 200 });
|
|
64
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
65
|
+
const entry = entries[i];
|
|
66
|
+
if (entry.sessionId !== sessionId) continue;
|
|
67
|
+
if (entry.kind === 'task_completed' || entry.kind === 'task_failed' || entry.kind === 'task_stalled') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (entry.kind === 'task_dispatched' && entry.payload?.source === 'direct') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function findDirectDispatchLedgerEntry(args: {
|
|
78
|
+
meshId: string;
|
|
79
|
+
taskId: string;
|
|
80
|
+
sessionId?: string;
|
|
81
|
+
}): { id: string; timestamp: string; nodeId?: string; sessionId?: string; providerType?: string; payload: Record<string, unknown> } | null {
|
|
82
|
+
const entries = readLedgerEntries(args.meshId, { tail: 500 });
|
|
83
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
84
|
+
const entry = entries[i];
|
|
85
|
+
if (entry.kind !== 'task_dispatched') continue;
|
|
86
|
+
const payloadTaskId = readNonEmptyString(entry.payload?.taskId);
|
|
87
|
+
if (payloadTaskId !== args.taskId) continue;
|
|
88
|
+
if (args.sessionId && entry.sessionId && entry.sessionId !== args.sessionId) continue;
|
|
89
|
+
return {
|
|
90
|
+
id: entry.id,
|
|
91
|
+
timestamp: entry.timestamp,
|
|
92
|
+
nodeId: entry.nodeId,
|
|
93
|
+
sessionId: entry.sessionId,
|
|
94
|
+
providerType: entry.providerType,
|
|
95
|
+
payload: entry.payload || {},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function hasTerminalLedgerAfterDispatch(args: {
|
|
102
|
+
meshId: string;
|
|
103
|
+
taskId: string;
|
|
104
|
+
sessionId?: string;
|
|
105
|
+
dispatchEntryId?: string;
|
|
106
|
+
dispatchTimestamp?: string;
|
|
107
|
+
}): boolean {
|
|
108
|
+
const entries = readLedgerEntries(args.meshId, { tail: 500 });
|
|
109
|
+
let afterDispatch = !args.dispatchEntryId && !args.dispatchTimestamp;
|
|
110
|
+
const dispatchTime = args.dispatchTimestamp ? new Date(args.dispatchTimestamp).getTime() : Number.NaN;
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (!afterDispatch) {
|
|
113
|
+
if (args.dispatchEntryId && entry.id === args.dispatchEntryId) {
|
|
114
|
+
afterDispatch = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!args.dispatchEntryId && Number.isFinite(dispatchTime)) {
|
|
118
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
119
|
+
if (Number.isFinite(entryTime) && entryTime >= dispatchTime) afterDispatch = true;
|
|
120
|
+
}
|
|
121
|
+
if (!afterDispatch) continue;
|
|
122
|
+
}
|
|
123
|
+
if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
|
|
124
|
+
const terminalTaskId = readNonEmptyString(entry.payload?.taskId);
|
|
125
|
+
if (terminalTaskId && terminalTaskId === args.taskId) return true;
|
|
126
|
+
if (terminalTaskId && terminalTaskId !== args.taskId) continue;
|
|
127
|
+
if (args.sessionId && entry.sessionId === args.sessionId) return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function reconcileDirectDispatchCompletionFromTranscript(args: {
|
|
133
|
+
meshId: string;
|
|
134
|
+
nodeId?: string;
|
|
135
|
+
sessionId: string;
|
|
136
|
+
providerType?: string;
|
|
137
|
+
providerSessionId?: string;
|
|
138
|
+
taskId: string;
|
|
139
|
+
finalSummary: string;
|
|
140
|
+
transcriptMessageAt?: string;
|
|
141
|
+
completedAt?: string;
|
|
142
|
+
targetCoordinatorDaemonId?: string;
|
|
143
|
+
source?: string;
|
|
144
|
+
}): { reconciled: boolean; kind?: MeshLedgerKind; alreadyTerminal?: boolean; workerResult?: unknown; ledgerEntryId?: string; reason?: string } {
|
|
145
|
+
const finalSummary = readNonEmptyString(args.finalSummary);
|
|
146
|
+
if (!args.meshId || !args.taskId || !args.sessionId || !finalSummary) {
|
|
147
|
+
return { reconciled: false, reason: 'missing_required_completion_evidence' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const dispatch = findDirectDispatchLedgerEntry({
|
|
151
|
+
meshId: args.meshId,
|
|
152
|
+
taskId: args.taskId,
|
|
153
|
+
sessionId: args.sessionId,
|
|
154
|
+
});
|
|
155
|
+
if (hasTerminalLedgerAfterDispatch({
|
|
156
|
+
meshId: args.meshId,
|
|
157
|
+
taskId: args.taskId,
|
|
158
|
+
sessionId: args.sessionId,
|
|
159
|
+
dispatchEntryId: dispatch?.id,
|
|
160
|
+
dispatchTimestamp: dispatch?.timestamp,
|
|
161
|
+
})) {
|
|
162
|
+
return { reconciled: false, alreadyTerminal: true, reason: 'terminal_ledger_entry_exists' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const nodeId = readNonEmptyString(args.nodeId) || dispatch?.nodeId;
|
|
166
|
+
const providerType = readNonEmptyString(args.providerType) || dispatch?.providerType || readNonEmptyString(dispatch?.payload.providerType);
|
|
167
|
+
const completedAt = args.completedAt || new Date().toISOString();
|
|
168
|
+
const evidence = buildTaskCompletionEvidence({
|
|
169
|
+
event: 'agent:generating_completed',
|
|
170
|
+
nodeId: nodeId || 'unknown',
|
|
171
|
+
sessionId: args.sessionId,
|
|
172
|
+
providerType,
|
|
173
|
+
providerSessionId: readNonEmptyString(args.providerSessionId),
|
|
174
|
+
finalSummary,
|
|
175
|
+
completedAt,
|
|
176
|
+
});
|
|
177
|
+
const workerResult = evidence.workerResult;
|
|
178
|
+
const dispatchTime = dispatch?.timestamp ? new Date(dispatch.timestamp).getTime() : Number.NaN;
|
|
179
|
+
const transcriptTime = args.transcriptMessageAt ? new Date(args.transcriptMessageAt).getTime() : Number.NaN;
|
|
180
|
+
const transcriptAfterDispatch = Number.isFinite(dispatchTime) && Number.isFinite(transcriptTime) && transcriptTime >= dispatchTime;
|
|
181
|
+
if (workerResult.source !== 'final_summary_json' && !transcriptAfterDispatch) {
|
|
182
|
+
return { reconciled: false, reason: 'transcript_not_proven_after_dispatch' };
|
|
183
|
+
}
|
|
184
|
+
const workerFailed = workerResult.status === 'failed' || (workerResult.status !== 'completed' && workerResult.errors.length > 0);
|
|
185
|
+
const kind: MeshLedgerKind = workerFailed ? 'task_failed' : 'task_completed';
|
|
186
|
+
|
|
187
|
+
const entry = appendLedgerEntry(args.meshId, {
|
|
188
|
+
kind,
|
|
189
|
+
nodeId: nodeId || undefined,
|
|
190
|
+
sessionId: args.sessionId,
|
|
191
|
+
providerType: providerType || undefined,
|
|
192
|
+
payload: {
|
|
193
|
+
event: 'agent:generating_completed',
|
|
194
|
+
source: args.source || 'direct_task_transcript_reconciliation',
|
|
195
|
+
taskId: args.taskId,
|
|
196
|
+
providerSessionId: readNonEmptyString(args.providerSessionId),
|
|
197
|
+
finalSummary,
|
|
198
|
+
workerResult,
|
|
199
|
+
completionDiagnostic: {
|
|
200
|
+
reason: 'direct_task_transcript_reconciliation',
|
|
201
|
+
dispatchEntryId: dispatch?.id,
|
|
202
|
+
dispatchTimestamp: dispatch?.timestamp,
|
|
203
|
+
transcriptMessageAt: readNonEmptyString(args.transcriptMessageAt),
|
|
204
|
+
transcriptFinalAssistantPresent: true,
|
|
205
|
+
},
|
|
206
|
+
evidence,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
updateDirectDispatchStatus(args.meshId, args.sessionId, kind === 'task_completed' ? 'completed' : 'failed');
|
|
210
|
+
markSessionDeliveriesTerminal(args.meshId, args.sessionId, kind === 'task_completed' ? 'completed' : 'failed');
|
|
211
|
+
setImmediate(() => cleanupTerminalDirectDispatches());
|
|
212
|
+
queuePendingMeshCoordinatorEvent({
|
|
213
|
+
event: kind === 'task_completed' ? 'agent:generating_completed' : 'agent:stopped',
|
|
214
|
+
meshId: args.meshId,
|
|
215
|
+
nodeLabel: nodeId ? `Node '${nodeId}'` : 'Remote agent',
|
|
216
|
+
nodeId: nodeId || undefined,
|
|
217
|
+
metadataEvent: {
|
|
218
|
+
targetSessionId: args.sessionId,
|
|
219
|
+
providerType: providerType || undefined,
|
|
220
|
+
providerSessionId: readNonEmptyString(args.providerSessionId),
|
|
221
|
+
finalSummary,
|
|
222
|
+
taskId: args.taskId,
|
|
223
|
+
workerResult,
|
|
224
|
+
completionDiagnostic: {
|
|
225
|
+
reason: 'direct_task_transcript_reconciliation',
|
|
226
|
+
terminalLedgerKind: kind,
|
|
227
|
+
terminalLedgerId: entry.id,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
coordinatorMessage: undefined,
|
|
231
|
+
queuedAt: Date.now(),
|
|
232
|
+
...(readNonEmptyString(args.targetCoordinatorDaemonId) ? { targetCoordinatorDaemonId: readNonEmptyString(args.targetCoordinatorDaemonId) } : {}),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return { reconciled: true, kind, workerResult, ledgerEntryId: entry.id };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function buildNoProgressCompletionReconciliation(args: {
|
|
239
|
+
meshId: string;
|
|
240
|
+
nodeId?: string;
|
|
241
|
+
nodeLabel: string;
|
|
242
|
+
metadataEvent: Record<string, unknown>;
|
|
243
|
+
sourceInstanceId?: string;
|
|
244
|
+
}): Record<string, unknown> | null {
|
|
245
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
246
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
247
|
+
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
248
|
+
const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId);
|
|
249
|
+
const workerResult = readWorkerResultMetadata(args.metadataEvent);
|
|
250
|
+
const completionDiagnostic = readRecord(args.metadataEvent.completionDiagnostic);
|
|
251
|
+
const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary);
|
|
252
|
+
const status = readNonEmptyString(args.metadataEvent.status).toLowerCase();
|
|
253
|
+
const explicitCompletionEvidence = Boolean(
|
|
254
|
+
finalSummary
|
|
255
|
+
|| workerResult
|
|
256
|
+
|| completionDiagnostic?.finalAssistantPresent === true
|
|
257
|
+
|| status === 'idle'
|
|
258
|
+
|| status === 'ready'
|
|
259
|
+
|| status === 'completed',
|
|
260
|
+
);
|
|
261
|
+
if (explicitCompletionEvidence) {
|
|
262
|
+
return {
|
|
263
|
+
...args.metadataEvent,
|
|
264
|
+
targetSessionId: sessionId,
|
|
265
|
+
providerType,
|
|
266
|
+
providerSessionId,
|
|
267
|
+
finalSummary,
|
|
268
|
+
source: 'no_progress_reconciliation',
|
|
269
|
+
reconciledFromEvent: 'monitor:no_progress',
|
|
270
|
+
timestamp: args.metadataEvent.timestamp ?? Date.now(),
|
|
271
|
+
completionDiagnostic: {
|
|
272
|
+
...(completionDiagnostic || {}),
|
|
273
|
+
reconciliationReason: 'provider_completion_evidence',
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const terminal = findRecentTerminalLedgerEvidence({
|
|
279
|
+
meshId: args.meshId,
|
|
280
|
+
sessionId: sessionId || undefined,
|
|
281
|
+
nodeId: nodeId || undefined,
|
|
282
|
+
});
|
|
283
|
+
if (!terminal) return null;
|
|
284
|
+
return {
|
|
285
|
+
...args.metadataEvent,
|
|
286
|
+
source: 'no_progress_terminal_ledger_suppression',
|
|
287
|
+
terminalLedgerKind: terminal.kind,
|
|
288
|
+
terminalLedgerAt: terminal.timestamp,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type { SessionRecoveryContext } from './mesh-ledger.js';
|
|
2
|
+
|
|
3
|
+
export function readNonEmptyString(value: unknown): string {
|
|
4
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
8
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
+
? value as Record<string, unknown>
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// A single daemon is identified three ways across the system: the raw machineId
|
|
14
|
+
// (`mach_X`, what loadConfig().machineId returns and what the core forwarder uses as
|
|
15
|
+
// localDaemonId), the cloud daemon id (`daemon_mach_X`), and the standalone daemon id
|
|
16
|
+
// (`standalone_mach_X`, the runtime instanceId the MCP coordinator reports as
|
|
17
|
+
// ctx.localDaemonId). Worker envelope stamps (meshCoordinatorDaemonId) and coordinator
|
|
18
|
+
// drains use the prefixed forms, while the forwarder compares against the raw id — so a
|
|
19
|
+
// naive `===` treats the same daemon as remote and breaks both coordinator matching and
|
|
20
|
+
// the R3 direct-delivered dedup. Canonicalize to the raw id before comparing.
|
|
21
|
+
export function canonicalDaemonId(value: unknown): string {
|
|
22
|
+
const id = readNonEmptyString(value);
|
|
23
|
+
if (!id) return '';
|
|
24
|
+
return id.replace(/^(?:daemon|standalone)_/, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function sameDaemonId(a: unknown, b: unknown): boolean {
|
|
28
|
+
const ca = canonicalDaemonId(a);
|
|
29
|
+
const cb = canonicalDaemonId(b);
|
|
30
|
+
return ca !== '' && ca === cb;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The relay-safety metadata a worker session must carry so that its completion /
|
|
35
|
+
* generating events route back to the coordinator proactively (without waiting for
|
|
36
|
+
* a mesh_read_chat reconcile). meshCoordinatorDaemonId is the routing anchor the
|
|
37
|
+
* core forwarder (injectMeshSystemMessage) keys on to pick a remote coordinator
|
|
38
|
+
* target; meshNodeFor/meshNodeId identify the worker, and launchedByCoordinator is
|
|
39
|
+
* the delegation proof. See resolveWorkerDelegateRouting().
|
|
40
|
+
*/
|
|
41
|
+
export interface MeshWorkerRelayStamp {
|
|
42
|
+
meshNodeFor?: string;
|
|
43
|
+
meshNodeId?: string;
|
|
44
|
+
meshCoordinatorDaemonId?: string;
|
|
45
|
+
launchedByCoordinator?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build the relay-safety stamp from a dispatch's meshContext, only including fields
|
|
50
|
+
* the session does not already carry. Returns undefined when there is nothing new to
|
|
51
|
+
* stamp, so callers can skip a no-op updateSettings() write.
|
|
52
|
+
*
|
|
53
|
+
* This closes the remote-session relay gap: a worker session reached by a dispatch
|
|
54
|
+
* that carries coordinatorDaemonId (mesh_send_task / queue assignment over P2P) gets
|
|
55
|
+
* the coordinator anchor persisted onto its settings at dispatch time, even if it was
|
|
56
|
+
* not launched via mesh_launch_session. Without the stamp the forwarder cannot resolve
|
|
57
|
+
* a remote coordinator and the completion event sits in the pending queue until a
|
|
58
|
+
* read_chat-triggered reconcile drains it.
|
|
59
|
+
*/
|
|
60
|
+
export function buildMeshWorkerRelayStamp(
|
|
61
|
+
currentSettings: Record<string, unknown> | undefined,
|
|
62
|
+
meshContext: {
|
|
63
|
+
meshId?: unknown;
|
|
64
|
+
nodeId?: unknown;
|
|
65
|
+
coordinatorDaemonId?: unknown;
|
|
66
|
+
} | undefined,
|
|
67
|
+
): MeshWorkerRelayStamp | undefined {
|
|
68
|
+
if (!meshContext) return undefined;
|
|
69
|
+
const settings = currentSettings && typeof currentSettings === 'object' ? currentSettings : {};
|
|
70
|
+
const stamp: MeshWorkerRelayStamp = {};
|
|
71
|
+
|
|
72
|
+
const meshId = readNonEmptyString(meshContext.meshId);
|
|
73
|
+
if (meshId && !readNonEmptyString(settings.meshNodeFor)) stamp.meshNodeFor = meshId;
|
|
74
|
+
|
|
75
|
+
const nodeId = readNonEmptyString(meshContext.nodeId);
|
|
76
|
+
if (nodeId && !readNonEmptyString(settings.meshNodeId)) stamp.meshNodeId = nodeId;
|
|
77
|
+
|
|
78
|
+
const coordinatorDaemonId = readNonEmptyString(meshContext.coordinatorDaemonId);
|
|
79
|
+
if (coordinatorDaemonId && !readNonEmptyString(settings.meshCoordinatorDaemonId)) {
|
|
80
|
+
stamp.meshCoordinatorDaemonId = coordinatorDaemonId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A dispatch from a coordinator is itself proof of delegation; stamp it when the
|
|
84
|
+
// session is being given any mesh routing context but has no marker yet.
|
|
85
|
+
if ((meshId || nodeId || coordinatorDaemonId) && settings.launchedByCoordinator !== true) {
|
|
86
|
+
stamp.launchedByCoordinator = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Object.keys(stamp).length > 0 ? stamp : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string {
|
|
93
|
+
return readNonEmptyString(event.targetSessionId)
|
|
94
|
+
|| readNonEmptyString(event.sessionId)
|
|
95
|
+
|| readNonEmptyString(event.instanceId)
|
|
96
|
+
|| readNonEmptyString(fallback);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function readRefineJobId(event: { metadataEvent?: Record<string, unknown> } | Record<string, unknown>): string {
|
|
100
|
+
const metadata = readRecord((event as any).metadataEvent) || event as Record<string, unknown>;
|
|
101
|
+
const result = readRecord(metadata.result);
|
|
102
|
+
const refineJob = readRecord(result?.refineJob);
|
|
103
|
+
return readNonEmptyString(metadata.jobId) || readNonEmptyString(refineJob?.jobId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function readWorkerResultMetadata(event: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
107
|
+
return readRecord(event.workerResult) || readRecord(event.meshWorkerResult) || readRecord(event.structuredResult);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const MESH_SURFACED_PREVIEW_MAX_CHARS = 512;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A coordinator that surfaces a REMOTE worker's mesh session has no local instance for
|
|
114
|
+
* it, so the status snapshot's getLastDisplayMessage has nothing to read and the only
|
|
115
|
+
* preview the coordinator-mirrored copy could ever carry comes from the worker's
|
|
116
|
+
* completion event. The worker's latest assistant reply rides on that event as
|
|
117
|
+
* `finalSummary` (and as `workerResult.summary` / `result.summary` on some paths).
|
|
118
|
+
*
|
|
119
|
+
* This resolves that assistant text into a preview the coordinator can stamp onto its
|
|
120
|
+
* mirror entry so the mobile inbox shows the worker's latest assistant response instead
|
|
121
|
+
* of being stuck on the first dispatched user task (which is the only message the
|
|
122
|
+
* coordinator-side transcript ever holds). Returns undefined when the event carries no
|
|
123
|
+
* assistant text — non-completion lifecycle events (generating_started / ready without a
|
|
124
|
+
* summary) must NOT clobber a previously surfaced preview.
|
|
125
|
+
*/
|
|
126
|
+
export function resolveMeshSurfacedSessionPreview(
|
|
127
|
+
metadataEvent: Record<string, unknown>,
|
|
128
|
+
): { preview: string; role: 'assistant'; receivedAt: number } | undefined {
|
|
129
|
+
const workerResult = readWorkerResultMetadata(metadataEvent);
|
|
130
|
+
const resultRecord = readRecord(metadataEvent.result);
|
|
131
|
+
const summaryText = readNonEmptyString(metadataEvent.finalSummary)
|
|
132
|
+
|| readNonEmptyString(workerResult?.summary)
|
|
133
|
+
|| readNonEmptyString(workerResult?.finalSummary)
|
|
134
|
+
|| readNonEmptyString(resultRecord?.summary)
|
|
135
|
+
|| readNonEmptyString(resultRecord?.finalSummary);
|
|
136
|
+
if (!summaryText) return undefined;
|
|
137
|
+
const truncationSuffix = '...[truncated]';
|
|
138
|
+
const preview = summaryText.length > MESH_SURFACED_PREVIEW_MAX_CHARS
|
|
139
|
+
? `${summaryText.slice(0, MESH_SURFACED_PREVIEW_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}`
|
|
140
|
+
: summaryText;
|
|
141
|
+
const timestamp = readEventTimestampValue(metadataEvent.timestamp);
|
|
142
|
+
return { preview, role: 'assistant', receivedAt: timestamp };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function readEventTimestampValue(value: unknown): number {
|
|
146
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
147
|
+
if (typeof value === 'string' && value.trim()) {
|
|
148
|
+
const parsed = Number(value);
|
|
149
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
150
|
+
const dateMs = Date.parse(value);
|
|
151
|
+
if (Number.isFinite(dateMs)) return dateMs;
|
|
152
|
+
}
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatCompletionMetadata(event: Record<string, unknown>): string {
|
|
157
|
+
const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === 'object'
|
|
158
|
+
? event.completionDiagnostic as Record<string, unknown>
|
|
159
|
+
: null;
|
|
160
|
+
const diagnosticReason = completionDiagnostic
|
|
161
|
+
? readNonEmptyString(completionDiagnostic.blockReason) || 'present'
|
|
162
|
+
: '';
|
|
163
|
+
const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
|
|
164
|
+
? String(completionDiagnostic.finalAssistantPresent)
|
|
165
|
+
: '';
|
|
166
|
+
const evidenceLevel = readNonEmptyString(event.evidenceLevel);
|
|
167
|
+
const parts = [
|
|
168
|
+
readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
|
|
169
|
+
readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
|
|
170
|
+
readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
|
|
171
|
+
diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
|
|
172
|
+
finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
|
|
173
|
+
evidenceLevel && evidenceLevel !== 'sufficient' ? `evidence_level=${evidenceLevel}` : '',
|
|
174
|
+
].filter(Boolean);
|
|
175
|
+
return parts.length > 0 ? ` (${parts.join('; ')})` : '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function buildMeshSystemMessage(args: {
|
|
179
|
+
event: string;
|
|
180
|
+
nodeLabel: string;
|
|
181
|
+
metadataEvent: Record<string, unknown>;
|
|
182
|
+
recoveryContext?: SessionRecoveryContext | null;
|
|
183
|
+
}): string {
|
|
184
|
+
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
185
|
+
if (args.event === 'agent:generating_completed') {
|
|
186
|
+
if (args.metadataEvent.source === 'no_progress_reconciliation') {
|
|
187
|
+
return `[System] ${args.nodeLabel} already has completion evidence${metadata}. The no-progress monitor reconciled the terminal handoff and marked the session complete; wait for the queued completion event/status refresh before doing any manual transcript check.`;
|
|
188
|
+
}
|
|
189
|
+
const reviewNote = args.metadataEvent.reviewRecommended === true
|
|
190
|
+
? ' Completion evidence is insufficient — verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly.'
|
|
191
|
+
: ' Use mesh_read_chat once to review its final progress, but do not poll repeatedly.';
|
|
192
|
+
return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
|
|
193
|
+
}
|
|
194
|
+
if (args.event === 'agent:waiting_approval') {
|
|
195
|
+
return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
|
|
196
|
+
}
|
|
197
|
+
if (args.event === 'agent:stopped') {
|
|
198
|
+
const rc = args.recoveryContext;
|
|
199
|
+
if (rc && rc.consecutiveNodeFailures > 0) {
|
|
200
|
+
const parts = [
|
|
201
|
+
`[System] ${args.nodeLabel} has stopped unexpectedly${metadata}.`,
|
|
202
|
+
`\n\n**Recovery Context:**`,
|
|
203
|
+
`- Consecutive failures on this node: ${rc.consecutiveNodeFailures}`,
|
|
204
|
+
rc.taskAttemptCount > 0 ? `- This task has been attempted ${rc.taskAttemptCount} time(s)` : '',
|
|
205
|
+
`- Recommendation: ${rc.advice}`,
|
|
206
|
+
];
|
|
207
|
+
if (rc.retryRecommended && rc.lastTaskMessage) {
|
|
208
|
+
parts.push(
|
|
209
|
+
`\n\n**Original task to retry:**`,
|
|
210
|
+
`> ${rc.lastTaskMessage.length > 300 ? rc.lastTaskMessage.slice(0, 300) + '...' : rc.lastTaskMessage}`,
|
|
211
|
+
`\nTo retry: call \`mesh_launch_session\` for this node, then \`mesh_send_task\` with the original task.`,
|
|
212
|
+
);
|
|
213
|
+
} else if (!rc.retryRecommended) {
|
|
214
|
+
parts.push(
|
|
215
|
+
`\nDo NOT retry on this node. Consider reassigning to a different node or asking the user for guidance.`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return parts.filter(Boolean).join('\n');
|
|
219
|
+
}
|
|
220
|
+
return `[System] ${args.nodeLabel} has stopped${metadata}. Use mesh_read_chat once if you need to inspect its last output.`;
|
|
221
|
+
}
|
|
222
|
+
if (args.event === 'monitor:no_progress') {
|
|
223
|
+
return `[System] ${args.nodeLabel} is still reported as generating after a long interval${metadata}. Wait for pendingCoordinatorEvents or a completion/status event; if the user explicitly asks for status, make one bounded status check and then wait again.`;
|
|
224
|
+
}
|
|
225
|
+
if (args.event === 'worktree_bootstrap_complete') {
|
|
226
|
+
const worktreePath = readNonEmptyString(args.metadataEvent.worktreePath);
|
|
227
|
+
const durationMs = typeof args.metadataEvent.durationMs === 'number' ? args.metadataEvent.durationMs : undefined;
|
|
228
|
+
return `[System] ${args.nodeLabel} worktree bootstrap completed${worktreePath ? ` at ${worktreePath}` : ''}${durationMs !== undefined ? ` in ${Math.round(durationMs / 1000)}s` : ''}. The worktree is ready — use \`mesh_launch_session\` to start an agent.`;
|
|
229
|
+
}
|
|
230
|
+
if (args.event === 'worktree_bootstrap_failed') {
|
|
231
|
+
const error = readNonEmptyString(args.metadataEvent.error);
|
|
232
|
+
return `[System] ${args.nodeLabel} worktree bootstrap failed${error ? `: ${error}` : '.'}. Use \`mesh_retry_node_bootstrap\` to retry or inspect the node state.`;
|
|
233
|
+
}
|
|
234
|
+
if (args.event === 'refine:accepted') {
|
|
235
|
+
const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
|
|
236
|
+
return `[System] Refinery accepted async job${jobId ? ` ${jobId}` : ''} for ${args.nodeLabel}. Completion/failure will be delivered as a terminal refine event; do not poll repeatedly.`;
|
|
237
|
+
}
|
|
238
|
+
if (args.event === 'refine:completed') {
|
|
239
|
+
const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
|
|
240
|
+
const result = readRecord(args.metadataEvent.result);
|
|
241
|
+
const validationSummary = readRecord(result?.validationSummary);
|
|
242
|
+
const patchEquivalence = readRecord(result?.patchEquivalence);
|
|
243
|
+
const finalConvergence = readRecord(result?.finalBranchConvergenceState);
|
|
244
|
+
const validationStatus = readNonEmptyString(validationSummary?.status);
|
|
245
|
+
const patchStatus = readNonEmptyString(patchEquivalence?.status)
|
|
246
|
+
|| (patchEquivalence?.equivalent === true ? 'passed' : '');
|
|
247
|
+
const into = readNonEmptyString(result?.into);
|
|
248
|
+
const branch = readNonEmptyString(result?.branch);
|
|
249
|
+
const mergeStatus = result?.merged === true ? 'merged' : readNonEmptyString(finalConvergence?.status);
|
|
250
|
+
const convergenceStatus = readNonEmptyString(finalConvergence?.status);
|
|
251
|
+
const nextStep = readNonEmptyString(result?.nextStep)
|
|
252
|
+
|| readNonEmptyString(finalConvergence?.nextStep)
|
|
253
|
+
|| 'Continue from the updated mesh state.';
|
|
254
|
+
const details = [
|
|
255
|
+
jobId ? `job_id=${jobId}` : '',
|
|
256
|
+
branch && into ? `${branch}→${into}` : '',
|
|
257
|
+
validationStatus ? `validation=${validationStatus}` : '',
|
|
258
|
+
patchStatus ? `patch_equivalence=${patchStatus}` : '',
|
|
259
|
+
mergeStatus ? `merge=${mergeStatus}` : '',
|
|
260
|
+
convergenceStatus ? `final_convergence=${convergenceStatus}` : '',
|
|
261
|
+
].filter(Boolean).join('; ');
|
|
262
|
+
return `[System] Refinery async job for ${args.nodeLabel} completed successfully${details ? ` (${details})` : ''}.\nNext step: ${nextStep}`;
|
|
263
|
+
}
|
|
264
|
+
if (args.event === 'refine:failed') {
|
|
265
|
+
const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
|
|
266
|
+
const result = readRecord(args.metadataEvent.result);
|
|
267
|
+
const validationSummary = readRecord(result?.validationSummary);
|
|
268
|
+
const patchEquivalence = readRecord(result?.patchEquivalence);
|
|
269
|
+
const finalConvergence = readRecord(result?.finalBranchConvergenceState);
|
|
270
|
+
const code = readNonEmptyString(result?.code);
|
|
271
|
+
const error = readNonEmptyString(result?.error);
|
|
272
|
+
const validationStatus = readNonEmptyString(validationSummary?.status);
|
|
273
|
+
const patchStatus = readNonEmptyString(patchEquivalence?.status)
|
|
274
|
+
|| (patchEquivalence?.equivalent === true ? 'passed' : '');
|
|
275
|
+
const mergeStatus = result?.merged === true
|
|
276
|
+
? 'merged'
|
|
277
|
+
: finalConvergence?.merged === false
|
|
278
|
+
? 'not_merged'
|
|
279
|
+
: '';
|
|
280
|
+
const convergenceStatus = readNonEmptyString(result?.convergenceStatus)
|
|
281
|
+
|| readNonEmptyString(finalConvergence?.status);
|
|
282
|
+
const blockedReason = readNonEmptyString(result?.blockedReason);
|
|
283
|
+
const nextStep = readNonEmptyString(result?.nextStep) || readNonEmptyString(finalConvergence?.nextStep);
|
|
284
|
+
const details = [
|
|
285
|
+
jobId ? `job_id=${jobId}` : '',
|
|
286
|
+
code ? `code=${code}` : '',
|
|
287
|
+
validationStatus ? `validation=${validationStatus}` : '',
|
|
288
|
+
patchStatus ? `patch_equivalence=${patchStatus}` : '',
|
|
289
|
+
mergeStatus ? `merge=${mergeStatus}` : '',
|
|
290
|
+
convergenceStatus ? `convergence=${convergenceStatus}` : '',
|
|
291
|
+
blockedReason ? `reason=${blockedReason}` : '',
|
|
292
|
+
].filter(Boolean).join('; ');
|
|
293
|
+
const parts = [
|
|
294
|
+
`[System] Refinery async job for ${args.nodeLabel} failed${details ? ` (${details})` : ''}${error ? `: ${error}` : '.'}`,
|
|
295
|
+
nextStep ? `Next step: ${nextStep}` : 'Review the terminal refine event/ledger before retrying.',
|
|
296
|
+
];
|
|
297
|
+
return parts.join('\n');
|
|
298
|
+
}
|
|
299
|
+
return '';
|
|
300
|
+
}
|