@adhdev/daemon-core 0.9.82-rc.21 → 0.9.82-rc.211
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 +1 -0
- package/dist/boot/process-hardening.d.ts +50 -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 +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +3 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +1 -2
- package/dist/cli-adapters/terminal-screen.d.ts +2 -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 +32 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +9 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +46 -11
- package/dist/index.js +35173 -15756
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34880 -15556
- 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/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 +90 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +151 -0
- package/dist/mesh/mesh-events-pending.d.ts +33 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +14 -0
- package/dist/mesh/mesh-events.d.ts +5 -40
- package/dist/mesh/mesh-fast-forward.d.ts +41 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -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 +58 -0
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-runtime-store.d.ts +324 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-work-queue.d.ts +137 -6
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +215 -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/cli-provider-instance.d.ts +39 -3
- package/dist/providers/contracts.d.ts +130 -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 +12 -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 +52 -0
- package/dist/providers/spec/cli-adapter.d.ts +101 -0
- package/dist/providers/spec/driver.d.ts +225 -0
- package/dist/providers/spec/evaluator.d.ts +54 -0
- package/dist/providers/spec/loader.d.ts +15 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +2025 -0
- package/dist/providers/spec/types.d.ts +302 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +109 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +42 -1
- 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 +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +6 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +22 -10
- package/src/boot/process-hardening.ts +89 -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 +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1138 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +673 -1139
- 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 +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +35 -29
- package/src/cli-adapters/terminal-backends/types.ts +1 -3
- package/src/cli-adapters/terminal-screen.ts +11 -81
- package/src/commands/chat-commands.ts +1787 -60
- package/src/commands/cli-manager.ts +283 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +4198 -557
- package/src/config/chat-history.ts +95 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -2
- 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/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +42 -12
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-status.ts +35 -6
- package/src/git/git-types.ts +2 -0
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +126 -10
- 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/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +218 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +1404 -0
- package/src/mesh/mesh-events-pending.ts +371 -0
- package/src/mesh/mesh-events-stale.ts +283 -0
- package/src/mesh/mesh-events-utils.ts +161 -0
- package/src/mesh/mesh-events.ts +24 -908
- package/src/mesh/mesh-fast-forward.ts +438 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +587 -102
- package/src/mesh/mesh-missions.ts +151 -0
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-runtime-store.ts +1305 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-work-queue.ts +490 -166
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +403 -0
- package/src/mesh/worktree-bootstrap-config.ts +326 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +833 -64
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- 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 +419 -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 +582 -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 +138 -0
- package/src/providers/spec/cli-adapter.ts +626 -0
- package/src/providers/spec/driver.ts +922 -0
- package/src/providers/spec/evaluator.ts +597 -0
- package/src/providers/spec/loader.ts +324 -0
- package/src/providers/spec/native-history-executor.ts +939 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +691 -0
- package/src/providers/spec/schema.json +341 -0
- package/src/providers/spec/types.ts +356 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +425 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +119 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- 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,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3: Mission persistence (minimal form).
|
|
3
|
+
*
|
|
4
|
+
* A mission is a persistent record of a multi-task goal so the plan lives in
|
|
5
|
+
* the system rather than in the coordinator LLM's context. Coordinator
|
|
6
|
+
* sessions can die or compact; a new coordinator reads the mission back at
|
|
7
|
+
* launch and continues.
|
|
8
|
+
*
|
|
9
|
+
* Explicit non-goals (see docs/mesh-product-plan-v2.md Phase M3): this is not
|
|
10
|
+
* a workflow engine and there is no automatic takeover daemon. Progress is
|
|
11
|
+
* never stored — it is derived from queue task statuses (mission_id) at
|
|
12
|
+
* query time.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { randomUUID } from 'crypto';
|
|
16
|
+
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
17
|
+
import { getQueue } from './mesh-work-queue.js';
|
|
18
|
+
|
|
19
|
+
export type MeshMissionStatus = 'active' | 'paused' | 'completed' | 'abandoned';
|
|
20
|
+
|
|
21
|
+
export const MESH_MISSION_STATUSES: MeshMissionStatus[] = ['active', 'paused', 'completed', 'abandoned'];
|
|
22
|
+
|
|
23
|
+
export interface MeshMissionRecord {
|
|
24
|
+
id: string;
|
|
25
|
+
meshId: string;
|
|
26
|
+
title: string;
|
|
27
|
+
goal: string;
|
|
28
|
+
status: MeshMissionStatus;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MeshMissionTaskAggregate {
|
|
34
|
+
total: number;
|
|
35
|
+
pending: number;
|
|
36
|
+
assigned: number;
|
|
37
|
+
completed: number;
|
|
38
|
+
failed: number;
|
|
39
|
+
cancelled: number;
|
|
40
|
+
/** Pending tasks held back by a dependency failure (blockedReason set). */
|
|
41
|
+
blocked: number;
|
|
42
|
+
/** Latest updatedAt across the mission's tasks, or null with no tasks. */
|
|
43
|
+
lastActivityAt: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MeshMissionSummary extends MeshMissionRecord {
|
|
47
|
+
tasks: MeshMissionTaskAggregate;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeMissionStatus(value: unknown): MeshMissionStatus {
|
|
51
|
+
return MESH_MISSION_STATUSES.includes(value as MeshMissionStatus)
|
|
52
|
+
? value as MeshMissionStatus
|
|
53
|
+
: 'active';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function upsertMeshMission(meshId: string, input: {
|
|
57
|
+
id?: string;
|
|
58
|
+
title: string;
|
|
59
|
+
goal?: string;
|
|
60
|
+
status?: string;
|
|
61
|
+
}): MeshMissionRecord {
|
|
62
|
+
const title = typeof input.title === 'string' ? input.title.trim() : '';
|
|
63
|
+
if (!title) throw new Error('mission_title_required: a mission needs a non-empty title');
|
|
64
|
+
if (input.status !== undefined && !MESH_MISSION_STATUSES.includes(input.status as MeshMissionStatus)) {
|
|
65
|
+
throw new Error(`invalid_mission_status: '${input.status}' (valid: ${MESH_MISSION_STATUSES.join(', ')})`);
|
|
66
|
+
}
|
|
67
|
+
const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : randomUUID();
|
|
68
|
+
const store = MeshRuntimeStore.getInstance();
|
|
69
|
+
const existing = store.getMission(meshId, id);
|
|
70
|
+
const record = {
|
|
71
|
+
id,
|
|
72
|
+
meshId,
|
|
73
|
+
title,
|
|
74
|
+
goal: typeof input.goal === 'string' ? input.goal : existing?.goal ?? '',
|
|
75
|
+
status: normalizeMissionStatus(input.status ?? existing?.status),
|
|
76
|
+
};
|
|
77
|
+
store.upsertMission(record);
|
|
78
|
+
const saved = store.getMission(meshId, id)!;
|
|
79
|
+
return { ...saved, status: normalizeMissionStatus(saved.status) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getMeshMissions(meshId: string, statuses?: MeshMissionStatus[]): MeshMissionRecord[] {
|
|
83
|
+
return MeshRuntimeStore.getInstance().getMissions(meshId, statuses)
|
|
84
|
+
.map(m => ({ ...m, status: normalizeMissionStatus(m.status) }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getMeshMission(meshId: string, missionId: string): MeshMissionRecord | null {
|
|
88
|
+
const record = MeshRuntimeStore.getInstance().getMission(meshId, missionId);
|
|
89
|
+
return record ? { ...record, status: normalizeMissionStatus(record.status) } : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Aggregate task statuses for a mission at query time (no stored progress). */
|
|
93
|
+
export function summarizeMissionTasks(meshId: string, missionId: string): MeshMissionTaskAggregate {
|
|
94
|
+
const tasks = getQueue(meshId).filter(task => task.missionId === missionId);
|
|
95
|
+
const aggregate: MeshMissionTaskAggregate = {
|
|
96
|
+
total: tasks.length,
|
|
97
|
+
pending: 0,
|
|
98
|
+
assigned: 0,
|
|
99
|
+
completed: 0,
|
|
100
|
+
failed: 0,
|
|
101
|
+
cancelled: 0,
|
|
102
|
+
blocked: 0,
|
|
103
|
+
lastActivityAt: null,
|
|
104
|
+
};
|
|
105
|
+
for (const task of tasks) {
|
|
106
|
+
if (task.status === 'pending') aggregate.pending += 1;
|
|
107
|
+
else if (task.status === 'assigned') aggregate.assigned += 1;
|
|
108
|
+
else if (task.status === 'completed') aggregate.completed += 1;
|
|
109
|
+
else if (task.status === 'failed') aggregate.failed += 1;
|
|
110
|
+
else if (task.status === 'cancelled') aggregate.cancelled += 1;
|
|
111
|
+
if (task.status === 'pending' && task.blockedReason) aggregate.blocked += 1;
|
|
112
|
+
if (task.updatedAt && (!aggregate.lastActivityAt || task.updatedAt > aggregate.lastActivityAt)) {
|
|
113
|
+
aggregate.lastActivityAt = task.updatedAt;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return aggregate;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function summarizeMeshMission(meshId: string, mission: MeshMissionRecord): MeshMissionSummary {
|
|
120
|
+
return { ...mission, tasks: summarizeMissionTasks(meshId, mission.id) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Active mission summaries for mesh_status / coordinator prompt injection. */
|
|
124
|
+
export function getActiveMeshMissionSummaries(meshId: string): MeshMissionSummary[] {
|
|
125
|
+
return getMeshMissions(meshId, ['active']).map(mission => summarizeMeshMission(meshId, mission));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* M3-3: render active missions as a prompt section for {{mission}}.
|
|
130
|
+
* Empty string when no active mission — the prompt stays byte-identical to
|
|
131
|
+
* the pre-M3 output in that case (regression guarantee).
|
|
132
|
+
*/
|
|
133
|
+
export function buildMissionPromptSection(meshId: string): string {
|
|
134
|
+
const summaries = getActiveMeshMissionSummaries(meshId);
|
|
135
|
+
if (summaries.length === 0) return '';
|
|
136
|
+
const lines: string[] = ['## Active Mission' + (summaries.length > 1 ? 's' : '')];
|
|
137
|
+
for (const mission of summaries) {
|
|
138
|
+
const t = mission.tasks;
|
|
139
|
+
lines.push(
|
|
140
|
+
`- **${mission.title}** (id: \`${mission.id}\`)`
|
|
141
|
+
+ (mission.goal ? `\n Goal: ${mission.goal}` : '')
|
|
142
|
+
+ `\n Tasks: ${t.total} total — ${t.pending} pending (${t.blocked} blocked), ${t.assigned} assigned, ${t.completed} completed, ${t.failed} failed, ${t.cancelled} cancelled`
|
|
143
|
+
+ (t.lastActivityAt ? `\n Last activity: ${t.lastActivityAt}` : ''),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
lines.push(
|
|
147
|
+
'Continue this mission from its current task state. Do not re-enqueue tasks that already exist — check mesh_view_queue first. '
|
|
148
|
+
+ 'Update the mission with mesh_mission_upsert when its goal changes or it reaches a terminal state (completed/abandoned).',
|
|
149
|
+
);
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import type { PendingMeshCoordinatorEvent } from './mesh-events.js';
|
|
3
|
+
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
4
|
+
|
|
5
|
+
export type MeshAsyncRefineJobStatus = 'accepted' | 'running' | 'completed' | 'failed';
|
|
6
|
+
|
|
7
|
+
export interface MeshAsyncRefineJobSummary extends MeshAsyncJobLifecycle {
|
|
8
|
+
jobId: string;
|
|
9
|
+
interactionId?: string;
|
|
10
|
+
status: MeshAsyncRefineJobStatus;
|
|
11
|
+
meshId?: string;
|
|
12
|
+
nodeId?: string;
|
|
13
|
+
targetNodeId?: string;
|
|
14
|
+
targetDaemonId?: string;
|
|
15
|
+
workspace?: string;
|
|
16
|
+
branch?: string;
|
|
17
|
+
into?: string;
|
|
18
|
+
retryOfJobId?: string;
|
|
19
|
+
lastEvent?: string;
|
|
20
|
+
lastLedgerKind?: string;
|
|
21
|
+
lastUpdatedAt?: string;
|
|
22
|
+
instruction: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readString(value: unknown): string | undefined {
|
|
26
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
30
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
31
|
+
? value as Record<string, unknown>
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function eventStatus(event: string | undefined, fallback?: string): MeshAsyncRefineJobStatus | undefined {
|
|
36
|
+
if (event === 'refine:accepted') return 'accepted';
|
|
37
|
+
if (event === 'refine:completed') return 'completed';
|
|
38
|
+
if (event === 'refine:failed') return 'failed';
|
|
39
|
+
if (fallback === 'completed' || fallback === 'failed' || fallback === 'accepted') return fallback;
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ledgerStatus(kind: string, fallback?: string): MeshAsyncRefineJobStatus {
|
|
44
|
+
if (kind === 'task_completed') return 'completed';
|
|
45
|
+
if (kind === 'task_failed') return 'failed';
|
|
46
|
+
if (fallback === 'accepted') return 'accepted';
|
|
47
|
+
return 'running';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function instructionForStatus(status: MeshAsyncRefineJobStatus): string {
|
|
51
|
+
if (status === 'accepted') return 'Refine job is accepted; wait for asyncRefineJobs or pendingCoordinatorEvents to report running/completed/failed.';
|
|
52
|
+
if (status === 'running') return 'Refine job is running; do not poll the ledger repeatedly. Watch asyncRefineJobs or pendingCoordinatorEvents for the terminal result.';
|
|
53
|
+
if (status === 'completed') return 'Refine job completed; inspect branch convergence and cleanup evidence before reporting final merge state.';
|
|
54
|
+
return 'Refine job failed; inspect result/finalBranchConvergenceState in mesh_task_history, fix the blocker, then rerun mesh_refine_node when ready.';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mergeJob(
|
|
58
|
+
jobs: Map<string, MeshAsyncRefineJobSummary>,
|
|
59
|
+
patch: Partial<MeshAsyncRefineJobSummary> & { jobId?: string },
|
|
60
|
+
): void {
|
|
61
|
+
const jobId = readString(patch.jobId);
|
|
62
|
+
if (!jobId) return;
|
|
63
|
+
const previous = jobs.get(jobId);
|
|
64
|
+
const status = patch.status || previous?.status || 'running';
|
|
65
|
+
const definedPatch = Object.fromEntries(
|
|
66
|
+
Object.entries(patch).filter(([, value]) => value !== undefined),
|
|
67
|
+
) as Partial<MeshAsyncRefineJobSummary>;
|
|
68
|
+
jobs.set(jobId, {
|
|
69
|
+
...previous,
|
|
70
|
+
...definedPatch,
|
|
71
|
+
jobId,
|
|
72
|
+
status,
|
|
73
|
+
instruction: instructionForStatus(status),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildMeshAsyncRefineJobs(args: {
|
|
78
|
+
meshId?: string;
|
|
79
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
80
|
+
pendingEvents?: PendingMeshCoordinatorEvent[];
|
|
81
|
+
}): MeshAsyncRefineJobSummary[] {
|
|
82
|
+
const jobs = new Map<string, MeshAsyncRefineJobSummary>();
|
|
83
|
+
|
|
84
|
+
for (const entry of args.ledgerEntries || []) {
|
|
85
|
+
const payload = readRecord(entry.payload);
|
|
86
|
+
if (payload?.source !== 'refine_mesh_node_async_job') continue;
|
|
87
|
+
const refineJob = readRecord(payload.refineJob);
|
|
88
|
+
const result = readRecord(payload.result);
|
|
89
|
+
const finalState = readRecord(payload.finalBranchConvergenceState) || readRecord(result?.finalBranchConvergenceState);
|
|
90
|
+
const jobId = readString(refineJob?.jobId);
|
|
91
|
+
if (!jobId) continue;
|
|
92
|
+
const status = ledgerStatus(entry.kind, readString(refineJob?.status));
|
|
93
|
+
mergeJob(jobs, {
|
|
94
|
+
jobId,
|
|
95
|
+
interactionId: readString(refineJob?.interactionId),
|
|
96
|
+
status,
|
|
97
|
+
meshId: readString(refineJob?.meshId) || args.meshId,
|
|
98
|
+
nodeId: readString(refineJob?.nodeId) || entry.nodeId,
|
|
99
|
+
targetNodeId: readString(refineJob?.nodeId) || entry.nodeId,
|
|
100
|
+
targetDaemonId: readString(refineJob?.targetDaemonId),
|
|
101
|
+
workspace: readString(refineJob?.workspace),
|
|
102
|
+
branch: readString(result?.branch) || readString(finalState?.branch),
|
|
103
|
+
into: readString(result?.into) || readString(finalState?.baseBranch),
|
|
104
|
+
startedAt: readString(refineJob?.startedAt),
|
|
105
|
+
completedAt: readString(refineJob?.completedAt),
|
|
106
|
+
retryOfJobId: readString(refineJob?.retryOfJobId) || readString(payload.retryOfJobId),
|
|
107
|
+
lastLedgerKind: entry.kind,
|
|
108
|
+
lastUpdatedAt: entry.timestamp,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const event of args.pendingEvents || []) {
|
|
113
|
+
const metadata = readRecord(event.metadataEvent);
|
|
114
|
+
if (metadata?.source !== 'refine_mesh_node_async_job') continue;
|
|
115
|
+
const result = readRecord(metadata.result);
|
|
116
|
+
const finalState = readRecord(result?.finalBranchConvergenceState);
|
|
117
|
+
const jobId = readString(metadata.jobId);
|
|
118
|
+
if (!jobId) continue;
|
|
119
|
+
const status = eventStatus(event.event, readString(metadata.status));
|
|
120
|
+
mergeJob(jobs, {
|
|
121
|
+
jobId,
|
|
122
|
+
interactionId: readString(metadata.interactionId),
|
|
123
|
+
...(status ? { status } : {}),
|
|
124
|
+
meshId: readString(metadata.meshId) || event.meshId || args.meshId,
|
|
125
|
+
nodeId: readString(metadata.nodeId) || event.nodeId,
|
|
126
|
+
targetNodeId: readString(metadata.nodeId) || event.nodeId,
|
|
127
|
+
targetDaemonId: readString(metadata.targetDaemonId),
|
|
128
|
+
workspace: readString(metadata.workspace) || event.workspace,
|
|
129
|
+
branch: readString(result?.branch) || readString(finalState?.branch),
|
|
130
|
+
into: readString(result?.into) || readString(finalState?.baseBranch),
|
|
131
|
+
startedAt: readString(metadata.startedAt),
|
|
132
|
+
completedAt: readString(metadata.completedAt),
|
|
133
|
+
retryOfJobId: readString(metadata.retryOfJobId),
|
|
134
|
+
lastEvent: event.event,
|
|
135
|
+
lastUpdatedAt: new Date(event.queuedAt).toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Array.from(jobs.values()).sort((a, b) => {
|
|
140
|
+
const aTime = new Date(a.lastUpdatedAt || a.startedAt || '').getTime();
|
|
141
|
+
const bTime = new Date(b.lastUpdatedAt || b.startedAt || '').getTime();
|
|
142
|
+
return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M4.0: Review inbox — derive review items from existing truth.
|
|
3
|
+
*
|
|
4
|
+
* No new store: review items are a query-time projection of
|
|
5
|
+
* - mesh_status node statuses (daemon-owned branchConvergence), and
|
|
6
|
+
* - the task ledger (completion evidence envelopes + refine job results).
|
|
7
|
+
*
|
|
8
|
+
* Scope (M4.0): host-daemon-local nodes only. Remote nodes are excluded and
|
|
9
|
+
* surfaced via remoteNodesExcluded/excludedRemoteNodeIds until the
|
|
10
|
+
* daemon↔daemon P2P relay path is live-verified (M4.2).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
14
|
+
import { buildMeshAsyncRefineJobs } from './mesh-refine-status.js';
|
|
15
|
+
|
|
16
|
+
export type MeshReviewInboxReason = 'merge_candidate' | 'refine_blocked_review';
|
|
17
|
+
|
|
18
|
+
export interface MeshReviewInboxConvergence {
|
|
19
|
+
status: string;
|
|
20
|
+
reason: string | null;
|
|
21
|
+
nextStep: string | null;
|
|
22
|
+
needsConvergence: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MeshReviewInboxEvidence {
|
|
26
|
+
available: boolean;
|
|
27
|
+
/** Ledger kind of the entry the evidence came from. */
|
|
28
|
+
kind?: 'task_completed' | 'task_failed';
|
|
29
|
+
/** 'task_completion' = ordinary worker completion envelope; 'refine_job' = Refinery result. */
|
|
30
|
+
source?: 'task_completion' | 'refine_job';
|
|
31
|
+
timestamp?: string;
|
|
32
|
+
taskId?: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
/** M2-3 bootstrap stage from the refine validation gate: cached|ran|failed|skipped|legacy|not_configured. */
|
|
35
|
+
bootstrap?: Record<string, unknown> | null;
|
|
36
|
+
validation?: Record<string, unknown> | null;
|
|
37
|
+
checkpoint?: Record<string, unknown> | null;
|
|
38
|
+
worker?: {
|
|
39
|
+
status: string;
|
|
40
|
+
classification?: string;
|
|
41
|
+
changedFiles: string[];
|
|
42
|
+
changedFilesTruncated?: true;
|
|
43
|
+
validationResults: Array<Record<string, unknown>>;
|
|
44
|
+
errors: string[];
|
|
45
|
+
requiresUserAction: boolean;
|
|
46
|
+
} | null;
|
|
47
|
+
finalBranchConvergenceState?: Record<string, unknown>;
|
|
48
|
+
refineJob?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MeshReviewInboxDiffFile {
|
|
52
|
+
path: string;
|
|
53
|
+
status?: string;
|
|
54
|
+
insertions?: number;
|
|
55
|
+
deletions?: number;
|
|
56
|
+
binary?: boolean;
|
|
57
|
+
oldPath?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MeshReviewInboxDiffSummary {
|
|
61
|
+
baseRef: string;
|
|
62
|
+
files: MeshReviewInboxDiffFile[];
|
|
63
|
+
totalFiles: number;
|
|
64
|
+
totalInsertions: number;
|
|
65
|
+
totalDeletions: number;
|
|
66
|
+
truncated: boolean;
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface MeshReviewInboxItem {
|
|
71
|
+
nodeId: string;
|
|
72
|
+
workspace: string | null;
|
|
73
|
+
branch: string | null;
|
|
74
|
+
defaultBranch: string | null;
|
|
75
|
+
isLocalWorktree: boolean;
|
|
76
|
+
reviewReason: MeshReviewInboxReason;
|
|
77
|
+
convergence: MeshReviewInboxConvergence;
|
|
78
|
+
evidence: MeshReviewInboxEvidence;
|
|
79
|
+
transcriptHandle: Record<string, unknown> | null;
|
|
80
|
+
/** Latest non-terminal Refinery job for this node (accepted/running), if any. */
|
|
81
|
+
activeRefineJob: Record<string, unknown> | null;
|
|
82
|
+
lastActivityAt: string | null;
|
|
83
|
+
/** Filled by the daemon command with a bounded base-branch diff probe; null when the workspace is not locally readable. */
|
|
84
|
+
diffSummary?: MeshReviewInboxDiffSummary | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MeshReviewInboxDerivation {
|
|
88
|
+
items: MeshReviewInboxItem[];
|
|
89
|
+
remoteNodesExcluded: boolean;
|
|
90
|
+
excludedRemoteNodeIds: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const MAX_CHANGED_FILES = 100;
|
|
94
|
+
|
|
95
|
+
function readString(value: unknown): string | null {
|
|
96
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readRecord(value: unknown): Record<string, unknown> | null {
|
|
100
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
101
|
+
? value as Record<string, unknown>
|
|
102
|
+
: null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readStringArray(value: unknown, max: number): { values: string[]; truncated: boolean } {
|
|
106
|
+
if (!Array.isArray(value)) return { values: [], truncated: false };
|
|
107
|
+
const values = value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
|
|
108
|
+
return { values: values.slice(0, max), truncated: values.length > max };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isLocalNodeStatus(node: Record<string, unknown>): boolean {
|
|
112
|
+
if (node.isLocalWorktree === true) return true;
|
|
113
|
+
const connection = readRecord(node.connection);
|
|
114
|
+
return readString(connection?.state) === 'self';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readNodeConvergence(node: Record<string, unknown>): (MeshReviewInboxConvergence & { branch: string | null; defaultBranch: string | null }) | null {
|
|
118
|
+
const convergence = readRecord(node.branchConvergence);
|
|
119
|
+
const status = readString(convergence?.status);
|
|
120
|
+
if (!convergence || !status) return null;
|
|
121
|
+
return {
|
|
122
|
+
status,
|
|
123
|
+
reason: readString(convergence.reason),
|
|
124
|
+
nextStep: readString(convergence.nextStep),
|
|
125
|
+
needsConvergence: convergence.needsConvergence === true,
|
|
126
|
+
branch: readString(convergence.branch),
|
|
127
|
+
defaultBranch: readString(convergence.defaultBranch),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isMergeCandidate(convergence: MeshReviewInboxConvergence): boolean {
|
|
132
|
+
if (convergence.status === 'pushed_feature_branch_needs_merge') return true;
|
|
133
|
+
// Clean non-default worktree branch — the local merge candidate shape
|
|
134
|
+
// (router classifies it cleanup_candidate with this reason).
|
|
135
|
+
return convergence.status === 'cleanup_candidate'
|
|
136
|
+
&& convergence.reason === 'clean_non_default_worktree_branch';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readWorkerArtifact(value: unknown): MeshReviewInboxEvidence['worker'] {
|
|
140
|
+
const worker = readRecord(value);
|
|
141
|
+
if (!worker) return null;
|
|
142
|
+
const changed = readStringArray(worker.changedFiles, MAX_CHANGED_FILES);
|
|
143
|
+
return {
|
|
144
|
+
status: readString(worker.status) ?? 'unknown',
|
|
145
|
+
...(readString(worker.classification) ? { classification: readString(worker.classification)! } : {}),
|
|
146
|
+
changedFiles: changed.values,
|
|
147
|
+
...(changed.truncated ? { changedFilesTruncated: true as const } : {}),
|
|
148
|
+
validationResults: Array.isArray(worker.validationResults)
|
|
149
|
+
? worker.validationResults.map(item => readRecord(item)).filter((item): item is Record<string, unknown> => item !== null)
|
|
150
|
+
: [],
|
|
151
|
+
errors: readStringArray(worker.errors, 20).values,
|
|
152
|
+
requiresUserAction: worker.requiresUserAction === true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isTerminalLedgerKind(kind: string): kind is 'task_completed' | 'task_failed' {
|
|
157
|
+
return kind === 'task_completed' || kind === 'task_failed';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Latest terminal evidence for a node, normalized into the review card shape.
|
|
162
|
+
* Refine job entries carry the validation gate summary (incl. the M2-3
|
|
163
|
+
* bootstrap stage); ordinary completions carry the B-series evidence envelope.
|
|
164
|
+
*/
|
|
165
|
+
function resolveNodeEvidence(nodeId: string, ledgerEntries: MeshLedgerEntry[]): {
|
|
166
|
+
evidence: MeshReviewInboxEvidence;
|
|
167
|
+
transcriptHandle: Record<string, unknown> | null;
|
|
168
|
+
} {
|
|
169
|
+
let evidence: MeshReviewInboxEvidence = { available: false };
|
|
170
|
+
let transcriptHandle: Record<string, unknown> | null = null;
|
|
171
|
+
|
|
172
|
+
for (let i = ledgerEntries.length - 1; i >= 0; i--) {
|
|
173
|
+
const entry = ledgerEntries[i];
|
|
174
|
+
if (entry.nodeId !== nodeId || !isTerminalLedgerKind(entry.kind)) continue;
|
|
175
|
+
const payload = readRecord(entry.payload) ?? {};
|
|
176
|
+
|
|
177
|
+
if (!evidence.available) {
|
|
178
|
+
if (payload.source === 'refine_mesh_node_async_job') {
|
|
179
|
+
const result = readRecord(payload.result);
|
|
180
|
+
const validationSummary = readRecord(result?.validationSummary);
|
|
181
|
+
evidence = {
|
|
182
|
+
available: true,
|
|
183
|
+
kind: entry.kind,
|
|
184
|
+
source: 'refine_job',
|
|
185
|
+
timestamp: entry.timestamp,
|
|
186
|
+
...(entry.sessionId ? { sessionId: entry.sessionId } : {}),
|
|
187
|
+
bootstrap: readRecord(validationSummary?.bootstrap),
|
|
188
|
+
validation: validationSummary
|
|
189
|
+
? Object.fromEntries(Object.entries(validationSummary).filter(([key]) => key !== 'bootstrap'))
|
|
190
|
+
: null,
|
|
191
|
+
checkpoint: readRecord(result?.checkpoint),
|
|
192
|
+
worker: null,
|
|
193
|
+
...(readRecord(payload.finalBranchConvergenceState) ?? readRecord(result?.finalBranchConvergenceState)
|
|
194
|
+
? { finalBranchConvergenceState: (readRecord(payload.finalBranchConvergenceState) ?? readRecord(result?.finalBranchConvergenceState))! }
|
|
195
|
+
: {}),
|
|
196
|
+
...(readRecord(payload.refineJob) ? { refineJob: readRecord(payload.refineJob)! } : {}),
|
|
197
|
+
};
|
|
198
|
+
} else {
|
|
199
|
+
const envelope = readRecord(payload.evidence);
|
|
200
|
+
evidence = {
|
|
201
|
+
available: true,
|
|
202
|
+
kind: entry.kind,
|
|
203
|
+
source: 'task_completion',
|
|
204
|
+
timestamp: entry.timestamp,
|
|
205
|
+
...(readString(payload.taskId) ? { taskId: readString(payload.taskId)! } : {}),
|
|
206
|
+
...(entry.sessionId ? { sessionId: entry.sessionId } : {}),
|
|
207
|
+
bootstrap: null,
|
|
208
|
+
validation: readRecord(envelope?.validation),
|
|
209
|
+
checkpoint: readRecord(envelope?.checkpoint),
|
|
210
|
+
worker: readWorkerArtifact(envelope?.workerResult ?? payload.workerResult),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!transcriptHandle) {
|
|
216
|
+
const envelope = readRecord(payload.evidence);
|
|
217
|
+
transcriptHandle = readRecord(envelope?.transcriptHandle);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (evidence.available && transcriptHandle) break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { evidence, transcriptHandle };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Latest refine terminal result for a node that classified it blocked_review. */
|
|
227
|
+
function hasBlockedReviewRefineResult(nodeId: string, ledgerEntries: MeshLedgerEntry[]): boolean {
|
|
228
|
+
for (let i = ledgerEntries.length - 1; i >= 0; i--) {
|
|
229
|
+
const entry = ledgerEntries[i];
|
|
230
|
+
if (entry.nodeId !== nodeId || !isTerminalLedgerKind(entry.kind)) continue;
|
|
231
|
+
const payload = readRecord(entry.payload) ?? {};
|
|
232
|
+
if (payload.source !== 'refine_mesh_node_async_job') continue;
|
|
233
|
+
const result = readRecord(payload.result);
|
|
234
|
+
const finalState = readRecord(payload.finalBranchConvergenceState) ?? readRecord(result?.finalBranchConvergenceState);
|
|
235
|
+
// Only the most recent refine terminal counts — an older blocked_review
|
|
236
|
+
// superseded by a successful refine must not resurrect the card.
|
|
237
|
+
return readString(finalState?.status) === 'blocked_review'
|
|
238
|
+
|| readString(result?.code) === 'blocked_review';
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Derive review inbox items from mesh_status node statuses and a bounded
|
|
245
|
+
* ledger tail. Pure and read-only; the caller attaches diffSummary separately.
|
|
246
|
+
*/
|
|
247
|
+
export function deriveMeshReviewInboxItems(args: {
|
|
248
|
+
nodes: Array<Record<string, unknown>>;
|
|
249
|
+
ledgerEntries: MeshLedgerEntry[];
|
|
250
|
+
}): MeshReviewInboxDerivation {
|
|
251
|
+
const items: MeshReviewInboxItem[] = [];
|
|
252
|
+
const excludedRemoteNodeIds: string[] = [];
|
|
253
|
+
const refineJobs = buildMeshAsyncRefineJobs({ ledgerEntries: args.ledgerEntries });
|
|
254
|
+
|
|
255
|
+
for (const node of args.nodes) {
|
|
256
|
+
const nodeId = readString(node.nodeId) ?? readString(node.id);
|
|
257
|
+
if (!nodeId) continue;
|
|
258
|
+
|
|
259
|
+
if (!isLocalNodeStatus(node)) {
|
|
260
|
+
excludedRemoteNodeIds.push(nodeId);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const convergence = readNodeConvergence(node);
|
|
265
|
+
if (!convergence) continue;
|
|
266
|
+
if (convergence.status === 'merged_to_main') continue;
|
|
267
|
+
|
|
268
|
+
let reviewReason: MeshReviewInboxReason | null = null;
|
|
269
|
+
if (hasBlockedReviewRefineResult(nodeId, args.ledgerEntries)) {
|
|
270
|
+
reviewReason = 'refine_blocked_review';
|
|
271
|
+
} else if (isMergeCandidate(convergence)) {
|
|
272
|
+
reviewReason = 'merge_candidate';
|
|
273
|
+
}
|
|
274
|
+
if (!reviewReason) continue;
|
|
275
|
+
|
|
276
|
+
const { evidence, transcriptHandle } = resolveNodeEvidence(nodeId, args.ledgerEntries);
|
|
277
|
+
const activeRefineJob = [...refineJobs].reverse().find(job =>
|
|
278
|
+
(job.nodeId === nodeId || job.targetNodeId === nodeId)
|
|
279
|
+
&& (job.status === 'accepted' || job.status === 'running'),
|
|
280
|
+
) ?? null;
|
|
281
|
+
|
|
282
|
+
items.push({
|
|
283
|
+
nodeId,
|
|
284
|
+
workspace: readString(node.workspace),
|
|
285
|
+
branch: convergence.branch ?? readString(node.worktreeBranch),
|
|
286
|
+
defaultBranch: convergence.defaultBranch,
|
|
287
|
+
isLocalWorktree: node.isLocalWorktree === true,
|
|
288
|
+
reviewReason,
|
|
289
|
+
convergence: {
|
|
290
|
+
status: convergence.status,
|
|
291
|
+
reason: convergence.reason,
|
|
292
|
+
nextStep: convergence.nextStep,
|
|
293
|
+
needsConvergence: convergence.needsConvergence,
|
|
294
|
+
},
|
|
295
|
+
evidence,
|
|
296
|
+
transcriptHandle,
|
|
297
|
+
activeRefineJob: activeRefineJob as Record<string, unknown> | null,
|
|
298
|
+
lastActivityAt: evidence.timestamp ?? null,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
items,
|
|
304
|
+
remoteNodesExcluded: excludedRemoteNodeIds.length > 0,
|
|
305
|
+
excludedRemoteNodeIds,
|
|
306
|
+
};
|
|
307
|
+
}
|