@g3un/pi-orchestra 0.2.0 → 0.2.1
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/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g3un/pi-orchestra",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Subagent orchestration tools for Pi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orchestration",
|
|
7
7
|
"pi",
|
|
8
8
|
"pi-extension",
|
|
9
|
-
"pi-package",
|
|
10
9
|
"subagent"
|
|
11
10
|
],
|
|
12
11
|
"homepage": "https://codeberg.org/g3un/pi-orchestra",
|
package/src/core/workflow.ts
CHANGED
|
@@ -2,17 +2,33 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { AgentRun } from "../core/subagent.ts";
|
|
3
3
|
import type { AgentStore } from "../core/store.ts";
|
|
4
4
|
import type { WorkflowRun, WorkflowStageRun } from "../core/workflow.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { isTerminalAgentState } from "../utils.ts";
|
|
6
6
|
|
|
7
7
|
const WIDGET_KEY = "pi-orchestra.workflow-monitor";
|
|
8
8
|
const MAX_MONITORED_WORKFLOWS = 2;
|
|
9
9
|
const MAX_WIDGET_LINES = 10;
|
|
10
|
+
const DEFAULT_TICK_MS = 1_000;
|
|
11
|
+
|
|
12
|
+
export interface WorkflowMonitorControllerOptions {
|
|
13
|
+
now?: () => number;
|
|
14
|
+
/** Defaults to 1000 ms. Use 0 to disable uptime ticks in tests. */
|
|
15
|
+
tickMs?: number;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
export class WorkflowMonitorController {
|
|
19
|
+
private readonly now: () => number;
|
|
20
|
+
private readonly tickMs: number;
|
|
12
21
|
private unsubscribe?: () => void;
|
|
22
|
+
private tickTimer?: ReturnType<typeof setInterval>;
|
|
13
23
|
private ctx?: ExtensionContext;
|
|
14
24
|
|
|
15
|
-
constructor(
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly store: AgentStore,
|
|
27
|
+
options: WorkflowMonitorControllerOptions = {},
|
|
28
|
+
) {
|
|
29
|
+
this.now = options.now ?? Date.now;
|
|
30
|
+
this.tickMs = options.tickMs ?? DEFAULT_TICK_MS;
|
|
31
|
+
}
|
|
16
32
|
|
|
17
33
|
hasActiveWorkflows(): boolean {
|
|
18
34
|
return listActiveWorkflows(this.store).length > 0;
|
|
@@ -30,6 +46,7 @@ export class WorkflowMonitorController {
|
|
|
30
46
|
unsubscribeWorkflows();
|
|
31
47
|
};
|
|
32
48
|
}
|
|
49
|
+
this.startTicking();
|
|
33
50
|
|
|
34
51
|
return this.render();
|
|
35
52
|
}
|
|
@@ -37,6 +54,7 @@ export class WorkflowMonitorController {
|
|
|
37
54
|
dispose(): void {
|
|
38
55
|
this.unsubscribe?.();
|
|
39
56
|
this.unsubscribe = undefined;
|
|
57
|
+
this.stopTicking();
|
|
40
58
|
|
|
41
59
|
if (this.ctx?.hasUI) {
|
|
42
60
|
this.ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
@@ -48,7 +66,7 @@ export class WorkflowMonitorController {
|
|
|
48
66
|
const ctx = this.ctx;
|
|
49
67
|
if (!ctx?.hasUI) return false;
|
|
50
68
|
|
|
51
|
-
const lines = buildWorkflowMonitorLines(this.store);
|
|
69
|
+
const lines = buildWorkflowMonitorLines(this.store, this.now());
|
|
52
70
|
if (lines.length === 0) {
|
|
53
71
|
this.dispose();
|
|
54
72
|
return false;
|
|
@@ -57,15 +75,28 @@ export class WorkflowMonitorController {
|
|
|
57
75
|
ctx.ui.setWidget(WIDGET_KEY, lines, { placement: "belowEditor" });
|
|
58
76
|
return true;
|
|
59
77
|
}
|
|
78
|
+
|
|
79
|
+
private startTicking(): void {
|
|
80
|
+
if (this.tickMs <= 0 || this.tickTimer) return;
|
|
81
|
+
const timer = setInterval(() => this.render(), this.tickMs);
|
|
82
|
+
(timer as typeof timer & { unref?: () => void }).unref?.();
|
|
83
|
+
this.tickTimer = timer;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private stopTicking(): void {
|
|
87
|
+
if (!this.tickTimer) return;
|
|
88
|
+
clearInterval(this.tickTimer);
|
|
89
|
+
this.tickTimer = undefined;
|
|
90
|
+
}
|
|
60
91
|
}
|
|
61
92
|
|
|
62
|
-
export function buildWorkflowMonitorLines(store: AgentStore): string[] {
|
|
93
|
+
export function buildWorkflowMonitorLines(store: AgentStore, nowMs = Date.now()): string[] {
|
|
63
94
|
const workflows = listActiveWorkflows(store);
|
|
64
95
|
if (workflows.length === 0) return [];
|
|
65
96
|
|
|
66
97
|
const lines: string[] = [];
|
|
67
98
|
for (const workflow of workflows.slice(0, MAX_MONITORED_WORKFLOWS)) {
|
|
68
|
-
appendWorkflowLines(lines, store, workflow);
|
|
99
|
+
appendWorkflowLines(lines, store, workflow, nowMs);
|
|
69
100
|
if (lines.length >= MAX_WIDGET_LINES) break;
|
|
70
101
|
}
|
|
71
102
|
|
|
@@ -77,12 +108,14 @@ export function buildWorkflowMonitorLines(store: AgentStore): string[] {
|
|
|
77
108
|
return lines.slice(0, MAX_WIDGET_LINES);
|
|
78
109
|
}
|
|
79
110
|
|
|
80
|
-
function appendWorkflowLines(lines: string[], store: AgentStore, workflow: WorkflowRun): void {
|
|
111
|
+
function appendWorkflowLines(lines: string[], store: AgentStore, workflow: WorkflowRun, nowMs: number): void {
|
|
81
112
|
if (lines.length >= MAX_WIDGET_LINES) return;
|
|
82
113
|
|
|
83
114
|
const stage = getCurrentStage(workflow);
|
|
84
|
-
const stageLabel = stage
|
|
85
|
-
|
|
115
|
+
const stageLabel = stage
|
|
116
|
+
? formatStageLabel(store, workflow, stage)
|
|
117
|
+
: `none (0/${workflow.stages.length}) | agents (0/0)`;
|
|
118
|
+
lines.push(`${workflow.name} | ${stageLabel} | ${formatWorkflowUptime(workflow, nowMs)}`);
|
|
86
119
|
}
|
|
87
120
|
|
|
88
121
|
function listActiveWorkflows(store: AgentStore): WorkflowRun[] {
|
|
@@ -98,7 +131,7 @@ function getCurrentStage(workflow: WorkflowRun): WorkflowStageRun | undefined {
|
|
|
98
131
|
function formatStageLabel(store: AgentStore, workflow: WorkflowRun, stage: WorkflowStageRun): string {
|
|
99
132
|
const stageIndex = workflow.stages.indexOf(stage);
|
|
100
133
|
const stagePosition = stageIndex >= 0 ? `${stageIndex + 1}/${workflow.stages.length}` : `?/${workflow.stages.length}`;
|
|
101
|
-
return `${stage.name}
|
|
134
|
+
return `${stage.name} (${stagePosition}) | agents (${formatStageProgress(store, stage)})`;
|
|
102
135
|
}
|
|
103
136
|
|
|
104
137
|
function formatStageProgress(store: AgentStore, stage: WorkflowStageRun): string {
|
|
@@ -106,6 +139,22 @@ function formatStageProgress(store: AgentStore, stage: WorkflowStageRun): string
|
|
|
106
139
|
return `${progress.completed}/${progress.total}`;
|
|
107
140
|
}
|
|
108
141
|
|
|
142
|
+
function formatWorkflowUptime(workflow: WorkflowRun, nowMs: number): string {
|
|
143
|
+
const elapsedSeconds = Math.max(0, Math.floor((nowMs - workflow.startedAtMs) / 1_000));
|
|
144
|
+
const seconds = elapsedSeconds % 60;
|
|
145
|
+
const totalMinutes = Math.floor(elapsedSeconds / 60);
|
|
146
|
+
const minutes = totalMinutes % 60;
|
|
147
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
148
|
+
|
|
149
|
+
if (hours > 0) return `${hours}h ${pad2(minutes)}m`;
|
|
150
|
+
if (totalMinutes > 0) return `${totalMinutes}m ${pad2(seconds)}s`;
|
|
151
|
+
return `${seconds}s`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function pad2(value: number): string {
|
|
155
|
+
return value.toString().padStart(2, "0");
|
|
156
|
+
}
|
|
157
|
+
|
|
109
158
|
function calculateStageProgress(store: AgentStore, stage: WorkflowStageRun): { completed: number; total: number } {
|
|
110
159
|
const runs = collectStageRuns(store, stage);
|
|
111
160
|
const completed = runs.filter((run) => isTerminalAgentState(run.state)).length;
|