@osolmaz/pi-workflows 0.15.2 → 0.16.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.
Files changed (124) hide show
  1. package/README.md +6 -6
  2. package/dist/client/activity.d.ts +2 -0
  3. package/dist/client/activity.js +6 -0
  4. package/dist/client/activity.js.map +1 -0
  5. package/dist/client/client.d.ts +101 -0
  6. package/dist/client/client.js +733 -0
  7. package/dist/client/client.js.map +1 -0
  8. package/dist/client/index.d.ts +3 -0
  9. package/dist/client/index.js +3 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/materialize.d.ts +7 -0
  12. package/dist/client/materialize.js +177 -0
  13. package/dist/client/materialize.js.map +1 -0
  14. package/dist/client/protocol.d.ts +60 -0
  15. package/dist/client/protocol.js +269 -0
  16. package/dist/client/protocol.js.map +1 -0
  17. package/dist/client/resolver.d.ts +23 -0
  18. package/dist/client/resolver.js +2 -0
  19. package/dist/client/resolver.js.map +1 -0
  20. package/dist/client/view.d.ts +118 -0
  21. package/dist/client/view.js +3 -0
  22. package/dist/client/view.js.map +1 -0
  23. package/dist/controllers/sqlite.d.ts +64 -0
  24. package/dist/controllers/sqlite.js +219 -3
  25. package/dist/controllers/sqlite.js.map +1 -1
  26. package/dist/extension/index.d.ts +1 -0
  27. package/dist/extension/index.js +384 -156
  28. package/dist/extension/index.js.map +1 -1
  29. package/dist/extension/session-delivery.d.ts +6 -0
  30. package/dist/extension/session-delivery.js +80 -25
  31. package/dist/extension/session-delivery.js.map +1 -1
  32. package/dist/extension/session-view.d.ts +21 -0
  33. package/dist/extension/session-view.js +127 -0
  34. package/dist/extension/session-view.js.map +1 -0
  35. package/dist/extension/widget.d.ts +2 -1
  36. package/dist/extension/widget.js +15 -7
  37. package/dist/extension/widget.js.map +1 -1
  38. package/dist/host/child-worker-supervisor.js +1 -1
  39. package/dist/host/child-worker-supervisor.js.map +1 -1
  40. package/dist/host/resolver-entry.d.ts +2 -23
  41. package/dist/host/resolver-entry.js +1 -1
  42. package/dist/host/resolver-entry.js.map +1 -1
  43. package/dist/host/runner.d.ts +12 -0
  44. package/dist/host/runner.js +565 -43
  45. package/dist/host/runner.js.map +1 -1
  46. package/dist/host/state.d.ts +8 -3
  47. package/dist/host/state.js +59 -27
  48. package/dist/host/state.js.map +1 -1
  49. package/dist/host/view.d.ts +73 -0
  50. package/dist/host/view.js +871 -0
  51. package/dist/host/view.js.map +1 -0
  52. package/dist/host/worker-protocol.js +1 -1
  53. package/dist/host/worker-protocol.js.map +1 -1
  54. package/dist/state/database.d.ts +1 -0
  55. package/dist/state/database.js +15 -0
  56. package/dist/state/database.js.map +1 -1
  57. package/dist/state/prune.d.ts +3 -1
  58. package/dist/state/prune.js +6 -8
  59. package/dist/state/prune.js.map +1 -1
  60. package/dist/state/schema.js +12 -1
  61. package/dist/state/schema.js.map +1 -1
  62. package/dist/viewer/backup.d.ts +2 -0
  63. package/dist/viewer/backup.js +28 -0
  64. package/dist/viewer/backup.js.map +1 -0
  65. package/dist/viewer/cli.d.ts +4 -0
  66. package/dist/viewer/cli.js +150 -170
  67. package/dist/viewer/cli.js.map +1 -1
  68. package/dist/viewer/tui.d.ts +5 -7
  69. package/dist/viewer/tui.js +188 -108
  70. package/dist/viewer/tui.js.map +1 -1
  71. package/dist/workflows/store.d.ts +62 -1
  72. package/dist/workflows/store.js +350 -44
  73. package/dist/workflows/store.js.map +1 -1
  74. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +139 -0
  75. package/docs/2026-09-01-unified-workflow-client-plan.md +381 -0
  76. package/docs/2026-09-02-installed-live-e2e-plan.md +225 -0
  77. package/docs/SQLITE_STATE.md +13 -11
  78. package/docs/WORKFLOW_HOST.md +81 -64
  79. package/docs/WORKFLOW_STEP_MESSAGES.md +4 -4
  80. package/docs/development.md +2 -1
  81. package/docs/live-replay-protocol.md +70 -132
  82. package/docs/tui-viewer.md +10 -14
  83. package/docs/workflows.md +56 -3
  84. package/herdr-plugin.toml +1 -1
  85. package/package.json +9 -3
  86. package/protocol/client.v1.schema.json +137 -0
  87. package/protocol/fixtures/client-v1.json +23 -0
  88. package/src/client/activity.ts +6 -0
  89. package/src/client/client.ts +935 -0
  90. package/src/client/index.ts +24 -0
  91. package/src/client/materialize.ts +228 -0
  92. package/src/client/protocol.ts +327 -0
  93. package/src/client/resolver.ts +26 -0
  94. package/src/client/view.ts +138 -0
  95. package/src/controllers/sqlite.ts +342 -3
  96. package/src/extension/index.ts +482 -171
  97. package/src/extension/session-delivery.ts +88 -25
  98. package/src/extension/session-view.ts +154 -0
  99. package/src/extension/widget.ts +18 -9
  100. package/src/host/child-worker-supervisor.ts +1 -1
  101. package/src/host/resolver-entry.ts +11 -26
  102. package/src/host/runner.ts +749 -75
  103. package/src/host/state.ts +82 -44
  104. package/src/host/view.ts +1084 -0
  105. package/src/host/worker-protocol.ts +1 -1
  106. package/src/state/database.ts +13 -0
  107. package/src/state/prune.ts +11 -11
  108. package/src/state/schema.ts +12 -1
  109. package/src/viewer/backup.ts +29 -0
  110. package/src/viewer/cli.ts +171 -185
  111. package/src/viewer/tui.ts +196 -124
  112. package/src/workflows/store.ts +500 -45
  113. package/dist/host/client.d.ts +0 -48
  114. package/dist/host/client.js +0 -216
  115. package/dist/host/client.js.map +0 -1
  116. package/dist/host/protocol.d.ts +0 -38
  117. package/dist/host/protocol.js +0 -156
  118. package/dist/host/protocol.js.map +0 -1
  119. package/dist/viewer/watch.d.ts +0 -6
  120. package/dist/viewer/watch.js +0 -46
  121. package/dist/viewer/watch.js.map +0 -1
  122. package/src/host/client.ts +0 -293
  123. package/src/host/protocol.ts +0 -196
  124. package/src/viewer/watch.ts +0 -51
