@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-fork.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/CHANGELOG.md +67 -0
- package/dist/src/agent-execution.js +36 -51
- package/dist/src/background-widget.d.ts +56 -0
- package/dist/src/background-widget.js +22 -15
- package/dist/src/decoders.js +2 -1
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-view.d.ts +16 -1
- package/dist/src/host-view.js +109 -42
- package/dist/src/host.js +57 -24
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +867 -545
- package/dist/src/index.js.map +3 -3
- package/dist/src/registry.js +2 -1
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +325 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +253 -0
- package/dist/src/trajectory.d.ts +29 -4
- package/dist/src/trajectory.js +58 -3
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.js +10 -0
- package/dist/src/validation.d.ts +5 -17
- package/dist/src/validation.js +9 -458
- package/dist/subagents/index.js +1132 -694
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -0
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +2 -1
- package/dist/subagents/src/manager.js +29 -10
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +23 -2
- package/dist/subagents/src/view.js +96 -30
- package/dist/trajectory/assets/index.html +347 -231
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +377 -257
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +347 -231
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +143 -42
- package/dist/trajectory/src/server.js +134 -9
- package/dist/trajectory/src/server.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-execution.ts +36 -42
- package/src/background-widget.ts +25 -18
- package/src/decoders.ts +2 -1
- package/src/host-navigator.ts +58 -14
- package/src/host-view.ts +93 -37
- package/src/host.ts +51 -21
- package/src/index.ts +3 -1
- package/src/registry.ts +2 -1
- package/src/roles.ts +326 -0
- package/src/settings.ts +206 -0
- package/src/trajectory.ts +76 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +8 -1
- package/src/validation.ts +12 -356
- package/subagents/README.md +1 -1
- package/subagents/src/contracts.ts +4 -0
- package/subagents/src/index.ts +3 -2
- package/subagents/src/manager.ts +28 -10
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +97 -28
- package/trajectory/src/assets/index.html +347 -231
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +118 -38
- package/trajectory/src/server.ts +112 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
## Unreleased
|
|
3
3
|
|
|
4
|
+
## [5.17.0] - 2026-09-23
|
|
5
|
+
|
|
6
|
+
### New capabilities
|
|
7
|
+
|
|
8
|
+
- `/subagents` opens the `/workflow` dashboard layout instead of a picker followed by a `key=value` detail dump. The run list sits beside the selected run's details at 80 columns or wider and drills down from list to details to actions below that, with the same configured and vim key hints and page scrolling. While a run is active, the active runs and the selection refresh every second. Details show the run ID and the shared agent fields in the `/workflow` agent order; the prompt, live system prompt, and result open in the editor, as in `/workflow`, and editor and copy actions leave the action menu open. A running run shows its live activity through the new opt-in `includeActivity` inspection context; `subagents_inspect` still leaves activity out. A retry selects the new run, and a repeated name carries its short ID in the list, as in the `/workflow` picker. Outside the TUI, the picker rows follow the `/workflow` picker: state glyph, name, state, model, cost, and runtime.
|
|
9
|
+
- `/subagents` gains the `/workflow` run actions that apply to standalone runs: `Delete`, `Delete all completed`, and `Delete all failed` (also in the picker outside the TUI), and `Copy run path`. Stop and deletions ask for confirmation. `SubagentManager` gains an optional `delete(request, context)` that removes a settled record and refuses running runs and runs whose worktree cleanup is pending; it is not a model tool.
|
|
10
|
+
- Background subagents appear below the editor in the workflow widget's frame, one row per run with model, tokens, cost, elapsed time, and quiet or stalled warnings, and each leaves a receipt in the transcript when it settles, like a background workflow run.
|
|
11
|
+
- The `subagents_run` result header shows tokens and cost, like the workflow header.
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- Declining the Stop confirmation in the `/workflow` dashboard no longer leaves Pi stuck. Pi's confirm dialog replaced the dashboard and then restored the editor, so the `/workflow` command never finished: later messages and extension commands such as `/subagents` queued without running, and the workflow widget stayed hidden. The question now appears inside the dashboard, as in `/subagents`; declining keeps the dashboard open on the action menu.
|
|
16
|
+
- A subagent that settled just before its manager was disposed, for example at session shutdown, no longer stays `running` on disk. Disposal skipped the terminal status write, and a new manager in the same Pi process treated the record as owned by a live process, so it kept showing as running and could not be retried.
|
|
17
|
+
|
|
18
|
+
## [5.16.3] - 2026-09-23
|
|
19
|
+
|
|
20
|
+
### Fixes
|
|
21
|
+
|
|
22
|
+
- A live foreground workflow block taller than the terminal no longer snaps pi's scrollback to the bottom on every update. Past `rows - 10` lines the block turns compact: finished phases collapse to one summary line (done, failed, wall time, tokens, cost) plus their failures, and the current phase lists failed, active, and the three latest settled agents with a count of the rest. `ctrl+o` still shows everything, and a finished block keeps the full list.
|
|
23
|
+
|
|
24
|
+
## [5.16.2] - 2026-09-23
|
|
25
|
+
|
|
26
|
+
### New capabilities
|
|
27
|
+
|
|
28
|
+
- Trajectory highlights workflow scripts, JSON values (tool and workflow arguments, environment, and output), and fenced code in messages with a vendored Prism 1.30.0 build (JavaScript, TypeScript, JSON, bash, diff, Python).
|
|
29
|
+
- Trajectory lists runs by newest completion with active runs on top. Each session shows every active run or subagent and at least three in total; the rest sit behind a "+ N more" toggle that is remembered per browser, and the selected entry always stays visible.
|
|
30
|
+
|
|
31
|
+
### Fixes
|
|
32
|
+
|
|
33
|
+
- Pi no longer adopts a Trajectory server that answers on the port without owning the lock, such as an older server whose lock was lost. `/health` now reports the server pid and fingerprint; a mismatching server that reports its pid is replaced, and one without an identity fails the attach with a clear error instead of serving outdated code indefinitely.
|
|
34
|
+
|
|
35
|
+
## [5.16.1] - 2026-09-23
|
|
36
|
+
|
|
37
|
+
### Fixes
|
|
38
|
+
|
|
39
|
+
- Trajectory no longer blanks a live subagent's or agent's events for about a second when its transcript grows between a state poll and the transcript read; the stale reply keeps the cached entries until the newer revision arrives.
|
|
40
|
+
|
|
41
|
+
## [5.16.0] - 2026-09-23
|
|
42
|
+
|
|
43
|
+
### Breaking changes
|
|
44
|
+
|
|
45
|
+
- `extensionSettings.trajectory.themes` is removed; settings files that still set it fail validation. Persisted launch snapshots that carry it still load.
|
|
46
|
+
|
|
47
|
+
### New capabilities
|
|
48
|
+
|
|
49
|
+
- Trajectory has a paper-style look in light and dark versions. It follows the system colour scheme, and a sidebar toggle overrides it per browser. On phones it shows one panel at a time, with Runs, Trace, and Details tabs.
|
|
50
|
+
- The Trajectory inspector shows workflow arguments (bounded for large values) (#300) and an Agent details Output tab (#301).
|
|
51
|
+
|
|
52
|
+
### Performance
|
|
53
|
+
|
|
54
|
+
- Foreground workflow progress bursts collapse to one update per 100ms, and progress reads `state.json` alone instead of state plus snapshot, so Pi no longer re-renders the whole tool block for every agent event.
|
|
55
|
+
- Trajectory ships tool timing only for the run a browser shows and omits timing it already delivered at the same revision. The gantt patches only changed lanes and merges tool calls that overlap on screen.
|
|
56
|
+
- Trajectory publishers poll every 10s while no browser is attached, and hidden tabs skip rendering.
|
|
57
|
+
|
|
58
|
+
### Fixes
|
|
59
|
+
|
|
60
|
+
- A Trajectory browser tab that drops without a WebSocket close frame is released, so viewer counts and pending requests no longer leak.
|
|
61
|
+
- Live Trajectory timing keeps the newest entries within its 64 KB per-agent budget, so a long agent's gantt keeps growing instead of freezing on its first calls.
|
|
62
|
+
- `piewf` `npx` examples use the scoped `@piewf/cli` package.
|
|
63
|
+
|
|
64
|
+
## [5.15.0] - 2026-09-18
|
|
65
|
+
|
|
66
|
+
### New capabilities
|
|
67
|
+
|
|
68
|
+
- Roles are exposed as a standalone `pi-extensible-workflows/roles` subpath (`discoverRoles`, `loadRole`, `resolveRole`) that resolves a role into plain data (model, tools, skills, extensions, prompt, context file scopes, unmatched selectors) without a workflow run; the workflow executor, validation, CLI, and subagents consume the same implementation (#294).
|
|
69
|
+
- `@piewf/cli` adds a `pi-role <role> [pi arguments...]` launcher that resolves a role and starts the regular Pi CLI with the matching model, tools, skills, extensions, and system prompt arguments; remaining arguments are forwarded to Pi (#295).
|
|
70
|
+
|
|
4
71
|
## [5.14.1] - 2026-09-17
|
|
5
72
|
|
|
6
73
|
### Fixes
|
|
@@ -7,7 +7,7 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { Compile } from "typebox/compile";
|
|
8
8
|
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, defineTool, getAgentDir, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
;
|
|
10
|
-
import { SerialLane, assertModelThinking, byPriorityThenName, deepFreeze, errorText, jsonObject, jsonValue, mergeWorkflowExtensionSettings, object,
|
|
10
|
+
import { SerialLane, assertModelThinking, byPriorityThenName, deepFreeze, errorText, jsonObject, jsonValue, mergeWorkflowExtensionSettings, object, resolveModelReference, resourcePatternHasMagic, unmatchedResourcePatterns } from "./utils.js";
|
|
11
11
|
import { SETTLED_AGENT_STATES, WorkflowError, isContextFileScope, zeroAccounting } from "./types.js";
|
|
12
12
|
import { createLiveSessionHandoff } from "./session-handoff.js";
|
|
13
13
|
import { createToolTimingExtension } from "./tool-timing.js";
|
|
@@ -16,6 +16,7 @@ import { createPiRuntimeAgentRunner, isRuntimeAgentProviderError, normalizePiRun
|
|
|
16
16
|
import { defaultWorkflowResultSchema } from "./runtime/workflow-result.js";
|
|
17
17
|
import { validateAgentOptions, validateSchema } from "./validation.js";
|
|
18
18
|
import { canonicalPath } from "./paths.js";
|
|
19
|
+
import { canonicalExtensionSelector, resolveRole } from "./roles.js";
|
|
19
20
|
const localToolTimingExtension = createToolTimingExtension();
|
|
20
21
|
function parseModel(value, fallback, aliases = {}, knownModels, settingsPath) {
|
|
21
22
|
if (!value)
|
|
@@ -36,16 +37,6 @@ const WORKFLOW_HOST_ENTRIES = new Set([
|
|
|
36
37
|
canonicalPath(resolve(workflowPackageRoot, "subagents/index.ts")),
|
|
37
38
|
canonicalPath(resolve(workflowPackageRoot, "dist/subagents/index.js")),
|
|
38
39
|
]);
|
|
39
|
-
function canonicalExtensionSelector(selector, base = process.cwd()) {
|
|
40
|
-
const negated = selector.startsWith("!");
|
|
41
|
-
const body = negated ? selector.slice(1) : selector;
|
|
42
|
-
if (body === "*" || body === "**" || body.startsWith("**/"))
|
|
43
|
-
return selector;
|
|
44
|
-
const resolved = resolve(base, body);
|
|
45
|
-
if (resourcePatternHasMagic(body))
|
|
46
|
-
return `${negated ? "!" : ""}${resolved}`;
|
|
47
|
-
return `${negated ? "!" : ""}${canonicalPath(resolved)}`;
|
|
48
|
-
}
|
|
49
40
|
const WORKFLOW_DIRECTORY = "pi-extensible-workflows";
|
|
50
41
|
function workflowSystemPromptPath(cwd, agentDir, projectTrusted) {
|
|
51
42
|
const projectPath = join(cwd, ".pi", WORKFLOW_DIRECTORY, "SYSTEM.md");
|
|
@@ -205,20 +196,22 @@ async function createLocalPiSessionHandle(input, sessionStartEvent) {
|
|
|
205
196
|
const resolved = await packageManager.resolve();
|
|
206
197
|
const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalPath(path)))];
|
|
207
198
|
const selectorSources = policy.selectorSources;
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
199
|
+
const selection = resolveRole(undefined, {
|
|
200
|
+
cwd: input.cwd,
|
|
201
|
+
selectorSources,
|
|
202
|
+
resources: { extensions: discoveredExtensions },
|
|
203
|
+
});
|
|
204
|
+
const selectedExtensions = selection.selectedExtensions ?? [];
|
|
211
205
|
const extensionPaths = selectedExtensions.filter((path) => !WORKFLOW_HOST_ENTRIES.has(path));
|
|
212
|
-
const unmatchedExtensions = extensionSelectors.filter(({ matching }) => unmatchedResourcePatterns([matching], discoveredExtensions).length > 0).map(({ original }) => original);
|
|
213
206
|
policy.selectedExtensions = selectedExtensions;
|
|
214
|
-
policy.unmatchedExtensions = unmatchedExtensions;
|
|
207
|
+
policy.unmatchedExtensions = selection.unmatchedExtensions ?? [];
|
|
215
208
|
const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
|
|
216
209
|
const updateSkillMatches = (skills) => {
|
|
217
210
|
const names = [...new Set(skills.map(({ name }) => name))];
|
|
218
|
-
const
|
|
219
|
-
const selectedSkills =
|
|
211
|
+
const selected = resolveRole(undefined, { cwd: input.cwd, selectorSources, resources: { skills: names } });
|
|
212
|
+
const selectedSkills = selected.selectedSkills ?? [];
|
|
220
213
|
policy.selectedSkills = selectedSkills;
|
|
221
|
-
policy.unmatchedSkills =
|
|
214
|
+
policy.unmatchedSkills = selected.unmatchedSkills ?? [];
|
|
222
215
|
return new Set(names.filter((name) => !selectedSkills.includes(name)));
|
|
223
216
|
};
|
|
224
217
|
resourceLoader = new DefaultResourceLoader({
|
|
@@ -944,7 +937,6 @@ export class WorkflowAgentExecutor {
|
|
|
944
937
|
const roleDefinition = roleName ? this.root.agentDefinitions?.[roleName] : undefined;
|
|
945
938
|
if (roleName && !roleDefinition)
|
|
946
939
|
throw new WorkflowError("UNKNOWN_AGENT_TYPE", `Unknown agent role: ${roleName}`);
|
|
947
|
-
const candidateTools = inheritedTools !== undefined ? [...inheritedTools] : [...this.root.tools];
|
|
948
940
|
if (inheritedTools === undefined)
|
|
949
941
|
for (const pattern of roleDefinition?.tools ?? []) {
|
|
950
942
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
@@ -956,37 +948,30 @@ export class WorkflowAgentExecutor {
|
|
|
956
948
|
}
|
|
957
949
|
}
|
|
958
950
|
}
|
|
959
|
-
const
|
|
960
|
-
this.root.
|
|
961
|
-
roleDefinition
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
const model = options.modelOverride ?? parseModel(requestedModel, this.root.model, this.root.modelAliases, this.root.knownModels ?? this.root.availableModels, this.root.settingsPath);
|
|
984
|
-
const availableModels = this.root.knownModels ?? this.root.availableModels ?? new Set([modelCapability(this.root.model)]);
|
|
985
|
-
if (!availableModels.has(modelCapability(model)))
|
|
986
|
-
throw new WorkflowError("UNKNOWN_MODEL", `Unknown model${requestedModel ? ` ${requestedModel} resolved to ${modelCapability(model)}` : ""}${this.root.settingsPath ? ` (settings: ${this.root.settingsPath})` : ""}`);
|
|
987
|
-
const overrideSystemPrompt = roleDefinition?.overrideSystemPrompt === true;
|
|
988
|
-
const contextFiles = options.contextFiles ?? roleDefinition?.contextFiles;
|
|
989
|
-
return { model, ...(alias && requestedModel ? { requestedModel } : {}), tools: requested, ...(overrideSystemPrompt ? { systemPrompt: roleDefinition.prompt ?? "" } : {}), systemPromptAppend: overrideSystemPrompt ? "" : roleDefinition?.prompt ?? "", ...(contextFiles === undefined ? {} : { contextFiles: [...contextFiles] }) };
|
|
951
|
+
const resolved = resolveRole(roleName, {
|
|
952
|
+
cwd: this.root.cwd,
|
|
953
|
+
...(roleDefinition === undefined ? {} : { definition: roleDefinition }),
|
|
954
|
+
...(this.root.agentDefinitions === undefined ? {} : { definitions: this.root.agentDefinitions }),
|
|
955
|
+
...(this.root.agentDir === undefined ? {} : { agentDir: this.root.agentDir }),
|
|
956
|
+
selectorSources: { global: this.root.resourceSelectors ?? {}, project: {} },
|
|
957
|
+
...(this.root.modelAliases === undefined ? {} : { modelAliases: this.root.modelAliases }),
|
|
958
|
+
...(this.root.knownModels === undefined ? {} : { knownModels: this.root.knownModels }),
|
|
959
|
+
...(this.root.availableModels === undefined ? {} : { availableModels: this.root.availableModels }),
|
|
960
|
+
...(this.root.blockedAliases === undefined ? {} : { blockedAliases: this.root.blockedAliases }),
|
|
961
|
+
...(this.root.blockedAliasTargets === undefined ? {} : { blockedAliasTargets: this.root.blockedAliasTargets }),
|
|
962
|
+
...(this.root.settingsPath === undefined ? {} : { settingsPath: this.root.settingsPath }),
|
|
963
|
+
rootModel: this.root.model,
|
|
964
|
+
rootTools: this.root.tools,
|
|
965
|
+
...(inheritedTools === undefined ? {} : { inheritedTools }),
|
|
966
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
967
|
+
...(options.modelOverride === undefined ? {} : { modelOverride: options.modelOverride }),
|
|
968
|
+
...(options.tools === undefined ? {} : { tools: options.tools }),
|
|
969
|
+
...(options.skills === undefined ? {} : { skills: options.skills }),
|
|
970
|
+
...(options.extensions === undefined ? {} : { extensions: options.extensions }),
|
|
971
|
+
...(options.contextFiles === undefined ? {} : { contextFiles: options.contextFiles }),
|
|
972
|
+
...(options.effectiveTools === undefined ? {} : { effectiveTools: options.effectiveTools }),
|
|
973
|
+
});
|
|
974
|
+
return { model: resolved.model ?? this.root.model, ...(resolved.requestedModel === undefined ? {} : { requestedModel: resolved.requestedModel }), tools: resolved.tools ?? [], ...(resolved.overrideSystemPrompt ? { systemPrompt: resolved.prompt } : {}), systemPromptAppend: resolved.overrideSystemPrompt ? "" : resolved.prompt, ...(resolved.contextFiles === undefined ? {} : { contextFiles: resolved.contextFiles }) };
|
|
990
975
|
}
|
|
991
976
|
async execute(task, options, signal, customTools = [], setSteer, beforeRetry) {
|
|
992
977
|
const executionSignal = signal ?? this.root.runContext?.signal;
|
|
@@ -22,6 +22,18 @@ export type BackgroundWidgetAPI = Pick<ExtensionAPI, "appendEntry" | "on"> & {
|
|
|
22
22
|
registerEntryRenderer?: ExtensionAPI["registerEntryRenderer"];
|
|
23
23
|
registerShortcut?: ExtensionAPI["registerShortcut"];
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* How long an agent may go without an event before the widget says so.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately shorter than the core's own stall threshold. That one answers
|
|
29
|
+
* "is this agent dead"; this one answers "should you go and look". An agent
|
|
30
|
+
* asking a question in its own pane produces exactly this signature — no tool
|
|
31
|
+
* calls, no events, waiting on a person — and the core cannot see it, since the
|
|
32
|
+
* pane is a separate process. Three minutes is longer than any single stretch
|
|
33
|
+
* of thinking and far shorter than the ten minutes it takes to be declared
|
|
34
|
+
* stalled.
|
|
35
|
+
*/
|
|
36
|
+
export declare const QUIET_MS: number;
|
|
25
37
|
/**
|
|
26
38
|
* Overrides the switch above, for the tests that cover navigation.
|
|
27
39
|
*
|
|
@@ -32,6 +44,11 @@ export type BackgroundWidgetAPI = Pick<ExtensionAPI, "appendEntry" | "on"> & {
|
|
|
32
44
|
export declare const __navigationForTests: {
|
|
33
45
|
enabled: boolean;
|
|
34
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Paints text in a themed colour role. Falls back to the bare text when no
|
|
49
|
+
* theme is available, so a frame rendered outside the TUI is still legible.
|
|
50
|
+
*/
|
|
51
|
+
export type Paint = (role: Parameters<Theme["fg"]>[0], text: string) => string;
|
|
35
52
|
interface ReceiptAgent {
|
|
36
53
|
id?: string;
|
|
37
54
|
parentId?: string;
|
|
@@ -62,8 +79,47 @@ interface Receipt {
|
|
|
62
79
|
agents: readonly ReceiptAgent[];
|
|
63
80
|
error?: string;
|
|
64
81
|
}
|
|
82
|
+
/** Status mark for a finished-or-running unit of work. */
|
|
83
|
+
export declare function mark(state: string | undefined, now: number, paint: Paint, runState?: string): string;
|
|
84
|
+
export declare function formatElapsed(ms: number): string;
|
|
65
85
|
export declare function formatTokens(tokens: number | undefined): string;
|
|
66
86
|
export declare function formatCost(cost: number | undefined): string;
|
|
87
|
+
interface Row {
|
|
88
|
+
text: string;
|
|
89
|
+
/**
|
|
90
|
+
* Numbers to set against the right-hand rule rather than trailing the name.
|
|
91
|
+
*
|
|
92
|
+
* Names are ragged — a phase called `plan` beside one called `refactor-2` —
|
|
93
|
+
* so numbers that follow them are ragged too, and comparing the cost of one
|
|
94
|
+
* agent against another means reading across a gap that moves on every row.
|
|
95
|
+
* Held to the right edge, they form a column.
|
|
96
|
+
*/
|
|
97
|
+
right?: string;
|
|
98
|
+
/** 2 = a run header or something needing an answer, 1 = live or failed work, 0 = finished detail. */
|
|
99
|
+
rank: number;
|
|
100
|
+
/**
|
|
101
|
+
* Set on the one row that stands for a whole run.
|
|
102
|
+
*
|
|
103
|
+
* Rank alone cannot protect it: a checkpoint needs an answer and so shares
|
|
104
|
+
* the top rank, and enough of them would spend the budget before a later
|
|
105
|
+
* run's header is reached. A run missing its header is not a terse run, it
|
|
106
|
+
* is an invisible one.
|
|
107
|
+
*/
|
|
108
|
+
header?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* A stable name for a row that can be folded shut, and the key of the row it
|
|
111
|
+
* hangs from. Together they let the cursor walk a tree rather than a list:
|
|
112
|
+
* collapsing a phase hides its agents, collapsing a run hides everything
|
|
113
|
+
* under it, and the choice survives the repaint that follows.
|
|
114
|
+
*/
|
|
115
|
+
key?: string;
|
|
116
|
+
parent?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The box around the live rows, shared with the subagent widget so both sit
|
|
120
|
+
* under the editor in one style.
|
|
121
|
+
*/
|
|
122
|
+
export declare function drawFrame(title: string, hint: string, body: readonly Pick<Row, "text" | "right">[], width: number, paint: Paint, accent: (text: string) => string, scrollbar?: (index: number) => string | undefined): string[];
|
|
67
123
|
/** The receipt as transcript lines: the widget's tree, one level deeper. */
|
|
68
124
|
export declare function renderReceipt(data: Receipt, expanded: boolean, theme: Theme): string[];
|
|
69
125
|
export interface BackgroundWidgetController {
|
|
@@ -51,7 +51,7 @@ const MAX_ROWS = 10;
|
|
|
51
51
|
* of thinking and far shorter than the ten minutes it takes to be declared
|
|
52
52
|
* stalled.
|
|
53
53
|
*/
|
|
54
|
-
const QUIET_MS = 3 * 60 * 1000;
|
|
54
|
+
export const QUIET_MS = 3 * 60 * 1000;
|
|
55
55
|
/** The core's own threshold for calling an agent stalled outright. */
|
|
56
56
|
const STALL_MS = WORKFLOW_AGENT_STALL_THRESHOLD_MS;
|
|
57
57
|
/**
|
|
@@ -130,7 +130,7 @@ function spinner(now) {
|
|
|
130
130
|
return SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? SPINNER[0];
|
|
131
131
|
}
|
|
132
132
|
/** Status mark for a finished-or-running unit of work. */
|
|
133
|
-
function mark(state, now, paint, runState) {
|
|
133
|
+
export function mark(state, now, paint, runState) {
|
|
134
134
|
if (state === "running" && (runState === undefined || runState === "running"))
|
|
135
135
|
return paint(ROLE.live, spinner(now));
|
|
136
136
|
if (state === "completed")
|
|
@@ -139,7 +139,7 @@ function mark(state, now, paint, runState) {
|
|
|
139
139
|
return paint(ROLE.failed, "✗");
|
|
140
140
|
return paint(ROLE.quiet, "·");
|
|
141
141
|
}
|
|
142
|
-
function formatElapsed(ms) {
|
|
142
|
+
export function formatElapsed(ms) {
|
|
143
143
|
const total = Math.max(0, Math.floor(ms / 1000));
|
|
144
144
|
const minutes = Math.floor(total / 60);
|
|
145
145
|
const seconds = total % 60;
|
|
@@ -576,13 +576,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
576
576
|
const title = live.length === 1 ? "Workflow" : `Workflows · ${String(live.length)} runs`;
|
|
577
577
|
sizeOut.rows = rows.length;
|
|
578
578
|
const body = collapse(rows, MAX_ROWS - 2, paint, offset, scrolling);
|
|
579
|
-
// Width budget: the rule takes a column and a space on each side, so the
|
|
580
|
-
// text has four fewer than the widget is given. Every piece is measured
|
|
581
|
-
// against `inner` so the right-hand rule lands in one column on every row.
|
|
582
|
-
const inner = Math.max(20, width - 4);
|
|
583
|
-
// Side rules below the lid, and no floor: the box opens downward into the
|
|
584
|
-
// editor rather than closing on itself, which is one more line kept for the
|
|
585
|
-
// run.
|
|
586
579
|
// A scrollbar on the right rule, drawn only when there is more tree than
|
|
587
580
|
// frame. Without it the window's position is a guess: three rows of a
|
|
588
581
|
// twenty-row tree look the same near the top as near the bottom.
|
|
@@ -610,6 +603,25 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
610
603
|
const top = limit === 0 ? 0 : start >= limit ? shown - size : Math.floor((start / limit) * (shown - size));
|
|
611
604
|
return index >= top && index < top + size ? "█" : "░";
|
|
612
605
|
};
|
|
606
|
+
// Only advertise navigation when it can reveal hidden rows: a hint for keys
|
|
607
|
+
// that do nothing is worse than no hint.
|
|
608
|
+
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0
|
|
609
|
+
? ""
|
|
610
|
+
: scrolling ? "↑/↓ to scroll · Esc to exit" : "Alt+O to scroll";
|
|
611
|
+
return drawFrame(title, hint, body, width, paint, accent, scrollbar);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* The box around the live rows, shared with the subagent widget so both sit
|
|
615
|
+
* under the editor in one style.
|
|
616
|
+
*/
|
|
617
|
+
export function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => undefined) {
|
|
618
|
+
// Width budget: the rule takes a column and a space on each side, so the
|
|
619
|
+
// text has four fewer than the widget is given. Every piece is measured
|
|
620
|
+
// against `inner` so the right-hand rule lands in one column on every row.
|
|
621
|
+
const inner = Math.max(20, width - 4);
|
|
622
|
+
// Side rules below the lid, and no floor: the box opens downward into the
|
|
623
|
+
// editor rather than closing on itself, which is one more line kept for the
|
|
624
|
+
// run.
|
|
613
625
|
const lines = body.map((row, index) => {
|
|
614
626
|
// The numbers hold the right edge; the name gives way when the two would
|
|
615
627
|
// meet. A truncated workflow name is still recognisable, where a truncated
|
|
@@ -630,11 +642,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
630
642
|
// rather than sitting under a separate run of dashes. In ten rows a line
|
|
631
643
|
// spent on decoration is a line not spent on the run, and the box still
|
|
632
644
|
// reads as closed at the top.
|
|
633
|
-
// Only advertise navigation when it can reveal hidden rows: a hint for keys
|
|
634
|
-
// that do nothing is worse than no hint.
|
|
635
|
-
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0
|
|
636
|
-
? ""
|
|
637
|
-
: scrolling ? "↑/↓ to scroll · Esc to exit" : "Alt+O to scroll";
|
|
638
645
|
const label = ` ${title} `;
|
|
639
646
|
const tail = hint === "" ? "" : ` ${hint} `;
|
|
640
647
|
const spare = inner + 2 - visibleLength(label) - visibleLength(tail);
|
package/dist/src/decoders.js
CHANGED
|
@@ -109,9 +109,10 @@ export function decodeWorkflowExtensions(value) {
|
|
|
109
109
|
continue;
|
|
110
110
|
}
|
|
111
111
|
if (namespace === "trajectory") {
|
|
112
|
+
// Snapshots persisted before the themes setting was removed still carry a boolean `themes`; it is accepted and dropped.
|
|
112
113
|
if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== undefined && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== undefined && typeof raw.themes !== "boolean")
|
|
113
114
|
return undefined;
|
|
114
|
-
normalized.trajectory = { ...(raw.port === undefined ? {} : { port: raw.port })
|
|
115
|
+
normalized.trajectory = { ...(raw.port === undefined ? {} : { port: raw.port }) };
|
|
115
116
|
continue;
|
|
116
117
|
}
|
|
117
118
|
if (!jsonValue(raw))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ExtensionUIContext, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { RunStore, type PersistedRun } from "./persistence.js";
|
|
3
3
|
import { type WorkflowRegistryApi } from "./registry.js";
|
|
4
|
+
import { type WorkflowProgressStyles } from "./host-view.js";
|
|
4
5
|
import { type WorkflowRecoveryContext, type createWorkflowRecovery } from "./host-recovery.js";
|
|
5
6
|
import { type WorkflowRunRecord } from "./host-runtime.js";
|
|
6
7
|
import { type TrajectoryPublisherProvider } from "./trajectory-host-handle.js";
|
|
@@ -34,6 +35,23 @@ export type UiHostCapabilities = {
|
|
|
34
35
|
};
|
|
35
36
|
type ReportBlocked = (active: boolean, label?: string) => void;
|
|
36
37
|
export declare function uiHostCapabilities(ui: unknown): UiHostCapabilities | undefined;
|
|
38
|
+
type WorkflowKeybindings = {
|
|
39
|
+
matches(data: string, binding: string): boolean;
|
|
40
|
+
};
|
|
41
|
+
export declare function workflowKeyMatches(keybindings: WorkflowKeybindings, data: string, binding: string): boolean;
|
|
42
|
+
export declare function workflowKeyLabel(keybindings: unknown, binding: string, fallback: string, labels?: Readonly<Record<string, string>>): string;
|
|
43
|
+
/**
|
|
44
|
+
* A yes/no question drawn inside a navigator dashboard. Pi's confirm dialog replaces a custom component and afterwards
|
|
45
|
+
* restores the editor rather than the component, which leaves the dashboard's promise pending and the session waiting on it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createInlineConfirm(requestRender: () => void): {
|
|
48
|
+
ask(title: string, message: string): Promise<boolean>;
|
|
49
|
+
active(): boolean;
|
|
50
|
+
rows(width: number, styles: WorkflowProgressStyles): string[];
|
|
51
|
+
hint(keybindings: unknown): string;
|
|
52
|
+
handleInput(keybindings: WorkflowKeybindings, data: string): void;
|
|
53
|
+
cancel(): void;
|
|
54
|
+
};
|
|
37
55
|
export type WorkflowNavigatorDependencies = {
|
|
38
56
|
pi: Pick<ExtensionAPI, "registerCommand">;
|
|
39
57
|
home: string | undefined;
|
|
@@ -110,13 +110,57 @@ function keybindingKeys(keybindings, name) {
|
|
|
110
110
|
return isKeybindingGetKeys(getKeys) ? getKeys.call(keybindings, name) : undefined;
|
|
111
111
|
}
|
|
112
112
|
const WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.editor.cursorLeft": "h", "tui.editor.cursorRight": "l" };
|
|
113
|
-
function workflowKeyMatches(keybindings, data, binding) { return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data; }
|
|
114
|
-
|
|
113
|
+
export function workflowKeyMatches(keybindings, data, binding) { return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data; }
|
|
114
|
+
const WORKFLOW_KEY_LABELS = { up: "↑", down: "↓", left: "←", right: "→", pageUp: "pgup", pageDown: "pgdn" };
|
|
115
|
+
export function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
|
|
115
116
|
const keys = keybindingKeys(keybindings, binding);
|
|
116
117
|
const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
|
|
117
118
|
const vim = WORKFLOW_VIM_KEYS[binding];
|
|
118
119
|
return [...new Set(vim ? [...configured, vim] : configured)].join("/");
|
|
119
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* A yes/no question drawn inside a navigator dashboard. Pi's confirm dialog replaces a custom component and afterwards
|
|
123
|
+
* restores the editor rather than the component, which leaves the dashboard's promise pending and the session waiting on it.
|
|
124
|
+
*/
|
|
125
|
+
export function createInlineConfirm(requestRender) {
|
|
126
|
+
let pending;
|
|
127
|
+
const answer = (value) => {
|
|
128
|
+
const current = pending;
|
|
129
|
+
pending = undefined;
|
|
130
|
+
current?.resolve(value);
|
|
131
|
+
requestRender();
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
ask(title, message) {
|
|
135
|
+
answer(false);
|
|
136
|
+
return new Promise((resolve) => { pending = { title, message, yes: true, resolve }; requestRender(); });
|
|
137
|
+
},
|
|
138
|
+
active() { return pending !== undefined; },
|
|
139
|
+
rows(width, styles) {
|
|
140
|
+
if (!pending)
|
|
141
|
+
return [];
|
|
142
|
+
const yes = pending.yes;
|
|
143
|
+
const message = truncateToVisualLines(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
144
|
+
return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => (option === "Yes") === yes ? `→ ${styles.accent(option)}` : ` ${option}`)];
|
|
145
|
+
},
|
|
146
|
+
hint(keybindings) {
|
|
147
|
+
return `${workflowKeyLabel(keybindings, "tui.select.up", "↑")}/${workflowKeyLabel(keybindings, "tui.select.down", "↓")} select · ${workflowKeyLabel(keybindings, "tui.select.confirm", "enter")} confirm · ${workflowKeyLabel(keybindings, "tui.select.cancel", "esc")} cancel`;
|
|
148
|
+
},
|
|
149
|
+
handleInput(keybindings, data) {
|
|
150
|
+
if (!pending)
|
|
151
|
+
return;
|
|
152
|
+
if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
|
|
153
|
+
pending.yes = !pending.yes;
|
|
154
|
+
requestRender();
|
|
155
|
+
}
|
|
156
|
+
else if (workflowKeyMatches(keybindings, data, "tui.select.confirm"))
|
|
157
|
+
answer(pending.yes);
|
|
158
|
+
else if (workflowKeyMatches(keybindings, data, "tui.select.cancel"))
|
|
159
|
+
answer(false);
|
|
160
|
+
},
|
|
161
|
+
cancel() { answer(false); },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
120
164
|
export function registerWorkflowNavigator(deps) {
|
|
121
165
|
const { pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates, ensureSessionLease, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground, isForegroundAttached, liveAgents, registry, projectTrusted, resumeHostContext, resumeSelectedWorkflow, reportBlocked, setNavigatorOpen, trajectoryProvider, coordinateRunMutation: coordinateRunMutationDependency } = deps;
|
|
122
166
|
const coordinateRunMutation = coordinateRunMutationDependency ?? ((task) => task());
|
|
@@ -160,7 +204,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
160
204
|
const setStatus = uiHostCapabilities(ctx.ui)?.setStatus;
|
|
161
205
|
setStatus?.call(ctx.ui, "workflow-stop", text);
|
|
162
206
|
};
|
|
163
|
-
const runAction = async (actionCommand, status = setWorkflowStatus) => {
|
|
207
|
+
const runAction = async (actionCommand, status = setWorkflowStatus, confirm = (title, message) => confirmWithBlocked(ctx.ui, reportBlocked, title, message)) => {
|
|
164
208
|
const [action, runId, ...rest] = actionCommand.split(/\s+/);
|
|
165
209
|
try {
|
|
166
210
|
const run = runId ? runs.get(runId) : undefined;
|
|
@@ -252,7 +296,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
252
296
|
}
|
|
253
297
|
if (action === "stop" && run) {
|
|
254
298
|
const workflowName = stored?.loaded.run.workflowName ?? run.metadata.name;
|
|
255
|
-
if (!await
|
|
299
|
+
if (!await confirm("Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`))
|
|
256
300
|
return "dashboard";
|
|
257
301
|
status(`Stopping workflow ${workflowName}...`);
|
|
258
302
|
await stopWorkflowRun(run.store.runId);
|
|
@@ -651,8 +695,18 @@ export function registerWorkflowNavigator(deps) {
|
|
|
651
695
|
let selectedNodeId = tree.nodes[0]?.id;
|
|
652
696
|
let expandedNodeIds = new Set(workflowPhaseTreeInitialExpanded(tree));
|
|
653
697
|
const terminalRows = () => Math.max(1, tuiRows(tui) - WORKFLOW_PANEL_FOOTER_ROWS);
|
|
654
|
-
const
|
|
655
|
-
const
|
|
698
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
699
|
+
const inlineConfirm = createInlineConfirm(() => { if (!disposed)
|
|
700
|
+
tui.requestRender(); });
|
|
701
|
+
const confirmInDashboard = async (title, message) => {
|
|
702
|
+
reportBlocked?.(true, title);
|
|
703
|
+
try {
|
|
704
|
+
return await inlineConfirm.ask(title, message);
|
|
705
|
+
}
|
|
706
|
+
finally {
|
|
707
|
+
reportBlocked?.(false);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
656
710
|
const progressNow = () => {
|
|
657
711
|
const now = Date.now();
|
|
658
712
|
if (hardTerminalRunStates.has(view.run.state)) {
|
|
@@ -738,7 +792,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
738
792
|
setWorkflowStatus(status);
|
|
739
793
|
if (!disposed)
|
|
740
794
|
tui.requestRender();
|
|
741
|
-
}).then(async (outcome) => {
|
|
795
|
+
}, confirmInDashboard).then(async (outcome) => {
|
|
742
796
|
if (outcome === "stopped") {
|
|
743
797
|
done("__stopped__");
|
|
744
798
|
return;
|
|
@@ -778,7 +832,8 @@ export function registerWorkflowNavigator(deps) {
|
|
|
778
832
|
const content = [...statusLines, ...phaseLines];
|
|
779
833
|
const rows = terminalRows();
|
|
780
834
|
const hintRows = rows >= 3 ? 1 : 0;
|
|
781
|
-
const
|
|
835
|
+
const confirmRows = inlineConfirm.rows(width, styles);
|
|
836
|
+
const viewport = Math.max(1, rows - hintRows - confirmRows.length);
|
|
782
837
|
const maxOffset = Math.max(0, content.length - viewport);
|
|
783
838
|
dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
|
|
784
839
|
if (actionMode) {
|
|
@@ -804,11 +859,15 @@ export function registerWorkflowNavigator(deps) {
|
|
|
804
859
|
dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
|
|
805
860
|
const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : undefined;
|
|
806
861
|
const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
|
|
807
|
-
const hint = truncateToVisualLines(theme.fg("dim", actionMode ? `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} actions · ${keyLabel("tui.select.confirm", "enter")} run · ${keyLabel("tui.editor.cursorLeft", "←")} tree · ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} tree · ${keyLabel("tui.editor.cursorLeft", "←")}/${keyLabel("tui.editor.cursorRight", "→")} collapse/expand · ${keyLabel("tui.select.confirm", "enter")} ${enterAction} · a actions · ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` · ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} · auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
808
|
-
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...(hintRows ? [hint] : [])];
|
|
862
|
+
const hint = truncateToVisualLines(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} actions · ${keyLabel("tui.select.confirm", "enter")} run · ${keyLabel("tui.editor.cursorLeft", "←")} tree · ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} tree · ${keyLabel("tui.editor.cursorLeft", "←")}/${keyLabel("tui.editor.cursorRight", "→")} collapse/expand · ${keyLabel("tui.select.confirm", "enter")} ${enterAction} · a actions · ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` · ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} · auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
863
|
+
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...confirmRows, ...(hintRows ? [hint] : [])];
|
|
809
864
|
},
|
|
810
865
|
invalidate() { },
|
|
811
866
|
handleInput(data) {
|
|
867
|
+
if (inlineConfirm.active()) {
|
|
868
|
+
inlineConfirm.handleInput(keybindings, data);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
812
871
|
if (stopRequested || editorRunning)
|
|
813
872
|
return;
|
|
814
873
|
const narrow = renderedWidth < 80;
|
|
@@ -958,7 +1017,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
958
1017
|
}
|
|
959
1018
|
tui.requestRender();
|
|
960
1019
|
},
|
|
961
|
-
dispose() { disposed = true; stopTimer(); setWorkflowStatus(undefined); },
|
|
1020
|
+
dispose() { disposed = true; stopTimer(); setWorkflowStatus(undefined); inlineConfirm.cancel(); },
|
|
962
1021
|
};
|
|
963
1022
|
})
|
|
964
1023
|
: await ctx.ui.select(view.dashboard, [...view.actions.keys(), "Back"]);
|
|
@@ -1016,8 +1075,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
1016
1075
|
const currentLayout = layout();
|
|
1017
1076
|
const maxOffset = Math.max(0, renderedLines.length - currentLayout.contentViewport);
|
|
1018
1077
|
offset = Math.min(offset, maxOffset);
|
|
1019
|
-
const
|
|
1020
|
-
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
|
|
1078
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
1021
1079
|
const hint = truncateToVisualLines(theme.fg("dim", `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")}/pgup/pgdn scroll · enter select · esc cancel`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
1022
1080
|
const controls = currentLayout.compactControls
|
|
1023
1081
|
? [options.map((option, index) => `${index === selectedIndex ? "[" : " "}${option}${index === selectedIndex ? "]" : " "}`).join(" ")]
|
package/dist/src/host-view.d.ts
CHANGED
|
@@ -12,7 +12,16 @@ export interface WorkflowProgressStyles {
|
|
|
12
12
|
dim(text: string): string;
|
|
13
13
|
bold(text: string): string;
|
|
14
14
|
}
|
|
15
|
-
export declare
|
|
15
|
+
export declare const PLAIN_WORKFLOW_PROGRESS_STYLES: WorkflowProgressStyles;
|
|
16
|
+
export declare function runStateGlyph(state: string, running: string): string;
|
|
17
|
+
export declare function progressStyleForState(state: string, styles: WorkflowProgressStyles): (text: string) => string;
|
|
18
|
+
export declare function formatWorkflowRuntime(durationMs: number): string;
|
|
19
|
+
/**
|
|
20
|
+
* Renders the run; with `maxLines`, a view taller than that limit switches to a compact one so the live block stays on screen.
|
|
21
|
+
* A block taller than the terminal pushes its changing header above the viewport, and pi-tui then clears and redraws the whole
|
|
22
|
+
* scrollback on every update, which snaps the reader back to the bottom.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatWorkflowProgress(run: PersistedRun, spinner?: string, styles?: WorkflowProgressStyles, now?: number, expanded?: boolean, width?: number, showHint?: boolean, maxLines?: number): string;
|
|
16
25
|
export declare function textBlock(text: string): {
|
|
17
26
|
render(width: number): string[];
|
|
18
27
|
invalidate(): void;
|
|
@@ -93,6 +102,7 @@ export interface AgentDetailPresentation {
|
|
|
93
102
|
readonly message: string;
|
|
94
103
|
};
|
|
95
104
|
}
|
|
105
|
+
export declare function formatAgentError(error: NonNullable<AgentDetailPresentation["error"]>, styles?: WorkflowProgressStyles): string;
|
|
96
106
|
export declare function formatAgentDetail(agent: Readonly<AgentDetailPresentation>, styles?: WorkflowProgressStyles, now?: number, options?: Readonly<{
|
|
97
107
|
includeError?: boolean;
|
|
98
108
|
}>): string[];
|
|
@@ -113,4 +123,9 @@ export declare function formatNavigatorRun(loaded: {
|
|
|
113
123
|
snapshot: Readonly<LaunchSnapshot>;
|
|
114
124
|
}, checkpoints: readonly AwaitingCheckpoint[], worktrees: readonly WorktreeReference[], now?: number): string;
|
|
115
125
|
export declare function formatWorkflowPhaseDashboard(run: PersistedRun, snapshot: Readonly<LaunchSnapshot>, width: number, selection?: WorkflowPhaseSelection, styles?: WorkflowProgressStyles, now?: number): string[];
|
|
126
|
+
/** The navigator layout: a sidebar beside details at 80 columns or wider, otherwise one of them (or both stacked). */
|
|
127
|
+
export declare function formatNavigatorColumns(sidebar: readonly string[], detail: readonly string[], width: number, layout?: Readonly<{
|
|
128
|
+
treeOnly?: boolean | undefined;
|
|
129
|
+
detailsOnly?: boolean | undefined;
|
|
130
|
+
}>): string[];
|
|
116
131
|
export declare function formatCheckpointReview(checkpoint: AwaitingCheckpoint): string;
|