pi-cockpit 0.3.0 → 0.4.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.
- package/package.json +2 -2
- package/src/agents-store.ts +34 -7
- package/src/render.ts +1 -1
- package/src/stack-widget.ts +133 -111
- package/src/types.ts +116 -114
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cockpit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Agent cockpit for Pi: a list-mode status stack (live teammates + todo plan) pinned above the editor, plus a Starship-style footer. Renders only via public extension APIs (setWidget/setFooter).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
60
60
|
"@earendil-works/pi-tui": "0.82.1",
|
|
61
61
|
"@types/node": "^22.0.0",
|
|
62
|
-
"pi-maestro-teammate": "1.
|
|
62
|
+
"pi-maestro-teammate": "1.2.0",
|
|
63
63
|
"typescript": "^5.5.0"
|
|
64
64
|
},
|
|
65
65
|
"main": "./src/index.ts",
|
package/src/agents-store.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface ProgressPayload {
|
|
|
37
37
|
dependencies?: number[];
|
|
38
38
|
status?: string;
|
|
39
39
|
startedAt?: number | string;
|
|
40
|
+
completedAt?: number | string;
|
|
41
|
+
durationMs?: number;
|
|
40
42
|
lastActivityAt?: number | string;
|
|
41
43
|
recentTools?: Array<string | { name?: string; status?: string }>;
|
|
42
44
|
toolCount?: number;
|
|
@@ -65,7 +67,8 @@ export interface MessagePayload {
|
|
|
65
67
|
lastActivityAt?: number | string;
|
|
66
68
|
progress?: ProgressPayload[];
|
|
67
69
|
}
|
|
68
|
-
export type CompletePayload = Pick<TeammateCompleteEvent, "correlationId" | "exitCode"
|
|
70
|
+
export type CompletePayload = Pick<TeammateCompleteEvent, "correlationId" | "exitCode">
|
|
71
|
+
& Partial<Pick<TeammateCompleteEvent, "durationMs">>;
|
|
69
72
|
|
|
70
73
|
/**
|
|
71
74
|
* How long a failed agent stays on screen after it completes.
|
|
@@ -109,6 +112,19 @@ function normalizeStartedAt(value: number | string | undefined, fallback: number
|
|
|
109
112
|
return fallback;
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
function terminalTime(
|
|
116
|
+
row: AgentRow,
|
|
117
|
+
payload: { completedAt?: number | string; durationMs?: number },
|
|
118
|
+
now: number,
|
|
119
|
+
): number {
|
|
120
|
+
const completedAt = normalizeStartedAt(payload.completedAt, Number.NaN);
|
|
121
|
+
if (Number.isFinite(completedAt)) return Math.max(row.startedAt, completedAt);
|
|
122
|
+
if (typeof payload.durationMs === "number" && Number.isFinite(payload.durationMs)) {
|
|
123
|
+
return row.startedAt + Math.max(0, payload.durationMs);
|
|
124
|
+
}
|
|
125
|
+
return row.finishedAt ?? now;
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
function latestTool(tools: MessagePayload["recentTools"]): string | undefined {
|
|
113
129
|
if (!tools?.length) return undefined;
|
|
114
130
|
const tool = tools.find((candidate) => typeof candidate === "object" && candidate?.status === "running")
|
|
@@ -133,7 +149,7 @@ export class AgentsStore {
|
|
|
133
149
|
applyStarted(p: StartedPayload, now: number = Date.now()): void {
|
|
134
150
|
const id = p.correlationId;
|
|
135
151
|
const prev = this.roster.get(id);
|
|
136
|
-
|
|
152
|
+
const row: AgentRow = {
|
|
137
153
|
...prev,
|
|
138
154
|
correlationId: id,
|
|
139
155
|
agent: clean(p.agent) || prev?.agent || "",
|
|
@@ -149,7 +165,9 @@ export class AgentsStore {
|
|
|
149
165
|
: prev?.parentCorrelationId
|
|
150
166
|
? { parentCorrelationId: prev.parentCorrelationId }
|
|
151
167
|
: {}),
|
|
152
|
-
}
|
|
168
|
+
};
|
|
169
|
+
if (row.status !== "done" && row.status !== "failed") delete row.finishedAt;
|
|
170
|
+
this.roster.set(id, row);
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
applyMessage(p: MessagePayload, now = Date.now()): void {
|
|
@@ -191,6 +209,8 @@ export class AgentsStore {
|
|
|
191
209
|
if (typeof p.status === "string") {
|
|
192
210
|
row.taskStatus = p.status;
|
|
193
211
|
row.status = mapAgentStatus(p.status);
|
|
212
|
+
if (row.status === "done" || row.status === "failed") row.finishedAt ??= now;
|
|
213
|
+
else delete row.finishedAt;
|
|
194
214
|
}
|
|
195
215
|
if (typeof p.taskIndex === "number") row.taskIndex = p.taskIndex;
|
|
196
216
|
if (Array.isArray(p.dependencies)) row.dependencies = [...p.dependencies];
|
|
@@ -216,6 +236,7 @@ export class AgentsStore {
|
|
|
216
236
|
if (row && (row.status === "failed" || failedByExitCode)) {
|
|
217
237
|
row.status = "failed";
|
|
218
238
|
row.taskStatus = "failed";
|
|
239
|
+
row.finishedAt = terminalTime(row, id === p.correlationId ? p : {}, now);
|
|
219
240
|
row.failedAt = now;
|
|
220
241
|
row.lastActivityAt = now;
|
|
221
242
|
delete row.activeTool;
|
|
@@ -263,20 +284,26 @@ export class AgentsStore {
|
|
|
263
284
|
}
|
|
264
285
|
const row = this.roster.get(p.correlationId);
|
|
265
286
|
if (!row) return;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
287
|
+
if (
|
|
288
|
+
parentCorrelationId !== p.correlationId
|
|
289
|
+
&& row.parentCorrelationId === undefined
|
|
290
|
+
) row.parentCorrelationId = parentCorrelationId;
|
|
269
291
|
row.agent = clean(p.agent) || row.agent;
|
|
270
292
|
row.name = p.name === undefined ? row.name : clean(p.name);
|
|
271
293
|
row.role = deriveRole(p.agent, p.name);
|
|
272
294
|
row.task = p.name === undefined ? row.task : clean(p.name);
|
|
295
|
+
if (p.startedAt !== undefined) row.startedAt = normalizeStartedAt(p.startedAt, row.startedAt);
|
|
273
296
|
if (typeof p.status === "string") {
|
|
274
297
|
row.taskStatus = p.status;
|
|
275
298
|
row.status = mapAgentStatus(p.status);
|
|
299
|
+
if (row.status === "done" || row.status === "failed") {
|
|
300
|
+
row.finishedAt = terminalTime(row, p, now);
|
|
301
|
+
} else {
|
|
302
|
+
delete row.finishedAt;
|
|
303
|
+
}
|
|
276
304
|
}
|
|
277
305
|
row.taskIndex = p.taskIndex;
|
|
278
306
|
row.dependencies = Array.isArray(p.dependencies) ? [...p.dependencies] : [];
|
|
279
|
-
if (p.startedAt !== undefined) row.startedAt = normalizeStartedAt(p.startedAt, row.startedAt);
|
|
280
307
|
row.lastActivityAt = normalizeStartedAt(p.lastActivityAt, now);
|
|
281
308
|
if (p.recentTools) {
|
|
282
309
|
const tool = latestTool(p.recentTools);
|
package/src/render.ts
CHANGED
|
@@ -239,7 +239,7 @@ export function renderAgents(
|
|
|
239
239
|
if (status.label) segs.push({ text: theme.fg(status.color, status.label), priority: 95, clippable: false });
|
|
240
240
|
segs.push({ text: theme.fg(rc, r.role), priority: 90 });
|
|
241
241
|
if (r.task) segs.push({ text: r.task, priority: 80, minWidth: 6 });
|
|
242
|
-
segs.push({ text: theme.fg("muted", formatDuration(now - r.startedAt)), priority: 70, clippable: false });
|
|
242
|
+
segs.push({ text: theme.fg("muted", formatDuration((r.finishedAt ?? now) - r.startedAt)), priority: 70, clippable: false });
|
|
243
243
|
if (r.dependencies?.length) {
|
|
244
244
|
segs.push({ text: theme.fg("dim", formatDependencies(r.dependencies, g)), priority: 60, clippable: false });
|
|
245
245
|
}
|
package/src/stack-widget.ts
CHANGED
|
@@ -1,111 +1,133 @@
|
|
|
1
|
-
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { TUI } from "@earendil-works/pi-tui";
|
|
3
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
-
import { renderAgents, renderTodos, type PaintTheme, type WidthUtils } from "./render.ts";
|
|
5
|
-
import { fitLineByPriority, type PrioritizedSegment } from "./layout.ts";
|
|
6
|
-
import { resolveGlyphs, spinFrame } from "./icons.ts";
|
|
7
|
-
import { panelRows } from "./viewport.ts";
|
|
8
|
-
import type { AgentRow, CockpitConfig, TodoItem } from "./types.ts";
|
|
9
|
-
|
|
10
|
-
export interface TodoWidgetDeps {
|
|
11
|
-
getTodos: () => TodoItem[];
|
|
12
|
-
getConfig: () => CockpitConfig;
|
|
13
|
-
/** False when no redraw loop is running, so spinners must not freeze mid-cycle. */
|
|
14
|
-
isAnimating?: () => boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface AgentWidgetDeps {
|
|
18
|
-
getAgents: () => AgentRow[];
|
|
19
|
-
getConfig: () => CockpitConfig;
|
|
20
|
-
isRunning: () => boolean;
|
|
21
|
-
isAnimating?: () => boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const UTILS: WidthUtils = { measure: visibleWidth, clip: truncateToWidth };
|
|
25
|
-
|
|
26
|
-
// The TUI exposes the live terminal size, but a widget must never fail to render
|
|
27
|
-
// because a host handed it a stub without one.
|
|
28
|
-
export function terminalRows(tui: TUI): number | undefined {
|
|
29
|
-
try {
|
|
30
|
-
const rows = tui.terminal?.rows;
|
|
31
|
-
return typeof rows === "number" && rows > 0 ? rows : undefined;
|
|
32
|
-
} catch {
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
+
import { renderAgents, renderTodos, type PaintTheme, type WidthUtils } from "./render.ts";
|
|
5
|
+
import { fitLineByPriority, type PrioritizedSegment } from "./layout.ts";
|
|
6
|
+
import { resolveGlyphs, spinFrame } from "./icons.ts";
|
|
7
|
+
import { panelRows } from "./viewport.ts";
|
|
8
|
+
import type { AgentRow, CockpitConfig, TodoItem } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
export interface TodoWidgetDeps {
|
|
11
|
+
getTodos: () => TodoItem[];
|
|
12
|
+
getConfig: () => CockpitConfig;
|
|
13
|
+
/** False when no redraw loop is running, so spinners must not freeze mid-cycle. */
|
|
14
|
+
isAnimating?: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentWidgetDeps {
|
|
18
|
+
getAgents: () => AgentRow[];
|
|
19
|
+
getConfig: () => CockpitConfig;
|
|
20
|
+
isRunning: () => boolean;
|
|
21
|
+
isAnimating?: () => boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const UTILS: WidthUtils = { measure: visibleWidth, clip: truncateToWidth };
|
|
25
|
+
|
|
26
|
+
// The TUI exposes the live terminal size, but a widget must never fail to render
|
|
27
|
+
// because a host handed it a stub without one.
|
|
28
|
+
export function terminalRows(tui: TUI): number | undefined {
|
|
29
|
+
try {
|
|
30
|
+
const rows = tui.terminal?.rows;
|
|
31
|
+
return typeof rows === "number" && rows > 0 ? rows : undefined;
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function visibleAgentRows(rows: AgentRow[]): AgentRow[] {
|
|
38
|
+
const byId = new Map(rows.map((row) => [row.correlationId, row]));
|
|
39
|
+
return rows.filter((row) => !row.agent.startsWith("graph(")).map((row) => {
|
|
40
|
+
let parent = row.parentCorrelationId;
|
|
41
|
+
const visited = new Set([row.correlationId]);
|
|
42
|
+
while (parent) {
|
|
43
|
+
if (visited.has(parent)) {
|
|
44
|
+
parent = undefined;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
visited.add(parent);
|
|
48
|
+
const parentRow = byId.get(parent);
|
|
49
|
+
if (!parentRow || !parentRow.agent.startsWith("graph(")) break;
|
|
50
|
+
parent = parentRow.parentCorrelationId;
|
|
51
|
+
}
|
|
52
|
+
return parent === row.parentCorrelationId ? row : { ...row, parentCorrelationId: parent };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Todo widget: pinned above the editor (setWidget "cockpit-stack", aboveEditor).
|
|
57
|
+
export function makeTodoWidget(deps: TodoWidgetDeps) {
|
|
58
|
+
return (tui: TUI, theme: Theme) => {
|
|
59
|
+
const paint: PaintTheme = theme;
|
|
60
|
+
return {
|
|
61
|
+
render(width: number): string[] {
|
|
62
|
+
const cfg = deps.getConfig();
|
|
63
|
+
const todos = deps.getTodos();
|
|
64
|
+
if (todos.length === 0) return [];
|
|
65
|
+
const g = resolveGlyphs(cfg.icons.mode);
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const spin = spinFrame(g, now, deps.isAnimating?.() ?? true);
|
|
68
|
+
const opts = {
|
|
69
|
+
glyphs: g,
|
|
70
|
+
spin,
|
|
71
|
+
now,
|
|
72
|
+
expanded: cfg.todoExpanded,
|
|
73
|
+
maxRows: panelRows(terminalRows(tui)),
|
|
74
|
+
};
|
|
75
|
+
return renderTodos(todos, cfg.todoExpanded ? "list" : cfg.todoMode, width, paint, UTILS, opts);
|
|
76
|
+
},
|
|
77
|
+
invalidate(): void {},
|
|
78
|
+
dispose(): void {},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Agent widget: pinned below the editor, near the input box (setWidget "cockpit-agents", belowEditor).
|
|
84
|
+
export function makeAgentWidget(deps: AgentWidgetDeps) {
|
|
85
|
+
return (tui: TUI, theme: Theme) => {
|
|
86
|
+
const paint: PaintTheme = theme;
|
|
87
|
+
return {
|
|
88
|
+
render(width: number): string[] {
|
|
89
|
+
const cfg = deps.getConfig();
|
|
90
|
+
// graph(...) is the dispatch container, not an additional worker. Keep it
|
|
91
|
+
// in AgentsStore for linkage and cleanup, and bridge its visible descendants
|
|
92
|
+
// to the nearest non-graph ancestor for rendering.
|
|
93
|
+
const agents = visibleAgentRows(deps.getAgents());
|
|
94
|
+
if (agents.length === 0) return [];
|
|
95
|
+
const g = resolveGlyphs(cfg.icons.mode);
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const animating = deps.isAnimating?.() ?? true;
|
|
98
|
+
const spin = spinFrame(g, now, animating);
|
|
99
|
+
const running = deps.isRunning();
|
|
100
|
+
// The panel budget covers the roster header and its rows.
|
|
101
|
+
const panel = panelRows(terminalRows(tui));
|
|
102
|
+
const rosterRows = panel === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: Math.max(1, panel - 1);
|
|
105
|
+
const opts = { glyphs: g, spin, now, maxRows: rosterRows };
|
|
106
|
+
const dot = theme.fg(running ? "success" : "muted", running ? g.dotRunning : g.dotIdle);
|
|
107
|
+
const failedCount = agents.filter((a) => a.status === "failed").length;
|
|
108
|
+
const runCount = agents.filter((a) => a.status === "running" || a.status === "retrying").length;
|
|
109
|
+
const pendingCount = agents.filter((a) => a.status === "pending").length;
|
|
110
|
+
const sleepingCount = agents.filter((a) => a.status === "sleeping").length;
|
|
111
|
+
// This header owns the roster summary, so compact mode must not print
|
|
112
|
+
// its own count line right underneath saying the same thing.
|
|
113
|
+
const headerSegs: PrioritizedSegment[] = [
|
|
114
|
+
{ text: dot, priority: 100, clippable: false },
|
|
115
|
+
{ text: theme.fg("muted", "Agents"), priority: 90, clippable: false },
|
|
116
|
+
];
|
|
117
|
+
if (failedCount) {
|
|
118
|
+
headerSegs.push({ text: theme.fg("error", `${failedCount} failed`), priority: 95, clippable: false });
|
|
119
|
+
}
|
|
120
|
+
if (runCount) headerSegs.push({ text: theme.fg("dim", `${runCount} running`), priority: 80, clippable: false });
|
|
121
|
+
if (pendingCount) headerSegs.push({ text: theme.fg("dim", `${pendingCount} pending`), priority: 60, clippable: false });
|
|
122
|
+
if (sleepingCount) headerSegs.push({ text: theme.fg("dim", `${sleepingCount} sleeping`), priority: 50, clippable: false });
|
|
123
|
+
const lines: string[] = [];
|
|
124
|
+
// Was the one line in the package pushed without any width clipping.
|
|
125
|
+
lines.push(fitLineByPriority(headerSegs, width, UTILS, theme.fg("dim", g.separator), g.ellipsis));
|
|
126
|
+
lines.push(...renderAgents(agents, cfg.agentsMode, width, paint, UTILS, { ...opts, withHead: false }));
|
|
127
|
+
return lines;
|
|
128
|
+
},
|
|
129
|
+
invalidate(): void {},
|
|
130
|
+
dispose(): void {},
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,114 +1,116 @@
|
|
|
1
|
-
// Cross-extension event names broadcast by pi-maestro-teammate.
|
|
2
|
-
// Hard-coded strings (stable contract; see pi-maestro-teammate/src/shared/types.ts:184-186)
|
|
3
|
-
// so this package needs no import path into the teammate package.
|
|
4
|
-
export const TEAMMATE_STARTED_EVENT = "teammate:started";
|
|
5
|
-
export const TEAMMATE_MESSAGE_EVENT = "teammate:message";
|
|
6
|
-
export const TEAMMATE_COMPLETE_EVENT = "teammate:complete";
|
|
7
|
-
export const COCKPIT_UI_OWNERSHIP_EVENT = "cockpit:ui-ownership";
|
|
8
|
-
export const COCKPIT_TODO_TOGGLE_EVENT = "cockpit:toggle-todo";
|
|
9
|
-
export const BASH_BG_UPDATE_EVENT = "bash-bg:update";
|
|
10
|
-
export const BASH_BG_QUERY_EVENT = "bash-bg:query";
|
|
11
|
-
export const WORKFLOW_STATUS_KEY = "maestro-workflow";
|
|
12
|
-
|
|
13
|
-
// Widget keys owned by pi-cockpit. Native Flow/teammate widgets yield through
|
|
14
|
-
// COCKPIT_UI_OWNERSHIP_EVENT instead of being cleared across extension boundaries.
|
|
15
|
-
export const STACK_WIDGET_KEY = "cockpit-stack";
|
|
16
|
-
export const AGENT_WIDGET_KEY = "cockpit-agents";
|
|
17
|
-
|
|
18
|
-
// todo tool name registered by pi-maestro-flow (src/extension/index.ts:594).
|
|
19
|
-
export const TODO_TOOL_NAME = "todo";
|
|
20
|
-
// appendEntry customType the todo tool persists after every mutation (tools/todo.ts:145).
|
|
21
|
-
export const TODO_STATE_ENTRY_TYPE = "todo-state";
|
|
22
|
-
|
|
23
|
-
export type AgentStatus = "pending" | "running" | "retrying" | "sleeping" | "done" | "failed";
|
|
24
|
-
|
|
25
|
-
export interface AgentRow {
|
|
26
|
-
correlationId: string;
|
|
27
|
-
parentCorrelationId?: string;
|
|
28
|
-
agent: string;
|
|
29
|
-
name: string | undefined;
|
|
30
|
-
role: string;
|
|
31
|
-
task: string;
|
|
32
|
-
status: AgentStatus;
|
|
33
|
-
tail: string;
|
|
34
|
-
startedAt: number;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
export type
|
|
91
|
-
|
|
92
|
-
export
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
1
|
+
// Cross-extension event names broadcast by pi-maestro-teammate.
|
|
2
|
+
// Hard-coded strings (stable contract; see pi-maestro-teammate/src/shared/types.ts:184-186)
|
|
3
|
+
// so this package needs no import path into the teammate package.
|
|
4
|
+
export const TEAMMATE_STARTED_EVENT = "teammate:started";
|
|
5
|
+
export const TEAMMATE_MESSAGE_EVENT = "teammate:message";
|
|
6
|
+
export const TEAMMATE_COMPLETE_EVENT = "teammate:complete";
|
|
7
|
+
export const COCKPIT_UI_OWNERSHIP_EVENT = "cockpit:ui-ownership";
|
|
8
|
+
export const COCKPIT_TODO_TOGGLE_EVENT = "cockpit:toggle-todo";
|
|
9
|
+
export const BASH_BG_UPDATE_EVENT = "bash-bg:update";
|
|
10
|
+
export const BASH_BG_QUERY_EVENT = "bash-bg:query";
|
|
11
|
+
export const WORKFLOW_STATUS_KEY = "maestro-workflow";
|
|
12
|
+
|
|
13
|
+
// Widget keys owned by pi-cockpit. Native Flow/teammate widgets yield through
|
|
14
|
+
// COCKPIT_UI_OWNERSHIP_EVENT instead of being cleared across extension boundaries.
|
|
15
|
+
export const STACK_WIDGET_KEY = "cockpit-stack";
|
|
16
|
+
export const AGENT_WIDGET_KEY = "cockpit-agents";
|
|
17
|
+
|
|
18
|
+
// todo tool name registered by pi-maestro-flow (src/extension/index.ts:594).
|
|
19
|
+
export const TODO_TOOL_NAME = "todo";
|
|
20
|
+
// appendEntry customType the todo tool persists after every mutation (tools/todo.ts:145).
|
|
21
|
+
export const TODO_STATE_ENTRY_TYPE = "todo-state";
|
|
22
|
+
|
|
23
|
+
export type AgentStatus = "pending" | "running" | "retrying" | "sleeping" | "done" | "failed";
|
|
24
|
+
|
|
25
|
+
export interface AgentRow {
|
|
26
|
+
correlationId: string;
|
|
27
|
+
parentCorrelationId?: string;
|
|
28
|
+
agent: string;
|
|
29
|
+
name: string | undefined;
|
|
30
|
+
role: string;
|
|
31
|
+
task: string;
|
|
32
|
+
status: AgentStatus;
|
|
33
|
+
tail: string;
|
|
34
|
+
startedAt: number;
|
|
35
|
+
/** Terminal wall-clock used to freeze elapsed time for completed/failed rows. */
|
|
36
|
+
finishedAt?: number;
|
|
37
|
+
lastActivityAt: number;
|
|
38
|
+
toolCount?: number;
|
|
39
|
+
tokens?: number;
|
|
40
|
+
inputTokens?: number;
|
|
41
|
+
outputTokens?: number;
|
|
42
|
+
taskStatus?: string;
|
|
43
|
+
activeTool?: string;
|
|
44
|
+
taskIndex?: number;
|
|
45
|
+
dependencies?: number[];
|
|
46
|
+
/** Set only on a failed row that outlived its completion, so it can be pruned. */
|
|
47
|
+
failedAt?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type BashBgStatus = "running" | "stopping" | "completed" | "failed" | "killed";
|
|
51
|
+
|
|
52
|
+
export interface BashBgJob {
|
|
53
|
+
id: string;
|
|
54
|
+
command: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
pid: number;
|
|
57
|
+
status: BashBgStatus;
|
|
58
|
+
startedAt: number;
|
|
59
|
+
updatedAt: number;
|
|
60
|
+
finishedAt?: number;
|
|
61
|
+
exitCode: number | null;
|
|
62
|
+
outputTail: string;
|
|
63
|
+
outputBytes: number;
|
|
64
|
+
logPath: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type TodoState = "pending" | "in_progress" | "completed" | "blocked";
|
|
68
|
+
|
|
69
|
+
export interface TodoItem {
|
|
70
|
+
id: string;
|
|
71
|
+
subject: string;
|
|
72
|
+
status: TodoState;
|
|
73
|
+
blockedBy: string[];
|
|
74
|
+
createdBy?: TodoActor;
|
|
75
|
+
assignee?: TodoActor;
|
|
76
|
+
skills: TodoSkill[];
|
|
77
|
+
updatedAt?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TodoActor {
|
|
81
|
+
id: string;
|
|
82
|
+
label: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TodoSkill {
|
|
86
|
+
name: string;
|
|
87
|
+
role?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ViewMode = "list" | "compact";
|
|
91
|
+
|
|
92
|
+
export type IconMode = "auto" | "nerd" | "ascii";
|
|
93
|
+
|
|
94
|
+
export interface CockpitConfig {
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
agentsMode: ViewMode;
|
|
97
|
+
todoMode: ViewMode;
|
|
98
|
+
todoExpanded: boolean;
|
|
99
|
+
hideNativeAgents: boolean;
|
|
100
|
+
icons: { mode: IconMode };
|
|
101
|
+
/**
|
|
102
|
+
* Theme to apply at session start. Empty means "leave whatever pi is using",
|
|
103
|
+
* so cockpit never overrides a theme the user picked elsewhere.
|
|
104
|
+
*/
|
|
105
|
+
theme: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const DEFAULT_CONFIG: CockpitConfig = {
|
|
109
|
+
enabled: true,
|
|
110
|
+
agentsMode: "list",
|
|
111
|
+
todoMode: "list",
|
|
112
|
+
todoExpanded: false,
|
|
113
|
+
hideNativeAgents: true,
|
|
114
|
+
icons: { mode: "auto" },
|
|
115
|
+
theme: "",
|
|
116
|
+
};
|