@ordewell/core 0.7.2 → 0.8.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/README.md +5 -2
- package/dist/{IFileSystem-B5asBRDa.d.mts → IFileSystem-D96mT-bm.d.ts} +1 -1
- package/dist/{IFileSystem-CbCmUDBn.d.ts → IFileSystem-DXJCRw5x.d.mts} +1 -1
- package/dist/IRunner-CGCihlGQ.d.ts +1165 -0
- package/dist/IRunner-XVUddYVX.d.mts +1165 -0
- package/dist/{ModeResolver-DtfhWeVL.d.ts → ModeResolver-DE-o2lRF.d.mts} +2 -2
- package/dist/{ModeResolver-DcqAho4y.d.mts → ModeResolver-EWlpmVAy.d.ts} +2 -2
- package/dist/Task-1Q19y7sZ.d.mts +1122 -0
- package/dist/Task-1Q19y7sZ.d.ts +1122 -0
- package/dist/{chunk-OUIQWNS3.mjs → chunk-2M4X3VB7.mjs} +15 -5
- package/dist/chunk-2M4X3VB7.mjs.map +1 -0
- package/dist/{chunk-AJ5KDO6V.mjs → chunk-BLLIBCCS.mjs} +117 -198
- package/dist/chunk-BLLIBCCS.mjs.map +1 -0
- package/dist/{chunk-MZYZS7B3.mjs → chunk-UUU46PVQ.mjs} +68 -8
- package/dist/chunk-UUU46PVQ.mjs.map +1 -0
- package/dist/index.d.mts +516 -713
- package/dist/index.d.ts +516 -713
- package/dist/index.js +5012 -6370
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3275 -4589
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +1 -4
- package/dist/order-labels.d.ts +1 -4
- package/dist/{parsing-COFGUEWx.d.ts → parsing-CZdp2p_n.d.ts} +5 -3
- package/dist/{parsing-D0uo-Icd.d.mts → parsing-GgUJoYKs.d.mts} +5 -3
- package/dist/parsing.d.mts +3 -6
- package/dist/parsing.d.ts +3 -6
- package/dist/parsing.js +25 -4
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +2 -2
- package/dist/{plan-utils-CdW3mxt5.d.mts → plan-utils-BMayhGBj.d.mts} +89 -12
- package/dist/{plan-utils-D4Z8UyLL.d.ts → plan-utils-Cv48fe7r.d.ts} +89 -12
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +120 -65
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +7 -3
- package/dist/testing.d.mts +12 -24
- package/dist/testing.d.ts +12 -24
- package/dist/testing.js +24 -28
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +17 -27
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/tdd/SKILL.md +19 -0
- package/dist/Task-BpzIbunq.d.mts +0 -2599
- package/dist/Task-BpzIbunq.d.ts +0 -2599
- package/dist/chunk-AJ5KDO6V.mjs.map +0 -1
- package/dist/chunk-MZYZS7B3.mjs.map +0 -1
- package/dist/chunk-OUIQWNS3.mjs.map +0 -1
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
} from "./chunk-H5O74L77.mjs";
|
|
9
9
|
import {
|
|
10
10
|
createTask,
|
|
11
|
-
opsFlag
|
|
12
|
-
|
|
11
|
+
opsFlag,
|
|
12
|
+
skillNames
|
|
13
|
+
} from "./chunk-UUU46PVQ.mjs";
|
|
13
14
|
|
|
14
15
|
// src/services/ModeResolver.ts
|
|
15
16
|
function runnerModesFrom(registry, runners) {
|
|
@@ -166,6 +167,10 @@ function looksLikePlanAttempt(raw) {
|
|
|
166
167
|
if (matches.length > 0) return true;
|
|
167
168
|
return sawUnbalanced && raw.includes(`"${PLAN_ENVELOPE_KEY}"`);
|
|
168
169
|
}
|
|
170
|
+
var skillsAsked = /* @__PURE__ */ new WeakMap();
|
|
171
|
+
function requestedSkills(task) {
|
|
172
|
+
return skillsAsked.get(task);
|
|
173
|
+
}
|
|
169
174
|
function parseTask(raw, runners, runnerModes, autonomousDefault = true, inherited) {
|
|
170
175
|
const assignedRunner = typeof raw.assignedRunner === "string" ? raw.assignedRunner : runners[0] ?? "claude-code";
|
|
171
176
|
const taskType = raw.type === "user" ? "user" : "ai";
|
|
@@ -173,7 +178,7 @@ function parseTask(raw, runners, runnerModes, autonomousDefault = true, inherite
|
|
|
173
178
|
const description = String(raw.description ?? "");
|
|
174
179
|
const autonomy = taskType === "ai" && typeof raw.autonomy === "string" && (raw.autonomy === "AFK" || raw.autonomy === "HITL") ? raw.autonomy : taskType === "ai" ? inherited?.autonomy : void 0;
|
|
175
180
|
const sliceType = typeof raw.sliceType === "string" && (raw.sliceType === "HITL" || raw.sliceType === "AFK") ? raw.sliceType : taskType === "ai" ? inherited?.sliceType : void 0;
|
|
176
|
-
|
|
181
|
+
const task = createTask({
|
|
177
182
|
id: String(raw.id ?? ""),
|
|
178
183
|
order: Number(raw.order ?? 0),
|
|
179
184
|
title,
|
|
@@ -207,8 +212,12 @@ function parseTask(raw, runners, runnerModes, autonomousDefault = true, inherite
|
|
|
207
212
|
sliceType,
|
|
208
213
|
userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : void 0,
|
|
209
214
|
// A subtask runs with its parent, so only a top-level task carries it (ADR-0020).
|
|
210
|
-
ops: inherited ? void 0 : opsFlag(raw.ops)
|
|
215
|
+
ops: inherited ? void 0 : opsFlag(raw.ops),
|
|
216
|
+
// Not inherited: a subtask's skills are its own.
|
|
217
|
+
skills: skillNames(raw.skills)
|
|
211
218
|
});
|
|
219
|
+
if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);
|
|
220
|
+
return task;
|
|
212
221
|
}
|
|
213
222
|
function verticalSliceErrors(tasks, parentTitle) {
|
|
214
223
|
const where = (t) => parentTitle ? `subtask "${t.title}" of "${parentTitle}"` : `Task "${t.title}"`;
|
|
@@ -411,6 +420,7 @@ export {
|
|
|
411
420
|
parsePlanJson,
|
|
412
421
|
validatePlanTasks,
|
|
413
422
|
looksLikePlanAttempt,
|
|
423
|
+
requestedSkills,
|
|
414
424
|
checkUniqueIds,
|
|
415
425
|
checkNoCycles,
|
|
416
426
|
checkDepsResolve,
|
|
@@ -419,4 +429,4 @@ export {
|
|
|
419
429
|
validatePlanModification,
|
|
420
430
|
parsePartialPlan
|
|
421
431
|
};
|
|
422
|
-
//# sourceMappingURL=chunk-
|
|
432
|
+
//# sourceMappingURL=chunk-2M4X3VB7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/ModeResolver.ts","../src/services/PlanValidator.ts","../src/services/PartialPlanParser.ts"],"sourcesContent":["import type { RunnerId } from '../models/Task';\n\nexport interface RunnerModeInfo {\n id: string;\n label: string;\n description: string;\n autonomous?: boolean;\n safe?: boolean;\n}\n\n/** The slice of RunnerRegistry this module needs — structural, so nothing here depends on the manifest layer. */\nexport interface ManifestLookup {\n getManifest(runner: RunnerId): { modes?: RunnerModeInfo[] } | undefined;\n}\n\n/**\n * A runner's modes are declared only in its manifest, never through a service,\n * so every caller that needs them fuses registry and runner list by hand. This\n * is that fusion, in one place: the planner prompt, the mode pickers on each\n * surface, and the runner-change retarget all read modes the same way.\n *\n * A runner with no modes maps to `[]` rather than being dropped — callers\n * distinguish \"declares no modes\" from \"not asked about\".\n */\nexport function runnerModesFrom(registry: ManifestLookup, runners: RunnerId[]): Record<RunnerId, RunnerModeInfo[]> {\n return Object.fromEntries(runners.map((r) => [r, registry.getManifest(r)?.modes ?? []] as const));\n}\n\n/**\n * Find the mode ID a runner resolves to under the given toggle state.\n * Tag-based first (autonomous/safe); falls back to positional last non-plan\n * if no tag is present on any mode (degrades gracefully on untagged manifests).\n * Returns undefined if `modes` is empty or undefined.\n */\nexport function resolveDefaultMode(\n modes: RunnerModeInfo[] | undefined,\n autonomousDefault = true,\n): string | undefined {\n if (!modes || modes.length === 0) return undefined;\n const tag: 'autonomous' | 'safe' = autonomousDefault ? 'autonomous' : 'safe';\n const tagged = modes.find((m) => m[tag]);\n if (tagged) return tagged.id;\n for (let i = modes.length - 1; i >= 0; i--) {\n if (modes[i].id !== 'plan') return modes[i].id;\n }\n return undefined;\n}\n\nfunction isDefaultMode(m: RunnerModeInfo, autonomousDefault: boolean): boolean {\n return autonomousDefault ? !!m.autonomous : !!m.safe;\n}\n\nfunction modeConflictsWithToggle(\n mode: RunnerModeInfo,\n autonomousDefault: boolean,\n): boolean {\n if (autonomousDefault) return !!mode.safe && !mode.autonomous;\n return !!mode.autonomous && !mode.safe;\n}\n\n/**\n * Build modes compatible with the current toggle. Plan is excluded from the\n * build-mode list (it is never a default for implementation tasks). Modes\n * tagged exclusively for the OPPOSITE toggle are hidden — the LLM cannot see\n * or select them. A dual-tagged mode (both autonomous + safe) is shown under\n * either toggle.\n */\nexport function filteredBuildModes(modes: RunnerModeInfo[], autonomousDefault: boolean): RunnerModeInfo[] {\n const buildModes = modes.filter((m) => m.id !== 'plan' && !modeConflictsWithToggle(m, autonomousDefault));\n const defaultIdx = buildModes.findIndex((m) => isDefaultMode(m, autonomousDefault));\n if (defaultIdx > 0) {\n const [def] = buildModes.splice(defaultIdx, 1);\n buildModes.unshift(def);\n }\n return buildModes;\n}\n\n/** The user-facing name of the `autonomousMode` boolean's two states. */\nexport function autonomyLevelLabel(autonomous: boolean): 'Full' | 'Guarded' {\n return autonomous ? 'Full' : 'Guarded';\n}\n\n/** `full`/`guarded` name the levels; `on`/`off` and `auto` are older words for them. Null for anything else. */\nexport function parseAutonomyLevel(arg: string | undefined): boolean | null {\n switch (arg?.trim().toLowerCase()) {\n case 'full': case 'on': return true;\n case 'guarded': case 'auto': case 'off': return false;\n default: return null;\n }\n}\n\nexport function buildModeGuide(\n runnerModes: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): string {\n const lines: string[] = [];\n for (const [runnerId, modes] of Object.entries(runnerModes)) {\n if (!modes || modes.length === 0) continue;\n const filtered = filteredBuildModes(modes, autonomousDefault);\n if (filtered.length === 0) continue;\n const modeStr = filtered\n .map((m) => {\n const tag = isDefaultMode(m, autonomousDefault) ? ' (DEFAULT)' : '';\n return `${m.id}${tag} (${m.description})`;\n })\n .join(', ');\n lines.push(`- ${runnerId}: ${modeStr}`);\n }\n if (lines.length === 0) return '';\n\n return [\n `Autonomy level: ${autonomyLevelLabel(autonomousDefault)}.`,\n 'AVAILABLE MODES PER RUNNER:',\n ...lines,\n '',\n 'For each AI task, use the mode marked (DEFAULT) unless the task needs a different compatible mode listed above. Avoid the \"plan\" mode (read-only) unless the user explicitly requested analysis-only.',\n ].join('\\n');\n}\n\nexport function resolveTaskMode(\n emittedMode: string | undefined,\n runnerId: RunnerId,\n runnerModes: Record<RunnerId, RunnerModeInfo[]> | undefined,\n autonomousDefault = true,\n): string {\n const rawMode = (emittedMode as string) || 'build';\n\n if (!runnerModes) return rawMode === 'plan' ? 'plan' : 'build';\n\n const modes = runnerModes[runnerId];\n\n if (!modes || modes.length === 0) return rawMode;\n\n // A missing mode is an invalid emission, not a request for legacy `build` —\n // which on Codex aliases the workspace-write sandbox, not full access.\n if (!emittedMode) return resolveDefaultMode(modes, autonomousDefault) ?? 'build';\n\n const validIds = new Set(modes.map((m) => m.id));\n\n if (rawMode === 'build' || validIds.has(rawMode)) {\n if (rawMode === 'plan' || rawMode === 'build') return rawMode;\n const modeObj = modes.find((m) => m.id === rawMode);\n if (modeObj && modeConflictsWithToggle(modeObj, autonomousDefault)) {\n const fallback = resolveDefaultMode(modes, autonomousDefault);\n return fallback ?? 'build';\n }\n return rawMode;\n }\n\n const fallback = resolveDefaultMode(modes, autonomousDefault);\n return fallback ?? 'build';\n}\n","import { Task, createTask, opsFlag, skillNames, TaskModelAssignment, RunnerId, type ValidationContext, type ValidationResult } from '../models/Task';\nimport { extractObjectsWithKey, stripTrailingCommas, escapeControlCharsInStrings, PlanParseError, PLAN_ENVELOPE_KEY } from './JsonExtractor';\nimport { resolveTaskMode, type RunnerModeInfo } from './ModeResolver';\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\n/** One reason a plan was refused, pinned to the task and field it is about. */\nexport interface PlanTaskError {\n /** Absent when the envelope itself is wrong rather than one of its tasks. */\n taskId?: string;\n field: string;\n message: string;\n}\n\nexport type PlanTasksResult = { ok: true; tasks: Task[] } | { 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, in the order the text path has always met them, so\n * the first is the one its 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 tasks = rawTasks.map((t) => parseTask(asRecord(t), runners, runnerModes, autonomousDefault));\n\n const errors = verticalSliceErrors(tasks);\n for (const t of tasks) {\n if (!runners.includes(t.assignedRunner)) {\n errors.push({\n taskId: t.id,\n field: 'assignedRunner',\n message: `Task \"${t.title}\" has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`,\n });\n }\n }\n\n return errors.length > 0 ? { ok: false, errors } : { 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// A non-object entry reads as an empty task, so the shape checks name it\n// instead of the parser throwing a TypeError out of the validator.\nfunction asRecord(value: unknown): Record<string, unknown> {\n return isRecord(value) ? 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 * The parent's slice classification, handed to `ai` subtasks that omit their\n * own. Budget models emit sub-steps as bare `{id, order, title, description,\n * type}` — neither prompt has ever shown a populated `subtasks` entry — and the\n * recursive slice contract then rejected the whole plan. Inheriting invents no\n * information: an `ai` sub-step of an AFK slice is AFK. Deliberately NOT a\n * blanket `'AFK'` default, which would silently strip a human gate the planner\n * intended (`sliceType` is what puts the checkpoint instructions in the runner prompt,\n * and subtasks are schedulable — PlanStore.rebuild flattens them into\n * _allTasks). `type: 'user'` subtasks inherit nothing and must still declare\n * HITL themselves.\n */\ntype InheritedSlice = { sliceType?: Task['sliceType']; autonomy?: Task['autonomy'] };\n\n/**\n * What each parsed task asked to attach, as the planner wrote it. Kept beside\n * the task rather than on it: {@link skillNames} drops a name no skill could\n * have, and the plan's skill check still owes a warning for it. A copy of the\n * task (assignment coercion) does not carry it, so check what was parsed.\n */\nconst skillsAsked = new WeakMap<Task, unknown[]>();\n\n/** The `skills` a parsed task was submitted with, before normalization; undefined for a task not parsed here. */\nexport function requestedSkills(task: Task): readonly unknown[] | undefined {\n return skillsAsked.get(task);\n}\n\nfunction parseTask(\n raw: Record<string, unknown>,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n inherited?: InheritedSlice,\n): Task {\n const assignedRunner = typeof raw.assignedRunner === 'string' ? raw.assignedRunner : (runners[0] ?? 'claude-code');\n const taskType = (raw.type === 'user' ? 'user' : 'ai') as Task['type'];\n const title = String(raw.title ?? '');\n const description = String(raw.description ?? '');\n // Explicit child values always win; inheritance only fills a gap, and only\n // for `ai` tasks.\n const autonomy = taskType === 'ai' && typeof raw.autonomy === 'string' && (raw.autonomy === 'AFK' || raw.autonomy === 'HITL')\n ? raw.autonomy\n : (taskType === 'ai' ? inherited?.autonomy : undefined);\n const sliceType = typeof raw.sliceType === 'string' && (raw.sliceType === 'HITL' || raw.sliceType === 'AFK')\n ? raw.sliceType\n : (taskType === 'ai' ? inherited?.sliceType : undefined);\n const task = createTask({\n id: String(raw.id ?? ''),\n order: Number(raw.order ?? 0),\n title,\n description,\n type: taskType,\n dependencies: Array.isArray(raw.dependencies)\n ? raw.dependencies.map(String)\n : [],\n // The planner sometimes omits `prompt` on an AI task while still filling in\n // description/title — same fallback TaskOps.applyTaskOps uses for `add`, so\n // a task never silently ends up unschedulable (getReadyTasks requires prompt).\n prompt: raw.prompt ? String(raw.prompt) : (description || title || undefined),\n userSteps: Array.isArray(raw.userSteps)\n ? raw.userSteps.map(asRecord).map((s) => ({\n order: Number(s.order ?? 0),\n instruction: String(s.instruction ?? ''),\n completed: false,\n }))\n : undefined,\n subtasks: Array.isArray(raw.subtasks)\n ? raw.subtasks.map((s: unknown) =>\n parseTask(asRecord(s), runners, runnerModes, autonomousDefault, { sliceType, autonomy }))\n : [],\n assignedModel: raw.assignedModel\n ? {\n modelId: String((raw.assignedModel as Record<string, unknown>).modelId ?? ''),\n modelLabel: String((raw.assignedModel as Record<string, unknown>).modelLabel ?? ''),\n thinkingEffort: (raw.assignedModel as Record<string, unknown>).thinkingEffort as TaskModelAssignment['thinkingEffort'],\n }\n : undefined,\n assignedRunner,\n thinkingEffort: raw.thinkingEffort ? String(raw.thinkingEffort) : undefined,\n taskMode: resolveTaskMode(\n raw.taskMode !== undefined ? String(raw.taskMode) : undefined,\n assignedRunner,\n runnerModes,\n autonomousDefault,\n ),\n autonomy,\n sliceType,\n userStoriesCovered: Array.isArray(raw.userStoriesCovered)\n ? raw.userStoriesCovered.map(String)\n : undefined,\n // A subtask runs with its parent, so only a top-level task carries it (ADR-0020).\n ops: inherited ? undefined : opsFlag(raw.ops),\n // Not inherited: a subtask's skills are its own.\n skills: skillNames(raw.skills),\n });\n if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);\n return task;\n}\n\nfunction verticalSliceErrors(tasks: Task[], parentTitle?: string): PlanTaskError[] {\n // Without this the message names a title that appears nowhere in the plan's\n // top-level `tasks` array, and neither the corrective nor a human can find\n // the offending object.\n const where = (t: Task) => (parentTitle ? `subtask \"${t.title}\" of \"${parentTitle}\"` : `Task \"${t.title}\"`);\n const errors: PlanTaskError[] = [];\n\n for (const t of tasks) {\n const fail = (field: string, message: string) => errors.push({ taskId: t.id, field, message });\n\n if (!t.sliceType) {\n fail('sliceType', `${where(t)} is missing sliceType. Every task must specify sliceType (\"AFK\" or \"HITL\").`);\n }\n\n if (t.type === 'ai' && !t.autonomy) {\n fail('autonomy', `AI ${where(t)} is missing autonomy. Every AI task must specify autonomy (\"AFK\" or \"HITL\").`);\n }\n\n if (t.autonomy === 'AFK' && t.userSteps && t.userSteps.length > 0) {\n fail('userSteps', `${where(t)} has autonomy \"AFK\" but contains userSteps. AFK tasks must not have user touchpoints.`);\n }\n\n if (t.type === 'user' && t.sliceType && t.sliceType !== 'HITL') {\n fail('sliceType', `User ${where(t)} has sliceType \"${t.sliceType}\". User tasks must have sliceType \"HITL\".`);\n }\n\n if (t.subtasks.length > 0) {\n errors.push(...verticalSliceErrors(t.subtasks, t.title));\n }\n }\n return errors;\n}\n\nexport function checkUniqueIds(ctx: ValidationContext): ValidationResult {\n const seen = new Set<string>();\n const errors: string[] = [];\n for (const t of ctx.newPending) {\n if (seen.has(t.id)) {\n errors.push(`Duplicate task ID in modified plan: ${t.id}`);\n }\n seen.add(t.id);\n }\n return { valid: errors.length === 0, errors };\n}\n\nexport function checkNoCycles(ctx: ValidationContext): ValidationResult {\n const ids = new Set(ctx.newPending.map((t) => t.id));\n const adj = new Map<string, string[]>();\n for (const t of ctx.newPending) {\n adj.set(t.id, t.dependencies.filter((d) => ids.has(d)));\n }\n\n const visited = new Set<string>();\n const inStack = new Set<string>();\n const errors: string[] = [];\n\n function dfs(node: string): boolean {\n if (inStack.has(node)) {\n errors.push(`Cycle detected in task dependencies involving task: ${node}`);\n return true;\n }\n if (visited.has(node)) return false;\n visited.add(node);\n inStack.add(node);\n for (const dep of adj.get(node) ?? []) {\n if (dfs(dep)) return true;\n }\n inStack.delete(node);\n return false;\n }\n\n for (const id of ids) {\n if (dfs(id)) break;\n }\n\n return { valid: errors.length === 0, errors };\n}\n\nexport function checkDepsResolve(ctx: ValidationContext): ValidationResult {\n const existingIds = new Set<string>();\n for (const t of ctx.executionLog) existingIds.add(t.id);\n for (const t of ctx.newPending) existingIds.add(t.id);\n\n const errors: string[] = [];\n for (const t of ctx.newPending) {\n for (const dep of t.dependencies) {\n if (!existingIds.has(dep)) {\n errors.push(`Task \"${t.title}\" depends on \"${dep}\" which is not in the execution log or pending tasks`);\n }\n }\n }\n return { valid: errors.length === 0, errors };\n}\n\nexport function checkImmutableLog(ctx: ValidationContext): ValidationResult {\n const logIds = new Map(ctx.executionLog.map((t) => [t.id, t]));\n\n const errors: string[] = [];\n\n for (const t of ctx.newPending) {\n const logEntry = logIds.get(t.id);\n if (logEntry) {\n if (logEntry.status === 'completed' && logEntry.finalized) {\n errors.push(`Task \"${t.id}\" was already completed in the execution log and cannot be re-added`);\n }\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\nexport function checkInProgress(ctx: ValidationContext): ValidationResult {\n const oldInProgress = new Map(\n ctx.oldPending.filter((t) => t.status === 'in_progress').map((t) => [t.id, t])\n );\n const newIds = new Set(ctx.newPending.map((t) => t.id));\n const errors: string[] = [];\n\n for (const [id, oldTask] of oldInProgress) {\n if (!newIds.has(id)) {\n errors.push(`In-progress task \"${oldTask.title}\" was removed from the modified plan`);\n continue;\n }\n const newTask = ctx.newPending.find((t) => t.id === id)!;\n if (newTask.prompt !== oldTask.prompt || newTask.assignedModel?.modelId !== oldTask.assignedModel?.modelId) {\n errors.push(`In-progress task \"${oldTask.title}\" was modified in the new plan`);\n }\n const oldDeps = new Set(oldTask.dependencies);\n const newDeps = new Set(newTask.dependencies);\n if (oldDeps.size !== newDeps.size || [...oldDeps].some((d) => !newDeps.has(d))) {\n errors.push(`In-progress task \"${oldTask.title}\" had dependencies modified`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\nexport function validatePlanModification(ctx: ValidationContext): ValidationResult {\n const checks = [\n checkUniqueIds,\n checkNoCycles,\n checkDepsResolve,\n checkImmutableLog,\n checkInProgress,\n ];\n const allErrors: string[] = [];\n for (const check of checks) {\n const result = check(ctx);\n allErrors.push(...result.errors);\n }\n return { valid: allErrors.length === 0, errors: allErrors };\n}\n","import { stripModelNoise } from './JsonExtractor';\n\n/** A task as seen in a still-streaming plan: its title (possibly partial) and whether its object has closed. */\nexport interface PartialPlanTask {\n title: string;\n status: 'streaming' | 'complete';\n /** Assigned model label, once it has streamed in — for the live row's chip. */\n model?: string;\n /** Task mode (\"build\"|\"plan\"), once it has streamed in — for the live row's chip. */\n mode?: string;\n}\n\n/**\n * Parse a partial (still-streaming) plan-JSON string into an ordered list of task\n * rows for the live progress view. Pure and lenient: it walks the characters of the\n * `tasks` array, emitting one row per task object once it has opened, with the title\n * read incrementally and the status flipping to `complete` when the object's brace\n * closes. The currently-streaming object stays `streaming` even with a partial title.\n * The React view is a thin renderer over this — it never parses JSON itself.\n */\nexport function parsePartialPlan(partial: string): PartialPlanTask[] {\n const text = stripModelNoise(partial);\n const arrayStart = findTasksArrayStart(text);\n if (arrayStart === -1) return [];\n\n const tasks: PartialPlanTask[] = [];\n let depth = 0; // brace depth relative to a task object (0 = between objects)\n let objText = ''; // accumulated text of the in-progress task object\n let inString = false;\n let escaped = false;\n\n for (let i = arrayStart; i < text.length; i++) {\n const ch = text[i];\n if (depth > 0) objText += ch;\n\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') { inString = true; continue; }\n if (ch === ']' && depth === 0) break; // end of the tasks array\n if (ch === '{') {\n if (depth === 0) objText = '{';\n depth++;\n } else if (ch === '}') {\n depth--;\n if (depth === 0) {\n tasks.push(rowFromObject(objText, 'complete'));\n objText = '';\n }\n }\n }\n\n // A task object that opened but never closed is the row currently streaming in.\n if (depth > 0) tasks.push(rowFromObject(objText, 'streaming'));\n return tasks;\n}\n\n/** Index just after the `[` that opens the top-level `tasks` array, or -1 if not present yet. */\nfunction findTasksArrayStart(text: string): number {\n const key = /\"tasks\"\\s*:\\s*\\[/.exec(text);\n return key ? key.index + key[0].length : -1;\n}\n\n/** Build a live row from a (possibly unterminated) task-object fragment. */\nfunction rowFromObject(objText: string, status: PartialPlanTask['status']): PartialPlanTask {\n const model = stringField(objText, 'modelLabel');\n const mode = stringField(objText, 'taskMode');\n return {\n title: titleFromObject(objText),\n status,\n ...(model ? { model } : {}),\n ...(mode ? { mode } : {}),\n };\n}\n\n/** Read the `title` value out of a (possibly unterminated) task-object fragment. */\nfunction titleFromObject(objText: string): string {\n const m = /\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)/.exec(objText);\n if (!m) return '';\n // The captured group may include a trailing closing quote's content only up to an\n // unescaped quote; for a completed value that's the whole title, for a streaming\n // one it's whatever has arrived so far.\n return m[1].replace(/\\\\\"/g, '\"');\n}\n\n/** Read a fully-streamed (closing-quote present) string field's value, or undefined. */\nfunction stringField(objText: string, key: string): string | undefined {\n const m = new RegExp(`\"${key}\"\\\\s*:\\\\s*\"((?:[^\"\\\\\\\\]|\\\\\\\\.)*)\"`).exec(objText);\n return m ? m[1].replace(/\\\\\"/g, '\"') : undefined;\n}\n"],"mappings":"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","names":["fallback"]}
|
|
@@ -10,11 +10,13 @@ import {
|
|
|
10
10
|
createTask,
|
|
11
11
|
flattenTasks,
|
|
12
12
|
inheritedOps,
|
|
13
|
+
inheritedSkills,
|
|
13
14
|
opsFlag,
|
|
14
15
|
removeTaskFromPlan,
|
|
15
16
|
renumberTasks,
|
|
17
|
+
skillNames,
|
|
16
18
|
updateTaskInPlan
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UUU46PVQ.mjs";
|
|
18
20
|
import {
|
|
19
21
|
taskOrderLabel,
|
|
20
22
|
taskRef,
|
|
@@ -120,183 +122,8 @@ function normalizeAgentArgs(tool, args) {
|
|
|
120
122
|
return out;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
// src/services/terminalRender.ts
|
|
124
|
-
var ANSI_OR_CTRL_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[()][AB012]|\x1b[=>]|[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
125
|
-
function flattenTerminalOutput(raw) {
|
|
126
|
-
return raw.replace(ANSI_OR_CTRL_RE, "").replace(/[─-▟]/g, "").replace(/\s+/g, "");
|
|
127
|
-
}
|
|
128
|
-
function renderTerminalOutput(raw) {
|
|
129
|
-
const rows = /* @__PURE__ */ new Map();
|
|
130
|
-
let row = 1;
|
|
131
|
-
let col = 1;
|
|
132
|
-
let savedRow = 1;
|
|
133
|
-
let savedCol = 1;
|
|
134
|
-
const cells = (r) => {
|
|
135
|
-
let line = rows.get(r);
|
|
136
|
-
if (!line) {
|
|
137
|
-
line = [];
|
|
138
|
-
rows.set(r, line);
|
|
139
|
-
}
|
|
140
|
-
return line;
|
|
141
|
-
};
|
|
142
|
-
const firstParam = (params, fallback = 1) => params[0] || fallback;
|
|
143
|
-
const eraseLine = (mode) => {
|
|
144
|
-
const line = cells(row);
|
|
145
|
-
if (mode === 2) {
|
|
146
|
-
rows.set(row, []);
|
|
147
|
-
} else if (mode === 1) {
|
|
148
|
-
for (let c = 0; c < col; c++) line[c] = " ";
|
|
149
|
-
} else {
|
|
150
|
-
line.length = Math.max(0, col - 1);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
for (let i = 0; i < raw.length; ) {
|
|
154
|
-
const ch = raw[i];
|
|
155
|
-
if (ch === "\x1B") {
|
|
156
|
-
const kind = raw[i + 1];
|
|
157
|
-
if (kind === "[") {
|
|
158
|
-
let end = i + 2;
|
|
159
|
-
while (end < raw.length) {
|
|
160
|
-
const code = raw.charCodeAt(end);
|
|
161
|
-
if (code >= 64 && code <= 126) break;
|
|
162
|
-
end++;
|
|
163
|
-
}
|
|
164
|
-
if (end >= raw.length) break;
|
|
165
|
-
const final = raw[end];
|
|
166
|
-
const body = raw.slice(i + 2, end).replace(/^[?<>=!]+/, "");
|
|
167
|
-
const params = body.split(";").map((p) => Number.parseInt(p, 10) || 0);
|
|
168
|
-
switch (final) {
|
|
169
|
-
case "H":
|
|
170
|
-
case "f":
|
|
171
|
-
row = params[0] || 1;
|
|
172
|
-
col = params[1] || 1;
|
|
173
|
-
break;
|
|
174
|
-
case "G":
|
|
175
|
-
col = firstParam(params);
|
|
176
|
-
break;
|
|
177
|
-
case "d":
|
|
178
|
-
row = firstParam(params);
|
|
179
|
-
break;
|
|
180
|
-
case "A":
|
|
181
|
-
row = Math.max(1, row - firstParam(params));
|
|
182
|
-
break;
|
|
183
|
-
case "B":
|
|
184
|
-
row += firstParam(params);
|
|
185
|
-
break;
|
|
186
|
-
case "C":
|
|
187
|
-
col += firstParam(params);
|
|
188
|
-
break;
|
|
189
|
-
case "D":
|
|
190
|
-
col = Math.max(1, col - firstParam(params));
|
|
191
|
-
break;
|
|
192
|
-
case "E":
|
|
193
|
-
row += firstParam(params);
|
|
194
|
-
col = 1;
|
|
195
|
-
break;
|
|
196
|
-
case "F":
|
|
197
|
-
row = Math.max(1, row - firstParam(params));
|
|
198
|
-
col = 1;
|
|
199
|
-
break;
|
|
200
|
-
case "s":
|
|
201
|
-
savedRow = row;
|
|
202
|
-
savedCol = col;
|
|
203
|
-
break;
|
|
204
|
-
case "u":
|
|
205
|
-
row = savedRow;
|
|
206
|
-
col = savedCol;
|
|
207
|
-
break;
|
|
208
|
-
case "K":
|
|
209
|
-
eraseLine(params[0] || 0);
|
|
210
|
-
break;
|
|
211
|
-
case "J":
|
|
212
|
-
if ((params[0] || 0) === 2 || (params[0] || 0) === 3) rows.clear();
|
|
213
|
-
break;
|
|
214
|
-
case "X": {
|
|
215
|
-
const line = cells(row);
|
|
216
|
-
for (let c = 0; c < firstParam(params); c++) line[col - 1 + c] = " ";
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case "P": {
|
|
220
|
-
cells(row).splice(col - 1, firstParam(params));
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
case "@": {
|
|
224
|
-
cells(row).splice(col - 1, 0, ...Array(firstParam(params)).fill(" "));
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
default:
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
i = end + 1;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (kind === "]" || kind === "P" || kind === "^" || kind === "_") {
|
|
234
|
-
let end = i + 2;
|
|
235
|
-
while (end < raw.length && raw[end] !== "\x07" && !(raw[end] === "\x1B" && raw[end + 1] === "\\")) end++;
|
|
236
|
-
if (end >= raw.length) break;
|
|
237
|
-
i = raw[end] === "\x07" ? end + 1 : end + 2;
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (kind === "7") {
|
|
241
|
-
savedRow = row;
|
|
242
|
-
savedCol = col;
|
|
243
|
-
} else if (kind === "8") {
|
|
244
|
-
row = savedRow;
|
|
245
|
-
col = savedCol;
|
|
246
|
-
} else if (kind === "c") {
|
|
247
|
-
rows.clear();
|
|
248
|
-
row = 1;
|
|
249
|
-
col = 1;
|
|
250
|
-
}
|
|
251
|
-
i += kind === "(" || kind === ")" ? 3 : 2;
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (ch === "\r") {
|
|
255
|
-
col = 1;
|
|
256
|
-
i++;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (ch === "\n") {
|
|
260
|
-
row++;
|
|
261
|
-
i++;
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (ch === "\b") {
|
|
265
|
-
col = Math.max(1, col - 1);
|
|
266
|
-
i++;
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (ch === " ") {
|
|
270
|
-
col += 8 - (col - 1) % 8;
|
|
271
|
-
i++;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
if (ch < " " || ch === "\x7F") {
|
|
275
|
-
i++;
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
cells(row)[col - 1] = ch;
|
|
279
|
-
col++;
|
|
280
|
-
i++;
|
|
281
|
-
}
|
|
282
|
-
const populated = [...rows.keys()].sort((a, b) => a - b);
|
|
283
|
-
return populated.map((r) => cells(r).join("")).join("\n");
|
|
284
|
-
}
|
|
285
|
-
function renderCleanCapture(raw, doneToken) {
|
|
286
|
-
const rendered = renderTerminalOutput(raw).replace(/[\s─-▟]+$/gm, "");
|
|
287
|
-
if (!doneToken) return rendered.trim();
|
|
288
|
-
const lines2 = rendered.split("\n");
|
|
289
|
-
const flat = (s) => flattenTerminalOutput(s);
|
|
290
|
-
for (let i = 0; i < lines2.length; i++) {
|
|
291
|
-
const own = flat(lines2[i]).includes(doneToken);
|
|
292
|
-
if (own) return lines2.slice(0, i).join("\n").trim();
|
|
293
|
-
const window = flat(lines2[i]) + (i + 1 < lines2.length ? flat(lines2[i + 1]) : "");
|
|
294
|
-
if (window.includes(doneToken)) return lines2.slice(0, i + 1).join("\n").trim();
|
|
295
|
-
}
|
|
296
|
-
return rendered.trim();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
125
|
// src/conversation/format.ts
|
|
126
|
+
var ANSI_OR_CTRL_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[()][AB012]|\x1b[=>]|[\x00-\x08\x0b-\x1f\x7f]/g;
|
|
300
127
|
var DISPLAY_NAMES = {
|
|
301
128
|
read_file: "Read",
|
|
302
129
|
read_files: "Read",
|
|
@@ -427,6 +254,31 @@ function diffSummary({ added, removed }) {
|
|
|
427
254
|
function settledMessage(id, role, text2, turnId) {
|
|
428
255
|
return { type: "message", id, role, text: text2, streaming: false, ...turnId ? { turnId } : {} };
|
|
429
256
|
}
|
|
257
|
+
function userMessage(id, text2, skills, turnId) {
|
|
258
|
+
const block = settledMessage(id, "user", text2, turnId);
|
|
259
|
+
return skills.length > 0 ? { ...block, skills: skills.map((s) => s.name) } : block;
|
|
260
|
+
}
|
|
261
|
+
function skillLoadNotice({ invokedBy, name, source, path, attaches }) {
|
|
262
|
+
return { invokedBy, name, source, path, ...attaches ? { attaches } : {} };
|
|
263
|
+
}
|
|
264
|
+
var LOAD_SKILL_TOOL = /(?:^|_)load_skill$/;
|
|
265
|
+
function surfaceStep(step) {
|
|
266
|
+
if (!step.success || !LOAD_SKILL_TOOL.test(step.toolLabel ?? step.tool)) return step;
|
|
267
|
+
const name = loadedSkillName(step.args);
|
|
268
|
+
return { ...step, result: name ? `Loaded skill ${name}.` : "Loaded a skill." };
|
|
269
|
+
}
|
|
270
|
+
function loadedSkillName(args) {
|
|
271
|
+
try {
|
|
272
|
+
const parsed = JSON.parse(args);
|
|
273
|
+
const name = typeof parsed === "object" && parsed !== null && "name" in parsed ? parsed.name : void 0;
|
|
274
|
+
return typeof name === "string" ? name : void 0;
|
|
275
|
+
} catch {
|
|
276
|
+
return void 0;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function skillLoadBlock(id, { invokedBy, name, source, path, attaches }, turnId) {
|
|
280
|
+
return { type: "skill_load", id, invokedBy, name, source, path, ...attaches ? { attaches } : {}, ...turnId ? { turnId } : {} };
|
|
281
|
+
}
|
|
430
282
|
function subagentBlock(id, { toolCallId, model, usage, turnId, ...fields }) {
|
|
431
283
|
return {
|
|
432
284
|
type: "subagent",
|
|
@@ -766,6 +618,8 @@ function unqueueMessage(view, { messageId }) {
|
|
|
766
618
|
}
|
|
767
619
|
function reduceTaskLog(view, event) {
|
|
768
620
|
switch (event.type) {
|
|
621
|
+
case "task_skills":
|
|
622
|
+
return append(view, (id) => settledMessage(id, "system", `Skills: ${event.skills.map((s) => `${s.name} (${s.path})`).join(", ")}`));
|
|
769
623
|
case "turn_start":
|
|
770
624
|
return startTurn(view, event);
|
|
771
625
|
case "turn_end":
|
|
@@ -1043,7 +897,8 @@ var UPDATABLE_FIELDS = [
|
|
|
1043
897
|
"userSteps",
|
|
1044
898
|
"autonomy",
|
|
1045
899
|
"sliceType",
|
|
1046
|
-
"ops"
|
|
900
|
+
"ops",
|
|
901
|
+
"skills"
|
|
1047
902
|
];
|
|
1048
903
|
function taskOpsProtocol(executionNote = "", tools = false) {
|
|
1049
904
|
const updateChanges = UPDATABLE_FIELDS.map((f) => f === "dependencies" ? '"dependencies"?:["<id or #order>"]' : `"${f}"?`).join(",");
|
|
@@ -1066,6 +921,7 @@ function taskOpsProtocol(executionNote = "", tools = false) {
|
|
|
1066
921
|
...tools ? ["edit_plan checks the batch when you call it and answers with what it will change, or with the error to fix \u2014 call it again in the same reply. The edit is applied when your reply ends; edits you make in one reply join one batch."] : [],
|
|
1067
922
|
'Just declare the dependencies you want \u2014 display order is repaired for you afterwards, so a rewire or a newly added prerequisite never needs a "reorder" op. Only a task that is running or completed cannot be shifted, so an edit that would need one to move is rejected.' + executionNote,
|
|
1068
923
|
'Flipping "type" between "ai" and "user" is a content change, not just a label: an update to "user" needs "userSteps" in the SAME op, and an update to "ai" needs "prompt" in the SAME op \u2014 the model/mode/effort/autonomy fields (flipping to "user") or the userSteps (flipping to "ai") are cleared automatically.',
|
|
924
|
+
`"skills" in "changes" REPLACES the task's whole skill list. To add one, send the names the plan block shows in its skills:[...] plus the new one; to drop one, send the rest; [] removes them all.`,
|
|
1069
925
|
'"rearm" puts a failed OR completed task back to pending \u2014 verdict and output summary are cleared, any dependents it had blocked are released, and it may carry field changes (e.g. a corrected "prompt") applied in the same op. A running task cannot be re-armed. Never rearm a task just to relabel it \u2014 only when it should actually run again.'
|
|
1070
926
|
];
|
|
1071
927
|
}
|
|
@@ -1161,8 +1017,22 @@ function updatableChanges(raw) {
|
|
|
1161
1017
|
if (raw && key in raw) changes[key] = raw[key];
|
|
1162
1018
|
}
|
|
1163
1019
|
if ("ops" in changes) changes.ops = opsFlag(changes.ops);
|
|
1020
|
+
if ("skills" in changes) changes.skills = skillNames(changes.skills);
|
|
1164
1021
|
return changes;
|
|
1165
1022
|
}
|
|
1023
|
+
function withSkillNames(subtasks) {
|
|
1024
|
+
if (!Array.isArray(subtasks)) return subtasks;
|
|
1025
|
+
return subtasks.map((sub) => {
|
|
1026
|
+
if (typeof sub !== "object" || sub === null) return sub;
|
|
1027
|
+
const { skills, ...rest } = sub;
|
|
1028
|
+
const names = skillNames(skills);
|
|
1029
|
+
return {
|
|
1030
|
+
...rest,
|
|
1031
|
+
...names ? { skills: names } : {},
|
|
1032
|
+
..."subtasks" in rest ? { subtasks: withSkillNames(rest.subtasks) } : {}
|
|
1033
|
+
};
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1166
1036
|
function findInTasks(tasks, id) {
|
|
1167
1037
|
return flattenTasks(tasks).find((t) => t.id === id);
|
|
1168
1038
|
}
|
|
@@ -1391,6 +1261,7 @@ function applyTaskOps(currentTasks, ops, runners, catalog) {
|
|
|
1391
1261
|
const newId = uuidv4();
|
|
1392
1262
|
tasks = addTaskToPlan(tasks, {
|
|
1393
1263
|
...spec,
|
|
1264
|
+
subtasks: withSkillNames(spec.subtasks),
|
|
1394
1265
|
id: newId,
|
|
1395
1266
|
dependencies: ids,
|
|
1396
1267
|
assignedRunner: runner,
|
|
@@ -1494,6 +1365,7 @@ function applyTaskOps(currentTasks, ops, runners, catalog) {
|
|
|
1494
1365
|
autonomy: spec.autonomy ?? toMerge.find((t) => t.autonomy)?.autonomy,
|
|
1495
1366
|
sliceType: spec.sliceType ?? toMerge.find((t) => t.sliceType)?.sliceType,
|
|
1496
1367
|
ops: inheritedOps(toMerge, spec.ops),
|
|
1368
|
+
skills: inheritedSkills(spec.skills, toMerge.flatMap((t) => t.skills ?? [])),
|
|
1497
1369
|
userStoriesCovered: spec.userStoriesCovered ?? (toMerge.flatMap((t) => t.userStoriesCovered ?? []).length ? [...new Set(toMerge.flatMap((t) => t.userStoriesCovered ?? []))] : void 0)
|
|
1498
1370
|
});
|
|
1499
1371
|
const mergedId = mergedTask.id;
|
|
@@ -1575,6 +1447,7 @@ function applyTaskOps(currentTasks, ops, runners, catalog) {
|
|
|
1575
1447
|
autonomy: spec.autonomy ?? target.autonomy,
|
|
1576
1448
|
sliceType: spec.sliceType ?? target.sliceType,
|
|
1577
1449
|
ops: inheritedOps([target], spec.ops),
|
|
1450
|
+
skills: inheritedSkills(spec.skills, target.skills),
|
|
1578
1451
|
userStoriesCovered: spec.userStoriesCovered ?? target.userStoriesCovered
|
|
1579
1452
|
});
|
|
1580
1453
|
newTasks.push(nt);
|
|
@@ -1719,9 +1592,8 @@ function continuability(task) {
|
|
|
1719
1592
|
return refuse("its work is waiting on a merge conflict. Resolve or repair the conflict instead.");
|
|
1720
1593
|
}
|
|
1721
1594
|
if (task.status !== "completed" && task.status !== "failed") return refuse("only a completed or failed task can be.");
|
|
1722
|
-
if (task.
|
|
1723
|
-
|
|
1724
|
-
return { ok: true, sessionId: task.transport.nativeSessionId };
|
|
1595
|
+
if (!task.runnerSessionId) return refuse("its runner left no saved session to resume. Use Retry instead.");
|
|
1596
|
+
return { ok: true, sessionId: task.runnerSessionId };
|
|
1725
1597
|
}
|
|
1726
1598
|
function canContinue(task) {
|
|
1727
1599
|
return continuability(task).ok;
|
|
@@ -1746,7 +1618,8 @@ function serializeTask(t) {
|
|
|
1746
1618
|
autonomy: t.autonomy || void 0,
|
|
1747
1619
|
sliceType: t.sliceType || void 0,
|
|
1748
1620
|
userStoriesCovered: t.userStoriesCovered || void 0,
|
|
1749
|
-
...t.ops ? { ops: true } : {}
|
|
1621
|
+
...t.ops ? { ops: true } : {},
|
|
1622
|
+
...t.skills?.length ? { skills: [...t.skills] } : {}
|
|
1750
1623
|
};
|
|
1751
1624
|
}
|
|
1752
1625
|
function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [], awaitingApproval = 0, mergeGate = [], checkpoint = "") {
|
|
@@ -1756,7 +1629,6 @@ function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [],
|
|
|
1756
1629
|
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null,
|
|
1757
1630
|
idleSince,
|
|
1758
1631
|
...isolation ? { isolation } : {},
|
|
1759
|
-
...t.transport ? { transport: t.transport.fallback ? { kind: t.transport.kind, fallback: t.transport.fallback } : { kind: t.transport.kind } } : {},
|
|
1760
1632
|
...t.status === "awaiting_user" && t.awaitingReason ? { awaitingReason: t.awaitingReason } : {},
|
|
1761
1633
|
...t.status === "awaiting_user" && t.awaitingReason === "checkpoint" && checkpoint ? { checkpoint } : {},
|
|
1762
1634
|
...queued.length > 0 ? { queued: queued.map((m) => ({ ...m })) } : {},
|
|
@@ -1766,14 +1638,39 @@ function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [],
|
|
|
1766
1638
|
...t.forcedPastGate?.length ? { forcedPastGate: [...t.forcedPastGate] } : {}
|
|
1767
1639
|
};
|
|
1768
1640
|
}
|
|
1641
|
+
function surfaceConversation(history) {
|
|
1642
|
+
return history?.map(({ skill, ...m }) => skill ? { ...m, skill: skillLoadNotice(skill) } : m);
|
|
1643
|
+
}
|
|
1644
|
+
function surfaceQueued(queued) {
|
|
1645
|
+
return queued?.map(({ skills, ...m }) => skills ? { ...m, skills: skills.map(skillLoadNotice) } : m);
|
|
1646
|
+
}
|
|
1647
|
+
function surfaceTask({ attemptSkills, subtasks, runnerSessionId: _runnerSessionId, ...task }) {
|
|
1648
|
+
return {
|
|
1649
|
+
...task,
|
|
1650
|
+
...attemptSkills ? { attemptSkills: attemptSkills.map(({ name, source, path }) => ({ name, source, path })) } : {},
|
|
1651
|
+
subtasks: (subtasks ?? []).map(surfaceTask)
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
function surfacePlan(plan) {
|
|
1655
|
+
return {
|
|
1656
|
+
...plan,
|
|
1657
|
+
tasks: plan.tasks.map(surfaceTask),
|
|
1658
|
+
conversationHistory: surfaceConversation(plan.conversationHistory),
|
|
1659
|
+
queuedMessages: surfaceQueued(plan.queuedMessages),
|
|
1660
|
+
researchLog: plan.researchLog?.map((e) => "type" in e ? e : surfaceStep(e))
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function surfacePlanState(state) {
|
|
1664
|
+
return state.phase === "planning" ? { ...state, pendingTasks: state.pendingTasks.map(surfaceTask) } : { ...state, pendingTasks: state.pendingTasks.map(surfaceTask), executionLog: state.executionLog.map(surfaceTask) };
|
|
1665
|
+
}
|
|
1769
1666
|
function serializePlan(plan) {
|
|
1770
1667
|
return {
|
|
1771
1668
|
tasks: plan.tasks.map(serializeTask),
|
|
1772
1669
|
runners: plan.runners,
|
|
1773
1670
|
generatedAt: plan.generatedAt,
|
|
1774
|
-
conversationHistory: plan.conversationHistory,
|
|
1671
|
+
conversationHistory: surfaceConversation(plan.conversationHistory),
|
|
1775
1672
|
prdMarkdown: plan.prdMarkdown,
|
|
1776
|
-
queuedMessages: plan.queuedMessages
|
|
1673
|
+
queuedMessages: surfaceQueued(plan.queuedMessages)
|
|
1777
1674
|
};
|
|
1778
1675
|
}
|
|
1779
1676
|
function executionSummary(tasks) {
|
|
@@ -1807,13 +1704,15 @@ function finalSegmentIndex(blocks, turnId, pastPlan = false) {
|
|
|
1807
1704
|
}
|
|
1808
1705
|
return -1;
|
|
1809
1706
|
}
|
|
1810
|
-
function openTurn(view, turnId, prompt) {
|
|
1707
|
+
function openTurn(view, { turnId, prompt = "", skills = [] }) {
|
|
1811
1708
|
const i = findLastIndex(view.blocks, (b) => b.type === "message" && b.role === "user");
|
|
1812
1709
|
const sent = view.blocks[i];
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1710
|
+
const adopted = sent?.type === "message" && sent.turnId === void 0 && sent.text === prompt;
|
|
1711
|
+
const said = adopted ? { ...view, blocks: replaceAt(view.blocks, i, userMessage(sent.id, prompt, skills, turnId)) } : append(view, (id) => userMessage(id, prompt, skills, turnId));
|
|
1712
|
+
if (skills.length === 0) return said;
|
|
1713
|
+
const at = adopted ? i + 1 : findLastIndex(said.blocks, (b) => b.type === "message" && b.role === "user") + 1;
|
|
1714
|
+
const loads = skills.map((skill, n) => skillLoadBlock(`b${said.nextId + n}`, skill, turnId));
|
|
1715
|
+
return { ...said, blocks: [...said.blocks.slice(0, at), ...loads, ...said.blocks.slice(at)], nextId: said.nextId + loads.length };
|
|
1817
1716
|
}
|
|
1818
1717
|
function streamText(view, { turnId, segmentId, text: text2 }) {
|
|
1819
1718
|
const i = findLastIndex(view.blocks, (b) => isUnsettledSegment(b, turnId) && b.segmentId === segmentId);
|
|
@@ -1960,7 +1859,9 @@ function reduceConversation(view, input) {
|
|
|
1960
1859
|
case "local_entry":
|
|
1961
1860
|
return append(view, (id) => settledMessage(id, input.role, input.text));
|
|
1962
1861
|
case "planner_turn_started":
|
|
1963
|
-
return input.prompt === void 0 ? view : openTurn(view, input
|
|
1862
|
+
return input.prompt === void 0 ? view : openTurn(view, input);
|
|
1863
|
+
case "planner_skill_loaded":
|
|
1864
|
+
return append(sealLane(view, null, -1), (id) => skillLoadBlock(id, input.skill, input.turnId));
|
|
1964
1865
|
case "planner_text_delta":
|
|
1965
1866
|
return streamText(view, input);
|
|
1966
1867
|
case "planner_message":
|
|
@@ -2000,11 +1901,27 @@ function reduceConversation(view, input) {
|
|
|
2000
1901
|
var USER = 0;
|
|
2001
1902
|
var RESEARCH = 1;
|
|
2002
1903
|
var SETTLED = 2;
|
|
2003
|
-
function fromEntry(entry) {
|
|
1904
|
+
function fromEntry(entry, loaded = []) {
|
|
2004
1905
|
if (entry.kind === "plan_generated") return (id) => ({ type: "plan", id: id(), text: "", ...planMarker(entry.content) });
|
|
1906
|
+
if (entry.kind === "skill_load" && entry.skill) {
|
|
1907
|
+
const { skill } = entry;
|
|
1908
|
+
return (id) => skillLoadBlock(id(), skillLoadNotice(skill));
|
|
1909
|
+
}
|
|
1910
|
+
if (entry.role === "user" && !entry.kind) return (id) => userMessage(id(), entry.content, loaded.map(skillLoadNotice));
|
|
2005
1911
|
const role = entry.kind === "system" || entry.kind === "compaction" ? "system" : entry.role === "user" ? "user" : "planner";
|
|
2006
1912
|
return (id) => settledMessage(id(), role, entry.content);
|
|
2007
1913
|
}
|
|
1914
|
+
function loadsAfter(entries, index) {
|
|
1915
|
+
const loads = [];
|
|
1916
|
+
for (let i = index + 1; i < entries.length && entries[i].kind === "skill_load"; i++) {
|
|
1917
|
+
const { skill } = entries[i];
|
|
1918
|
+
if (skill?.invokedBy === "user") loads.push(skill);
|
|
1919
|
+
}
|
|
1920
|
+
return loads;
|
|
1921
|
+
}
|
|
1922
|
+
function rankOf(entry) {
|
|
1923
|
+
return entry.role === "user" && (!entry.kind || entry.kind === "skill_load") ? USER : SETTLED;
|
|
1924
|
+
}
|
|
2008
1925
|
function spawnBrief(step) {
|
|
2009
1926
|
try {
|
|
2010
1927
|
const args = JSON.parse(step.args);
|
|
@@ -2067,7 +1984,7 @@ function fromTranscript(conversationHistory, researchLog, plannerUsage) {
|
|
|
2067
1984
|
const kept = compacted ? history.slice(1) : history;
|
|
2068
1985
|
const cutoff = compacted ? kept[0]?.timestamp ?? compacted.timestamp : "";
|
|
2069
1986
|
const placed = [
|
|
2070
|
-
...kept.map((entry) => ({ at: entry.timestamp, rank: entry
|
|
1987
|
+
...kept.map((entry, i) => ({ at: entry.timestamp, rank: rankOf(entry), build: fromEntry(entry, loadsAfter(kept, i)) })),
|
|
2071
1988
|
...research(researchLog ?? []).filter((item) => item.at >= cutoff)
|
|
2072
1989
|
];
|
|
2073
1990
|
placed.sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : a.rank - b.rank);
|
|
@@ -2114,7 +2031,8 @@ var TURN_STREAM = /* @__PURE__ */ new Set([
|
|
|
2114
2031
|
"research_step_done",
|
|
2115
2032
|
"subagent_started",
|
|
2116
2033
|
"subagent_finished",
|
|
2117
|
-
"planner_turn_ended"
|
|
2034
|
+
"planner_turn_ended",
|
|
2035
|
+
"planner_skill_loaded"
|
|
2118
2036
|
]);
|
|
2119
2037
|
function cutOff(gate, input) {
|
|
2120
2038
|
if (gate.stopped === null || !TURN_STREAM.has(input.type)) return false;
|
|
@@ -2259,15 +2177,14 @@ export {
|
|
|
2259
2177
|
plannerContextFill,
|
|
2260
2178
|
mapAgentTool,
|
|
2261
2179
|
normalizeAgentArgs,
|
|
2262
|
-
flattenTerminalOutput,
|
|
2263
|
-
renderTerminalOutput,
|
|
2264
|
-
renderCleanCapture,
|
|
2265
2180
|
toolHeadline,
|
|
2266
2181
|
outputLines,
|
|
2267
2182
|
outputPreview,
|
|
2268
2183
|
diffStat,
|
|
2269
2184
|
diffRows,
|
|
2270
2185
|
diffSummary,
|
|
2186
|
+
skillLoadNotice,
|
|
2187
|
+
surfaceStep,
|
|
2271
2188
|
EMPTY_TASK_LOG,
|
|
2272
2189
|
runnerToolSubject,
|
|
2273
2190
|
reduceTaskLog,
|
|
@@ -2300,6 +2217,8 @@ export {
|
|
|
2300
2217
|
canContinue,
|
|
2301
2218
|
serializeTask,
|
|
2302
2219
|
serializeTaskStatus,
|
|
2220
|
+
surfacePlan,
|
|
2221
|
+
surfacePlanState,
|
|
2303
2222
|
serializePlan,
|
|
2304
2223
|
executionSummary,
|
|
2305
2224
|
CHECKPOINT_TRUNCATE_LENGTH,
|
|
@@ -2329,4 +2248,4 @@ export {
|
|
|
2329
2248
|
pastGateConfirmation,
|
|
2330
2249
|
newTaskFields
|
|
2331
2250
|
};
|
|
2332
|
-
//# sourceMappingURL=chunk-
|
|
2251
|
+
//# sourceMappingURL=chunk-BLLIBCCS.mjs.map
|