@ordewell/core 0.4.21 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -2492,7 +2492,6 @@ declare class Session {
2492
2492
  private approvalPolicy;
2493
2493
  private fetcher;
2494
2494
  private settingsFn;
2495
- private currentAllowlist;
2496
2495
  /** Last discovered model catalog — lets sync plan commits clamp thinking efforts to real variants. */
2497
2496
  private modelsCache;
2498
2497
  private unsubObserver;
@@ -2559,11 +2558,26 @@ declare class Session {
2559
2558
  * The catalog a model/task-mode edit is checked against — the same
2560
2559
  * discovered models and manifest modes the per-turn catalog block shows the
2561
2560
  * planner, so a refusal here can never name something invalid that the
2562
- * planner was never told about. `modelsCache` is read live, not filtered by
2563
- * the allowlist here — {@link checkModelAndModeValidity} narrows by
2561
+ * planner was never told about. The catalog is not filtered by the
2562
+ * allowlist here — {@link checkModelAndModeValidity} narrows by
2564
2563
  * allowlist itself, the same way `coerceAssignments` does.
2565
2564
  */
2566
2565
  private editCatalog;
2566
+ /**
2567
+ * The allowlist in force right now. Unset means no restriction — falling
2568
+ * back to the one planning started under would keep a restriction the user
2569
+ * has since cleared.
2570
+ */
2571
+ private allowlist;
2572
+ /**
2573
+ * The discovered catalog as the resolver holds it now, per runner, with this
2574
+ * session's own discovery as the fallback where the resolver has nothing
2575
+ * cached. The resolver's cache outlives this session's snapshot in both
2576
+ * directions — an allowlist picker re-discovers into it mid-session — and a
2577
+ * model allowed after that must reach the planner with its real label and
2578
+ * variants, not as an id-only stub. Never triggers discovery itself.
2579
+ */
2580
+ private models;
2567
2581
  get planState(): LegacyPlanState | null;
2568
2582
  /**
2569
2583
  * The live plan in the shape a saved session is read back as. The disk
@@ -2643,7 +2657,7 @@ declare class Session {
2643
2657
  *
2644
2658
  * Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
2645
2659
  * one the system prompt used — a restricted allowlist stays a hard bound on
2646
- * every turn, not just the first. `this.modelsCache` itself is never
2660
+ * every turn, not just the first. {@link models} itself is never
2647
2661
  * filtered: `coerceAssignments` needs the full discovered catalog alongside
2648
2662
  * the allowlist to resolve labels and clamp effort to real variants.
2649
2663
  *
@@ -2942,6 +2956,13 @@ declare function coerceAssignments(tasks: Task[], perRunnerAllowlist: Partial<Re
2942
2956
  interface RunnerCatalog {
2943
2957
  models: DiscoveredModel[];
2944
2958
  modes: RunnerModeInfo[];
2959
+ /**
2960
+ * The mode a task lands on when it carries none this runner offers: the
2961
+ * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
2962
+ * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
2963
+ * workspace-write sandbox, not the full access an unattended run needs.
2964
+ */
2965
+ defaultMode?: string;
2945
2966
  }
2946
2967
  /**
2947
2968
  * The model, thinking effort and mode a runner offers for a task.
@@ -2952,8 +2973,8 @@ interface RunnerCatalog {
2952
2973
  * assignment — a runner change (below) and a hand-added task derive it the same
2953
2974
  * way. Each field in `current` is preserved when the runner also offers it, and
2954
2975
  * 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).
2976
+ * models by the manifest's `preferredPatterns`; the mode is the catalog's
2977
+ * `defaultMode`).
2957
2978
  *
2958
2979
  * An empty catalog means discovery failed or the runner is a plugin we have no
2959
2980
  * list for — not that the runner offers nothing. That field is left out of the
package/dist/index.d.ts CHANGED
@@ -2492,7 +2492,6 @@ declare class Session {
2492
2492
  private approvalPolicy;
2493
2493
  private fetcher;
2494
2494
  private settingsFn;
2495
- private currentAllowlist;
2496
2495
  /** Last discovered model catalog — lets sync plan commits clamp thinking efforts to real variants. */
2497
2496
  private modelsCache;
2498
2497
  private unsubObserver;
@@ -2559,11 +2558,26 @@ declare class Session {
2559
2558
  * The catalog a model/task-mode edit is checked against — the same
2560
2559
  * discovered models and manifest modes the per-turn catalog block shows the
2561
2560
  * planner, so a refusal here can never name something invalid that the
2562
- * planner was never told about. `modelsCache` is read live, not filtered by
2563
- * the allowlist here — {@link checkModelAndModeValidity} narrows by
2561
+ * planner was never told about. The catalog is not filtered by the
2562
+ * allowlist here — {@link checkModelAndModeValidity} narrows by
2564
2563
  * allowlist itself, the same way `coerceAssignments` does.
2565
2564
  */
2566
2565
  private editCatalog;
2566
+ /**
2567
+ * The allowlist in force right now. Unset means no restriction — falling
2568
+ * back to the one planning started under would keep a restriction the user
2569
+ * has since cleared.
2570
+ */
2571
+ private allowlist;
2572
+ /**
2573
+ * The discovered catalog as the resolver holds it now, per runner, with this
2574
+ * session's own discovery as the fallback where the resolver has nothing
2575
+ * cached. The resolver's cache outlives this session's snapshot in both
2576
+ * directions — an allowlist picker re-discovers into it mid-session — and a
2577
+ * model allowed after that must reach the planner with its real label and
2578
+ * variants, not as an id-only stub. Never triggers discovery itself.
2579
+ */
2580
+ private models;
2567
2581
  get planState(): LegacyPlanState | null;
2568
2582
  /**
2569
2583
  * The live plan in the shape a saved session is read back as. The disk
@@ -2643,7 +2657,7 @@ declare class Session {
2643
2657
  *
2644
2658
  * Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
2645
2659
  * one the system prompt used — a restricted allowlist stays a hard bound on
2646
- * every turn, not just the first. `this.modelsCache` itself is never
2660
+ * every turn, not just the first. {@link models} itself is never
2647
2661
  * filtered: `coerceAssignments` needs the full discovered catalog alongside
2648
2662
  * the allowlist to resolve labels and clamp effort to real variants.
2649
2663
  *
@@ -2942,6 +2956,13 @@ declare function coerceAssignments(tasks: Task[], perRunnerAllowlist: Partial<Re
2942
2956
  interface RunnerCatalog {
2943
2957
  models: DiscoveredModel[];
2944
2958
  modes: RunnerModeInfo[];
2959
+ /**
2960
+ * The mode a task lands on when it carries none this runner offers: the
2961
+ * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
2962
+ * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
2963
+ * workspace-write sandbox, not the full access an unattended run needs.
2964
+ */
2965
+ defaultMode?: string;
2945
2966
  }
2946
2967
  /**
2947
2968
  * The model, thinking effort and mode a runner offers for a task.
@@ -2952,8 +2973,8 @@ interface RunnerCatalog {
2952
2973
  * assignment — a runner change (below) and a hand-added task derive it the same
2953
2974
  * way. Each field in `current` is preserved when the runner also offers it, and
2954
2975
  * 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).
2976
+ * models by the manifest's `preferredPatterns`; the mode is the catalog's
2977
+ * `defaultMode`).
2957
2978
  *
2958
2979
  * An empty catalog means discovery failed or the runner is a plugin we have no
2959
2980
  * 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;
@@ -5588,10 +5589,10 @@ function checkModelAndModeValidity(runner, assignedModel, taskMode, catalog) {
5588
5589
  const models = catalog.modelsByRunner[runner];
5589
5590
  if (models) {
5590
5591
  const modelId = assignedModel.modelId;
5591
- if (!models.some((m) => m.modelId === modelId)) {
5592
+ const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, catalog.modelsByRunner);
5593
+ if (!models.some((m) => m.modelId === modelId) && !allowed?.includes(modelId)) {
5592
5594
  return { ok: false, error: `Runner "${runner}" does not offer model "${modelId}"` };
5593
5595
  }
5594
- const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, catalog.modelsByRunner);
5595
5596
  if (allowed && !allowed.includes(modelId)) {
5596
5597
  return { ok: false, error: `Model "${modelId}" is excluded by the allowlist for runner "${runner}"` };
5597
5598
  }
@@ -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
  }
@@ -13313,7 +13314,6 @@ var Session = class {
13313
13314
  approvalPolicy;
13314
13315
  fetcher;
13315
13316
  settingsFn;
13316
- currentAllowlist = {};
13317
13317
  /** Last discovered model catalog — lets sync plan commits clamp thinking efforts to real variants. */
13318
13318
  modelsCache = {};
13319
13319
  unsubObserver = null;
@@ -13518,7 +13518,6 @@ var Session = class {
13518
13518
  this.beginFreshPlan();
13519
13519
  this.approvalPolicy.reset();
13520
13520
  this.modelsCache = {};
13521
- this.currentAllowlist = {};
13522
13521
  this.remintSessionId();
13523
13522
  }
13524
13523
  runnerModesFor(runners) {
@@ -13528,17 +13527,41 @@ var Session = class {
13528
13527
  * The catalog a model/task-mode edit is checked against — the same
13529
13528
  * discovered models and manifest modes the per-turn catalog block shows the
13530
13529
  * planner, so a refusal here can never name something invalid that the
13531
- * planner was never told about. `modelsCache` is read live, not filtered by
13532
- * the allowlist here — {@link checkModelAndModeValidity} narrows by
13530
+ * planner was never told about. The catalog is not filtered by the
13531
+ * allowlist here — {@link checkModelAndModeValidity} narrows by
13533
13532
  * allowlist itself, the same way `coerceAssignments` does.
13534
13533
  */
13535
13534
  editCatalog() {
13536
13535
  return {
13537
- modelsByRunner: this.modelsCache,
13536
+ modelsByRunner: this.models(),
13538
13537
  runnerModes: this.runnerModesFor(this.plan?.runners ?? []),
13539
- perRunnerAllowlist: this.settingsFn().modelAllowlist ?? this.currentAllowlist
13538
+ perRunnerAllowlist: this.allowlist()
13540
13539
  };
13541
13540
  }
13541
+ /**
13542
+ * The allowlist in force right now. Unset means no restriction — falling
13543
+ * back to the one planning started under would keep a restriction the user
13544
+ * has since cleared.
13545
+ */
13546
+ allowlist() {
13547
+ return this.settingsFn().modelAllowlist ?? {};
13548
+ }
13549
+ /**
13550
+ * The discovered catalog as the resolver holds it now, per runner, with this
13551
+ * session's own discovery as the fallback where the resolver has nothing
13552
+ * cached. The resolver's cache outlives this session's snapshot in both
13553
+ * directions — an allowlist picker re-discovers into it mid-session — and a
13554
+ * model allowed after that must reach the planner with its real label and
13555
+ * variants, not as an id-only stub. Never triggers discovery itself.
13556
+ */
13557
+ models() {
13558
+ const out = { ...this.modelsCache };
13559
+ for (const runner of /* @__PURE__ */ new Set([...Object.keys(out), ...this.plan?.runners ?? []])) {
13560
+ const cached2 = this.modelResolver.getCachedRunnerModels(runner);
13561
+ if (cached2.length > 0) out[runner] = cached2;
13562
+ }
13563
+ return out;
13564
+ }
13542
13565
  get planState() {
13543
13566
  return this.plan;
13544
13567
  }
@@ -13676,8 +13699,7 @@ var Session = class {
13676
13699
  this.modelsCache = modelsByRunner;
13677
13700
  const runnerModes = this.runnerModesFor(chosenRunners);
13678
13701
  const { verificationEnabled, modelAllowlist } = this.settingsFn();
13679
- this.currentAllowlist = modelAllowlist ?? {};
13680
- const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
13702
+ const filteredModels = filterModelsForPrompt(modelsByRunner, modelAllowlist ?? {});
13681
13703
  const now = (/* @__PURE__ */ new Date()).toISOString();
13682
13704
  this.plan = {
13683
13705
  tasks: [],
@@ -13802,12 +13824,12 @@ ${TASK_QUERY_ANSWER_OR_OPS}` : answer,
13802
13824
  */
13803
13825
  taskQueryAnswer(query) {
13804
13826
  const runners = this.plan?.runners ?? [];
13805
- const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
13827
+ const allowlist = this.allowlist();
13806
13828
  return renderTaskQueryAnswer(query, this.store.planTasks, {
13807
13829
  runners,
13808
13830
  // Allowlist-filtered like the always-on block: a read must not offer a
13809
13831
  // model the planner is forbidden to assign.
13810
- models: filterModelsForPrompt(this.modelsCache, allowlist),
13832
+ models: filterModelsForPrompt(this.models(), allowlist),
13811
13833
  modes: this.runnerModesFor(runners),
13812
13834
  autonomousDefault: this.config.autonomousMode
13813
13835
  });
@@ -13868,7 +13890,7 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
13868
13890
  *
13869
13891
  * Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
13870
13892
  * one the system prompt used — a restricted allowlist stays a hard bound on
13871
- * every turn, not just the first. `this.modelsCache` itself is never
13893
+ * every turn, not just the first. {@link models} itself is never
13872
13894
  * filtered: `coerceAssignments` needs the full discovered catalog alongside
13873
13895
  * the allowlist to resolve labels and clamp effort to real variants.
13874
13896
  *
@@ -13878,8 +13900,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
13878
13900
  */
13879
13901
  catalogBlock() {
13880
13902
  if (!this.plan) return null;
13881
- const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
13882
- const filteredModels = filterModelsForPrompt(this.modelsCache, allowlist);
13903
+ const allowlist = this.allowlist();
13904
+ const filteredModels = filterModelsForPrompt(this.models(), allowlist);
13883
13905
  const runnerModes = this.runnerModesFor(this.plan.runners);
13884
13906
  const autonomousDefault = this.config.autonomousMode;
13885
13907
  const modelLines = this.plan.runners.map((runner) => {
@@ -13963,8 +13985,8 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
13963
13985
  const plan = this.mutatePlan(
13964
13986
  () => {
13965
13987
  this.plan.researchLog = [...this.plan.researchLog ?? [], ...turn.researchLog];
13966
- const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
13967
- const coerced = coerceAssignments(result.tasks, allowlist, this.plan.runners, this.modelsCache);
13988
+ const allowlist = this.allowlist();
13989
+ const coerced = coerceAssignments(result.tasks, allowlist, this.plan.runners, this.models());
13968
13990
  if (this.orchestrator.isRunning) {
13969
13991
  this.orchestrator.reconcilePlan(coerced, this.plan.runners);
13970
13992
  } else {
@@ -13996,8 +14018,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
13996
14018
  this.modelsCache = modelsByRunner;
13997
14019
  const runnerModes = this.runnerModesFor(runners);
13998
14020
  const { verificationEnabled, modelAllowlist } = this.settingsFn();
13999
- this.currentAllowlist = modelAllowlist ?? {};
14000
- const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
14021
+ const filteredModels = filterModelsForPrompt(modelsByRunner, modelAllowlist ?? {});
14001
14022
  const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
14002
14023
  return this.aiService.startConversation({
14003
14024
  goal,
@@ -14045,8 +14066,8 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
14045
14066
  return this.mutatePlan(() => {
14046
14067
  this.plan.researchLog = [...this.plan.researchLog ?? [], ...turn.researchLog];
14047
14068
  this.capturePrd(turn.text);
14048
- const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
14049
- const coerced = coerceAssignments(turn.tasks, allowlist, this.plan.runners, this.modelsCache);
14069
+ const allowlist = this.allowlist();
14070
+ const coerced = coerceAssignments(turn.tasks, allowlist, this.plan.runners, this.models());
14050
14071
  this.orchestrator.loadPlan(coerced, this.plan.runners);
14051
14072
  this.store.resetForRun({ preserveCompleted: false });
14052
14073
  this.appendTranscript("assistant", `Plan generated with ${coerced.length} task${coerced.length === 1 ? "" : "s"}.`, now, "plan_generated");
@@ -14276,9 +14297,11 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
14276
14297
  }
14277
14298
  /** What a runner offers, as {@link runnerAssignment} needs it. Spawns the runner's CLI to list models. */
14278
14299
  async catalogFor(runner) {
14300
+ const modes = this.runnerModesFor([runner])[runner];
14279
14301
  return {
14280
14302
  models: (await this.modelResolver.modelsForRunners([runner]))[runner] ?? [],
14281
- modes: this.runnerModesFor([runner])[runner]
14303
+ modes,
14304
+ defaultMode: resolveDefaultMode(modes, this.config.autonomousMode)
14282
14305
  };
14283
14306
  }
14284
14307
  /**
@@ -14295,7 +14318,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
14295
14318
  const allowed = effectiveAllowlist(
14296
14319
  this.settingsFn().modelAllowlist?.[runner],
14297
14320
  runner,
14298
- { ...this.modelsCache, [runner]: catalog.models }
14321
+ { ...this.models(), [runner]: catalog.models }
14299
14322
  );
14300
14323
  if (!allowed) return catalog;
14301
14324
  const models = catalog.models.filter((m) => allowed.includes(m.modelId));