shariq-pi-extensions 0.2.25 → 0.2.27
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/docs/ARCHITECTURE.md +1 -1
- package/docs/EXTENSIONS.md +4 -4
- package/extensions/antigravity-provider/README.md +1 -1
- package/extensions/antigravity-provider/antigravity/dashboard.ts +26 -5
- package/extensions/antigravity-provider/antigravity/index.ts +1 -3
- package/extensions/background-terminals/README.md +1 -1
- package/extensions/background-terminals/index.ts +68 -4
- package/extensions/factory-provider/API_KEYS.md +1 -1
- package/extensions/factory-provider/README.md +1 -1
- package/extensions/factory-provider/factory/dashboard.ts +24 -10
- package/extensions/factory-provider/index.ts +1 -3
- package/extensions/git-info/index.ts +4 -6
- package/extensions/orchestration/README.md +2 -2
- package/extensions/orchestration/dashboard.ts +56 -30
- package/extensions/orchestration/index.ts +80 -33
- package/extensions/performance-status/index.ts +23 -0
- package/extensions/pi-memory/index.ts +2 -4
- package/extensions/shared/README.md +2 -0
- package/extensions/shared/activity-dock.ts +100 -0
- package/extensions/shared/tool-card.ts +39 -0
- package/extensions/smart-compaction/index.ts +5 -2
- package/extensions/subagents/README.md +1 -1
- package/extensions/subagents/index.ts +114 -6
- package/extensions/subagents/src/ui/takeover.ts +17 -2
- package/extensions/task-list/README.md +1 -1
- package/extensions/task-list/index.ts +21 -30
- package/package.json +1 -1
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -17,7 +17,7 @@ docs/ package-level maintenance documentation
|
|
|
17
17
|
scripts/ repository validation helpers
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Extension entrypoints register tools, commands, providers, events, or UI. Shared code stays in `extensions/shared` only when more than one extension owns the behavior. Provider-specific protocol and authentication code remains inside its provider directory. The background-terminal, orchestration, and subagent skills ship beside their extensions so a new machine receives the required lifecycle guidance with the tools.
|
|
20
|
+
Extension entrypoints register tools, commands, providers, events, or UI. Shared code stays in `extensions/shared` only when more than one extension owns the behavior. The shared activity dock combines active Task List, terminal, subagent, and orchestration rows under one bounded widget; shared tool cards keep lifecycle operations compact in the timeline while preserving expanded output. Provider-specific protocol and authentication code remains inside its provider directory. The background-terminal, orchestration, and subagent skills ship beside their extensions so a new machine receives the required lifecycle guidance with the tools.
|
|
21
21
|
|
|
22
22
|
## Dependency model
|
|
23
23
|
|
package/docs/EXTENSIONS.md
CHANGED
|
@@ -44,13 +44,13 @@ State is stored in Pi session entries and reconstructed on resume, reload, and t
|
|
|
44
44
|
|
|
45
45
|
The subagent extension runs flat Pi child agents with profiles, capability policies, continuation, result delivery, optional worktrees, pre-warmed task dispatch, instant cascading cancellation, cross-session persistence, and a dashboard. Configuration lives in `<agent-dir>/subagents.json`; trusted projects may override it through their Pi config directory. The configured concurrency ceiling is 50.
|
|
46
46
|
|
|
47
|
-
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. All completed subagent snapshots and transcripts persist across Pi restarts (`<agent-dir>/subagents/runs/`), allowing `resume_from` to resume completed workers at any point. Interruption immediately cascades across all child fibers in `<10ms`. Child settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with the summary guaranteed in model context and never rendered as user-authored or follow-up input; status tools are for explicit inspection, not waiting.
|
|
47
|
+
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. These lifecycle operations render as compact expandable main-chat cards; running children share the bounded Active work dock and completed counts no longer occupy footer space after settlement. All completed subagent snapshots and transcripts persist across Pi restarts (`<agent-dir>/subagents/runs/`), allowing `resume_from` to resume completed workers at any point. Interruption immediately cascades across all child fibers in `<10ms`. Child settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with the summary guaranteed in model context and never rendered as user-authored or follow-up input; status tools are for explicit inspection, not waiting.
|
|
48
48
|
|
|
49
49
|
### [Orchestration](../extensions/orchestration/README.md)
|
|
50
50
|
|
|
51
51
|
The Orchestration extension coordinates explicitly requested large tasks through a dedicated orchestrator plus configurable explorer, frontend, backend, general-worker, and reviewer models. `/orchestration` opens the dashboard and `/orchestration settings` configures global role models. It reuses the canonical Subagents runtime, maintains a 10-worker pool per project, isolates Git writers in task worktrees, resumes the original worker for substantial review fixes, and applies final reviewed changes without committing or pushing.
|
|
52
52
|
|
|
53
|
-
The model-facing `create_orchestration` tool starts planning only after an explicit orchestration request. `get_orchestration` reports status without advancing work. Interrupted runs recover paused under `<agent-dir>/orchestration/`.
|
|
53
|
+
The model-facing `create_orchestration` tool starts planning only after an explicit orchestration request. `get_orchestration` reports status without advancing work. Both use compact expandable timeline cards, active runs share the Active work dock, and footer states distinguish plan-ready, blocked, running, and paused work. Dashboard editors and confirmations run outside the live overlay to avoid nested-input stalls. Interrupted runs recover paused under `<agent-dir>/orchestration/`.
|
|
54
54
|
|
|
55
55
|
### [Smart Compaction](../extensions/smart-compaction/README.md)
|
|
56
56
|
|
|
@@ -71,7 +71,7 @@ Key capabilities include:
|
|
|
71
71
|
|
|
72
72
|
Managed PTYs support servers, watchers, long builds, downloads, and interactive processes. The extension tracks up to eight concurrent terminals, retains bounded output, stores full logs in restrictive temporary directories, and stops process groups during shutdown or reload.
|
|
73
73
|
|
|
74
|
-
Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal keeps completion or failure in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with bounded output guaranteed in model context and never rendered as user-authored or follow-up input. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
|
|
74
|
+
Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. All use compact expandable main-chat cards, while running PTYs share the bounded Active work dock with elapsed time and a latest-output preview. A model-started terminal keeps completion or failure in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with bounded output guaranteed in model context and never rendered as user-authored or follow-up input. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
|
|
75
75
|
|
|
76
76
|
## Web access
|
|
77
77
|
|
|
@@ -91,7 +91,7 @@ Displays the estimated prompt and session contribution to the active context win
|
|
|
91
91
|
|
|
92
92
|
### [Performance status](../extensions/performance-status/README.md)
|
|
93
93
|
|
|
94
|
-
Adds a responsive footer-area status row
|
|
94
|
+
Adds a responsive footer-area status row while each assistant message is active and for an eight-second completion linger, then clears it to avoid permanent footer noise. Live TPS/output are marked as estimates; final TPS uses conventional decode throughput from first streamed token to message completion. TTFT and total elapsed time separately expose provider latency, prefill, and hidden reasoning; output tokens and active tools remain separate.
|
|
95
95
|
|
|
96
96
|
### [Git info](../extensions/git-info/README.md)
|
|
97
97
|
|
|
@@ -10,7 +10,7 @@ Local persistent Pi provider for Google Antigravity-compatible models.
|
|
|
10
10
|
|
|
11
11
|
The provider includes an IPv4 OAuth token-exchange fallback for Node environments where the default request fails. Its curated catalog follows current Antigravity model identifiers and runtime behavior.
|
|
12
12
|
|
|
13
|
-
Successful logins are added to `<agent-dir>/antigravity/accounts.json`, written with owner-only permissions. A missing account file triggers one-time migration of an existing Pi OAuth credential; after that, the valid account file is authoritative, so removing an account cannot be undone by a stale credential in `auth.json`. Requests select the least recently used eligible account, skip disabled/cooling/exhausted accounts, refresh expiring OAuth tokens, and rotate to another account when an auth, rate, quota, or capacity failure occurs before response streaming begins. Cached per-model remaining quota and reset times guide selection; `/antigravity` refreshes the authoritative catalog, uses `d` to reversibly enable or disable an account, and uses `x`
|
|
13
|
+
Successful logins are added to `<agent-dir>/antigravity/accounts.json`, written with owner-only permissions. A missing account file triggers one-time migration of an existing Pi OAuth credential; after that, the valid account file is authoritative, so removing an account cannot be undone by a stale credential in `auth.json`. Requests select the least recently used eligible account, skip disabled/cooling/exhausted accounts, refresh expiring OAuth tokens, and rotate to another account when an auth, rate, quota, or capacity failure occurs before response streaming begins. Cached per-model remaining quota and reset times guide selection; `/antigravity` refreshes the authoritative catalog, uses `d` to reversibly enable or disable an account, and uses `x` twice to confirm and remove one permanently; Escape cancels an armed removal.
|
|
14
14
|
|
|
15
15
|
Current public model IDs:
|
|
16
16
|
- `antigravity/gemini-3.7-flash`
|
|
@@ -71,6 +71,7 @@ function compactGroup(label: string, entries: AntigravityQuotaEntry[]) {
|
|
|
71
71
|
export class AntigravityDashboard implements Component {
|
|
72
72
|
private selected = 0;
|
|
73
73
|
private refreshing = false;
|
|
74
|
+
private pendingRemoval: { id: string; label: string } | undefined;
|
|
74
75
|
private closed = false;
|
|
75
76
|
private readonly tui: TUI;
|
|
76
77
|
private readonly theme: Theme;
|
|
@@ -132,21 +133,36 @@ export class AntigravityDashboard implements Component {
|
|
|
132
133
|
handleInput(data: string) {
|
|
133
134
|
const accounts = this.snapshot.accounts;
|
|
134
135
|
if (this.keys.matches(data, "tui.select.cancel")) {
|
|
136
|
+
if (this.pendingRemoval) {
|
|
137
|
+
this.pendingRemoval = undefined;
|
|
138
|
+
this.tui.requestRender();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
135
141
|
this.closed = true;
|
|
136
142
|
this.done();
|
|
137
143
|
return;
|
|
138
144
|
}
|
|
139
145
|
if (this.keys.matches(data, "tui.select.up") || data === "k") {
|
|
146
|
+
this.pendingRemoval = undefined;
|
|
140
147
|
if (accounts.length) this.selected = (this.selected - 1 + accounts.length) % accounts.length;
|
|
141
148
|
} else if (this.keys.matches(data, "tui.select.down") || data === "j") {
|
|
149
|
+
this.pendingRemoval = undefined;
|
|
142
150
|
if (accounts.length) this.selected = (this.selected + 1) % accounts.length;
|
|
143
151
|
} else if (data === "r") {
|
|
152
|
+
this.pendingRemoval = undefined;
|
|
144
153
|
this.startRefresh(true);
|
|
145
154
|
} else if ((data === "d" || data === "x") && accounts[this.selected] && !this.refreshing) {
|
|
146
155
|
const account = accounts[this.selected]!;
|
|
156
|
+
const label = account.email || `account-${account.id.slice(0, 6)}`;
|
|
157
|
+
if (data === "x" && this.pendingRemoval?.id !== account.id) {
|
|
158
|
+
this.pendingRemoval = { id: account.id, label };
|
|
159
|
+
this.tui.requestRender();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.pendingRemoval = undefined;
|
|
147
163
|
this.refreshing = true;
|
|
148
164
|
const action = data === "x"
|
|
149
|
-
? this.removeAccount(account.id,
|
|
165
|
+
? this.removeAccount(account.id, label)
|
|
150
166
|
: this.toggleAccount(account.id, account.disabled === true);
|
|
151
167
|
void action
|
|
152
168
|
.then((snapshot) => this.replaceSnapshot(snapshot))
|
|
@@ -172,9 +188,11 @@ export class AntigravityDashboard implements Component {
|
|
|
172
188
|
const active = accounts.filter((account) => account.active).length;
|
|
173
189
|
const right = this.refreshing
|
|
174
190
|
? this.theme.fg("warning", "refreshing…")
|
|
175
|
-
: this.
|
|
176
|
-
? this.theme.fg("warning", oneLine(this.
|
|
177
|
-
: this.
|
|
191
|
+
: this.pendingRemoval
|
|
192
|
+
? this.theme.fg("warning", `press x again to remove ${oneLine(this.pendingRemoval.label)}`)
|
|
193
|
+
: this.snapshot.warning
|
|
194
|
+
? this.theme.fg("warning", oneLine(this.snapshot.warning))
|
|
195
|
+
: this.theme.fg("muted", `${active}/${accounts.length} active · ${this.snapshot.modelCount} models`);
|
|
178
196
|
const title = ` ${this.theme.fg("accent", this.theme.bold("◆ ANTIGRAVITY"))} ${this.theme.fg("dim", `· ${this.snapshot.authentication}`)}`;
|
|
179
197
|
const lines = width >= 64
|
|
180
198
|
? [joinSides(title, `${right} `, width)]
|
|
@@ -194,7 +212,10 @@ export class AntigravityDashboard implements Component {
|
|
|
194
212
|
for (let row = 0; row < bodyHeight; row++) lines.push(this.theme.fg("border", "│") + padLine(list[row] ?? "", inner) + this.theme.fg("border", "│"));
|
|
195
213
|
}
|
|
196
214
|
lines.push(frameBottom(this.theme, width));
|
|
197
|
-
|
|
215
|
+
const controls = this.pendingRemoval
|
|
216
|
+
? `${this.theme.fg("warning", " x")} ${this.theme.fg("warning", "confirm permanent removal")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "cancel")}`
|
|
217
|
+
: `${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`;
|
|
218
|
+
lines.push(truncateToWidth(controls, width, ""));
|
|
198
219
|
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
199
220
|
}
|
|
200
221
|
|
|
@@ -135,9 +135,7 @@ export default function antigravityProviderExtension(pi: ExtensionAPI) {
|
|
|
135
135
|
if (enabled) await refreshAntigravityQuotas({ force: true, signal: ctx.signal });
|
|
136
136
|
return dashboardSnapshot(ctx);
|
|
137
137
|
},
|
|
138
|
-
async (id
|
|
139
|
-
const confirmed = await ctx.ui.confirm("Remove Antigravity account?", `Permanently remove ${label} from this Pi installation?`);
|
|
140
|
-
if (!confirmed) return dashboardSnapshot(ctx);
|
|
138
|
+
async (id) => {
|
|
141
139
|
removeAntigravityAccount(id);
|
|
142
140
|
await reconcileStoredCredential(ctx);
|
|
143
141
|
return dashboardSnapshot(ctx);
|
|
@@ -10,7 +10,7 @@ Session-scoped background pseudo-terminals for Pi. The extension combines Codex-
|
|
|
10
10
|
- `list_terminals` — list running and settled terminals.
|
|
11
11
|
- `stop_terminal` — stop complete process groups with TERM-to-KILL escalation.
|
|
12
12
|
|
|
13
|
-
Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with bounded output guaranteed in model context and never rendered as user-authored or follow-up input, so the parent can continue other work or end its turn rather than poll.
|
|
13
|
+
Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Terminal lifecycle tools render as compact main-chat cards with expandable output, and running terminals appear in the shared bounded **Active work** dock with elapsed time and the latest output line. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement stays in a private extension queue while the parent is active, then starts one custom-result turn at Pi's safe idle edge with bounded output guaranteed in model context and never rendered as user-authored or follow-up input, so the parent can continue other work or end its turn rather than poll.
|
|
14
14
|
|
|
15
15
|
## User interface
|
|
16
16
|
|
|
@@ -3,23 +3,27 @@ import * as path from "node:path";
|
|
|
3
3
|
import type {
|
|
4
4
|
ExtensionAPI,
|
|
5
5
|
ExtensionCommandContext,
|
|
6
|
+
ExtensionContext,
|
|
6
7
|
ExtensionUIContext,
|
|
7
8
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { formatSize, getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
10
11
|
import { Type } from "typebox";
|
|
12
|
+
import { clearActivitySource, setActivitySource } from "../shared/activity-dock.ts";
|
|
11
13
|
import { settlementDelivery } from "../shared/settlement-delivery.ts";
|
|
12
|
-
import {
|
|
14
|
+
import { toolCallCard, toolResultCard } from "../shared/tool-card.ts";
|
|
15
|
+
import { formatDurationMs, oneLine, sanitizeTerminalText, stateLabel } from "../shared/tui-dashboard.ts";
|
|
13
16
|
import { TerminalManager, MAX_RUNNING_TERMINALS } from "./src/manager.ts";
|
|
14
17
|
import {
|
|
15
18
|
formatCompletion,
|
|
16
19
|
formatReadResult,
|
|
17
20
|
formatTerminal,
|
|
18
21
|
} from "./src/presentation.ts";
|
|
19
|
-
import type
|
|
22
|
+
import { terminalElapsedMs, type TerminalReadResult, type TerminalSnapshot } from "./src/types.ts";
|
|
20
23
|
import { openTerminalDashboard } from "./src/ui.ts";
|
|
21
24
|
|
|
22
25
|
const STATUS_KEY = "background-terminals";
|
|
26
|
+
const ACTIVITY_SOURCE = "background-terminals";
|
|
23
27
|
|
|
24
28
|
function titleFrom(command: string, title?: string): string {
|
|
25
29
|
const requested = oneLine(title ?? "").slice(0, 100);
|
|
@@ -39,6 +43,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
39
43
|
const deliverSettlement = settlementDelivery(pi);
|
|
40
44
|
let manager: TerminalManager | undefined;
|
|
41
45
|
let ui: ExtensionUIContext | undefined;
|
|
46
|
+
let activityContext: ExtensionContext | undefined;
|
|
42
47
|
let unsubscribe: (() => void) | undefined;
|
|
43
48
|
let lastStatus = "";
|
|
44
49
|
const pendingResults = new Map<string, TerminalSnapshot>();
|
|
@@ -79,6 +84,21 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
79
84
|
const next = running > 0
|
|
80
85
|
? `${running} background terminal${running === 1 ? "" : "s"} · /term`
|
|
81
86
|
: "";
|
|
87
|
+
if (activityContext) {
|
|
88
|
+
setActivitySource(activityContext, ACTIVITY_SOURCE, terminals
|
|
89
|
+
.filter((terminal) => terminal.status === "running")
|
|
90
|
+
.map((terminal) => {
|
|
91
|
+
const latest = sanitizeTerminalText(terminal.output.text).split(/\r?\n/).filter(Boolean).at(-1);
|
|
92
|
+
return {
|
|
93
|
+
id: terminal.id,
|
|
94
|
+
label: "Terminal",
|
|
95
|
+
title: terminal.title,
|
|
96
|
+
detail: `${formatDurationMs(terminalElapsedMs(terminal))}${latest ? ` · ${oneLine(latest)}` : ""}`,
|
|
97
|
+
state: "active" as const,
|
|
98
|
+
priority: 50,
|
|
99
|
+
};
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
82
102
|
if (next === lastStatus) return;
|
|
83
103
|
lastStatus = next;
|
|
84
104
|
if (!next) ui.setStatus(STATUS_KEY, undefined);
|
|
@@ -108,7 +128,11 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
108
128
|
}
|
|
109
129
|
|
|
110
130
|
pi.on("session_start", (_event, ctx) => {
|
|
111
|
-
if (ctx.hasUI)
|
|
131
|
+
if (ctx.hasUI) {
|
|
132
|
+
ui = ctx.ui;
|
|
133
|
+
activityContext = ctx;
|
|
134
|
+
updateStatus();
|
|
135
|
+
}
|
|
112
136
|
});
|
|
113
137
|
|
|
114
138
|
pi.on("session_shutdown", async () => {
|
|
@@ -118,6 +142,8 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
118
142
|
unsubscribe?.();
|
|
119
143
|
unsubscribe = undefined;
|
|
120
144
|
ui?.setStatus(STATUS_KEY, undefined);
|
|
145
|
+
if (activityContext) clearActivitySource(activityContext, ACTIVITY_SOURCE);
|
|
146
|
+
activityContext = undefined;
|
|
121
147
|
ui = undefined;
|
|
122
148
|
lastStatus = "";
|
|
123
149
|
const closing = manager;
|
|
@@ -236,6 +262,15 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
236
262
|
},
|
|
237
263
|
};
|
|
238
264
|
},
|
|
265
|
+
renderCall(args, theme) {
|
|
266
|
+
return new Text(toolCallCard(theme, "terminal read", args.id, `cursor ${args.cursor ?? "tail"}`), 0, 0);
|
|
267
|
+
},
|
|
268
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
269
|
+
if (isPartial) return new Text(theme.fg("warning", "Reading terminal…"), 0, 0);
|
|
270
|
+
const details = result.details as { id?: string; status?: string; cursor?: number; omittedBytes?: number } | undefined;
|
|
271
|
+
const state = details?.status === "failed" ? "error" : details?.status === "running" ? "active" : "success";
|
|
272
|
+
return new Text(toolResultCard(result, expanded, theme, state, details?.id ?? "terminal", `${details?.status ?? "unknown"} · cursor ${details?.cursor ?? "?"}${details?.omittedBytes ? ` · ${formatSize(details.omittedBytes)} omitted` : ""}`), 0, 0);
|
|
273
|
+
},
|
|
239
274
|
});
|
|
240
275
|
|
|
241
276
|
pi.registerTool({
|
|
@@ -273,6 +308,16 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
273
308
|
},
|
|
274
309
|
};
|
|
275
310
|
},
|
|
311
|
+
renderCall(args, theme) {
|
|
312
|
+
const input = args.input === "\u0003" ? "Ctrl+C" : `${Array.from(args.input ?? "").length} chars${args.press_enter === false ? "" : " + Enter"}`;
|
|
313
|
+
return new Text(toolCallCard(theme, "terminal input", args.id, input), 0, 0);
|
|
314
|
+
},
|
|
315
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
316
|
+
if (isPartial) return new Text(theme.fg("warning", "Sending terminal input…"), 0, 0);
|
|
317
|
+
const details = result.details as { id?: string; status?: string; cursor?: number } | undefined;
|
|
318
|
+
const state = details?.status === "failed" ? "error" : details?.status === "running" ? "active" : "success";
|
|
319
|
+
return new Text(toolResultCard(result, expanded, theme, state, details?.id ?? "terminal", `${details?.status ?? "unknown"} · cursor ${details?.cursor ?? "?"}`), 0, 0);
|
|
320
|
+
},
|
|
276
321
|
});
|
|
277
322
|
|
|
278
323
|
pi.registerTool({
|
|
@@ -287,6 +332,15 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
287
332
|
details: { terminals: terminals.map((terminal) => ({ id: terminal.id, title: terminal.title, status: terminal.status, pid: terminal.pid })) },
|
|
288
333
|
};
|
|
289
334
|
},
|
|
335
|
+
renderCall(_args, theme) {
|
|
336
|
+
return new Text(toolCallCard(theme, "terminals", "list managed PTYs"), 0, 0);
|
|
337
|
+
},
|
|
338
|
+
renderResult(result, { expanded }, theme) {
|
|
339
|
+
const details = result.details as { terminals?: Array<{ status?: string }> } | undefined;
|
|
340
|
+
const terminals = details?.terminals ?? [];
|
|
341
|
+
const running = terminals.filter((terminal) => terminal.status === "running").length;
|
|
342
|
+
return new Text(toolResultCard(result, expanded, theme, running ? "active" : "muted", "terminals", `${terminals.length} tracked · ${running} running`), 0, 0);
|
|
343
|
+
},
|
|
290
344
|
});
|
|
291
345
|
|
|
292
346
|
pi.registerTool({
|
|
@@ -305,6 +359,16 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
305
359
|
details: { terminals: terminals.map((terminal) => ({ id: terminal.id, status: terminal.status, exitCode: terminal.exitCode })) },
|
|
306
360
|
};
|
|
307
361
|
},
|
|
362
|
+
renderCall(args, theme) {
|
|
363
|
+
return new Text(toolCallCard(theme, "terminal stop", `${args.ids?.length ?? 0} terminal${args.ids?.length === 1 ? "" : "s"}`, (args.ids ?? []).join(", ")), 0, 0);
|
|
364
|
+
},
|
|
365
|
+
renderResult(result, { expanded, isPartial }, theme) {
|
|
366
|
+
if (isPartial) return new Text(theme.fg("warning", "Stopping terminals…"), 0, 0);
|
|
367
|
+
const details = result.details as { terminals?: Array<{ status?: string }> } | undefined;
|
|
368
|
+
const terminals = details?.terminals ?? [];
|
|
369
|
+
const failed = terminals.some((terminal) => terminal.status === "failed");
|
|
370
|
+
return new Text(toolResultCard(result, expanded, theme, failed ? "error" : "success", "terminals stopped", `${terminals.length} terminal${terminals.length === 1 ? "" : "s"}`), 0, 0);
|
|
371
|
+
},
|
|
308
372
|
});
|
|
309
373
|
|
|
310
374
|
pi.registerMessageRenderer(
|
|
@@ -14,4 +14,4 @@ Alternatively choose Pi's top-level **Use an API key** authentication method to
|
|
|
14
14
|
|
|
15
15
|
Use `/logout` to remove the active Factory authentication selection. The configured key file is intentionally not deleted by logout.
|
|
16
16
|
|
|
17
|
-
Open `/factory` to inspect every account, its Standard/Core usage, and rotation status. File-backed keys remain visible when disabled: select one and press `d` to enable/disable it, or press `x`
|
|
17
|
+
Open `/factory` to inspect every account, its Standard/Core usage, and rotation status. File-backed keys remain visible when disabled: select one and press `d` to enable/disable it, or press `x` twice to confirm permanent removal from `api-keys.json`; Escape cancels an armed removal. Environment-provided keys are read-only. For a smoke test, use the current `factory/kimi-k3` model.
|
|
@@ -59,7 +59,7 @@ There are no `-oauth` or `-api-key` model duplicates. The user-curated catalog e
|
|
|
59
59
|
/factory
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
`/factory` opens a full-width account dashboard combining provider status, authentication, Droid/model metadata, rotation cooldowns, and separate Standard and Droid Core usage for every credential. Percentages are explicitly labeled as **used**. Navigate with the arrow keys or `j`/`k`, press `r` to force-refresh every account, use `d` to reversibly enable or disable a rotating file-backed key,
|
|
62
|
+
`/factory` opens a full-width account dashboard combining provider status, authentication, Droid/model metadata, rotation cooldowns, and separate Standard and Droid Core usage for every credential. Percentages are explicitly labeled as **used**. Navigate with the arrow keys or `j`/`k`, press `r` to force-refresh every account, use `d` to reversibly enable or disable a rotating file-backed key, press `x` twice to confirm and remove one permanently, and press Escape to cancel an armed removal or close the dashboard. Environment variables and single credentials managed by Pi auth remain read-only in this dashboard.
|
|
63
63
|
|
|
64
64
|
Usage is never estimated from Pi token counts: Factory's API already applies model multipliers and cache-hit discounts. Cached records refresh at most every 15 minutes during normal sessions and after Factory runs; the manual command is the explicit force-refresh path.
|
|
65
65
|
|
|
@@ -119,6 +119,7 @@ function stateColor(state: string) {
|
|
|
119
119
|
export class FactoryDashboard implements Component {
|
|
120
120
|
private selected = 0;
|
|
121
121
|
private refreshing = false;
|
|
122
|
+
private pendingRemoval: { id: string; label: string } | undefined;
|
|
122
123
|
private closed = false;
|
|
123
124
|
private readonly tui: TUI;
|
|
124
125
|
private readonly theme: Theme;
|
|
@@ -190,20 +191,34 @@ export class FactoryDashboard implements Component {
|
|
|
190
191
|
handleInput(data: string) {
|
|
191
192
|
const accounts = this.snapshot.accounts;
|
|
192
193
|
if (this.keys.matches(data, "tui.select.cancel")) {
|
|
194
|
+
if (this.pendingRemoval) {
|
|
195
|
+
this.pendingRemoval = undefined;
|
|
196
|
+
this.tui.requestRender();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
193
199
|
this.closed = true;
|
|
194
200
|
this.done();
|
|
195
201
|
return;
|
|
196
202
|
}
|
|
197
203
|
if (this.keys.matches(data, "tui.select.up") || data === "k") {
|
|
204
|
+
this.pendingRemoval = undefined;
|
|
198
205
|
if (accounts.length) {
|
|
199
206
|
this.selected = (this.selected - 1 + accounts.length) % accounts.length;
|
|
200
207
|
}
|
|
201
208
|
} else if (this.keys.matches(data, "tui.select.down") || data === "j") {
|
|
209
|
+
this.pendingRemoval = undefined;
|
|
202
210
|
if (accounts.length) this.selected = (this.selected + 1) % accounts.length;
|
|
203
211
|
} else if (data === "r") {
|
|
212
|
+
this.pendingRemoval = undefined;
|
|
204
213
|
this.startRefresh(true);
|
|
205
214
|
} else if ((data === "d" || data === "x") && accounts[this.selected]?.editable && !this.refreshing) {
|
|
206
215
|
const account = accounts[this.selected]!;
|
|
216
|
+
if (data === "x" && this.pendingRemoval?.id !== account.id) {
|
|
217
|
+
this.pendingRemoval = { id: account.id, label: account.label };
|
|
218
|
+
this.tui.requestRender();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.pendingRemoval = undefined;
|
|
207
222
|
this.refreshing = true;
|
|
208
223
|
const action = data === "x"
|
|
209
224
|
? this.removeAccount(account.id, account.label)
|
|
@@ -232,9 +247,11 @@ export class FactoryDashboard implements Component {
|
|
|
232
247
|
const ready = `${this.snapshot.active}/${this.snapshot.configured} active`;
|
|
233
248
|
const headerRight = this.refreshing
|
|
234
249
|
? this.theme.fg("warning", "refreshing…")
|
|
235
|
-
: this.
|
|
236
|
-
? this.theme.fg("warning", oneLine(this.
|
|
237
|
-
: this.
|
|
250
|
+
: this.pendingRemoval
|
|
251
|
+
? this.theme.fg("warning", `press x again to remove ${oneLine(this.pendingRemoval.label)}`)
|
|
252
|
+
: this.snapshot.warning
|
|
253
|
+
? this.theme.fg("warning", oneLine(this.snapshot.warning))
|
|
254
|
+
: this.theme.fg("muted", `${ready} · ${this.snapshot.modelCount} models · Droid ${this.snapshot.version}`);
|
|
238
255
|
const title = ` ${this.theme.fg("accent", this.theme.bold("◆ FACTORY"))} ${this.theme.fg("dim", `· ${this.snapshot.authentication}`)}`;
|
|
239
256
|
const lines = width >= 60
|
|
240
257
|
? [joinSides(title, `${headerRight} `, width)]
|
|
@@ -268,13 +285,10 @@ export class FactoryDashboard implements Component {
|
|
|
268
285
|
}
|
|
269
286
|
}
|
|
270
287
|
lines.push(frameBottom(this.theme, width));
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"",
|
|
276
|
-
),
|
|
277
|
-
);
|
|
288
|
+
const controls = this.pendingRemoval
|
|
289
|
+
? `${this.theme.fg("warning", " x")} ${this.theme.fg("warning", "confirm permanent removal")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "cancel")}`
|
|
290
|
+
: `${this.theme.fg("accent", " ↑↓ / j k")} ${this.theme.fg("dim", "select")} ${this.theme.fg("accent", "r")} ${this.theme.fg("dim", "refresh all")} ${this.theme.fg("accent", "d")} ${this.theme.fg("dim", "enable/disable")} ${this.theme.fg("accent", "x")} ${this.theme.fg("dim", "remove")} ${this.theme.fg("accent", "esc")} ${this.theme.fg("dim", "close")}`;
|
|
291
|
+
lines.push(truncateToWidth(controls, width, ""));
|
|
278
292
|
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
279
293
|
}
|
|
280
294
|
|
|
@@ -223,9 +223,7 @@ export default async function factoryExtension(pi: ExtensionAPI) {
|
|
|
223
223
|
if (enabled) await refreshLimits(ctx, true);
|
|
224
224
|
return dashboardSnapshot(ctx);
|
|
225
225
|
},
|
|
226
|
-
async (id
|
|
227
|
-
const confirmed = await ctx.ui.confirm("Remove Factory account?", `Permanently remove ${label} from the rotating-key file?`);
|
|
228
|
-
if (!confirmed) return dashboardSnapshot(ctx);
|
|
226
|
+
async (id) => {
|
|
229
227
|
if (!removeFactoryApiKey(id)) throw new Error("This Factory credential is not editable from the rotating-key file.");
|
|
230
228
|
return dashboardSnapshot(ctx);
|
|
231
229
|
},
|
|
@@ -18,15 +18,13 @@ const REFRESH_DEBOUNCE_MS = 150;
|
|
|
18
18
|
function statusText(ui: ExtensionUIContext, summary: GitSummary): string | undefined {
|
|
19
19
|
if (!summary.isRepository || !summary.branch) return undefined;
|
|
20
20
|
const changed = summary.changedFiles === 0
|
|
21
|
-
? ui.theme.fg("muted", "
|
|
22
|
-
: ui.theme.fg(
|
|
23
|
-
"warning",
|
|
24
|
-
`${summary.changedFiles} changed ${summary.changedFiles === 1 ? "file" : "files"}`,
|
|
25
|
-
);
|
|
21
|
+
? ui.theme.fg("muted", "✓")
|
|
22
|
+
: ui.theme.fg("warning", `+${summary.changedFiles}`);
|
|
26
23
|
const pr = summary.pullRequest
|
|
27
24
|
? ` · ${ui.theme.fg("accent", `PR #${summary.pullRequest.number}${summary.pullRequest.draft ? " draft" : ""}`)}`
|
|
28
25
|
: "";
|
|
29
|
-
|
|
26
|
+
const command = summary.changedFiles > 0 || summary.pullRequest ? ` · ${ui.theme.fg("dim", "/lg")}` : "";
|
|
27
|
+
return `${ui.theme.fg("muted", "git")} ${ui.theme.fg("accent", summary.branch)} ${changed}${pr}${command}`;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export default function gitInfoExtension(pi: ExtensionAPI) {
|
|
@@ -31,7 +31,7 @@ Each project has up to 10 concurrent worker, explorer, or reviewer sessions. The
|
|
|
31
31
|
|
|
32
32
|
## Dashboard
|
|
33
33
|
|
|
34
|
-
`/orchestration` opens the full-screen operations dashboard.
|
|
34
|
+
Orchestration creation and inspection render as compact main-chat cards with expandable detail. Active runs appear in the shared bounded **Active work** dock; plan-ready and blocked runs are prioritized as attention states. `/orchestration` opens the full-screen operations dashboard. Dashboard actions close the overlay before opening editors, settings, or confirmation prompts, then return to the live dashboard so nested UI cannot stall.
|
|
35
35
|
|
|
36
36
|
- `j` / `k`: select a run
|
|
37
37
|
- `Enter`: inspect tasks and reviews
|
|
@@ -40,7 +40,7 @@ Each project has up to 10 concurrent worker, explorer, or reviewer sessions. The
|
|
|
40
40
|
- `a`: approve the initial plan
|
|
41
41
|
- `f`: give plan feedback
|
|
42
42
|
- `p`: pause, resume, or recover
|
|
43
|
-
- `x`:
|
|
43
|
+
- `x`: leave the dashboard and confirm cancellation while preserving artifacts
|
|
44
44
|
- `Esc`: go back or close
|
|
45
45
|
|
|
46
46
|
Interrupted runs recover in a paused state and require explicit resume. Run state is machine-local under Pi's active agent directory at `orchestration/runs/<run-id>/`.
|
|
@@ -12,81 +12,96 @@ import {
|
|
|
12
12
|
import type { OrchestrationEngine } from "./engine.ts";
|
|
13
13
|
import type { OrchestrationRun } from "./types.ts";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
15
|
+
export type OrchestrationDashboardAction =
|
|
16
|
+
| { kind: "close" }
|
|
17
|
+
| { kind: "create" }
|
|
18
|
+
| { kind: "settings" }
|
|
19
|
+
| { kind: "feedback"; id: string }
|
|
20
|
+
| { kind: "approve"; id: string }
|
|
21
|
+
| { kind: "toggle-pause"; id: string }
|
|
22
|
+
| { kind: "cancel"; id: string };
|
|
23
23
|
|
|
24
24
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
25
25
|
|
|
26
26
|
class OrchestrationDashboard {
|
|
27
27
|
private selected = 0;
|
|
28
|
+
private selectedId?: string;
|
|
28
29
|
private detail = false;
|
|
29
30
|
private unsubscribe: () => void;
|
|
30
31
|
private readonly tui: TUI;
|
|
31
32
|
private readonly theme: Theme;
|
|
32
33
|
private readonly keys: KeybindingsManager;
|
|
33
34
|
private readonly engine: OrchestrationEngine;
|
|
34
|
-
private readonly
|
|
35
|
-
private
|
|
35
|
+
private readonly done: (action: OrchestrationDashboardAction) => void;
|
|
36
|
+
private closed = false;
|
|
36
37
|
|
|
37
38
|
constructor(
|
|
38
39
|
tui: TUI,
|
|
39
40
|
theme: Theme,
|
|
40
41
|
keys: KeybindingsManager,
|
|
41
42
|
engine: OrchestrationEngine,
|
|
42
|
-
|
|
43
|
-
close: () => void,
|
|
43
|
+
done: (action: OrchestrationDashboardAction) => void,
|
|
44
44
|
) {
|
|
45
45
|
this.tui = tui;
|
|
46
46
|
this.theme = theme;
|
|
47
47
|
this.keys = keys;
|
|
48
48
|
this.engine = engine;
|
|
49
|
-
this.
|
|
50
|
-
this.close = close;
|
|
49
|
+
this.done = done;
|
|
51
50
|
this.unsubscribe = engineChangeSubscription(engine, () => tui.requestRender());
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
private close(action: OrchestrationDashboardAction) {
|
|
54
|
+
if (this.closed) return;
|
|
55
|
+
this.closed = true;
|
|
55
56
|
this.unsubscribe();
|
|
57
|
+
this.done(action);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
dispose() {
|
|
61
|
+
if (!this.closed) this.unsubscribe();
|
|
62
|
+
this.closed = true;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
invalidate() {}
|
|
59
66
|
|
|
67
|
+
private reconcileSelection(runs: OrchestrationRun[]) {
|
|
68
|
+
const stable = this.selectedId ? runs.findIndex((run) => run.id === this.selectedId) : -1;
|
|
69
|
+
this.selected = stable >= 0 ? stable : Math.min(this.selected, Math.max(0, runs.length - 1));
|
|
70
|
+
this.selectedId = runs[this.selected]?.id;
|
|
71
|
+
}
|
|
72
|
+
|
|
60
73
|
handleInput(data: string) {
|
|
61
74
|
const runs = this.engine.list();
|
|
75
|
+
this.reconcileSelection(runs);
|
|
62
76
|
if (matchesKey(data, Key.escape)) {
|
|
63
77
|
if (this.detail) this.detail = false;
|
|
64
78
|
else {
|
|
65
|
-
this.
|
|
66
|
-
this.close();
|
|
79
|
+
this.close({ kind: "close" });
|
|
67
80
|
return;
|
|
68
81
|
}
|
|
69
82
|
} else if (!this.detail && (this.keys.matches(data, "tui.select.up") || data === "k")) {
|
|
70
83
|
this.selected = Math.max(0, this.selected - 1);
|
|
84
|
+
this.selectedId = runs[this.selected]?.id;
|
|
71
85
|
} else if (!this.detail && (this.keys.matches(data, "tui.select.down") || data === "j")) {
|
|
72
86
|
this.selected = Math.min(Math.max(0, runs.length - 1), this.selected + 1);
|
|
87
|
+
this.selectedId = runs[this.selected]?.id;
|
|
73
88
|
} else if (!this.detail && this.keys.matches(data, "tui.select.confirm") && runs[this.selected]) {
|
|
74
89
|
this.detail = true;
|
|
75
|
-
} else if (data === "n") this.
|
|
76
|
-
else if (data === "s") this.
|
|
90
|
+
} else if (data === "n") this.close({ kind: "create" });
|
|
91
|
+
else if (data === "s") this.close({ kind: "settings" });
|
|
77
92
|
else if (this.detail && runs[this.selected]) {
|
|
78
93
|
const run = runs[this.selected];
|
|
79
|
-
if (data === "a" && run.status === "awaiting-approval") this.
|
|
80
|
-
else if (data === "f" && run.status === "awaiting-approval") this.
|
|
81
|
-
else if (data === "p") this.
|
|
82
|
-
else if (data === "x") this.
|
|
94
|
+
if (data === "a" && run.status === "awaiting-approval") this.close({ kind: "approve", id: run.id });
|
|
95
|
+
else if (data === "f" && run.status === "awaiting-approval") this.close({ kind: "feedback", id: run.id });
|
|
96
|
+
else if (data === "p") this.close({ kind: "toggle-pause", id: run.id });
|
|
97
|
+
else if (data === "x") this.close({ kind: "cancel", id: run.id });
|
|
83
98
|
}
|
|
84
99
|
this.tui.requestRender();
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
render(width: number) {
|
|
88
103
|
const runs = this.engine.list();
|
|
89
|
-
this.
|
|
104
|
+
this.reconcileSelection(runs);
|
|
90
105
|
const lines = this.detail && runs[this.selected]
|
|
91
106
|
? this.renderDetail(runs[this.selected], width)
|
|
92
107
|
: this.renderList(runs, width);
|
|
@@ -109,7 +124,12 @@ class OrchestrationDashboard {
|
|
|
109
124
|
this.theme.fg("borderMuted", "─".repeat(Math.max(0, width))),
|
|
110
125
|
];
|
|
111
126
|
if (!runs.length) lines.push(this.theme.fg("muted", "No runs yet. Press n to create one."));
|
|
112
|
-
|
|
127
|
+
const rows = this.tui.terminal.rows || 30;
|
|
128
|
+
const visibleCount = Math.max(1, Math.floor((rows - 5) / 2));
|
|
129
|
+
const start = Math.min(Math.max(0, this.selected - Math.floor(visibleCount / 2)), Math.max(0, runs.length - visibleCount));
|
|
130
|
+
const visible = runs.slice(start, start + visibleCount);
|
|
131
|
+
for (const [offset, run] of visible.entries()) {
|
|
132
|
+
const index = start + offset;
|
|
113
133
|
const marker = index === this.selected ? this.theme.fg("accent", "❯") : " ";
|
|
114
134
|
const complete = run.tasks.filter((task) => task.status === "completed").length;
|
|
115
135
|
const color = run.status === "completed" ? "success" : run.status === "blocked" ? "error" : run.status === "running" ? "warning" : "muted";
|
|
@@ -122,8 +142,13 @@ class OrchestrationDashboard {
|
|
|
122
142
|
);
|
|
123
143
|
lines.push(` ${this.theme.fg("dim", `${run.id} · ${run.cwd}`)}`);
|
|
124
144
|
}
|
|
145
|
+
if (start > 0) lines.splice(2, 0, this.theme.fg("dim", ` ↑ ${start} more`));
|
|
146
|
+
const below = runs.length - start - visible.length;
|
|
147
|
+
if (below > 0) lines.push(this.theme.fg("dim", ` ↓ ${below} more`));
|
|
125
148
|
lines.push("", this.theme.fg("dim", "j/k select · enter open · n new · s settings · esc close"));
|
|
126
|
-
|
|
149
|
+
const maxRows = Math.max(8, rows);
|
|
150
|
+
if (lines.length <= maxRows) return lines;
|
|
151
|
+
return [...lines.slice(0, maxRows - 2), this.theme.fg("dim", "… more runs"), lines.at(-1)!];
|
|
127
152
|
}
|
|
128
153
|
|
|
129
154
|
private renderDetail(run: OrchestrationRun, width: number) {
|
|
@@ -161,7 +186,9 @@ class OrchestrationDashboard {
|
|
|
161
186
|
"esc back",
|
|
162
187
|
].filter(Boolean).join(" · ");
|
|
163
188
|
lines.push("", this.theme.fg("dim", controls));
|
|
164
|
-
|
|
189
|
+
const maxRows = Math.max(8, (this.tui.terminal.rows || 30) - 2);
|
|
190
|
+
if (lines.length <= maxRows) return lines;
|
|
191
|
+
return [...lines.slice(0, maxRows - 2), this.theme.fg("dim", "… more tasks in run state"), lines.at(-1)!];
|
|
165
192
|
}
|
|
166
193
|
}
|
|
167
194
|
|
|
@@ -179,11 +206,10 @@ function engineChangeSubscription(engine: OrchestrationEngine, listener: () => v
|
|
|
179
206
|
export async function openOrchestrationDashboard(
|
|
180
207
|
ctx: ExtensionContext,
|
|
181
208
|
engine: OrchestrationEngine,
|
|
182
|
-
actions: DashboardActions,
|
|
183
209
|
) {
|
|
184
|
-
|
|
210
|
+
return ctx.ui.custom<OrchestrationDashboardAction>(
|
|
185
211
|
(tui, theme, keys, done) =>
|
|
186
|
-
new OrchestrationDashboard(tui, theme, keys, engine,
|
|
212
|
+
new OrchestrationDashboard(tui, theme, keys, engine, done),
|
|
187
213
|
{
|
|
188
214
|
overlay: true,
|
|
189
215
|
overlayOptions: { anchor: "center", width: "100%", maxHeight: "100%" },
|