@ordewell/core 0.10.11 → 0.11.0
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 +37 -0
- package/NOTICE +71 -0
- package/README.md +1 -1
- package/dist/{IFileSystem-1PqRCjhu.d.mts → IRunner-B5l9PCTv.d.ts} +926 -102
- package/dist/{IFileSystem-lTRL_G9Z.d.ts → IRunner-DBgDx_Mr.d.mts} +926 -102
- package/dist/{ModeResolver-CoJAJD-f.d.ts → ModeResolver-BDAgouuE.d.mts} +9 -2
- package/dist/{ModeResolver-6TQXVsde.d.mts → ModeResolver-DyXeZ9ib.d.ts} +9 -2
- package/dist/{Task-CfBM0wkV.d.mts → Task-BL4Idm9r.d.mts} +656 -26
- package/dist/{Task-CfBM0wkV.d.ts → Task-BL4Idm9r.d.ts} +656 -26
- package/dist/{chunk-ESKK5I4F.mjs → chunk-4QXOWEGC.mjs} +1888 -334
- package/dist/chunk-4QXOWEGC.mjs.map +1 -0
- package/dist/{chunk-KWS3VEER.mjs → chunk-MYXMIVWA.mjs} +6 -6
- package/dist/chunk-MYXMIVWA.mjs.map +1 -0
- package/dist/{chunk-PFH7WFIB.mjs → chunk-QTZ5XTBY.mjs} +228 -15
- package/dist/chunk-QTZ5XTBY.mjs.map +1 -0
- package/dist/{chunk-24EL4Z2W.mjs → chunk-YAFNYNFX.mjs} +342 -36
- package/dist/chunk-YAFNYNFX.mjs.map +1 -0
- package/dist/index.d.mts +1490 -769
- package/dist/index.d.ts +1490 -769
- package/dist/index.js +32009 -20354
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +27785 -17956
- 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 +232 -25
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +1156 -96
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +144 -8
- package/dist/{runSettingsText-Jb_efnI4.d.mts → runSettingsText-BKwWs052.d.mts} +2882 -621
- package/dist/{runSettingsText-C5uv2axI.d.ts → runSettingsText-CAXiTpsj.d.ts} +2882 -621
- package/dist/testing.d.mts +10 -4
- package/dist/testing.d.ts +10 -4
- package/dist/testing.js +30 -7
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +26 -7
- package/dist/testing.mjs.map +1 -1
- package/package.json +7 -3
- package/skills/diagnose/SKILL.md +52 -0
- package/skills/explore/SKILL.md +65 -0
- package/dist/chunk-24EL4Z2W.mjs.map +0 -1
- package/dist/chunk-ESKK5I4F.mjs.map +0 -1
- package/dist/chunk-KWS3VEER.mjs.map +0 -1
- package/dist/chunk-NJGID6BG.mjs +0 -115
- package/dist/chunk-NJGID6BG.mjs.map +0 -1
- package/dist/chunk-PFH7WFIB.mjs.map +0 -1
|
@@ -7,7 +7,221 @@ import {
|
|
|
7
7
|
flattenTasksWithParents,
|
|
8
8
|
inheritedSkills,
|
|
9
9
|
skillNames
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-QTZ5XTBY.mjs";
|
|
11
|
+
|
|
12
|
+
// src/models/ScriptTask.ts
|
|
13
|
+
var DEFAULT_SCRIPT_TIMEOUT_MS = 10 * 6e4;
|
|
14
|
+
var MAX_SCRIPT_TIMEOUT_MS = 24 * 60 * 6e4;
|
|
15
|
+
var SCRIPT_BODY_FIELDS = ["commands", "script", "timeoutMs"];
|
|
16
|
+
var NOT_A_SCRIPTS_FIELDS = ["assignedModel", "taskMode", "prompt", "skills", "userSteps", "ops"];
|
|
17
|
+
function scriptBodyOf(task) {
|
|
18
|
+
if (task.type !== "script" || scriptBodyErrors(task).length > 0) return null;
|
|
19
|
+
const timeoutMs = task.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS;
|
|
20
|
+
return task.commands ? { commands: task.commands.map((argv) => [...argv]), timeoutMs } : { script: task.script ?? "", timeoutMs };
|
|
21
|
+
}
|
|
22
|
+
function sameScriptBody(a, b) {
|
|
23
|
+
return JSON.stringify(bodyKey(a)) === JSON.stringify(bodyKey(b));
|
|
24
|
+
}
|
|
25
|
+
function rewrittenScripts(before, after) {
|
|
26
|
+
const prior = new Map(flattenTasks(before).map((t) => [t.id, t]));
|
|
27
|
+
return flattenTasks(after).filter((t) => {
|
|
28
|
+
const was = prior.get(t.id);
|
|
29
|
+
return was !== void 0 && (t.type === "script" || was.type === "script") && !sameScriptBody(was, t);
|
|
30
|
+
}).map((t) => t.id);
|
|
31
|
+
}
|
|
32
|
+
function bodyKey(task) {
|
|
33
|
+
return task.type === "script" ? [task.commands ?? null, task.script ?? null, task.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS] : null;
|
|
34
|
+
}
|
|
35
|
+
function scriptBodyErrors(task) {
|
|
36
|
+
if (task.type !== "script") {
|
|
37
|
+
return SCRIPT_BODY_FIELDS.filter((f) => task[f] !== void 0).map((field) => ({ field, message: `has "${field}", which only a script task has: set "type": "script", or leave it out` }));
|
|
38
|
+
}
|
|
39
|
+
const errors = [];
|
|
40
|
+
const { commands, script, timeoutMs } = task;
|
|
41
|
+
if (commands !== void 0 && script !== void 0) {
|
|
42
|
+
errors.push({ field: "commands", message: 'has both "commands" and "script": a script task runs exactly one of them' });
|
|
43
|
+
} else if (commands === void 0 && script === void 0) {
|
|
44
|
+
errors.push({ field: "commands", message: 'is a script task with neither "commands" nor "script": give it the argument vectors to run, or the script' });
|
|
45
|
+
}
|
|
46
|
+
if (commands !== void 0) errors.push(...commandsErrors(commands));
|
|
47
|
+
if (script !== void 0 && (typeof script !== "string" || !script.trim())) {
|
|
48
|
+
errors.push({ field: "script", message: 'has an empty "script": write the script it runs' });
|
|
49
|
+
}
|
|
50
|
+
if (timeoutMs !== void 0 && !(typeof timeoutMs === "number" && Number.isInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= MAX_SCRIPT_TIMEOUT_MS)) {
|
|
51
|
+
errors.push({ field: "timeoutMs", message: `has "timeoutMs" ${JSON.stringify(timeoutMs)}: the time limit is a whole number of milliseconds from 1 to ${MAX_SCRIPT_TIMEOUT_MS}, or left out for ten minutes` });
|
|
52
|
+
}
|
|
53
|
+
return errors;
|
|
54
|
+
}
|
|
55
|
+
function commandsErrors(commands) {
|
|
56
|
+
const shape = 'a list of argument vectors, each a list of strings, like [["git", "push", "origin", "main"]]';
|
|
57
|
+
if (!Array.isArray(commands) || commands.length === 0) return [{ field: "commands", message: `has "commands" that is not ${shape}` }];
|
|
58
|
+
for (const [i, argv] of commands.entries()) {
|
|
59
|
+
const n = i + 1;
|
|
60
|
+
if (!Array.isArray(argv) || !argv.every((word) => typeof word === "string")) {
|
|
61
|
+
return [{ field: "commands", message: `has command ${n} that is not an argument vector: "commands" is ${shape}` }];
|
|
62
|
+
}
|
|
63
|
+
if (argv.length === 0) return [{ field: "commands", message: `has command ${n} as an empty argument vector: name the program it runs` }];
|
|
64
|
+
if (!argv[0].trim()) return [{ field: "commands", message: `has command ${n} naming no program: its first word is the program it runs` }];
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
function notAScriptsFields(task) {
|
|
69
|
+
return NOT_A_SCRIPTS_FIELDS.filter((f) => {
|
|
70
|
+
const value = task[f];
|
|
71
|
+
return value !== void 0 && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function clearedBodyFields(changes) {
|
|
75
|
+
for (const field of SCRIPT_BODY_FIELDS) if (changes[field] === null) changes[field] = void 0;
|
|
76
|
+
return changes;
|
|
77
|
+
}
|
|
78
|
+
var PLAIN_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
79
|
+
var UNSEEN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
|
|
80
|
+
function commandLine(argv) {
|
|
81
|
+
return argv.map(quotedWord).join(" ");
|
|
82
|
+
}
|
|
83
|
+
function quotedWord(word) {
|
|
84
|
+
if (PLAIN_WORD.test(word)) return word;
|
|
85
|
+
if (!UNSEEN.test(word)) return `'${word.replace(/'/g, `'\\''`)}'`;
|
|
86
|
+
return `$'${[...word].map(escaped).join("")}'`;
|
|
87
|
+
}
|
|
88
|
+
var ESCAPES = { "\\": "\\\\", "'": "\\'", "\n": "\\n", "\r": "\\r", " ": "\\t" };
|
|
89
|
+
function escaped(char) {
|
|
90
|
+
return ESCAPES[char] ?? (UNSEEN.test(char) ? codeOf(char) : char);
|
|
91
|
+
}
|
|
92
|
+
function codeOf(char) {
|
|
93
|
+
const code = char.codePointAt(0) ?? 0;
|
|
94
|
+
const hex = code.toString(16).toUpperCase();
|
|
95
|
+
return code <= 255 ? `\\x${hex.padStart(2, "0")}` : code <= 65535 ? `\\u${hex.padStart(4, "0")}` : `\\U${hex.padStart(8, "0")}`;
|
|
96
|
+
}
|
|
97
|
+
function scriptBodyText(body) {
|
|
98
|
+
const shown = "commands" in body ? body.commands.map(commandLine) : [body.script.replace(/\r\n|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (char) => char === "\r\n" || char === "\n" || char === " " ? char : `<U+${(char.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}>`)];
|
|
99
|
+
return [...shown, "", `Time limit: ${timeLimitText(body.timeoutMs)}.`].join("\n");
|
|
100
|
+
}
|
|
101
|
+
function scriptTaskText(task) {
|
|
102
|
+
const body = scriptBodyOf(task);
|
|
103
|
+
return body ? scriptBodyText(body) : "This script task has no valid body, so it cannot run.";
|
|
104
|
+
}
|
|
105
|
+
function timeLimitText(ms) {
|
|
106
|
+
const units = [[36e5, "hour"], [6e4, "minute"], [1e3, "second"], [1, "millisecond"]];
|
|
107
|
+
const [size, name] = units.find(([unit]) => ms % unit === 0) ?? units[units.length - 1];
|
|
108
|
+
const n = ms / size;
|
|
109
|
+
return `${n} ${name}${n === 1 ? "" : "s"}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/models/Loop.ts
|
|
113
|
+
var DEFAULT_LOOP_ROUNDS = 3;
|
|
114
|
+
var MAX_LOOP_ROUNDS = 10;
|
|
115
|
+
function loopRoundOf(check) {
|
|
116
|
+
return check.loopRound ?? 1;
|
|
117
|
+
}
|
|
118
|
+
function foundWanting(check) {
|
|
119
|
+
return check.verdict?.outcome === "fail" && check.verdict.reported === "failed";
|
|
120
|
+
}
|
|
121
|
+
function betweenRounds(check) {
|
|
122
|
+
return !!check.loop && check.status === "failed" && foundWanting(check) && loopRoundOf(check) < check.loop.maxRounds;
|
|
123
|
+
}
|
|
124
|
+
function outOfRounds(check) {
|
|
125
|
+
return !!check.loop && check.status === "failed" && foundWanting(check) && loopRoundOf(check) >= check.loop.maxRounds;
|
|
126
|
+
}
|
|
127
|
+
function checkOf(tasks, taskId) {
|
|
128
|
+
return tasks.find((t) => t.loop?.body.includes(taskId));
|
|
129
|
+
}
|
|
130
|
+
function inLoop(tasks, task) {
|
|
131
|
+
return !!task.loop || checkOf(tasks, task.id) !== void 0;
|
|
132
|
+
}
|
|
133
|
+
var SHAPE = 'is not {"body": ["<task id>", ...], "maxRounds"?: <rounds>}';
|
|
134
|
+
function roundsMessage(value) {
|
|
135
|
+
return `has "maxRounds" ${JSON.stringify(value)}: a loop runs its check from 1 to ${MAX_LOOP_ROUNDS} times, or ${DEFAULT_LOOP_ROUNDS} when it is left out`;
|
|
136
|
+
}
|
|
137
|
+
function isRoundCount(value) {
|
|
138
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_LOOP_ROUNDS;
|
|
139
|
+
}
|
|
140
|
+
function loopSpec(value) {
|
|
141
|
+
if (value === void 0 || value === null) return { loop: void 0 };
|
|
142
|
+
if (typeof value !== "object" || Array.isArray(value)) return { error: `has a "loop" that ${SHAPE}` };
|
|
143
|
+
const { body, maxRounds } = value;
|
|
144
|
+
if (!Array.isArray(body) || !body.every((id) => typeof id === "string" || typeof id === "number")) return { error: `has a "loop" whose "body" ${SHAPE}` };
|
|
145
|
+
if (body.length === 0) return { error: 'has a "loop" with an empty "body": name the tasks that fix what it finds' };
|
|
146
|
+
if (maxRounds !== void 0 && !isRoundCount(maxRounds)) return { error: roundsMessage(maxRounds) };
|
|
147
|
+
return { loop: { body: body.map(String), maxRounds: maxRounds ?? DEFAULT_LOOP_ROUNDS } };
|
|
148
|
+
}
|
|
149
|
+
function loopErrors(tasks) {
|
|
150
|
+
const rows = flattenTasksWithParents(tasks);
|
|
151
|
+
const byId = new Map(rows.map((row) => [row.task.id, row]));
|
|
152
|
+
const errors = [];
|
|
153
|
+
const bodyOwner = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const { task: check, parent } of rows) {
|
|
155
|
+
const loop = check.loop;
|
|
156
|
+
if (!loop) continue;
|
|
157
|
+
const where = parent ? `subtask "${check.title}" of "${parent.title}"` : `Task "${check.title}"`;
|
|
158
|
+
const fail = (message) => errors.push({ taskId: check.id, field: "loop", message: `${where} ${message}` });
|
|
159
|
+
if (parent) fail(`has a "loop", but only a top-level task can be a loop's check`);
|
|
160
|
+
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");
|
|
161
|
+
if (check.subtasks?.length) fail("is a loop's check, which has no subtasks: each round runs it again whole");
|
|
162
|
+
if (!isRoundCount(loop.maxRounds)) fail(roundsMessage(loop.maxRounds));
|
|
163
|
+
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');
|
|
164
|
+
const named = /* @__PURE__ */ new Set();
|
|
165
|
+
for (const id of loop.body ?? []) {
|
|
166
|
+
const row = byId.get(id);
|
|
167
|
+
if (named.has(id)) {
|
|
168
|
+
fail(`names "${row?.task.title ?? id}" twice in its loop's "body"`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
named.add(id);
|
|
172
|
+
if (!row) {
|
|
173
|
+
fail(`names "${id}" in its loop's "body", which is no task in the plan`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const body = row.task;
|
|
177
|
+
if (body.id === check.id) {
|
|
178
|
+
fail(`names itself in its loop's "body": the body is the tasks that fix what it finds`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (row.parent) {
|
|
182
|
+
fail(`names subtask "${body.title}" of "${row.parent.title}" in its loop's "body": only a top-level task can be in a loop's body`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const other = bodyOwner.get(id);
|
|
186
|
+
if (other) {
|
|
187
|
+
fail(`names "${body.title}" in its loop's "body", which is in the body of "${other.title}"'s loop too: a task belongs to one loop only`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
bodyOwner.set(id, check);
|
|
191
|
+
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
|
+
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`);
|
|
193
|
+
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
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const [bodyId, check] of bodyOwner) {
|
|
197
|
+
const body = byId.get(bodyId).task;
|
|
198
|
+
const where = `Task "${body.title}" is in the body of "${check.title}"'s loop`;
|
|
199
|
+
if (!body.dependencies.includes(check.id)) {
|
|
200
|
+
errors.push({ taskId: body.id, field: "dependencies", message: `${where}, so it depends on "${check.title}": the check's report is what it fixes. Add "${check.title}" to its dependencies` });
|
|
201
|
+
}
|
|
202
|
+
for (const dep of body.dependencies) {
|
|
203
|
+
if (dep === check.id || check.dependencies.includes(dep) || bodyOwner.get(dep) === check) continue;
|
|
204
|
+
errors.push({ taskId: body.id, field: "dependencies", message: `${where}, so it depends only on "${check.title}", on what "${check.title}" depends on, or on other tasks of that body; "${byId.get(dep)?.task.title ?? dep}" is none of these` });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const { task } of rows) {
|
|
208
|
+
for (const dep of task.dependencies) {
|
|
209
|
+
const check = bodyOwner.get(dep);
|
|
210
|
+
if (!check || bodyOwner.get(task.id) === check) continue;
|
|
211
|
+
const body = byId.get(dep).task;
|
|
212
|
+
const message = task.id === check.id ? `Task "${task.title}" depends on "${body.title}" from its own loop's body: the body runs after the check, so the check cannot wait for it` : `Task "${task.title}" depends on "${body.title}", which is in the body of "${check.title}"'s loop and runs only if "${check.title}" fails: depend on "${check.title}" instead`;
|
|
213
|
+
errors.push({ taskId: task.id, field: "dependencies", message });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return errors;
|
|
217
|
+
}
|
|
218
|
+
function loopRemovalRefusal(task) {
|
|
219
|
+
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
|
+
}
|
|
221
|
+
function loopReshapeRefusal(tasks, target, action) {
|
|
222
|
+
if (target.loop) return `"${target.title}" is a loop's check and cannot be ${action}: drop its loop first ("loop": null)`;
|
|
223
|
+
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
|
+
}
|
|
11
225
|
|
|
12
226
|
// src/services/JsonExtractor.ts
|
|
13
227
|
var PLAN_ENVELOPE_KEY = "tasks";
|
|
@@ -70,12 +284,12 @@ function opensWithJsonObject(prefix) {
|
|
|
70
284
|
function balancedObjectAt(text2, start) {
|
|
71
285
|
let depth = 0;
|
|
72
286
|
let inString = false;
|
|
73
|
-
let
|
|
287
|
+
let escaped2 = false;
|
|
74
288
|
for (let i = start; i < text2.length; i++) {
|
|
75
289
|
const ch = text2[i];
|
|
76
290
|
if (inString) {
|
|
77
|
-
if (
|
|
78
|
-
else if (ch === "\\")
|
|
291
|
+
if (escaped2) escaped2 = false;
|
|
292
|
+
else if (ch === "\\") escaped2 = true;
|
|
79
293
|
else if (ch === '"') inString = false;
|
|
80
294
|
continue;
|
|
81
295
|
}
|
|
@@ -96,12 +310,12 @@ function extractObjectsWithKey(raw, key) {
|
|
|
96
310
|
let sawBrace = false;
|
|
97
311
|
let sawUnbalanced = false;
|
|
98
312
|
let inString = false;
|
|
99
|
-
let
|
|
313
|
+
let escaped2 = false;
|
|
100
314
|
for (let i = 0; i < stripped.length; i++) {
|
|
101
315
|
const ch = stripped[i];
|
|
102
316
|
if (inString) {
|
|
103
|
-
if (
|
|
104
|
-
else if (ch === "\\")
|
|
317
|
+
if (escaped2) escaped2 = false;
|
|
318
|
+
else if (ch === "\\") escaped2 = true;
|
|
105
319
|
else if (ch === '"') inString = false;
|
|
106
320
|
continue;
|
|
107
321
|
}
|
|
@@ -141,17 +355,17 @@ function stripTrailingCommas(json) {
|
|
|
141
355
|
function escapeControlCharsInStrings(json) {
|
|
142
356
|
let out = "";
|
|
143
357
|
let inString = false;
|
|
144
|
-
let
|
|
358
|
+
let escaped2 = false;
|
|
145
359
|
for (const ch of json) {
|
|
146
360
|
if (inString) {
|
|
147
|
-
if (
|
|
361
|
+
if (escaped2) {
|
|
148
362
|
out += ch;
|
|
149
|
-
|
|
363
|
+
escaped2 = false;
|
|
150
364
|
continue;
|
|
151
365
|
}
|
|
152
366
|
if (ch === "\\") {
|
|
153
367
|
out += ch;
|
|
154
|
-
|
|
368
|
+
escaped2 = true;
|
|
155
369
|
continue;
|
|
156
370
|
}
|
|
157
371
|
if (ch === '"') {
|
|
@@ -184,7 +398,7 @@ function escapeControlCharsInStrings(json) {
|
|
|
184
398
|
function runnerModesFrom(registry, runners) {
|
|
185
399
|
return Object.fromEntries(runners.map((r) => [r, registry.getManifest(r)?.modes ?? []]));
|
|
186
400
|
}
|
|
187
|
-
function resolveDefaultMode(modes, autonomousDefault =
|
|
401
|
+
function resolveDefaultMode(modes, autonomousDefault = false) {
|
|
188
402
|
if (!modes || modes.length === 0) return void 0;
|
|
189
403
|
const tag = autonomousDefault ? "autonomous" : "safe";
|
|
190
404
|
const tagged = modes.find((m) => m[tag]);
|
|
@@ -210,6 +424,13 @@ function parseAutonomyLevel(arg) {
|
|
|
210
424
|
return null;
|
|
211
425
|
}
|
|
212
426
|
}
|
|
427
|
+
function parseAutonomyFlag(value) {
|
|
428
|
+
const normalized = value?.trim().toLowerCase();
|
|
429
|
+
if (!normalized) return void 0;
|
|
430
|
+
if (normalized === "true" || normalized === "1") return true;
|
|
431
|
+
if (normalized === "false" || normalized === "0") return false;
|
|
432
|
+
return parseAutonomyLevel(normalized) ?? void 0;
|
|
433
|
+
}
|
|
213
434
|
function taggedReadOnlyMode(modes) {
|
|
214
435
|
return modes?.find((m) => m.readOnly)?.id;
|
|
215
436
|
}
|
|
@@ -227,7 +448,7 @@ function unavailableTaskModeMessage(manifest, mode) {
|
|
|
227
448
|
const reason = unavailableModeReason(manifest.modes, mode);
|
|
228
449
|
return reason ? `Mode "${mode}" of ${manifest.displayName} is unavailable on this host: ${reason}` : void 0;
|
|
229
450
|
}
|
|
230
|
-
function plannedMode(runner, runnerModes, { readOnly, current }, autonomousDefault =
|
|
451
|
+
function plannedMode(runner, runnerModes, { readOnly, current }, autonomousDefault = false) {
|
|
231
452
|
if (!runnerModes) return { ok: true, mode: void 0 };
|
|
232
453
|
const modes = runnerModes[runner] ?? [];
|
|
233
454
|
const readOnlyId = readOnlyMode(modes);
|
|
@@ -251,8 +472,8 @@ function carryPlannedModes(before, rewrite, catalog) {
|
|
|
251
472
|
const carry = (t) => {
|
|
252
473
|
const subtasks = (t.subtasks ?? []).map(carry);
|
|
253
474
|
const was = prior.get(t.id);
|
|
254
|
-
if (t.type
|
|
255
|
-
const mode = plannedMode(t.assignedRunner, catalog.runnerModes, { readOnly: false, current: was.taskMode }, catalog.autonomousDefault ??
|
|
475
|
+
if (t.type !== "ai" || !was || was.type !== "ai" || t.taskMode === taggedReadOnlyMode(catalog.runnerModes[t.assignedRunner])) return { ...t, subtasks };
|
|
476
|
+
const mode = plannedMode(t.assignedRunner, catalog.runnerModes, { readOnly: false, current: was.taskMode }, catalog.autonomousDefault ?? false);
|
|
256
477
|
return { ...t, subtasks, ...mode.ok && mode.mode !== void 0 ? { taskMode: mode.mode } : {} };
|
|
257
478
|
};
|
|
258
479
|
return rewrite.map(carry);
|
|
@@ -318,9 +539,8 @@ function liveModelRefusal(task) {
|
|
|
318
539
|
function liveRunnerRefusal(task) {
|
|
319
540
|
return `"${task.title}" has a live runner, so its runner cannot change now: stop the task first`;
|
|
320
541
|
}
|
|
321
|
-
function assignableRunners(writer, enabled
|
|
322
|
-
if (writer === "plan") return [...enabled];
|
|
323
|
-
if (writer === "edit") return [.../* @__PURE__ */ new Set([...planRunners, ...enabled])];
|
|
542
|
+
function assignableRunners(writer, enabled) {
|
|
543
|
+
if (writer === "plan" || writer === "edit") return [...enabled];
|
|
324
544
|
return void 0;
|
|
325
545
|
}
|
|
326
546
|
function sameAssignment(a, b) {
|
|
@@ -343,7 +563,7 @@ function modeFor(write, task, moved) {
|
|
|
343
563
|
if (unavailable) return { ok: false, error: `Mode "${task.taskMode}" of runner "${runner}" is unavailable on this host: ${unavailable}` };
|
|
344
564
|
return { ok: true, mode: task.taskMode };
|
|
345
565
|
}
|
|
346
|
-
const becameAi = before
|
|
566
|
+
const becameAi = before !== void 0 && before.type !== "ai";
|
|
347
567
|
if (writer === "runner" || !catalog.runnerModes || before && !moved && !becameAi && write.readOnly === void 0) return { ok: true, mode: task.taskMode };
|
|
348
568
|
const carried = prior !== void 0 && task.taskMode === prior.taskMode && readOnlyOn(prior.taskMode, prior.assignedRunner, catalog);
|
|
349
569
|
const readOnly = write.readOnly ?? (carried || readOnlyOn(task.taskMode, runner, catalog));
|
|
@@ -355,7 +575,7 @@ function modeFor(write, task, moved) {
|
|
|
355
575
|
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}` : ""}`
|
|
356
576
|
};
|
|
357
577
|
}
|
|
358
|
-
const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ??
|
|
578
|
+
const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ?? false);
|
|
359
579
|
return mode.ok ? { ok: true, mode: mode.mode ?? task.taskMode } : mode;
|
|
360
580
|
}
|
|
361
581
|
function resolveAssignment(write) {
|
|
@@ -367,7 +587,7 @@ function resolveAssignment(write) {
|
|
|
367
587
|
const live = writer !== "runner" && !!before && !!catalog.liveRunner?.(before.id);
|
|
368
588
|
if (live && moved) return { ok: false, field: "assignedRunner", error: liveRunnerRefusal({ title }) };
|
|
369
589
|
if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: "assignedModel", error: liveModelRefusal({ title }) };
|
|
370
|
-
if (written.type
|
|
590
|
+
if (written.type !== "ai") return { ok: true, task: written, adjustments: [] };
|
|
371
591
|
const mode = modeFor(write, written, moved);
|
|
372
592
|
if (!mode.ok) return { ok: false, field: "taskMode", error: mode.error };
|
|
373
593
|
const model = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);
|
|
@@ -457,14 +677,25 @@ function resolveAssignments(tasks, planned, catalog) {
|
|
|
457
677
|
import { v4 as uuidv4 } from "uuid";
|
|
458
678
|
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`;
|
|
459
679
|
var MANUAL_NOT_READ_ONLY = '"readOnly" is for AI tasks: a manual task runs in no mode';
|
|
460
|
-
var OPS_NOT_READ_ONLY =
|
|
680
|
+
var OPS_NOT_READ_ONLY = '"readOnly" is not for an ops task: it acts in the workspace, while a read-only task lands nothing';
|
|
461
681
|
function isRecord(value) {
|
|
462
682
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
463
683
|
}
|
|
464
684
|
function text(value) {
|
|
465
685
|
return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
|
|
466
686
|
}
|
|
687
|
+
var NOT_ON_A_SCRIPT = ["assignedRunner", "assignedModel", "thinkingEffort", "taskMode", "readOnly", "prompt", "skills", "userSteps", "ops", "subtasks"];
|
|
688
|
+
function notOnAScript(raw) {
|
|
689
|
+
return NOT_ON_A_SCRIPT.filter((f) => {
|
|
690
|
+
const value = raw[f];
|
|
691
|
+
return value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function notOnAScriptMessage(fields) {
|
|
695
|
+
return `is a script task, which runs its "commands" or "script" with no model: leave out ${fields.map((f) => `"${f}"`).join(", ")}`;
|
|
696
|
+
}
|
|
467
697
|
function opsPlacementRefusal(type, isSubtask) {
|
|
698
|
+
if (type === "script") return 'is a script task, which is an ops task by nature: leave "ops" out';
|
|
468
699
|
if (type !== "ai") return "is a manual task: only an AI task can be an ops task";
|
|
469
700
|
if (isSubtask) return "is a subtask, which runs with its parent: only a top-level task can be an ops task";
|
|
470
701
|
return null;
|
|
@@ -488,6 +719,25 @@ function modelOf(raw, from) {
|
|
|
488
719
|
}
|
|
489
720
|
return from && effort !== void 0 ? { ...from, thinkingEffort: effort } : from;
|
|
490
721
|
}
|
|
722
|
+
function bodyFields(raw, base) {
|
|
723
|
+
const pick = (field) => raw[field] !== void 0 && raw[field] !== null ? raw[field] : base[field];
|
|
724
|
+
const judged = { commands: pick("commands"), script: pick("script"), timeoutMs: pick("timeoutMs") };
|
|
725
|
+
const malformed = /* @__PURE__ */ new Set();
|
|
726
|
+
const commands = isCommands(judged.commands) ? judged.commands : void 0;
|
|
727
|
+
if (judged.commands !== void 0 && !commands) malformed.add("commands");
|
|
728
|
+
const script = typeof judged.script === "string" ? judged.script : void 0;
|
|
729
|
+
if (judged.script !== void 0 && script === void 0) malformed.add("script");
|
|
730
|
+
const timeoutMs = typeof judged.timeoutMs === "number" ? judged.timeoutMs : void 0;
|
|
731
|
+
if (judged.timeoutMs !== void 0 && timeoutMs === void 0) malformed.add("timeoutMs");
|
|
732
|
+
return {
|
|
733
|
+
fields: { ...commands ? { commands } : {}, ...script !== void 0 ? { script } : {}, ...timeoutMs !== void 0 ? { timeoutMs } : {} },
|
|
734
|
+
raw: judged,
|
|
735
|
+
malformed
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function isCommands(value) {
|
|
739
|
+
return Array.isArray(value) && value.every((argv) => Array.isArray(argv) && argv.every((word) => typeof word === "string"));
|
|
740
|
+
}
|
|
491
741
|
function plannedTask(spec, catalog, at = {}) {
|
|
492
742
|
const raw = isRecord(spec) ? spec : {};
|
|
493
743
|
const { parent, from, freshIds, newId = uuidv4 } = at;
|
|
@@ -501,17 +751,26 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
501
751
|
const plannerId = text(raw.id);
|
|
502
752
|
const id = freshIds || !plannerId ? newId() : plannerId;
|
|
503
753
|
if (freshIds && plannerId) renamed.set(plannerId, id);
|
|
504
|
-
const type = raw.type === "user" || raw.type === "ai" ? raw.type : base.type ?? "ai";
|
|
754
|
+
const type = raw.type === "user" || raw.type === "ai" || raw.type === "script" ? raw.type : base.type ?? "ai";
|
|
505
755
|
const ai = type === "ai";
|
|
756
|
+
const script = type === "script";
|
|
506
757
|
const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];
|
|
507
758
|
const description = text(raw.description) ?? base.description ?? title;
|
|
508
|
-
|
|
509
|
-
if (
|
|
510
|
-
|
|
759
|
+
const notScripts = script ? notOnAScript(raw) : [];
|
|
760
|
+
if (notScripts.length > 0) errors.push({ taskId: id, field: notScripts[0], message: `${where} ${notOnAScriptMessage(notScripts)}` });
|
|
761
|
+
const body = bodyFields(raw, base);
|
|
762
|
+
const shapeErrors = scriptBodyErrors({ type, ...body.raw }).filter((e) => body.malformed.has(e.field));
|
|
763
|
+
for (const e of shapeErrors) errors.push({ taskId: id, field: e.field, message: `${where} ${e.message}` });
|
|
764
|
+
const loop = loopSpec(raw.loop);
|
|
765
|
+
if ("error" in loop) errors.push({ taskId: id, field: "loop", message: `${where} ${loop.error}` });
|
|
766
|
+
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}` });
|
|
768
|
+
if (type === "user" && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${MANUAL_NOT_READ_ONLY}` });
|
|
511
769
|
const ops = raw.ops !== void 0 ? raw.ops === true : base.ops;
|
|
512
770
|
if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: "readOnly", message: `${where}: ${OPS_NOT_READ_ONLY}` });
|
|
513
771
|
const asked = typeof raw.readOnly === "boolean" ? raw.readOnly : void 0;
|
|
514
772
|
const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);
|
|
773
|
+
if (raw.research !== void 0 && typeof raw.research !== "boolean") errors.push({ taskId: id, field: "research", message: `${where}: "research" must be a boolean` });
|
|
515
774
|
const assignedModel = ai ? modelOf(raw, base.assignedModel) : void 0;
|
|
516
775
|
const slice = (value) => value === "AFK" || value === "HITL" ? value : void 0;
|
|
517
776
|
const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : void 0;
|
|
@@ -534,7 +793,8 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
534
793
|
description,
|
|
535
794
|
type,
|
|
536
795
|
dependencies: Array.isArray(raw.dependencies) ? raw.dependencies.map(String) : base.dependencies ?? [],
|
|
537
|
-
|
|
796
|
+
// 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),
|
|
538
798
|
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,
|
|
539
799
|
subtasks,
|
|
540
800
|
assignedModel,
|
|
@@ -542,10 +802,13 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
542
802
|
autonomy,
|
|
543
803
|
sliceType,
|
|
544
804
|
userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,
|
|
545
|
-
ops,
|
|
546
|
-
|
|
805
|
+
ops: script ? void 0 : ops,
|
|
806
|
+
research: typeof raw.research === "boolean" ? raw.research : base.research,
|
|
807
|
+
skills: script ? void 0 : from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),
|
|
808
|
+
...body.fields,
|
|
809
|
+
..."loop" in loop && loop.loop ? { loop: loop.loop } : {}
|
|
547
810
|
};
|
|
548
|
-
let taskMode = base.taskMode;
|
|
811
|
+
let taskMode = script ? void 0 : base.taskMode;
|
|
549
812
|
let landedModel = assignedModel;
|
|
550
813
|
if (ai && catalog.runners.includes(assignedRunner)) {
|
|
551
814
|
const resolved = resolveAssignment({
|
|
@@ -568,30 +831,50 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
568
831
|
if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);
|
|
569
832
|
return { task, errors, ignored, renamed, adjustments };
|
|
570
833
|
}
|
|
834
|
+
function checkPlanRunners(tasks, runners) {
|
|
835
|
+
if (!runners) return [];
|
|
836
|
+
return flattenTasksWithParents(tasks).filter(({ task: t }) => t.type === "ai" && !runners.includes(t.assignedRunner)).map(({ task: t, parent }) => ({
|
|
837
|
+
taskId: t.id,
|
|
838
|
+
field: "assignedRunner",
|
|
839
|
+
message: `${taskWhere(t.title, parent?.title)} has invalid assignedRunner "${t.assignedRunner}". Expected one of: ${runners.join(", ")}`
|
|
840
|
+
}));
|
|
841
|
+
}
|
|
571
842
|
function checkPlanInvariants(tasks, runners, runnerModes) {
|
|
572
843
|
const rows = flattenTasksWithParents(tasks);
|
|
573
844
|
const errors = [];
|
|
574
845
|
const seen = /* @__PURE__ */ new Set();
|
|
846
|
+
const body = new Set(tasks.flatMap((t) => t.loop?.body ?? []));
|
|
575
847
|
for (const { task: t, parent } of rows) {
|
|
576
848
|
const where = taskWhere(t.title, parent?.title);
|
|
577
849
|
const fail = (field, message) => errors.push({ taskId: t.id, field, message });
|
|
578
850
|
if (seen.has(t.id)) fail("id", `${where} has id "${t.id}", which another task in the plan has too: every task, subtasks included, needs an id of its own`);
|
|
579
851
|
seen.add(t.id);
|
|
852
|
+
if (t.research !== void 0 && typeof t.research !== "boolean") fail("research", `${where}: "research" must be a boolean`);
|
|
853
|
+
const readOnly = runnerModes ? taggedReadOnlyMode(runnerModes[t.assignedRunner]) : "plan";
|
|
854
|
+
if (t.research && (t.type !== "ai" || parent !== null || t.ops || body.has(t.id) || readOnly === void 0 || t.taskMode !== readOnly)) {
|
|
855
|
+
fail("research", `${where}: research is only for top-level read-only AI tasks, never a loop's body`);
|
|
856
|
+
}
|
|
580
857
|
if (t.autonomy === "AFK" && t.userSteps && t.userSteps.length > 0) {
|
|
581
858
|
fail("userSteps", `${where} has autonomy "AFK" but contains userSteps. AFK tasks must not have user touchpoints.`);
|
|
582
859
|
}
|
|
583
860
|
if (t.type === "user" && t.sliceType && t.sliceType !== "HITL") {
|
|
584
861
|
fail("sliceType", `User ${where} has sliceType "${t.sliceType}". User tasks must have sliceType "HITL".`);
|
|
585
862
|
}
|
|
586
|
-
const opsRefused = t.ops ? opsPlacementRefusal(t.type, parent !== null) : null;
|
|
863
|
+
const opsRefused = t.ops && t.type !== "script" ? opsPlacementRefusal(t.type, parent !== null) : null;
|
|
587
864
|
if (opsRefused) fail("ops", `${where} ${opsRefused}`);
|
|
588
|
-
if (t.
|
|
589
|
-
fail("
|
|
865
|
+
if (t.type === "script") {
|
|
866
|
+
if (parent !== null) fail("type", `${where} is a script task: only a top-level task can be one, as only a top-level task can be an ops task`);
|
|
867
|
+
if (t.subtasks?.length) fail("subtasks", `${where} is a script task, which has no subtasks: make each step a command of its own, or a task of its own`);
|
|
868
|
+
const extra = notAScriptsFields(t);
|
|
869
|
+
if (extra.length > 0) fail(extra[0], `${where} ${notOnAScriptMessage(extra)}`);
|
|
590
870
|
}
|
|
591
|
-
|
|
592
|
-
|
|
871
|
+
for (const e of scriptBodyErrors(t)) fail(e.field, `${where} ${e.message}`);
|
|
872
|
+
if (t.ops && t.taskMode !== void 0 && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {
|
|
873
|
+
fail("taskMode", `${where} is an ops task, which acts in the workspace, so it cannot use the read-only profile`);
|
|
593
874
|
}
|
|
594
875
|
}
|
|
876
|
+
errors.push(...checkPlanRunners(tasks, runners));
|
|
877
|
+
errors.push(...loopErrors(tasks));
|
|
595
878
|
for (const { task: t, parent } of rows) {
|
|
596
879
|
for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {
|
|
597
880
|
errors.push({ taskId: t.id, field: "dependencies", message: `${taskWhere(t.title, parent?.title)}: ${error}` });
|
|
@@ -609,6 +892,27 @@ function detectCycle(tasks) {
|
|
|
609
892
|
}
|
|
610
893
|
|
|
611
894
|
export {
|
|
895
|
+
DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
896
|
+
SCRIPT_BODY_FIELDS,
|
|
897
|
+
scriptBodyOf,
|
|
898
|
+
sameScriptBody,
|
|
899
|
+
rewrittenScripts,
|
|
900
|
+
notAScriptsFields,
|
|
901
|
+
clearedBodyFields,
|
|
902
|
+
commandLine,
|
|
903
|
+
scriptBodyText,
|
|
904
|
+
scriptTaskText,
|
|
905
|
+
timeLimitText,
|
|
906
|
+
DEFAULT_LOOP_ROUNDS,
|
|
907
|
+
MAX_LOOP_ROUNDS,
|
|
908
|
+
loopRoundOf,
|
|
909
|
+
betweenRounds,
|
|
910
|
+
outOfRounds,
|
|
911
|
+
checkOf,
|
|
912
|
+
inLoop,
|
|
913
|
+
loopSpec,
|
|
914
|
+
loopRemovalRefusal,
|
|
915
|
+
loopReshapeRefusal,
|
|
612
916
|
PLAN_ENVELOPE_KEY,
|
|
613
917
|
TASK_OPS_ENVELOPE_KEY,
|
|
614
918
|
TASK_QUERY_ENVELOPE_KEY,
|
|
@@ -622,6 +926,7 @@ export {
|
|
|
622
926
|
runnerModesFrom,
|
|
623
927
|
autonomyLevelLabel,
|
|
624
928
|
parseAutonomyLevel,
|
|
929
|
+
parseAutonomyFlag,
|
|
625
930
|
taggedReadOnlyMode,
|
|
626
931
|
readOnlyMode,
|
|
627
932
|
unavailableTaskModeMessage,
|
|
@@ -637,6 +942,7 @@ export {
|
|
|
637
942
|
MODE_NOT_THE_PLANNERS,
|
|
638
943
|
MANUAL_NOT_READ_ONLY,
|
|
639
944
|
OPS_NOT_READ_ONLY,
|
|
945
|
+
notOnAScriptMessage,
|
|
640
946
|
opsPlacementRefusal,
|
|
641
947
|
taskWhere,
|
|
642
948
|
requestedSkills,
|
|
@@ -644,4 +950,4 @@ export {
|
|
|
644
950
|
checkPlanInvariants,
|
|
645
951
|
detectCycle
|
|
646
952
|
};
|
|
647
|
-
//# sourceMappingURL=chunk-
|
|
953
|
+
//# sourceMappingURL=chunk-YAFNYNFX.mjs.map
|