peaks-loop 4.0.29 → 4.0.31
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/CHANGELOG.md +27 -0
- package/dist/cli/commands/_register.js +4 -0
- package/dist/cli/commands/dispatch-commands.d.ts +5 -41
- package/dist/cli/commands/dispatch-commands.js +35 -185
- package/dist/cli/commands/evidence-commands.d.ts +12 -0
- package/dist/cli/commands/evidence-commands.js +43 -0
- package/dist/cli/commands/fresh-context-commands.d.ts +15 -0
- package/dist/cli/commands/fresh-context-commands.js +24 -0
- package/dist/cli/commands/request-commands.js +2 -96
- package/dist/cli/commands/request-format-helpers.d.ts +19 -0
- package/dist/cli/commands/request-format-helpers.js +102 -0
- package/dist/cli/commands/workspace/init-command.js +27 -14
- package/dist/cli/commands/worktree-auth-commands.js +2 -472
- package/dist/cli/commands/worktree-lease-commands.d.ts +21 -0
- package/dist/cli/commands/worktree-lease-commands.js +500 -0
- package/dist/services/artifacts/request-artifact-service.js +13 -3
- package/dist/services/code/auto-compact-lifecycle.d.ts +102 -0
- package/dist/services/code/auto-compact-lifecycle.js +235 -0
- package/dist/services/code/auto-compact-orchestrator.d.ts +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +1 -222
- package/dist/services/codegraph/codegraph-autorefresh.js +44 -6
- package/dist/services/codegraph/codegraph-preflight-service.d.ts +5 -2
- package/dist/services/codegraph/codegraph-preflight-service.js +13 -6
- package/dist/services/codegraph/codegraph-service.d.ts +17 -2
- package/dist/services/codegraph/codegraph-service.js +27 -2
- package/dist/services/context/build-dispatch-system-prompt.d.ts +26 -0
- package/dist/services/context/build-dispatch-system-prompt.js +38 -3
- package/dist/services/dispatch/dispatch-record-types.d.ts +278 -0
- package/dist/services/dispatch/dispatch-record-types.js +15 -0
- package/dist/services/dispatch/dispatch-record-upgrade.d.ts +5 -0
- package/dist/services/dispatch/dispatch-record-upgrade.js +230 -0
- package/dist/services/dispatch/dispatch-record-writer.d.ts +3 -278
- package/dist/services/dispatch/dispatch-record-writer.js +3 -245
- package/dist/services/dispatch/dispatch-sub-agent.d.ts +43 -0
- package/dist/services/dispatch/dispatch-sub-agent.js +55 -0
- package/dist/services/dispatch/isolation-lease.d.ts +43 -0
- package/dist/services/dispatch/isolation-lease.js +129 -0
- package/dist/services/evidence/evidence-generator.d.ts +26 -0
- package/dist/services/evidence/evidence-generator.js +349 -0
- package/dist/services/fresh-context/config.d.ts +1 -0
- package/dist/services/fresh-context/config.js +13 -0
- package/dist/services/fresh-context/fresh-context-block.d.ts +12 -0
- package/dist/services/fresh-context/fresh-context-block.js +40 -0
- package/dist/services/fresh-context/trigger-scan.d.ts +30 -0
- package/dist/services/fresh-context/trigger-scan.js +29 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +65 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +204 -0
- package/dist/services/skills/hooks-settings-service.d.ts +2 -64
- package/dist/services/skills/hooks-settings-service.js +2 -215
- package/dist/services/skills/skill-statusline-renderer.d.ts +2 -34
- package/dist/services/skills/skill-statusline-renderer.js +1 -184
- package/dist/services/skills/statusline-palette.d.ts +62 -0
- package/dist/services/skills/statusline-palette.js +190 -0
- package/dist/services/slice/slice-decompose-import-edges.d.ts +8 -0
- package/dist/services/slice/slice-decompose-import-edges.js +102 -0
- package/dist/services/slice/slice-decompose-service.js +3 -198
- package/dist/services/slice/slice-decompose-tarjan.d.ts +8 -0
- package/dist/services/slice/slice-decompose-tarjan.js +108 -0
- package/dist/services/standards/project-standards-service.d.ts +1 -9
- package/dist/services/standards/project-standards-service.js +3 -232
- package/dist/services/standards/standards-render.d.ts +23 -0
- package/dist/services/standards/standards-render.js +238 -0
- package/dist/services/standards/ui-library-dispatch-block.d.ts +27 -0
- package/dist/services/standards/ui-library-dispatch-block.js +48 -0
- package/dist/services/workspace/reconcile-migrate.d.ts +77 -0
- package/dist/services/workspace/reconcile-migrate.js +230 -0
- package/dist/services/workspace/reconcile-service.d.ts +0 -72
- package/dist/services/workspace/reconcile-service.js +3 -220
- package/dist/services/workspace/workspace-service.js +13 -2
- package/dist/shared/incrementing-number.d.ts +11 -0
- package/dist/shared/incrementing-number.js +16 -3
- package/package.json +5 -5
- package/skills/peaks-code/SKILL.md +6 -2
- package/skills/peaks-code/references/fresh-context-preflight.md +81 -0
- package/skills/peaks-code/references/sub-agent-dispatch.md +27 -8
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
export function upgradeRecord(parsed) {
|
|
2
|
+
if (!isObject(parsed)) {
|
|
3
|
+
throw new Error('Dispatch record root must be an object');
|
|
4
|
+
}
|
|
5
|
+
const obj = parsed;
|
|
6
|
+
// Slice 4.0.8: 3.2 → 4.0.0 schema bump. Phase A Task 8: 4.0.0 → 4.1.0
|
|
7
|
+
// (additive). The literal type narrows to '4.1.0' but legacy v4.0.0 /
|
|
8
|
+
// v3.2 / v3.1 / 3 / 2 / 1 records are accepted transparently and
|
|
9
|
+
// upgraded on read.
|
|
10
|
+
const rawVersion = obj.version;
|
|
11
|
+
if (rawVersion !== '4.1.0' && rawVersion !== '4.0.0' && rawVersion !== '3.2' && rawVersion !== '3.1' && rawVersion !== 3 && rawVersion !== 2 && rawVersion !== 1) {
|
|
12
|
+
throw new Error(`Dispatch record version mismatch: expected '4.1.0', '4.0.0', '3.2', '3.1', 3, 2, or 1, got ${JSON.stringify(rawVersion)}. ` +
|
|
13
|
+
'The v1 → v4.1.0 migration is in-file; records from much older or newer builds must be regenerated.');
|
|
14
|
+
}
|
|
15
|
+
const legacy = parseUpgradeRecordLegacyFields(obj);
|
|
16
|
+
const migration = parseUpgradeRecordMigrationFields(obj);
|
|
17
|
+
return {
|
|
18
|
+
version: '4.1.0',
|
|
19
|
+
createdAt: legacy.createdAt,
|
|
20
|
+
completedAt: legacy.completedAt,
|
|
21
|
+
outcome: legacy.outcome,
|
|
22
|
+
artifactPaths: legacy.artifactPaths,
|
|
23
|
+
disposed: legacy.disposed,
|
|
24
|
+
disposedAt: legacy.disposedAt,
|
|
25
|
+
role: legacy.role,
|
|
26
|
+
requestId: legacy.requestId,
|
|
27
|
+
sessionId: legacy.sessionId,
|
|
28
|
+
prompt: legacy.prompt,
|
|
29
|
+
toolCall: legacy.toolCall,
|
|
30
|
+
batchId: legacy.batchId,
|
|
31
|
+
heartbeats: legacy.heartbeats,
|
|
32
|
+
lastBeatAt: legacy.lastBeatAt,
|
|
33
|
+
status: legacy.status,
|
|
34
|
+
stage: migration.stage,
|
|
35
|
+
leaseId: migration.leaseId,
|
|
36
|
+
isolationStartedAt: migration.isolationStartedAt,
|
|
37
|
+
serviceKill: migration.serviceKill,
|
|
38
|
+
mergeBackAttempts: migration.mergeBackAttempts,
|
|
39
|
+
workflowId: migration.workflowId,
|
|
40
|
+
graphNodeId: migration.graphNodeId,
|
|
41
|
+
graphRef: migration.graphRef,
|
|
42
|
+
// Phase A Task 8: detached sub-agent fields. Legacy records
|
|
43
|
+
// (pre-4.1.0) default mode='in-process', vendor=null,
|
|
44
|
+
// autoCompactEvents=[], tokenUsage=null. See
|
|
45
|
+
// parseUpgradeRecordMigrationFields for the per-field
|
|
46
|
+
// validation rules.
|
|
47
|
+
mode: migration.mode,
|
|
48
|
+
vendor: migration.vendor,
|
|
49
|
+
autoCompactEvents: migration.autoCompactEvents,
|
|
50
|
+
tokenUsage: migration.tokenUsage
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse the v1..v3.2 core fields of a legacy dispatch record.
|
|
55
|
+
* PRD-002b slice 6: extracted from `upgradeRecord` so the reader
|
|
56
|
+
* stays under the `max-lines-per-function: 50` ESLint ceiling.
|
|
57
|
+
* Behavior is byte-identical to the previous inline block.
|
|
58
|
+
*/
|
|
59
|
+
function parseUpgradeRecordLegacyFields(obj) {
|
|
60
|
+
const role = stringField(obj, 'role');
|
|
61
|
+
const requestId = stringField(obj, 'requestId');
|
|
62
|
+
const sessionId = stringField(obj, 'sessionId');
|
|
63
|
+
const prompt = stringField(obj, 'prompt');
|
|
64
|
+
// Slice 2026-06-23-audit-4th #C2: preserve toolCallVersion on read.
|
|
65
|
+
// Pre-versioning records default to '2.0.0' (the pre-#C2 implicit
|
|
66
|
+
// shape; matches the version stamped by every current dispatcher).
|
|
67
|
+
const rawToolCall = obj.toolCall;
|
|
68
|
+
if (!isObject(rawToolCall) || typeof rawToolCall.name !== 'string') {
|
|
69
|
+
throw new Error('Dispatch record toolCall must be { name, args }');
|
|
70
|
+
}
|
|
71
|
+
const toolCall = {
|
|
72
|
+
name: rawToolCall.name,
|
|
73
|
+
args: (isObject(rawToolCall.args) ? rawToolCall.args : {}),
|
|
74
|
+
...(typeof rawToolCall.toolCallVersion === 'string' ? { toolCallVersion: rawToolCall.toolCallVersion } : { toolCallVersion: '2.0.0' })
|
|
75
|
+
};
|
|
76
|
+
const createdAt = stringField(obj, 'createdAt');
|
|
77
|
+
const heartbeats = Array.isArray(obj.heartbeats)
|
|
78
|
+
? obj.heartbeats.filter(isValidHeartbeat)
|
|
79
|
+
: [];
|
|
80
|
+
const lastBeatAt = typeof obj.lastBeatAt === 'string' ? obj.lastBeatAt : null;
|
|
81
|
+
// Slice 2026-07-29-dispatch-stall-governance / S1 (UQ-1) — `no-execution`
|
|
82
|
+
// keeps its natural "dispatched, never executed" reading; an unparseable
|
|
83
|
+
// status field now resolves to a *distinct* `unreadable` label so the
|
|
84
|
+
// caller can tell "corrupt record" apart from "record written, no first
|
|
85
|
+
// heartbeat" (which is the new `never-started` state).
|
|
86
|
+
const status = isDispatchStatus(obj.status)
|
|
87
|
+
? obj.status
|
|
88
|
+
: 'unreadable';
|
|
89
|
+
const completedAt = typeof obj.completedAt === 'string' ? obj.completedAt : null;
|
|
90
|
+
const outcome = isOutcome(obj.outcome) ? obj.outcome : 'no-execution';
|
|
91
|
+
const artifactPaths = Array.isArray(obj.artifactPaths)
|
|
92
|
+
? obj.artifactPaths.filter((p) => typeof p === 'string')
|
|
93
|
+
: [];
|
|
94
|
+
const disposed = obj.disposed === true;
|
|
95
|
+
const disposedAt = typeof obj.disposedAt === 'string' ? obj.disposedAt : null;
|
|
96
|
+
const batchId = typeof obj.batchId === 'string' && obj.batchId.length > 0
|
|
97
|
+
? obj.batchId
|
|
98
|
+
: 'legacy-batch';
|
|
99
|
+
return {
|
|
100
|
+
role,
|
|
101
|
+
requestId,
|
|
102
|
+
sessionId,
|
|
103
|
+
prompt,
|
|
104
|
+
toolCall,
|
|
105
|
+
createdAt,
|
|
106
|
+
heartbeats,
|
|
107
|
+
lastBeatAt,
|
|
108
|
+
status,
|
|
109
|
+
completedAt,
|
|
110
|
+
outcome,
|
|
111
|
+
artifactPaths,
|
|
112
|
+
disposed,
|
|
113
|
+
disposedAt,
|
|
114
|
+
batchId
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse the post-v3 migration fields of a legacy dispatch record.
|
|
119
|
+
* PRD-002b slice 6: extracted from `upgradeRecord` so the reader
|
|
120
|
+
* stays under the `max-lines-per-function: 50` ESLint ceiling.
|
|
121
|
+
* Behavior is byte-identical to the previous inline block.
|
|
122
|
+
*/
|
|
123
|
+
function parseUpgradeRecordMigrationFields(obj) {
|
|
124
|
+
return {
|
|
125
|
+
// Slice 2026-07-29-dispatch-stall-governance / S5 (AC-5.1 / PB-2)
|
|
126
|
+
// — legacy records (pre-slice) had no `stage` field. The reader
|
|
127
|
+
// defaults to `null` so the watch surface can tell "no stage ever
|
|
128
|
+
// emitted" apart from "stage: ''" (which is itself a *valid*
|
|
129
|
+
// round-trip through the writer — an empty stage is rejected by
|
|
130
|
+
// `setStage`, but a record that round-tripped through a non-strict
|
|
131
|
+
// tool would land here).
|
|
132
|
+
stage: typeof obj.stage === 'string' && obj.stage.length > 0 ? obj.stage : null,
|
|
133
|
+
// Slice 2026-07-29-worktree-l2-extended Part 3.A: legacy records
|
|
134
|
+
// have no `leaseId`; default to `null` so the auto-release hook
|
|
135
|
+
// in `markCompleted` is a clean no-op for them.
|
|
136
|
+
leaseId: typeof obj.leaseId === 'string' && /^[a-f0-9]{16}$/.test(obj.leaseId)
|
|
137
|
+
? obj.leaseId
|
|
138
|
+
: null,
|
|
139
|
+
// Slice 2026-07-29-worktree-l2-extended Part 7: v3 → v3.1
|
|
140
|
+
// migration. Legacy records have no `isolationStartedAt`; default
|
|
141
|
+
// to `null`. v3.1 readers can treat the field as opt-in.
|
|
142
|
+
isolationStartedAt: typeof obj.isolationStartedAt === 'string' && obj.isolationStartedAt.length > 0
|
|
143
|
+
? obj.isolationStartedAt
|
|
144
|
+
: null,
|
|
145
|
+
// Slice 2026-08-01-subagent-merge-and-e2e (Task 7): v3.1 → v3.2
|
|
146
|
+
// migration. Legacy v3.1 records have no `serviceKill` or
|
|
147
|
+
// `mergeBackAttempts` fields. Default to [] and 0 so the
|
|
148
|
+
// merge-back-runner (Task 9) can read either schema on disk.
|
|
149
|
+
serviceKill: Array.isArray(obj.serviceKill)
|
|
150
|
+
? (obj.serviceKill.filter((e) => {
|
|
151
|
+
if (typeof e !== 'object' || e === null)
|
|
152
|
+
return false;
|
|
153
|
+
const o = e;
|
|
154
|
+
return typeof o.pid === 'number' && typeof o.name === 'string' && typeof o.signal === 'string' && (o.exitCode === null || typeof o.exitCode === 'number');
|
|
155
|
+
}))
|
|
156
|
+
: [],
|
|
157
|
+
mergeBackAttempts: typeof obj.mergeBackAttempts === 'number' && Number.isFinite(obj.mergeBackAttempts) && obj.mergeBackAttempts >= 0
|
|
158
|
+
? Math.floor(obj.mergeBackAttempts)
|
|
159
|
+
: 0,
|
|
160
|
+
// Slice 4.0.8: 3.2 → 4.0.0 migration. v3.2 records on disk
|
|
161
|
+
// pre-date the workflow-graph binding; default all three
|
|
162
|
+
// fields to `null` so a legacy record upgrades transparently.
|
|
163
|
+
workflowId: typeof obj.workflowId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(obj.workflowId) ? obj.workflowId : null,
|
|
164
|
+
graphNodeId: typeof obj.graphNodeId === 'string' && /^[a-zA-Z0-9._-]{1,200}$/.test(obj.graphNodeId) ? obj.graphNodeId : null,
|
|
165
|
+
graphRef: typeof obj.graphRef === 'string' ? obj.graphRef : null,
|
|
166
|
+
// Phase A Task 8: 4.0.0 → 4.1.0 migration. Pre-4.1.0 records
|
|
167
|
+
// have no mode / vendor / autoCompactEvents / tokenUsage
|
|
168
|
+
// fields. Default to safe in-process / null / [] / null so
|
|
169
|
+
// legacy records upgrade transparently without breaking
|
|
170
|
+
// consumers (e.g. the dashboard, the merge-back-runner).
|
|
171
|
+
mode: obj.mode === 'detached' ? 'detached' : 'in-process',
|
|
172
|
+
vendor: obj.vendor === 'claude' || obj.vendor === 'codex' || obj.vendor === 'copilot' ? obj.vendor : null,
|
|
173
|
+
autoCompactEvents: Array.isArray(obj.autoCompactEvents)
|
|
174
|
+
? obj.autoCompactEvents.filter((e) => typeof e?.at === 'number' &&
|
|
175
|
+
(e?.threshold === '0.85' || e?.threshold === '0.95') &&
|
|
176
|
+
typeof e?.tokensBefore === 'number' &&
|
|
177
|
+
typeof e?.tokensAfter === 'number')
|
|
178
|
+
: [],
|
|
179
|
+
tokenUsage: typeof obj.tokenUsage === 'object' && obj.tokenUsage !== null && typeof obj.tokenUsage.promptTokens === 'number' && typeof obj.tokenUsage.completionTokens === 'number'
|
|
180
|
+
? {
|
|
181
|
+
promptTokens: obj.tokenUsage.promptTokens,
|
|
182
|
+
completionTokens: obj.tokenUsage.completionTokens,
|
|
183
|
+
...(typeof obj.tokenUsage.totalCostUsd === 'number'
|
|
184
|
+
? { totalCostUsd: obj.tokenUsage.totalCostUsd }
|
|
185
|
+
: {}),
|
|
186
|
+
}
|
|
187
|
+
: null
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function isObject(v) {
|
|
191
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
192
|
+
}
|
|
193
|
+
function stringField(obj, key) {
|
|
194
|
+
const v = obj[key];
|
|
195
|
+
if (typeof v !== 'string') {
|
|
196
|
+
throw new Error(`Dispatch record field '${key}' must be a string (got ${typeof v})`);
|
|
197
|
+
}
|
|
198
|
+
return v;
|
|
199
|
+
}
|
|
200
|
+
function isValidHeartbeat(v) {
|
|
201
|
+
if (!isObject(v))
|
|
202
|
+
return false;
|
|
203
|
+
return (typeof v.at === 'string' &&
|
|
204
|
+
isHeartbeatStatus(v.status) &&
|
|
205
|
+
typeof v.progress === 'number' &&
|
|
206
|
+
(v.note === null || typeof v.note === 'string'));
|
|
207
|
+
}
|
|
208
|
+
function isHeartbeatStatus(v) {
|
|
209
|
+
return (v === 'queued' || v === 'running' || v === 'finalizing' ||
|
|
210
|
+
v === 'done' || v === 'failed' || v === 'stale' ||
|
|
211
|
+
// Slice 2026-07-29-dispatch-stall-governance / S2 — accept the
|
|
212
|
+
// S1 terminal members so a sub-agent can report `cancelled`,
|
|
213
|
+
// `no-execution`, `never-started`, or `unreadable` through the
|
|
214
|
+
// heartbeat CLI.
|
|
215
|
+
v === 'cancelled' || v === 'no-execution' ||
|
|
216
|
+
v === 'never-started' || v === 'unreadable');
|
|
217
|
+
}
|
|
218
|
+
function isDispatchStatus(v) {
|
|
219
|
+
return (v === 'queued' || v === 'running' || v === 'finalizing' ||
|
|
220
|
+
v === 'done' || v === 'failed' || v === 'cancelled' ||
|
|
221
|
+
v === 'no-execution' || v === 'stale' ||
|
|
222
|
+
// Slice 2026-07-29-dispatch-stall-governance / S1 — accept the two
|
|
223
|
+
// new terminal members from the startup-timeout service.
|
|
224
|
+
v === 'never-started' || v === 'unreadable');
|
|
225
|
+
}
|
|
226
|
+
function isOutcome(v) {
|
|
227
|
+
return (v === 'success' || v === 'failed' || v === 'timeout' ||
|
|
228
|
+
v === 'cancelled' || v === 'no-execution');
|
|
229
|
+
}
|
|
230
|
+
export { isDispatchStatus, isOutcome };
|
|
@@ -1,279 +1,7 @@
|
|
|
1
|
-
import type { SubAgentToolCall } from './sub-agent-dispatcher.js';
|
|
2
1
|
import { type StageLabel } from './stage-enum.js';
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
readonly status: HeartbeatStatus;
|
|
7
|
-
readonly progress: number;
|
|
8
|
-
readonly note: string | null;
|
|
9
|
-
}
|
|
10
|
-
export type HeartbeatStatus = 'queued' | 'running' | 'finalizing' | 'done' | 'failed' | 'stale' | 'cancelled' | 'no-execution' | 'never-started' | 'unreadable';
|
|
11
|
-
export type DispatchRecordStatus = 'queued' | 'running' | 'finalizing' | 'done' | 'failed' | 'cancelled' | 'no-execution' | 'stale' | 'never-started' | 'unreadable';
|
|
12
|
-
export type DispatchOutcome = 'success' | 'failed' | 'timeout' | 'cancelled' | 'no-execution';
|
|
13
|
-
/** G2+G5+G6 dispatch record schema (AC-26 + AC-34). */
|
|
14
|
-
export interface DispatchRecord {
|
|
15
|
-
/**
|
|
16
|
-
* Slice 2026-07-29-worktree-l2-extended Part 4.C: schema v3 makes
|
|
17
|
-
* `leaseId` a structurally required field (was `leaseId?: string | null`
|
|
18
|
-
* in v2). The v3 upgrade is a "fill in" migration: every dispatch
|
|
19
|
-
* writer knows its lease id at construction time (Part 2.C's
|
|
20
|
-
* --isolation worktree spawns it; non-isolation dispatches stamp
|
|
21
|
-
* `null`). Readers tolerate both v2 and v3 on disk (see
|
|
22
|
-
* `upgradeRecord`); the `?` was a Part 4.A ergonomic concession
|
|
23
|
-
* to keep 4 unit-test literal sites from breaking the build. v3
|
|
24
|
-
* moves the optional off the type and adds the field to the
|
|
25
|
-
* 4 literal sites in one pass.
|
|
26
|
-
*
|
|
27
|
-
* Slice 2026-07-29-worktree-l2-extended Part 7: schema v3.1 adds
|
|
28
|
-
* `isolationStartedAt: string | null` for the L4 isolation
|
|
29
|
-
* bridge (Part 8 container POC). It's the ISO timestamp of
|
|
30
|
-
* when the isolation mode was set up (e.g. when the worktree
|
|
31
|
-
* was spawned). `null` means the dispatch did not request
|
|
32
|
-
* isolation. The `version` field stays at 3 because the
|
|
33
|
-
* v3 → v3.1 transition is additive; the new field is
|
|
34
|
-
* defaulted to `null` on read so v3 records upgrade cleanly.
|
|
35
|
-
*/
|
|
36
|
-
/**
|
|
37
|
-
* Phase A Task 8: schema bumped to v4.1.0 (additive). The bump
|
|
38
|
-
* is purely additive — new fields (`mode`, `vendor`,
|
|
39
|
-
* `autoCompactEvents`, `tokenUsage`) all default safely on
|
|
40
|
-
* read for legacy v4.0.0 / v3.2 / v3.1 / v3 / v2 / v1 records.
|
|
41
|
-
* No existing field semantics changed.
|
|
42
|
-
*/
|
|
43
|
-
readonly version: '4.1.0';
|
|
44
|
-
readonly createdAt: string;
|
|
45
|
-
readonly completedAt: string | null;
|
|
46
|
-
readonly outcome: DispatchOutcome;
|
|
47
|
-
readonly artifactPaths: readonly string[];
|
|
48
|
-
readonly disposed: boolean;
|
|
49
|
-
readonly disposedAt: string | null;
|
|
50
|
-
readonly role: string;
|
|
51
|
-
readonly requestId: string;
|
|
52
|
-
readonly sessionId: string;
|
|
53
|
-
readonly prompt: string;
|
|
54
|
-
readonly toolCall: SubAgentToolCall;
|
|
55
|
-
/** G5 batch id (AC-27) — uuid-like opaque token grouping one batch. */
|
|
56
|
-
readonly batchId: string;
|
|
57
|
-
/** G6 fields (AC-34) — backward compat: defaults on read. */
|
|
58
|
-
readonly heartbeats: readonly Heartbeat[];
|
|
59
|
-
readonly lastBeatAt: string | null;
|
|
60
|
-
readonly status: DispatchRecordStatus;
|
|
61
|
-
readonly stage: string | null;
|
|
62
|
-
/**
|
|
63
|
-
* Slice 2026-07-29-worktree-l2-extended Part 3.A + Part 4.C: the
|
|
64
|
-
* worktree lease id stamped on this dispatch (via `peaks sub-agent
|
|
65
|
-
* dispatch --isolation worktree`). The release hook (see
|
|
66
|
-
* markCompleted + `peaks sub-agent heartbeat --status done`)
|
|
67
|
-
* reads this field to auto-call `peaks worktree release` when
|
|
68
|
-
* the sub-agent finalizes. `null` means the dispatch did not
|
|
69
|
-
* request isolation and no release will fire. Persisted for
|
|
70
|
-
* audit + idempotency so a re-read of an old record still
|
|
71
|
-
* surfaces the lease id even if the on-disk lease file has since
|
|
72
|
-
* been gc'd.
|
|
73
|
-
*
|
|
74
|
-
* v3 (Part 4.C) makes this structurally required. v2 records
|
|
75
|
-
* missing the field upgrade to `null` on read (see
|
|
76
|
-
* `upgradeRecord`).
|
|
77
|
-
*/
|
|
78
|
-
readonly leaseId: string | null;
|
|
79
|
-
/**
|
|
80
|
-
* Slice 2026-07-29-worktree-l2-extended Part 7: ISO timestamp of
|
|
81
|
-
* when the isolation mode was set up. For `--isolation worktree`
|
|
82
|
-
* this is the moment `peaks worktree spawn` returned; for
|
|
83
|
-
* `--isolation container` (Part 8) it's when the container
|
|
84
|
-
* runtime reported the container as running. `null` when the
|
|
85
|
-
* dispatch did not request isolation. Lets the dashboard
|
|
86
|
-
* compute isolation duration (now - isolationStartedAt) without
|
|
87
|
-
* cross-referencing the metrics stream.
|
|
88
|
-
*/
|
|
89
|
-
readonly isolationStartedAt: string | null;
|
|
90
|
-
/**
|
|
91
|
-
* Slice 2026-08-01-subagent-merge-and-e2e (Task 7): v3.2 schema
|
|
92
|
-
* bump. One entry per pid the parent best-effort-killed during
|
|
93
|
-
* the service-shutdown phase of the merge-back pipeline (see
|
|
94
|
-
* src/services/dispatch/service-shutdown.ts). Empty array when
|
|
95
|
-
* the sub-agent did not register any services. The shape is the
|
|
96
|
-
* union of ServiceKillOutcome (skipped=false) and
|
|
97
|
-
* ServiceKillSkipped (skipped=true) — kept as a plain object
|
|
98
|
-
* here so the on-disk schema does not lock onto the helper's
|
|
99
|
-
* narrower union. The reader (merge-back-runner.ts) interprets
|
|
100
|
-
* each entry based on the `skipped` field.
|
|
101
|
-
*/
|
|
102
|
-
readonly serviceKill: ReadonlyArray<{
|
|
103
|
-
readonly pid: number;
|
|
104
|
-
readonly name: string;
|
|
105
|
-
readonly signal: string;
|
|
106
|
-
readonly exitCode: number | null;
|
|
107
|
-
readonly skipped?: boolean;
|
|
108
|
-
readonly reason?: string;
|
|
109
|
-
}>;
|
|
110
|
-
/**
|
|
111
|
-
* Slice 2026-08-01-subagent-merge-and-e2e (Task 7): v3.2 schema
|
|
112
|
-
* bump. Counts how many merge attempts the parent session has
|
|
113
|
-
* made against this dispatch's branch. The conflict-replay
|
|
114
|
-
* orchestrator bumps this on each retry (bounded to ONE re-dispatch
|
|
115
|
-
* per merge attempt; multi-conflict cases escalate). Persisted so
|
|
116
|
-
* the dashboard can render the retry count without replaying the
|
|
117
|
-
* merge transcript.
|
|
118
|
-
*/
|
|
119
|
-
readonly mergeBackAttempts: number;
|
|
120
|
-
/**
|
|
121
|
-
* Slice 4.0.8 RD §4 D4c (presence-lease-graph): the workflow id +
|
|
122
|
-
* graph node id + graphRef this dispatch is bound to. Persisted
|
|
123
|
-
* directly in the dispatch record (NOT in a sidecar) so the
|
|
124
|
-
* envelope-writer `markCompleted` can auto-transition the bound
|
|
125
|
-
* graph node to `envelope-received` with `ackStatus=pending` in
|
|
126
|
-
* one protected update.
|
|
127
|
-
*
|
|
128
|
-
* The schema bump from `3.2 → 4.0.0` is BREAKING in the sense
|
|
129
|
-
* that the literal type is narrowed. The optional `?` keeps back-
|
|
130
|
-
* compat for old records that pre-date the binding (the
|
|
131
|
-
* `upgradeRecord` reader defaults them to `null`).
|
|
132
|
-
*/
|
|
133
|
-
readonly workflowId: string | null;
|
|
134
|
-
readonly graphNodeId: string | null;
|
|
135
|
-
readonly graphRef: string | null;
|
|
136
|
-
/**
|
|
137
|
-
* Phase A Task 8: dispatch execution mode. `in-process` is the
|
|
138
|
-
* current behavior (LLM-side runner, no separate OS process).
|
|
139
|
-
* `detached` is the new path: `peaks sub-agent dispatch --mode
|
|
140
|
-
* detached` spawns a real child OS process running a different
|
|
141
|
-
* LLM vendor (claude / codex / copilot) and reports back via
|
|
142
|
-
* the dispatch record. v4.1.0 is the additive bump; legacy v4.0.0
|
|
143
|
-
* records upgrade to `in-process` on read.
|
|
144
|
-
*/
|
|
145
|
-
readonly mode: 'in-process' | 'detached';
|
|
146
|
-
/**
|
|
147
|
-
* Phase A Task 8: vendor id when `mode='detached'`. `null` when
|
|
148
|
-
* the dispatch is in-process. Reserved for future use; current
|
|
149
|
-
* detached sub-agents use `claude` but the schema also accepts
|
|
150
|
-
* `codex` and `copilot` for the vendor-neutral adapter layer.
|
|
151
|
-
*/
|
|
152
|
-
readonly vendor: 'claude' | 'codex' | 'copilot' | null;
|
|
153
|
-
/**
|
|
154
|
-
* Phase A Task 8: G8 autoCompact events accumulated by the child
|
|
155
|
-
* LLM during a detached run. Each event records the threshold
|
|
156
|
-
* that fired (0.85 = first warning, 0.95 = second warning) plus
|
|
157
|
-
* token counts before/after. Empty for in-process dispatches
|
|
158
|
-
* and for legacy records upgraded on read.
|
|
159
|
-
*/
|
|
160
|
-
readonly autoCompactEvents: ReadonlyArray<{
|
|
161
|
-
readonly at: number;
|
|
162
|
-
readonly threshold: '0.85' | '0.95';
|
|
163
|
-
readonly tokensBefore: number;
|
|
164
|
-
readonly tokensAfter: number;
|
|
165
|
-
readonly scratchFile?: string;
|
|
166
|
-
}>;
|
|
167
|
-
/**
|
|
168
|
-
* Phase A Task 8: G8 token-usage accounting for detached sub-agents.
|
|
169
|
-
* Detached runs have "unlimited spend but recorded" semantics —
|
|
170
|
-
* the cost is recorded for audit but not enforced. `null` when
|
|
171
|
-
* the dispatch is in-process (no detached accounting) or for
|
|
172
|
-
* legacy records upgraded on read.
|
|
173
|
-
*/
|
|
174
|
-
readonly tokenUsage: {
|
|
175
|
-
readonly promptTokens: number;
|
|
176
|
-
readonly completionTokens: number;
|
|
177
|
-
readonly totalCostUsd?: number;
|
|
178
|
-
} | null;
|
|
179
|
-
}
|
|
180
|
-
/** Input for the initial write. */
|
|
181
|
-
export type WriteInitialDispatchInput = {
|
|
182
|
-
projectRoot: string;
|
|
183
|
-
sessionId: string;
|
|
184
|
-
requestId: string;
|
|
185
|
-
role: string;
|
|
186
|
-
prompt: string;
|
|
187
|
-
toolCall: SubAgentToolCall;
|
|
188
|
-
batchId: string;
|
|
189
|
-
/** Override the timestamp (testing). */
|
|
190
|
-
now?: () => Date;
|
|
191
|
-
/**
|
|
192
|
-
* Slice 2026-07-29-worktree-l2-extended Part 3.A: the worktree
|
|
193
|
-
* lease id this dispatch owns (set by `peaks sub-agent dispatch
|
|
194
|
-
* --isolation worktree`). Persisted so the finalize-time release
|
|
195
|
-
* hook in `markCompleted` can fire even after the dispatch
|
|
196
|
-
* process exits. Optional; absent when the dispatch did not
|
|
197
|
-
* request isolation.
|
|
198
|
-
*/
|
|
199
|
-
leaseId?: string | null;
|
|
200
|
-
/**
|
|
201
|
-
* Slice 2026-07-29-worktree-l2-extended Part 7: ISO timestamp
|
|
202
|
-
* when the isolation mode was set up. Optional on the input
|
|
203
|
-
* (defaults to `null`); dispatch-commands.ts passes the spawn
|
|
204
|
-
* time when `--isolation` is requested.
|
|
205
|
-
*/
|
|
206
|
-
isolationStartedAt?: string | null;
|
|
207
|
-
/**
|
|
208
|
-
* Slice 4.0.8: workflow graph binding for the dispatch. Defaults
|
|
209
|
-
* to `null` so a non-graph dispatch (legacy CLI flow, ad-hoc
|
|
210
|
-
* dispatch) still writes a v4.0.0 record.
|
|
211
|
-
*/
|
|
212
|
-
workflowId?: string | null;
|
|
213
|
-
graphNodeId?: string | null;
|
|
214
|
-
graphRef?: string | null;
|
|
215
|
-
/**
|
|
216
|
-
* Phase A Task 8: dispatch execution mode. Default `'in-process'`
|
|
217
|
-
* preserves the current LLM-side runner behavior. `'detached'`
|
|
218
|
-
* triggers the new real-OS-process path via
|
|
219
|
-
* `peaks sub-agent dispatch --mode detached`.
|
|
220
|
-
*/
|
|
221
|
-
mode?: 'in-process' | 'detached';
|
|
222
|
-
/**
|
|
223
|
-
* Phase A Task 8: vendor id when `mode='detached'`. Required by
|
|
224
|
-
* the adapter layer to know which CLI / runtime to spawn. The
|
|
225
|
-
* schema accepts the three vendors peaks-loop has adapters for
|
|
226
|
-
* (claude / codex / copilot). Ignored when `mode='in-process'`.
|
|
227
|
-
*/
|
|
228
|
-
vendor?: 'claude' | 'codex' | 'copilot';
|
|
229
|
-
/**
|
|
230
|
-
* Phase A Task 8: G8 autoCompact events accumulated by the child
|
|
231
|
-
* LLM. Optional on input — most dispatches start with an empty
|
|
232
|
-
* array and the detached runner appends events as they fire.
|
|
233
|
-
*/
|
|
234
|
-
autoCompactEvents?: Array<{
|
|
235
|
-
at: number;
|
|
236
|
-
threshold: '0.85' | '0.95';
|
|
237
|
-
tokensBefore: number;
|
|
238
|
-
tokensAfter: number;
|
|
239
|
-
scratchFile?: string;
|
|
240
|
-
}>;
|
|
241
|
-
/**
|
|
242
|
-
* Phase A Task 8: G8 token-usage accounting. Detached runs
|
|
243
|
-
* record spend for audit (unlimited, but persisted). Optional
|
|
244
|
-
* on input; the detached runner fills this in as it streams
|
|
245
|
-
* usage from the vendor API.
|
|
246
|
-
*/
|
|
247
|
-
tokenUsage?: {
|
|
248
|
-
promptTokens: number;
|
|
249
|
-
completionTokens: number;
|
|
250
|
-
totalCostUsd?: number;
|
|
251
|
-
};
|
|
252
|
-
};
|
|
253
|
-
/** Heartbeat write input. */
|
|
254
|
-
export type AppendHeartbeatInput = {
|
|
255
|
-
recordPath: string;
|
|
256
|
-
status: HeartbeatStatus;
|
|
257
|
-
progress: number;
|
|
258
|
-
note?: string;
|
|
259
|
-
now?: () => Date;
|
|
260
|
-
};
|
|
261
|
-
/** Lifecycle transition input. */
|
|
262
|
-
export type LifecycleInput = {
|
|
263
|
-
recordPath: string;
|
|
264
|
-
outcome: DispatchOutcome;
|
|
265
|
-
status: DispatchRecordStatus;
|
|
266
|
-
artifactPaths?: readonly string[];
|
|
267
|
-
now?: () => Date;
|
|
268
|
-
/**
|
|
269
|
-
* Slice 2026-06-23-audit-4th #A4: trusted project root. Required
|
|
270
|
-
* so the active-dispatches index can be updated without deriving
|
|
271
|
-
* the root from the recordPath (the same anti-pattern that
|
|
272
|
-
* audit-3rd #1 fixed for heartbeat). The CLI / LLM-side runner
|
|
273
|
-
* passes this from `--project` or `process.cwd()`.
|
|
274
|
-
*/
|
|
275
|
-
projectRoot?: string;
|
|
276
|
-
};
|
|
2
|
+
import type { AppendHeartbeatInput, DispatchRecord, Heartbeat, LifecycleInput, WriteInitialDispatchInput } from './dispatch-record-types.js';
|
|
3
|
+
export type { AppendHeartbeatInput, DispatchOutcome, DispatchRecord, DispatchRecordStatus, Heartbeat, HeartbeatStatus, LifecycleInput, WriteInitialDispatchInput } from './dispatch-record-types.js';
|
|
4
|
+
export { isDispatchStatus, isOutcome } from './dispatch-record-upgrade.js';
|
|
277
5
|
/** Write a new dispatch record (G2 + G5 + G6). Returns the absolute path. */
|
|
278
6
|
export declare function writeInitialDispatchRecord(input: WriteInitialDispatchInput): {
|
|
279
7
|
path: string;
|
|
@@ -386,6 +114,3 @@ export declare function setStage(input: {
|
|
|
386
114
|
export declare function readRecord(recordPath: string): DispatchRecord;
|
|
387
115
|
/** Read multiple records from a list of paths. Tolerates missing files. */
|
|
388
116
|
export declare function readRecords(paths: readonly string[]): DispatchRecord[];
|
|
389
|
-
declare function isDispatchStatus(v: unknown): v is DispatchRecordStatus;
|
|
390
|
-
declare function isOutcome(v: unknown): v is DispatchOutcome;
|
|
391
|
-
export { isDispatchStatus, isOutcome };
|