@@ -2,6 +2,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  export type ClaimedSessionDelivery = {
4
4
  deliveryId: string;
5
+ claimExpiresAt: number;
6
+ isStillDeliverable: () => boolean | Promise<boolean>;
5
7
  findSessionEntryId: (entries: readonly unknown[]) => string | undefined;
6
8
  send: () => void;
7
9
  settle: (sessionEntryId: string) => Promise<void>;
@@ -26,6 +28,7 @@ const SESSION_ENTRY_CONFIRMATION_MS = 10_000;
26
28
  */
27
29
  export class SessionDeliveryCoordinator {
28
30
  private readonly queued = new Map<string, QueuedSessionDelivery>();
31
+ private claimed: ClaimedSessionDelivery | undefined;
29
32
  private synchronizing = false;
30
33
 
31
34
  async synchronize(
@@ -36,6 +39,7 @@ export class SessionDeliveryCoordinator {
36
39
  this.synchronizing = true;
37
40
  try {
38
41
  if (await this.settleQueued(ctx)) return;
42
+ if (await this.sendClaimed(ctx)) return;
39
43
  if (!ctx.isIdle() || ctx.hasPendingMessages()) return;
40
44
 
41
45
  for (const claim of claimers) {
@@ -44,32 +48,17 @@ export class SessionDeliveryCoordinator {
44
48
 
45
49
  const existingEntryId = delivery.findSessionEntryId(ctx.sessionManager.getBranch());
46
50
  if (existingEntryId !== undefined) {
47
- await delivery.settle(existingEntryId);
51
+ if (delivery.claimExpiresAt <= Date.now()) return;
52
+ this.rememberQueued(delivery);
53
+ await this.settleDelivery(ctx, delivery.deliveryId, existingEntryId);
48
54
  return;
49
55
  }
50
56
 
51
- // The host claim is asynchronous. Pi can start another turn while that
52
- // request is in flight, so check again immediately before the
53
- // synchronous send. An unused claim expires and is never sent later.
54
- if (!ctx.isIdle() || ctx.hasPendingMessages()) return;
55
-
56
- this.queued.set(delivery.deliveryId, {
57
- delivery,
58
- queuedAt: Date.now(),
59
- ambiguityReported: false,
60
- });
61
- try {
62
- delivery.send();
63
- } catch (error) {
64
- this.queued.delete(delivery.deliveryId);
65
- throw error;
66
- }
67
-
68
- const insertedEntryId = delivery.findSessionEntryId(ctx.sessionManager.getBranch());
69
- if (insertedEntryId !== undefined) {
70
- this.queued.delete(delivery.deliveryId);
71
- await delivery.settle(insertedEntryId);
72
- }
57
+ // Remember the host claim before the final idle check. If Pi starts a
58
+ // turn while the claim request is in flight, a later poll can use this
59
+ // exact still-live claim instead of making a conflicting second claim.
60
+ this.claimed = delivery;
61
+ await this.sendClaimed(ctx);
73
62
  return;
74
63
  }
75
64
  } finally {
@@ -78,9 +67,63 @@ export class SessionDeliveryCoordinator {
78
67
  }
79
68
 
80
69
  clear(): void {
70
+ this.claimed = undefined;
81
71
  this.queued.clear();
82
72
  }
83
73
 
74
+ private async sendClaimed(
75
+ ctx: Pick<ExtensionContext, "hasPendingMessages" | "isIdle" | "sessionManager" | "ui">,
76
+ ): Promise<boolean> {
77
+ const delivery = this.claimed;
78
+ if (delivery === undefined) return false;
79
+ if (delivery.claimExpiresAt <= Date.now()) {
80
+ this.claimed = undefined;
81
+ return false;
82
+ }
83
+
84
+ const existingEntryId = delivery.findSessionEntryId(ctx.sessionManager.getBranch());
85
+ if (existingEntryId !== undefined) {
86
+ this.rememberQueued(delivery);
87
+ this.claimed = undefined;
88
+ await this.settleDelivery(ctx, delivery.deliveryId, existingEntryId);
89
+ return true;
90
+ }
91
+ if (!ctx.isIdle() || ctx.hasPendingMessages()) return true;
92
+ if (!(await delivery.isStillDeliverable())) {
93
+ this.claimed = undefined;
94
+ return false;
95
+ }
96
+ if (delivery.claimExpiresAt <= Date.now()) {
97
+ this.claimed = undefined;
98
+ return false;
99
+ }
100
+ if (!ctx.isIdle() || ctx.hasPendingMessages()) return true;
101
+
102
+ this.rememberQueued(delivery);
103
+ this.claimed = undefined;
104
+ try {
105
+ delivery.send();
106
+ } catch (error) {
107
+ this.queued.delete(delivery.deliveryId);
108
+ throw error;
109
+ }
110
+
111
+ const insertedEntryId = delivery.findSessionEntryId(ctx.sessionManager.getBranch());
112
+ if (insertedEntryId !== undefined) {
113
+ await this.settleDelivery(ctx, delivery.deliveryId, insertedEntryId);
114
+ }
115
+ return true;
116
+ }
117
+
118
+ private rememberQueued(delivery: ClaimedSessionDelivery): void {
119
+ if (this.queued.has(delivery.deliveryId)) return;
120
+ this.queued.set(delivery.deliveryId, {
121
+ delivery,
122
+ queuedAt: Date.now(),
123
+ ambiguityReported: false,
124
+ });
125
+ }
126
+
84
127
  private async settleQueued(
85
128
  ctx: Pick<ExtensionContext, "hasPendingMessages" | "isIdle" | "sessionManager" | "ui">,
86
129
  ): Promise<boolean> {
@@ -103,9 +146,29 @@ export class SessionDeliveryCoordinator {
103
146
  }
104
147
  return true;
105
148
  }
106
- this.queued.delete(deliveryId);
107
- await queued.delivery.settle(sessionEntryId);
149
+ await this.settleDelivery(ctx, deliveryId, sessionEntryId);
108
150
  }
109
151
  return hadQueuedDelivery;
110
152
  }
153
+
154
+ private async settleDelivery(
155
+ ctx: Pick<ExtensionContext, "ui">,
156
+ deliveryId: string,
157
+ sessionEntryId: string,
158
+ ): Promise<void> {
159
+ const queued = this.queued.get(deliveryId);
160
+ if (queued === undefined) return;
161
+ try {
162
+ await queued.delivery.settle(sessionEntryId);
163
+ this.queued.delete(deliveryId);
164
+ } catch (error) {
165
+ if (!queued.ambiguityReported) {
166
+ queued.ambiguityReported = true;
167
+ ctx.ui.notify(
168
+ `Workflow session delivery ${deliveryId} is visible but its durable receipt is ambiguous: ${String(error)}. Do not retry it until recovery checks the session history.`,
169
+ "warning",
170
+ );
171
+ }
172
+ }
173
+ }
111
174
  }
@@ -0,0 +1,154 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { WorkflowSessionView } from "../client/view.js";
3
+ import type { WorkflowDefinitionSnapshot, WorkflowRunState } from "../workflows/types.js";
4
+ import { buildWidgetView } from "./widget.js";
5
+
6
+ const WIDGET_KEY = "pi-workflows";
7
+ const WIDGET_SCROLL_STEP = 3;
8
+
9
+ /** Client-backed projection of the host-owned run into the origin Pi session. */
10
+ export class SessionWorkflowView {
11
+ private session: WorkflowSessionView | null = null;
12
+ private scroll: number | null = null;
13
+ private shownScroll = 0;
14
+ private maxScroll = 0;
15
+ private stepCount = 0;
16
+ private visible = false;
17
+ private actionHint: string | undefined;
18
+
19
+ update(session: WorkflowSessionView, ctx: ExtensionContext): void {
20
+ const run = session.run;
21
+ if (
22
+ run === null ||
23
+ !isWorkflowRunState(run.state) ||
24
+ !isWorkflowDefinitionSnapshot(run.workflow)
25
+ ) {
26
+ this.session = session;
27
+ this.clearWidget(ctx);
28
+ return;
29
+ }
30
+ const previousRun = this.session?.run;
31
+ if (previousRun?.runId !== run.runId || this.stepCount !== run.state.steps.length) {
32
+ this.scroll = null;
33
+ this.stepCount = run.state.steps.length;
34
+ }
35
+ this.session = session;
36
+ this.render(ctx);
37
+ }
38
+
39
+ setActionHint(hint: string | undefined, ctx: ExtensionContext): void {
40
+ this.actionHint = hint;
41
+ if (this.session?.run !== null) this.render(ctx);
42
+ }
43
+
44
+ refresh(ctx: ExtensionContext): void {
45
+ if (this.session?.run === null || this.session === null) {
46
+ this.clearWidget(ctx);
47
+ return;
48
+ }
49
+ this.render(ctx);
50
+ }
51
+
52
+ scrollUp(ctx: ExtensionContext): void {
53
+ this.scrollBy(ctx, -WIDGET_SCROLL_STEP);
54
+ }
55
+
56
+ scrollDown(ctx: ExtensionContext): void {
57
+ this.scrollBy(ctx, WIDGET_SCROLL_STEP);
58
+ }
59
+
60
+ clear(ctx: ExtensionContext): void {
61
+ this.session = null;
62
+ this.scroll = null;
63
+ this.shownScroll = 0;
64
+ this.maxScroll = 0;
65
+ this.stepCount = 0;
66
+ this.clearWidget(ctx);
67
+ }
68
+
69
+ private scrollBy(ctx: ExtensionContext, delta: number): void {
70
+ if (this.session?.run === null || this.session === null) return;
71
+ const current = this.scroll ?? this.shownScroll;
72
+ this.scroll = Math.max(0, Math.min(this.maxScroll, current + delta));
73
+ this.render(ctx);
74
+ }
75
+
76
+ private clearWidget(ctx: ExtensionContext): void {
77
+ if (!this.visible) return;
78
+ this.visible = false;
79
+ safelyUpdateUi(ctx, () => {
80
+ ctx.ui.setWidget(WIDGET_KEY, undefined);
81
+ ctx.ui.setStatus(WIDGET_KEY, undefined);
82
+ });
83
+ }
84
+
85
+ private render(ctx: ExtensionContext): void {
86
+ const run = this.session?.run;
87
+ if (run === null || run === undefined) return;
88
+ if (!isWorkflowRunState(run.state) || !isWorkflowDefinitionSnapshot(run.workflow)) return;
89
+ const state = run.state;
90
+ const snapshot = run.workflow;
91
+ const render = (
92
+ width = Number.POSITIVE_INFINITY,
93
+ theme?: Parameters<typeof buildWidgetView>[6],
94
+ ) => {
95
+ const view = buildWidgetView(
96
+ state,
97
+ snapshot,
98
+ new Date(),
99
+ this.scroll,
100
+ run.display.status === "paused",
101
+ width,
102
+ theme,
103
+ undefined,
104
+ this.actionHint,
105
+ run.display.status,
106
+ );
107
+ this.shownScroll = view.scroll;
108
+ this.maxScroll = view.maxScroll;
109
+ if (this.scroll !== null) this.scroll = view.scroll;
110
+ return view.lines;
111
+ };
112
+ safelyUpdateUi(ctx, () => {
113
+ if (ctx.mode === "tui") {
114
+ ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({
115
+ render: (width) => render(width, theme),
116
+ invalidate() {},
117
+ }));
118
+ } else {
119
+ ctx.ui.setWidget(WIDGET_KEY, render());
120
+ }
121
+ ctx.ui.setStatus(WIDGET_KEY, `${state.workflowName} [${run.display.status}]`);
122
+ this.visible = true;
123
+ });
124
+ }
125
+ }
126
+
127
+ function isWorkflowRunState(value: unknown): value is WorkflowRunState {
128
+ return (
129
+ typeof value === "object" &&
130
+ value !== null &&
131
+ !Array.isArray(value) &&
132
+ (value as { schema?: unknown }).schema === "pi-workflows.run-state.v1" &&
133
+ typeof (value as { workflowName?: unknown }).workflowName === "string" &&
134
+ Array.isArray((value as { steps?: unknown }).steps)
135
+ );
136
+ }
137
+
138
+ function isWorkflowDefinitionSnapshot(value: unknown): value is WorkflowDefinitionSnapshot {
139
+ return (
140
+ typeof value === "object" &&
141
+ value !== null &&
142
+ !Array.isArray(value) &&
143
+ (value as { schema?: unknown }).schema === "pi-workflows.definition-snapshot.v1" &&
144
+ typeof (value as { nodes?: unknown }).nodes === "object"
145
+ );
146
+ }
147
+
148
+ function safelyUpdateUi(ctx: ExtensionContext, update: () => void): void {
149
+ try {
150
+ if (ctx.hasUI) update();
151
+ } catch {
152
+ // A session replacement can make a captured context stale between updates.
153
+ }
154
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
2
2
  import { truncateToWidth } from "@earendil-works/pi-tui";
3
+ import type { WorkflowDisplayStatus } from "../client/view.js";
3
4
  import { formatDuration } from "../render/format.js";
4
5
  import { nodeTypeGlyph } from "../render/node-type.js";
5
6
  import {
@@ -14,17 +15,19 @@ import { sanitizeText } from "../workflows/text.js";
14
15
  import type {
15
16
  WorkflowDefinitionSnapshot,
16
17
  WorkflowRunState,
17
- WorkflowRunStatus,
18
18
  WorkflowUpdateRecord,
19
19
  } from "../workflows/types.js";
20
20
 
21
- const STATUS_GLYPHS: Record<WorkflowRunStatus, string> = {
21
+ const STATUS_GLYPHS: Record<WorkflowDisplayStatus, string> = {
22
+ queued: "…",
22
23
  running: "◐",
23
- waiting: "",
24
+ waiting: "",
25
+ paused: "⏸",
24
26
  completed: "✓",
25
27
  failed: "✗",
26
28
  timed_out: "✗",
27
29
  cancelled: "✗",
30
+ ambiguous: "!",
28
31
  };
29
32
 
30
33
  /**
@@ -79,26 +82,28 @@ export function buildWidgetView(
79
82
  theme?: WidgetTheme,
80
83
  updateHistory?: WorkflowUpdateRecord[],
81
84
  actionHint?: string,
85
+ displayStatus?: WorkflowDisplayStatus,
82
86
  ): WidgetView {
83
87
  const availableWidth = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : width;
84
88
  if (availableWidth === 0) return { lines: [], scroll: 0, maxScroll: 0 };
85
89
 
86
90
  // `held` covers pauses the state cannot see yet: an escape-interrupted
87
91
  // step or a pause requested while the current node is still finishing.
88
- const paused = held || state.paused === true;
89
- const glyph = paused ? "" : STATUS_GLYPHS[state.status];
90
- const statusText = paused ? "paused" : state.status;
92
+ const status = displayStatus ?? (held || state.paused === true ? "paused" : state.status);
93
+ const paused = status === "paused";
94
+ const glyph = STATUS_GLYPHS[status];
95
+ const statusText = status;
91
96
  // Titles, status details, and errors can carry model- or shell-controlled
92
97
  // text; never let escape sequences or newlines reach the terminal.
93
98
  const title = state.runTitle ? ` — ${sanitizeText(state.runTitle)}` : "";
94
- const headerTone = statusTone(paused ? "waiting" : state.status);
99
+ const headerTone = statusTone(status);
95
100
  const header = `${paint(theme, headerTone, glyph)} workflow ${sanitizeText(state.workflowName)}${title} ${paint(theme, headerTone, `[${statusText}]`)}`;
96
101
 
97
102
  const footer: string[] = [];
98
103
  if (state.error) {
99
104
  footer.push(paint(theme, "error", ` error: ${truncate(sanitizeText(state.error), 120)}`));
100
105
  }
101
- if (state.status === "waiting" && state.waitingOn) {
106
+ if (status === "waiting" && state.waitingOn) {
102
107
  footer.push(
103
108
  paint(theme, "warning", ` waiting on checkpoint: ${sanitizeText(state.waitingOn)}`),
104
109
  );
@@ -398,7 +403,7 @@ function elapsedSince(startedAt: string | undefined, now: Date): string | null {
398
403
  return formatDuration(Math.max(0, now.getTime() - started));
399
404
  }
400
405
 
401
- function statusTone(status: WorkflowRunStatus): ThemeColor {
406
+ function statusTone(status: string): ThemeColor {
402
407
  switch (status) {
403
408
  case "completed":
404
409
  return "success";
@@ -407,8 +412,12 @@ function statusTone(status: WorkflowRunStatus): ThemeColor {
407
412
  case "cancelled":
408
413
  return "error";
409
414
  case "waiting":
415
+ case "paused":
410
416
  return "warning";
417
+ case "ambiguous":
418
+ return "error";
411
419
  case "running":
420
+ default:
412
421
  return "accent";
413
422
  }
414
423
  }
@@ -1,8 +1,8 @@
1
1
  import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import { once } from "node:events";
3
+ import { NdjsonFrameDecoder } from "../client/protocol.js";
3
4
  import { errorMessage } from "../workflows/errors.js";
4
5
  import { HostProcessRegistry, type ProcessIdentity } from "./processes.js";
5
- import { NdjsonFrameDecoder } from "./protocol.js";
6
6
  import type { WorkerOutcome } from "./state.js";
7
7
 
8
8
  const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
@@ -3,6 +3,12 @@ import { createHash } from "node:crypto";
3
3
  import fs from "node:fs/promises";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { builtinWorkflowCatalog } from "../builtins/catalog.js";
6
+ import { MAX_PROTOCOL_MESSAGE_BYTES } from "../client/protocol.js";
7
+ import type {
8
+ ResolvedControllerInitialization,
9
+ ResolvedSettingsChange,
10
+ ResolvedWorkflowLaunch,
11
+ } from "../client/resolver.js";
6
12
  import { discoverControllers, loadControllerFile } from "../controllers/loader.js";
7
13
  import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
8
14
  import { compositionMetadata } from "../workflows/composition.js";
@@ -12,32 +18,11 @@ import { resolveWorkflowRef } from "../workflows/loader.js";
12
18
  import { applyWorkflowSettingsPatch } from "../workflows/settings.js";
13
19
  import { createDefinitionSnapshot } from "../workflows/store.js";
14
20
  import type { WorkflowDefinition, WorkflowSource } from "../workflows/types.js";
15
- import { MAX_PROTOCOL_MESSAGE_BYTES } from "./protocol.js";
16
-
17
- export type ResolvedWorkflowLaunch = {
18
- schema: "pi-workflows.resolved-launch.v1";
19
- workflowName: string;
20
- workflowSourceRef: string;
21
- workflowSource: JsonValue;
22
- definitionDigest: string;
23
- definitionSnapshot: JsonValue;
24
- };
25
-
26
- export type ResolvedControllerInitialization = {
27
- schema: "pi-workflows.resolved-controller-initialization.v1";
28
- controllerName: string;
29
- controllerPath: string;
30
- sourceHash: string;
31
- initialStatus: JsonValue;
32
- };
33
-
34
- export type ResolvedSettingsChange = {
35
- schema: "pi-workflows.resolved-settings-change.v1";
36
- definitionDigest: string;
37
- patch: JsonValue;
38
- settings: JsonValue;
39
- paths: JsonValue;
40
- };
21
+ export type {
22
+ ResolvedControllerInitialization,
23
+ ResolvedSettingsChange,
24
+ ResolvedWorkflowLaunch,
25
+ } from "../client/resolver.js";
41
26
 
42
27
  type ResolverRequest = {
43
28
  schema: "pi-workflows.resolve-request.v1";