@ordewell/core 0.10.1 → 0.10.3
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/{IFileSystem-Bj6fy1cG.d.mts → IFileSystem-B8Nkgg4e.d.mts} +1 -1
- package/dist/{IFileSystem-BCJBHFwB.d.ts → IFileSystem-DCd-rkeH.d.ts} +1 -1
- package/dist/{IRunner-hWAJC4hg.d.mts → IRunner-CXdYyIZo.d.mts} +213 -202
- package/dist/{IRunner-C7_SSpzG.d.ts → IRunner-QBdYcatb.d.ts} +213 -202
- package/dist/{ModeResolver-m1d8Cb-j.d.mts → ModeResolver-BOU90Nn-.d.ts} +1 -1
- package/dist/{ModeResolver-u5ZhRC8x.d.ts → ModeResolver-geqjKkLY.d.mts} +1 -1
- package/dist/{Task-CxwnCJLi.d.mts → Task-CRVIqSfy.d.mts} +58 -11
- package/dist/{Task-CxwnCJLi.d.ts → Task-CRVIqSfy.d.ts} +58 -11
- package/dist/{chunk-DNFT4SQG.mjs → chunk-645FALZX.mjs} +5 -5
- package/dist/chunk-645FALZX.mjs.map +1 -0
- package/dist/{chunk-DJAPDO6E.mjs → chunk-A57ITQE7.mjs} +41 -21
- package/dist/chunk-A57ITQE7.mjs.map +1 -0
- package/dist/{chunk-CH5IGWFS.mjs → chunk-FH6M5QZG.mjs} +168 -122
- package/dist/chunk-FH6M5QZG.mjs.map +1 -0
- package/dist/{chunk-2IIRSBEQ.mjs → chunk-SM6BV6XS.mjs} +20 -4
- package/dist/chunk-SM6BV6XS.mjs.map +1 -0
- package/dist/index.d.mts +454 -63
- package/dist/index.d.ts +454 -63
- package/dist/index.js +19575 -18631
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +17991 -17137
- 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 +15 -14
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/{plan-utils-CKXdD1AD.d.mts → plan-utils-KU1Rse82.d.mts} +1322 -1146
- package/dist/{plan-utils-B6yJdpgi.d.ts → plan-utils-oC8bO4pN.d.ts} +1322 -1146
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +60 -0
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +11 -3
- package/dist/testing.d.mts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +3 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +4 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-2IIRSBEQ.mjs.map +0 -1
- package/dist/chunk-CH5IGWFS.mjs.map +0 -1
- package/dist/chunk-DJAPDO6E.mjs.map +0 -1
- package/dist/chunk-DNFT4SQG.mjs.map +0 -1
|
@@ -330,8 +330,26 @@ interface PreparedTask {
|
|
|
330
330
|
setupFailed?: SetupFailure[];
|
|
331
331
|
/** What the attempt this one replaces left unlanded, kept on a branch before its worktree went; absent when nothing was. */
|
|
332
332
|
preserved?: PreservedWork[];
|
|
333
|
-
|
|
333
|
+
/** Set for a `restore` prepare: whether the previous attempt's work was put back, or every repo fell back to its tip. */
|
|
334
|
+
restoration?: 'restored' | 'fallback';
|
|
334
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* How a task workspace starts: `fresh` from the integration tips, `restore`
|
|
338
|
+
* with the previous attempt's preserved work merged on top of them — for a
|
|
339
|
+
* continue (ADR-0018, K1) and for a carry-over to another runner.
|
|
340
|
+
*/
|
|
341
|
+
type PrepareIntent = 'fresh' | 'restore';
|
|
342
|
+
/**
|
|
343
|
+
* What a restoring attempt is told became of the previous attempt's work: in
|
|
344
|
+
* its working tree, or kept on `preserved` branches because restoring it
|
|
345
|
+
* conflicted with the integration tips.
|
|
346
|
+
*/
|
|
347
|
+
type PreviousWork = {
|
|
348
|
+
outcome: 'restored';
|
|
349
|
+
} | {
|
|
350
|
+
outcome: 'fallback';
|
|
351
|
+
preserved: readonly PreservedWork[];
|
|
352
|
+
};
|
|
335
353
|
/** A task as a removal names it. */
|
|
336
354
|
type IsolationTaskRef = Pick<IsolationTaskRecord, 'taskId' | 'order' | 'title'>;
|
|
337
355
|
/**
|
|
@@ -400,11 +418,11 @@ interface IWorktreeIsolation {
|
|
|
400
418
|
/**
|
|
401
419
|
* Create the task workspace — one worktree per isolated repo from its
|
|
402
420
|
* integration tip, the shared paths linked in — and return the cwd to spawn
|
|
403
|
-
* the Runner into. Fresh attempts start clean from the tips. A
|
|
404
|
-
*
|
|
405
|
-
*
|
|
421
|
+
* the Runner into. Fresh attempts start clean from the tips. A `restore`
|
|
422
|
+
* puts the previous attempt's preserved work on top of them, falling back
|
|
423
|
+
* to the tips in every repo when any restoration conflicts (ADR-0018).
|
|
406
424
|
*/
|
|
407
|
-
prepare(task: Task, run: IsolationRun, intent?:
|
|
425
|
+
prepare(task: Task, run: IsolationRun, intent?: PrepareIntent): Promise<PreparedTask>;
|
|
408
426
|
/**
|
|
409
427
|
* Hand a conflicted task's kept workspace to a conflict repair (ADR-0015)
|
|
410
428
|
* as it is — nothing is re-cut — and return the same cwd. Records each
|
|
@@ -914,7 +932,8 @@ interface TaskOutputSummary {
|
|
|
914
932
|
type TaskType = 'ai' | 'user';
|
|
915
933
|
/**
|
|
916
934
|
* `skipped` is terminal and verdict-less: the task will not be waited for, and
|
|
917
|
-
* nothing it did lands. Its dependents go ahead without it
|
|
935
|
+
* nothing it did lands. Its dependents go ahead without it once the work it
|
|
936
|
+
* waited on is done.
|
|
918
937
|
*/
|
|
919
938
|
type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'blocked' | 'awaiting_user' | 'skipped';
|
|
920
939
|
type TaskMode = string;
|
|
@@ -983,6 +1002,12 @@ interface Task {
|
|
|
983
1002
|
* waits for a manual retry. Set only with `awaitingReason` `usage-limit`.
|
|
984
1003
|
*/
|
|
985
1004
|
resumeAt?: string;
|
|
1005
|
+
/**
|
|
1006
|
+
* The runner a task paused on its usage limit was moved off (ADR-0018, U1):
|
|
1007
|
+
* its next attempt is a carry-over, a fresh session that starts from that
|
|
1008
|
+
* attempt's files. Cleared once that attempt runs, and by a retry.
|
|
1009
|
+
*/
|
|
1010
|
+
carriedFrom?: RunnerId;
|
|
986
1011
|
/** Why the task was skipped, as whoever skipped it put it; set only while `status` is `skipped`, and not always then. */
|
|
987
1012
|
skipReason?: string;
|
|
988
1013
|
/**
|
|
@@ -1274,9 +1299,17 @@ interface LegacyPlanState {
|
|
|
1274
1299
|
* The tasks the user approved to run: every task when they executed the
|
|
1275
1300
|
* plan, and each one they started or retried themselves since. A command
|
|
1276
1301
|
* from anyone else starts only these (ADR-0029); a task the planner added
|
|
1277
|
-
* after the approval waits for the user's.
|
|
1302
|
+
* after the approval waits for the user's. Only tasks still in the plan: a
|
|
1303
|
+
* task that leaves takes its approval with it.
|
|
1278
1304
|
*/
|
|
1279
1305
|
approvedTasks?: string[];
|
|
1306
|
+
/**
|
|
1307
|
+
* The user approved the plan to run: Execute plan, signing off a review, or
|
|
1308
|
+
* autorun's start (ADR-0029, ADR-0030). Kept apart from {@link status},
|
|
1309
|
+
* which surfaces rewrite for display; a user's start of one task approves
|
|
1310
|
+
* that task, not the plan.
|
|
1311
|
+
*/
|
|
1312
|
+
approved?: boolean;
|
|
1280
1313
|
}
|
|
1281
1314
|
interface Message {
|
|
1282
1315
|
id: string;
|
|
@@ -1290,7 +1323,8 @@ interface TaskSnapshot extends Task {
|
|
|
1290
1323
|
retryCount: number;
|
|
1291
1324
|
finalized: boolean;
|
|
1292
1325
|
}
|
|
1293
|
-
|
|
1326
|
+
/** The plan a surface reads whole carries the approval record, so it can tell which tasks wait for the user (ADR-0035). */
|
|
1327
|
+
type PlanState = ({
|
|
1294
1328
|
phase: 'planning';
|
|
1295
1329
|
history: Message[];
|
|
1296
1330
|
message: string;
|
|
@@ -1304,11 +1338,15 @@ type PlanState = {
|
|
|
1304
1338
|
goal: string;
|
|
1305
1339
|
runners: string[];
|
|
1306
1340
|
status: PlanStatus;
|
|
1307
|
-
};
|
|
1341
|
+
}) & ApprovalRecord;
|
|
1308
1342
|
declare function migratePlanState(raw: unknown): PlanState;
|
|
1309
1343
|
|
|
1310
1344
|
declare function createTask(overrides?: Partial<Task>): Task;
|
|
1311
|
-
/**
|
|
1345
|
+
/**
|
|
1346
|
+
* The runner set a plan has until one is chosen. The one runner named outside
|
|
1347
|
+
* its declaration on purpose: a default is a product choice that no manifest
|
|
1348
|
+
* or connector implies.
|
|
1349
|
+
*/
|
|
1312
1350
|
declare const DEFAULT_RUNNERS: readonly RunnerId[];
|
|
1313
1351
|
declare function createEmptyPlan(): LegacyPlanState;
|
|
1314
1352
|
declare function flattenTasks(tasks: readonly Task[]): Task[];
|
|
@@ -1337,5 +1375,14 @@ interface TaskWithParent {
|
|
|
1337
1375
|
declare function flattenTasksWithParents(tasks: readonly Task[]): TaskWithParent[];
|
|
1338
1376
|
/** A task paused on its runner's usage limit, whether it resumes on its own or waits for the user (#84). */
|
|
1339
1377
|
declare function pausedOnLimit(task: Readonly<Task>): boolean;
|
|
1378
|
+
/** The user's approval record, as a plan carries it (ADR-0029). */
|
|
1379
|
+
type ApprovalRecord = Pick<LegacyPlanState, 'approved' | 'approvedTasks'>;
|
|
1380
|
+
/**
|
|
1381
|
+
* A task the scheduler leaves for the user (ADR-0035): an AI task not started
|
|
1382
|
+
* yet, in a plan they approved, outside their approval record — the planner
|
|
1383
|
+
* added it since. It starts once they start it or execute the plan. In a plan
|
|
1384
|
+
* they never approved nothing is theirs yet, so nothing is singled out.
|
|
1385
|
+
*/
|
|
1386
|
+
declare function waitsForApproval(task: Pick<Task, 'id' | 'type' | 'status'>, record: ApprovalRecord): boolean;
|
|
1340
1387
|
|
|
1341
|
-
export { type
|
|
1388
|
+
export { type ResearchStepOutcome as $, type ApprovalRecord as A, BUILTIN_SKILL_NAMES as B, type ConversationMessage as C, DEFAULT_RUNNERS as D, type ExecutionActor as E, type IsolationTaskStatus as F, type GuardTarget as G, type IsolationView as H, type IWorktreeIsolation as I, type PlanIsolation as J, type PlanState as K, type LegacyPlanState as L, type ModelUsage as M, type PlanStatus as N, type PlanUsage as O, type PendingPlanTurn as P, type PlannerUsage as Q, type PrepareIntent as R, type PreparedTask as S, type PreservedWork as T, type PreviousWork as U, type RefusedRemoval as V, type RepairEvidence as W, type RepoGroupLayout as X, type ResearchLogEntry as Y, type ResearchProgress as Z, type ResearchStep as _, type AttemptUsage as a, type ResearchToolType as a0, type RunSettings as a1, type RunnerId as a2, type SetupFailure as a3, type ShadowedSkill as a4, type SkillAppliesTo as a5, type SkillCatalog as a6, type SkillInfo as a7, type SkillLoad as a8, type SkillLoadNotice as a9, createEmptyPlan as aA, createSkillsService as aB, createTask as aC, endAttemptUsage as aD, flattenTasks as aE, flattenTasksWithParents as aF, isAwaitingReason as aG, isMeasured as aH, isSupervisorLevel as aI, isUserMessage as aJ, migratePlanState as aK, partedPromptUsage as aL, pausedOnLimit as aM, planUsage as aN, plannerContextFill as aO, rejoinPlanTree as aP, startAttemptUsage as aQ, usageLine as aR, waitsForApproval as aS, type SkillMetadata as aa, type SkillSource as ab, SkillsService as ac, type SubagentOutcome as ad, type SupervisorLevel as ae, type Task as af, type TaskDiffRead as ag, type TaskDiffResult as ah, type TaskIsolation as ai, type TaskIsolationState as aj, type TaskModelAssignment as ak, type TaskSkillNotice as al, type TaskSkillSnapshot as am, type TaskSnapshot as an, type TaskStatus as ao, type TreeSnapshot as ap, type UsageLine as aq, type UsageRecord as ar, type UsageTotals as as, type Verdict as at, type VerificationCheck as au, addAttemptUsage as av, addModelUsage as aw, addPlannerUsage as ax, addUsage as ay, addUsageTotals as az, type AwaitingReason as b, DEFAULT_SUPERVISOR_RETRY_LIMIT as c, type DiscoveredModel as d, type ExecutionCommandName as e, type ExecutionRecord as f, type GuardTrip as g, type IntegrationDisposal as h, type InvalidSkill as i, type IsolationAvailability as j, type IsolationHandoff as k, type IsolationHandoffRepo as l, type IsolationInactiveReason as m, type IsolationLandedTask as n, type IsolationLanding as o, type IsolationMergeBlock as p, type IsolationMergeBlockReason as q, type IsolationMergeResult as r, type IsolationOutcome as s, type IsolationPruneResult as t, type IsolationRemoval as u, type IsolationRepo as v, type IsolationRun as w, type IsolationTaskRecord as x, type IsolationTaskRef as y, type IsolationTaskRepo as z };
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
plannedTask,
|
|
8
8
|
stripTrailingCommas,
|
|
9
9
|
taskWhere
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-A57ITQE7.mjs";
|
|
11
11
|
import {
|
|
12
12
|
flattenTasksWithParents
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-SM6BV6XS.mjs";
|
|
14
14
|
|
|
15
15
|
// src/services/PlanValidator.ts
|
|
16
16
|
function parsePlanJson(raw, runners, runnerModes, autonomousDefault = true) {
|
|
@@ -27,7 +27,7 @@ function parsePlanJson(raw, runners, runnerModes, autonomousDefault = true) {
|
|
|
27
27
|
}
|
|
28
28
|
if (matches.length === 0 && sawUnbalanced) {
|
|
29
29
|
throw new PlanParseError(
|
|
30
|
-
"Plan response appears truncated
|
|
30
|
+
"Plan response appears truncated: the model likely hit its output length limit before finishing the JSON.",
|
|
31
31
|
raw,
|
|
32
32
|
{ truncated: true }
|
|
33
33
|
);
|
|
@@ -67,7 +67,7 @@ function validatePlanTasks(obj, runners, runnerModes, autonomousDefault = true)
|
|
|
67
67
|
const ignored = planned.flatMap((p) => p.ignored);
|
|
68
68
|
const warnings = [
|
|
69
69
|
...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.`] : [],
|
|
70
|
-
...ignored.filter((i) => i.field === "readOnly").map((i) => `${i.message}
|
|
70
|
+
...ignored.filter((i) => i.field === "readOnly").map((i) => `${i.message}: it was ignored.`)
|
|
71
71
|
];
|
|
72
72
|
return warnings.length > 0 ? { ok: true, tasks, warnings } : { ok: true, tasks };
|
|
73
73
|
}
|
|
@@ -94,4 +94,4 @@ export {
|
|
|
94
94
|
validatePlanTasks,
|
|
95
95
|
looksLikePlanAttempt
|
|
96
96
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
97
|
+
//# sourceMappingURL=chunk-645FALZX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/PlanValidator.ts"],"sourcesContent":["import { Task, RunnerId, flattenTasksWithParents } from '../models/Task';\nimport { extractObjectsWithKey, stripTrailingCommas, escapeControlCharsInStrings, PlanParseError, PLAN_ENVELOPE_KEY } from './JsonExtractor';\nimport type { RunnerModeInfo } from './ModeResolver';\nimport { plannedTask, checkPlanInvariants, taskWhere, type PlanTaskError } from './PlannedTask';\n\nexport function parsePlanJson(\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): Task[] {\n const { matches, fallback, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n\n // A reply can carry several tasks-keyed objects — a schema echo from the\n // prompt, an inline draft, then the real plan. The model's LAST emission is\n // its final answer, so candidates are tried back to front and the first one\n // that fully parses and validates wins.\n const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];\n\n let lastError: PlanParseError | null = null;\n for (const json of candidates) {\n try {\n return parsePlanObject(json, raw, runners, runnerModes, autonomousDefault);\n } catch (err) {\n if (!(err instanceof PlanParseError)) throw err;\n // Prefer surfacing the error from the most plan-like candidate: the\n // last one carrying a tasks key (tried first).\n if (!lastError) lastError = err;\n }\n }\n\n // No usable tasks object anywhere, but an object ran off the end of the\n // text: the plan was almost certainly cut off mid-stream.\n if (matches.length === 0 && sawUnbalanced) {\n throw new PlanParseError(\n 'Plan response appears truncated: the model likely hit its output length limit before finishing the JSON.',\n raw,\n { truncated: true },\n );\n }\n throw lastError ?? new PlanParseError('Invalid plan: missing tasks array', raw);\n}\n\nfunction parsePlanObject(\n json: string,\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): Task[] {\n const cleaned = stripTrailingCommas(escapeControlCharsInStrings(json));\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new PlanParseError(`Plan response was not valid JSON: ${detail}`, raw);\n }\n\n const result = validatePlanTasks(parsed, runners, runnerModes, autonomousDefault);\n if (result.ok) return result.tasks;\n const [first] = result.errors;\n // An envelope problem (no tasks array, an empty one) keeps the generic\n // re-emit; only a rule broken by a task is worth naming in the corrective.\n throw new PlanParseError(first.message, raw, { semantic: first.taskId !== undefined });\n}\n\nexport type { PlanTaskError } from './PlannedTask';\nexport { requestedSkills } from './PlannedTask';\n\n/** `warnings` name what was accepted but not used, for a channel that can tell the planner. */\nexport type PlanTasksResult = { ok: true; tasks: Task[]; warnings?: string[] } | { ok: false; errors: PlanTaskError[] };\n\n/**\n * Every check a plan's tasks must pass, on an already-parsed `{ tasks }`\n * object — what a plan is held to whichever channel it arrived on. All\n * problems are reported, the slice classification first, so the first is the\n * one the text path's corrective names.\n */\nexport function validatePlanTasks(\n obj: unknown,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): PlanTasksResult {\n const rawTasks = isRecord(obj) ? obj.tasks : undefined;\n if (!Array.isArray(rawTasks)) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Invalid plan: missing tasks array' }] };\n }\n if (rawTasks.length === 0) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Plan contained no tasks' }] };\n }\n\n const planned = rawTasks.map((t) => plannedTask(t, { runners, runnerModes, autonomousDefault }));\n const tasks = planned.map((p) => p.task);\n const errors = [\n ...sliceRequired(tasks),\n ...planned.flatMap((p) => p.errors),\n ...checkPlanInvariants(tasks, runners),\n ];\n\n if (errors.length > 0) return { ok: false, errors };\n const ignored = planned.flatMap((p) => p.ignored);\n const warnings = [\n ...(ignored.some((i) => i.field === 'taskMode')\n ? ['\"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.']\n : []),\n ...ignored.filter((i) => i.field === 'readOnly').map((i) => `${i.message}: it was ignored.`),\n ];\n return warnings.length > 0 ? { ok: true, tasks, warnings } : { ok: true, tasks };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Whether a reply that failed {@link parsePlanJson} actually LOOKED like a\n * plan attempt — a balanced tasks-keyed object, or JSON that got cut off.\n * Distinguishes \"the model tried to emit a plan and botched it\" (worth a\n * corrective retry) from prose that merely mentions `\"tasks\"` (leave alone).\n */\nexport function looksLikePlanAttempt(raw: string): boolean {\n const { matches, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n if (matches.length > 0) return true;\n return sawUnbalanced && raw.includes(`\"${PLAN_ENVELOPE_KEY}\"`);\n}\n\n/**\n * A whole plan says how each task is sliced: an edit may leave it to the\n * task's parent or the tasks it was made from, a submitted plan may not.\n */\nfunction sliceRequired(tasks: readonly Task[]): PlanTaskError[] {\n return flattenTasksWithParents(tasks).flatMap(({ task: t, parent }) => {\n const where = taskWhere(t.title, parent?.title);\n const errors: PlanTaskError[] = [];\n if (!t.sliceType) errors.push({ taskId: t.id, field: 'sliceType', message: `${where} is missing sliceType. Every task must specify sliceType (\"AFK\" or \"HITL\").` });\n if (t.type === 'ai' && !t.autonomy) errors.push({ taskId: t.id, field: 'autonomy', message: `AI ${where} is missing autonomy. Every AI task must specify autonomy (\"AFK\" or \"HITL\").` });\n return errors;\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_RUNNERS,
|
|
2
3
|
addTaskToPlan,
|
|
3
4
|
createTask,
|
|
4
5
|
flattenTasks,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
resolveTaskRef,
|
|
12
13
|
skillNames,
|
|
13
14
|
updateTaskInPlan
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-SM6BV6XS.mjs";
|
|
15
16
|
|
|
16
17
|
// src/services/JsonExtractor.ts
|
|
17
18
|
var PLAN_ENVELOPE_KEY = "tasks";
|
|
@@ -241,7 +242,7 @@ function plannedMode(runner, runnerModes, { readOnly, current }, autonomousDefau
|
|
|
241
242
|
const reason2 = unavailableModeReason(modes, taggedReadOnlyMode(modes));
|
|
242
243
|
return {
|
|
243
244
|
ok: false,
|
|
244
|
-
error: `Runner "${runner}" has no read-only mode${reason2 ? " available on this host" : ""}
|
|
245
|
+
error: `Runner "${runner}" has no read-only mode${reason2 ? " available on this host" : ""}: assign a read-only task to ${able.length > 0 ? `one of: ${able.join(", ")}` : "no runner: none of the enabled runners has one"}, or drop "readOnly"${reason2 ? `. ${reason2}` : ""}`
|
|
245
246
|
};
|
|
246
247
|
}
|
|
247
248
|
const keep = current !== void 0 && modes.some((m) => m.id === current) && !(readOnly === false && current === taggedReadOnlyMode(modes));
|
|
@@ -300,6 +301,7 @@ function blockedByLabel(refs) {
|
|
|
300
301
|
function skippedLabel(reason) {
|
|
301
302
|
return reason ? `Skipped: ${reason}` : "Skipped";
|
|
302
303
|
}
|
|
304
|
+
var WAITS_FOR_APPROVAL_LABEL = "Waiting for your approval to start";
|
|
303
305
|
function approvalLabel(count) {
|
|
304
306
|
if (!count) return null;
|
|
305
307
|
return count > 1 ? `Waiting for approval (${count})` : "Waiting for approval";
|
|
@@ -315,6 +317,9 @@ function resumeLabel(task, now = /* @__PURE__ */ new Date()) {
|
|
|
315
317
|
return Number.isNaN(at.getTime()) ? null : `Resumes at ${resumeTime(at, now)}`;
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
// src/services/TaskOps.ts
|
|
321
|
+
import { v4 as uuidv42 } from "uuid";
|
|
322
|
+
|
|
318
323
|
// src/services/ModelAllowlistResolver.ts
|
|
319
324
|
function effectiveAllowlist(allowlist, runner, modelsByRunner) {
|
|
320
325
|
if (!allowlist || allowlist.length === 0) return void 0;
|
|
@@ -422,9 +427,9 @@ function coerceAssignments(tasks, perRunnerAllowlist, modelsByRunner) {
|
|
|
422
427
|
|
|
423
428
|
// src/services/PlannedTask.ts
|
|
424
429
|
import { v4 as uuidv4 } from "uuid";
|
|
425
|
-
var MODE_NOT_THE_PLANNERS = `"taskMode" is the user's to choose
|
|
426
|
-
var MANUAL_NOT_READ_ONLY = '"readOnly" is for AI tasks
|
|
427
|
-
var OPS_NOT_READ_ONLY = `"readOnly" is not for an ops task
|
|
430
|
+
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`;
|
|
431
|
+
var MANUAL_NOT_READ_ONLY = '"readOnly" is for AI tasks: a manual task runs in no mode';
|
|
432
|
+
var OPS_NOT_READ_ONLY = `"readOnly" is not for an ops task: it runs commands, in the session's mode, and a read-only mode refuses them`;
|
|
428
433
|
function isRecord(value) {
|
|
429
434
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
430
435
|
}
|
|
@@ -432,8 +437,8 @@ function text(value) {
|
|
|
432
437
|
return typeof value === "string" || typeof value === "number" ? String(value) : void 0;
|
|
433
438
|
}
|
|
434
439
|
function opsPlacementRefusal(type, isSubtask) {
|
|
435
|
-
if (type !== "ai") return "is a manual task
|
|
436
|
-
if (isSubtask) return "is a subtask, which runs with its parent
|
|
440
|
+
if (type !== "ai") return "is a manual task: only an AI task can be an ops task";
|
|
441
|
+
if (isSubtask) return "is a subtask, which runs with its parent: only a top-level task can be an ops task";
|
|
437
442
|
return null;
|
|
438
443
|
}
|
|
439
444
|
function taskWhere(title, parentTitle) {
|
|
@@ -457,7 +462,7 @@ function modelOf(raw, from) {
|
|
|
457
462
|
}
|
|
458
463
|
function plannedTask(spec, catalog, at = {}) {
|
|
459
464
|
const raw = isRecord(spec) ? spec : {};
|
|
460
|
-
const { parent, from, freshIds } = at;
|
|
465
|
+
const { parent, from, freshIds, newId = uuidv4 } = at;
|
|
461
466
|
const base = from?.fields ?? {};
|
|
462
467
|
const errors = [];
|
|
463
468
|
const ignored = [];
|
|
@@ -465,11 +470,11 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
465
470
|
const title = text(raw.title) ?? "";
|
|
466
471
|
const where = taskWhere(title, parent?.title);
|
|
467
472
|
const plannerId = text(raw.id);
|
|
468
|
-
const id = freshIds || !plannerId ?
|
|
473
|
+
const id = freshIds || !plannerId ? newId() : plannerId;
|
|
469
474
|
if (freshIds && plannerId) renamed.set(plannerId, id);
|
|
470
475
|
const type = raw.type === "user" || raw.type === "ai" ? raw.type : base.type ?? "ai";
|
|
471
476
|
const ai = type === "ai";
|
|
472
|
-
const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ??
|
|
477
|
+
const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];
|
|
473
478
|
const description = text(raw.description) ?? base.description ?? title;
|
|
474
479
|
if (raw.ops === true && !ai) errors.push({ taskId: id, field: "ops", message: `${where} ${opsPlacementRefusal(type, !!parent)}` });
|
|
475
480
|
if (raw.taskMode !== void 0) ignored.push({ taskId: id, field: "taskMode", message: `${where}: ${MODE_NOT_THE_PLANNERS}` });
|
|
@@ -496,7 +501,7 @@ function plannedTask(spec, catalog, at = {}) {
|
|
|
496
501
|
const subtasks = [];
|
|
497
502
|
if (Array.isArray(raw.subtasks)) {
|
|
498
503
|
for (const s of raw.subtasks) {
|
|
499
|
-
const child = plannedTask(s, catalog, { parent: { title, sliceType, autonomy, readOnly }, freshIds });
|
|
504
|
+
const child = plannedTask(s, catalog, { parent: { title, sliceType, autonomy, readOnly }, freshIds, newId });
|
|
500
505
|
subtasks.push(child.task);
|
|
501
506
|
errors.push(...child.errors);
|
|
502
507
|
ignored.push(...child.ignored);
|
|
@@ -608,7 +613,7 @@ function checkModelAndModeValidity(runner, assignedModel, taskMode, catalog) {
|
|
|
608
613
|
if (taskMode !== void 0) {
|
|
609
614
|
const modes = catalog.runnerModes[runner];
|
|
610
615
|
if (modes && modes.length > 0 && !modes.some((m) => m.id === taskMode)) {
|
|
611
|
-
return { ok: false, error: `Runner "${runner}" has no task mode "${taskMode}"
|
|
616
|
+
return { ok: false, error: `Runner "${runner}" has no task mode "${taskMode}": valid modes: ${modes.map((m) => m.id).join(", ")}` };
|
|
612
617
|
}
|
|
613
618
|
const unavailable = unavailableModeReason(modes, taskMode);
|
|
614
619
|
if (unavailable) return { ok: false, error: `Mode "${taskMode}" of runner "${runner}" is unavailable on this host: ${unavailable}` };
|
|
@@ -619,7 +624,7 @@ function typeCoherenceCheck(target, changes) {
|
|
|
619
624
|
if (!changes.type || changes.type === target.type) return null;
|
|
620
625
|
if (changes.type === "user") {
|
|
621
626
|
if (!Array.isArray(changes.userSteps) || changes.userSteps.length === 0) {
|
|
622
|
-
return { ok: false, error: `"${target.title}" cannot become a manual task without user steps
|
|
627
|
+
return { ok: false, error: `"${target.title}" cannot become a manual task without user steps: include "userSteps" in this edit` };
|
|
623
628
|
}
|
|
624
629
|
return {
|
|
625
630
|
ok: true,
|
|
@@ -629,7 +634,7 @@ function typeCoherenceCheck(target, changes) {
|
|
|
629
634
|
};
|
|
630
635
|
}
|
|
631
636
|
if (!changes.prompt || !changes.prompt.trim()) {
|
|
632
|
-
return { ok: false, error: `"${target.title}" cannot become an AI task without a prompt
|
|
637
|
+
return { ok: false, error: `"${target.title}" cannot become an AI task without a prompt: include "prompt" in this edit` };
|
|
633
638
|
}
|
|
634
639
|
return { ok: true, clear: target.userSteps && target.userSteps.length > 0 ? ["userSteps"] : [] };
|
|
635
640
|
}
|
|
@@ -879,6 +884,17 @@ function changedFields(target, changes, catalog) {
|
|
|
879
884
|
if ("taskMode" in changes && isReadOnly({ ...target, ...changes }, catalog) !== isReadOnly(target, catalog)) fields.push("readOnly");
|
|
880
885
|
return fields;
|
|
881
886
|
}
|
|
887
|
+
var keptIds = /* @__PURE__ */ new WeakMap();
|
|
888
|
+
function idsOf(op) {
|
|
889
|
+
if (typeof op !== "object" || op === null) return uuidv42;
|
|
890
|
+
const ids = keptIds.get(op) ?? [];
|
|
891
|
+
keptIds.set(op, ids);
|
|
892
|
+
let next = 0;
|
|
893
|
+
return () => {
|
|
894
|
+
if (next === ids.length) ids.push(uuidv42());
|
|
895
|
+
return ids[next++];
|
|
896
|
+
};
|
|
897
|
+
}
|
|
882
898
|
function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
883
899
|
const ops = [...held, ...newOps];
|
|
884
900
|
const opLabel = (i) => i < held.length ? `earlier op ${i + 1} (${ops[i].op})` : `op ${i - held.length + 1} (${ops[i].op})`;
|
|
@@ -994,6 +1010,7 @@ function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
|
994
1010
|
let lastLabel = opLabel(0);
|
|
995
1011
|
for (const [i, op] of ops.entries()) {
|
|
996
1012
|
const label = opLabel(i);
|
|
1013
|
+
const newId = idsOf(op);
|
|
997
1014
|
switch (op.op) {
|
|
998
1015
|
case "update": {
|
|
999
1016
|
const prep = prepareEdit(op, i, label, "planner");
|
|
@@ -1023,7 +1040,7 @@ function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
|
1023
1040
|
errors.push(`${label}: new task needs a title`);
|
|
1024
1041
|
break;
|
|
1025
1042
|
}
|
|
1026
|
-
const planned = plannedTask(spec, intake, { freshIds: true });
|
|
1043
|
+
const planned = plannedTask(spec, intake, { freshIds: true, newId });
|
|
1027
1044
|
const refused = refusalOf(planned);
|
|
1028
1045
|
if (refused) {
|
|
1029
1046
|
errors.push(`${label}: ${refused}`);
|
|
@@ -1119,6 +1136,7 @@ function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
|
1119
1136
|
const stories = [...new Set(toMerge.flatMap((t) => t.userStoriesCovered ?? []))];
|
|
1120
1137
|
const planned = plannedTask(spec, intake, {
|
|
1121
1138
|
freshIds: true,
|
|
1139
|
+
newId,
|
|
1122
1140
|
from: {
|
|
1123
1141
|
readOnly: !writes,
|
|
1124
1142
|
fields: {
|
|
@@ -1212,6 +1230,7 @@ function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
|
1212
1230
|
}
|
|
1213
1231
|
const planned = plannedTask(spec, intake, {
|
|
1214
1232
|
freshIds: true,
|
|
1233
|
+
newId,
|
|
1215
1234
|
from: {
|
|
1216
1235
|
readOnly: isReadOnly(target, catalog),
|
|
1217
1236
|
fields: {
|
|
@@ -1302,7 +1321,7 @@ function applyTaskOps(currentTasks, newOps, runners, catalog, held = []) {
|
|
|
1302
1321
|
tasks = renumberTasks(repaired.tasks);
|
|
1303
1322
|
}
|
|
1304
1323
|
}
|
|
1305
|
-
return errors.length ? { ok: false, tasks: currentTasks, errors, summary: [] } : { ok: true, tasks, errors: [], summary };
|
|
1324
|
+
return errors.length ? { ok: false, tasks: currentTasks, errors, summary: [] } : { ok: true, tasks, errors: [], summary, handles: handleId };
|
|
1306
1325
|
}
|
|
1307
1326
|
|
|
1308
1327
|
export {
|
|
@@ -1316,10 +1335,6 @@ export {
|
|
|
1316
1335
|
extractJsonObject,
|
|
1317
1336
|
stripTrailingCommas,
|
|
1318
1337
|
escapeControlCharsInStrings,
|
|
1319
|
-
effectiveAllowlist,
|
|
1320
|
-
filterModelsForPrompt,
|
|
1321
|
-
clampThinkingEffort,
|
|
1322
|
-
coerceAssignments,
|
|
1323
1338
|
runnerModesFrom,
|
|
1324
1339
|
resolveDefaultMode,
|
|
1325
1340
|
autonomyLevelLabel,
|
|
@@ -1330,12 +1345,17 @@ export {
|
|
|
1330
1345
|
unavailableTaskModeMessage,
|
|
1331
1346
|
carryPlannedModes,
|
|
1332
1347
|
readOnlyRunners,
|
|
1348
|
+
effectiveAllowlist,
|
|
1349
|
+
filterModelsForPrompt,
|
|
1350
|
+
clampThinkingEffort,
|
|
1351
|
+
coerceAssignments,
|
|
1333
1352
|
isTaskRunning,
|
|
1334
1353
|
NOT_STARTED,
|
|
1335
1354
|
taskStatusKind,
|
|
1336
1355
|
awaitingLabel,
|
|
1337
1356
|
blockedByLabel,
|
|
1338
1357
|
skippedLabel,
|
|
1358
|
+
WAITS_FOR_APPROVAL_LABEL,
|
|
1339
1359
|
approvalLabel,
|
|
1340
1360
|
resumeTime,
|
|
1341
1361
|
resumeLabel,
|
|
@@ -1355,4 +1375,4 @@ export {
|
|
|
1355
1375
|
canSplitTask,
|
|
1356
1376
|
applyTaskOps
|
|
1357
1377
|
};
|
|
1358
|
-
//# sourceMappingURL=chunk-
|
|
1378
|
+
//# sourceMappingURL=chunk-A57ITQE7.mjs.map
|