@ordewell/core 0.11.3 → 0.12.1
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/AUTHORS.md +3 -1
- package/dist/{ModeResolver-DyXeZ9ib.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
- package/dist/{ModeResolver-BDAgouuE.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
- package/dist/{Task-BL4Idm9r.d.mts → Task-CxGgN6I0.d.mts} +801 -39
- package/dist/{Task-BL4Idm9r.d.ts → Task-CxGgN6I0.d.ts} +801 -39
- package/dist/{chunk-YAFNYNFX.mjs → chunk-2IFKPYPO.mjs} +453 -17
- package/dist/chunk-2IFKPYPO.mjs.map +1 -0
- package/dist/{chunk-MYXMIVWA.mjs → chunk-FX33UKAD.mjs} +8 -6
- package/dist/chunk-FX33UKAD.mjs.map +1 -0
- package/dist/{chunk-QTZ5XTBY.mjs → chunk-PKJTN2PD.mjs} +458 -13
- package/dist/chunk-PKJTN2PD.mjs.map +1 -0
- package/dist/{chunk-7WNLN63J.mjs → chunk-RZ23BMVR.mjs} +599 -124
- package/dist/chunk-RZ23BMVR.mjs.map +1 -0
- package/dist/chunk-UDUATSCJ.mjs +48 -0
- package/dist/chunk-UDUATSCJ.mjs.map +1 -0
- package/dist/{IRunner-DOMtDTVt.d.ts → eventStore-CTCSKS9E.d.ts} +976 -145
- package/dist/{IRunner-ihalYfTo.d.mts → eventStore-CcB8yELZ.d.mts} +976 -145
- package/dist/index.d.mts +905 -164
- package/dist/index.d.ts +905 -164
- package/dist/index.js +12496 -3308
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10830 -2919
- 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 +277 -21
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +8 -5
- package/dist/plan-utils.d.ts +8 -5
- package/dist/plan-utils.js +262 -33
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +39 -3
- package/dist/{runSettingsText-DVOd7vKC.d.ts → runSettingsText-DFVpe6aU.d.mts} +1996 -895
- package/dist/{runSettingsText-BuLVBlXS.d.mts → runSettingsText-lKLDsyli.d.ts} +1996 -895
- package/dist/testing.d.mts +99 -4
- package/dist/testing.d.ts +99 -4
- package/dist/testing.js +213 -8
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +193 -8
- package/dist/testing.mjs.map +1 -1
- package/docker/task.Dockerfile +22 -0
- package/package.json +3 -1
- package/skills/diagnose/SKILL.md +2 -2
- package/skills/explore/SKILL.md +1 -1
- package/dist/chunk-7WNLN63J.mjs.map +0 -1
- package/dist/chunk-MYXMIVWA.mjs.map +0 -1
- package/dist/chunk-QTZ5XTBY.mjs.map +0 -1
- package/dist/chunk-YAFNYNFX.mjs.map +0 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
flattenTasksWithParents,
|
|
8
8
|
inheritedSkills,
|
|
9
9
|
skillNames
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PKJTN2PD.mjs";
|
|
11
11
|
|
|
12
12
|
// src/models/ScriptTask.ts
|
|
13
13
|
var DEFAULT_SCRIPT_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -124,9 +124,19 @@ function betweenRounds(check) {
|
|
|
124
124
|
function outOfRounds(check) {
|
|
125
125
|
return !!check.loop && check.status === "failed" && foundWanting(check) && loopRoundOf(check) >= check.loop.maxRounds;
|
|
126
126
|
}
|
|
127
|
+
function loopEnded(check) {
|
|
128
|
+
return check.status === "completed" || check.status === "skipped" || outOfRounds(check);
|
|
129
|
+
}
|
|
127
130
|
function checkOf(tasks, taskId) {
|
|
128
131
|
return tasks.find((t) => t.loop?.body.includes(taskId));
|
|
129
132
|
}
|
|
133
|
+
function checksOfBodies(tasks) {
|
|
134
|
+
const checks = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const task of tasks) {
|
|
136
|
+
for (const id of task.loop?.body ?? []) if (!checks.has(id)) checks.set(id, task);
|
|
137
|
+
}
|
|
138
|
+
return checks;
|
|
139
|
+
}
|
|
130
140
|
function inLoop(tasks, task) {
|
|
131
141
|
return !!task.loop || checkOf(tasks, task.id) !== void 0;
|
|
132
142
|
}
|
|
@@ -158,6 +168,7 @@ function loopErrors(tasks) {
|
|
|
158
168
|
const fail = (message) => errors.push({ taskId: check.id, field: "loop", message: `${where} ${message}` });
|
|
159
169
|
if (parent) fail(`has a "loop", but only a top-level task can be a loop's check`);
|
|
160
170
|
if (check.type === "user") fail("is a manual task, so it cannot be a loop's check: a check is an AI task, which reports through task_complete, or a script task, whose exit status decides");
|
|
171
|
+
if (check.type === "delegated") fail("is a delegated task, so it cannot be a loop's check: a round would start a new child on what is left of its budget. Add a check that depends on it, and put the loop on that");
|
|
161
172
|
if (check.subtasks?.length) fail("is a loop's check, which has no subtasks: each round runs it again whole");
|
|
162
173
|
if (!isRoundCount(loop.maxRounds)) fail(roundsMessage(loop.maxRounds));
|
|
163
174
|
if (!Array.isArray(loop.body) || loop.body.length === 0) fail('has a "loop" with an empty "body": name the tasks that fix what it finds');
|
|
@@ -190,6 +201,7 @@ function loopErrors(tasks) {
|
|
|
190
201
|
bodyOwner.set(id, check);
|
|
191
202
|
if (body.loop) fail(`names "${body.title}" in its loop's "body", which has a loop of its own: a loop has no loop inside its body`);
|
|
192
203
|
if (body.type === "user") fail(`names manual task "${body.title}" in its loop's "body": a body is AI or script tasks, which Ordewell runs again each round`);
|
|
204
|
+
if (body.type === "delegated") fail(`names delegated task "${body.title}" in its loop's "body": a round would start a new child on what is left of its budget`);
|
|
193
205
|
if (body.subtasks?.length) fail(`names "${body.title}" in its loop's "body", which has subtasks: each round runs a body task again whole, so make each step a task of the body`);
|
|
194
206
|
}
|
|
195
207
|
}
|
|
@@ -219,10 +231,25 @@ function loopRemovalRefusal(task) {
|
|
|
219
231
|
return task.loop ? `"${task.title}" is a loop's check: remove its body tasks first, or drop its loop ("loop": null), so they do not run on their own` : null;
|
|
220
232
|
}
|
|
221
233
|
function loopReshapeRefusal(tasks, target, action) {
|
|
234
|
+
if (target.type === "delegated") return `"${target.title}" is a delegated task and cannot be ${action}: remove it and add what should replace it`;
|
|
222
235
|
if (target.loop) return `"${target.title}" is a loop's check and cannot be ${action}: drop its loop first ("loop": null)`;
|
|
223
236
|
return checkOf(tasks, target.id) ? `"${target.title}" is in a loop's body and cannot be ${action}: take it out of the loop first` : null;
|
|
224
237
|
}
|
|
225
238
|
|
|
239
|
+
// src/models/lineBreaks.ts
|
|
240
|
+
var BREAKS = "\\n\\v\\f\\r\\u0085\\u2028\\u2029";
|
|
241
|
+
var LINE_BREAK = new RegExp(`\\r\\n|[${BREAKS}]`);
|
|
242
|
+
var ONE_LINE = new RegExp(`^[^${BREAKS}]*$`);
|
|
243
|
+
function splitLines(text2) {
|
|
244
|
+
return text2.split(LINE_BREAK);
|
|
245
|
+
}
|
|
246
|
+
function quotedLine(text2) {
|
|
247
|
+
return JSON.stringify(text2).replace(/[\u0085\u2028\u2029]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
248
|
+
}
|
|
249
|
+
function onOneLine(text2) {
|
|
250
|
+
return ONE_LINE.test(text2) ? text2 : quotedLine(text2);
|
|
251
|
+
}
|
|
252
|
+
|
|
226
253
|
// src/services/JsonExtractor.ts
|
|
227
254
|
var PLAN_ENVELOPE_KEY = "tasks";
|
|
228
255
|
var TASK_OPS_ENVELOPE_KEY = "taskOps";
|
|
@@ -532,6 +559,173 @@ function clampThinkingEffort(effort, variants) {
|
|
|
532
559
|
return void 0;
|
|
533
560
|
}
|
|
534
561
|
|
|
562
|
+
// src/services/Mandate.ts
|
|
563
|
+
var GRANTS = ["observe", "steer", "control", "replan", "approve", "delegate", "effect", "research", "merge"];
|
|
564
|
+
var FLOOR_GRANTS = ["observe", "steer", "control", "replan", "approve", "merge"];
|
|
565
|
+
var SHAPE_LIMITS = {
|
|
566
|
+
depth: { default: 2, hard: 4 },
|
|
567
|
+
delegatedTasks: { default: 8, hard: 30 },
|
|
568
|
+
tasksPerPlan: { default: 30, hard: 100 }
|
|
569
|
+
};
|
|
570
|
+
var DEFAULT_CHILD_WALL_CLOCK_MS = 24 * 60 * 60 * 1e3;
|
|
571
|
+
function mandateRefusalText({ level, field, asked, there }) {
|
|
572
|
+
return `Level ${level} refused ${field}: asked ${asked}, and there is ${there}.`;
|
|
573
|
+
}
|
|
574
|
+
var LEVEL_GRANTS = {
|
|
575
|
+
off: ["research"],
|
|
576
|
+
watch: ["observe", "research"],
|
|
577
|
+
act: ["observe", "steer", "control", "replan", "research", "merge"]
|
|
578
|
+
};
|
|
579
|
+
var DELEGATED_GRANTS = [...FLOOR_GRANTS, "research", "delegate"];
|
|
580
|
+
function rootMandate(settings) {
|
|
581
|
+
const effect = (settings.effects?.rules.length ?? 0) > 0;
|
|
582
|
+
const delegation = settings.delegationTokens > 0;
|
|
583
|
+
const effectRules = settings.effects ? [settings.effects] : [];
|
|
584
|
+
return {
|
|
585
|
+
grants: /* @__PURE__ */ new Set([
|
|
586
|
+
...LEVEL_GRANTS[settings.level],
|
|
587
|
+
...settings.autorun ? ["approve"] : [],
|
|
588
|
+
...effect ? ["effect"] : [],
|
|
589
|
+
...delegation ? ["delegate"] : []
|
|
590
|
+
]),
|
|
591
|
+
grantable: new Set(delegation ? [...DELEGATED_GRANTS, ...effect ? ["effect"] : []] : []),
|
|
592
|
+
autonomy: settings.autonomous ? "full" : "guarded",
|
|
593
|
+
limits: {
|
|
594
|
+
tokens: settings.delegationTokens,
|
|
595
|
+
wallClockMs: null,
|
|
596
|
+
depth: SHAPE_LIMITS.depth.default,
|
|
597
|
+
delegatedTasks: SHAPE_LIMITS.delegatedTasks.default,
|
|
598
|
+
tasksPerPlan: SHAPE_LIMITS.tasksPerPlan.default,
|
|
599
|
+
runners: settings.runners,
|
|
600
|
+
models: settings.models
|
|
601
|
+
},
|
|
602
|
+
exclusions: { protectedPaths: [] },
|
|
603
|
+
effectRules,
|
|
604
|
+
ownEffectRules: effectRules
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function heldMandate(saved, ancestors) {
|
|
608
|
+
const within = (grants) => new Set([...grants].filter((grant) => ancestors.every((ancestor) => ancestor.grantable.has(grant))));
|
|
609
|
+
const effectRules = [...ancestors.flatMap((ancestor) => ancestor.effectRules), ...saved.ownEffectRules];
|
|
610
|
+
return { ...saved, grants: within(saved.grants), grantable: within(saved.grantable), effectRules };
|
|
611
|
+
}
|
|
612
|
+
var listed = (grants) => [...grants].sort().join(", ") || "nothing";
|
|
613
|
+
var lesser = (limit) => (parent, request) => {
|
|
614
|
+
const { hard } = SHAPE_LIMITS[limit];
|
|
615
|
+
const asked = request[limit];
|
|
616
|
+
if (asked !== void 0 && asked > hard) return { refused: { field: `limits.${limit}`, asked: String(asked), there: `a hard limit of ${hard}` } };
|
|
617
|
+
return { value: Math.min(parent[limit], asked ?? parent[limit]) };
|
|
618
|
+
};
|
|
619
|
+
function intersectModels(parent, asked = {}) {
|
|
620
|
+
const models = { ...asked, ...parent };
|
|
621
|
+
for (const runner of Object.keys(parent)) {
|
|
622
|
+
if (asked[runner]) models[runner] = parent[runner].filter((model) => asked[runner].includes(model));
|
|
623
|
+
}
|
|
624
|
+
return models;
|
|
625
|
+
}
|
|
626
|
+
var LIMIT_RULES = {
|
|
627
|
+
tokens: ({ tokens }, { tokens: asked }) => {
|
|
628
|
+
if (asked === void 0 || asked <= 0 || asked > tokens) return { refused: { field: "limits.tokens", asked: asked === void 0 ? "none" : String(asked), there: String(tokens) } };
|
|
629
|
+
return { value: asked };
|
|
630
|
+
},
|
|
631
|
+
wallClockMs: ({ wallClockMs: left }, { wallClockMs: asked }) => {
|
|
632
|
+
if (asked === void 0) return { value: left === null ? DEFAULT_CHILD_WALL_CLOCK_MS : Math.min(left, DEFAULT_CHILD_WALL_CLOCK_MS) };
|
|
633
|
+
if (asked <= 0 || left !== null && asked > left) return { refused: { field: "limits.wallClockMs", asked: String(asked), there: left === null ? "no limit" : String(left) } };
|
|
634
|
+
return { value: asked };
|
|
635
|
+
},
|
|
636
|
+
depth: ({ depth }, { depth: asked }) => {
|
|
637
|
+
if (depth <= 0) return { refused: { field: "limits.depth", asked: "a child", there: "no depth left" } };
|
|
638
|
+
const { hard } = SHAPE_LIMITS.depth;
|
|
639
|
+
if (asked !== void 0 && asked > hard) return { refused: { field: "limits.depth", asked: String(asked), there: `a hard limit of ${hard}` } };
|
|
640
|
+
return { value: Math.min(depth - 1, asked ?? depth - 1) };
|
|
641
|
+
},
|
|
642
|
+
delegatedTasks: lesser("delegatedTasks"),
|
|
643
|
+
tasksPerPlan: lesser("tasksPerPlan"),
|
|
644
|
+
runners: ({ runners }, { runners: asked }) => ({ value: asked ? runners.filter((runner) => asked.includes(runner)) : runners }),
|
|
645
|
+
models: ({ models }, { models: asked }) => ({ value: intersectModels(models, asked) })
|
|
646
|
+
};
|
|
647
|
+
function handedOn(parent, request) {
|
|
648
|
+
const asked = /* @__PURE__ */ new Set([...FLOOR_GRANTS, ...request.grants ?? []]);
|
|
649
|
+
const lost = FLOOR_GRANTS.some((grant) => !parent.grantable.has(grant));
|
|
650
|
+
if (lost) return { refused: { field: "grants", asked: listed(asked), there: listed(parent.grantable) } };
|
|
651
|
+
return { value: new Set([...asked].filter((grant) => parent.grantable.has(grant))) };
|
|
652
|
+
}
|
|
653
|
+
var MANDATE_RULES = {
|
|
654
|
+
grants: handedOn,
|
|
655
|
+
grantable: handedOn,
|
|
656
|
+
autonomy: ({ autonomy }, { autonomy: asked }) => ({ value: autonomy === "guarded" || asked === "guarded" ? "guarded" : "full" }),
|
|
657
|
+
limits: ({ limits }, request) => applyRules(LIMIT_RULES, limits, request),
|
|
658
|
+
exclusions: ({ exclusions }, { protectedPaths }) => ({ value: { protectedPaths: [.../* @__PURE__ */ new Set([...exclusions.protectedPaths, ...protectedPaths ?? []])] } }),
|
|
659
|
+
effectRules: ({ effectRules }, { effectRules: asked }) => ({ value: asked ? [...effectRules, asked] : effectRules }),
|
|
660
|
+
ownEffectRules: (_parent, { effectRules: asked }) => ({ value: asked ? [asked] : [] })
|
|
661
|
+
};
|
|
662
|
+
function applyRules(rules, parent, request) {
|
|
663
|
+
const value = {};
|
|
664
|
+
for (const key of Object.keys(rules)) {
|
|
665
|
+
const ruled = rules[key](parent, request);
|
|
666
|
+
if ("refused" in ruled) return ruled;
|
|
667
|
+
value[key] = ruled.value;
|
|
668
|
+
}
|
|
669
|
+
return { value };
|
|
670
|
+
}
|
|
671
|
+
function narrow(parent, request) {
|
|
672
|
+
const ruled = applyRules(MANDATE_RULES, parent, request);
|
|
673
|
+
return "refused" in ruled ? { ok: false, refusal: { level: request.level, ...ruled.refused } } : { ok: true, mandate: ruled.value };
|
|
674
|
+
}
|
|
675
|
+
function delegatedRequest(task, level) {
|
|
676
|
+
return {
|
|
677
|
+
level,
|
|
678
|
+
...task.grants ? { grants: task.grants } : {},
|
|
679
|
+
...task.budget?.tokens !== void 0 ? { tokens: task.budget.tokens } : {},
|
|
680
|
+
...task.budget?.wallClockMs !== void 0 ? { wallClockMs: task.budget.wallClockMs } : {},
|
|
681
|
+
...task.protectedPaths ? { protectedPaths: task.protectedPaths } : {}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
var COMMAND_GRANTS = {
|
|
685
|
+
run_plan: "control",
|
|
686
|
+
run_task: "control",
|
|
687
|
+
stop_task: "control",
|
|
688
|
+
stop_run: "control",
|
|
689
|
+
retry_task: "control",
|
|
690
|
+
message_task: "steer",
|
|
691
|
+
answer_approval: "steer",
|
|
692
|
+
merge_all: "merge",
|
|
693
|
+
skip_task: "control",
|
|
694
|
+
force_start: null,
|
|
695
|
+
mark_complete: null,
|
|
696
|
+
mark_not_done: null,
|
|
697
|
+
revert_task: null,
|
|
698
|
+
interrupt_task: null,
|
|
699
|
+
force_send_queued: null,
|
|
700
|
+
remove_queued: null,
|
|
701
|
+
cancel_resume: null,
|
|
702
|
+
answer_checkpoint: "steer",
|
|
703
|
+
approve_review: null,
|
|
704
|
+
run_script: "control"
|
|
705
|
+
};
|
|
706
|
+
function allowsCommand(mandate, command) {
|
|
707
|
+
const grant = COMMAND_GRANTS[command];
|
|
708
|
+
return grant !== null && mandate.grants.has(grant);
|
|
709
|
+
}
|
|
710
|
+
function missingGrant(depth, mandate, needed) {
|
|
711
|
+
return `Level ${depth} refused this: it needs the ${needed} grant, and this node holds ${listed(mandate.grants)}.`;
|
|
712
|
+
}
|
|
713
|
+
var SCRIPT_PAST_THE_RULES = "its body is not covered at every level of the effect rules, and it would run unsealed";
|
|
714
|
+
var TOOL_GRANTS = [
|
|
715
|
+
...Object.entries(COMMAND_GRANTS).flatMap(([tool, grant]) => grant ? [[tool, grant]] : []),
|
|
716
|
+
["submit_plan", "replan"],
|
|
717
|
+
["edit_plan", "replan"]
|
|
718
|
+
];
|
|
719
|
+
function withheldPlannerTools(mandate) {
|
|
720
|
+
return TOOL_GRANTS.filter(([, grant]) => !mandate.grants.has(grant)).map(([tool]) => tool);
|
|
721
|
+
}
|
|
722
|
+
function autonomousAt(child, global) {
|
|
723
|
+
return child ? child.mandate("off").autonomy === "full" : global;
|
|
724
|
+
}
|
|
725
|
+
function rootAuthority(settings) {
|
|
726
|
+
return { depth: 0, mandate: (level) => rootMandate({ ...settings(), level }) };
|
|
727
|
+
}
|
|
728
|
+
|
|
535
729
|
// src/services/TaskAssignment.ts
|
|
536
730
|
function liveModelRefusal(task) {
|
|
537
731
|
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`;
|
|
@@ -587,8 +781,8 @@ function resolveAssignment(write) {
|
|
|
587
781
|
const live = writer !== "runner" && !!before && !!catalog.liveRunner?.(before.id);
|
|
588
782
|
if (live && moved) return { ok: false, field: "assignedRunner", error: liveRunnerRefusal({ title }) };
|
|
589
783
|
if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: "assignedModel", error: liveModelRefusal({ title }) };
|
|
590
|
-
if (written.type !== "ai") return { ok: true, task: written, adjustments: [] };
|
|
591
|
-
const mode = modeFor(write, written, moved);
|
|
784
|
+
if (written.type !== "ai" && written.type !== "delegated") return { ok: true, task: written, adjustments: [] };
|
|
785
|
+
const mode = written.type === "delegated" ? { ok: true, mode: void 0 } : modeFor(write, written, moved);
|
|
592
786
|
if (!mode.ok) return { ok: false, field: "taskMode", error: mode.error };
|
|
593
787
|
const model = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);
|
|
594
788
|
if (!model.ok) return { ok: false, field: "assignedModel", error: model.error };
|
|
@@ -673,6 +867,179 @@ function resolveAssignments(tasks, planned, catalog) {
|
|
|
673
867
|
return { tasks: walk(tasks), adjustments };
|
|
674
868
|
}
|
|
675
869
|
|
|
870
|
+
// src/models/Delegation.ts
|
|
871
|
+
function countChildTasks(statuses) {
|
|
872
|
+
const counts = { completed: 0, running: 0, waiting: 0, pending: 0, failed: 0, skipped: 0 };
|
|
873
|
+
for (const status of statuses) {
|
|
874
|
+
if (status === "completed") counts.completed += 1;
|
|
875
|
+
else if (status === "in_progress") counts.running += 1;
|
|
876
|
+
else if (status === "awaiting_user") counts.waiting += 1;
|
|
877
|
+
else if (status === "failed") counts.failed += 1;
|
|
878
|
+
else if (status === "skipped") counts.skipped += 1;
|
|
879
|
+
else counts.pending += 1;
|
|
880
|
+
}
|
|
881
|
+
return counts;
|
|
882
|
+
}
|
|
883
|
+
var SCOPE_LIMITS = { item: 200, items: 50 };
|
|
884
|
+
var NOT_A_DELEGATED_TASKS_FIELDS = ["prompt", "taskMode", "skills", "userSteps", "ops", "commands", "script", "timeoutMs", "research"];
|
|
885
|
+
var DELEGATED_FIELDS = ["goal", "budget", "grants", "protectedPaths", "scope"];
|
|
886
|
+
var DELEGATION_UNAVAILABLE = "delegation is not available in this session";
|
|
887
|
+
function notOnADelegatedMessage(fields) {
|
|
888
|
+
return `is a delegated task, which holds a goal for a child plan: leave out ${fields.map((f) => `"${f}"`).join(", ")}`;
|
|
889
|
+
}
|
|
890
|
+
function isBudget(value) {
|
|
891
|
+
if (typeof value !== "object" || value === null) return false;
|
|
892
|
+
const { tokens } = value;
|
|
893
|
+
return typeof tokens === "number" && Number.isInteger(tokens) && tokens > 0;
|
|
894
|
+
}
|
|
895
|
+
function wallClockOk(budget) {
|
|
896
|
+
const { wallClockMs } = budget;
|
|
897
|
+
return wallClockMs === void 0 || Number.isInteger(wallClockMs) && wallClockMs > 0;
|
|
898
|
+
}
|
|
899
|
+
function readsOnly(task, runnerModes) {
|
|
900
|
+
if (task.type !== "ai") return false;
|
|
901
|
+
const mode = runnerModes ? taggedReadOnlyMode(runnerModes[task.assignedRunner]) : "plan";
|
|
902
|
+
return mode !== void 0 && task.taskMode === mode;
|
|
903
|
+
}
|
|
904
|
+
function runnerUnread(task, runnerModes) {
|
|
905
|
+
return task.type === "ai" && runnerModes !== void 0 && runnerModes[task.assignedRunner] === void 0;
|
|
906
|
+
}
|
|
907
|
+
function atAGate(task) {
|
|
908
|
+
return task.type === "script" && !task.readOnly || task.ops === true;
|
|
909
|
+
}
|
|
910
|
+
function reaches(tasks, from, target) {
|
|
911
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
912
|
+
const seen = /* @__PURE__ */ new Set();
|
|
913
|
+
const walk = (task) => task.dependencies.some((id) => {
|
|
914
|
+
if (seen.has(id)) return false;
|
|
915
|
+
seen.add(id);
|
|
916
|
+
const dep = byId.get(id);
|
|
917
|
+
return dep !== void 0 && (target(dep) || walk(dep));
|
|
918
|
+
});
|
|
919
|
+
return walk(from);
|
|
920
|
+
}
|
|
921
|
+
function delegationErrors(tasks, rules = {}) {
|
|
922
|
+
const errors = [];
|
|
923
|
+
const delegated = tasks.filter((t) => t.type === "delegated");
|
|
924
|
+
const fail = (task, field, message) => errors.push({ taskId: task.id, field, message });
|
|
925
|
+
for (const task of delegated) {
|
|
926
|
+
const where = `Task "${task.title}"`;
|
|
927
|
+
if (typeof task.goal !== "string" || !task.goal.trim()) fail(task, "goal", `${where} is a delegated task with no "goal": say what its child plan must achieve`);
|
|
928
|
+
if (!isBudget(task.budget)) fail(task, "budget", `${where} is a delegated task with no "budget": give it {"tokens": <a whole number>}, and "wallClockMs" if it has a time limit`);
|
|
929
|
+
else if (!wallClockOk(task.budget)) fail(task, "budget", `${where} has a "wallClockMs" that is not a whole number of milliseconds above 0`);
|
|
930
|
+
const paths = task.protectedPaths;
|
|
931
|
+
if (!task.readOnly && (!Array.isArray(paths) || paths.length === 0 || !paths.every((p) => typeof p === "string" && p.trim()))) {
|
|
932
|
+
fail(task, "protectedPaths", `${where} is a delegated task that lands, with no "protectedPaths": name the paths its child may not change, what its check runs and the configuration that runs it`);
|
|
933
|
+
}
|
|
934
|
+
if (task.scope !== void 0 && !scopeOk(task.scope)) fail(task, "scope", `${where} has a "scope" that is not lists of text: "paths", "provides" and "consumes", at most ${SCOPE_LIMITS.items} items of one line and ${SCOPE_LIMITS.item} characters each`);
|
|
935
|
+
const unknown = (task.grants ?? []).filter((grant) => !GRANTS.includes(grant));
|
|
936
|
+
if (unknown.length > 0) fail(task, "grants", `${where} asks in "grants" for ${unknown.map((g) => `"${g}"`).join(", ")}, which no grant is: one of ${GRANTS.join(", ")}`);
|
|
937
|
+
const dependents = tasks.filter((t) => t.dependencies.includes(task.id));
|
|
938
|
+
if (task.readOnly) {
|
|
939
|
+
for (const script of dependents.filter((t) => t.type === "script")) {
|
|
940
|
+
fail(script, "dependencies", `Task "${script.title}" depends on "${task.title}", which is a read-only delegated task, which lands nothing, so a script has nothing to run on: make the check a read-only AI task, which reads its handoff`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!dependents.some((t) => judges(t, task, rules.runnerModes))) {
|
|
944
|
+
fail(task, "dependencies", `${where} is a delegated task with no check: add a read-only AI task${task.readOnly ? ", which reads its handoff," : ', or a script task marked "readOnly",'} that depends on it directly`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
for (const task of tasks.filter((t) => t.type === "script" && t.readOnly)) {
|
|
948
|
+
if (rules.canSeal === false) fail(task, "readOnly", `Task "${task.title}" is a read-only script task, and this machine has no sandbox to seal one in: make the check a read-only AI task`);
|
|
949
|
+
}
|
|
950
|
+
for (const check of tasks.filter((t) => t.loop)) {
|
|
951
|
+
if (!reaches(tasks, check, (t) => t.type === "delegated")) continue;
|
|
952
|
+
for (const id of check.loop?.body ?? []) {
|
|
953
|
+
const body = tasks.find((t) => t.id === id);
|
|
954
|
+
if (body && atAGate(body)) {
|
|
955
|
+
fail(body, "loop", `Task "${check.title}" names "${body.title}" in its loop's "body", which waits at a merge gate for work a delegated task's check holds back: make the fix a change task, cut from the integration tip`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return errors;
|
|
960
|
+
}
|
|
961
|
+
function scopeOk(scope) {
|
|
962
|
+
if (typeof scope !== "object" || scope === null) return false;
|
|
963
|
+
const { paths, provides, consumes } = scope;
|
|
964
|
+
const itemOk = (item) => typeof item === "string" && item.trim().length > 0 && item.length <= SCOPE_LIMITS.item && ONE_LINE.test(item);
|
|
965
|
+
return [paths, provides, consumes].every((list) => list === void 0 || Array.isArray(list) && list.length <= SCOPE_LIMITS.items && list.every(itemOk));
|
|
966
|
+
}
|
|
967
|
+
function judges(task, delegated, runnerModes) {
|
|
968
|
+
return readsOnly(task, runnerModes) || !delegated.readOnly && task.type === "script" && task.readOnly === true;
|
|
969
|
+
}
|
|
970
|
+
function checksOf(tasks, delegated, rules = {}) {
|
|
971
|
+
return tasks.filter((t) => t.dependencies.includes(delegated.id) && (judges(t, delegated, rules.runnerModes) || runnerUnread(t, rules.runnerModes)));
|
|
972
|
+
}
|
|
973
|
+
function checksHoldingMerge(tasks, awaitsMerge, rules = {}) {
|
|
974
|
+
return tasks.filter((t) => t.type === "delegated" && awaitsMerge(t.id)).flatMap((t) => checksOf(tasks, t, rules)).filter((check) => check.status !== "completed" && check.status !== "skipped");
|
|
975
|
+
}
|
|
976
|
+
function commandsOf({ script, commands }) {
|
|
977
|
+
const written = splitLines(script ?? "").flatMap((line) => line.split(/[;|&]+/)).map((command) => command.trim()).filter((command) => !command.startsWith("#"));
|
|
978
|
+
return [...written, ...(commands ?? []).map((argv) => argv.join(" "))].filter(Boolean);
|
|
979
|
+
}
|
|
980
|
+
function checkWords(tasks) {
|
|
981
|
+
const delegated = new Set(tasks.filter((t) => t.type === "delegated").map((t) => t.id));
|
|
982
|
+
return tasks.filter((t) => t.dependencies.some((id) => delegated.has(id))).flatMap((t) => [
|
|
983
|
+
...[t.title, t.prompt ?? ""].map((text2) => ({ text: text2, command: false })),
|
|
984
|
+
...commandsOf(t).map((text2) => ({ text: text2, command: true }))
|
|
985
|
+
]);
|
|
986
|
+
}
|
|
987
|
+
function checksDelegatedTask(tasks, task, rules = {}) {
|
|
988
|
+
if (!readsOnly(task, rules.runnerModes) && !runnerUnread(task, rules.runnerModes) && !(task.type === "script" && task.readOnly)) return false;
|
|
989
|
+
return tasks.some((t) => t.type === "delegated" && task.dependencies.includes(t.id));
|
|
990
|
+
}
|
|
991
|
+
function planSize(tasks) {
|
|
992
|
+
return flattenTasks(tasks).length;
|
|
993
|
+
}
|
|
994
|
+
function underProtectedPath(file, protectedPath) {
|
|
995
|
+
const rule = protectedPath.trim().replace(/^\.\//, "").replace(/\\/g, "/");
|
|
996
|
+
if (!rule) return false;
|
|
997
|
+
if (rule.includes("*")) return globRegExp(rule).test(file);
|
|
998
|
+
const bare = rule.replace(/\/+$/, "");
|
|
999
|
+
return file === bare || file.startsWith(`${bare}/`);
|
|
1000
|
+
}
|
|
1001
|
+
function fixedStart(rule) {
|
|
1002
|
+
const bare = rule.trim().replace(/^\.\//, "").replace(/\\/g, "/");
|
|
1003
|
+
const star = bare.indexOf("*");
|
|
1004
|
+
return star === -1 ? bare : bare.slice(0, star);
|
|
1005
|
+
}
|
|
1006
|
+
function pathsMeet(a, b) {
|
|
1007
|
+
if (underProtectedPath(a.replace(/\/+$/, ""), b) || underProtectedPath(b.replace(/\/+$/, ""), a)) return true;
|
|
1008
|
+
if (!a.includes("*") || !b.includes("*")) return false;
|
|
1009
|
+
const [one, other] = [fixedStart(a), fixedStart(b)];
|
|
1010
|
+
return one.startsWith(other) || other.startsWith(one);
|
|
1011
|
+
}
|
|
1012
|
+
function narrower(a, b) {
|
|
1013
|
+
return b.includes("*") || !a.includes("*") && a.length >= b.length ? a : b;
|
|
1014
|
+
}
|
|
1015
|
+
function ordered(tasks, a, b) {
|
|
1016
|
+
return reaches(tasks, a, (t) => t.id === b.id) || reaches(tasks, b, (t) => t.id === a.id);
|
|
1017
|
+
}
|
|
1018
|
+
function scopeOverlaps(tasks) {
|
|
1019
|
+
const scoped = tasks.filter((t) => t.type === "delegated" && t.scope);
|
|
1020
|
+
const said = [];
|
|
1021
|
+
scoped.forEach((a, i) => {
|
|
1022
|
+
for (const b of scoped.slice(i + 1)) {
|
|
1023
|
+
if (ordered(tasks, a, b)) continue;
|
|
1024
|
+
const declared = (a.scope?.paths ?? []).flatMap((one) => (b.scope?.paths ?? []).filter((other) => pathsMeet(one, other)).map((other) => narrower(one, other)));
|
|
1025
|
+
if (declared.length > 0) said.push(`Tasks "${a.title}" and "${b.title}" both declare ${[...new Set(declared)].join(", ")}, and nothing orders them: give one a dependency on the other, or name an owner.`);
|
|
1026
|
+
for (const name of a.scope?.provides ?? []) {
|
|
1027
|
+
if (b.scope?.provides?.includes(name)) said.push(`Tasks "${a.title}" and "${b.title}" both provide "${name}", and nothing orders them: name one owner.`);
|
|
1028
|
+
}
|
|
1029
|
+
for (const [producer, consumer] of [[a, b], [b, a]]) {
|
|
1030
|
+
for (const name of producer.scope?.provides ?? []) {
|
|
1031
|
+
if (consumer.scope?.consumes?.includes(name)) said.push(`Task "${consumer.title}" consumes "${name}", which "${producer.title}" provides, and nothing orders them: a dependency puts the producer first; without one it gets a line when "${producer.title}" lands.`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
return said;
|
|
1037
|
+
}
|
|
1038
|
+
function globRegExp(rule) {
|
|
1039
|
+
const pattern = rule.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\/?|\*/g, (star) => star === "*" ? "[^/]*" : "(?:.*/)?");
|
|
1040
|
+
return new RegExp(`^${pattern}(?:/.*)?$`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
676
1043
|
// src/services/PlannedTask.ts
|
|
677
1044
|
import { v4 as uuidv4 } from "uuid";
|
|
678
1045
|
var MODE_NOT_THE_PLANNERS = `"taskMode" is the user's to choose: set "readOnly": true on a task that must not change files, and leave it out otherwise`;
|
|
@@ -691,6 +1058,28 @@ function notOnAScript(raw) {
|
|
|
691
1058
|
return value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
692
1059
|
});
|
|
693
1060
|
}
|
|
1061
|
+
function notOnADelegated(raw) {
|
|
1062
|
+
return NOT_A_DELEGATED_TASKS_FIELDS.filter((f) => {
|
|
1063
|
+
const value = raw[f];
|
|
1064
|
+
return value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
function delegatedFields(raw, base) {
|
|
1068
|
+
const list = (value) => Array.isArray(value) ? value.map(String) : void 0;
|
|
1069
|
+
const goal = text(raw.goal) ?? base.goal;
|
|
1070
|
+
const budget = (isRecord(raw.budget) ? raw.budget : void 0) ?? base.budget;
|
|
1071
|
+
const grants = list(raw.grants) ?? base.grants;
|
|
1072
|
+
const protectedPaths = list(raw.protectedPaths) ?? base.protectedPaths;
|
|
1073
|
+
const scope = (isRecord(raw.scope) ? raw.scope : void 0) ?? base.scope;
|
|
1074
|
+
return {
|
|
1075
|
+
...goal !== void 0 ? { goal } : {},
|
|
1076
|
+
...budget ? { budget } : {},
|
|
1077
|
+
...grants ? { grants } : {},
|
|
1078
|
+
...protectedPaths ? { protectedPaths } : {},
|
|
1079
|
+
...scope ? { scope } : {},
|
|
1080
|
+
...raw.readOnly === true || raw.readOnly === void 0 && base.readOnly ? { readOnly: true } : {}
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
694
1083
|
function notOnAScriptMessage(fields) {
|
|
695
1084
|
return `is a script task, which runs its "commands" or "script" with no model: leave out ${fields.map((f) => `"${f}"`).join(", ")}`;
|
|
696
1085
|
}
|
|
@@ -751,27 +1140,35 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
751
1140
|
const plannerId = text(raw.id);
|
|
752
1141
|
const id = freshIds || !plannerId ? newId() : plannerId;
|
|
753
1142
|
if (freshIds && plannerId) renamed.set(plannerId, id);
|
|
754
|
-
const
|
|
1143
|
+
const delegating = catalog.delegationRefusal === null;
|
|
1144
|
+
const type = raw.type === "user" || raw.type === "ai" || raw.type === "script" || raw.type === "delegated" ? raw.type : base.type ?? "ai";
|
|
755
1145
|
const ai = type === "ai";
|
|
756
1146
|
const script = type === "script";
|
|
1147
|
+
const delegated = type === "delegated";
|
|
1148
|
+
const sealedScript = script && (catalog.sealsScripts?.({ ...Array.isArray(raw.commands) ? { commands: raw.commands } : {}, ...typeof raw.script === "string" ? { script: raw.script } : {}, ...typeof raw.timeoutMs === "number" ? { timeoutMs: raw.timeoutMs } : {} }) === true || delegating && raw.readOnly === true);
|
|
757
1149
|
const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];
|
|
758
1150
|
const description = text(raw.description) ?? base.description ?? title;
|
|
759
|
-
const notScripts = script ? notOnAScript(raw) : [];
|
|
1151
|
+
const notScripts = script ? notOnAScript(raw).filter((f) => !(sealedScript && f === "readOnly")) : [];
|
|
760
1152
|
if (notScripts.length > 0) errors.push({ taskId: id, field: notScripts[0], message: `${where} ${notOnAScriptMessage(notScripts)}` });
|
|
1153
|
+
if (delegated) {
|
|
1154
|
+
if (!delegating) errors.push({ taskId: id, field: "type", message: catalog.delegationRefusal ?? `${where} is a delegated task, and ${DELEGATION_UNAVAILABLE}.` });
|
|
1155
|
+
const extra = notOnADelegated(raw);
|
|
1156
|
+
if (extra.length > 0) errors.push({ taskId: id, field: extra[0], message: `${where} ${notOnADelegatedMessage(extra)}` });
|
|
1157
|
+
}
|
|
761
1158
|
const body = bodyFields(raw, base);
|
|
762
1159
|
const shapeErrors = scriptBodyErrors({ type, ...body.raw }).filter((e) => body.malformed.has(e.field));
|
|
763
1160
|
for (const e of shapeErrors) errors.push({ taskId: id, field: e.field, message: `${where} ${e.message}` });
|
|
764
1161
|
const loop = loopSpec(raw.loop);
|
|
765
1162
|
if ("error" in loop) errors.push({ taskId: id, field: "loop", message: `${where} ${loop.error}` });
|
|
766
1163
|
if (raw.ops === true && !ai && !script) errors.push({ taskId: id, field: "ops", message: `${where} ${opsPlacementRefusal(type, !!parent)}` });
|
|
767
|
-
if (raw.taskMode !== void 0 && !script) ignored.push({ taskId: id, field: "taskMode", message: `${where}: ${MODE_NOT_THE_PLANNERS}` });
|
|
1164
|
+
if (raw.taskMode !== void 0 && !script && !delegated) ignored.push({ taskId: id, field: "taskMode", message: `${where}: ${MODE_NOT_THE_PLANNERS}` });
|
|
768
1165
|
if (type === "user" && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${MANUAL_NOT_READ_ONLY}` });
|
|
769
1166
|
const ops = raw.ops !== void 0 ? raw.ops === true : base.ops;
|
|
770
1167
|
if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${OPS_NOT_READ_ONLY}` });
|
|
771
1168
|
const asked = typeof raw.readOnly === "boolean" ? raw.readOnly : void 0;
|
|
772
1169
|
const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);
|
|
773
1170
|
if (raw.research !== void 0 && typeof raw.research !== "boolean") errors.push({ taskId: id, field: "research", message: `${where}: "research" must be a boolean` });
|
|
774
|
-
const assignedModel = ai ? modelOf(raw, base.assignedModel) : void 0;
|
|
1171
|
+
const assignedModel = ai || delegated ? modelOf(raw, base.assignedModel) : void 0;
|
|
775
1172
|
const slice = (value) => value === "AFK" || value === "HITL" ? value : void 0;
|
|
776
1173
|
const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : void 0;
|
|
777
1174
|
const sliceType = slice(raw.sliceType) ?? base.sliceType ?? (ai ? parent?.sliceType : void 0);
|
|
@@ -794,7 +1191,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
794
1191
|
type,
|
|
795
1192
|
dependencies: Array.isArray(raw.dependencies) ? raw.dependencies.map(String) : base.dependencies ?? [],
|
|
796
1193
|
// What a merge or split carries over for a runner is nothing a script runs.
|
|
797
|
-
prompt: script ? void 0 : text(raw.prompt) || base.prompt || (ai ? description || title || void 0 : void 0),
|
|
1194
|
+
prompt: script || delegated ? void 0 : text(raw.prompt) || base.prompt || (ai ? description || title || void 0 : void 0),
|
|
798
1195
|
userSteps: Array.isArray(raw.userSteps) ? raw.userSteps.map((s) => isRecord(s) ? s : {}).map((s) => ({ order: Number(s.order ?? 0), instruction: String(s.instruction ?? ""), completed: false })) : base.userSteps,
|
|
799
1196
|
subtasks,
|
|
800
1197
|
assignedModel,
|
|
@@ -802,15 +1199,17 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
802
1199
|
autonomy,
|
|
803
1200
|
sliceType,
|
|
804
1201
|
userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,
|
|
805
|
-
ops: script ? void 0 : ops,
|
|
1202
|
+
ops: script || delegated ? void 0 : ops,
|
|
806
1203
|
research: typeof raw.research === "boolean" ? raw.research : base.research,
|
|
807
|
-
skills: script ? void 0 : from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),
|
|
1204
|
+
skills: script || delegated ? void 0 : from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),
|
|
808
1205
|
...body.fields,
|
|
809
|
-
..."loop" in loop && loop.loop ? { loop: loop.loop } : {}
|
|
1206
|
+
..."loop" in loop && loop.loop ? { loop: loop.loop } : {},
|
|
1207
|
+
...delegated ? delegatedFields(raw, base) : {},
|
|
1208
|
+
...sealedScript ? { readOnly: true } : {}
|
|
810
1209
|
};
|
|
811
|
-
let taskMode = script ? void 0 : base.taskMode;
|
|
1210
|
+
let taskMode = script || delegated ? void 0 : base.taskMode;
|
|
812
1211
|
let landedModel = assignedModel;
|
|
813
|
-
if (ai && catalog.runners.includes(assignedRunner)) {
|
|
1212
|
+
if ((ai || delegated) && catalog.runners.includes(assignedRunner)) {
|
|
814
1213
|
const resolved = resolveAssignment({
|
|
815
1214
|
writer: catalog.models ? "edit" : "plan",
|
|
816
1215
|
task: { ...createTask(fields), taskMode },
|
|
@@ -821,7 +1220,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
821
1220
|
if (!resolved.ok) {
|
|
822
1221
|
errors.push({ taskId: id, field: resolved.field === "taskMode" ? "readOnly" : resolved.field, message: `${where}: ${resolved.error}` });
|
|
823
1222
|
} else {
|
|
824
|
-
taskMode = resolved.task.taskMode;
|
|
1223
|
+
taskMode = delegated ? void 0 : resolved.task.taskMode;
|
|
825
1224
|
landedModel = resolved.task.assignedModel;
|
|
826
1225
|
adjustments.push(...resolved.adjustments);
|
|
827
1226
|
}
|
|
@@ -833,13 +1232,13 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
833
1232
|
}
|
|
834
1233
|
function checkPlanRunners(tasks, runners) {
|
|
835
1234
|
if (!runners) return [];
|
|
836
|
-
return flattenTasksWithParents(tasks).filter(({ task: t }) => t.type === "ai" && !runners.includes(t.assignedRunner)).map(({ task: t, parent }) => ({
|
|
1235
|
+
return flattenTasksWithParents(tasks).filter(({ task: t }) => (t.type === "ai" || t.type === "delegated") && !runners.includes(t.assignedRunner)).map(({ task: t, parent }) => ({
|
|
837
1236
|
taskId: t.id,
|
|
838
1237
|
field: "assignedRunner",
|
|
839
1238
|
message: `${taskWhere(t.title, parent?.title)} has invalid assignedRunner "${t.assignedRunner}". Expected one of: ${runners.join(", ")}`
|
|
840
1239
|
}));
|
|
841
1240
|
}
|
|
842
|
-
function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
1241
|
+
function checkPlanInvariants(tasks, runners, runnerModes, delegation = {}) {
|
|
843
1242
|
const rows = flattenTasksWithParents(tasks);
|
|
844
1243
|
const errors = [];
|
|
845
1244
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -868,6 +1267,10 @@ function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
|
868
1267
|
const extra = notAScriptsFields(t);
|
|
869
1268
|
if (extra.length > 0) fail(extra[0], `${where} ${notOnAScriptMessage(extra)}`);
|
|
870
1269
|
}
|
|
1270
|
+
if (t.type === "delegated") {
|
|
1271
|
+
if (parent !== null) fail("type", `${where} is a delegated task: only a top-level task can be delegated`);
|
|
1272
|
+
if (t.subtasks?.length) fail("subtasks", `${where} is a delegated task, which has no subtasks: its child plan's tasks are its child's own`);
|
|
1273
|
+
}
|
|
871
1274
|
for (const e of scriptBodyErrors(t)) fail(e.field, `${where} ${e.message}`);
|
|
872
1275
|
if (t.ops && t.taskMode !== void 0 && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {
|
|
873
1276
|
fail("taskMode", `${where} is an ops task, which acts in the workspace, so it cannot use the read-only profile`);
|
|
@@ -875,6 +1278,7 @@ function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
|
875
1278
|
}
|
|
876
1279
|
errors.push(...checkPlanRunners(tasks, runners));
|
|
877
1280
|
errors.push(...loopErrors(tasks));
|
|
1281
|
+
errors.push(...delegationErrors(tasks, { ...runnerModes ? { runnerModes } : {}, ...delegation }));
|
|
878
1282
|
for (const { task: t, parent } of rows) {
|
|
879
1283
|
for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {
|
|
880
1284
|
errors.push({ taskId: t.id, field: "dependencies", message: `${taskWhere(t.title, parent?.title)}: ${error}` });
|
|
@@ -908,11 +1312,17 @@ export {
|
|
|
908
1312
|
loopRoundOf,
|
|
909
1313
|
betweenRounds,
|
|
910
1314
|
outOfRounds,
|
|
1315
|
+
loopEnded,
|
|
911
1316
|
checkOf,
|
|
1317
|
+
checksOfBodies,
|
|
912
1318
|
inLoop,
|
|
913
1319
|
loopSpec,
|
|
914
1320
|
loopRemovalRefusal,
|
|
915
1321
|
loopReshapeRefusal,
|
|
1322
|
+
ONE_LINE,
|
|
1323
|
+
splitLines,
|
|
1324
|
+
quotedLine,
|
|
1325
|
+
onOneLine,
|
|
916
1326
|
PLAN_ENVELOPE_KEY,
|
|
917
1327
|
TASK_OPS_ENVELOPE_KEY,
|
|
918
1328
|
TASK_QUERY_ENVELOPE_KEY,
|
|
@@ -924,6 +1334,7 @@ export {
|
|
|
924
1334
|
stripTrailingCommas,
|
|
925
1335
|
escapeControlCharsInStrings,
|
|
926
1336
|
runnerModesFrom,
|
|
1337
|
+
resolveDefaultMode,
|
|
927
1338
|
autonomyLevelLabel,
|
|
928
1339
|
parseAutonomyLevel,
|
|
929
1340
|
parseAutonomyFlag,
|
|
@@ -939,6 +1350,31 @@ export {
|
|
|
939
1350
|
assignableRunners,
|
|
940
1351
|
resolveAssignment,
|
|
941
1352
|
resolveAssignments,
|
|
1353
|
+
GRANTS,
|
|
1354
|
+
FLOOR_GRANTS,
|
|
1355
|
+
SHAPE_LIMITS,
|
|
1356
|
+
mandateRefusalText,
|
|
1357
|
+
rootMandate,
|
|
1358
|
+
heldMandate,
|
|
1359
|
+
narrow,
|
|
1360
|
+
delegatedRequest,
|
|
1361
|
+
COMMAND_GRANTS,
|
|
1362
|
+
allowsCommand,
|
|
1363
|
+
missingGrant,
|
|
1364
|
+
SCRIPT_PAST_THE_RULES,
|
|
1365
|
+
withheldPlannerTools,
|
|
1366
|
+
autonomousAt,
|
|
1367
|
+
rootAuthority,
|
|
1368
|
+
countChildTasks,
|
|
1369
|
+
DELEGATED_FIELDS,
|
|
1370
|
+
notOnADelegatedMessage,
|
|
1371
|
+
checksOf,
|
|
1372
|
+
checksHoldingMerge,
|
|
1373
|
+
checkWords,
|
|
1374
|
+
checksDelegatedTask,
|
|
1375
|
+
planSize,
|
|
1376
|
+
underProtectedPath,
|
|
1377
|
+
scopeOverlaps,
|
|
942
1378
|
MODE_NOT_THE_PLANNERS,
|
|
943
1379
|
MANUAL_NOT_READ_ONLY,
|
|
944
1380
|
OPS_NOT_READ_ONLY,
|
|
@@ -950,4 +1386,4 @@ export {
|
|
|
950
1386
|
checkPlanInvariants,
|
|
951
1387
|
detectCycle
|
|
952
1388
|
};
|
|
953
|
-
//# sourceMappingURL=chunk-
|
|
1389
|
+
//# sourceMappingURL=chunk-2IFKPYPO.mjs.map
|