@afokapu/atdd-flow 0.3.58 → 0.3.59

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.58",
3
+ "version": "0.3.59",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/core.ts CHANGED
@@ -25,6 +25,8 @@ export type Desk = {
25
25
  /** Named commands that model portfolio entries may resolve through. */
26
26
  executables?: Record<string, string>;
27
27
  aliases?: Record<string, string>;
28
+ /** Model id used for a role when launch model selection falls back; unlisted roles use the strongest candidate. */
29
+ fallback_model?: Record<string, string>;
28
30
  };
29
31
  export type ModelCandidate = {
30
32
  id: string;
@@ -108,6 +108,15 @@ async function launchEligibleTasks(root: string, record: Seat, address: string)
108
108
  return eligible.filter((entry): entry is NonNullable<typeof entry> => Boolean(entry));
109
109
  }
110
110
 
111
+ /** Fallback is the role's configured model when set (failing closed if unavailable), else the strongest candidate. */
112
+ function fallbackCandidate(config: Desk, role: string, candidates: ModelCandidate[], reason: string) {
113
+ const configured = config.fallback_model?.[role];
114
+ if (configured === undefined) return { candidate: candidates[0]! };
115
+ const candidate = candidates.find((entry) => entry.id === configured);
116
+ if (!candidate) throw new Error(`fallback_model.${role} names "${configured}", which is not an enabled model with an available executable in models.yaml; refusing launch.`);
117
+ return { candidate, appliedFallback: { role, model: candidate.id, reason } };
118
+ }
119
+
111
120
  async function chooseLaunchModel(root: string, config: Desk, record: Seat, portfolio: ModelPortfolio, select: (input: ModelSelectionInput) => Promise<ModelSelectionResponse> = selectModel) {
112
121
  const candidates = availableModelCandidates(config, portfolio);
113
122
  if (!candidates.length) throw new Error("No enabled model in models.yaml has an available executable.");
@@ -128,7 +137,7 @@ async function chooseLaunchModel(root: string, config: Desk, record: Seat, portf
128
137
  const reason = !selection.available ? selection.reason : selection.confidence < 0.75
129
138
  ? `Jev model selection confidence is low (${selection.confidence}).`
130
139
  : `Jev selected a model outside the available portfolio: ${selection.selected_model}.`;
131
- return { candidate: candidates[0]!, selection, fallback: reason };
140
+ return { selection, fallback: reason, ...fallbackCandidate(config, record.role, candidates, reason) };
132
141
  }
133
142
  return { candidate: required(candidates.find((entry) => entry.id === selection.selected_model), `selected model ${selection.selected_model}`), selection };
134
143
  }
@@ -390,7 +399,7 @@ async function verifyRuntime(launch: PreparedLaunch, pane: string, priorPath?: s
390
399
 
391
400
  function receiptSelection(launch: PreparedLaunch) {
392
401
  const { selection } = launch;
393
- if (selection.fallback) return { result: "fallback", reason: selection.fallback };
402
+ if (selection.fallback) return { result: "fallback", reason: selection.fallback, ...(selection.appliedFallback ? { applied_fallback: selection.appliedFallback } : {}) };
394
403
  return { result: "selected", confidence: selection.selection.confidence,
395
404
  ...(selection.selection.available && selection.selection.model ? { model: selection.selection.model } : {}) };
396
405
  }