@ordewell/core 0.12.0 → 0.12.2
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/{ModeResolver-CHsaoV7M.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
- package/dist/{ModeResolver-b_7bPlBS.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
- package/dist/{Task-BTEH-ztX.d.mts → Task-CxGgN6I0.d.mts} +503 -16
- package/dist/{Task-BTEH-ztX.d.ts → Task-CxGgN6I0.d.ts} +503 -16
- package/dist/{chunk-BKTDYGIP.mjs → chunk-2IFKPYPO.mjs} +441 -17
- package/dist/chunk-2IFKPYPO.mjs.map +1 -0
- package/dist/{chunk-BFLVOYNR.mjs → chunk-FX33UKAD.mjs} +8 -6
- package/dist/chunk-FX33UKAD.mjs.map +1 -0
- package/dist/{chunk-F6WS3JLQ.mjs → chunk-PKJTN2PD.mjs} +20 -11
- package/dist/chunk-PKJTN2PD.mjs.map +1 -0
- package/dist/{chunk-HP2LNKST.mjs → chunk-RZ23BMVR.mjs} +345 -134
- 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-BwzyUVIL.d.mts → eventStore-CTCSKS9E.d.ts} +916 -129
- package/dist/{IRunner-DjJkYwko.d.ts → eventStore-CcB8yELZ.d.mts} +916 -129
- package/dist/index.d.mts +677 -97
- package/dist/index.d.ts +677 -97
- package/dist/index.js +8233 -2450
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6951 -1772
- 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 +30 -5
- package/dist/plan-utils.d.ts +30 -5
- package/dist/plan-utils.js +163 -16
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +49 -3
- package/dist/plan-utils.mjs.map +1 -1
- package/dist/{runSettingsText-x-YML2ay.d.mts → runSettingsText-DFVpe6aU.d.mts} +1142 -934
- package/dist/{runSettingsText-CvZ40IPv.d.ts → runSettingsText-lKLDsyli.d.ts} +1142 -934
- package/dist/testing.d.mts +83 -4
- package/dist/testing.d.ts +83 -4
- package/dist/testing.js +142 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +126 -2
- package/dist/testing.mjs.map +1 -1
- package/docker/task.Dockerfile +22 -0
- package/package.json +2 -1
- package/skills/diagnose/SKILL.md +2 -2
- package/skills/explore/SKILL.md +1 -1
- package/dist/chunk-BFLVOYNR.mjs.map +0 -1
- package/dist/chunk-BKTDYGIP.mjs.map +0 -1
- package/dist/chunk-F6WS3JLQ.mjs.map +0 -1
- package/dist/chunk-HP2LNKST.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 { b3 as RunnerId, bp as Task } from './Task-CxGgN6I0.mjs';
|
|
2
|
+
import { R as RunnerModeInfo } from './ModeResolver-CA2fUU9h.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 { b3 as RunnerId, bp as Task } from './Task-CxGgN6I0.js';
|
|
2
|
+
import { R as RunnerModeInfo } from './ModeResolver-B64aUZXJ.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
|
@@ -121,7 +121,7 @@ function createTask(overrides = {}) {
|
|
|
121
121
|
assignedModel: overrides.assignedModel,
|
|
122
122
|
assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],
|
|
123
123
|
// A script task runs in no mode (ADR-0035): one given it would read as a mode it runs in.
|
|
124
|
-
taskMode: overrides.taskMode ?? (type === "script" ? void 0 : "build"),
|
|
124
|
+
taskMode: overrides.taskMode ?? (type === "script" || type === "delegated" ? void 0 : "build"),
|
|
125
125
|
autonomy: overrides.autonomy,
|
|
126
126
|
sliceType: overrides.sliceType,
|
|
127
127
|
userStoriesCovered: overrides.userStoriesCovered,
|
|
@@ -131,6 +131,12 @@ function createTask(overrides = {}) {
|
|
|
131
131
|
...overrides.commands !== void 0 ? { commands: overrides.commands.map((argv) => [...argv]) } : {},
|
|
132
132
|
...overrides.script !== void 0 ? { script: overrides.script } : {},
|
|
133
133
|
...overrides.timeoutMs !== void 0 ? { timeoutMs: overrides.timeoutMs } : {},
|
|
134
|
+
...overrides.goal !== void 0 ? { goal: overrides.goal } : {},
|
|
135
|
+
...overrides.budget !== void 0 ? { budget: { ...overrides.budget } } : {},
|
|
136
|
+
...overrides.grants !== void 0 ? { grants: [...overrides.grants] } : {},
|
|
137
|
+
...overrides.protectedPaths !== void 0 ? { protectedPaths: [...overrides.protectedPaths] } : {},
|
|
138
|
+
...overrides.scope !== void 0 ? { scope: structuredClone(overrides.scope) } : {},
|
|
139
|
+
...overrides.readOnly ? { readOnly: true } : {},
|
|
134
140
|
...overrides.loop ? { loop: { body: [...overrides.loop.body], maxRounds: overrides.loop.maxRounds } } : {},
|
|
135
141
|
...overrides.loopRound !== void 0 ? { loopRound: overrides.loopRound } : {},
|
|
136
142
|
...overrides.research !== void 0 ? { research: overrides.research } : {}
|
|
@@ -299,8 +305,82 @@ function escapeControlCharsInStrings(json) {
|
|
|
299
305
|
return out;
|
|
300
306
|
}
|
|
301
307
|
|
|
302
|
-
// src/services/
|
|
303
|
-
var
|
|
308
|
+
// src/services/Mandate.ts
|
|
309
|
+
var GRANTS = ["observe", "steer", "control", "replan", "approve", "delegate", "effect", "research", "merge"];
|
|
310
|
+
var FLOOR_GRANTS = ["observe", "steer", "control", "replan", "approve", "merge"];
|
|
311
|
+
var SHAPE_LIMITS = {
|
|
312
|
+
depth: { default: 2, hard: 4 },
|
|
313
|
+
delegatedTasks: { default: 8, hard: 30 },
|
|
314
|
+
tasksPerPlan: { default: 30, hard: 100 }
|
|
315
|
+
};
|
|
316
|
+
var DEFAULT_CHILD_WALL_CLOCK_MS = 24 * 60 * 60 * 1e3;
|
|
317
|
+
var DELEGATED_GRANTS = [...FLOOR_GRANTS, "research", "delegate"];
|
|
318
|
+
var lesser = (limit) => (parent, request) => {
|
|
319
|
+
const { hard } = SHAPE_LIMITS[limit];
|
|
320
|
+
const asked = request[limit];
|
|
321
|
+
if (asked !== void 0 && asked > hard) return { refused: { field: `limits.${limit}`, asked: String(asked), there: `a hard limit of ${hard}` } };
|
|
322
|
+
return { value: Math.min(parent[limit], asked ?? parent[limit]) };
|
|
323
|
+
};
|
|
324
|
+
function intersectModels(parent, asked = {}) {
|
|
325
|
+
const models = { ...asked, ...parent };
|
|
326
|
+
for (const runner of Object.keys(parent)) {
|
|
327
|
+
if (asked[runner]) models[runner] = parent[runner].filter((model) => asked[runner].includes(model));
|
|
328
|
+
}
|
|
329
|
+
return models;
|
|
330
|
+
}
|
|
331
|
+
var LIMIT_RULES = {
|
|
332
|
+
tokens: ({ tokens }, { tokens: asked }) => {
|
|
333
|
+
if (asked === void 0 || asked <= 0 || asked > tokens) return { refused: { field: "limits.tokens", asked: asked === void 0 ? "none" : String(asked), there: String(tokens) } };
|
|
334
|
+
return { value: asked };
|
|
335
|
+
},
|
|
336
|
+
wallClockMs: ({ wallClockMs: left }, { wallClockMs: asked }) => {
|
|
337
|
+
if (asked === void 0) return { value: left === null ? DEFAULT_CHILD_WALL_CLOCK_MS : Math.min(left, DEFAULT_CHILD_WALL_CLOCK_MS) };
|
|
338
|
+
if (asked <= 0 || left !== null && asked > left) return { refused: { field: "limits.wallClockMs", asked: String(asked), there: left === null ? "no limit" : String(left) } };
|
|
339
|
+
return { value: asked };
|
|
340
|
+
},
|
|
341
|
+
depth: ({ depth }, { depth: asked }) => {
|
|
342
|
+
if (depth <= 0) return { refused: { field: "limits.depth", asked: "a child", there: "no depth left" } };
|
|
343
|
+
const { hard } = SHAPE_LIMITS.depth;
|
|
344
|
+
if (asked !== void 0 && asked > hard) return { refused: { field: "limits.depth", asked: String(asked), there: `a hard limit of ${hard}` } };
|
|
345
|
+
return { value: Math.min(depth - 1, asked ?? depth - 1) };
|
|
346
|
+
},
|
|
347
|
+
delegatedTasks: lesser("delegatedTasks"),
|
|
348
|
+
tasksPerPlan: lesser("tasksPerPlan"),
|
|
349
|
+
runners: ({ runners }, { runners: asked }) => ({ value: asked ? runners.filter((runner) => asked.includes(runner)) : runners }),
|
|
350
|
+
models: ({ models }, { models: asked }) => ({ value: intersectModels(models, asked) })
|
|
351
|
+
};
|
|
352
|
+
var COMMAND_GRANTS = {
|
|
353
|
+
run_plan: "control",
|
|
354
|
+
run_task: "control",
|
|
355
|
+
stop_task: "control",
|
|
356
|
+
stop_run: "control",
|
|
357
|
+
retry_task: "control",
|
|
358
|
+
message_task: "steer",
|
|
359
|
+
answer_approval: "steer",
|
|
360
|
+
merge_all: "merge",
|
|
361
|
+
skip_task: "control",
|
|
362
|
+
force_start: null,
|
|
363
|
+
mark_complete: null,
|
|
364
|
+
mark_not_done: null,
|
|
365
|
+
revert_task: null,
|
|
366
|
+
interrupt_task: null,
|
|
367
|
+
force_send_queued: null,
|
|
368
|
+
remove_queued: null,
|
|
369
|
+
cancel_resume: null,
|
|
370
|
+
answer_checkpoint: "steer",
|
|
371
|
+
approve_review: null,
|
|
372
|
+
run_script: "control"
|
|
373
|
+
};
|
|
374
|
+
var TOOL_GRANTS = [
|
|
375
|
+
...Object.entries(COMMAND_GRANTS).flatMap(([tool, grant]) => grant ? [[tool, grant]] : []),
|
|
376
|
+
["submit_plan", "replan"],
|
|
377
|
+
["edit_plan", "replan"]
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
// src/models/lineBreaks.ts
|
|
381
|
+
var BREAKS = "\\n\\v\\f\\r\\u0085\\u2028\\u2029";
|
|
382
|
+
var LINE_BREAK = new RegExp(`\\r\\n|[${BREAKS}]`);
|
|
383
|
+
var ONE_LINE = new RegExp(`^[^${BREAKS}]*$`);
|
|
304
384
|
|
|
305
385
|
// src/services/ModeResolver.ts
|
|
306
386
|
function resolveDefaultMode(modes, autonomousDefault = false) {
|
|
@@ -349,6 +429,142 @@ function readOnlyRunners(runnerModes) {
|
|
|
349
429
|
return Object.entries(runnerModes).filter(([, ms]) => readOnlyMode(ms)).map(([id]) => id);
|
|
350
430
|
}
|
|
351
431
|
|
|
432
|
+
// src/models/Delegation.ts
|
|
433
|
+
var SCOPE_LIMITS = { item: 200, items: 50 };
|
|
434
|
+
var NOT_A_DELEGATED_TASKS_FIELDS = ["prompt", "taskMode", "skills", "userSteps", "ops", "commands", "script", "timeoutMs", "research"];
|
|
435
|
+
var DELEGATION_UNAVAILABLE = "delegation is not available in this session";
|
|
436
|
+
function notOnADelegatedMessage(fields) {
|
|
437
|
+
return `is a delegated task, which holds a goal for a child plan: leave out ${fields.map((f) => `"${f}"`).join(", ")}`;
|
|
438
|
+
}
|
|
439
|
+
function isBudget(value) {
|
|
440
|
+
if (typeof value !== "object" || value === null) return false;
|
|
441
|
+
const { tokens } = value;
|
|
442
|
+
return typeof tokens === "number" && Number.isInteger(tokens) && tokens > 0;
|
|
443
|
+
}
|
|
444
|
+
function wallClockOk(budget) {
|
|
445
|
+
const { wallClockMs } = budget;
|
|
446
|
+
return wallClockMs === void 0 || Number.isInteger(wallClockMs) && wallClockMs > 0;
|
|
447
|
+
}
|
|
448
|
+
function readsOnly(task, runnerModes) {
|
|
449
|
+
if (task.type !== "ai") return false;
|
|
450
|
+
const mode = runnerModes ? taggedReadOnlyMode(runnerModes[task.assignedRunner]) : "plan";
|
|
451
|
+
return mode !== void 0 && task.taskMode === mode;
|
|
452
|
+
}
|
|
453
|
+
function atAGate(task) {
|
|
454
|
+
return task.type === "script" && !task.readOnly || task.ops === true;
|
|
455
|
+
}
|
|
456
|
+
function reaches(tasks, from, target) {
|
|
457
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
458
|
+
const seen = /* @__PURE__ */ new Set();
|
|
459
|
+
const walk = (task) => task.dependencies.some((id) => {
|
|
460
|
+
if (seen.has(id)) return false;
|
|
461
|
+
seen.add(id);
|
|
462
|
+
const dep = byId.get(id);
|
|
463
|
+
return dep !== void 0 && (target(dep) || walk(dep));
|
|
464
|
+
});
|
|
465
|
+
return walk(from);
|
|
466
|
+
}
|
|
467
|
+
function delegationErrors(tasks, rules = {}) {
|
|
468
|
+
const errors = [];
|
|
469
|
+
const delegated = tasks.filter((t) => t.type === "delegated");
|
|
470
|
+
const fail = (task, field, message) => errors.push({ taskId: task.id, field, message });
|
|
471
|
+
for (const task of delegated) {
|
|
472
|
+
const where = `Task "${task.title}"`;
|
|
473
|
+
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`);
|
|
474
|
+
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`);
|
|
475
|
+
else if (!wallClockOk(task.budget)) fail(task, "budget", `${where} has a "wallClockMs" that is not a whole number of milliseconds above 0`);
|
|
476
|
+
const paths = task.protectedPaths;
|
|
477
|
+
if (!task.readOnly && (!Array.isArray(paths) || paths.length === 0 || !paths.every((p) => typeof p === "string" && p.trim()))) {
|
|
478
|
+
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`);
|
|
479
|
+
}
|
|
480
|
+
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`);
|
|
481
|
+
const unknown = (task.grants ?? []).filter((grant) => !GRANTS.includes(grant));
|
|
482
|
+
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(", ")}`);
|
|
483
|
+
const dependents = tasks.filter((t) => t.dependencies.includes(task.id));
|
|
484
|
+
if (task.readOnly) {
|
|
485
|
+
for (const script of dependents.filter((t) => t.type === "script")) {
|
|
486
|
+
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`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!dependents.some((t) => judges(t, task, rules.runnerModes))) {
|
|
490
|
+
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`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for (const task of tasks.filter((t) => t.type === "script" && t.readOnly)) {
|
|
494
|
+
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`);
|
|
495
|
+
}
|
|
496
|
+
for (const check of tasks.filter((t) => t.loop)) {
|
|
497
|
+
if (!reaches(tasks, check, (t) => t.type === "delegated")) continue;
|
|
498
|
+
for (const id of check.loop?.body ?? []) {
|
|
499
|
+
const body = tasks.find((t) => t.id === id);
|
|
500
|
+
if (body && atAGate(body)) {
|
|
501
|
+
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`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return errors;
|
|
506
|
+
}
|
|
507
|
+
function scopeOk(scope) {
|
|
508
|
+
if (typeof scope !== "object" || scope === null) return false;
|
|
509
|
+
const { paths, provides, consumes } = scope;
|
|
510
|
+
const itemOk = (item) => typeof item === "string" && item.trim().length > 0 && item.length <= SCOPE_LIMITS.item && ONE_LINE.test(item);
|
|
511
|
+
return [paths, provides, consumes].every((list) => list === void 0 || Array.isArray(list) && list.length <= SCOPE_LIMITS.items && list.every(itemOk));
|
|
512
|
+
}
|
|
513
|
+
function judges(task, delegated, runnerModes) {
|
|
514
|
+
return readsOnly(task, runnerModes) || !delegated.readOnly && task.type === "script" && task.readOnly === true;
|
|
515
|
+
}
|
|
516
|
+
function underProtectedPath(file, protectedPath) {
|
|
517
|
+
const rule = protectedPath.trim().replace(/^\.\//, "").replace(/\\/g, "/");
|
|
518
|
+
if (!rule) return false;
|
|
519
|
+
if (rule.includes("*")) return globRegExp(rule).test(file);
|
|
520
|
+
const bare = rule.replace(/\/+$/, "");
|
|
521
|
+
return file === bare || file.startsWith(`${bare}/`);
|
|
522
|
+
}
|
|
523
|
+
function fixedStart(rule) {
|
|
524
|
+
const bare = rule.trim().replace(/^\.\//, "").replace(/\\/g, "/");
|
|
525
|
+
const star = bare.indexOf("*");
|
|
526
|
+
return star === -1 ? bare : bare.slice(0, star);
|
|
527
|
+
}
|
|
528
|
+
function pathsMeet(a, b) {
|
|
529
|
+
if (underProtectedPath(a.replace(/\/+$/, ""), b) || underProtectedPath(b.replace(/\/+$/, ""), a)) return true;
|
|
530
|
+
if (!a.includes("*") || !b.includes("*")) return false;
|
|
531
|
+
const [one, other] = [fixedStart(a), fixedStart(b)];
|
|
532
|
+
return one.startsWith(other) || other.startsWith(one);
|
|
533
|
+
}
|
|
534
|
+
function narrower(a, b) {
|
|
535
|
+
return b.includes("*") || !a.includes("*") && a.length >= b.length ? a : b;
|
|
536
|
+
}
|
|
537
|
+
function ordered(tasks, a, b) {
|
|
538
|
+
return reaches(tasks, a, (t) => t.id === b.id) || reaches(tasks, b, (t) => t.id === a.id);
|
|
539
|
+
}
|
|
540
|
+
function scopeOverlaps(tasks) {
|
|
541
|
+
const scoped = tasks.filter((t) => t.type === "delegated" && t.scope);
|
|
542
|
+
const said = [];
|
|
543
|
+
scoped.forEach((a, i) => {
|
|
544
|
+
for (const b of scoped.slice(i + 1)) {
|
|
545
|
+
if (ordered(tasks, a, b)) continue;
|
|
546
|
+
const declared = (a.scope?.paths ?? []).flatMap((one) => (b.scope?.paths ?? []).filter((other) => pathsMeet(one, other)).map((other) => narrower(one, other)));
|
|
547
|
+
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.`);
|
|
548
|
+
for (const name of a.scope?.provides ?? []) {
|
|
549
|
+
if (b.scope?.provides?.includes(name)) said.push(`Tasks "${a.title}" and "${b.title}" both provide "${name}", and nothing orders them: name one owner.`);
|
|
550
|
+
}
|
|
551
|
+
for (const [producer, consumer] of [[a, b], [b, a]]) {
|
|
552
|
+
for (const name of producer.scope?.provides ?? []) {
|
|
553
|
+
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.`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
return said;
|
|
559
|
+
}
|
|
560
|
+
function globRegExp(rule) {
|
|
561
|
+
const pattern = rule.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\/?|\*/g, (star) => star === "*" ? "[^/]*" : "(?:.*/)?");
|
|
562
|
+
return new RegExp(`^${pattern}(?:/.*)?$`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/services/PlannedTask.ts
|
|
566
|
+
var import_uuid2 = require("uuid");
|
|
567
|
+
|
|
352
568
|
// src/services/ModelAllowlistResolver.ts
|
|
353
569
|
function effectiveAllowlist(allowlist, runner, modelsByRunner) {
|
|
354
570
|
if (!allowlist || allowlist.length === 0) return void 0;
|
|
@@ -433,8 +649,8 @@ function resolveAssignment(write) {
|
|
|
433
649
|
const live = writer !== "runner" && !!before && !!catalog.liveRunner?.(before.id);
|
|
434
650
|
if (live && moved) return { ok: false, field: "assignedRunner", error: liveRunnerRefusal({ title }) };
|
|
435
651
|
if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: "assignedModel", error: liveModelRefusal({ title }) };
|
|
436
|
-
if (written.type !== "ai") return { ok: true, task: written, adjustments: [] };
|
|
437
|
-
const mode = modeFor(write, written, moved);
|
|
652
|
+
if (written.type !== "ai" && written.type !== "delegated") return { ok: true, task: written, adjustments: [] };
|
|
653
|
+
const mode = written.type === "delegated" ? { ok: true, mode: void 0 } : modeFor(write, written, moved);
|
|
438
654
|
if (!mode.ok) return { ok: false, field: "taskMode", error: mode.error };
|
|
439
655
|
const model = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);
|
|
440
656
|
if (!model.ok) return { ok: false, field: "assignedModel", error: model.error };
|
|
@@ -578,6 +794,7 @@ function loopErrors(tasks) {
|
|
|
578
794
|
const fail = (message) => errors.push({ taskId: check.id, field: "loop", message: `${where} ${message}` });
|
|
579
795
|
if (parent) fail(`has a "loop", but only a top-level task can be a loop's check`);
|
|
580
796
|
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");
|
|
797
|
+
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");
|
|
581
798
|
if (check.subtasks?.length) fail("is a loop's check, which has no subtasks: each round runs it again whole");
|
|
582
799
|
if (!isRoundCount(loop.maxRounds)) fail(roundsMessage(loop.maxRounds));
|
|
583
800
|
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');
|
|
@@ -610,6 +827,7 @@ function loopErrors(tasks) {
|
|
|
610
827
|
bodyOwner.set(id, check);
|
|
611
828
|
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`);
|
|
612
829
|
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`);
|
|
830
|
+
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`);
|
|
613
831
|
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`);
|
|
614
832
|
}
|
|
615
833
|
}
|
|
@@ -653,6 +871,28 @@ function notOnAScript(raw) {
|
|
|
653
871
|
return value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
654
872
|
});
|
|
655
873
|
}
|
|
874
|
+
function notOnADelegated(raw) {
|
|
875
|
+
return NOT_A_DELEGATED_TASKS_FIELDS.filter((f) => {
|
|
876
|
+
const value = raw[f];
|
|
877
|
+
return value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
function delegatedFields(raw, base) {
|
|
881
|
+
const list = (value) => Array.isArray(value) ? value.map(String) : void 0;
|
|
882
|
+
const goal = text(raw.goal) ?? base.goal;
|
|
883
|
+
const budget = (isRecord(raw.budget) ? raw.budget : void 0) ?? base.budget;
|
|
884
|
+
const grants = list(raw.grants) ?? base.grants;
|
|
885
|
+
const protectedPaths = list(raw.protectedPaths) ?? base.protectedPaths;
|
|
886
|
+
const scope = (isRecord(raw.scope) ? raw.scope : void 0) ?? base.scope;
|
|
887
|
+
return {
|
|
888
|
+
...goal !== void 0 ? { goal } : {},
|
|
889
|
+
...budget ? { budget } : {},
|
|
890
|
+
...grants ? { grants } : {},
|
|
891
|
+
...protectedPaths ? { protectedPaths } : {},
|
|
892
|
+
...scope ? { scope } : {},
|
|
893
|
+
...raw.readOnly === true || raw.readOnly === void 0 && base.readOnly ? { readOnly: true } : {}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
656
896
|
function notOnAScriptMessage(fields) {
|
|
657
897
|
return `is a script task, which runs its "commands" or "script" with no model: leave out ${fields.map((f) => `"${f}"`).join(", ")}`;
|
|
658
898
|
}
|
|
@@ -710,27 +950,35 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
710
950
|
const plannerId = text(raw.id);
|
|
711
951
|
const id = freshIds || !plannerId ? newId() : plannerId;
|
|
712
952
|
if (freshIds && plannerId) renamed.set(plannerId, id);
|
|
713
|
-
const
|
|
953
|
+
const delegating = catalog.delegationRefusal === null;
|
|
954
|
+
const type = raw.type === "user" || raw.type === "ai" || raw.type === "script" || raw.type === "delegated" ? raw.type : base.type ?? "ai";
|
|
714
955
|
const ai = type === "ai";
|
|
715
956
|
const script = type === "script";
|
|
957
|
+
const delegated = type === "delegated";
|
|
958
|
+
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);
|
|
716
959
|
const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];
|
|
717
960
|
const description = text(raw.description) ?? base.description ?? title;
|
|
718
|
-
const notScripts = script ? notOnAScript(raw) : [];
|
|
961
|
+
const notScripts = script ? notOnAScript(raw).filter((f) => !(sealedScript && f === "readOnly")) : [];
|
|
719
962
|
if (notScripts.length > 0) errors.push({ taskId: id, field: notScripts[0], message: `${where} ${notOnAScriptMessage(notScripts)}` });
|
|
963
|
+
if (delegated) {
|
|
964
|
+
if (!delegating) errors.push({ taskId: id, field: "type", message: catalog.delegationRefusal ?? `${where} is a delegated task, and ${DELEGATION_UNAVAILABLE}.` });
|
|
965
|
+
const extra = notOnADelegated(raw);
|
|
966
|
+
if (extra.length > 0) errors.push({ taskId: id, field: extra[0], message: `${where} ${notOnADelegatedMessage(extra)}` });
|
|
967
|
+
}
|
|
720
968
|
const body = bodyFields(raw, base);
|
|
721
969
|
const shapeErrors = scriptBodyErrors({ type, ...body.raw }).filter((e) => body.malformed.has(e.field));
|
|
722
970
|
for (const e of shapeErrors) errors.push({ taskId: id, field: e.field, message: `${where} ${e.message}` });
|
|
723
971
|
const loop = loopSpec(raw.loop);
|
|
724
972
|
if ("error" in loop) errors.push({ taskId: id, field: "loop", message: `${where} ${loop.error}` });
|
|
725
973
|
if (raw.ops === true && !ai && !script) errors.push({ taskId: id, field: "ops", message: `${where} ${opsPlacementRefusal(type, !!parent)}` });
|
|
726
|
-
if (raw.taskMode !== void 0 && !script) ignored.push({ taskId: id, field: "taskMode", message: `${where}: ${MODE_NOT_THE_PLANNERS}` });
|
|
974
|
+
if (raw.taskMode !== void 0 && !script && !delegated) ignored.push({ taskId: id, field: "taskMode", message: `${where}: ${MODE_NOT_THE_PLANNERS}` });
|
|
727
975
|
if (type === "user" && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${MANUAL_NOT_READ_ONLY}` });
|
|
728
976
|
const ops = raw.ops !== void 0 ? raw.ops === true : base.ops;
|
|
729
977
|
if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${OPS_NOT_READ_ONLY}` });
|
|
730
978
|
const asked = typeof raw.readOnly === "boolean" ? raw.readOnly : void 0;
|
|
731
979
|
const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);
|
|
732
980
|
if (raw.research !== void 0 && typeof raw.research !== "boolean") errors.push({ taskId: id, field: "research", message: `${where}: "research" must be a boolean` });
|
|
733
|
-
const assignedModel = ai ? modelOf(raw, base.assignedModel) : void 0;
|
|
981
|
+
const assignedModel = ai || delegated ? modelOf(raw, base.assignedModel) : void 0;
|
|
734
982
|
const slice = (value) => value === "AFK" || value === "HITL" ? value : void 0;
|
|
735
983
|
const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : void 0;
|
|
736
984
|
const sliceType = slice(raw.sliceType) ?? base.sliceType ?? (ai ? parent?.sliceType : void 0);
|
|
@@ -753,7 +1001,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
753
1001
|
type,
|
|
754
1002
|
dependencies: Array.isArray(raw.dependencies) ? raw.dependencies.map(String) : base.dependencies ?? [],
|
|
755
1003
|
// What a merge or split carries over for a runner is nothing a script runs.
|
|
756
|
-
prompt: script ? void 0 : text(raw.prompt) || base.prompt || (ai ? description || title || void 0 : void 0),
|
|
1004
|
+
prompt: script || delegated ? void 0 : text(raw.prompt) || base.prompt || (ai ? description || title || void 0 : void 0),
|
|
757
1005
|
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,
|
|
758
1006
|
subtasks,
|
|
759
1007
|
assignedModel,
|
|
@@ -761,15 +1009,17 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
761
1009
|
autonomy,
|
|
762
1010
|
sliceType,
|
|
763
1011
|
userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,
|
|
764
|
-
ops: script ? void 0 : ops,
|
|
1012
|
+
ops: script || delegated ? void 0 : ops,
|
|
765
1013
|
research: typeof raw.research === "boolean" ? raw.research : base.research,
|
|
766
|
-
skills: script ? void 0 : from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),
|
|
1014
|
+
skills: script || delegated ? void 0 : from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),
|
|
767
1015
|
...body.fields,
|
|
768
|
-
..."loop" in loop && loop.loop ? { loop: loop.loop } : {}
|
|
1016
|
+
..."loop" in loop && loop.loop ? { loop: loop.loop } : {},
|
|
1017
|
+
...delegated ? delegatedFields(raw, base) : {},
|
|
1018
|
+
...sealedScript ? { readOnly: true } : {}
|
|
769
1019
|
};
|
|
770
|
-
let taskMode = script ? void 0 : base.taskMode;
|
|
1020
|
+
let taskMode = script || delegated ? void 0 : base.taskMode;
|
|
771
1021
|
let landedModel = assignedModel;
|
|
772
|
-
if (ai && catalog.runners.includes(assignedRunner)) {
|
|
1022
|
+
if ((ai || delegated) && catalog.runners.includes(assignedRunner)) {
|
|
773
1023
|
const resolved = resolveAssignment({
|
|
774
1024
|
writer: catalog.models ? "edit" : "plan",
|
|
775
1025
|
task: { ...createTask(fields), taskMode },
|
|
@@ -780,7 +1030,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
780
1030
|
if (!resolved.ok) {
|
|
781
1031
|
errors.push({ taskId: id, field: resolved.field === "taskMode" ? "readOnly" : resolved.field, message: `${where}: ${resolved.error}` });
|
|
782
1032
|
} else {
|
|
783
|
-
taskMode = resolved.task.taskMode;
|
|
1033
|
+
taskMode = delegated ? void 0 : resolved.task.taskMode;
|
|
784
1034
|
landedModel = resolved.task.assignedModel;
|
|
785
1035
|
adjustments.push(...resolved.adjustments);
|
|
786
1036
|
}
|
|
@@ -792,13 +1042,13 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
792
1042
|
}
|
|
793
1043
|
function checkPlanRunners(tasks, runners) {
|
|
794
1044
|
if (!runners) return [];
|
|
795
|
-
return flattenTasksWithParents(tasks).filter(({ task: t }) => t.type === "ai" && !runners.includes(t.assignedRunner)).map(({ task: t, parent }) => ({
|
|
1045
|
+
return flattenTasksWithParents(tasks).filter(({ task: t }) => (t.type === "ai" || t.type === "delegated") && !runners.includes(t.assignedRunner)).map(({ task: t, parent }) => ({
|
|
796
1046
|
taskId: t.id,
|
|
797
1047
|
field: "assignedRunner",
|
|
798
1048
|
message: `${taskWhere(t.title, parent?.title)} has invalid assignedRunner "${t.assignedRunner}". Expected one of: ${runners.join(", ")}`
|
|
799
1049
|
}));
|
|
800
1050
|
}
|
|
801
|
-
function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
1051
|
+
function checkPlanInvariants(tasks, runners, runnerModes, delegation = {}) {
|
|
802
1052
|
const rows = flattenTasksWithParents(tasks);
|
|
803
1053
|
const errors = [];
|
|
804
1054
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -827,6 +1077,10 @@ function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
|
827
1077
|
const extra = notAScriptsFields(t);
|
|
828
1078
|
if (extra.length > 0) fail(extra[0], `${where} ${notOnAScriptMessage(extra)}`);
|
|
829
1079
|
}
|
|
1080
|
+
if (t.type === "delegated") {
|
|
1081
|
+
if (parent !== null) fail("type", `${where} is a delegated task: only a top-level task can be delegated`);
|
|
1082
|
+
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`);
|
|
1083
|
+
}
|
|
830
1084
|
for (const e of scriptBodyErrors(t)) fail(e.field, `${where} ${e.message}`);
|
|
831
1085
|
if (t.ops && t.taskMode !== void 0 && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {
|
|
832
1086
|
fail("taskMode", `${where} is an ops task, which acts in the workspace, so it cannot use the read-only profile`);
|
|
@@ -834,6 +1088,7 @@ function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
|
834
1088
|
}
|
|
835
1089
|
errors.push(...checkPlanRunners(tasks, runners));
|
|
836
1090
|
errors.push(...loopErrors(tasks));
|
|
1091
|
+
errors.push(...delegationErrors(tasks, { ...runnerModes ? { runnerModes } : {}, ...delegation }));
|
|
837
1092
|
for (const { task: t, parent } of rows) {
|
|
838
1093
|
for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {
|
|
839
1094
|
errors.push({ taskId: t.id, field: "dependencies", message: `${taskWhere(t.title, parent?.title)}: ${error}` });
|
|
@@ -883,7 +1138,7 @@ function parsePlanObject(json, raw, runners, runnerModes, autonomousDefault = fa
|
|
|
883
1138
|
const [first] = result.errors;
|
|
884
1139
|
throw new PlanParseError(first.message, raw, { semantic: first.taskId !== void 0 });
|
|
885
1140
|
}
|
|
886
|
-
function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = false) {
|
|
1141
|
+
function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = false, delegation = {}) {
|
|
887
1142
|
const rawTasks = isRecord2(obj) ? obj.tasks : void 0;
|
|
888
1143
|
if (!Array.isArray(rawTasks)) {
|
|
889
1144
|
return { ok: false, errors: [{ field: "tasks", message: "Invalid plan: missing tasks array" }] };
|
|
@@ -891,7 +1146,7 @@ function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = false)
|
|
|
891
1146
|
if (rawTasks.length === 0) {
|
|
892
1147
|
return { ok: false, errors: [{ field: "tasks", message: "Plan contained no tasks" }] };
|
|
893
1148
|
}
|
|
894
|
-
const planned = rawTasks.map((t) => plannedTask(t, { runners, runnerModes, autonomousDefault }));
|
|
1149
|
+
const planned = rawTasks.map((t) => plannedTask(t, { runners, runnerModes, autonomousDefault, ...delegation }));
|
|
895
1150
|
const tasks = planned.map((p) => p.task);
|
|
896
1151
|
const errors = [
|
|
897
1152
|
...sliceRequired(tasks),
|
|
@@ -902,7 +1157,8 @@ function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = false)
|
|
|
902
1157
|
const ignored = planned.flatMap((p) => p.ignored);
|
|
903
1158
|
const warnings = [
|
|
904
1159
|
...ignored.some((i) => i.field === "taskMode") ? [`"taskMode" is not yours to set and was ignored: each task runs in its runner's mode for the user's autonomy level, or its read-only mode when you set "readOnly": true.`] : [],
|
|
905
|
-
...ignored.filter((i) => i.field === "readOnly").map((i) => `${i.message}: it was ignored.`)
|
|
1160
|
+
...ignored.filter((i) => i.field === "readOnly").map((i) => `${i.message}: it was ignored.`),
|
|
1161
|
+
...scopeOverlaps(tasks)
|
|
906
1162
|
];
|
|
907
1163
|
return warnings.length > 0 ? { ok: true, tasks, warnings } : { ok: true, tasks };
|
|
908
1164
|
}
|