@agimon-ai/doompi-workflow 0.0.1-alpha.103 → 0.0.1-alpha.104

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 CHANGED
@@ -97,10 +97,31 @@ adopts the eligible failure, validates the evidence, and transfers ownership to
97
97
  Live controls remain scoped to the owning session. Recovery does not launch a second copy beside a
98
98
  still-running job and does not guarantee that parent-session work survives shutdown.
99
99
 
100
- The package publishes `workflow-recovery` for active Workflow mode. While parent Help mode is
101
- active, it also contributes `doompi-author-workflow` for writing workflow definitions and
102
- `doompi-use-workflow` for launching, monitoring, and recovering runs. Deactivating Help hides both
103
- descriptors; cached files may remain.
100
+ The package publishes `workflow-recovery`, `doompi-author-workflow` for writing workflow
101
+ definitions, and `doompi-use-workflow` for launching, monitoring, and recovering runs while Workflow
102
+ mode is active. Deactivating Workflow mode hides them; cached files may remain.
103
+
104
+ ## Workflow sessions
105
+
106
+ In a server (web cockpit) session, a launch does not run in the session that asked for it. The
107
+ session creates a child **workflow session** (provenance `workflow-session`), nested under it in the
108
+ rail, and hands it the launch. The workflow session owns the run (`PI_SESSION_ID`), runs its step
109
+ sessions, and has Workflow mode on, so its agent can troubleshoot and recover the run. The run's
110
+ environment also carries `DOOMPI_WORKFLOW_LAUNCHER_SESSION_ID`, naming the launching session, which
111
+ lists the run in its Activity group, is told when it ends, and may read its status.
112
+
113
+ - The workflow session's rail row shows the run (`workflow · build › test · 4m`, or a failure or
114
+ pause marked like a pending question) through the host's per-session activity line.
115
+ - A failure or finish is posted into the workflow session's conversation as a notice, without
116
+ starting an agent turn. The owner agent also gets a live brief of its runs on every turn.
117
+ - After a success, a workflow session nobody interacted with asks its launcher to release it, and it
118
+ becomes `stopped · open to wake`. A session whose run failed or paused, or whose agent you
119
+ prompted, stays live until you remove it. Removing or restarting a live workflow session stops its
120
+ run.
121
+ - Recovery is refused from any session other than the owner while the owner is live.
122
+
123
+ Launches from inside a workflow session, and launches in hosts without a session service (the TUI
124
+ and CLI), run in place as before.
104
125
 
105
126
  Each Pi extension instance uses the runner's shared Doom lifecycle. Reload or shutdown stops retained
106
127
  callbacks, interrupts bounded inline work, removes UI and service registrations, and releases
@@ -126,10 +147,12 @@ Maintained by [Agimon](https://agimon.ai/about).
126
147
  ## Web cockpit plugin
127
148
 
128
149
  The `.web` routed files under `src/extensions/workspaces/sessions/(frontend)` are this package's
129
- DoomPi web cockpit plugin: the workflows panel the dock's workflows group opens in a temporary tab,
130
- its store, and its `workflow_runs` session channel, compiled into the cockpit bundle by
131
- `doompi-web`'s build. Each surface keeps its components in a colocated `_components` folder and its
132
- stores and helpers in `_lib`.
150
+ DoomPi web cockpit plugin: the `workflow` dock face beside Activity and Context (the owned run's
151
+ jobs, active steps and artifacts, each opening its own tab), the workflow catalog and launch
152
+ dialog in the overlay slot, the Activity group's run rows (a handed-off run opens its workflow
153
+ session), their stores, and the `workflow_runs` session channel, compiled into the cockpit bundle
154
+ by `doompi-web`'s build. Each surface keeps its components in a colocated `_components` folder and
155
+ its stores and helpers in `_lib`.
133
156
  The hub-side data source ships behind the `./web-hub` subpath and reads the workflow registry
