@adhdev/daemon-core 0.9.82-rc.209 → 0.9.82-rc.210
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/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +2 -0
- package/dist/commands/router.d.ts +6 -0
- package/dist/git/git-commands.d.ts +2 -0
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +5699 -3486
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5679 -3483
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +6 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +5 -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 -198
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +19 -0
- package/dist/mesh/mesh-missions.d.ts +58 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-runtime-store.d.ts +175 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-work-queue.d.ts +82 -0
- package/dist/mesh/refine-config.d.ts +24 -2
- package/dist/mesh/worktree-bootstrap-config.d.ts +22 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/spec/driver.d.ts +8 -0
- package/dist/providers/spec/evaluator.d.ts +4 -5
- package/dist/providers/spec/loader.d.ts +1 -0
- package/dist/providers/spec/schema.gen.d.ts +1409 -6
- package/dist/providers/spec/types.d.ts +188 -175
- package/dist/repo-mesh-types.d.ts +1 -0
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +3 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +20 -7
- package/src/commands/router.ts +594 -66
- package/src/git/git-commands.ts +5 -5
- package/src/git/git-diff.ts +53 -0
- package/src/index.ts +11 -5
- package/src/mesh/coordinator-prompt.ts +14 -1
- package/src/mesh/mesh-delivery-policy.ts +17 -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 +27 -2143
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +134 -2
- package/src/mesh/mesh-missions.ts +151 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-runtime-store.ts +539 -3
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-work-queue.ts +233 -17
- package/src/mesh/refine-config.ts +42 -5
- package/src/mesh/worktree-bootstrap-config.ts +79 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/cli-provider-instance.ts +34 -13
- package/src/providers/spec/driver.ts +57 -29
- package/src/providers/spec/evaluator.ts +302 -112
- package/src/providers/spec/loader.ts +226 -37
- package/src/providers/spec/schema.gen.ts +450 -334
- package/src/providers/spec/schema.json +162 -75
- package/src/providers/spec/types.ts +234 -183
- package/src/repo-mesh-types.ts +1 -0
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* spec.json loader + strict validator.
|
|
3
|
-
* conform to schema.json — unknown fields, missing required fields,
|
|
4
|
-
* wrong types, regex strings that don't compile, state references
|
|
5
|
-
* that don't exist, etc.
|
|
2
|
+
* spec.json loader + strict validator.
|
|
6
3
|
*
|
|
7
|
-
*
|
|
4
|
+
* Supports both v1 (adhdev:cli/spec@1) and v3 (adhdev:cli/spec@3).
|
|
5
|
+
* v1 specs are auto-migrated to v3 before validation.
|
|
6
|
+
* Rejects spec files that don't conform to the schema.
|
|
8
7
|
*/
|
|
9
8
|
'use strict';
|
|
10
9
|
|
|
11
10
|
import * as fs from 'node:fs';
|
|
12
11
|
import * as path from 'node:path';
|
|
13
12
|
import Ajv from 'ajv';
|
|
14
|
-
import type { CliSpec } from './types.js';
|
|
15
|
-
import {
|
|
13
|
+
import type { CliSpec, SectionDef, Condition, ExtractTitle, ExtractButtons } from './types.js';
|
|
14
|
+
import { SCHEMA_V1, SCHEMA_V3 } from './schema.gen.js';
|
|
16
15
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
16
|
+
const ajvV1 = new Ajv({ allErrors: true, strict: false });
|
|
17
|
+
const validateV1 = ajvV1.compile(SCHEMA_V1);
|
|
18
|
+
|
|
19
|
+
const ajvV3 = new Ajv({ allErrors: true, strict: false });
|
|
20
|
+
const validateV3 = ajvV3.compile<CliSpec>(SCHEMA_V3);
|
|
19
21
|
|
|
20
22
|
export interface SpecLoadResult {
|
|
21
23
|
ok: true;
|
|
@@ -38,8 +40,19 @@ export function loadSpec(sourcePath: string): SpecLoadResult | SpecLoadError {
|
|
|
38
40
|
return { ok: false, errors: [`Failed to read spec: ${(err as Error).message}`], sourcePath };
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Auto-migrate v1 → v3
|
|
44
|
+
const schemaStr = (raw as any)?.$schema;
|
|
45
|
+
if (schemaStr === 'adhdev:cli/spec@1') {
|
|
46
|
+
// Validate as v1 first
|
|
47
|
+
if (!validateV1(raw)) {
|
|
48
|
+
const errors = (validateV1.errors || []).map(e => `${e.instancePath || '(root)'} ${e.message ?? 'invalid'} (v1 parse)`);
|
|
49
|
+
return { ok: false, errors, sourcePath };
|
|
50
|
+
}
|
|
51
|
+
raw = migrateV1toV3(raw as any);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!validateV3(raw)) {
|
|
55
|
+
const errors = (validateV3.errors || []).map(e => `${e.instancePath || '(root)'} ${e.message ?? 'invalid'}`);
|
|
43
56
|
return { ok: false, errors, sourcePath };
|
|
44
57
|
}
|
|
45
58
|
|
|
@@ -47,19 +60,178 @@ export function loadSpec(sourcePath: string): SpecLoadResult | SpecLoadError {
|
|
|
47
60
|
const extra = validateRefs(spec);
|
|
48
61
|
if (extra.length > 0) return { ok: false, errors: extra, sourcePath };
|
|
49
62
|
|
|
63
|
+
// Populate legacy `debounce` alias from `timing` for backward compat
|
|
64
|
+
attachDebounceAlias(spec);
|
|
65
|
+
|
|
50
66
|
return { ok: true, spec, sourcePath };
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* - default_state exists
|
|
58
|
-
* - regex / pattern strings compile
|
|
70
|
+
* Populate spec.debounce as a backward-compat alias for spec.timing.
|
|
71
|
+
* The driver and tests read debounce.{busy_hold_ms, idle_hold_ms, ...}.
|
|
72
|
+
* After this call both fields point at equivalent data.
|
|
59
73
|
*/
|
|
74
|
+
function attachDebounceAlias(spec: CliSpec): void {
|
|
75
|
+
const t = spec.timing;
|
|
76
|
+
if (!t) return;
|
|
77
|
+
const cm = t.completion_marker;
|
|
78
|
+
(spec as any).debounce = {
|
|
79
|
+
...(t.busy_hold_ms !== undefined ? { busy_hold_ms: t.busy_hold_ms } : {}),
|
|
80
|
+
...(t.idle_hold_ms !== undefined ? { idle_hold_ms: t.idle_hold_ms } : {}),
|
|
81
|
+
...(t.startup_grace_ms !== undefined ? { startup_grace_ms: t.startup_grace_ms } : {}),
|
|
82
|
+
...(cm ? {
|
|
83
|
+
completion_idle_after: {
|
|
84
|
+
...(cm.section ? { section: cm.section } : {}),
|
|
85
|
+
regex: cm.matches,
|
|
86
|
+
...(cm.flags ? { flags: cm.flags } : {}),
|
|
87
|
+
hold_ms: cm.hold_ms,
|
|
88
|
+
...(cm.force_after_ms !== undefined ? { force_after_ms: cm.force_after_ms } : {}),
|
|
89
|
+
},
|
|
90
|
+
} : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
// Migration: v1 → v3
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
export function migrateV1toV3(raw: any): any {
|
|
99
|
+
// sections: array → object
|
|
100
|
+
const sections: Record<string, SectionDef> = {};
|
|
101
|
+
for (const sec of raw.layout?.sections ?? []) {
|
|
102
|
+
const { id, anchor_regex, until_regex, until_regex_flags, ...rest } = sec;
|
|
103
|
+
const sectionDef: any = { ...rest };
|
|
104
|
+
|
|
105
|
+
// anchor_regex → anchor
|
|
106
|
+
if (anchor_regex) sectionDef.anchor = anchor_regex;
|
|
107
|
+
|
|
108
|
+
// until: { section: id } → until: id (section reference)
|
|
109
|
+
if (rest.until?.section) {
|
|
110
|
+
sectionDef.until = rest.until.section;
|
|
111
|
+
} else {
|
|
112
|
+
delete sectionDef.until;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// keep until_regex for anchor-based extension
|
|
116
|
+
if (until_regex) sectionDef.until_regex = until_regex;
|
|
117
|
+
if (until_regex_flags) sectionDef.until_regex_flags = until_regex_flags;
|
|
118
|
+
|
|
119
|
+
sections[id] = sectionDef;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// states: convert when/extract_title/modal_buttons
|
|
123
|
+
const states = (raw.states ?? []).map((s: any) => {
|
|
124
|
+
// Convert when: SectionRegex → when: AllCondition | AnyCondition
|
|
125
|
+
let when: any;
|
|
126
|
+
if (s.when?.cursor_above_lines && s.when?.changed) {
|
|
127
|
+
// v1 delta detection condition
|
|
128
|
+
when = {
|
|
129
|
+
any: [
|
|
130
|
+
{ cursor_above: s.when.cursor_above_lines, changed: true as const },
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
} else if (s.when?.regex) {
|
|
134
|
+
const regexCond: any = {
|
|
135
|
+
section: s.when.section,
|
|
136
|
+
matches: s.when.regex,
|
|
137
|
+
...(s.when.flags ? { flags: s.when.flags } : {}),
|
|
138
|
+
...(s.when.cursor_row_min !== undefined ? { cursor_row_min: s.when.cursor_row_min } : {}),
|
|
139
|
+
...(s.when.cursor_row_max !== undefined ? { cursor_row_max: s.when.cursor_row_max } : {}),
|
|
140
|
+
...(s.when.cursor_col_min !== undefined ? { cursor_col_min: s.when.cursor_col_min } : {}),
|
|
141
|
+
...(s.when.cursor_col_max !== undefined ? { cursor_col_max: s.when.cursor_col_max } : {}),
|
|
142
|
+
};
|
|
143
|
+
if (!regexCond.section) delete regexCond.section;
|
|
144
|
+
when = { all: [regexCond] };
|
|
145
|
+
} else {
|
|
146
|
+
// No regex — empty all (always matches)
|
|
147
|
+
when = { all: [] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const extract: any = {};
|
|
151
|
+
if (s.extract_title) {
|
|
152
|
+
if (s.extract_title.first_line) {
|
|
153
|
+
extract.title = { section: s.extract_title.section, first_line: true as const };
|
|
154
|
+
if (!extract.title.section) delete extract.title.section;
|
|
155
|
+
} else if (s.extract_title.regex) {
|
|
156
|
+
extract.title = {
|
|
157
|
+
section: s.extract_title.section,
|
|
158
|
+
regex: s.extract_title.regex,
|
|
159
|
+
...(s.extract_title.flags ? { flags: s.extract_title.flags } : {}),
|
|
160
|
+
};
|
|
161
|
+
if (!extract.title.section) delete extract.title.section;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (s.modal_buttons) {
|
|
165
|
+
const mb = s.modal_buttons;
|
|
166
|
+
// Use first pattern from patterns[] array, or single pattern
|
|
167
|
+
const pat = mb.patterns?.length ? mb.patterns[0].pattern : mb.pattern;
|
|
168
|
+
const flg = mb.patterns?.length ? mb.patterns[0].flags : mb.flags;
|
|
169
|
+
if (pat) {
|
|
170
|
+
extract.buttons = {
|
|
171
|
+
...(mb.section ? { section: mb.section } : {}),
|
|
172
|
+
pattern: pat,
|
|
173
|
+
...(flg ? { flags: flg } : {}),
|
|
174
|
+
key_for_index: mb.key_for_index,
|
|
175
|
+
...(mb.min_count !== undefined ? { min_count: mb.min_count } : {}),
|
|
176
|
+
...(mb.continuation_lines !== undefined ? { continuation_lines: mb.continuation_lines } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
id: s.id,
|
|
183
|
+
label: s.label,
|
|
184
|
+
when,
|
|
185
|
+
...(Object.keys(extract).length > 0 ? { extract } : {}),
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// timing (from debounce)
|
|
190
|
+
const d = raw.debounce ?? {};
|
|
191
|
+
const timing: any = {};
|
|
192
|
+
if (d.busy_hold_ms !== undefined) timing.busy_hold_ms = d.busy_hold_ms;
|
|
193
|
+
if (d.idle_hold_ms !== undefined) timing.idle_hold_ms = d.idle_hold_ms;
|
|
194
|
+
if (d.startup_grace_ms !== undefined) timing.startup_grace_ms = d.startup_grace_ms;
|
|
195
|
+
if (d.completion_idle_after) {
|
|
196
|
+
timing.completion_marker = {
|
|
197
|
+
...(d.completion_idle_after.section ? { section: d.completion_idle_after.section } : {}),
|
|
198
|
+
matches: d.completion_idle_after.regex,
|
|
199
|
+
...(d.completion_idle_after.flags ? { flags: d.completion_idle_after.flags } : {}),
|
|
200
|
+
hold_ms: d.completion_idle_after.hold_ms,
|
|
201
|
+
...(d.completion_idle_after.force_after_ms !== undefined
|
|
202
|
+
? { force_after_ms: d.completion_idle_after.force_after_ms } : {}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
$schema: 'adhdev:cli/spec@3',
|
|
208
|
+
id: raw.id,
|
|
209
|
+
name: raw.name,
|
|
210
|
+
binary: raw.binary,
|
|
211
|
+
...(raw.cli_version_range ? { cli_version_range: raw.cli_version_range } : {}),
|
|
212
|
+
...(raw.spawn_args ? { spawn_args: raw.spawn_args } : {}),
|
|
213
|
+
...(raw.env ? { env: raw.env } : {}),
|
|
214
|
+
send_message: raw.send_message,
|
|
215
|
+
sections,
|
|
216
|
+
states,
|
|
217
|
+
default_state: raw.default_state,
|
|
218
|
+
...(Object.keys(timing).length > 0 ? { timing } : {}),
|
|
219
|
+
...(raw.control_bar ? { control_bar: raw.control_bar } : {}),
|
|
220
|
+
...(raw.notifications ? { notifications: raw.notifications } : {}),
|
|
221
|
+
...(raw.delegate ? { delegate: raw.delegate } : {}),
|
|
222
|
+
...(raw.native_history ? { native_history: raw.native_history } : {}),
|
|
223
|
+
...(raw.requiresFinalAssistantBeforeIdle
|
|
224
|
+
? { requiresFinalAssistantBeforeIdle: raw.requiresFinalAssistantBeforeIdle } : {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// Cross-field validation
|
|
230
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
60
232
|
function validateRefs(spec: CliSpec): string[] {
|
|
61
233
|
const errs: string[] = [];
|
|
62
|
-
const sectionIds = new Set(spec.
|
|
234
|
+
const sectionIds = new Set(Object.keys(spec.sections));
|
|
63
235
|
const stateIds = new Set(spec.states.map(s => s.id));
|
|
64
236
|
|
|
65
237
|
if (!stateIds.has(spec.default_state)) {
|
|
@@ -67,22 +239,21 @@ function validateRefs(spec: CliSpec): string[] {
|
|
|
67
239
|
}
|
|
68
240
|
|
|
69
241
|
for (const s of spec.states) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (s.modal_buttons) {
|
|
77
|
-
if (s.modal_buttons.section && !sectionIds.has(s.modal_buttons.section)) {
|
|
78
|
-
errs.push(`states[${s.id}].modal_buttons.section "${s.modal_buttons.section}" unknown`);
|
|
242
|
+
validateConditionRefs(s.when, `states[${s.id}].when`, sectionIds, errs);
|
|
243
|
+
const ext = s.extract;
|
|
244
|
+
if (ext?.title) {
|
|
245
|
+
if (ext.title.section && !sectionIds.has(ext.title.section)) {
|
|
246
|
+
errs.push(`states[${s.id}].extract.title.section "${ext.title.section}" unknown`);
|
|
79
247
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
248
|
+
if (ext.title.regex) {
|
|
249
|
+
compileRegexCheck(ext.title.regex, ext.title.flags, `states[${s.id}].extract.title.regex`, errs);
|
|
82
250
|
}
|
|
83
|
-
|
|
84
|
-
|
|
251
|
+
}
|
|
252
|
+
if (ext?.buttons) {
|
|
253
|
+
if (ext.buttons.section && !sectionIds.has(ext.buttons.section)) {
|
|
254
|
+
errs.push(`states[${s.id}].extract.buttons.section "${ext.buttons.section}" unknown`);
|
|
85
255
|
}
|
|
256
|
+
compileRegexCheck(ext.buttons.pattern, ext.buttons.flags ?? 'm', `states[${s.id}].extract.buttons.pattern`, errs);
|
|
86
257
|
}
|
|
87
258
|
}
|
|
88
259
|
|
|
@@ -91,12 +262,16 @@ function validateRefs(spec: CliSpec): string[] {
|
|
|
91
262
|
if (!stateIds.has(stId)) errs.push(`control_bar[${c.id}].visible_when_state references unknown state "${stId}"`);
|
|
92
263
|
}
|
|
93
264
|
if (c.action.type === 'open_picker') {
|
|
94
|
-
|
|
95
|
-
|
|
265
|
+
if (c.action.wait_for.section && !sectionIds.has(c.action.wait_for.section ?? '')) {
|
|
266
|
+
errs.push(`control_bar[${c.id}].action.wait_for.section "${c.action.wait_for.section}" unknown`);
|
|
267
|
+
}
|
|
268
|
+
if (c.action.wait_for.regex) {
|
|
269
|
+
compileRegexCheck(c.action.wait_for.regex, c.action.wait_for.flags, `control_bar[${c.id}].action.wait_for.regex`, errs);
|
|
270
|
+
}
|
|
96
271
|
if (c.action.extract_choices.section && !sectionIds.has(c.action.extract_choices.section)) {
|
|
97
272
|
errs.push(`control_bar[${c.id}].action.extract_choices.section "${c.action.extract_choices.section}" unknown`);
|
|
98
273
|
}
|
|
99
|
-
|
|
274
|
+
compileRegexCheck(c.action.extract_choices.pattern, c.action.extract_choices.flags ?? 'm', `control_bar[${c.id}].action.extract_choices.pattern`, errs);
|
|
100
275
|
}
|
|
101
276
|
}
|
|
102
277
|
|
|
@@ -121,12 +296,26 @@ function validateRefs(spec: CliSpec): string[] {
|
|
|
121
296
|
return errs;
|
|
122
297
|
}
|
|
123
298
|
|
|
124
|
-
function
|
|
125
|
-
if (
|
|
299
|
+
function validateConditionRefs(cond: any, where: string, sectionIds: Set<string>, errs: string[]): void {
|
|
300
|
+
if (!cond) return;
|
|
301
|
+
if ('all' in cond) {
|
|
302
|
+
for (const c of cond.all ?? []) validateConditionRefs(c, where, sectionIds, errs);
|
|
303
|
+
} else if ('any' in cond) {
|
|
304
|
+
for (const c of cond.any ?? []) validateConditionRefs(c, where, sectionIds, errs);
|
|
305
|
+
} else if ('matches' in cond) {
|
|
306
|
+
if (cond.section && !sectionIds.has(cond.section)) {
|
|
307
|
+
errs.push(`${where}.section "${cond.section}" unknown`);
|
|
308
|
+
}
|
|
309
|
+
compileRegexCheck(cond.matches, cond.flags, `${where}.matches`, errs);
|
|
310
|
+
}
|
|
311
|
+
// ChangedCondition has no section refs
|
|
126
312
|
}
|
|
127
313
|
|
|
128
|
-
function
|
|
129
|
-
|
|
314
|
+
function compileRegexCheck(source: string | undefined, flags: string | undefined, where: string, errs: string[]): void {
|
|
315
|
+
if (!source) return;
|
|
316
|
+
try { new RegExp(source, flags ?? ''); } catch (err) {
|
|
317
|
+
errs.push(`${where} regex invalid: ${(err as Error).message}`);
|
|
318
|
+
}
|
|
130
319
|
}
|
|
131
320
|
|
|
132
321
|
/** Convenience: look up a provider's spec.json next to its provider dir. */
|