@ordewell/core 0.4.21 → 0.4.22

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.
@@ -63,6 +63,7 @@ function resolveTaskMode(emittedMode, runnerId, runnerModes, autonomousDefault =
63
63
  if (!runnerModes) return rawMode === "plan" ? "plan" : "build";
64
64
  const modes = runnerModes[runnerId];
65
65
  if (!modes || modes.length === 0) return rawMode;
66
+ if (!emittedMode) return resolveDefaultMode(modes, autonomousDefault) ?? "build";
66
67
  const validIds = new Set(modes.map((m) => m.id));
67
68
  if (rawMode === "build" || validIds.has(rawMode)) {
68
69
  if (rawMode === "plan" || rawMode === "build") return rawMode;
@@ -395,4 +396,4 @@ export {
395
396
  validatePlanModification,
396
397
  parsePartialPlan
397
398
  };
398
- //# sourceMappingURL=chunk-QJ63PD5K.mjs.map
399
+ //# sourceMappingURL=chunk-MKUTJDT2.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 plugin 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\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 '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, 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 if (!parsed || typeof parsed !== 'object' || !Array.isArray((parsed as { tasks?: unknown }).tasks)) {\n throw new PlanParseError('Invalid plan: missing tasks array', raw);\n }\n\n const rawTasks = (parsed as { tasks: Record<string, unknown>[] }).tasks;\n if (rawTasks.length === 0) {\n throw new PlanParseError('Plan contained no tasks', raw);\n }\n\n const tasks = rawTasks.map((t) => parseTask(t, runners, runnerModes, autonomousDefault));\n\n validateVerticalSliceShape(tasks, raw);\n\n for (const t of tasks) {\n if (!runners.includes(t.assignedRunner)) {\n throw new PlanParseError(\n `Task \"${t.title}\" has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`,\n raw,\n { semantic: true },\n );\n }\n }\n\n return tasks;\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 ORDEWELL_CHECKPOINT 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\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 return 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((s: Record<string, unknown>) => ({\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: Record<string, unknown>) =>\n parseTask(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 });\n}\n\nfunction validateVerticalSliceShape(tasks: Task[], raw: string, parentTitle?: string): void {\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 semantic = { semantic: true };\n\n for (const t of tasks) {\n if (!t.sliceType) {\n throw new PlanParseError(\n `${where(t)} is missing sliceType. Every task must specify sliceType (\"AFK\" or \"HITL\").`,\n raw,\n semantic,\n );\n }\n\n if (t.type === 'ai' && !t.autonomy) {\n throw new PlanParseError(\n `AI ${where(t)} is missing autonomy. Every AI task must specify autonomy (\"AFK\" or \"HITL\").`,\n raw,\n semantic,\n );\n }\n\n if (t.autonomy === 'AFK' && t.userSteps && t.userSteps.length > 0) {\n throw new PlanParseError(\n `${where(t)} has autonomy \"AFK\" but contains userSteps. AFK tasks must not have user touchpoints.`,\n raw,\n semantic,\n );\n }\n\n if (t.type === 'user' && t.sliceType !== 'HITL') {\n throw new PlanParseError(\n `User ${where(t)} has sliceType \"${t.sliceType}\". User tasks must have sliceType \"HITL\".`,\n raw,\n semantic,\n );\n }\n\n if (t.subtasks.length > 0) {\n validateVerticalSliceShape(t.subtasks, raw, t.title);\n }\n }\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"]}
package/dist/index.d.mts CHANGED
@@ -2942,6 +2942,13 @@ declare function coerceAssignments(tasks: Task[], perRunnerAllowlist: Partial<Re
2942
2942
  interface RunnerCatalog {
2943
2943
  models: DiscoveredModel[];
2944
2944
  modes: RunnerModeInfo[];
2945
+ /**
2946
+ * The mode a task lands on when it carries none this runner offers: the
2947
+ * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
2948
+ * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
2949
+ * workspace-write sandbox, not the full access an unattended run needs.
2950
+ */
2951
+ defaultMode?: string;
2945
2952
  }
2946
2953
  /**
2947
2954
  * The model, thinking effort and mode a runner offers for a task.
@@ -2952,8 +2959,8 @@ interface RunnerCatalog {
2952
2959
  * assignment — a runner change (below) and a hand-added task derive it the same
2953
2960
  * way. Each field in `current` is preserved when the runner also offers it, and
2954
2961
  * otherwise snapped to that runner's preferred entry (discovery already sorts
2955
- * models by the manifest's `preferredPatterns`; `modes[0]` is the manifest's own
2956
- * first choice).
2962
+ * models by the manifest's `preferredPatterns`; the mode is the catalog's
2963
+ * `defaultMode`).
2957
2964
  *
2958
2965
  * An empty catalog means discovery failed or the runner is a plugin we have no
2959
2966
  * list for — not that the runner offers nothing. That field is left out of the
package/dist/index.d.ts CHANGED
@@ -2942,6 +2942,13 @@ declare function coerceAssignments(tasks: Task[], perRunnerAllowlist: Partial<Re
2942
2942
  interface RunnerCatalog {
2943
2943
  models: DiscoveredModel[];
2944
2944
  modes: RunnerModeInfo[];
2945
+ /**
2946
+ * The mode a task lands on when it carries none this runner offers: the
2947
+ * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
2948
+ * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
2949
+ * workspace-write sandbox, not the full access an unattended run needs.
2950
+ */
2951
+ defaultMode?: string;
2945
2952
  }
2946
2953
  /**
2947
2954
  * The model, thinking effort and mode a runner offers for a task.
@@ -2952,8 +2959,8 @@ interface RunnerCatalog {
2952
2959
  * assignment — a runner change (below) and a hand-added task derive it the same
2953
2960
  * way. Each field in `current` is preserved when the runner also offers it, and
2954
2961
  * otherwise snapped to that runner's preferred entry (discovery already sorts
2955
- * models by the manifest's `preferredPatterns`; `modes[0]` is the manifest's own
2956
- * first choice).
2962
+ * models by the manifest's `preferredPatterns`; the mode is the catalog's
2963
+ * `defaultMode`).
2957
2964
  *
2958
2965
  * An empty catalog means discovery failed or the runner is a plugin we have no
2959
2966
  * list for — not that the runner offers nothing. That field is left out of the
package/dist/index.js CHANGED
@@ -4340,6 +4340,7 @@ function resolveTaskMode(emittedMode, runnerId, runnerModes, autonomousDefault =
4340
4340
  if (!runnerModes) return rawMode === "plan" ? "plan" : "build";
4341
4341
  const modes = runnerModes[runnerId];
4342
4342
  if (!modes || modes.length === 0) return rawMode;
4343
+ if (!emittedMode) return resolveDefaultMode(modes, autonomousDefault) ?? "build";
4343
4344
  const validIds = new Set(modes.map((m) => m.id));
4344
4345
  if (rawMode === "build" || validIds.has(rawMode)) {
4345
4346
  if (rawMode === "plan" || rawMode === "build") return rawMode;
@@ -12836,7 +12837,7 @@ function runnerAssignment(catalog, current) {
12836
12837
  changes.thinkingEffort = thinkingEffort;
12837
12838
  }
12838
12839
  if (catalog.modes.length > 0) {
12839
- changes.taskMode = catalog.modes.some((m) => m.id === current?.taskMode) ? current.taskMode : catalog.modes[0].id;
12840
+ changes.taskMode = catalog.modes.some((m) => m.id === current?.taskMode) ? current.taskMode : catalog.defaultMode ?? catalog.modes[0].id;
12840
12841
  }
12841
12842
  return changes;
12842
12843
  }
@@ -14276,9 +14277,11 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
14276
14277
  }
14277
14278
  /** What a runner offers, as {@link runnerAssignment} needs it. Spawns the runner's CLI to list models. */
14278
14279
  async catalogFor(runner) {
14280
+ const modes = this.runnerModesFor([runner])[runner];
14279
14281
  return {
14280
14282
  models: (await this.modelResolver.modelsForRunners([runner]))[runner] ?? [],
14281
- modes: this.runnerModesFor([runner])[runner]
14283
+ modes,
14284
+ defaultMode: resolveDefaultMode(modes, this.config.autonomousMode)
14282
14285
  };
14283
14286
  }
14284
14287
  /**