134
157
  (run.json plus progress.ndjson) exactly as the engine writes it. Both halves are declared by the
135
158
  `doompiWeb` block in package.json.
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_workflowTerminal = require("../workflowTerminal-Dse-ahCB.cjs");
5
+ const require_workflowTerminal = require("../workflowTerminal-DeJ9BBta.cjs");
6
6
  const require_workflowLaunchCommand = require("../workflowLaunchCommand-DJyDB96d.cjs");
7
7
  let _agimon_ai_doompi_core_piExtension = require("@agimon-ai/doompi-core/piExtension");
8
8
  let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
@@ -3462,12 +3462,13 @@ function createWorkflowPiRuntime(pi, options) {
3462
3462
  if (!refreshIsCurrent()) return;
3463
3463
  const sessionId = (0, _agimon_ai_doompi_core_childProcess.resolveRootSessionId)(ctx.sessionManager.getSessionId(), options.environment);
3464
3464
  const rootProcess = isRootProcess(ctx, sessionId);
3465
- const sessionRecords = records.filter((record) => require_workflowTerminal.isSessionRun(record, sessionId));
3466
- const running = sessionRecords.filter((record) => record.stage === RUNNING_STATUS && !record.stale);
3467
- const failed = sessionRecords.filter((record) => record.stage === ERROR_STAGE);
3465
+ const ownRecords = records.filter((record) => require_workflowTerminal.isSessionRun(record, sessionId));
3466
+ const sessionRecords = records.filter((record) => require_workflowTerminal.isSessionRun(record, sessionId) || require_workflowTerminal.isLaunchedRun(record, sessionId));
3467
+ const running = ownRecords.filter((record) => record.stage === RUNNING_STATUS && !record.stale);
3468
+ const failed = ownRecords.filter((record) => record.stage === ERROR_STAGE);
3468
3469
  if (activeRevisionAtRead === activeRunRevision) replaceActiveRecords(running);
3469
3470
  activeTelemetry.recordEvent("doom_workflow.monitor_counts", {
3470
- "workflow.run_count": sessionRecords.length,
3471
+ "workflow.run_count": ownRecords.length,
3471
3472
  "workflow.running_count": running.length,
3472
3473
  "workflow.failed_count": failed.length,
3473
3474
  mode: workflowMode ? "on" : "off"
@@ -3482,11 +3483,12 @@ function createWorkflowPiRuntime(pi, options) {
3482
3483
  const previous = previousStages.get(identity);
3483
3484
  const startedAt = Date.parse(record.startedAt);
3484
3485
  const startedDuringSession = previous === void 0 && (sessionStartedAt === 0 || !Number.isFinite(startedAt) || startedAt >= sessionStartedAt);
3485
- if (lifecycleNarrationReady) narrateWorkflowTransition(narrationSink, record, previous === RUNNING_STATUS || previous === COMPLETED_STATUS || previous === ERROR_STAGE ? previous : void 0, startedDuringSession);
3486
+ if (lifecycleNarrationReady && require_workflowTerminal.isSessionRun(record, sessionId)) narrateWorkflowTransition(narrationSink, record, previous === RUNNING_STATUS || previous === COMPLETED_STATUS || previous === ERROR_STAGE ? previous : void 0, startedDuringSession);
3486
3487
  if (!notifyChanges || !rootProcess || record.stage === RUNNING_STATUS) continue;
3487
3488
  if (!deliveredTerminalRuns.has(identity) && (previous === RUNNING_STATUS || startedDuringSession)) pendingTerminalRuns.set(identity, record);
3488
3489
  }
3489
3490
  const retainedWork = new Map(mine.map((record) => [runIdentity(record), record]));
3491
+ for (const record of sessionRecords) if (require_workflowTerminal.isLaunchedRun(record, sessionId) && record.stage === RUNNING_STATUS && !record.stale) retainedWork.set(runIdentity(record), record);
3490
3492
  for (const [identity, record] of pendingTerminalRuns) retainedWork.set(identity, record);
3491
3493
  if (!refreshIsCurrent()) return;
3492
3494
  runProvider?.update([...retainedWork.values()].map((record) => ({
@@ -3505,28 +3507,39 @@ function createWorkflowPiRuntime(pi, options) {
3505
3507
  if (rootProcess && pendingTerminalRuns.size > 0) try {
3506
3508
  const terminalRecords = [...pendingTerminalRuns.values()];
3507
3509
  const summaries = await Promise.all(terminalRecords.map(async (record) => {
3508
- if (record.stage === COMPLETED_STATUS) return require_workflowTerminal.finishedRunSummary(record, []);
3510
+ if (record.stage === COMPLETED_STATUS) {
3511
+ const summary = require_workflowTerminal.finishedRunSummary(record, []);
3512
+ return require_workflowTerminal.isLaunchedRun(record, sessionId) ? `${summary}\n${require_workflowTerminal.launchedRunHint(record)}` : summary;
3513
+ }
3509
3514
  const identity = runIdentity(record);
3510
3515
  const events = runEvents.get(identity) ?? progressEventsForRun(record, await (0, _agimon_ai_workflow_mcp.readWorkflowProgress)(registry.runDirectoryFor(record)));
3511
- return require_workflowTerminal.finishedRunSummary(record, (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(events));
3516
+ const summary = require_workflowTerminal.finishedRunSummary(record, (0, _agimon_ai_workflow_mcp.summarizeWorkflowProgress)(events));
3517
+ return require_workflowTerminal.isLaunchedRun(record, sessionId) ? `${summary}\n${require_workflowTerminal.launchedRunHint(record)}` : summary;
3512
3518
  }));
3513
3519
  if (!refreshIsCurrent()) return;
3514
- pi.sendMessage({
3520
+ const message = (indexes) => ({
3515
3521
  customType: WORKFLOW_FINISHED_MESSAGE$1,
3516
- content: summaries.join("\n\n"),
3522
+ content: indexes.map((index) => summaries[index]).join("\n\n"),
3517
3523
  display: true,
3518
3524
  details: {
3519
- runIds: terminalRecords.map((record) => record.runId ?? runIdentity(record)),
3520
- runs: terminalRecords.map((record) => ({
3521
- runKey: record.runKey,
3522
- workspace: record.workspace,
3523
- stage: record.stage,
3524
- ...record.workflowId ? { workflowId: record.workflowId } : {},
3525
- ...record.failedJob ? { failedJob: record.failedJob } : {},
3526
- ...record.errorMessage ? { error: record.errorMessage } : {}
3527
- }))
3525
+ runIds: indexes.map((index) => terminalRecords[index].runId ?? runIdentity(terminalRecords[index])),
3526
+ runs: indexes.map((index) => {
3527
+ const record = terminalRecords[index];
3528
+ return {
3529
+ runKey: record.runKey,
3530
+ workspace: record.workspace,
3531
+ stage: record.stage,
3532
+ ...record.workflowId ? { workflowId: record.workflowId } : {},
3533
+ ...record.failedJob ? { failedJob: record.failedJob } : {},
3534
+ ...record.errorMessage ? { error: record.errorMessage } : {}
3535
+ };
3536
+ })
3528
3537
  }
3529
- }, {
3538
+ });
3539
+ const quiet = terminalRecords.flatMap((record, index) => require_workflowTerminal.isOwnedDelegatedRun(record, sessionId) ? [index] : []);
3540
+ const loud = terminalRecords.flatMap((record, index) => require_workflowTerminal.isOwnedDelegatedRun(record, sessionId) ? [] : [index]);
3541
+ if (quiet.length > 0) pi.sendMessage(message(quiet), { triggerTurn: false });
3542
+ if (loud.length > 0) pi.sendMessage(message(loud), {
3530
3543
  triggerTurn: true,
3531
3544
  deliverAs: "steer"
3532
3545
  });