@signalridge/pi-workflows 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -15,8 +15,20 @@ import {
15
15
  queryChildSessionContextImmediate,
16
16
  } from "./rpc-client.js";
17
17
  import { listSavedWorkflows, loadSavedWorkflow, saveWorkflow } from "./saved-workflows.js";
18
+ import {
19
+ defaultStrengthTable,
20
+ isWorkflowStrength,
21
+ WORKFLOW_STRENGTH_LIST,
22
+ WORKFLOW_STRENGTHS,
23
+ type WorkflowStrengthTable,
24
+ } from "./strengths.js";
18
25
  import { liveWidgetLines, showWorkflowNavigator } from "./ui.js";
19
- import { loadWorkflowSettings, saveWorkflowSettings } from "./workflow-settings.js";
26
+ import {
27
+ loadProjectWorkflowSettings,
28
+ loadWorkflowSettings,
29
+ saveWorkflowSettings,
30
+ type WorkflowSettings,
31
+ } from "./workflow-settings.js";
20
32
 
21
33
  const WorkflowToolSchema = Type.Object(
22
34
  {
@@ -129,7 +141,7 @@ export function buildAdHocWorkflowScript(prompt: string): string {
129
141
  const originalPrompt = ${JSON.stringify(prompt)};
130
142
  const plan = await agent("Design a bounded execution plan for this user task. Return 1-8 independent or dependency-linked tasks. Use unique short ids, concise descriptions, exact worker prompts, and only dependencies that appear in the task list. Prefer independent tasks when possible. User task:\\n\\n" + originalPrompt, {
131
143
  label: "planner",
132
- tier: "medium",
144
+ strength: "medium",
133
145
  schema: {
134
146
  type: "object",
135
147
  properties: {
@@ -158,7 +170,7 @@ const graph = await orchestrate(plan.tasks.map((task) => ({
158
170
  id: task.id,
159
171
  description: task.description,
160
172
  dependsOn: task.dependsOn || [],
161
- run: ({ results, statuses }) => agent(task.prompt + "\\n\\nDependency results (null means unavailable):\\n" + JSON.stringify({ results, statuses }), { label: task.id, tier: "low" }),
173
+ run: ({ results, statuses }) => agent(task.prompt + "\\n\\nDependency results (null means unavailable):\\n" + JSON.stringify({ results, statuses }), { label: task.id, strength: "low" }),
162
174
  })), { onError: "continue" });
163
175
  const SYNTHESIS_CONTEXT_LIMIT = 78000;
164
176
  function renderContextValue(value) {
@@ -232,7 +244,7 @@ while (synthesisJson.length > SYNTHESIS_CONTEXT_LIMIT && (valuePreviewBudget > 1
232
244
  synthesisJson = JSON.stringify(synthesisContext);
233
245
  }
234
246
  if (synthesisJson.length > SYNTHESIS_CONTEXT_LIMIT) throw new Error("Unable to fit the synthesis context within its managed prompt budget");
235
- return await agent("Synthesize the worker results into a direct answer to the original task. Mention incomplete or failed coverage explicitly.\\n\\nOriginal task, execution plan, and graph ledger:\\n" + synthesisJson, { label: "synthesizer", tier: "medium" });`;
247
+ return await agent("Synthesize the worker results into a direct answer to the original task. Mention incomplete or failed coverage explicitly.\\n\\nOriginal task, execution plan, and graph ledger:\\n" + synthesisJson, { label: "synthesizer", strength: "medium" });`;
236
248
  }
237
249
 
238
250
  export default function piWorkflows(pi: ExtensionAPI): void {
@@ -346,37 +358,35 @@ export default function piWorkflows(pi: ExtensionAPI): void {
346
358
  const DEFAULT_EXCLUDED_TOOLS = ["workflow", "workflow_control"] as const;
347
359
 
348
360
  /**
349
- * Resolve a nested `workflow(name)` reference, saying where the script came
350
- * from.
361
+ * Persist one settings change into this project's own file.
351
362
  *
352
- * Shipped-ness has to be reported per script rather than inherited from the
353
- * frame that called it: a user script may call a built-in by name, and a
354
- * built-in's tier names are preferences against whatever catalogue the host
355
- * defines, while a user script's are assertions about a catalogue the user
356
- * owns. Inheriting the caller's flag would apply the wrong rule in both
357
- * directions.
363
+ * `workflowSettings` is the merged view — defaults, then global, then project
364
+ * — while `saveWorkflowSettings` replaces the project file with whatever it
365
+ * is handed. Handing it the merged view would copy every global value and
366
+ * every default into the project on the first edit, and the project would
367
+ * stop tracking the global file from then on. So the patch lands on project
368
+ * scope and the merged view is re-derived from what actually landed, which is
369
+ * also what makes it safe to report the result rather than the intent.
358
370
  */
359
- const resolveNestedWorkflow = (name: string): { script: string; shippedScript?: boolean } | undefined => {
360
- // A saved file shadows a built-in of the same name; it is the user's own
361
- // script and keeps the strict tier check.
362
- const saved = loadSavedWorkflow(name, sessionCwd);
363
- if (saved !== undefined) return { script: saved };
364
- const builtin = BUILTIN_WORKFLOWS[name]?.script;
365
- return builtin === undefined ? undefined : { script: builtin, shippedScript: true };
371
+ const persistWorkflowSetting = (patch: WorkflowSettings): void => {
372
+ saveWorkflowSettings({ ...loadProjectWorkflowSettings(sessionCwd), ...patch }, sessionCwd);
373
+ workflowSettings = loadWorkflowSettings(sessionCwd);
366
374
  };
367
375
 
376
+ /** Resolve a nested `workflow(name)` reference; a saved file shadows a built-in. */
377
+ const resolveNestedWorkflow = (name: string): string | undefined =>
378
+ loadSavedWorkflow(name, sessionCwd) ?? BUILTIN_WORKFLOWS[name]?.script;
379
+
368
380
  const resolveScript = (
369
381
  script: string | undefined,
370
382
  name: string | undefined,
371
383
  args: unknown,
372
- ): { script: string; source: string; toolset?: string; shippedScript?: boolean } => {
384
+ ): { script: string; source: string; toolset?: string } => {
373
385
  if (script !== undefined && name !== undefined) {
374
386
  throw new Error("Provide either `script` or `name`, not both.");
375
387
  }
376
388
  if (script !== undefined) return { script, source: "inline" };
377
389
  if (name !== undefined) {
378
- // A saved workflow is the user's own file even when it shadows a built-in
379
- // name, so it keeps the strict tier check.
380
390
  const saved = loadSavedWorkflow(name, sessionCwd);
381
391
  if (saved) return { script: saved, source: `saved:${name}` };
382
392
  const builtin = BUILTIN_WORKFLOWS[name];
@@ -384,12 +394,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
384
394
  // Validate named invocations before starting a potentially expensive fleet.
385
395
  // Slash commands provide the same shape through their descriptor primaryArg.
386
396
  validateBuiltinArgs(name, args);
387
- return {
388
- script: builtin.script,
389
- source: `builtin:${name}`,
390
- toolset: builtin.toolset,
391
- shippedScript: true,
392
- };
397
+ return { script: builtin.script, source: `builtin:${name}`, toolset: builtin.toolset };
393
398
  }
394
399
  throw new Error(`Unknown workflow name "${name}". It is neither a saved workflow nor a built-in.`);
395
400
  }
@@ -401,6 +406,11 @@ export default function piWorkflows(pi: ExtensionAPI): void {
401
406
  active = true;
402
407
  const workflowEngine = currentEngine();
403
408
  workflowEngine.onRunSettled = notifyRunChanged;
409
+ // `control("resume")` and the provider-limit retry start executions with no
410
+ // caller to pass options for them, and the strength table is deliberately
411
+ // never frozen onto a run. Without this they would resume with no table and
412
+ // re-run every finished call untabled.
413
+ workflowEngine.strengths = () => workflowSettings.strengths;
404
414
  // Command/tool registrations persist across session replacement, but the
405
415
  // widget belongs to the newly restored engine and must be refreshed too.
406
416
  refreshWidget();
@@ -447,12 +457,12 @@ export default function piWorkflows(pi: ExtensionAPI): void {
447
457
  ? (() => {
448
458
  const prior = workflowEngine.getRun(params.resumeFromRunId);
449
459
  if (!prior) throw new Error(`Workflow run not found: ${params.resumeFromRunId}`);
450
- // Resume reads the frozen flag off the restored run, so
451
- // this only has to satisfy the shared shape.
452
- return { script: prior.script, source: "resume", toolset: prior.toolset, shippedScript: undefined };
460
+ // Resume replays the stored script; only the shared shape
461
+ // has to be satisfied here.
462
+ return { script: prior.script, source: "resume", toolset: prior.toolset };
453
463
  })()
454
464
  : resolveScript(params.script, params.name, params.args);
455
- const { script, source, toolset, shippedScript } = resolved;
465
+ const { script, source, toolset } = resolved;
456
466
  const options: ScriptStartOptions = {
457
467
  args: params.args,
458
468
  background: params.background ?? true,
@@ -462,7 +472,6 @@ export default function piWorkflows(pi: ExtensionAPI): void {
462
472
  tokenBudget: params.tokenBudget,
463
473
  agentTimeoutMs: params.agentTimeoutMs,
464
474
  toolset,
465
- ...(shippedScript === undefined ? {} : { shippedScript }),
466
475
  excludeTools: [...DEFAULT_EXCLUDED_TOOLS],
467
476
  signal,
468
477
  mainModel: ctx?.model?.id,
@@ -479,6 +488,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
479
488
  }
480
489
  : undefined,
481
490
  loadSavedWorkflow: resolveNestedWorkflow,
491
+ strengths: workflowSettings.strengths,
482
492
  };
483
493
  if (params.resumeFromRunId) {
484
494
  const replacement = params.script !== undefined || params.name !== undefined ? script : undefined;
@@ -546,7 +556,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
546
556
 
547
557
  // /workflows: navigator + run/status/watch/stop/pause/resume/rm/save subcommands
548
558
  pi.registerCommand("workflows", {
549
- description: "Workflow runs: open the navigator, or run/status/watch/stop/pause/resume/rm/save",
559
+ description: "Workflow runs: navigator, or run/status/watch/stop/pause/resume/rm/save/strength",
550
560
  handler: async (args: string, ctx: ExtensionCommandContext) => {
551
561
  const tokens = args.trim().split(/\s+/).filter(Boolean);
552
562
  const [subcommand, ...rest] = tokens;
@@ -656,23 +666,158 @@ export default function piWorkflows(pi: ExtensionAPI): void {
656
666
  }
657
667
  return;
658
668
  }
669
+ if (subcommand === "strength") {
670
+ // The Agent tier catalogue is owned by pi-subagents and still is (A8:
671
+ // no second tier config file). What this edits is narrower and is
672
+ // genuinely ours: which Agent tier each workflow strength runs on.
673
+ //
674
+ // A subcommand rather than a command of its own, because the reason
675
+ // to surface it at all is the three-part view below — two lines of
676
+ // which come from this package and one from the other — and that is
677
+ // a page to read, not a mode to be in.
678
+ //
679
+ // The catalogue lives on the managed peer, so it can legitimately be
680
+ // unreachable here. Status still has its other two parts, and a set
681
+ // made against a table that already exists is stored unvalidated: the
682
+ // run-start check reports a tier this host does not define and drops
683
+ // that entry, which costs the redirect rather than the run. A set made
684
+ // with no table at all is refused instead — see below, where the seed
685
+ // is read.
686
+ const catalogue = await (async (): Promise<string[] | undefined> => {
687
+ try {
688
+ return Object.keys((await awaitProtocol()).routingPolicy.profiles).sort();
689
+ } catch {
690
+ return undefined;
691
+ }
692
+ })();
693
+ const knownTiers = catalogue ? new Set(catalogue) : undefined;
694
+ // What a run would resolve, by the same rule the runtime uses: a
695
+ // configured table if there is one, else the shipped default. Shown
696
+ // rather than the configured table alone, because an unconfigured
697
+ // machine is not an unrouted one and a row reading "(unset)" there
698
+ // would describe something other than what the next run does.
699
+ const effective = (): { table: WorkflowStrengthTable; shipped: boolean } => {
700
+ const configured = workflowSettings.strengths;
701
+ return configured === undefined
702
+ ? { table: defaultStrengthTable(knownTiers), shipped: true }
703
+ : { table: configured, shipped: false };
704
+ };
705
+ const describe = (): string => {
706
+ const { table, shipped } = effective();
707
+ const rows = WORKFLOW_STRENGTHS.map((name) => {
708
+ // The default is derived from the catalogue, so without one there
709
+ // is nothing honest to print: the run will still route these, and
710
+ // "(unset)" would claim it will not.
711
+ if (shipped && knownTiers === undefined) return ` ${name} → (default; needs the tier catalogue)`;
712
+ if (table[name] === undefined) return ` ${name} → (unset; the agent's own default)`;
713
+ return ` ${name} → ${table[name]}${shipped ? " (default)" : ""}`;
714
+ });
715
+ // The shipped default is identity, so on a stock install workflow
716
+ // work rides the same profiles every ordinary spawn does — the
717
+ // coupling this indirection exists to let you break, but does not
718
+ // break on its own. Saying so here is the only place the two steps
719
+ // out of it are visible together.
720
+ const decouple = shipped
721
+ ? "\nThis is the shipped default: each strength on the tier of the same name, so " +
722
+ "workflow work shares the tiers your ordinary spawns use (Explore among them). " +
723
+ "To price workflow work on its own, define a tier in /agents → Model tiers, then " +
724
+ "point a strength at it here."
725
+ : "";
726
+ return (
727
+ `Workflow strengths and the Agent tier each runs on:\n${rows.join("\n")}\n` +
728
+ `Tiers this host defines: ${catalogue?.join(", ") ?? "(unavailable)"}\n` +
729
+ "Edits apply to this project; for a machine-wide table edit " +
730
+ "<agent dir>/workflows/settings.json. The tiers themselves live in " +
731
+ "/agents → Model tiers; this only says which one a strength uses." +
732
+ decouple
733
+ );
734
+ };
735
+ const [strength, target] = rest;
736
+ if (strength === undefined) {
737
+ ctx.ui.notify(describe(), "info");
738
+ return;
739
+ }
740
+ if (!isWorkflowStrength(strength)) {
741
+ ctx.ui.notify(`"${strength}" is not a workflow strength. Use one of ${WORKFLOW_STRENGTH_LIST}.`, "warning");
742
+ return;
743
+ }
744
+ if (target === undefined || rest.length !== 2) {
745
+ ctx.ui.notify(`/workflows strength [<${WORKFLOW_STRENGTHS.join("|")}> <tier>|off]`, "warning");
746
+ return;
747
+ }
748
+ // `off` reads as "unset" unless the host really defines a tier by
749
+ // that name, in which case the user's own catalogue outranks our
750
+ // keyword — a tier you cannot point at is worse than a clumsier way
751
+ // to clear one.
752
+ const unset = target === "off" && catalogue?.includes("off") !== true;
753
+ if (!unset && catalogue && !catalogue.includes(target)) {
754
+ ctx.ui.notify(
755
+ `"${target}" is not a tier this host defines. Available: ${catalogue.join(", ") || "(none)"}. ` +
756
+ "Define it in /agents → Model tiers first.",
757
+ "warning",
758
+ );
759
+ return;
760
+ }
761
+ const { table: current, shipped } = effective();
762
+ if (shipped && knownTiers === undefined) {
763
+ // Seeding from a default we cannot compute would write a one-entry
764
+ // table, and this key replaces rather than merges — so the other two
765
+ // strengths would go from routed to unmapped as a side effect of
766
+ // setting this one. Refusing costs a retry; writing costs a run.
767
+ ctx.ui.notify(
768
+ "The tier catalogue is unavailable, so the current table cannot be read. " +
769
+ "Retry once the managed peer is reachable.",
770
+ "warning",
771
+ );
772
+ return;
773
+ }
774
+ // Seeded from the table a run would use, not from this project's file.
775
+ // Every level of this key replaces the one below rather than merging
776
+ // into it, so a patch built on project scope would silently drop
777
+ // whatever the level below contributed: adding one entry would discard
778
+ // an inherited global table, and `off` against an entry this project
779
+ // never wrote would write nothing and leave the mapping standing. The
780
+ // seed does freeze the current table into the project, which is what a
781
+ // replace-wholesale key costs — the entries a partial file left out
782
+ // would be unmapped, not inherited.
783
+ const { [strength]: _cleared, ...others } = current;
784
+ try {
785
+ persistWorkflowSetting({ strengths: unset ? others : { ...others, [strength]: target } });
786
+ } catch (error: unknown) {
787
+ ctx.ui.notify(
788
+ `Could not save workflow strength routing: ${error instanceof Error ? error.message : String(error)}`,
789
+ "warning",
790
+ );
791
+ return;
792
+ }
793
+ // Show the resolved result rather than echoing the write. The
794
+ // settings layer can drop what it was handed, and `off` against the
795
+ // last remaining entry stores an explicit empty table — "map nothing",
796
+ // which is not the same as the shipped default an absent key would
797
+ // mean — so a message announcing the write would describe something
798
+ // other than what the next run will do. Runs already in flight keep
799
+ // what they started with; the table is read fresh at each start and
800
+ // resume.
801
+ //
802
+ // The one case the table alone cannot explain: the host defines a tier
803
+ // named `off`, so the keyword lost to it above and this mapped rather
804
+ // than cleared. The catalogue outranking our keyword is the right call
805
+ // — a tier you cannot point at is worse than a clumsier way to clear
806
+ // one — but a user who typed the documented keyword and got a mapping
807
+ // has to be told which reading won, and where the other one lives.
808
+ const shadowed =
809
+ target === "off" && !unset
810
+ ? '\n\nNote: this host defines a tier named "off", so `off` was read as that tier rather ' +
811
+ "than as the clear keyword. To leave a strength unmapped here, remove its entry from the " +
812
+ "`strengths` object in this project's settings.json."
813
+ : "";
814
+ ctx.ui.notify(`${describe()}${shadowed}`, "info");
815
+ return;
816
+ }
659
817
  ctx.ui.notify(`Unknown /workflows subcommand: ${subcommand}`, "warning");
660
818
  },
661
819
  });
662
820
 
663
- // /workflows-models: tier configuration is owned by pi-subagents; this
664
- // command documents the delegation (A8: no second tier config file).
665
- pi.registerCommand("workflows-models", {
666
- description: "Workflow tier routing: tiers are configured in pi-subagents /agents → Model tiers",
667
- handler: async (_args: string, ctx: ExtensionCommandContext) => {
668
- ctx.ui.notify(
669
- "Workflow tiers (small/medium/large) route through pi-subagents' workflow.tiers settings. " +
670
- "Use /agents → Model tiers to configure them.",
671
- "info",
672
- );
673
- },
674
- });
675
-
676
821
  // /workflows-progress: persistent compact/detailed live-panel settings.
677
822
  pi.registerCommand("workflows-progress", {
678
823
  description: "Workflow progress panel: compact|detailed|status|max <N>",
@@ -686,21 +831,22 @@ export default function piWorkflows(pi: ExtensionAPI): void {
686
831
  );
687
832
  return;
688
833
  }
834
+ let patch: WorkflowSettings;
689
835
  if (action === "compact" || action === "detailed") {
690
- workflowSettings = { ...workflowSettings, progressMode: action };
836
+ patch = { progressMode: action };
691
837
  } else if (action === "max") {
692
838
  const max = Number(tokens[1]);
693
839
  if (!Number.isInteger(max) || max < 1 || max > 32) {
694
840
  ctx.ui.notify("Usage: /workflows-progress max <1-32>", "warning");
695
841
  return;
696
842
  }
697
- workflowSettings = { ...workflowSettings, maxAgentsShown: max };
843
+ patch = { maxAgentsShown: max };
698
844
  } else {
699
845
  ctx.ui.notify("Usage: /workflows-progress compact|detailed|status|max <N>", "warning");
700
846
  return;
701
847
  }
702
848
  try {
703
- saveWorkflowSettings(workflowSettings, sessionCwd);
849
+ persistWorkflowSetting(patch);
704
850
  refreshWidget();
705
851
  ctx.ui.notify(
706
852
  `Workflow progress set to ${workflowSettings.progressMode}, max ${workflowSettings.maxAgentsShown}.`,
@@ -723,9 +869,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
723
869
  // an ordinary answer.
724
870
  const TRIGGER_WORD_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,31}$/u;
725
871
  const persistTriggerSettings = (): void => {
726
- workflowSettings = { ...workflowSettings, keywordTriggerWord: triggerKeyword, keywordTriggerEnabled };
727
872
  try {
728
- saveWorkflowSettings(workflowSettings, sessionCwd);
873
+ persistWorkflowSetting({ keywordTriggerWord: triggerKeyword, keywordTriggerEnabled });
729
874
  } catch {
730
875
  /* setting changes remain active in memory */
731
876
  }
@@ -784,7 +929,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
784
929
  if (event.text.includes(WORKFLOW_ARMED_DIRECTIVE)) return { action: "continue" as const };
785
930
  const effortDirective =
786
931
  effortLevel === "ultra"
787
- ? "Effort: ULTRA. Be exhaustive: use broad parallel review, verification, and completeness checks; choose the large tier where useful."
932
+ ? 'Effort: ULTRA. Be exhaustive: use broad parallel review, verification, and completeness checks; give the deepest steps strength: "high".'
788
933
  : effortLevel === "high"
789
934
  ? "Effort: HIGH. Be thorough: use several independent reviewers and an adversarial verification pass."
790
935
  : "";
@@ -806,9 +951,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
806
951
  return;
807
952
  }
808
953
  effortLevel = level;
809
- workflowSettings = { ...workflowSettings, effort: effortLevel };
810
954
  try {
811
- saveWorkflowSettings(workflowSettings, sessionCwd);
955
+ persistWorkflowSetting({ effort: effortLevel });
812
956
  } catch {
813
957
  /* warning is non-fatal */
814
958
  }
@@ -824,9 +968,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
824
968
  description: "Ultracode: toggle exhaustive workflow effort",
825
969
  handler: async (args: string, ctx: ExtensionCommandContext) => {
826
970
  effortLevel = args.trim().toLowerCase() === "off" ? "off" : "ultra";
827
- workflowSettings = { ...workflowSettings, effort: effortLevel };
828
971
  try {
829
- saveWorkflowSettings(workflowSettings, sessionCwd);
972
+ persistWorkflowSetting({ effort: effortLevel });
830
973
  } catch {
831
974
  /* warning is non-fatal */
832
975
  }
@@ -873,9 +1016,9 @@ export default function piWorkflows(pi: ExtensionAPI): void {
873
1016
  args: scriptArgs,
874
1017
  background: true,
875
1018
  toolset: descriptor.toolset,
876
- shippedScript: true,
877
1019
  excludeTools: [...DEFAULT_EXCLUDED_TOOLS],
878
1020
  loadSavedWorkflow: resolveNestedWorkflow,
1021
+ strengths: workflowSettings.strengths,
879
1022
  };
880
1023
  try {
881
1024
  const workflowEngine = currentEngine();
@@ -1019,9 +1162,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
1019
1162
  const workflowEngine = currentEngine();
1020
1163
  const result = await workflowEngine.start(script, {
1021
1164
  background: true,
1022
- // We wrote this script, tiers included, so its tier names are a
1023
- // preference against whatever catalogue this host defines.
1024
- shippedScript: true,
1165
+ strengths: workflowSettings.strengths,
1025
1166
  mainModel: ctx.model?.id,
1026
1167
  });
1027
1168
  void deliverBackgroundResult(workflowEngine, result.runId);
@@ -1190,6 +1331,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
1190
1331
  background: true,
1191
1332
  mainModel: commandCtx?.model?.id,
1192
1333
  loadSavedWorkflow: resolveNestedWorkflow,
1334
+ strengths: workflowSettings.strengths,
1193
1335
  };
1194
1336
  const result = await workflowEngine.start(script, options);
1195
1337
  void deliverBackgroundResult(workflowEngine, result.runId);
package/src/journal.ts CHANGED
@@ -133,12 +133,6 @@ export interface ScriptRun {
133
133
  frozenArgsPresent: boolean;
134
134
  /** Number of the latest edited-script revision. */
135
135
  revision?: number;
136
- /**
137
- * This run's script ships with the package. Frozen for the same reason
138
- * `toolset` is: a resume must keep treating a shipped script's tier names as
139
- * preferences, not as assertions about the user's catalogue.
140
- */
141
- shippedScript?: boolean;
142
136
  /** Toolset marker persisted across resume (e.g. "web-research" for deep-research). */
143
137
  toolset?: string;
144
138
  /** Frozen run params — persisted so resume after restore keeps original budget/scale. */
@@ -238,7 +232,6 @@ export type JournalEvent =
238
232
  args?: unknown;
239
233
  /** Explicit args presence marker for deterministic resume. */
240
234
  frozenArgsPresent: boolean;
241
- shippedScript?: boolean;
242
235
  toolset?: string;
243
236
  frozenMaxAgents?: number;
244
237
  frozenConcurrency?: number;
@@ -678,11 +671,6 @@ function parseEvent(raw: unknown, runs: Map<string, ScriptRun>): JournalEvent {
678
671
  const frozenArgsPresent = parseFrozenArgsPresence(raw);
679
672
  const args = raw.args === undefined ? undefined : parsePersistedValue(raw.args, "workflow args");
680
673
  const toolset = Object.hasOwn(raw, "toolset") ? boundedString(raw.toolset, "toolset", 64) : undefined;
681
- const shippedScript = (() => {
682
- if (!Object.hasOwn(raw, "shippedScript")) return undefined;
683
- if (typeof raw.shippedScript !== "boolean") throw new Error("shippedScript is invalid");
684
- return raw.shippedScript;
685
- })();
686
674
  const frozenMaxAgents = (() => {
687
675
  if (!Object.hasOwn(raw, "frozenMaxAgents")) return undefined;
688
676
  if (typeof raw.frozenMaxAgents !== "number" || !Number.isFinite(raw.frozenMaxAgents) || raw.frozenMaxAgents < 1) {
@@ -752,7 +740,6 @@ function parseEvent(raw: unknown, runs: Map<string, ScriptRun>): JournalEvent {
752
740
  meta,
753
741
  ...(args === undefined ? {} : { args }),
754
742
  frozenArgsPresent,
755
- ...(shippedScript === undefined ? {} : { shippedScript }),
756
743
  ...(toolset ? { toolset } : {}),
757
744
  ...(frozenMaxAgents !== undefined ? { frozenMaxAgents } : {}),
758
745
  ...(frozenConcurrency !== undefined ? { frozenConcurrency } : {}),
@@ -1192,7 +1179,6 @@ function applyJournalEvent(runs: Map<string, ScriptRun>, event: JournalEvent): v
1192
1179
  ...(event.args === undefined ? {} : { args: structuredClone(event.args) }),
1193
1180
  frozenArgsPresent: event.frozenArgsPresent,
1194
1181
  revision: 0,
1195
- ...(event.shippedScript === undefined ? {} : { shippedScript: event.shippedScript }),
1196
1182
  ...(event.toolset ? { toolset: event.toolset } : {}),
1197
1183
  ...(event.frozenMaxAgents !== undefined ? { frozenMaxAgents: event.frozenMaxAgents } : {}),
1198
1184
  ...(event.frozenConcurrency !== undefined ? { frozenConcurrency: event.frozenConcurrency } : {}),