@ai-agent-forge/agent-forge 0.86.3 → 0.86.5
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 +60 -0
- package/dist/bundle/.build-manifest.json +500 -434
- package/dist/bundle/chunks/anthropic-messages-ALDZBUKI.js +6 -0
- package/dist/bundle/chunks/anthropic.js +2 -2
- package/dist/bundle/chunks/{azure-openai-responses-WQCZWFAH.js → azure-openai-responses-A2WJWZFB.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-A7GUOX2H.js +141 -0
- package/dist/bundle/chunks/{chunk-WGLBM2BP.js → chunk-CIX3FSBK.js} +1 -1
- package/dist/bundle/chunks/{chunk-RK2X2KU2.js → chunk-EALJ5DDX.js} +111 -101
- package/dist/bundle/chunks/chunk-OO6DJ3CL.js +31 -0
- package/dist/bundle/chunks/{chunk-N6BQJSGH.js → chunk-SQFG22WN.js} +3 -3
- package/dist/bundle/chunks/{chunk-V46XUTK3.js → chunk-Y7HXTASS.js} +1 -1
- package/dist/bundle/chunks/{chunk-3WE3W52L.js → chunk-YJCQVXW5.js} +1 -1
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/google-generative-ai-NIB4WONV.js +2 -0
- package/dist/bundle/chunks/google-vertex-DCPYUEUO.js +2 -0
- package/dist/bundle/chunks/mistral-conversations-OC6LA5XL.js +5 -0
- package/dist/bundle/chunks/{openai-codex-responses-YDJSZOIH.js → openai-codex-responses-IO2X4Y7L.js} +1 -1
- package/dist/bundle/chunks/openai-codex.js +2 -2
- package/dist/bundle/chunks/{openai-completions-UPRWTQPT.js → openai-completions-XYQQHETU.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-O63W6HQ3.js → openai-responses-DLIRN3CI.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-G3GT5BVT.js → openrouter-images-G4GQRUSG.js} +1 -1
- package/dist/bundle/chunks/openrouter.js +2 -2
- package/dist/bundle/chunks/{pi-messages-SLF355GF.js → pi-messages-E6RRYBO7.js} +1 -1
- package/dist/bundle/chunks/radius.js +2 -2
- package/dist/bundle/chunks/{virtual-modules-O72ICTMQ.js → virtual-modules-3IZ4MYSQ.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/capabilities/builtin.d.ts +4 -2
- package/dist/capabilities/builtin.d.ts.map +1 -1
- package/dist/capabilities/builtin.js +5 -1
- package/dist/capabilities/builtin.js.map +1 -1
- package/dist/cli/plugin-command.d.ts.map +1 -1
- package/dist/cli/plugin-command.js +51 -5
- package/dist/cli/plugin-command.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +2 -2
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +54 -6
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-internal.d.ts +2 -4
- package/dist/core/agent-session-internal.d.ts.map +1 -1
- package/dist/core/agent-session-internal.js.map +1 -1
- package/dist/core/agent-session-paths.d.ts +2 -0
- package/dist/core/agent-session-paths.d.ts.map +1 -1
- package/dist/core/agent-session-paths.js +2 -1
- package/dist/core/agent-session-paths.js.map +1 -1
- package/dist/core/agent-session-projection.d.ts +10 -4
- package/dist/core/agent-session-projection.d.ts.map +1 -1
- package/dist/core/agent-session-projection.js +17 -4
- package/dist/core/agent-session-projection.js.map +1 -1
- package/dist/core/agent-session.d.ts +19 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +30 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.js +1 -2
- package/dist/core/extensions/bootstrap-compaction-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.d.ts +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.js +5 -2
- package/dist/core/extensions/bootstrap-lifecycle.js.map +1 -1
- package/dist/core/extensions/capability-runtime-store.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-store.js +1 -0
- package/dist/core/extensions/capability-runtime-store.js.map +1 -1
- package/dist/core/extensions/capability-runtime-types.d.ts +16 -1
- package/dist/core/extensions/capability-runtime-types.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-types.js.map +1 -1
- package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime.js +38 -2
- package/dist/core/extensions/capability-runtime.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.d.ts +46 -0
- package/dist/core/extensions/lifecycle-catalog.d.ts.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.js +101 -0
- package/dist/core/extensions/lifecycle-catalog.js.map +1 -1
- package/dist/core/extensions/microcompact.d.ts +16 -8
- package/dist/core/extensions/microcompact.d.ts.map +1 -1
- package/dist/core/extensions/microcompact.js +9 -7
- package/dist/core/extensions/microcompact.js.map +1 -1
- package/dist/core/extensions/provider-failover.d.ts +106 -0
- package/dist/core/extensions/provider-failover.d.ts.map +1 -0
- package/dist/core/extensions/provider-failover.js +692 -0
- package/dist/core/extensions/provider-failover.js.map +1 -0
- package/dist/core/facade/coding-agent-facade.d.ts +13 -6
- package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
- package/dist/core/facade/coding-agent-facade.js +3 -1
- package/dist/core/facade/coding-agent-facade.js.map +1 -1
- package/dist/core/keybindings.d.ts +6 -6
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +23 -6
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +102 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +46 -14
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +15 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +30 -18
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +4 -9
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +107 -24
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +10 -5
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +74 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +143 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +17 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +9 -10
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +6 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +26 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/tool-definition.d.ts +21 -2
- package/dist/core/tools/tool-definition.d.ts.map +1 -1
- package/dist/core/tools/tool-definition.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -6
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/agents-view.d.ts +57 -0
- package/dist/modes/interactive/components/agents-view.d.ts.map +1 -0
- package/dist/modes/interactive/components/agents-view.js +260 -0
- package/dist/modes/interactive/components/agents-view.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +2 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +15 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +90 -9
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +12 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +1 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +89 -10
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts +3 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +9 -3
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/workflow-view.d.ts +119 -0
- package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -0
- package/dist/modes/interactive/components/workflow-view.js +638 -0
- package/dist/modes/interactive/components/workflow-view.js.map +1 -0
- package/dist/modes/interactive/interactive-commands.d.ts +22 -0
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +56 -0
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +51 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +218 -37
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.d.ts +2 -4
- package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +10 -1
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +5 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +4 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/plugins/ecosystem/bootstrap-factory-manifest.d.ts.map +1 -1
- package/dist/plugins/ecosystem/bootstrap-factory-manifest.js +7 -0
- package/dist/plugins/ecosystem/bootstrap-factory-manifest.js.map +1 -1
- package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
- package/dist/plugins/ecosystem/cli.js +1 -1
- package/dist/plugins/ecosystem/cli.js.map +1 -1
- package/dist/plugins/ecosystem/install-store-paths.d.ts +15 -0
- package/dist/plugins/ecosystem/install-store-paths.d.ts.map +1 -0
- package/dist/plugins/ecosystem/install-store-paths.js +58 -0
- package/dist/plugins/ecosystem/install-store-paths.js.map +1 -0
- package/dist/plugins/ecosystem/node-disk-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-disk-registry-source.js +3 -8
- package/dist/plugins/ecosystem/node-disk-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +9 -1
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.js +3 -8
- package/dist/plugins/ecosystem/node-package-install-persistence.js.map +1 -1
- package/dist/profile/plugin-profiles.d.ts +2 -0
- package/dist/profile/plugin-profiles.d.ts.map +1 -1
- package/dist/profile/plugin-profiles.js +4 -0
- package/dist/profile/plugin-profiles.js.map +1 -1
- package/dist/profiles/builtin-suites.d.ts +19 -2
- package/dist/profiles/builtin-suites.d.ts.map +1 -1
- package/dist/profiles/builtin-suites.js +20 -2
- package/dist/profiles/builtin-suites.js.map +1 -1
- package/dist/profiles/suite-loader.d.ts.map +1 -1
- package/dist/profiles/suite-loader.js +4 -1
- package/dist/profiles/suite-loader.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +10 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/shell.d.ts +1 -3
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +3 -30
- package/dist/utils/shell.js.map +1 -1
- package/docs/keybindings.md +8 -8
- package/docs/models.md +1 -1
- package/docs/rpc.md +3 -1
- package/docs/settings.md +2 -1
- package/package.json +4 -4
- package/dist/bundle/chunks/anthropic-messages-EVFQ6MLR.js +0 -6
- package/dist/bundle/chunks/chunk-F6SBZUJG.js +0 -141
- package/dist/bundle/chunks/chunk-NUHFSC37.js +0 -25
- package/dist/bundle/chunks/google-generative-ai-ISTTUZMW.js +0 -2
- package/dist/bundle/chunks/google-vertex-NS7ZF7DK.js +0 -2
- package/dist/bundle/chunks/mistral-conversations-GINO6Z4I.js +0 -5
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow overlay view (§9 L2/L3 of docs/design/工作流编排插件设计.md).
|
|
3
|
+
*
|
|
4
|
+
* Read-only panorama (L2) and transition stream (L3) rendered from the
|
|
5
|
+
* session's `workflow.state` / `workflow.transition` custom entries — the
|
|
6
|
+
* same isomorphic read the footer performs on `goal.state`, so the plugin
|
|
7
|
+
* does not need to be present and this module never imports plugin code.
|
|
8
|
+
* Waiting-condition labels mirror the engine's `computeWaitingOn` order
|
|
9
|
+
* (active-limit precedes write-scope conflict); plan lookups read the latest
|
|
10
|
+
* `plan.state` snapshot the same way.
|
|
11
|
+
*
|
|
12
|
+
* Operations are never executed here: approve/reject/skip/cancel forward an
|
|
13
|
+
* action text (e.g. `approve stage s2-review`) to the conversation input and
|
|
14
|
+
* the main agent drives it through workflow_advance — rendering and execution
|
|
15
|
+
* stay separated (设计 §9 L2 "转发到对话").
|
|
16
|
+
*/
|
|
17
|
+
import { Container, getKeybindings, Text, truncateToWidth } from "@agent-forge/tui";
|
|
18
|
+
import { theme } from "../theme/theme.js";
|
|
19
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
20
|
+
import { keyHint, rawKeyHint } from "./keybinding-hints.js";
|
|
21
|
+
const STAGE_STATUSES = ["pending", "active", "awaiting_review", "completed", "failed", "skipped"];
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function stringOrUndefined(value) {
|
|
26
|
+
return typeof value === "string" ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
function parseStageSpec(value) {
|
|
29
|
+
if (!isRecord(value))
|
|
30
|
+
return undefined;
|
|
31
|
+
const id = stringOrUndefined(value.id);
|
|
32
|
+
if (id === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
const dependsOn = Array.isArray(value.dependsOn) ? value.dependsOn.filter(isString) : [];
|
|
35
|
+
const acceptance = isRecord(value.objective) && Array.isArray(value.objective.acceptance) ? value.objective.acceptance : [];
|
|
36
|
+
const scope = Array.isArray(value.scope) ? value.scope.filter(isString) : undefined;
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
title: stringOrUndefined(value.title) ?? id,
|
|
40
|
+
dependsOn,
|
|
41
|
+
planId: stringOrUndefined(value.planId),
|
|
42
|
+
kind: value.kind === "read" ? "read" : "write",
|
|
43
|
+
scope: scope !== undefined && scope.length > 0 ? scope : undefined,
|
|
44
|
+
acceptanceIds: acceptance
|
|
45
|
+
.map((item) => (isRecord(item) ? stringOrUndefined(item.id) : undefined))
|
|
46
|
+
.filter((item) => item !== undefined),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function isString(value) {
|
|
50
|
+
return typeof value === "string";
|
|
51
|
+
}
|
|
52
|
+
function parseStageRuntime(value) {
|
|
53
|
+
if (!isRecord(value))
|
|
54
|
+
return undefined;
|
|
55
|
+
const stageId = stringOrUndefined(value.stageId);
|
|
56
|
+
const status = stringOrUndefined(value.status);
|
|
57
|
+
if (stageId === undefined || status === undefined || !STAGE_STATUSES.includes(status))
|
|
58
|
+
return undefined;
|
|
59
|
+
const evidence = Array.isArray(value.evidence) ? value.evidence : [];
|
|
60
|
+
const covered = new Set();
|
|
61
|
+
let evidenceCount = 0;
|
|
62
|
+
for (const item of evidence) {
|
|
63
|
+
if (!isRecord(item))
|
|
64
|
+
continue;
|
|
65
|
+
evidenceCount++;
|
|
66
|
+
const acceptanceId = stringOrUndefined(item.acceptanceId);
|
|
67
|
+
if (acceptanceId !== undefined)
|
|
68
|
+
covered.add(acceptanceId);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
stageId,
|
|
72
|
+
status: status,
|
|
73
|
+
attempts: typeof value.attempts === "number" ? value.attempts : 0,
|
|
74
|
+
evidenceCount,
|
|
75
|
+
coveredAcceptanceIds: covered,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function parseWorkflowRecord(value) {
|
|
79
|
+
if (!isRecord(value))
|
|
80
|
+
return undefined;
|
|
81
|
+
const id = stringOrUndefined(value.id);
|
|
82
|
+
const title = stringOrUndefined(value.title);
|
|
83
|
+
const status = stringOrUndefined(value.status);
|
|
84
|
+
if (id === undefined || title === undefined || status === undefined)
|
|
85
|
+
return undefined;
|
|
86
|
+
const stages = [];
|
|
87
|
+
for (const item of Array.isArray(value.stages) ? value.stages : []) {
|
|
88
|
+
const spec = parseStageSpec(item);
|
|
89
|
+
if (spec !== undefined)
|
|
90
|
+
stages.push(spec);
|
|
91
|
+
}
|
|
92
|
+
const runtime = new Map();
|
|
93
|
+
for (const item of Array.isArray(value.runtime) ? value.runtime : []) {
|
|
94
|
+
const parsed = parseStageRuntime(item);
|
|
95
|
+
if (parsed !== undefined)
|
|
96
|
+
runtime.set(parsed.stageId, parsed);
|
|
97
|
+
}
|
|
98
|
+
const diagnostics = Array.isArray(value.diagnostics) ? value.diagnostics.filter(isString) : [];
|
|
99
|
+
return {
|
|
100
|
+
id,
|
|
101
|
+
title,
|
|
102
|
+
status,
|
|
103
|
+
revision: typeof value.revision === "number" ? value.revision : 0,
|
|
104
|
+
goalId: stringOrUndefined(value.goalId),
|
|
105
|
+
maxActiveStages: typeof value.maxActiveStages === "number" ? value.maxActiveStages : 4,
|
|
106
|
+
maxRetreats: typeof value.maxRetreats === "number" ? value.maxRetreats : 3,
|
|
107
|
+
retreatCount: typeof value.retreatCount === "number" ? value.retreatCount : 0,
|
|
108
|
+
diagnostics,
|
|
109
|
+
stages,
|
|
110
|
+
runtime,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Latest `plan.state` snapshot reduced to id -> status (plan-dangling / plan-incomplete labels). */
|
|
114
|
+
function parsePlanStatuses(data) {
|
|
115
|
+
const statuses = new Map();
|
|
116
|
+
if (!isRecord(data) || !Array.isArray(data.plans))
|
|
117
|
+
return statuses;
|
|
118
|
+
for (const item of data.plans) {
|
|
119
|
+
if (!isRecord(item))
|
|
120
|
+
continue;
|
|
121
|
+
const id = stringOrUndefined(item.id);
|
|
122
|
+
const status = stringOrUndefined(item.status);
|
|
123
|
+
if (id !== undefined && status !== undefined)
|
|
124
|
+
statuses.set(id, status);
|
|
125
|
+
}
|
|
126
|
+
return statuses;
|
|
127
|
+
}
|
|
128
|
+
function parseTransition(value) {
|
|
129
|
+
if (!isRecord(value))
|
|
130
|
+
return undefined;
|
|
131
|
+
const workflowId = stringOrUndefined(value.workflowId);
|
|
132
|
+
const from = stringOrUndefined(value.from);
|
|
133
|
+
const to = stringOrUndefined(value.to);
|
|
134
|
+
const reason = isRecord(value.reason) ? value.reason : undefined;
|
|
135
|
+
const code = reason !== undefined ? stringOrUndefined(reason.code) : undefined;
|
|
136
|
+
if (workflowId === undefined ||
|
|
137
|
+
from === undefined ||
|
|
138
|
+
to === undefined ||
|
|
139
|
+
code === undefined ||
|
|
140
|
+
typeof value.revision !== "number") {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
revision: value.revision,
|
|
145
|
+
workflowId,
|
|
146
|
+
stageId: stringOrUndefined(value.stageId),
|
|
147
|
+
from,
|
|
148
|
+
to,
|
|
149
|
+
trigger: stringOrUndefined(value.trigger) ?? "tool",
|
|
150
|
+
code,
|
|
151
|
+
message: reason !== undefined ? (stringOrUndefined(reason.message) ?? "") : "",
|
|
152
|
+
detail: reason !== undefined ? stringOrUndefined(reason.detail) : undefined,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function scopesIntersect(a, b) {
|
|
156
|
+
if (a === undefined || a.length === 0 || b === undefined || b.length === 0)
|
|
157
|
+
return true;
|
|
158
|
+
return a.some((tag) => b.includes(tag));
|
|
159
|
+
}
|
|
160
|
+
function findScopeConflict(workflow, spec) {
|
|
161
|
+
if (spec.kind !== "write")
|
|
162
|
+
return null;
|
|
163
|
+
for (const other of workflow.stages) {
|
|
164
|
+
if (other.id === spec.id || other.kind !== "write")
|
|
165
|
+
continue;
|
|
166
|
+
if (workflow.runtime.get(other.id)?.status !== "active")
|
|
167
|
+
continue;
|
|
168
|
+
if (scopesIntersect(spec.scope, other.scope))
|
|
169
|
+
return other.id;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
function uncoveredAcceptanceIds(spec, runtime) {
|
|
174
|
+
if (runtime === undefined)
|
|
175
|
+
return [...spec.acceptanceIds];
|
|
176
|
+
return spec.acceptanceIds.filter((id) => !runtime.coveredAcceptanceIds.has(id));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* §8.1 waiting-condition label, mirroring the engine's `computeWaitingOn`
|
|
180
|
+
* check order (active-limit precedes write-scope conflict) so the overlay
|
|
181
|
+
* names the same condition the audit stream recorded. Plan lookups use the
|
|
182
|
+
* latest `plan.state` snapshot instead of the engine's live dependency.
|
|
183
|
+
*/
|
|
184
|
+
function computeWaitingOn(workflow, spec, runtime, planStatuses) {
|
|
185
|
+
const status = runtime?.status ?? "pending";
|
|
186
|
+
if (status === "awaiting_review")
|
|
187
|
+
return "review";
|
|
188
|
+
if (status === "pending") {
|
|
189
|
+
if (workflow.status === "draft")
|
|
190
|
+
return "not-yet-active";
|
|
191
|
+
if ((runtime?.attempts ?? 0) > 0) {
|
|
192
|
+
if (spec.acceptanceIds.length === 0)
|
|
193
|
+
return "retry-wait";
|
|
194
|
+
return `acceptance-missing:[${uncoveredAcceptanceIds(spec, runtime).join(", ")}]`;
|
|
195
|
+
}
|
|
196
|
+
const incomplete = spec.dependsOn.filter((dep) => {
|
|
197
|
+
const dependency = workflow.runtime.get(dep)?.status;
|
|
198
|
+
return dependency !== "completed" && dependency !== "skipped";
|
|
199
|
+
});
|
|
200
|
+
if (incomplete.length > 0)
|
|
201
|
+
return `deps-incomplete:[${incomplete.join(", ")}]`;
|
|
202
|
+
if (spec.planId !== undefined && !planStatuses.has(spec.planId))
|
|
203
|
+
return "plan-dangling";
|
|
204
|
+
const activeCount = [...workflow.runtime.values()].filter((item) => item.status === "active").length;
|
|
205
|
+
if (activeCount >= workflow.maxActiveStages)
|
|
206
|
+
return "active-limit";
|
|
207
|
+
const conflict = findScopeConflict(workflow, spec);
|
|
208
|
+
if (conflict !== null)
|
|
209
|
+
return `write-scope-conflict:[${conflict}]`;
|
|
210
|
+
return "not-yet-active";
|
|
211
|
+
}
|
|
212
|
+
if (status === "active") {
|
|
213
|
+
if (spec.planId === undefined)
|
|
214
|
+
return null;
|
|
215
|
+
const planStatus = planStatuses.get(spec.planId);
|
|
216
|
+
if (planStatus === undefined)
|
|
217
|
+
return "plan-dangling";
|
|
218
|
+
if (planStatus !== "completed")
|
|
219
|
+
return "plan-incomplete";
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function buildStageRows(workflow, planStatuses) {
|
|
225
|
+
const rows = [];
|
|
226
|
+
const visited = new Set();
|
|
227
|
+
const walk = (spec, depth) => {
|
|
228
|
+
if (visited.has(spec.id)) {
|
|
229
|
+
rows.push({
|
|
230
|
+
workflowId: workflow.id,
|
|
231
|
+
stageId: spec.id,
|
|
232
|
+
title: spec.title,
|
|
233
|
+
depth,
|
|
234
|
+
status: workflow.runtime.get(spec.id)?.status ?? "pending",
|
|
235
|
+
waitingOn: null,
|
|
236
|
+
attempts: 0,
|
|
237
|
+
evidenceCount: 0,
|
|
238
|
+
scopeConflict: null,
|
|
239
|
+
backReference: true,
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
visited.add(spec.id);
|
|
244
|
+
const runtime = workflow.runtime.get(spec.id);
|
|
245
|
+
rows.push({
|
|
246
|
+
workflowId: workflow.id,
|
|
247
|
+
stageId: spec.id,
|
|
248
|
+
title: spec.title,
|
|
249
|
+
depth,
|
|
250
|
+
status: runtime?.status ?? "pending",
|
|
251
|
+
waitingOn: computeWaitingOn(workflow, spec, runtime, planStatuses),
|
|
252
|
+
attempts: runtime?.attempts ?? 0,
|
|
253
|
+
evidenceCount: runtime?.evidenceCount ?? 0,
|
|
254
|
+
scopeConflict: findScopeConflict(workflow, spec),
|
|
255
|
+
backReference: false,
|
|
256
|
+
});
|
|
257
|
+
for (const child of workflow.stages.filter((item) => item.dependsOn.includes(spec.id))) {
|
|
258
|
+
walk(child, depth + 1);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
for (const root of workflow.stages.filter((item) => item.dependsOn.length === 0)) {
|
|
262
|
+
walk(root, 0);
|
|
263
|
+
}
|
|
264
|
+
// Stages unreachable from a root (defensive; the engine validates acyclic
|
|
265
|
+
// graphs) still render at the top level, mirroring the L1 text panorama.
|
|
266
|
+
for (const spec of workflow.stages) {
|
|
267
|
+
if (!visited.has(spec.id))
|
|
268
|
+
walk(spec, 0);
|
|
269
|
+
}
|
|
270
|
+
return rows;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Build the L2/L3 view model from session entries: the latest
|
|
274
|
+
* `workflow.state` snapshot plus the `workflow.transition` audit stream
|
|
275
|
+
* (newest first, capped). Malformed records are skipped, never thrown.
|
|
276
|
+
*/
|
|
277
|
+
export function buildWorkflowViewModel(entries, transitionLimit = 30) {
|
|
278
|
+
let stateData;
|
|
279
|
+
let hasStateEntry = false;
|
|
280
|
+
let planData;
|
|
281
|
+
const transitions = [];
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
if (entry.type !== "custom")
|
|
284
|
+
continue;
|
|
285
|
+
if (entry.customType === "workflow.state") {
|
|
286
|
+
hasStateEntry = true;
|
|
287
|
+
stateData = entry.data;
|
|
288
|
+
}
|
|
289
|
+
else if (entry.customType === "plan.state") {
|
|
290
|
+
planData = entry.data;
|
|
291
|
+
}
|
|
292
|
+
else if (entry.customType === "workflow.transition") {
|
|
293
|
+
const parsed = parseTransition(entry.data);
|
|
294
|
+
if (parsed !== undefined)
|
|
295
|
+
transitions.push(parsed);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const planStatuses = parsePlanStatuses(planData);
|
|
299
|
+
let blocks;
|
|
300
|
+
if (hasStateEntry) {
|
|
301
|
+
const workflows = isRecord(stateData) && Array.isArray(stateData.workflows) ? stateData.workflows : [];
|
|
302
|
+
blocks = workflows
|
|
303
|
+
.map((item) => {
|
|
304
|
+
const parsed = parseWorkflowRecord(item);
|
|
305
|
+
if (parsed === undefined)
|
|
306
|
+
return undefined;
|
|
307
|
+
return {
|
|
308
|
+
workflowId: parsed.id,
|
|
309
|
+
title: parsed.title,
|
|
310
|
+
status: parsed.status,
|
|
311
|
+
revision: parsed.revision,
|
|
312
|
+
goalId: parsed.goalId,
|
|
313
|
+
activeCount: [...parsed.runtime.values()].filter((item) => item.status === "active").length,
|
|
314
|
+
maxActiveStages: parsed.maxActiveStages,
|
|
315
|
+
retreatCount: parsed.retreatCount,
|
|
316
|
+
maxRetreats: parsed.maxRetreats,
|
|
317
|
+
diagnostics: [...parsed.diagnostics],
|
|
318
|
+
stages: buildStageRows(parsed, planStatuses),
|
|
319
|
+
};
|
|
320
|
+
})
|
|
321
|
+
.filter((block) => block !== undefined);
|
|
322
|
+
}
|
|
323
|
+
const activeRecord = blocks?.filter((block) => block.status === "draft" || block.status === "running").at(-1);
|
|
324
|
+
const activeWorkflowId = activeRecord?.workflowId ?? blocks?.at(-1)?.workflowId;
|
|
325
|
+
return {
|
|
326
|
+
panorama: blocks,
|
|
327
|
+
transitions: transitions.slice(-transitionLimit).reverse(),
|
|
328
|
+
activeWorkflowId,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/** Action text injected into the conversation input, e.g. `approve stage s2-review`. */
|
|
332
|
+
export function buildWorkflowActionText(action, workflowId, stageId) {
|
|
333
|
+
if (action === "cancel" || stageId === undefined)
|
|
334
|
+
return `cancel workflow ${workflowId}`;
|
|
335
|
+
return `${action} stage ${stageId}`;
|
|
336
|
+
}
|
|
337
|
+
function stageStatusColor(status) {
|
|
338
|
+
switch (status) {
|
|
339
|
+
case "active":
|
|
340
|
+
return (text) => theme.fg("accent", text);
|
|
341
|
+
case "awaiting_review":
|
|
342
|
+
return (text) => theme.fg("warning", text);
|
|
343
|
+
case "completed":
|
|
344
|
+
return (text) => theme.fg("success", text);
|
|
345
|
+
case "failed":
|
|
346
|
+
return (text) => theme.fg("error", text);
|
|
347
|
+
case "skipped":
|
|
348
|
+
return (text) => theme.fg("dim", text);
|
|
349
|
+
case "pending":
|
|
350
|
+
return (text) => theme.fg("muted", text);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function workflowStatusColor(status) {
|
|
354
|
+
if (status === "running")
|
|
355
|
+
return (text) => theme.fg("accent", text);
|
|
356
|
+
if (status === "completed")
|
|
357
|
+
return (text) => theme.fg("success", text);
|
|
358
|
+
if (status === "failed")
|
|
359
|
+
return (text) => theme.fg("error", text);
|
|
360
|
+
return (text) => theme.fg("dim", text);
|
|
361
|
+
}
|
|
362
|
+
function transitionCodeColor(code) {
|
|
363
|
+
if (code.includes("failed") || code.includes("retreat") || code.includes("error") || code.includes("limit")) {
|
|
364
|
+
return (text) => theme.fg("error", text);
|
|
365
|
+
}
|
|
366
|
+
if (code.includes("held") || code.includes("retry") || code.includes("amended")) {
|
|
367
|
+
return (text) => theme.fg("warning", text);
|
|
368
|
+
}
|
|
369
|
+
return (text) => theme.fg("muted", text);
|
|
370
|
+
}
|
|
371
|
+
/** Renders the visible row window; reads live state from the owning component. */
|
|
372
|
+
class WorkflowRowsView {
|
|
373
|
+
view;
|
|
374
|
+
constructor(view) {
|
|
375
|
+
this.view = view;
|
|
376
|
+
}
|
|
377
|
+
invalidate() { }
|
|
378
|
+
render(width) {
|
|
379
|
+
return this.view.renderRows(width);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
export class WorkflowViewComponent extends Container {
|
|
383
|
+
onClose;
|
|
384
|
+
onForwardAction;
|
|
385
|
+
config;
|
|
386
|
+
mode = "panorama";
|
|
387
|
+
rows = [];
|
|
388
|
+
selectedIndex = 0;
|
|
389
|
+
offset = 0;
|
|
390
|
+
titleText;
|
|
391
|
+
modeHintText;
|
|
392
|
+
helpText;
|
|
393
|
+
flashText;
|
|
394
|
+
cache;
|
|
395
|
+
flashTimeout = null;
|
|
396
|
+
constructor(config) {
|
|
397
|
+
super();
|
|
398
|
+
this.config = config;
|
|
399
|
+
this.titleText = new Text(theme.fg("accent", theme.bold(" Workflow Overview")), 1, 0);
|
|
400
|
+
this.modeHintText = new Text("", 1, 0);
|
|
401
|
+
this.helpText = new Text("", 1, 0);
|
|
402
|
+
this.flashText = new Text("", 1, 0);
|
|
403
|
+
this.updateModeTexts();
|
|
404
|
+
this.addChild(new DynamicBorder());
|
|
405
|
+
this.addChild(this.titleText);
|
|
406
|
+
this.addChild(this.modeHintText);
|
|
407
|
+
this.addChild(new WorkflowRowsView(this));
|
|
408
|
+
this.addChild(this.flashText);
|
|
409
|
+
this.addChild(this.helpText);
|
|
410
|
+
this.addChild(new DynamicBorder());
|
|
411
|
+
}
|
|
412
|
+
dispose() {
|
|
413
|
+
if (this.flashTimeout !== null) {
|
|
414
|
+
clearTimeout(this.flashTimeout);
|
|
415
|
+
this.flashTimeout = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
updateModeTexts() {
|
|
419
|
+
const label = this.mode === "panorama" ? "panorama" : "transition log (newest first)";
|
|
420
|
+
this.modeHintText.setText(theme.fg("dim", ` ${label}`));
|
|
421
|
+
this.helpText.setText(this.buildHelpLine());
|
|
422
|
+
}
|
|
423
|
+
buildHelpLine() {
|
|
424
|
+
const parts = [rawKeyHint("↑↓", "move")];
|
|
425
|
+
if (this.mode === "panorama") {
|
|
426
|
+
parts.push(rawKeyHint("tab/l", "log"), rawKeyHint("a", "approve"), rawKeyHint("r", "reject"), rawKeyHint("s", "skip"), rawKeyHint("c", "cancel"));
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
parts.push(rawKeyHint("tab/l", "panorama"));
|
|
430
|
+
}
|
|
431
|
+
parts.push(keyHint("tui.select.cancel", "close"));
|
|
432
|
+
return ` ${parts.join(" ")}`;
|
|
433
|
+
}
|
|
434
|
+
getModel() {
|
|
435
|
+
const entries = this.config.getEntries();
|
|
436
|
+
if (this.cache && this.cache.entries === entries && this.cache.length === entries.length) {
|
|
437
|
+
return this.cache.model;
|
|
438
|
+
}
|
|
439
|
+
const model = buildWorkflowViewModel(entries);
|
|
440
|
+
this.cache = { entries, length: entries.length, model };
|
|
441
|
+
return model;
|
|
442
|
+
}
|
|
443
|
+
buildRows(model) {
|
|
444
|
+
const rows = [];
|
|
445
|
+
if (this.mode === "log") {
|
|
446
|
+
if (model.transitions.length === 0) {
|
|
447
|
+
rows.push({ text: " no workflow transitions recorded", stage: undefined });
|
|
448
|
+
return rows;
|
|
449
|
+
}
|
|
450
|
+
for (const transition of model.transitions) {
|
|
451
|
+
const stage = transition.stageId !== undefined ? `/${transition.stageId}` : "";
|
|
452
|
+
const detail = transition.detail !== undefined ? ` — ${transition.detail}` : "";
|
|
453
|
+
const code = transitionCodeColor(transition.code)(transition.code);
|
|
454
|
+
rows.push({
|
|
455
|
+
text: ` #${transition.revision} ${transition.workflowId}${stage} ${transition.from} -> ${transition.to} [${transition.trigger}] ${code}: ${transition.message}${detail}`,
|
|
456
|
+
stage: undefined,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return rows;
|
|
460
|
+
}
|
|
461
|
+
const blocks = model.panorama;
|
|
462
|
+
if (blocks === undefined) {
|
|
463
|
+
rows.push({ text: " no workflow.state entry in this session", stage: undefined });
|
|
464
|
+
return rows;
|
|
465
|
+
}
|
|
466
|
+
if (blocks.length === 0) {
|
|
467
|
+
rows.push({ text: " no workflows in this session; create one with workflow_set", stage: undefined });
|
|
468
|
+
return rows;
|
|
469
|
+
}
|
|
470
|
+
for (const block of blocks) {
|
|
471
|
+
const status = workflowStatusColor(block.status)(block.status);
|
|
472
|
+
rows.push({
|
|
473
|
+
text: ` workflow "${block.title}" (${block.workflowId}) — ${status} · revision ${block.revision} · active ${block.activeCount}/${block.maxActiveStages} · retreats ${block.retreatCount}/${block.maxRetreats}`,
|
|
474
|
+
stage: undefined,
|
|
475
|
+
});
|
|
476
|
+
if (block.goalId !== undefined) {
|
|
477
|
+
rows.push({ text: ` goal: ${block.goalId}`, stage: undefined });
|
|
478
|
+
}
|
|
479
|
+
for (const stage of block.stages) {
|
|
480
|
+
rows.push({
|
|
481
|
+
text: this.formatStageRow(stage),
|
|
482
|
+
stage: { workflowId: block.workflowId, stageId: stage.stageId },
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (block.diagnostics.length > 0) {
|
|
486
|
+
rows.push({ text: " diagnostics:", stage: undefined });
|
|
487
|
+
for (const diagnostic of block.diagnostics) {
|
|
488
|
+
rows.push({ text: theme.fg("warning", ` - ${diagnostic}`), stage: undefined });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
rows.push({ text: "", stage: undefined });
|
|
492
|
+
}
|
|
493
|
+
return rows;
|
|
494
|
+
}
|
|
495
|
+
formatStageRow(stage) {
|
|
496
|
+
const pad = " ".repeat(stage.depth + 1);
|
|
497
|
+
if (stage.backReference) {
|
|
498
|
+
return `${pad} -> ${stage.stageId} (shown above)`;
|
|
499
|
+
}
|
|
500
|
+
const bits = [`- ${stage.stageId} [${stageStatusColor(stage.status)(stage.status)}] ${stage.title}`];
|
|
501
|
+
if (stage.waitingOn !== null)
|
|
502
|
+
bits.push(theme.fg("dim", `— waiting: ${stage.waitingOn}`));
|
|
503
|
+
if (stage.attempts > 0)
|
|
504
|
+
bits.push(theme.fg("dim", `· attempts ${stage.attempts}`));
|
|
505
|
+
if (stage.evidenceCount > 0)
|
|
506
|
+
bits.push(theme.fg("dim", `· evidence ${stage.evidenceCount}`));
|
|
507
|
+
if (stage.scopeConflict !== null) {
|
|
508
|
+
bits.push(theme.fg("warning", `⚠ scope:${stage.scopeConflict}`));
|
|
509
|
+
}
|
|
510
|
+
return `${pad}${bits.join(" ")}`;
|
|
511
|
+
}
|
|
512
|
+
/** Rows are rebuilt lazily on access; the key couples rebuild + selection restore. */
|
|
513
|
+
syncRows() {
|
|
514
|
+
const model = this.getModel();
|
|
515
|
+
const previous = this.rows[this.selectedIndex]?.stage;
|
|
516
|
+
this.rows = this.buildRows(model);
|
|
517
|
+
if (previous !== undefined) {
|
|
518
|
+
const restored = this.rows.findIndex((row) => row.stage?.stageId === previous.stageId);
|
|
519
|
+
if (restored !== -1)
|
|
520
|
+
this.selectedIndex = restored;
|
|
521
|
+
}
|
|
522
|
+
if (this.selectedIndex >= this.rows.length)
|
|
523
|
+
this.selectedIndex = Math.max(0, this.rows.length - 1);
|
|
524
|
+
this.clampOffset();
|
|
525
|
+
}
|
|
526
|
+
clampOffset() {
|
|
527
|
+
const maxRows = Math.max(1, this.config.maxRows);
|
|
528
|
+
if (this.selectedIndex < this.offset)
|
|
529
|
+
this.offset = this.selectedIndex;
|
|
530
|
+
if (this.selectedIndex >= this.offset + maxRows)
|
|
531
|
+
this.offset = this.selectedIndex - maxRows + 1;
|
|
532
|
+
if (this.offset < 0)
|
|
533
|
+
this.offset = 0;
|
|
534
|
+
}
|
|
535
|
+
renderRows(width) {
|
|
536
|
+
this.syncRows();
|
|
537
|
+
const maxRows = Math.max(1, this.config.maxRows);
|
|
538
|
+
const lines = [];
|
|
539
|
+
for (let index = this.offset; index < Math.min(this.rows.length, this.offset + maxRows); index++) {
|
|
540
|
+
const row = this.rows[index];
|
|
541
|
+
if (row === undefined)
|
|
542
|
+
continue;
|
|
543
|
+
const selected = index === this.selectedIndex;
|
|
544
|
+
const prefix = selected ? theme.fg("accent", "→ ") : " ";
|
|
545
|
+
lines.push(truncateToWidth(`${prefix}${row.text}`, width));
|
|
546
|
+
}
|
|
547
|
+
const hiddenAbove = this.offset;
|
|
548
|
+
const hiddenBelow = this.rows.length - this.offset - maxRows;
|
|
549
|
+
const hidden = [];
|
|
550
|
+
if (hiddenAbove > 0)
|
|
551
|
+
hidden.push(`↑${hiddenAbove}`);
|
|
552
|
+
if (hiddenBelow > 0)
|
|
553
|
+
hidden.push(`↓${hiddenBelow}`);
|
|
554
|
+
// Fixed output height (exactly maxRows content rows plus this final
|
|
555
|
+
// indicator row), the same contract as agents-view renderRows: the
|
|
556
|
+
// overlay's grid footprint must not change with mode switches or row
|
|
557
|
+
// counts, or the chat/editor layout boundary moves between frames.
|
|
558
|
+
while (lines.length < maxRows)
|
|
559
|
+
lines.push(truncateToWidth(" ", width));
|
|
560
|
+
lines.push(truncateToWidth(hidden.length > 0 ? theme.fg("dim", ` ${hidden.join(" ")}`) : " ", width));
|
|
561
|
+
return lines;
|
|
562
|
+
}
|
|
563
|
+
move(delta) {
|
|
564
|
+
if (this.rows.length === 0)
|
|
565
|
+
return;
|
|
566
|
+
this.selectedIndex = Math.min(this.rows.length - 1, Math.max(0, this.selectedIndex + delta));
|
|
567
|
+
this.clampOffset();
|
|
568
|
+
}
|
|
569
|
+
flash(message) {
|
|
570
|
+
this.flashText.setText(theme.fg("warning", ` ${message}`));
|
|
571
|
+
if (this.flashTimeout !== null)
|
|
572
|
+
clearTimeout(this.flashTimeout);
|
|
573
|
+
this.flashTimeout = setTimeout(() => {
|
|
574
|
+
this.flashText.setText("");
|
|
575
|
+
this.flashTimeout = null;
|
|
576
|
+
this.config.requestRender?.();
|
|
577
|
+
}, 2500);
|
|
578
|
+
this.config.requestRender?.();
|
|
579
|
+
}
|
|
580
|
+
toggleMode() {
|
|
581
|
+
this.mode = this.mode === "panorama" ? "log" : "panorama";
|
|
582
|
+
this.selectedIndex = 0;
|
|
583
|
+
this.offset = 0;
|
|
584
|
+
this.updateModeTexts();
|
|
585
|
+
this.config.requestRender?.();
|
|
586
|
+
}
|
|
587
|
+
forward(action) {
|
|
588
|
+
const selected = this.rows[this.selectedIndex];
|
|
589
|
+
if (selected?.stage === undefined) {
|
|
590
|
+
if (action === "cancel") {
|
|
591
|
+
const workflowId = this.getModel().activeWorkflowId;
|
|
592
|
+
if (workflowId === undefined) {
|
|
593
|
+
this.flash("No workflow to cancel");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
this.onForwardAction?.(buildWorkflowActionText(action, workflowId, undefined));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
this.flash("Select a stage row first");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this.onForwardAction?.(buildWorkflowActionText(action, selected.stage.workflowId, selected.stage.stageId));
|
|
603
|
+
}
|
|
604
|
+
handleInput(keyData) {
|
|
605
|
+
const kb = getKeybindings();
|
|
606
|
+
if (kb.matches(keyData, "tui.select.cancel")) {
|
|
607
|
+
this.onClose?.();
|
|
608
|
+
}
|
|
609
|
+
else if (kb.matches(keyData, "tui.input.tab") || keyData === "l") {
|
|
610
|
+
this.toggleMode();
|
|
611
|
+
}
|
|
612
|
+
else if (kb.matches(keyData, "tui.select.up") || keyData === "k") {
|
|
613
|
+
this.move(-1);
|
|
614
|
+
}
|
|
615
|
+
else if (kb.matches(keyData, "tui.select.down") || keyData === "j") {
|
|
616
|
+
this.move(1);
|
|
617
|
+
}
|
|
618
|
+
else if (kb.matches(keyData, "tui.select.pageUp")) {
|
|
619
|
+
this.move(-Math.max(1, this.config.maxRows));
|
|
620
|
+
}
|
|
621
|
+
else if (kb.matches(keyData, "tui.select.pageDown")) {
|
|
622
|
+
this.move(Math.max(1, this.config.maxRows));
|
|
623
|
+
}
|
|
624
|
+
else if (keyData === "a") {
|
|
625
|
+
this.forward("approve");
|
|
626
|
+
}
|
|
627
|
+
else if (keyData === "r") {
|
|
628
|
+
this.forward("reject");
|
|
629
|
+
}
|
|
630
|
+
else if (keyData === "s") {
|
|
631
|
+
this.forward("skip");
|
|
632
|
+
}
|
|
633
|
+
else if (keyData === "c") {
|
|
634
|
+
this.forward("cancel");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
//# sourceMappingURL=workflow-view.js.map
|