@ordewell/core 0.10.5 → 0.10.7
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/{IRunner-Crkt3tHD.d.ts → IFileSystem-C4_mpfze.d.ts} +97 -14
- package/dist/{IRunner-lMQIa5Wc.d.mts → IFileSystem-DUgeWWQN.d.mts} +97 -14
- package/dist/{ModeResolver-B4hhABNf.d.mts → ModeResolver-4qNjxcDs.d.mts} +1 -1
- package/dist/{ModeResolver-CUR5jCSU.d.ts → ModeResolver-DGK2rWHE.d.ts} +1 -1
- package/dist/{Task-D4QHjdGv.d.mts → Task-C0o4i3Nn.d.mts} +23 -4
- package/dist/{Task-D4QHjdGv.d.ts → Task-C0o4i3Nn.d.ts} +23 -4
- package/dist/{chunk-6LEA6WAM.mjs → chunk-DMG4ISUG.mjs} +923 -45
- package/dist/chunk-DMG4ISUG.mjs.map +1 -0
- package/dist/{chunk-RT4NQ2BZ.mjs → chunk-EEE2GBLG.mjs} +114 -27
- package/dist/chunk-EEE2GBLG.mjs.map +1 -0
- package/dist/chunk-H7QX32BH.mjs +647 -0
- package/dist/chunk-H7QX32BH.mjs.map +1 -0
- package/dist/{chunk-F3KIX7WR.mjs → chunk-LTHKFBKM.mjs} +4 -4
- package/dist/chunk-LTHKFBKM.mjs.map +1 -0
- package/dist/index.d.mts +278 -1701
- package/dist/index.d.ts +278 -1701
- package/dist/index.js +2744 -1915
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2186 -1535
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js +231 -75
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +20 -4
- package/dist/plan-utils.d.ts +20 -4
- package/dist/plan-utils.js +255 -97
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +33 -19
- package/dist/{plan-utils-wI8MuDBC.d.mts → runSettingsText-BgOmLxdw.d.mts} +1941 -108
- package/dist/{plan-utils-BVJCzNvm.d.ts → runSettingsText-BgXvajj2.d.ts} +1941 -108
- package/dist/testing.d.mts +2 -3
- package/dist/testing.d.ts +2 -3
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +1 -1
- package/dist/IFileSystem-ClBdp59f.d.ts +0 -78
- package/dist/IFileSystem-Db34maxC.d.mts +0 -78
- package/dist/chunk-4QLFM73A.mjs +0 -1407
- package/dist/chunk-4QLFM73A.mjs.map +0 -1
- package/dist/chunk-6LEA6WAM.mjs.map +0 -1
- package/dist/chunk-F3KIX7WR.mjs.map +0 -1
- package/dist/chunk-RT4NQ2BZ.mjs.map +0 -1
package/dist/parsing.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { R as RunnerModeInfo } from './ModeResolver-
|
|
1
|
+
import { a4 as RunnerId, ai as Task } from './Task-C0o4i3Nn.mjs';
|
|
2
|
+
import { R as RunnerModeInfo } from './ModeResolver-4qNjxcDs.mjs';
|
|
3
3
|
|
|
4
4
|
declare function parsePlanJson(raw: string, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): Task[];
|
|
5
5
|
|
package/dist/parsing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { R as RunnerModeInfo } from './ModeResolver-
|
|
1
|
+
import { a4 as RunnerId, ai as Task } from './Task-C0o4i3Nn.js';
|
|
2
|
+
import { R as RunnerModeInfo } from './ModeResolver-DGK2rWHE.js';
|
|
3
3
|
|
|
4
4
|
declare function parsePlanJson(raw: string, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): Task[];
|
|
5
5
|
|
package/dist/parsing.js
CHANGED
|
@@ -34,9 +34,68 @@ var import_uuid = require("uuid");
|
|
|
34
34
|
var SKILL_NAME_PATTERN = "[a-z0-9][a-z0-9_-]*";
|
|
35
35
|
var SKILL_TOKEN = new RegExp(`(^|\\s)\\/(${SKILL_NAME_PATTERN})([,.!?;:]*)(?=\\s|$)`, "gi");
|
|
36
36
|
|
|
37
|
-
// src/services/
|
|
38
|
-
function
|
|
39
|
-
|
|
37
|
+
// src/services/PlanTree.ts
|
|
38
|
+
function* walkPlan(tasks) {
|
|
39
|
+
function* walk(level, ancestors, parentLabel) {
|
|
40
|
+
for (const task of level) {
|
|
41
|
+
const label = parentLabel ? `${parentLabel}.${task.order}` : String(task.order);
|
|
42
|
+
yield { task, parent: ancestors.at(-1), ancestors, siblings: level, label };
|
|
43
|
+
yield* walk(task.subtasks ?? [], [...ancestors, task], label);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
yield* walk(tasks, []);
|
|
47
|
+
}
|
|
48
|
+
function taskPlacement(tasks, id) {
|
|
49
|
+
for (const row of walkPlan(tasks)) if (row.task.id === id) return row;
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
function subtreeIds(tasks, id) {
|
|
53
|
+
const found = taskPlacement(tasks, id);
|
|
54
|
+
return new Set(found ? [...walkPlan([found.task])].map(({ task }) => task.id) : []);
|
|
55
|
+
}
|
|
56
|
+
function dependencyTargetErrors(tasks, taskId, dependencies) {
|
|
57
|
+
const found = taskPlacement(tasks, taskId);
|
|
58
|
+
if (!found) return ["Task not found"];
|
|
59
|
+
const descendants = subtreeIds(tasks, taskId);
|
|
60
|
+
const errors = [];
|
|
61
|
+
for (const dep of dependencies) {
|
|
62
|
+
if (dep === taskId) {
|
|
63
|
+
errors.push(`"${found.task.title}" cannot depend on itself`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const target = taskPlacement(tasks, dep)?.task;
|
|
67
|
+
if (!target) {
|
|
68
|
+
errors.push(`Unknown dependency "${dep}"`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (found.ancestors.some((t) => t.id === dep) || descendants.has(dep)) {
|
|
72
|
+
errors.push(`"${found.task.title}" cannot depend on "${target.title}", its ancestor or descendant`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return errors;
|
|
76
|
+
}
|
|
77
|
+
function findDependencyCycle(tasks) {
|
|
78
|
+
const byId = new Map([...walkPlan(tasks)].map(({ task }) => [task.id, task]));
|
|
79
|
+
const path = [];
|
|
80
|
+
const done = /* @__PURE__ */ new Set();
|
|
81
|
+
const visit = (id) => {
|
|
82
|
+
if (done.has(id)) return null;
|
|
83
|
+
const at = path.indexOf(id);
|
|
84
|
+
if (at >= 0) return path.slice(at);
|
|
85
|
+
path.push(id);
|
|
86
|
+
for (const dep of byId.get(id)?.dependencies ?? []) {
|
|
87
|
+
const hit = visit(dep);
|
|
88
|
+
if (hit) return hit;
|
|
89
|
+
}
|
|
90
|
+
path.pop();
|
|
91
|
+
done.add(id);
|
|
92
|
+
return null;
|
|
93
|
+
};
|
|
94
|
+
for (const id of byId.keys()) {
|
|
95
|
+
const hit = visit(id);
|
|
96
|
+
if (hit) return hit.map((key) => byId.get(key));
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
40
99
|
}
|
|
41
100
|
|
|
42
101
|
// src/models/Task.ts
|
|
@@ -85,14 +144,7 @@ function opsFlag(value, type = "ai") {
|
|
|
85
144
|
}
|
|
86
145
|
var DEFAULT_RUNNERS = ["claude-code"];
|
|
87
146
|
function flattenTasksWithParents(tasks) {
|
|
88
|
-
|
|
89
|
-
const walk = (t, parent, parentLabel) => {
|
|
90
|
-
const label = taskOrderLabel(t, parent ? { order: parent.order, label: parentLabel } : void 0);
|
|
91
|
-
rows.push({ task: t, parent, label, ref: `#${label}` });
|
|
92
|
-
for (const sub of t.subtasks ?? []) walk(sub, t, label);
|
|
93
|
-
};
|
|
94
|
-
for (const t of tasks) walk(t, null);
|
|
95
|
-
return rows;
|
|
147
|
+
return [...walkPlan(tasks)].map(({ task, parent, label }) => ({ task, parent: parent ?? null, label, ref: `#${label}` }));
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
// src/services/JsonExtractor.ts
|
|
@@ -300,32 +352,144 @@ function effectiveAllowlist(allowlist, runner, modelsByRunner) {
|
|
|
300
352
|
const kept = allowlist.filter((id) => own.has(id) || !elsewhere.has(id));
|
|
301
353
|
return kept.length > 0 ? kept : void 0;
|
|
302
354
|
}
|
|
355
|
+
var EFFORT_LADDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
356
|
+
var EFFORT_ALIASES = { disabled: "none", adaptive: "medium" };
|
|
357
|
+
function clampThinkingEffort(effort, variants) {
|
|
358
|
+
if (!effort) return void 0;
|
|
359
|
+
if (variants.length === 0) return void 0;
|
|
360
|
+
const ids = new Set(variants.map((v) => v.id));
|
|
361
|
+
if (ids.has(effort)) return effort;
|
|
362
|
+
const canon = EFFORT_ALIASES[effort] ?? effort;
|
|
363
|
+
if (ids.has(canon)) return canon;
|
|
364
|
+
const idx = EFFORT_LADDER.indexOf(canon);
|
|
365
|
+
if (idx < 0) return void 0;
|
|
366
|
+
for (let d = 1; d < EFFORT_LADDER.length; d++) {
|
|
367
|
+
const lower = EFFORT_LADDER[idx - d];
|
|
368
|
+
if (lower && ids.has(lower)) return lower;
|
|
369
|
+
const higher = EFFORT_LADDER[idx + d];
|
|
370
|
+
if (higher && ids.has(higher)) return higher;
|
|
371
|
+
}
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
303
374
|
|
|
304
|
-
// src/services/
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
375
|
+
// src/services/TaskAssignment.ts
|
|
376
|
+
function liveModelRefusal(task) {
|
|
377
|
+
return `"${task.title}" has a live runner, so its model and effort cannot change now: stop the task first, or use the runner's own /model`;
|
|
378
|
+
}
|
|
379
|
+
function liveRunnerRefusal(task) {
|
|
380
|
+
return `"${task.title}" has a live runner, so its runner cannot change now: stop the task first`;
|
|
381
|
+
}
|
|
382
|
+
function sameAssignment(a, b) {
|
|
383
|
+
return a?.modelId === b?.modelId && a?.thinkingEffort === b?.thinkingEffort;
|
|
384
|
+
}
|
|
385
|
+
function readOnlyOn(mode, runner, catalog) {
|
|
386
|
+
return mode !== void 0 && mode === taggedReadOnlyMode(catalog.runnerModes?.[runner]);
|
|
387
|
+
}
|
|
388
|
+
function modeFor(write, task, moved) {
|
|
389
|
+
const { writer, before, catalog } = write;
|
|
390
|
+
const prior = before ?? write.from;
|
|
391
|
+
const runner = task.assignedRunner;
|
|
392
|
+
const modes = catalog.runnerModes?.[runner];
|
|
393
|
+
const picked = (writer === "user" || writer === "userAdd") && task.taskMode !== void 0 && task.taskMode !== prior?.taskMode;
|
|
394
|
+
if (picked) {
|
|
395
|
+
if (modes && modes.length > 0 && !modes.some((m) => m.id === task.taskMode)) {
|
|
396
|
+
return { ok: false, error: `Runner "${runner}" has no task mode "${task.taskMode}": valid modes: ${modes.map((m) => m.id).join(", ")}` };
|
|
318
397
|
}
|
|
398
|
+
const unavailable = unavailableModeReason(modes, task.taskMode);
|
|
399
|
+
if (unavailable) return { ok: false, error: `Mode "${task.taskMode}" of runner "${runner}" is unavailable on this host: ${unavailable}` };
|
|
400
|
+
return { ok: true, mode: task.taskMode };
|
|
319
401
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
402
|
+
const becameAi = before?.type === "user";
|
|
403
|
+
if (writer === "runner" || !catalog.runnerModes || before && !moved && !becameAi && write.readOnly === void 0) return { ok: true, mode: task.taskMode };
|
|
404
|
+
const carried = prior !== void 0 && task.taskMode === prior.taskMode && readOnlyOn(prior.taskMode, prior.assignedRunner, catalog);
|
|
405
|
+
const readOnly = write.readOnly ?? (carried || readOnlyOn(task.taskMode, runner, catalog));
|
|
406
|
+
if (readOnly && !readOnlyMode(modes) && writer !== "plan" && writer !== "edit") {
|
|
407
|
+
const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));
|
|
408
|
+
const able = readOnlyRunners(catalog.runnerModes).filter((r) => r !== runner);
|
|
409
|
+
return {
|
|
410
|
+
ok: false,
|
|
411
|
+
error: `"${task.title}" is read-only, and runner "${runner}" has no read-only mode${reason ? " available on this host" : ""}: give it another mode first${able.length > 0 ? `, or move it to one of: ${able.join(", ")}` : ""}${reason ? `. ${reason}` : ""}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ?? true);
|
|
415
|
+
return mode.ok ? { ok: true, mode: mode.mode ?? task.taskMode } : mode;
|
|
416
|
+
}
|
|
417
|
+
function resolveAssignment(write) {
|
|
418
|
+
const { writer, before, catalog } = write;
|
|
419
|
+
const written = write.task;
|
|
420
|
+
const prior = before ?? write.from;
|
|
421
|
+
const moved = prior !== void 0 && prior.assignedRunner !== written.assignedRunner;
|
|
422
|
+
const title = (before ?? written).title;
|
|
423
|
+
const live = writer !== "runner" && !!before && !!catalog.liveRunner?.(before.id);
|
|
424
|
+
if (live && moved) return { ok: false, field: "assignedRunner", error: liveRunnerRefusal({ title }) };
|
|
425
|
+
if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: "assignedModel", error: liveModelRefusal({ title }) };
|
|
426
|
+
if (written.type === "user") return { ok: true, task: written, adjustments: [] };
|
|
427
|
+
const mode = modeFor(write, written, moved);
|
|
428
|
+
if (!mode.ok) return { ok: false, field: "taskMode", error: mode.error };
|
|
429
|
+
const model = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);
|
|
430
|
+
if (!model.ok) return { ok: false, field: "assignedModel", error: model.error };
|
|
431
|
+
const task = model.assignment === written.assignedModel && mode.mode === written.taskMode ? written : { ...written, assignedModel: model.assignment, taskMode: mode.mode };
|
|
432
|
+
const adjustments = [];
|
|
433
|
+
const note = (field, from, to) => {
|
|
434
|
+
if ((from ?? null) !== (to ?? null)) adjustments.push({ taskId: written.id, title: written.title, field, from: from ?? null, to: to ?? null });
|
|
435
|
+
};
|
|
436
|
+
note("assignedModel", written.assignedModel?.modelId, task.assignedModel?.modelId);
|
|
437
|
+
note("thinkingEffort", written.assignedModel?.thinkingEffort, task.assignedModel?.thinkingEffort);
|
|
438
|
+
return { ok: true, task, adjustments };
|
|
439
|
+
}
|
|
440
|
+
function modelFor(write, moved) {
|
|
441
|
+
const { writer, before, catalog } = write;
|
|
442
|
+
const prior = before ?? write.from;
|
|
443
|
+
const written = write.task.assignedModel;
|
|
444
|
+
const runner = write.task.assignedRunner;
|
|
445
|
+
const models = catalog.modelsByRunner;
|
|
446
|
+
const own = models[runner] ?? [];
|
|
447
|
+
const listing = (id2) => own.find((m) => m.modelId === id2);
|
|
448
|
+
const carriedFrom = moved && written && prior?.assignedModel?.modelId === written.modelId ? prior.assignedRunner : void 0;
|
|
449
|
+
const seen = carriedFrom && written && !models[carriedFrom]?.some((m) => m.modelId === written.modelId) ? { ...models, [carriedFrom]: [...models[carriedFrom] ?? [], { modelId: written.modelId, modelLabel: written.modelLabel, variants: [] }] } : models;
|
|
450
|
+
const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, seen);
|
|
451
|
+
const allows = (id2) => !allowed || allowed.includes(id2);
|
|
452
|
+
const settled = (assignment) => {
|
|
453
|
+
const known = listing(assignment.modelId);
|
|
454
|
+
if (!known) return assignment;
|
|
455
|
+
const thinkingEffort = clampThinkingEffort(assignment.thinkingEffort, known.variants);
|
|
456
|
+
const availableVariants = known.variants.map((v) => v.id);
|
|
457
|
+
if (thinkingEffort === assignment.thinkingEffort && assignment.availableVariants?.join() === availableVariants.join()) return assignment;
|
|
458
|
+
const next = { ...assignment, availableVariants };
|
|
459
|
+
if (thinkingEffort === void 0) delete next.thinkingEffort;
|
|
460
|
+
else next.thinkingEffort = thinkingEffort;
|
|
461
|
+
return next;
|
|
462
|
+
};
|
|
463
|
+
const refill = (effort) => {
|
|
464
|
+
const id2 = allowed?.find((a) => listing(a)) ?? allowed?.[0] ?? own[0]?.modelId;
|
|
465
|
+
if (id2 === void 0) return void 0;
|
|
466
|
+
const known = listing(id2);
|
|
467
|
+
const thinkingEffort = clampThinkingEffort(effort, known?.variants ?? []);
|
|
468
|
+
return {
|
|
469
|
+
modelId: id2,
|
|
470
|
+
modelLabel: known?.modelLabel ?? id2,
|
|
471
|
+
...thinkingEffort !== void 0 ? { thinkingEffort } : {},
|
|
472
|
+
...known ? { availableVariants: known.variants.map((v) => v.id) } : {}
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
if (!written) return { ok: true, assignment: writer === "runner" ? void 0 : refill(void 0) };
|
|
476
|
+
if (writer === "runner") return { ok: true, assignment: settled(written) };
|
|
477
|
+
const id = written.modelId;
|
|
478
|
+
const same = prior?.assignedModel?.modelId === id;
|
|
479
|
+
if (before && same && !moved) return { ok: true, assignment: settled(written) };
|
|
480
|
+
const listedElsewhere = Object.entries(models).some(([other, list]) => other !== runner && list?.some((m) => m.modelId === id));
|
|
481
|
+
if (same) {
|
|
482
|
+
const fits = !moved || !!listing(id) || own.length === 0 && !listedElsewhere;
|
|
483
|
+
return { ok: true, assignment: fits && allows(id) ? settled(written) : refill(written.thinkingEffort) };
|
|
327
484
|
}
|
|
328
|
-
|
|
485
|
+
if (writer === "edit" || writer === "user") {
|
|
486
|
+
const offered = own.length === 0 || !!listing(id) || !!allowed?.includes(id);
|
|
487
|
+
if (!offered) return { ok: false, error: `Runner "${runner}" does not offer model "${id}"` };
|
|
488
|
+
if (!allows(id)) return { ok: false, error: `Model "${id}" is excluded by the allowlist for runner "${runner}"` };
|
|
489
|
+
return { ok: true, assignment: settled(written) };
|
|
490
|
+
}
|
|
491
|
+
const disowned = !listing(id) && listedElsewhere;
|
|
492
|
+
return { ok: true, assignment: disowned || !allows(id) ? refill(written.thinkingEffort) : settled(written) };
|
|
329
493
|
}
|
|
330
494
|
|
|
331
495
|
// src/services/PlannedTask.ts
|
|
@@ -366,6 +530,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
366
530
|
const errors = [];
|
|
367
531
|
const ignored = [];
|
|
368
532
|
const renamed = /* @__PURE__ */ new Map();
|
|
533
|
+
const adjustments = [];
|
|
369
534
|
const title = text(raw.title) ?? "";
|
|
370
535
|
const where = taskWhere(title, parent?.title);
|
|
371
536
|
const plannerId = text(raw.id);
|
|
@@ -382,18 +547,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
382
547
|
if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${OPS_NOT_READ_ONLY}` });
|
|
383
548
|
const asked = typeof raw.readOnly === "boolean" ? raw.readOnly : void 0;
|
|
384
549
|
const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);
|
|
385
|
-
let taskMode = base.taskMode;
|
|
386
|
-
if (ai && catalog.runners.includes(assignedRunner)) {
|
|
387
|
-
const mode = plannedMode(assignedRunner, catalog.runnerModes, { readOnly, current: base.taskMode }, catalog.autonomousDefault ?? true);
|
|
388
|
-
if (!mode.ok) errors.push({ taskId: id, field: "readOnly", message: `${where}: ${mode.error}` });
|
|
389
|
-
else if (mode.mode !== void 0) taskMode = mode.mode;
|
|
390
|
-
}
|
|
391
|
-
if (taskMode === void 0 && readOnly && !catalog.runnerModes) taskMode = "plan";
|
|
392
550
|
const assignedModel = ai ? modelOf(raw, base.assignedModel) : void 0;
|
|
393
|
-
if (assignedModel && isRecord(raw.assignedModel) && catalog.models && catalog.runners.includes(assignedRunner)) {
|
|
394
|
-
const validity = checkModelAndModeValidity(assignedRunner, assignedModel, void 0, catalog.models);
|
|
395
|
-
if (!validity.ok) errors.push({ taskId: id, field: "assignedModel", message: `${where}: ${validity.error}` });
|
|
396
|
-
}
|
|
397
551
|
const slice = (value) => value === "AFK" || value === "HITL" ? value : void 0;
|
|
398
552
|
const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : void 0;
|
|
399
553
|
const sliceType = slice(raw.sliceType) ?? base.sliceType ?? (ai ? parent?.sliceType : void 0);
|
|
@@ -404,10 +558,11 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
404
558
|
subtasks.push(child.task);
|
|
405
559
|
errors.push(...child.errors);
|
|
406
560
|
ignored.push(...child.ignored);
|
|
561
|
+
adjustments.push(...child.adjustments);
|
|
407
562
|
for (const [was, now] of child.renamed) renamed.set(was, now);
|
|
408
563
|
}
|
|
409
564
|
}
|
|
410
|
-
const
|
|
565
|
+
const fields = {
|
|
411
566
|
id,
|
|
412
567
|
order: Number(raw.order ?? 0),
|
|
413
568
|
title,
|
|
@@ -419,17 +574,36 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
419
574
|
subtasks,
|
|
420
575
|
assignedModel,
|
|
421
576
|
assignedRunner,
|
|
422
|
-
taskMode,
|
|
423
577
|
autonomy,
|
|
424
578
|
sliceType,
|
|
425
579
|
userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,
|
|
426
580
|
ops,
|
|
427
581
|
skills: from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills)
|
|
428
|
-
}
|
|
582
|
+
};
|
|
583
|
+
let taskMode = base.taskMode;
|
|
584
|
+
let landedModel = assignedModel;
|
|
585
|
+
if (ai && catalog.runners.includes(assignedRunner)) {
|
|
586
|
+
const resolved = resolveAssignment({
|
|
587
|
+
writer: catalog.models ? "edit" : "plan",
|
|
588
|
+
task: { ...createTask(fields), taskMode },
|
|
589
|
+
from: from ? { assignedRunner: base.assignedRunner ?? assignedRunner, assignedModel: base.assignedModel, taskMode: base.taskMode } : void 0,
|
|
590
|
+
readOnly,
|
|
591
|
+
catalog: { modelsByRunner: {}, ...catalog.models, runnerModes: catalog.runnerModes, autonomousDefault: catalog.autonomousDefault }
|
|
592
|
+
});
|
|
593
|
+
if (!resolved.ok) {
|
|
594
|
+
errors.push({ taskId: id, field: resolved.field === "taskMode" ? "readOnly" : resolved.field, message: `${where}: ${resolved.error}` });
|
|
595
|
+
} else {
|
|
596
|
+
taskMode = resolved.task.taskMode;
|
|
597
|
+
landedModel = resolved.task.assignedModel;
|
|
598
|
+
adjustments.push(...resolved.adjustments);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (taskMode === void 0 && readOnly && !catalog.runnerModes) taskMode = "plan";
|
|
602
|
+
const task = createTask({ ...fields, assignedModel: landedModel, taskMode });
|
|
429
603
|
if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);
|
|
430
|
-
return { task, errors, ignored, renamed };
|
|
604
|
+
return { task, errors, ignored, renamed, adjustments };
|
|
431
605
|
}
|
|
432
|
-
function checkPlanInvariants(tasks, runners) {
|
|
606
|
+
function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
433
607
|
const rows = flattenTasksWithParents(tasks);
|
|
434
608
|
const errors = [];
|
|
435
609
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -446,14 +620,16 @@ function checkPlanInvariants(tasks, runners) {
|
|
|
446
620
|
}
|
|
447
621
|
const opsRefused = t.ops ? opsPlacementRefusal(t.type, parent !== null) : null;
|
|
448
622
|
if (opsRefused) fail("ops", `${where} ${opsRefused}`);
|
|
449
|
-
if (t.
|
|
623
|
+
if (t.ops && t.taskMode !== void 0 && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {
|
|
624
|
+
fail("taskMode", `${where} is an ops task, which runs commands, so it cannot run in its runner's read-only mode`);
|
|
625
|
+
}
|
|
626
|
+
if (runners && t.type !== "user" && !runners.includes(t.assignedRunner)) {
|
|
450
627
|
fail("assignedRunner", `${where} has invalid assignedRunner "${t.assignedRunner}". Expected one of: ${runners.join(", ")}`);
|
|
451
628
|
}
|
|
452
629
|
}
|
|
453
|
-
const ids = new Set(rows.map((r) => r.task.id));
|
|
454
630
|
for (const { task: t, parent } of rows) {
|
|
455
|
-
for (const
|
|
456
|
-
|
|
631
|
+
for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {
|
|
632
|
+
errors.push({ taskId: t.id, field: "dependencies", message: `${taskWhere(t.title, parent?.title)}: ${error}` });
|
|
457
633
|
}
|
|
458
634
|
}
|
|
459
635
|
const cycle = findCycle(rows.map((r) => r.task));
|
|
@@ -461,27 +637,7 @@ function checkPlanInvariants(tasks, runners) {
|
|
|
461
637
|
return errors;
|
|
462
638
|
}
|
|
463
639
|
function findCycle(tasks) {
|
|
464
|
-
|
|
465
|
-
const path = [];
|
|
466
|
-
const done = /* @__PURE__ */ new Set();
|
|
467
|
-
const visit = (id) => {
|
|
468
|
-
if (done.has(id)) return null;
|
|
469
|
-
const at = path.indexOf(id);
|
|
470
|
-
if (at >= 0) return path.slice(at);
|
|
471
|
-
path.push(id);
|
|
472
|
-
for (const dep of byId.get(id)?.dependencies ?? []) {
|
|
473
|
-
const hit = visit(dep);
|
|
474
|
-
if (hit) return hit;
|
|
475
|
-
}
|
|
476
|
-
path.pop();
|
|
477
|
-
done.add(id);
|
|
478
|
-
return null;
|
|
479
|
-
};
|
|
480
|
-
for (const t of tasks) {
|
|
481
|
-
const hit = visit(t.id);
|
|
482
|
-
if (hit) return hit.map((id) => byId.get(id));
|
|
483
|
-
}
|
|
484
|
-
return null;
|
|
640
|
+
return findDependencyCycle(tasks);
|
|
485
641
|
}
|
|
486
642
|
|
|
487
643
|
// src/services/PlanValidator.ts
|
|
@@ -533,7 +689,7 @@ function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = true)
|
|
|
533
689
|
const errors = [
|
|
534
690
|
...sliceRequired(tasks),
|
|
535
691
|
...planned.flatMap((p) => p.errors),
|
|
536
|
-
...checkPlanInvariants(tasks, runners)
|
|
692
|
+
...checkPlanInvariants(tasks, runners, runnerModes)
|
|
537
693
|
];
|
|
538
694
|
if (errors.length > 0) return { ok: false, errors };
|
|
539
695
|
const ignored = planned.flatMap((p) => p.ignored);
|