@ordewell/core 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +9 -6
  2. package/dist/{IFileSystem-B5asBRDa.d.mts → IFileSystem-D96mT-bm.d.ts} +1 -1
  3. package/dist/{IFileSystem-CbCmUDBn.d.ts → IFileSystem-DXJCRw5x.d.mts} +1 -1
  4. package/dist/IRunner-CGCihlGQ.d.ts +1165 -0
  5. package/dist/IRunner-XVUddYVX.d.mts +1165 -0
  6. package/dist/{ModeResolver-DtfhWeVL.d.ts → ModeResolver-DE-o2lRF.d.mts} +2 -2
  7. package/dist/{ModeResolver-DcqAho4y.d.mts → ModeResolver-EWlpmVAy.d.ts} +2 -2
  8. package/dist/Task-1Q19y7sZ.d.mts +1122 -0
  9. package/dist/Task-1Q19y7sZ.d.ts +1122 -0
  10. package/dist/{chunk-OUIQWNS3.mjs → chunk-2M4X3VB7.mjs} +15 -5
  11. package/dist/chunk-2M4X3VB7.mjs.map +1 -0
  12. package/dist/{chunk-AJ5KDO6V.mjs → chunk-BLLIBCCS.mjs} +117 -198
  13. package/dist/chunk-BLLIBCCS.mjs.map +1 -0
  14. package/dist/{chunk-MZYZS7B3.mjs → chunk-UUU46PVQ.mjs} +68 -8
  15. package/dist/chunk-UUU46PVQ.mjs.map +1 -0
  16. package/dist/index.d.mts +517 -714
  17. package/dist/index.d.ts +517 -714
  18. package/dist/index.js +5013 -6371
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +3276 -4590
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/order-labels.d.mts +1 -4
  23. package/dist/order-labels.d.ts +1 -4
  24. package/dist/{parsing-COFGUEWx.d.ts → parsing-CZdp2p_n.d.ts} +5 -3
  25. package/dist/{parsing-D0uo-Icd.d.mts → parsing-GgUJoYKs.d.mts} +5 -3
  26. package/dist/parsing.d.mts +3 -6
  27. package/dist/parsing.d.ts +3 -6
  28. package/dist/parsing.js +25 -4
  29. package/dist/parsing.js.map +1 -1
  30. package/dist/parsing.mjs +2 -2
  31. package/dist/{plan-utils-CdW3mxt5.d.mts → plan-utils-BMayhGBj.d.mts} +89 -12
  32. package/dist/{plan-utils-D4Z8UyLL.d.ts → plan-utils-Cv48fe7r.d.ts} +89 -12
  33. package/dist/plan-utils.d.mts +4 -4
  34. package/dist/plan-utils.d.ts +4 -4
  35. package/dist/plan-utils.js +120 -65
  36. package/dist/plan-utils.js.map +1 -1
  37. package/dist/plan-utils.mjs +7 -3
  38. package/dist/testing.d.mts +12 -24
  39. package/dist/testing.d.ts +12 -24
  40. package/dist/testing.js +24 -28
  41. package/dist/testing.js.map +1 -1
  42. package/dist/testing.mjs +17 -27
  43. package/dist/testing.mjs.map +1 -1
  44. package/package.json +1 -1
  45. package/skills/tdd/SKILL.md +19 -0
  46. package/dist/Task-BpzIbunq.d.mts +0 -2599
  47. package/dist/Task-BpzIbunq.d.ts +0 -2599
  48. package/dist/chunk-AJ5KDO6V.mjs.map +0 -1
  49. package/dist/chunk-MZYZS7B3.mjs.map +0 -1
  50. 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
- } from "./chunk-MZYZS7B3.mjs";
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
- return createTask({
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-OUIQWNS3.mjs.map
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-MZYZS7B3.mjs";
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.transport?.kind !== "structured") return refuse("it ran in a terminal, so there is no saved session to resume. Use Retry instead.");
1723
- if (!task.transport.nativeSessionId) return refuse("its runner left no saved session to resume. Use Retry instead.");
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
- if (sent?.type === "message" && sent.turnId === void 0 && sent.text === prompt) {
1814
- return { ...view, blocks: replaceAt(view.blocks, i, { ...sent, turnId }) };
1815
- }
1816
- return append(view, (id) => settledMessage(id, "user", prompt, turnId));
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.turnId, input.prompt);
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.role === "user" && !entry.kind ? USER : SETTLED, build: fromEntry(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-AJ5KDO6V.mjs.map
2251
+ //# sourceMappingURL=chunk-BLLIBCCS.mjs.map