@agimon-ai/doompi-workflow 0.0.1-alpha.49 → 0.0.1-alpha.50

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
@@ -117,8 +117,9 @@ Maintained by [Agimon](https://agimon.ai/about).
117
117
 
118
118
  ## Web cockpit plugin
119
119
 
120
- The `src/web` directory is this package's DoomPi web cockpit plugin: the workflows tab, its store,
121
- and its `workflow_runs` session channel, compiled into the cockpit bundle by `doompi-web`'s build.
120
+ The `src/web` directory is this package's DoomPi web cockpit plugin: the workflows panel the dock's
121
+ workflows group opens in a temporary tab, its store, and its `workflow_runs` session channel,
122
+ compiled into the cockpit bundle by `doompi-web`'s build.
122
123
  The hub-side data source ships behind the `./web-hub` subpath and reads the workflow registry
123
124
  (run.json plus progress.ndjson) exactly as the engine writes it. Both halves are declared by the
124
125
  `doompiWeb` block in package.json.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-workflow",
3
- "version": "0.0.1-alpha.49",
3
+ "version": "0.0.1-alpha.50",
4
4
  "description": "GitHub Actions-style workflow graphs, artifacts, recovery, and asynchronous runs for DoomPi.",
5
5
  "keywords": [
6
6
  "ai",
@@ -68,12 +68,12 @@
68
68
  "@modelcontextprotocol/sdk": "1.30.0",
69
69
  "hono": "4.13.5",
70
70
  "zod": "4.4.3",
71
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.48",
72
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.48",
73
- "@agimon-ai/doompi-ui": "0.0.1-alpha.49",
74
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.9",
75
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.12",
76
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.10"
71
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.49",
72
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.49",
73
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.10",
74
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.50",
75
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.13",
76
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.11"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@earendil-works/pi-coding-agent": "0.84.4",
@@ -10,10 +10,10 @@ import {
10
10
  workflowActivityRows,
11
11
  } from './workflowActivity.ts';
12
12
  import { openCatalog } from './catalogStore.ts';
13
+ import { workflowsTab } from './WorkflowsPanel.tsx';
13
14
  import { focusRun, workflows } from './workflowsStore.ts';
14
15
 
15
16
  const TICK_MS = 10_000;
16
- const WORKFLOWS_TAB = 'workflows';
17
17
 
18
18
  const TONE_DOT: Readonly<Record<WorkflowActivityTone, DotTone>> = {
19
19
  running: 'yellow',
@@ -45,7 +45,7 @@ const GROUP_LABEL: Readonly<Record<WorkflowActivityGroupName, string>> = {
45
45
  * they are the only ones that need an answer, and the rest stay out of the way
46
46
  * until somebody asks. Idle offers the catalog instead of saying nothing.
47
47
  */
48
- export function WorkflowsActivitySection({ sessionId, openTab }: WebPluginSlotProps) {
48
+ export function WorkflowsActivitySection({ sessionId, openTransientTab }: WebPluginSlotProps) {
49
49
  const runs = useStore(workflows.store, (state) => workflows.select(state, sessionId).runs);
50
50
  const [now, setNow] = useState(() => Date.now());
51
51
  const [folded, setFolded] = useState<Partial<Record<WorkflowActivityGroupName, boolean>>>({});
@@ -70,7 +70,7 @@ export function WorkflowsActivitySection({ sessionId, openTab }: WebPluginSlotPr
70
70
  onClick={() => {
71
71
  if (sessionId === null) return;
72
72
  openCatalog(sessionId);
73
- openTab(WORKFLOWS_TAB);
73
+ openTransientTab(workflowsTab());
74
74
  }}
75
75
  >
76
76
  launch a workflow
@@ -89,7 +89,7 @@ export function WorkflowsActivitySection({ sessionId, openTab }: WebPluginSlotPr
89
89
  onClick={() => {
90
90
  if (sessionId === null) return;
91
91
  focusRun(sessionId, row.identity);
92
- openTab(WORKFLOWS_TAB);
92
+ openTransientTab(workflowsTab());
93
93
  }}
94
94
  className="min-w-0 gap-0.5 rounded-[5px] px-1 py-1 hover:bg-doom-panel"
95
95
  >
@@ -25,7 +25,7 @@ import {
25
25
  StatusBadge,
26
26
  StreamCursor,
27
27
  } from '@agimon-ai/doompi-web-components';
28
- import type { WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
28
+ import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
29
29
  import { useStore } from '@tanstack/react-store';
30
30
  import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
31
31
  import type {
@@ -45,6 +45,9 @@ import { WorkflowCatalogDrawer } from './WorkflowCatalogDrawer.tsx';
45
45
  import { workflowRunIdentity } from './workflowActivity.ts';
46
46
  import { focusRun, removeRun, workflows } from './workflowsStore.ts';
47
47
 
48
+ /** One workflows tab per session; the surface is singular, so the id needs nothing else. */
49
+ export const WORKFLOWS_TAB_ID = 'workflows-runs';
50
+
48
51
  const TICK_MS = 10_000;
49
52
 
50
53
  const STATE_ICON: Readonly<Record<WorkflowProgressState, { glyph: string; className: string }>> = {
@@ -727,3 +730,8 @@ export function WorkflowsPanel({ sessionId, openTransientTab, sendSessionFrame }
727
730
  </div>
728
731
  );
729
732
  }
733
+
734
+ /** The temporary tab the workflow surface opens in; opening it again only focuses it. */
735
+ export function workflowsTab(): TransientTab {
736
+ return { id: WORKFLOWS_TAB_ID, label: 'workflows', panel: WorkflowsPanel };
737
+ }
package/src/web/index.ts CHANGED
@@ -1,19 +1,10 @@
1
1
  import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
2
- import { useStore } from '@tanstack/react-store';
3
2
  import { openCatalog, workflowCatalogChannel } from './catalogStore.ts';
4
3
  import { WorkflowsActivitySection } from './WorkflowsActivitySection.tsx';
5
- import { WorkflowsPanel } from './WorkflowsPanel.tsx';
4
+ import { workflowsTab } from './WorkflowsPanel.tsx';
6
5
  import { WorkflowToolMessage } from './WorkflowToolMessage.tsx';
7
6
  import { workflowRunsChannel, workflows } from './workflowsStore.ts';
8
7
 
9
- /** The tab badge: only runs still in the running stage. */
10
- export function useWorkflowsBadge(sessionId: string | null): number {
11
- return useStore(
12
- workflows.store,
13
- (state) => workflows.select(state, sessionId).runs.filter((run) => run.stage === 'running').length,
14
- );
15
- }
16
-
17
8
  const WORKFLOWS_GROUP = { key: 'w', label: 'workflows', detail: 'multi-step agent runs' };
18
9
 
19
10
  const workflowActivitySource = {
@@ -29,7 +20,8 @@ const workflowActivitySource = {
29
20
  /** The named export the generated plugin registry imports. */
30
21
  export const webPlugin = defineWebPlugin({
31
22
  id: 'workflows',
32
- tabs: [{ id: 'workflows', label: 'workflows', panel: WorkflowsPanel, useBadge: useWorkflowsBadge }],
23
+ // No declared tab: the runs surface is a panel the reader opens from the
24
+ // dock's workflows group, and closes when they are done with it.
33
25
  channels: [workflowRunsChannel, workflowCatalogChannel],
34
26
  minorModes: [{ name: 'workflow', keys: 'w e', widgetKey: 'workflow-mcp-progress', order: 50 }],
35
27
  activityGroups: [
@@ -38,7 +30,7 @@ export const webPlugin = defineWebPlugin({
38
30
  keys: 'w r',
39
31
  widgetKeys: ['workflow-mcp-progress', 'workflow-mcp-follow'],
40
32
  activeSource: workflowActivitySource,
41
- tab: 'workflows',
33
+ transientTab: workflowsTab,
42
34
  order: 30,
43
35
  },
44
36
  ],
@@ -53,14 +45,14 @@ export const webPlugin = defineWebPlugin({
53
45
  {
54
46
  id: 'doom-workflow.manage',
55
47
  path: [WORKFLOWS_GROUP, { key: 'r', label: 'runs', detail: 'runs in this session' }],
56
- run: (context) => context.openTab('workflows'),
48
+ run: (context) => context.openTransientTab(workflowsTab()),
57
49
  },
58
50
  {
59
51
  id: 'doom-workflow.catalog',
60
52
  path: [WORKFLOWS_GROUP, { key: 'l', label: 'launch', detail: 'pick a workflow and launch it' }],
61
53
  run: (context) => {
62
54
  if (context.sessionId !== null) openCatalog(context.sessionId);
63
- context.openTab('workflows');
55
+ context.openTransientTab(workflowsTab());
64
56
  },
65
57
  },
66
58
  {