@osolmaz/pi-workflows 0.16.1 → 0.16.3
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/README.md +41 -41
- package/dist/builtins/monitor.workflow.d.ts +1 -1
- package/dist/builtins/monitor.workflow.js +8 -8
- package/dist/builtins/pi-agent-group.js +3 -3
- package/dist/builtins/pi-agent-group.js.map +1 -1
- package/dist/channels/adapter-entry.js +14 -14
- package/dist/channels/adapter-entry.js.map +1 -1
- package/dist/client/client.d.ts +6 -6
- package/dist/client/client.js +27 -27
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +4 -1
- package/dist/client/protocol.js +17 -11
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/resolver.d.ts +4 -4
- package/dist/client/view.d.ts +7 -1
- package/dist/client/view.js +1 -0
- package/dist/client/view.js.map +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +66 -47
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +1 -1
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/remote-recorder-store.d.ts +1 -1
- package/dist/extension/remote-recorder-store.js +2 -2
- package/dist/extension/remote-recorder-store.js.map +1 -1
- package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
- package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
- package/dist/extension/resource-manager-command.js.map +1 -0
- package/dist/extension/session-delivery.d.ts +1 -1
- package/dist/extension/session-delivery.js +2 -2
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +1 -1
- package/dist/extension/session-view.js +1 -1
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +4 -3
- package/dist/extension/workflow-message-coordinator.js +81 -29
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/resource-managers/conditions.d.ts +6 -0
- package/dist/{controllers → resource-managers}/conditions.js +3 -3
- package/dist/resource-managers/conditions.js.map +1 -0
- package/dist/resource-managers/definition.d.ts +6 -0
- package/dist/resource-managers/definition.js +45 -0
- package/dist/resource-managers/definition.js.map +1 -0
- package/dist/resource-managers/effects.d.ts +15 -0
- package/dist/{controllers → resource-managers}/effects.js +2 -2
- package/dist/resource-managers/effects.js.map +1 -0
- package/dist/resource-managers/errors.d.ts +12 -0
- package/dist/resource-managers/errors.js +25 -0
- package/dist/resource-managers/errors.js.map +1 -0
- package/dist/resource-managers/index.d.ts +12 -0
- package/dist/resource-managers/index.js +12 -0
- package/dist/resource-managers/index.js.map +1 -0
- package/dist/resource-managers/json.js.map +1 -0
- package/dist/resource-managers/loader.d.ts +23 -0
- package/dist/resource-managers/loader.js +75 -0
- package/dist/resource-managers/loader.js.map +1 -0
- package/dist/resource-managers/results.d.ts +5 -0
- package/dist/resource-managers/results.js.map +1 -0
- package/dist/resource-managers/runtime.d.ts +59 -0
- package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
- package/dist/resource-managers/runtime.js.map +1 -0
- package/dist/{controllers → resource-managers}/sqlite.d.ts +51 -43
- package/dist/{controllers → resource-managers}/sqlite.js +187 -125
- package/dist/resource-managers/sqlite.js.map +1 -0
- package/dist/{controllers → resource-managers}/store.d.ts +33 -33
- package/dist/{controllers → resource-managers}/store.js.map +1 -1
- package/dist/{controllers → resource-managers}/types.d.ts +41 -41
- package/dist/{controllers → resource-managers}/types.js.map +1 -1
- package/dist/resource-managers/workflows.d.ts +34 -0
- package/dist/{controllers → resource-managers}/workflows.js +15 -15
- package/dist/resource-managers/workflows.js.map +1 -0
- package/dist/server/channel-effects.js.map +1 -0
- package/dist/{host → server}/channel-supervisor.d.ts +6 -6
- package/dist/{host → server}/channel-supervisor.js +3 -3
- package/dist/server/channel-supervisor.js.map +1 -0
- package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
- package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
- package/dist/server/child-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/processes.d.ts +3 -3
- package/dist/{host → server}/processes.js +3 -3
- package/dist/server/processes.js.map +1 -0
- package/dist/{host → server}/resolver-entry.d.ts +6 -6
- package/dist/{host → server}/resolver-entry.js +14 -14
- package/dist/server/resolver-entry.js.map +1 -0
- package/dist/server/resource-runner-entry.d.ts +2 -0
- package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
- package/dist/server/resource-runner-entry.js.map +1 -0
- package/dist/server/resource-runner-protocol.d.ts +36 -0
- package/dist/server/resource-runner-protocol.js +49 -0
- package/dist/server/resource-runner-protocol.js.map +1 -0
- package/dist/server/resource-runner-supervisor.d.ts +21 -0
- package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
- package/dist/server/resource-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/rpc-bridge.d.ts +3 -3
- package/dist/{host → server}/rpc-bridge.js +3 -3
- package/dist/server/rpc-bridge.js.map +1 -0
- package/dist/{host → server}/rpc-executor.d.ts +1 -1
- package/dist/{host → server}/rpc-executor.js +3 -3
- package/dist/server/rpc-executor.js.map +1 -0
- package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
- package/dist/server/server-entry.js.map +1 -0
- package/dist/{host/runner.d.ts → server/server.d.ts} +47 -39
- package/dist/{host/runner.js → server/server.js} +624 -535
- package/dist/server/server.js.map +1 -0
- package/dist/{host → server}/state.d.ts +25 -25
- package/dist/{host → server}/state.js +49 -49
- package/dist/server/state.js.map +1 -0
- package/dist/{host → server}/view.d.ts +7 -7
- package/dist/{host → server}/view.js +19 -15
- package/dist/server/view.js.map +1 -0
- package/dist/server/workflow-runner-content.d.ts +6 -0
- package/dist/server/workflow-runner-content.js +96 -0
- package/dist/server/workflow-runner-content.js.map +1 -0
- package/dist/server/workflow-runner-entry.d.ts +13 -0
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +89 -57
- package/dist/server/workflow-runner-entry.js.map +1 -0
- package/dist/server/workflow-runner-protocol.d.ts +69 -0
- package/dist/server/workflow-runner-protocol.js +176 -0
- package/dist/server/workflow-runner-protocol.js.map +1 -0
- package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
- package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
- package/dist/server/workflow-runner-store.js.map +1 -0
- package/dist/server/workflow-runner-supervisor.d.ts +22 -0
- package/dist/server/workflow-runner-supervisor.js +56 -0
- package/dist/server/workflow-runner-supervisor.js.map +1 -0
- package/dist/state/prune.d.ts +1 -1
- package/dist/state/prune.js +17 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +2 -2
- package/dist/state/workflow-messages.d.ts +3 -1
- package/dist/state/workflow-messages.js +32 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +1 -1
- package/dist/viewer/backup.js.map +1 -1
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +46 -32
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +1 -1
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/command-batch.js +2 -2
- package/dist/workflows/composition.js +0 -4
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.js +0 -8
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.js +18 -20
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +27 -11
- package/dist/workflows/store.js +66 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +3 -5
- package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
- package/docs/2026-08-25-workflow-follow-ups.md +7 -7
- package/docs/2026-08-25-workflow-settings.md +3 -3
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +5 -5
- package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
- package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
- package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
- package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
- package/docs/DEFERRED_TURNS.md +7 -7
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +11 -11
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
- package/docs/MONITOR.md +1 -1
- package/docs/RESOURCE_MANAGERS.md +221 -0
- package/docs/SQLITE_STATE.md +40 -34
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +131 -116
- package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
- package/docs/WORKFLOW_UPDATES.md +16 -16
- package/docs/development.md +15 -14
- package/docs/live-replay-protocol.md +20 -20
- package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
- package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
- package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
- package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
- package/docs/tui-viewer.md +9 -9
- package/docs/workflows.md +78 -63
- package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
- package/herdr-plugin.toml +1 -1
- package/package.json +5 -5
- package/protocol/client.v1.schema.json +7 -7
- package/protocol/fixtures/client-v1.json +1 -1
- package/src/builtins/monitor.workflow.ts +9 -9
- package/src/builtins/pi-agent-group.ts +3 -3
- package/src/channels/adapter-entry.ts +14 -14
- package/src/client/client.ts +33 -32
- package/src/client/protocol.ts +16 -11
- package/src/client/resolver.ts +4 -4
- package/src/client/view.ts +9 -1
- package/src/extension/index.ts +81 -53
- package/src/extension/recorder.ts +1 -1
- package/src/extension/remote-recorder-store.ts +2 -2
- package/src/extension/resource-manager-command.ts +45 -0
- package/src/extension/session-delivery.ts +2 -2
- package/src/extension/session-view.ts +1 -1
- package/src/extension/workflow-message-coordinator.ts +94 -30
- package/src/{controllers → resource-managers}/conditions.ts +23 -23
- package/src/resource-managers/definition.ts +71 -0
- package/src/{controllers → resource-managers}/effects.ts +9 -9
- package/src/resource-managers/errors.ts +27 -0
- package/src/resource-managers/index.ts +88 -0
- package/src/resource-managers/loader.ts +111 -0
- package/src/{controllers → resource-managers}/results.ts +4 -4
- package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
- package/src/{controllers → resource-managers}/sqlite.ts +275 -181
- package/src/{controllers → resource-managers}/store.ts +46 -38
- package/src/{controllers → resource-managers}/types.ts +50 -41
- package/src/{controllers → resource-managers}/workflows.ts +43 -41
- package/src/{host → server}/channel-supervisor.ts +8 -8
- package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
- package/src/{host → server}/processes.ts +3 -3
- package/src/{host → server}/resolver-entry.ts +28 -25
- package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
- package/src/server/resource-runner-protocol.ts +103 -0
- package/src/server/resource-runner-supervisor.ts +76 -0
- package/src/{host → server}/rpc-bridge.ts +3 -3
- package/src/{host → server}/rpc-executor.ts +4 -4
- package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
- package/src/{host/runner.ts → server/server.ts} +786 -672
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +21 -17
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +131 -82
- package/src/server/workflow-runner-protocol.ts +241 -0
- package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
- package/src/server/workflow-runner-supervisor.ts +79 -0
- package/src/state/prune.ts +27 -9
- package/src/state/schema.ts +2 -2
- package/src/state/workflow-messages.ts +52 -1
- package/src/viewer/backup.ts +1 -1
- package/src/viewer/cli.ts +50 -36
- package/src/viewer/tui.ts +1 -1
- package/src/workflows/command-batch.ts +2 -2
- package/src/workflows/composition.ts +0 -5
- package/src/workflows/definition.ts +0 -8
- package/src/workflows/engine.ts +18 -21
- package/src/workflows/schema.ts +0 -6
- package/src/workflows/store.ts +95 -28
- package/src/workflows/types.ts +3 -5
- package/dist/controllers/conditions.d.ts +0 -6
- package/dist/controllers/conditions.js.map +0 -1
- package/dist/controllers/definition.d.ts +0 -6
- package/dist/controllers/definition.js +0 -45
- package/dist/controllers/definition.js.map +0 -1
- package/dist/controllers/effects.d.ts +0 -15
- package/dist/controllers/effects.js.map +0 -1
- package/dist/controllers/errors.d.ts +0 -12
- package/dist/controllers/errors.js +0 -25
- package/dist/controllers/errors.js.map +0 -1
- package/dist/controllers/index.d.ts +0 -12
- package/dist/controllers/index.js +0 -12
- package/dist/controllers/index.js.map +0 -1
- package/dist/controllers/json.js.map +0 -1
- package/dist/controllers/loader.d.ts +0 -23
- package/dist/controllers/loader.js +0 -74
- package/dist/controllers/loader.js.map +0 -1
- package/dist/controllers/manager.d.ts +0 -59
- package/dist/controllers/manager.js.map +0 -1
- package/dist/controllers/results.d.ts +0 -5
- package/dist/controllers/results.js.map +0 -1
- package/dist/controllers/sqlite.js.map +0 -1
- package/dist/controllers/workflows.d.ts +0 -34
- package/dist/controllers/workflows.js.map +0 -1
- package/dist/extension/controller-command.js.map +0 -1
- package/dist/host/channel-effects.js.map +0 -1
- package/dist/host/channel-supervisor.js.map +0 -1
- package/dist/host/child-worker-supervisor.js.map +0 -1
- package/dist/host/controller-worker-entry.d.ts +0 -2
- package/dist/host/controller-worker-entry.js.map +0 -1
- package/dist/host/controller-worker-protocol.d.ts +0 -36
- package/dist/host/controller-worker-protocol.js +0 -49
- package/dist/host/controller-worker-protocol.js.map +0 -1
- package/dist/host/controller-worker-supervisor.d.ts +0 -21
- package/dist/host/controller-worker-supervisor.js +0 -54
- package/dist/host/controller-worker-supervisor.js.map +0 -1
- package/dist/host/host-entry.js.map +0 -1
- package/dist/host/processes.js.map +0 -1
- package/dist/host/resolver-entry.js.map +0 -1
- package/dist/host/rpc-bridge.js.map +0 -1
- package/dist/host/rpc-executor.js.map +0 -1
- package/dist/host/runner.js.map +0 -1
- package/dist/host/state.js.map +0 -1
- package/dist/host/view.js.map +0 -1
- package/dist/host/worker-entry.d.ts +0 -10
- package/dist/host/worker-entry.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -31
- package/dist/host/worker-protocol.js +0 -122
- package/dist/host/worker-protocol.js.map +0 -1
- package/dist/host/worker-store.js.map +0 -1
- package/dist/host/worker-supervisor.d.ts +0 -22
- package/dist/host/worker-supervisor.js.map +0 -1
- package/docs/CONTROLLERS.md +0 -217
- package/src/controllers/definition.ts +0 -65
- package/src/controllers/errors.ts +0 -27
- package/src/controllers/index.ts +0 -88
- package/src/controllers/loader.ts +0 -104
- package/src/extension/controller-command.ts +0 -45
- package/src/host/controller-worker-protocol.ts +0 -104
- package/src/host/controller-worker-supervisor.ts +0 -79
- package/src/host/worker-protocol.ts +0 -165
- package/src/host/worker-supervisor.ts +0 -74
- /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
- /package/dist/{controllers → resource-managers}/json.js +0 -0
- /package/dist/{controllers → resource-managers}/results.js +0 -0
- /package/dist/{controllers → resource-managers}/store.js +0 -0
- /package/dist/{controllers → resource-managers}/types.js +0 -0
- /package/dist/{host → server}/channel-effects.d.ts +0 -0
- /package/dist/{host → server}/channel-effects.js +0 -0
- /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
- /package/src/{controllers → resource-managers}/json.ts +0 -0
- /package/src/{host → server}/channel-effects.ts +0 -0
package/src/extension/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { errorMessage } from "../workflows/errors.js";
|
|
|
17
17
|
import { discoverWorkflows } from "../workflows/loader.js";
|
|
18
18
|
import { createRunId } from "../workflows/store.js";
|
|
19
19
|
import type { AgentStepContract, HumanDecisionResponse } from "../workflows/types.js";
|
|
20
|
-
import { parseControllerArgs, type ParsedControllerArgs } from "./controller-command.js";
|
|
21
20
|
import {
|
|
22
21
|
HerdrWorkflowViewer,
|
|
23
22
|
PIW_SHORTCUT,
|
|
@@ -27,12 +26,19 @@ import {
|
|
|
27
26
|
} from "./herdr-viewer.js";
|
|
28
27
|
import { SessionRecorder } from "./recorder.js";
|
|
29
28
|
import { RemoteSessionRecordingStore } from "./remote-recorder-store.js";
|
|
29
|
+
import {
|
|
30
|
+
parseResourceManagerArgs,
|
|
31
|
+
type ParsedResourceManagerArgs,
|
|
32
|
+
} from "./resource-manager-command.js";
|
|
30
33
|
import { SessionWorkflowView } from "./session-view.js";
|
|
31
34
|
import { recoverAssistantStep, registerWorkflowAgentStepMessageRenderer } from "./step-message.js";
|
|
32
35
|
import { responseEntryId, WorkflowMessageCoordinator } from "./workflow-message-coordinator.js";
|
|
33
36
|
import { parseWorkflowToolInput, WorkflowToolParameters } from "./workflow-tool.js";
|
|
34
37
|
|
|
35
|
-
export {
|
|
38
|
+
export {
|
|
39
|
+
parseResourceManagerArgs,
|
|
40
|
+
type ParsedResourceManagerArgs,
|
|
41
|
+
} from "./resource-manager-command.js";
|
|
36
42
|
|
|
37
43
|
export {
|
|
38
44
|
audienceChannels,
|
|
@@ -176,14 +182,16 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
176
182
|
let sessionGeneration = 0;
|
|
177
183
|
let sessionUnsubscribe: (() => Promise<void>) | null = null;
|
|
178
184
|
let sessionConnectTask: Promise<void> | null = null;
|
|
179
|
-
let
|
|
185
|
+
let serverUnavailableNotified = false;
|
|
180
186
|
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
181
187
|
let presentationTail = Promise.resolve();
|
|
182
188
|
let toolTail = Promise.resolve();
|
|
183
189
|
const workflowMessages = new WorkflowMessageCoordinator();
|
|
184
190
|
const sessionView = new SessionWorkflowView();
|
|
185
191
|
const sessionRecorders = new Map<string, SessionRecorder>();
|
|
192
|
+
let agentRunning = false;
|
|
186
193
|
let activeRecorder: SessionRecorder | null = null;
|
|
194
|
+
let activeRecorderMessageId: string | null = null;
|
|
187
195
|
|
|
188
196
|
const ensureRecorder = async (
|
|
189
197
|
message: WorkflowMessage,
|
|
@@ -203,6 +211,33 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
203
211
|
return recorder;
|
|
204
212
|
};
|
|
205
213
|
|
|
214
|
+
const activateRecorder = async (ctx: ExtensionContext): Promise<void> => {
|
|
215
|
+
if (!agentRunning) return;
|
|
216
|
+
const message = workflowMessages.activeTurnMessage();
|
|
217
|
+
if (message === undefined || activeRecorderMessageId === message.workflowMessageId) return;
|
|
218
|
+
const contract = agentContractForWorkflowMessage(message);
|
|
219
|
+
if (contract === undefined && message.kind !== "terminal" && message.kind !== "followUp") {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const recorder = await ensureRecorder(message, ctx);
|
|
223
|
+
if (
|
|
224
|
+
!agentRunning ||
|
|
225
|
+
workflowMessages.activeTurnMessage()?.workflowMessageId !== message.workflowMessageId
|
|
226
|
+
) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (contract === undefined) {
|
|
230
|
+
recorder.beginWorkflowMessage(
|
|
231
|
+
message.workflowMessageId,
|
|
232
|
+
message.kind as "terminal" | "followUp",
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
recorder.beginAttempt(contract);
|
|
236
|
+
}
|
|
237
|
+
activeRecorder = recorder;
|
|
238
|
+
activeRecorderMessageId = message.workflowMessageId;
|
|
239
|
+
};
|
|
240
|
+
|
|
206
241
|
const presentInOrder = async (ctx: ExtensionContext): Promise<void> => {
|
|
207
242
|
const prior = presentationTail;
|
|
208
243
|
let release: (() => void) | undefined;
|
|
@@ -212,6 +247,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
212
247
|
await prior;
|
|
213
248
|
try {
|
|
214
249
|
await workflowMessages.synchronize(pi, client, ctx);
|
|
250
|
+
await activateRecorder(ctx);
|
|
215
251
|
} finally {
|
|
216
252
|
sessionView.refresh(ctx);
|
|
217
253
|
release?.();
|
|
@@ -430,8 +466,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
430
466
|
},
|
|
431
467
|
});
|
|
432
468
|
|
|
433
|
-
pi.registerCommand("
|
|
434
|
-
description: "Manage hosted
|
|
469
|
+
pi.registerCommand("resource-manager", {
|
|
470
|
+
description: "Manage hosted managed resources: list, get, apply, reconcile, or delete",
|
|
435
471
|
getArgumentCompletions: (prefix: string) => {
|
|
436
472
|
const items = ["list", "get", "apply", "reconcile", "delete"]
|
|
437
473
|
.filter((value) => value.startsWith(prefix))
|
|
@@ -440,7 +476,11 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
440
476
|
},
|
|
441
477
|
handler: async (args, ctx) => {
|
|
442
478
|
try {
|
|
443
|
-
const result = await
|
|
479
|
+
const result = await executeResourceManagerCommand(
|
|
480
|
+
client,
|
|
481
|
+
ctx,
|
|
482
|
+
parseResourceManagerArgs(args),
|
|
483
|
+
);
|
|
444
484
|
ctx.ui.notify(result.message, result.level ?? "info");
|
|
445
485
|
} catch (error) {
|
|
446
486
|
ctx.ui.notify(errorMessage(error), "error");
|
|
@@ -549,7 +589,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
549
589
|
|
|
550
590
|
pi.on("session_start", (_event, ctx) => {
|
|
551
591
|
sessionContext = ctx;
|
|
552
|
-
|
|
592
|
+
serverUnavailableNotified = false;
|
|
553
593
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
554
594
|
const generation = ++sessionGeneration;
|
|
555
595
|
let snapshotGeneration = 0;
|
|
@@ -615,7 +655,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
615
655
|
return;
|
|
616
656
|
}
|
|
617
657
|
sessionUnsubscribe = unsubscribe;
|
|
618
|
-
|
|
658
|
+
serverUnavailableNotified = false;
|
|
619
659
|
const capability = await herdrViewer.probe();
|
|
620
660
|
if (generation !== sessionGeneration || sessionContext !== ctx) return;
|
|
621
661
|
sessionView.setActionHint(capability.available ? PIW_SHORTCUT_HINT : undefined, ctx);
|
|
@@ -624,10 +664,10 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
624
664
|
if (
|
|
625
665
|
generation === sessionGeneration &&
|
|
626
666
|
sessionContext === ctx &&
|
|
627
|
-
!
|
|
667
|
+
!serverUnavailableNotified
|
|
628
668
|
) {
|
|
629
|
-
|
|
630
|
-
ctx.ui.notify(`Workflow
|
|
669
|
+
serverUnavailableNotified = true;
|
|
670
|
+
ctx.ui.notify(`Workflow server is unavailable: ${errorMessage(error)}`, "warning");
|
|
631
671
|
}
|
|
632
672
|
}
|
|
633
673
|
})();
|
|
@@ -651,27 +691,11 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
651
691
|
});
|
|
652
692
|
|
|
653
693
|
pi.on("agent_start", async (_event, ctx) => {
|
|
694
|
+
agentRunning = true;
|
|
695
|
+
activeRecorder = null;
|
|
696
|
+
activeRecorderMessageId = null;
|
|
654
697
|
workflowMessages.startTurn();
|
|
655
698
|
await presentInOrder(ctx).catch(() => undefined);
|
|
656
|
-
const message = workflowMessages.activeTurnMessage();
|
|
657
|
-
const contract = message === undefined ? undefined : agentContractForWorkflowMessage(message);
|
|
658
|
-
if (
|
|
659
|
-
message === undefined ||
|
|
660
|
-
(contract === undefined && message.kind !== "terminal" && message.kind !== "followUp")
|
|
661
|
-
) {
|
|
662
|
-
activeRecorder = null;
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const recorder = await ensureRecorder(message, ctx);
|
|
666
|
-
if (contract === undefined) {
|
|
667
|
-
recorder.beginWorkflowMessage(
|
|
668
|
-
message.workflowMessageId,
|
|
669
|
-
message.kind as "terminal" | "followUp",
|
|
670
|
-
);
|
|
671
|
-
} else {
|
|
672
|
-
recorder.beginAttempt(contract);
|
|
673
|
-
}
|
|
674
|
-
activeRecorder = recorder;
|
|
675
699
|
});
|
|
676
700
|
|
|
677
701
|
pi.on("agent_end", async (event, ctx) => {
|
|
@@ -681,6 +705,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
681
705
|
ctx.ui.notify(`Workflow response was rejected: ${errorMessage(error)}`, "error");
|
|
682
706
|
}
|
|
683
707
|
const finishedMessage = workflowMessages.activeTurnMessage();
|
|
708
|
+
agentRunning = false;
|
|
709
|
+
activeRecorderMessageId = null;
|
|
684
710
|
workflowMessages.endTurn(
|
|
685
711
|
workflowTurnStopReason(event.messages, ctx.signal?.aborted === true),
|
|
686
712
|
responseEntryId(ctx.sessionManager.getBranch()),
|
|
@@ -747,7 +773,9 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
747
773
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
748
774
|
sessionGeneration += 1;
|
|
749
775
|
sessionContext = null;
|
|
776
|
+
agentRunning = false;
|
|
750
777
|
activeRecorder = null;
|
|
778
|
+
activeRecorderMessageId = null;
|
|
751
779
|
await Promise.allSettled(
|
|
752
780
|
[...sessionRecorders.values()].map(async (recorder) => recorder.stop()),
|
|
753
781
|
);
|
|
@@ -760,7 +788,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
|
|
|
760
788
|
if (sessionUnsubscribe !== null) await sessionUnsubscribe().catch(() => undefined);
|
|
761
789
|
sessionUnsubscribe = null;
|
|
762
790
|
sessionConnectTask = null;
|
|
763
|
-
|
|
791
|
+
serverUnavailableNotified = false;
|
|
764
792
|
await client.close();
|
|
765
793
|
client = new WorkflowClient({ clientId: `pi-extension-${randomUUID()}` });
|
|
766
794
|
});
|
|
@@ -1065,57 +1093,57 @@ async function executeCommand(
|
|
|
1065
1093
|
}
|
|
1066
1094
|
}
|
|
1067
1095
|
|
|
1068
|
-
async function
|
|
1096
|
+
async function executeResourceManagerCommand(
|
|
1069
1097
|
client: WorkflowClient,
|
|
1070
1098
|
ctx: ExtensionContext,
|
|
1071
|
-
command:
|
|
1099
|
+
command: ParsedResourceManagerArgs,
|
|
1072
1100
|
): Promise<CommandResult> {
|
|
1073
1101
|
const projectPath = path.resolve(ctx.cwd);
|
|
1074
1102
|
if (command.kind === "list") {
|
|
1075
1103
|
const response = await requestAccepted(client, {
|
|
1076
|
-
operation: "
|
|
1104
|
+
operation: "resourceManager.list",
|
|
1077
1105
|
payload: { projectPath },
|
|
1078
1106
|
});
|
|
1079
1107
|
return {
|
|
1080
|
-
message:
|
|
1108
|
+
message: summarizeManagedResources(response.receipt),
|
|
1081
1109
|
details: { action: "list", resources: response.receipt ?? [] },
|
|
1082
1110
|
};
|
|
1083
1111
|
}
|
|
1084
1112
|
|
|
1085
1113
|
const idempotencyKey = randomUUID();
|
|
1086
1114
|
if (command.kind === "apply") {
|
|
1087
|
-
const resolved = await client.
|
|
1115
|
+
const resolved = await client.resolveResourceManagerInitialization({
|
|
1088
1116
|
cwd: projectPath,
|
|
1089
|
-
|
|
1117
|
+
resourceManagerName: command.resourceManager,
|
|
1090
1118
|
spec: command.spec,
|
|
1091
1119
|
});
|
|
1092
1120
|
const response = await requestAccepted(client, {
|
|
1093
|
-
operation: "
|
|
1094
|
-
requestId: `
|
|
1121
|
+
operation: "resourceManager.apply",
|
|
1122
|
+
requestId: `resource-manager-apply-${idempotencyKey}`,
|
|
1095
1123
|
idempotencyKey,
|
|
1096
1124
|
payload: {
|
|
1097
1125
|
projectPath,
|
|
1098
|
-
|
|
1126
|
+
resourceManager: resolved.resourceManagerName,
|
|
1099
1127
|
key: command.key,
|
|
1100
1128
|
spec: command.spec,
|
|
1101
1129
|
initialStatus: resolved.initialStatus,
|
|
1102
|
-
|
|
1130
|
+
resourceManagerPath: resolved.resourceManagerPath,
|
|
1103
1131
|
sourceHash: resolved.sourceHash,
|
|
1104
1132
|
},
|
|
1105
1133
|
});
|
|
1106
1134
|
return {
|
|
1107
|
-
message: `Applied
|
|
1135
|
+
message: `Applied managed resource ${command.resourceManager}/${command.key}.`,
|
|
1108
1136
|
details: { action: "apply", resource: response.receipt ?? null },
|
|
1109
1137
|
};
|
|
1110
1138
|
}
|
|
1111
1139
|
|
|
1112
1140
|
const response = await requestAccepted(client, {
|
|
1113
|
-
operation: `
|
|
1114
|
-
requestId: `
|
|
1141
|
+
operation: `resourceManager.${command.kind}`,
|
|
1142
|
+
requestId: `resource-manager-${command.kind}-${idempotencyKey}`,
|
|
1115
1143
|
idempotencyKey,
|
|
1116
1144
|
payload: {
|
|
1117
1145
|
projectPath,
|
|
1118
|
-
|
|
1146
|
+
resourceManager: command.resourceManager,
|
|
1119
1147
|
key: command.key,
|
|
1120
1148
|
},
|
|
1121
1149
|
});
|
|
@@ -1126,7 +1154,7 @@ async function executeControllerCommand(
|
|
|
1126
1154
|
};
|
|
1127
1155
|
}
|
|
1128
1156
|
return {
|
|
1129
|
-
message: `
|
|
1157
|
+
message: `Managed resource ${command.resourceManager}/${command.key} ${command.kind} request accepted.`,
|
|
1130
1158
|
details: { action: command.kind, resource: response.receipt ?? null },
|
|
1131
1159
|
};
|
|
1132
1160
|
}
|
|
@@ -1315,7 +1343,7 @@ async function requestAccepted(
|
|
|
1315
1343
|
? await client.request(options)
|
|
1316
1344
|
: await client.requestDurable({ ...options, idempotencyKey: options.idempotencyKey });
|
|
1317
1345
|
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
1318
|
-
throw new Error(response.error ?? `Workflow
|
|
1346
|
+
throw new Error(response.error ?? `Workflow server rejected ${options.operation}`);
|
|
1319
1347
|
}
|
|
1320
1348
|
return response;
|
|
1321
1349
|
}
|
|
@@ -1400,18 +1428,18 @@ function summarizeRun(value: JsonValue | undefined): string {
|
|
|
1400
1428
|
.join(" ");
|
|
1401
1429
|
}
|
|
1402
1430
|
|
|
1403
|
-
function
|
|
1404
|
-
if (!Array.isArray(value) || value.length === 0) return "No
|
|
1431
|
+
function summarizeManagedResources(value: JsonValue | undefined): string {
|
|
1432
|
+
if (!Array.isArray(value) || value.length === 0) return "No managed resources.";
|
|
1405
1433
|
const resources = value.map((item) => {
|
|
1406
1434
|
if (!isRecord(item) || !isRecord(item.metadata)) return "unknown";
|
|
1407
|
-
const
|
|
1408
|
-
typeof item.metadata.
|
|
1435
|
+
const resourceManager =
|
|
1436
|
+
typeof item.metadata.resourceManager === "string" ? item.metadata.resourceManager : "unknown";
|
|
1409
1437
|
const key = typeof item.metadata.key === "string" ? item.metadata.key : "unknown";
|
|
1410
1438
|
const generation =
|
|
1411
1439
|
typeof item.metadata.generation === "number" ? item.metadata.generation : "unknown";
|
|
1412
|
-
return `${
|
|
1440
|
+
return `${resourceManager}/${key} generation=${generation}`;
|
|
1413
1441
|
});
|
|
1414
|
-
return `
|
|
1442
|
+
return `Managed resources: ${resources.join(", ")}.`;
|
|
1415
1443
|
}
|
|
1416
1444
|
|
|
1417
1445
|
function jsonValue(value: unknown): JsonValue {
|
|
@@ -104,7 +104,7 @@ export type SessionRecordingStore = {
|
|
|
104
104
|
): Promise<{ eventCount: number; entryCount: number; lastEventSeq: number }>;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
/** Records settled Pi entries and normalized temporal events through the workflow
|
|
107
|
+
/** Records settled Pi entries and normalized temporal events through the workflow server. */
|
|
108
108
|
export class SessionRecorder {
|
|
109
109
|
private readonly store: SessionRecordingStore;
|
|
110
110
|
private readonly runId: string;
|
|
@@ -13,7 +13,7 @@ type SessionCommandContext = {
|
|
|
13
13
|
coordinatorEpoch: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
/** Sends bounded session recording batches to the single workflow
|
|
16
|
+
/** Sends bounded session recording batches to the single workflow server writer. */
|
|
17
17
|
export class RemoteSessionRecordingStore implements SessionRecordingStore {
|
|
18
18
|
constructor(
|
|
19
19
|
private readonly client: WorkflowClient,
|
|
@@ -103,7 +103,7 @@ export class RemoteSessionRecordingStore implements SessionRecordingStore {
|
|
|
103
103
|
payload: { ...this.context(), action, ...extra },
|
|
104
104
|
});
|
|
105
105
|
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
106
|
-
throw new Error(response.error ?? "Workflow
|
|
106
|
+
throw new Error(response.error ?? "Workflow server rejected the session recording batch");
|
|
107
107
|
}
|
|
108
108
|
if (!isRecord(response.receipt)) throw new Error("Session recording receipt is invalid");
|
|
109
109
|
return response.receipt;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { JsonValue } from "../state/json.js";
|
|
2
|
+
|
|
3
|
+
export type ParsedResourceManagerArgs =
|
|
4
|
+
| { kind: "list" }
|
|
5
|
+
| { kind: "get"; resourceManager: string; key: string }
|
|
6
|
+
| { kind: "apply"; resourceManager: string; key: string; spec: JsonValue }
|
|
7
|
+
| { kind: "reconcile"; resourceManager: string; key: string }
|
|
8
|
+
| { kind: "delete"; resourceManager: string; key: string };
|
|
9
|
+
|
|
10
|
+
const RESOURCE_MANAGER_USAGE =
|
|
11
|
+
"Usage: /resource-manager [list|get <resource-manager> <key>|apply <resource-manager> <key> <json>|reconcile <resource-manager> <key>|delete <resource-manager> <key>]";
|
|
12
|
+
|
|
13
|
+
export function parseResourceManagerArgs(args: string): ParsedResourceManagerArgs {
|
|
14
|
+
const trimmed = args.trim();
|
|
15
|
+
if (trimmed.length === 0 || trimmed === "list") return { kind: "list" };
|
|
16
|
+
|
|
17
|
+
const apply = trimmed.match(/^apply\s+(\S+)\s+(\S+)\s+([\s\S]+)$/u);
|
|
18
|
+
if (apply !== null) {
|
|
19
|
+
let spec: JsonValue;
|
|
20
|
+
try {
|
|
21
|
+
spec = JSON.parse(apply[3] as string) as JsonValue;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Invalid resource manager spec JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
kind: "apply",
|
|
29
|
+
resourceManager: apply[1] as string,
|
|
30
|
+
key: apply[2] as string,
|
|
31
|
+
spec,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const operation = trimmed.match(/^(get|reconcile|delete)\s+(\S+)\s+(\S+)$/u);
|
|
36
|
+
if (operation !== null) {
|
|
37
|
+
return {
|
|
38
|
+
kind: operation[1] as "get" | "reconcile" | "delete",
|
|
39
|
+
resourceManager: operation[2] as string,
|
|
40
|
+
key: operation[3] as string,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error(RESOURCE_MANAGER_USAGE);
|
|
45
|
+
}
|
|
@@ -20,7 +20,7 @@ type QueuedSessionDelivery = {
|
|
|
20
20
|
const SESSION_ENTRY_CONFIRMATION_MS = 10_000;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Delivers at most one
|
|
23
|
+
* Delivers at most one server-owned message into a Pi session at a time.
|
|
24
24
|
*
|
|
25
25
|
* Pi's public sendMessage API does not return the saved session entry ID. Keep
|
|
26
26
|
* the claimed delivery in memory until that entry becomes observable. Polling
|
|
@@ -54,7 +54,7 @@ export class SessionDeliveryCoordinator {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Remember the
|
|
57
|
+
// Remember the server claim before the final idle check. If Pi starts a
|
|
58
58
|
// turn while the claim request is in flight, a later poll can use this
|
|
59
59
|
// exact still-live claim instead of making a conflicting second claim.
|
|
60
60
|
this.claimed = delivery;
|
|
@@ -10,7 +10,7 @@ import { buildWidgetView } from "./widget.js";
|
|
|
10
10
|
const WIDGET_KEY = "pi-workflows";
|
|
11
11
|
const WIDGET_SCROLL_STEP = 3;
|
|
12
12
|
|
|
13
|
-
/** Client-backed projection of the
|
|
13
|
+
/** Client-backed projection of the server-owned run into the origin Pi session. */
|
|
14
14
|
export class SessionWorkflowView {
|
|
15
15
|
private session: WorkflowSessionView | null = null;
|
|
16
16
|
private scroll: number | null = null;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { WorkflowClient } from "../client/client.js";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
WORKFLOW_TURN_REPORT_RECEIPT_SCHEMA,
|
|
6
|
+
type WorkflowSessionView,
|
|
7
|
+
type WorkflowTurnReport,
|
|
8
|
+
type WorkflowTurnReportReceipt,
|
|
9
|
+
} from "../client/view.js";
|
|
5
10
|
import type { JsonValue } from "../state/json.js";
|
|
6
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
WORKFLOW_TURN_SCHEMA,
|
|
13
|
+
type WorkflowMessage,
|
|
14
|
+
type WorkflowTurnStopReason,
|
|
15
|
+
} from "../state/workflow-messages.js";
|
|
7
16
|
|
|
8
17
|
const WORKFLOW_MESSAGE_ID_FIELD = "workflowMessageId";
|
|
9
18
|
|
|
@@ -11,17 +20,19 @@ type PendingTurn = {
|
|
|
11
20
|
workflowTurnId: string;
|
|
12
21
|
workflowMessageId: string | null;
|
|
13
22
|
runId: string | null;
|
|
23
|
+
message: WorkflowMessage | null;
|
|
14
24
|
startedReported: boolean;
|
|
15
25
|
end: { stopReason: WorkflowTurnStopReason; responseSessionEntryId: string | null } | null;
|
|
16
26
|
};
|
|
17
27
|
|
|
18
|
-
/** Adds every
|
|
28
|
+
/** Adds every server-owned workflow message to one Pi session through one public API path. */
|
|
19
29
|
export class WorkflowMessageCoordinator {
|
|
20
30
|
private readonly queued = new Set<string>();
|
|
21
31
|
private readonly closedTurnMessages = new Set<string>();
|
|
22
32
|
private synchronizing = false;
|
|
23
33
|
private lastBranchEpoch: string | null = null;
|
|
24
34
|
private view: WorkflowSessionView | null = null;
|
|
35
|
+
private awaitingTurnMessage: WorkflowMessage | null = null;
|
|
25
36
|
private turn: PendingTurn | null = null;
|
|
26
37
|
|
|
27
38
|
updateView(view: WorkflowSessionView): void {
|
|
@@ -50,15 +61,17 @@ export class WorkflowMessageCoordinator {
|
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
startTurn(): void {
|
|
53
|
-
|
|
64
|
+
const awaited = this.awaitingTurnMessage;
|
|
65
|
+
this.awaitingTurnMessage = null;
|
|
54
66
|
this.turn = {
|
|
55
67
|
workflowTurnId: `workflow-turn-${randomUUID()}`,
|
|
56
68
|
workflowMessageId: null,
|
|
57
69
|
runId: null,
|
|
70
|
+
message: null,
|
|
58
71
|
startedReported: false,
|
|
59
72
|
end: null,
|
|
60
73
|
};
|
|
61
|
-
const candidate = this.turnCandidate();
|
|
74
|
+
const candidate = awaited ?? this.turnCandidate();
|
|
62
75
|
if (candidate !== undefined) this.bindTurn(candidate);
|
|
63
76
|
}
|
|
64
77
|
|
|
@@ -68,10 +81,8 @@ export class WorkflowMessageCoordinator {
|
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
activeTurnMessage(): WorkflowMessage | undefined {
|
|
71
|
-
if (this.
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
return messageById(this.view, this.turn.workflowMessageId);
|
|
84
|
+
if (this.turn === null || !this.turn.startedReported) return undefined;
|
|
85
|
+
return this.turn.message ?? undefined;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
async synchronize(
|
|
@@ -89,7 +100,8 @@ export class WorkflowMessageCoordinator {
|
|
|
89
100
|
workflowTurnId: view.openWorkflowTurn.workflowTurnId,
|
|
90
101
|
workflowMessageId: view.openWorkflowTurn.workflowMessageId,
|
|
91
102
|
runId: view.openWorkflowTurn.runId,
|
|
92
|
-
|
|
103
|
+
message: messageById(view, view.openWorkflowTurn.workflowMessageId) ?? null,
|
|
104
|
+
startedReported: true,
|
|
93
105
|
end: null,
|
|
94
106
|
};
|
|
95
107
|
}
|
|
@@ -128,6 +140,9 @@ export class WorkflowMessageCoordinator {
|
|
|
128
140
|
this.queued.delete(messageId);
|
|
129
141
|
return;
|
|
130
142
|
}
|
|
143
|
+
if (message.content.triggerTurn && messageStartsTurn(message)) {
|
|
144
|
+
this.awaitingTurnMessage = message;
|
|
145
|
+
}
|
|
131
146
|
try {
|
|
132
147
|
pi.sendMessage(
|
|
133
148
|
{
|
|
@@ -140,6 +155,9 @@ export class WorkflowMessageCoordinator {
|
|
|
140
155
|
);
|
|
141
156
|
} catch (error) {
|
|
142
157
|
this.queued.delete(messageId);
|
|
158
|
+
if (this.awaitingTurnMessage?.workflowMessageId === messageId) {
|
|
159
|
+
this.awaitingTurnMessage = null;
|
|
160
|
+
}
|
|
143
161
|
throw error;
|
|
144
162
|
}
|
|
145
163
|
await this.reportBranch(client, ctx, view);
|
|
@@ -153,6 +171,7 @@ export class WorkflowMessageCoordinator {
|
|
|
153
171
|
this.queued.clear();
|
|
154
172
|
this.closedTurnMessages.clear();
|
|
155
173
|
this.view = null;
|
|
174
|
+
this.awaitingTurnMessage = null;
|
|
156
175
|
this.turn = null;
|
|
157
176
|
this.lastBranchEpoch = null;
|
|
158
177
|
this.synchronizing = false;
|
|
@@ -182,6 +201,7 @@ export class WorkflowMessageCoordinator {
|
|
|
182
201
|
if (this.turn === null) return;
|
|
183
202
|
this.turn.workflowMessageId = message.workflowMessageId;
|
|
184
203
|
this.turn.runId = message.runId;
|
|
204
|
+
this.turn.message = message;
|
|
185
205
|
}
|
|
186
206
|
|
|
187
207
|
private async flushTurn(client: WorkflowClient, view: WorkflowSessionView): Promise<void> {
|
|
@@ -195,31 +215,47 @@ export class WorkflowMessageCoordinator {
|
|
|
195
215
|
) {
|
|
196
216
|
return;
|
|
197
217
|
}
|
|
198
|
-
|
|
199
|
-
if (message?.status !== "sent") return;
|
|
218
|
+
let message = pending.message;
|
|
200
219
|
if (!pending.startedReported) {
|
|
220
|
+
message = messageById(view, pending.workflowMessageId) ?? null;
|
|
221
|
+
if (message?.status !== "sent") return;
|
|
222
|
+
try {
|
|
223
|
+
const receipt = await reportTurn(client, {
|
|
224
|
+
state: "started",
|
|
225
|
+
workflowMessageId: pending.workflowMessageId,
|
|
226
|
+
workflowTurnId: pending.workflowTurnId,
|
|
227
|
+
runId: pending.runId,
|
|
228
|
+
targetSessionId: view.sessionId,
|
|
229
|
+
coordinatorEpoch: view.coordinatorEpoch,
|
|
230
|
+
});
|
|
231
|
+
if (receipt.ownership !== "active") {
|
|
232
|
+
this.turn = null;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
pending.message = message;
|
|
236
|
+
pending.startedReported = true;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.turn = null;
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (pending.end === null) return;
|
|
243
|
+
try {
|
|
201
244
|
await reportTurn(client, {
|
|
202
|
-
state: "
|
|
245
|
+
state: "ended",
|
|
203
246
|
workflowMessageId: pending.workflowMessageId,
|
|
204
247
|
workflowTurnId: pending.workflowTurnId,
|
|
205
248
|
runId: pending.runId,
|
|
206
249
|
targetSessionId: view.sessionId,
|
|
207
250
|
coordinatorEpoch: view.coordinatorEpoch,
|
|
251
|
+
stopReason: pending.end.stopReason,
|
|
252
|
+
responseSessionEntryId: pending.end.responseSessionEntryId,
|
|
208
253
|
});
|
|
209
|
-
|
|
254
|
+
} catch (error) {
|
|
255
|
+
this.turn = null;
|
|
256
|
+
throw error;
|
|
210
257
|
}
|
|
211
|
-
if (
|
|
212
|
-
await reportTurn(client, {
|
|
213
|
-
state: "ended",
|
|
214
|
-
workflowMessageId: pending.workflowMessageId,
|
|
215
|
-
workflowTurnId: pending.workflowTurnId,
|
|
216
|
-
runId: pending.runId,
|
|
217
|
-
targetSessionId: view.sessionId,
|
|
218
|
-
coordinatorEpoch: view.coordinatorEpoch,
|
|
219
|
-
stopReason: pending.end.stopReason,
|
|
220
|
-
responseSessionEntryId: pending.end.responseSessionEntryId,
|
|
221
|
-
});
|
|
222
|
-
if (message.kind === "terminal" || message.kind === "followUp") {
|
|
258
|
+
if (message?.kind === "terminal" || message?.kind === "followUp") {
|
|
223
259
|
this.closedTurnMessages.add(pending.workflowMessageId);
|
|
224
260
|
}
|
|
225
261
|
for (const current of new Set([view, this.view])) {
|
|
@@ -228,7 +264,7 @@ export class WorkflowMessageCoordinator {
|
|
|
228
264
|
}
|
|
229
265
|
if (
|
|
230
266
|
current?.openWorkflowMessageId === pending.workflowMessageId &&
|
|
231
|
-
(message
|
|
267
|
+
(message?.kind === "terminal" || message?.kind === "followUp")
|
|
232
268
|
) {
|
|
233
269
|
current.openWorkflowMessageId = null;
|
|
234
270
|
}
|
|
@@ -260,7 +296,7 @@ export class WorkflowMessageCoordinator {
|
|
|
260
296
|
},
|
|
261
297
|
});
|
|
262
298
|
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
263
|
-
throw new Error(response.error ?? "Workflow
|
|
299
|
+
throw new Error(response.error ?? "Workflow server rejected the Pi branch report");
|
|
264
300
|
}
|
|
265
301
|
for (const entry of entries) {
|
|
266
302
|
const message = messageById(view, entry.workflowMessageId);
|
|
@@ -318,15 +354,43 @@ function messageStartsTurn(message: WorkflowMessage): boolean {
|
|
|
318
354
|
return message.kind === "step" || message.kind === "terminal" || message.kind === "followUp";
|
|
319
355
|
}
|
|
320
356
|
|
|
321
|
-
async function reportTurn(
|
|
357
|
+
async function reportTurn(
|
|
358
|
+
client: WorkflowClient,
|
|
359
|
+
report: WorkflowTurnReport,
|
|
360
|
+
): Promise<WorkflowTurnReportReceipt> {
|
|
322
361
|
const response = await client.request({
|
|
323
362
|
operation: "workflowTurn.report",
|
|
324
363
|
runId: report.runId,
|
|
325
364
|
payload: report as unknown as JsonValue,
|
|
326
365
|
});
|
|
327
366
|
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
328
|
-
throw new Error(response.error ?? "Workflow
|
|
367
|
+
throw new Error(response.error ?? "Workflow server rejected the model-turn report");
|
|
368
|
+
}
|
|
369
|
+
const receipt = response.receipt;
|
|
370
|
+
if (
|
|
371
|
+
!isRecord(receipt) ||
|
|
372
|
+
receipt.schema !== WORKFLOW_TURN_REPORT_RECEIPT_SCHEMA ||
|
|
373
|
+
!["active", "settled", "absent"].includes(String(receipt.ownership))
|
|
374
|
+
) {
|
|
375
|
+
throw new Error("Workflow server returned an invalid model-turn receipt");
|
|
376
|
+
}
|
|
377
|
+
const ownership = receipt.ownership as WorkflowTurnReportReceipt["ownership"];
|
|
378
|
+
if (ownership === "absent") {
|
|
379
|
+
if (receipt.turn !== null) {
|
|
380
|
+
throw new Error("Workflow server returned an invalid model-turn receipt");
|
|
381
|
+
}
|
|
382
|
+
} else if (
|
|
383
|
+
!isRecord(receipt.turn) ||
|
|
384
|
+
receipt.turn.schema !== WORKFLOW_TURN_SCHEMA ||
|
|
385
|
+
receipt.turn.workflowTurnId !== report.workflowTurnId ||
|
|
386
|
+
receipt.turn.workflowMessageId !== report.workflowMessageId ||
|
|
387
|
+
receipt.turn.runId !== report.runId ||
|
|
388
|
+
receipt.turn.targetSessionId !== report.targetSessionId ||
|
|
389
|
+
receipt.turn.state !== (ownership === "active" ? "started" : "ended")
|
|
390
|
+
) {
|
|
391
|
+
throw new Error("Workflow server returned an invalid model-turn receipt");
|
|
329
392
|
}
|
|
393
|
+
return receipt as unknown as WorkflowTurnReportReceipt;
|
|
330
394
|
}
|
|
331
395
|
|
|
332
396
|
function isRecord(value: unknown): value is Record<string, unknown> {
|