@astralyn/pi 1.4.9 → 1.4.11
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 +69 -0
- package/dist/bundle/chunks/{chunk-BAGHIP5J.js → chunk-NUQMVNKQ.js} +3 -3
- package/dist/bundle/chunks/{chunk-3PKXUKTB.js → chunk-PUU43PDV.js} +121 -136
- package/dist/bundle/chunks/{chunk-F66AY3WR.js → chunk-TH3QDDPD.js} +1 -1
- package/dist/bundle/chunks/{execute-P22VMM2H.js → execute-VLAIP26K.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-VKMBQTR2.js → virtual-modules-63XNTSVN.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/core/agent-session.d.ts +34 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +99 -5
- package/dist/core/agent-session.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/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +14 -7
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/isolated-session.d.ts +7 -2
- package/dist/core/isolated-session.d.ts.map +1 -1
- package/dist/core/isolated-session.js +7 -3
- package/dist/core/isolated-session.js.map +1 -1
- package/dist/core/session-manager.d.ts +17 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +29 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tasks/delivery.d.ts +31 -12
- package/dist/core/tasks/delivery.d.ts.map +1 -1
- package/dist/core/tasks/delivery.js +97 -36
- package/dist/core/tasks/delivery.js.map +1 -1
- package/dist/core/tasks/execution.d.ts +27 -14
- package/dist/core/tasks/execution.d.ts.map +1 -1
- package/dist/core/tasks/execution.js +90 -86
- package/dist/core/tasks/execution.js.map +1 -1
- package/dist/core/tasks/limits.d.ts +7 -11
- package/dist/core/tasks/limits.d.ts.map +1 -1
- package/dist/core/tasks/limits.js +8 -16
- package/dist/core/tasks/limits.js.map +1 -1
- package/dist/core/tasks/output.d.ts +9 -1
- package/dist/core/tasks/output.d.ts.map +1 -1
- package/dist/core/tasks/output.js +23 -10
- package/dist/core/tasks/output.js.map +1 -1
- package/dist/core/tasks/presentation.d.ts +1 -1
- package/dist/core/tasks/presentation.d.ts.map +1 -1
- package/dist/core/tasks/presentation.js +6 -4
- package/dist/core/tasks/presentation.js.map +1 -1
- package/dist/core/tasks/record.d.ts +41 -29
- package/dist/core/tasks/record.d.ts.map +1 -1
- package/dist/core/tasks/record.js +82 -37
- package/dist/core/tasks/record.js.map +1 -1
- package/dist/core/tasks/reminder.js +2 -2
- package/dist/core/tasks/reminder.js.map +1 -1
- package/dist/core/tasks/runtime.d.ts +15 -23
- package/dist/core/tasks/runtime.d.ts.map +1 -1
- package/dist/core/tasks/runtime.js +50 -178
- package/dist/core/tasks/runtime.js.map +1 -1
- package/dist/core/tasks/scheduler.d.ts +3 -1
- package/dist/core/tasks/scheduler.d.ts.map +1 -1
- package/dist/core/tasks/scheduler.js +16 -12
- package/dist/core/tasks/scheduler.js.map +1 -1
- package/dist/core/tasks/session.d.ts +24 -6
- package/dist/core/tasks/session.d.ts.map +1 -1
- package/dist/core/tasks/session.js +70 -22
- package/dist/core/tasks/session.js.map +1 -1
- package/dist/core/tasks/store.d.ts +17 -4
- package/dist/core/tasks/store.d.ts.map +1 -1
- package/dist/core/tasks/store.js +45 -48
- package/dist/core/tasks/store.js.map +1 -1
- package/dist/core/tasks/types.d.ts +64 -93
- package/dist/core/tasks/types.d.ts.map +1 -1
- package/dist/core/tasks/types.js +7 -0
- package/dist/core/tasks/types.js.map +1 -1
- package/dist/core/tools/shell-execution.d.ts.map +1 -1
- package/dist/core/tools/shell-execution.js +2 -1
- package/dist/core/tools/shell-execution.js.map +1 -1
- package/dist/core/tools/shell-tool.d.ts.map +1 -1
- package/dist/core/tools/shell-tool.js +20 -13
- package/dist/core/tools/shell-tool.js.map +1 -1
- package/dist/extensions/agent/activity-lines.d.ts +10 -0
- package/dist/extensions/agent/activity-lines.d.ts.map +1 -0
- package/dist/extensions/agent/activity-lines.js +76 -0
- package/dist/extensions/agent/activity-lines.js.map +1 -0
- package/dist/extensions/agent/activity.d.ts +25 -4
- package/dist/extensions/agent/activity.d.ts.map +1 -1
- package/dist/extensions/agent/activity.js +76 -21
- package/dist/extensions/agent/activity.js.map +1 -1
- package/dist/extensions/agent/artifacts.d.ts +15 -0
- package/dist/extensions/agent/artifacts.d.ts.map +1 -0
- package/dist/extensions/agent/artifacts.js +44 -0
- package/dist/extensions/agent/artifacts.js.map +1 -0
- package/dist/extensions/agent/progress.d.ts +30 -4
- package/dist/extensions/agent/progress.d.ts.map +1 -1
- package/dist/extensions/agent/progress.js +84 -11
- package/dist/extensions/agent/progress.js.map +1 -1
- package/dist/extensions/agent/render.d.ts.map +1 -1
- package/dist/extensions/agent/render.js +8 -24
- package/dist/extensions/agent/render.js.map +1 -1
- package/dist/extensions/agent/report.d.ts +11 -3
- package/dist/extensions/agent/report.d.ts.map +1 -1
- package/dist/extensions/agent/report.js +32 -7
- package/dist/extensions/agent/report.js.map +1 -1
- package/dist/extensions/agent/tool.d.ts.map +1 -1
- package/dist/extensions/agent/tool.js +20 -5
- package/dist/extensions/agent/tool.js.map +1 -1
- package/dist/extensions/agent/types.d.ts +2 -0
- package/dist/extensions/agent/types.d.ts.map +1 -1
- package/dist/extensions/agent/types.js.map +1 -1
- package/dist/extensions/agent/worker.d.ts +7 -0
- package/dist/extensions/agent/worker.d.ts.map +1 -1
- package/dist/extensions/agent/worker.js +9 -3
- package/dist/extensions/agent/worker.js.map +1 -1
- package/dist/extensions/cached-compaction/summary.d.ts +1 -1
- package/dist/extensions/cached-compaction/summary.d.ts.map +1 -1
- package/dist/extensions/cached-compaction/summary.js +13 -16
- package/dist/extensions/cached-compaction/summary.js.map +1 -1
- package/dist/extensions/search/index.d.ts.map +1 -1
- package/dist/extensions/search/index.js +6 -7
- package/dist/extensions/search/index.js.map +1 -1
- package/dist/extensions/tasks/tool/actions.d.ts +1 -2
- package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/actions.js +12 -80
- package/dist/extensions/tasks/tool/actions.js.map +1 -1
- package/dist/extensions/tasks/tool/constants.d.ts +1 -3
- package/dist/extensions/tasks/tool/constants.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/constants.js +1 -3
- package/dist/extensions/tasks/tool/constants.js.map +1 -1
- package/dist/extensions/tasks/tool/schema.d.ts +2 -3
- package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/schema.js +10 -18
- package/dist/extensions/tasks/tool/schema.js.map +1 -1
- package/dist/extensions/tasks/tool/types.d.ts +1 -17
- package/dist/extensions/tasks/tool/types.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/types.js.map +1 -1
- package/dist/extensions/tasks/tool.d.ts +1 -2
- package/dist/extensions/tasks/tool.d.ts.map +1 -1
- package/dist/extensions/tasks/tool.js +5 -6
- package/dist/extensions/tasks/tool.js.map +1 -1
- package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/completion-render.js +2 -2
- package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
- package/dist/extensions/tasks/ui/footer.d.ts +37 -0
- package/dist/extensions/tasks/ui/footer.d.ts.map +1 -0
- package/dist/extensions/tasks/ui/footer.js +129 -0
- package/dist/extensions/tasks/ui/footer.js.map +1 -0
- package/dist/extensions/tasks/ui/frame.d.ts +36 -0
- package/dist/extensions/tasks/ui/frame.d.ts.map +1 -0
- package/dist/extensions/tasks/ui/frame.js +33 -0
- package/dist/extensions/tasks/ui/frame.js.map +1 -0
- package/dist/extensions/tasks/ui/help.d.ts +6 -0
- package/dist/extensions/tasks/ui/help.d.ts.map +1 -0
- package/dist/extensions/tasks/ui/help.js +53 -0
- package/dist/extensions/tasks/ui/help.js.map +1 -0
- package/dist/extensions/tasks/ui/inspector.d.ts +4 -3
- package/dist/extensions/tasks/ui/inspector.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/inspector.js +25 -6
- package/dist/extensions/tasks/ui/inspector.js.map +1 -1
- package/dist/extensions/tasks/ui/layout.d.ts +15 -18
- package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/layout.js +162 -255
- package/dist/extensions/tasks/ui/layout.js.map +1 -1
- package/dist/extensions/tasks/ui/list.d.ts +3 -1
- package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/list.js +19 -14
- package/dist/extensions/tasks/ui/list.js.map +1 -1
- package/dist/extensions/tasks/ui/manager.d.ts +17 -5
- package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/manager.js +147 -90
- package/dist/extensions/tasks/ui/manager.js.map +1 -1
- package/dist/extensions/tasks/ui/model.d.ts +32 -5
- package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/model.js +7 -7
- package/dist/extensions/tasks/ui/model.js.map +1 -1
- package/dist/extensions/tasks/ui/panes.d.ts +40 -0
- package/dist/extensions/tasks/ui/panes.d.ts.map +1 -0
- package/dist/extensions/tasks/ui/panes.js +144 -0
- package/dist/extensions/tasks/ui/panes.js.map +1 -0
- package/dist/extensions/tasks/ui/render.d.ts +2 -16
- package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/render.js +13 -85
- package/dist/extensions/tasks/ui/render.js.map +1 -1
- package/dist/extensions/tasks/ui/viewport.d.ts +12 -4
- package/dist/extensions/tasks/ui/viewport.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/viewport.js +23 -7
- package/dist/extensions/tasks/ui/viewport.js.map +1 -1
- package/dist/extensions/tasks/ui/views.d.ts +12 -6
- package/dist/extensions/tasks/ui/views.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/views.js +39 -32
- package/dist/extensions/tasks/ui/views.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +11 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +43 -17
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +38 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +11 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +48 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/bundled/extensions/agent.md +39 -7
- package/docs/bundled/extensions/statusline.md +1 -1
- package/docs/bundled/tasks.md +28 -37
- package/docs/bundled/tool-presentation.md +1 -1
- package/docs/extensions.md +15 -13
- package/docs/json.md +3 -0
- package/docs/models-configuration.md +1 -1
- package/docs/models.md +7 -85
- package/docs/rpc.md +14 -0
- package/docs/sdk.md +12 -2
- package/docs/session-format.md +11 -5
- package/docs/sessions.md +1 -1
- package/docs/usage.md +2 -0
- package/package.json +3 -4
- package/dist/modes/interactive/tool-view/tool-execution.d.ts +0 -14
- package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +0 -1
- package/dist/modes/interactive/tool-view/tool-execution.js +0 -13
- package/dist/modes/interactive/tool-view/tool-execution.js.map +0 -1
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/npm-shrinkwrap.json +0 -4207
|
@@ -1,7 +1,33 @@
|
|
|
1
|
+
import type { Input } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { AppKeybinding } from "../../../core/keybindings.ts";
|
|
1
3
|
import { type TaskSnapshot } from "../../../core/tasks/types.ts";
|
|
2
4
|
import type { TaskViewport } from "./viewport.ts";
|
|
3
5
|
export type TasksTab = "output" | "info";
|
|
4
6
|
export type PanelFocus = "list" | "inspector";
|
|
7
|
+
/** What activating a rendered cell does; the manager dispatches on `kind` alone. */
|
|
8
|
+
export type TaskAction = {
|
|
9
|
+
kind: "select";
|
|
10
|
+
taskId: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "tab";
|
|
13
|
+
tab: TasksTab;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "key";
|
|
16
|
+
key: AppKeybinding;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "open";
|
|
19
|
+
}
|
|
20
|
+
/** Escape: unwind one layer, return from the inspector in a narrow terminal, or close the panel. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "back";
|
|
23
|
+
};
|
|
24
|
+
/** A clickable row of cells on the screen and the action a click on it performs. */
|
|
25
|
+
export interface TaskHit {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
action: TaskAction;
|
|
30
|
+
}
|
|
5
31
|
/**
|
|
6
32
|
* The one transient layer over the browsing panel. Each layer owns its data, so a stale
|
|
7
33
|
* combination (help over a confirmation, a restore point without a search) cannot exist.
|
|
@@ -9,13 +35,14 @@ export type PanelFocus = "list" | "inspector";
|
|
|
9
35
|
export type PanelMode = {
|
|
10
36
|
kind: "browse";
|
|
11
37
|
}
|
|
12
|
-
/** Searching moves focus to the list; `restore` is where Esc returns to. */
|
|
38
|
+
/** Searching moves focus to the list; `restore` is where Esc returns to. The input owns the query. */
|
|
13
39
|
| {
|
|
14
40
|
kind: "search";
|
|
15
41
|
restore: {
|
|
16
42
|
id?: string;
|
|
17
43
|
focus: PanelFocus;
|
|
18
44
|
};
|
|
45
|
+
input: Input;
|
|
19
46
|
} | {
|
|
20
47
|
kind: "confirmStop";
|
|
21
48
|
taskId: string;
|
|
@@ -37,14 +64,14 @@ export declare class TaskSelection {
|
|
|
37
64
|
readonly state: TasksPanelState;
|
|
38
65
|
all: TaskSnapshot[];
|
|
39
66
|
rows: TaskSnapshot[];
|
|
40
|
-
query: string;
|
|
41
67
|
constructor(state: TasksPanelState);
|
|
42
68
|
get selected(): TaskSnapshot | undefined;
|
|
43
69
|
get activeCount(): number;
|
|
44
|
-
sync(all: TaskSnapshot[]): void;
|
|
45
|
-
select(id: string | undefined): void;
|
|
70
|
+
sync(all: TaskSnapshot[], query?: string): void;
|
|
71
|
+
select(id: string | undefined, query?: string): void;
|
|
46
72
|
/** On reopen, prefer the newest work started since the panel closed over the remembered row. */
|
|
47
73
|
selectStartedSinceClose(): void;
|
|
48
|
-
|
|
74
|
+
/** Rows are the retained tasks matching `query`; an empty query matches all of them. */
|
|
75
|
+
refresh(query?: string): void;
|
|
49
76
|
}
|
|
50
77
|
//# sourceMappingURL=model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE;AACpB,
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC;AAE9C,oFAAoF;AACpF,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,QAAQ,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,oGAAoG;GAClG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpB,oFAAoF;AACpF,MAAM,WAAW,OAAO;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE;AACpB,sGAAsG;GACpG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,CAAC;AAE5C,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG,OAAO,CAEvE;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC/B,GAAG,EAAE,QAAQ,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8FAA8F;AAC9F,qBAAa,aAAa;IACzB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,GAAG,EAAE,YAAY,EAAE,CAAM;IACzB,IAAI,EAAE,YAAY,EAAE,CAAM;gBACd,KAAK,EAAE,eAAe;IAGlC,IAAI,QAAQ,IAAI,YAAY,GAAG,SAAS,CAEvC;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IACD,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,KAAK,SAAK,GAAG,IAAI;IAI3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,SAAK,GAAG,IAAI;IAIhD,gGAAgG;IAChG,uBAAuB,IAAI,IAAI;IAM/B,wFAAwF;IACxF,OAAO,CAAC,KAAK,SAAK,GAAG,IAAI;CAmBzB"}
|
|
@@ -8,7 +8,6 @@ export class TaskSelection {
|
|
|
8
8
|
constructor(state) {
|
|
9
9
|
this.all = [];
|
|
10
10
|
this.rows = [];
|
|
11
|
-
this.query = "";
|
|
12
11
|
this.state = state;
|
|
13
12
|
}
|
|
14
13
|
get selected() {
|
|
@@ -17,13 +16,13 @@ export class TaskSelection {
|
|
|
17
16
|
get activeCount() {
|
|
18
17
|
return this.all.filter((task) => !isTaskTerminal(task.status)).length;
|
|
19
18
|
}
|
|
20
|
-
sync(all) {
|
|
19
|
+
sync(all, query = "") {
|
|
21
20
|
this.all = all;
|
|
22
|
-
this.refresh();
|
|
21
|
+
this.refresh(query);
|
|
23
22
|
}
|
|
24
|
-
select(id) {
|
|
23
|
+
select(id, query = "") {
|
|
25
24
|
this.state.selectedId = id;
|
|
26
|
-
this.refresh();
|
|
25
|
+
this.refresh(query);
|
|
27
26
|
}
|
|
28
27
|
/** On reopen, prefer the newest work started since the panel closed over the remembered row. */
|
|
29
28
|
selectStartedSinceClose() {
|
|
@@ -34,7 +33,8 @@ export class TaskSelection {
|
|
|
34
33
|
if (fresh.length)
|
|
35
34
|
this.select(fresh.reduce((a, b) => (b.startedAt > a.startedAt ? b : a)).id);
|
|
36
35
|
}
|
|
37
|
-
|
|
36
|
+
/** Rows are the retained tasks matching `query`; an empty query matches all of them. */
|
|
37
|
+
refresh(query = "") {
|
|
38
38
|
const { selectedId } = this.state;
|
|
39
39
|
const active = this.all
|
|
40
40
|
.filter((task) => !isTaskTerminal(task.status))
|
|
@@ -43,7 +43,7 @@ export class TaskSelection {
|
|
|
43
43
|
.filter((task) => isTaskTerminal(task.status))
|
|
44
44
|
.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0) || a.id.localeCompare(b.id));
|
|
45
45
|
const rows = [...active, ...history];
|
|
46
|
-
const search =
|
|
46
|
+
const search = query.toLocaleLowerCase().trim();
|
|
47
47
|
this.rows = search
|
|
48
48
|
? rows.filter((task) => `${task.title}\n${task.input?.text ?? ""}\n${task.label}\n${task.kind}\n${task.id}`
|
|
49
49
|
.toLocaleLowerCase()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/model.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/model.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Input } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding } from \"../../../core/keybindings.ts\";\nimport { isTaskTerminal, type TaskSnapshot } from \"../../../core/tasks/types.ts\";\nimport type { TaskViewport } from \"./viewport.ts\";\n\nexport type TasksTab = \"output\" | \"info\";\nexport type PanelFocus = \"list\" | \"inspector\";\n\n/** What activating a rendered cell does; the manager dispatches on `kind` alone. */\nexport type TaskAction =\n\t| { kind: \"select\"; taskId: string }\n\t| { kind: \"tab\"; tab: TasksTab }\n\t| { kind: \"key\"; key: AppKeybinding }\n\t| { kind: \"open\" }\n\t/** Escape: unwind one layer, return from the inspector in a narrow terminal, or close the panel. */\n\t| { kind: \"back\" };\n\n/** A clickable row of cells on the screen and the action a click on it performs. */\nexport interface TaskHit {\n\tx: number;\n\ty: number;\n\twidth: number;\n\taction: TaskAction;\n}\n\n/**\n * The one transient layer over the browsing panel. Each layer owns its data, so a stale\n * combination (help over a confirmation, a restore point without a search) cannot exist.\n */\nexport type PanelMode =\n\t| { kind: \"browse\" }\n\t/** Searching moves focus to the list; `restore` is where Esc returns to. The input owns the query. */\n\t| { kind: \"search\"; restore: { id?: string; focus: PanelFocus }; input: Input }\n\t| { kind: \"confirmStop\"; taskId: string }\n\t| { kind: \"help\"; viewport: TaskViewport };\n\n/** A task can be asked to stop while it is neither settled nor already stopping. */\nexport function isStoppable(task: Pick<TaskSnapshot, \"status\">): boolean {\n\treturn !isTaskTerminal(task.status) && task.status !== \"stopping\";\n}\n\n/** Remember navigation within one runtime, without retaining output or execution handles. */\nexport interface TasksPanelState {\n\ttab: TasksTab;\n\tselectedId?: string;\n\t/** When the panel last closed; reopening selects work started since then. */\n\tclosedAt?: number;\n}\n\n/** Selection follows identity, including when its row moves from active work into history. */\nexport class TaskSelection {\n\treadonly state: TasksPanelState;\n\tall: TaskSnapshot[] = [];\n\trows: TaskSnapshot[] = [];\n\tconstructor(state: TasksPanelState) {\n\t\tthis.state = state;\n\t}\n\tget selected(): TaskSnapshot | undefined {\n\t\treturn this.rows.find((task) => task.id === this.state.selectedId);\n\t}\n\tget activeCount(): number {\n\t\treturn this.all.filter((task) => !isTaskTerminal(task.status)).length;\n\t}\n\tsync(all: TaskSnapshot[], query = \"\"): void {\n\t\tthis.all = all;\n\t\tthis.refresh(query);\n\t}\n\tselect(id: string | undefined, query = \"\"): void {\n\t\tthis.state.selectedId = id;\n\t\tthis.refresh(query);\n\t}\n\t/** On reopen, prefer the newest work started since the panel closed over the remembered row. */\n\tselectStartedSinceClose(): void {\n\t\tconst { closedAt } = this.state;\n\t\tif (closedAt === undefined) return;\n\t\tconst fresh = this.rows.filter((task) => task.startedAt > closedAt);\n\t\tif (fresh.length) this.select(fresh.reduce((a, b) => (b.startedAt > a.startedAt ? b : a)).id);\n\t}\n\t/** Rows are the retained tasks matching `query`; an empty query matches all of them. */\n\trefresh(query = \"\"): void {\n\t\tconst { selectedId } = this.state;\n\t\tconst active = this.all\n\t\t\t.filter((task) => !isTaskTerminal(task.status))\n\t\t\t.sort((a, b) => b.startedAt - a.startedAt || a.id.localeCompare(b.id));\n\t\tconst history = this.all\n\t\t\t.filter((task) => isTaskTerminal(task.status))\n\t\t\t.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0) || a.id.localeCompare(b.id));\n\t\tconst rows = [...active, ...history];\n\t\tconst search = query.toLocaleLowerCase().trim();\n\t\tthis.rows = search\n\t\t\t? rows.filter((task) =>\n\t\t\t\t\t`${task.title}\\n${task.input?.text ?? \"\"}\\n${task.label}\\n${task.kind}\\n${task.id}`\n\t\t\t\t\t\t.toLocaleLowerCase()\n\t\t\t\t\t\t.includes(search),\n\t\t\t\t)\n\t\t\t: rows;\n\t\tif (!this.rows.some((task) => task.id === selectedId)) this.state.selectedId = this.rows[0]?.id;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Keybinding } from "../../../core/keybindings.ts";
|
|
2
|
+
import type { Theme } from "../../../modes/interactive/theme/theme.ts";
|
|
3
|
+
import { type Rect } from "./frame.ts";
|
|
4
|
+
import type { TaskInspector } from "./inspector.ts";
|
|
5
|
+
import type { TaskList } from "./list.ts";
|
|
6
|
+
import type { TaskHit, TaskSelection } from "./model.ts";
|
|
7
|
+
export interface PaneRender {
|
|
8
|
+
lines: string[];
|
|
9
|
+
hits: TaskHit[];
|
|
10
|
+
}
|
|
11
|
+
interface PaneEnv {
|
|
12
|
+
theme: Theme;
|
|
13
|
+
selection: TaskSelection;
|
|
14
|
+
}
|
|
15
|
+
export interface ListPaneOptions {
|
|
16
|
+
/** The focused pane has an accented border. */
|
|
17
|
+
active: boolean;
|
|
18
|
+
/** Whether the rows can be clicked. */
|
|
19
|
+
interactive: boolean;
|
|
20
|
+
/** The rendered search input, shown as the first content row while searching. */
|
|
21
|
+
searchRow?: string;
|
|
22
|
+
/** The task a pending stop question is about. */
|
|
23
|
+
stopTargetId?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Task rows between the borders, and how many tasks a page moves. */
|
|
26
|
+
export declare function renderListPane(env: PaneEnv & {
|
|
27
|
+
list: TaskList;
|
|
28
|
+
}, rect: Rect, options: ListPaneOptions): PaneRender & {
|
|
29
|
+
pageSize: number;
|
|
30
|
+
};
|
|
31
|
+
export interface InspectorPaneOptions {
|
|
32
|
+
active: boolean;
|
|
33
|
+
interactive: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function renderInspectorPane(env: PaneEnv & {
|
|
36
|
+
inspector: TaskInspector;
|
|
37
|
+
hint: (key: Keybinding) => string;
|
|
38
|
+
}, rect: Rect, options: InspectorPaneOptions): PaneRender;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=panes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/panes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAK/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAa,KAAK,IAAI,EAAgB,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAY,MAAM,YAAY,CAAC;AAKnE,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,EAAE,OAAO,EAAE,CAAC;CAChB;AAED,UAAU,OAAO;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC/B,+CAA+C;IAC/C,MAAM,EAAE,OAAO,CAAC;IAChB,uCAAuC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAC7B,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,EACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,eAAe,GACtB,UAAU,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAkCnC;AAED,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACrB;AA8BD,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,GAAG;IAAE,SAAS,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,MAAM,CAAA;CAAE,EAC9E,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,oBAAoB,GAC3B,UAAU,CA2EZ"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** The list and inspector panes. Each returns its own rows and only the hits it was asked to expose. */
|
|
2
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { runtimeLabel } from "../../../core/tasks/format.js";
|
|
4
|
+
import { taskReadBytes } from "../../../core/tasks/output.js";
|
|
5
|
+
import { isTaskTerminal } from "../../../core/tasks/types.js";
|
|
6
|
+
import { formatSize } from "../../../core/tools/truncate.js";
|
|
7
|
+
import { framePane, TITLE_INDENT } from "./frame.js";
|
|
8
|
+
import { modeName, statusName, statusView } from "./status.js";
|
|
9
|
+
import { cleanTaskText, firstCommandLine } from "./text.js";
|
|
10
|
+
import { streamsLog } from "./views.js";
|
|
11
|
+
/** Task rows between the borders, and how many tasks a page moves. */
|
|
12
|
+
export function renderListPane(env, rect, options) {
|
|
13
|
+
const { theme, selection, list } = env;
|
|
14
|
+
const innerWidth = rect.width - 2;
|
|
15
|
+
const rowsHeight = Math.max(1, rect.height - 2 - (options.searchRow === undefined ? 0 : 1));
|
|
16
|
+
const visible = list.render(selection, theme, innerWidth, rowsHeight, options.stopTargetId);
|
|
17
|
+
const first = options.searchRow === undefined ? 0 : 1;
|
|
18
|
+
const hits = [];
|
|
19
|
+
if (options.interactive)
|
|
20
|
+
visible.forEach((row, index) => {
|
|
21
|
+
if (row.taskId)
|
|
22
|
+
hits.push({
|
|
23
|
+
x: rect.x + 1,
|
|
24
|
+
y: rect.y + 1 + first + index,
|
|
25
|
+
width: innerWidth,
|
|
26
|
+
action: { kind: "select", taskId: row.taskId },
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
const active = selection.rows.filter((task) => !isTaskTerminal(task.status)).length;
|
|
30
|
+
const finished = selection.rows.length - active;
|
|
31
|
+
const counts = [active ? `${active} active` : "", finished ? `${finished} finished` : ""].filter(Boolean);
|
|
32
|
+
const title = theme.fg("text", "Tasks") + (counts.length ? theme.fg("muted", ` · ${counts.join(" · ")}`) : "");
|
|
33
|
+
const rows = options.searchRow === undefined
|
|
34
|
+
? visible.map((row) => row.text)
|
|
35
|
+
: [options.searchRow, ...visible.map((row) => row.text)];
|
|
36
|
+
const frame = framePane({
|
|
37
|
+
width: rect.width,
|
|
38
|
+
theme,
|
|
39
|
+
accent: options.active,
|
|
40
|
+
title,
|
|
41
|
+
status: list.range ? theme.fg("muted", list.range) : undefined,
|
|
42
|
+
rows: Array.from({ length: rect.height - 2 }, (_, i) => rows[i] ?? ""),
|
|
43
|
+
});
|
|
44
|
+
return { lines: frame.lines, hits, pageSize: Math.max(1, rowsHeight - 2) };
|
|
45
|
+
}
|
|
46
|
+
/** `Label · Status · Mode · runtime`, with the status in its own color. */
|
|
47
|
+
function metaLine(task, theme) {
|
|
48
|
+
return [
|
|
49
|
+
theme.fg("muted", cleanTaskText(task.label)),
|
|
50
|
+
theme.fg(statusView(task.status).color, statusName(task.status)),
|
|
51
|
+
theme.fg("muted", modeName(task.mode)),
|
|
52
|
+
theme.fg("muted", runtimeLabel(task)),
|
|
53
|
+
]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(theme.fg("dim", " · "));
|
|
56
|
+
}
|
|
57
|
+
/** The log's reading state: following, browsing a held snapshot, or finished. Other output has none. */
|
|
58
|
+
function readingState(task, inspector, hint) {
|
|
59
|
+
const { output } = inspector.position;
|
|
60
|
+
const terminal = isTaskTerminal(task.status);
|
|
61
|
+
if (output.follow)
|
|
62
|
+
return { text: terminal ? "Finished" : "Live", followable: false };
|
|
63
|
+
const follow = hint("app.tasks.follow");
|
|
64
|
+
return {
|
|
65
|
+
text: terminal ? `Finished · ${follow} load final output` : `Browsing · ${follow} follow`,
|
|
66
|
+
followable: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function renderInspectorPane(env, rect, options) {
|
|
70
|
+
const { theme, selection, inspector, hint } = env;
|
|
71
|
+
const innerWidth = rect.width - 2;
|
|
72
|
+
const innerHeight = rect.height - 2;
|
|
73
|
+
const task = inspector.task;
|
|
74
|
+
const tab = selection.state.tab;
|
|
75
|
+
const hits = [];
|
|
76
|
+
// The tabs live in the top border; hit boxes follow the rendered label widths.
|
|
77
|
+
const tabs = [
|
|
78
|
+
["output", "Output"],
|
|
79
|
+
["info", "Details"],
|
|
80
|
+
];
|
|
81
|
+
let tabX = rect.x + TITLE_INDENT;
|
|
82
|
+
const title = tabs
|
|
83
|
+
.map(([id, label]) => {
|
|
84
|
+
if (options.interactive)
|
|
85
|
+
hits.push({ x: tabX, y: rect.y, width: visibleWidth(label), action: { kind: "tab", tab: id } });
|
|
86
|
+
tabX += visibleWidth(label) + 2;
|
|
87
|
+
return theme.style(label, tab === id ? { fg: "text", underline: true } : { fg: "muted" });
|
|
88
|
+
})
|
|
89
|
+
.join(" ");
|
|
90
|
+
const rows = [];
|
|
91
|
+
let status = "";
|
|
92
|
+
let followWidth = 0;
|
|
93
|
+
let divider;
|
|
94
|
+
if (task) {
|
|
95
|
+
// A bold title and the meta line form a header; a rule joined to the frame separates it from the tab.
|
|
96
|
+
rows.push(theme.style(cleanTaskText(firstCommandLine(task.title)), { fg: "text", bold: true }), metaLine(task, theme), "");
|
|
97
|
+
divider = rows.length - 1;
|
|
98
|
+
const position = inspector.position;
|
|
99
|
+
const read = position.read;
|
|
100
|
+
const diagnostics = [task.error, read?.readError].filter((text) => !!text).map(cleanTaskText);
|
|
101
|
+
if (tab === "output" && diagnostics.length)
|
|
102
|
+
rows.push(theme.fg("warning", diagnostics.join(" · ").replace(/\n/g, " ")));
|
|
103
|
+
const body = Math.max(1, innerHeight - rows.length);
|
|
104
|
+
if (tab === "info") {
|
|
105
|
+
rows.push(...position.info.layout(inspector.details(innerWidth), innerWidth, body));
|
|
106
|
+
if (position.info.overflows)
|
|
107
|
+
status = theme.fg("muted", position.info.range);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
rows.push(...position.output.layout(inspector.output(innerWidth), innerWidth, body));
|
|
111
|
+
// Only a log has a reading state; a published result is always the current one.
|
|
112
|
+
if (streamsLog(task)) {
|
|
113
|
+
const reading = readingState(task, inspector, hint);
|
|
114
|
+
status = theme.fg("muted", reading.text);
|
|
115
|
+
if (read?.truncated)
|
|
116
|
+
status += theme.fg("muted", ` · Tail preview: ${formatSize(taskReadBytes(read))} of ${formatSize(read.totalBytes)}`);
|
|
117
|
+
followWidth = reading.followable ? visibleWidth(reading.text) : 0;
|
|
118
|
+
}
|
|
119
|
+
else if (position.output.overflows)
|
|
120
|
+
status = theme.fg("muted", position.output.range);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
rows.push(theme.fg("muted", "Select a task to inspect its result."));
|
|
125
|
+
const frame = framePane({
|
|
126
|
+
width: rect.width,
|
|
127
|
+
theme,
|
|
128
|
+
accent: options.active,
|
|
129
|
+
title,
|
|
130
|
+
status: status || undefined,
|
|
131
|
+
rows: Array.from({ length: innerHeight }, (_, i) => rows[i] ?? ""),
|
|
132
|
+
divider,
|
|
133
|
+
});
|
|
134
|
+
// The held-snapshot state is itself the control that resumes following.
|
|
135
|
+
if (options.interactive && followWidth)
|
|
136
|
+
hits.push({
|
|
137
|
+
x: rect.x + frame.status.x,
|
|
138
|
+
y: rect.y + rect.height - 1,
|
|
139
|
+
width: Math.min(followWidth, frame.status.width),
|
|
140
|
+
action: { kind: "key", key: "app.tasks.follow" },
|
|
141
|
+
});
|
|
142
|
+
return { lines: frame.lines, hits };
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=panes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panes.js","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/panes.ts"],"names":[],"mappings":"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","sourcesContent":["/** The list and inspector panes. Each returns its own rows and only the hits it was asked to expose. */\nimport { visibleWidth } from \"@earendil-works/pi-tui\";\nimport type { Keybinding } from \"../../../core/keybindings.ts\";\nimport { runtimeLabel } from \"../../../core/tasks/format.ts\";\nimport { taskReadBytes } from \"../../../core/tasks/output.ts\";\nimport { isTaskTerminal, type TaskSnapshot } from \"../../../core/tasks/types.ts\";\nimport { formatSize } from \"../../../core/tools/truncate.ts\";\nimport type { Theme } from \"../../../modes/interactive/theme/theme.ts\";\nimport { framePane, type Rect, TITLE_INDENT } from \"./frame.ts\";\nimport type { TaskInspector } from \"./inspector.ts\";\nimport type { TaskList } from \"./list.ts\";\nimport type { TaskHit, TaskSelection, TasksTab } from \"./model.ts\";\nimport { modeName, statusName, statusView } from \"./status.ts\";\nimport { cleanTaskText, firstCommandLine } from \"./text.ts\";\nimport { streamsLog } from \"./views.ts\";\n\nexport interface PaneRender {\n\tlines: string[];\n\thits: TaskHit[];\n}\n\ninterface PaneEnv {\n\ttheme: Theme;\n\tselection: TaskSelection;\n}\n\nexport interface ListPaneOptions {\n\t/** The focused pane has an accented border. */\n\tactive: boolean;\n\t/** Whether the rows can be clicked. */\n\tinteractive: boolean;\n\t/** The rendered search input, shown as the first content row while searching. */\n\tsearchRow?: string;\n\t/** The task a pending stop question is about. */\n\tstopTargetId?: string;\n}\n\n/** Task rows between the borders, and how many tasks a page moves. */\nexport function renderListPane(\n\tenv: PaneEnv & { list: TaskList },\n\trect: Rect,\n\toptions: ListPaneOptions,\n): PaneRender & { pageSize: number } {\n\tconst { theme, selection, list } = env;\n\tconst innerWidth = rect.width - 2;\n\tconst rowsHeight = Math.max(1, rect.height - 2 - (options.searchRow === undefined ? 0 : 1));\n\tconst visible = list.render(selection, theme, innerWidth, rowsHeight, options.stopTargetId);\n\tconst first = options.searchRow === undefined ? 0 : 1;\n\tconst hits: TaskHit[] = [];\n\tif (options.interactive)\n\t\tvisible.forEach((row, index) => {\n\t\t\tif (row.taskId)\n\t\t\t\thits.push({\n\t\t\t\t\tx: rect.x + 1,\n\t\t\t\t\ty: rect.y + 1 + first + index,\n\t\t\t\t\twidth: innerWidth,\n\t\t\t\t\taction: { kind: \"select\", taskId: row.taskId },\n\t\t\t\t});\n\t\t});\n\tconst active = selection.rows.filter((task) => !isTaskTerminal(task.status)).length;\n\tconst finished = selection.rows.length - active;\n\tconst counts = [active ? `${active} active` : \"\", finished ? `${finished} finished` : \"\"].filter(Boolean);\n\tconst title = theme.fg(\"text\", \"Tasks\") + (counts.length ? theme.fg(\"muted\", ` · ${counts.join(\" · \")}`) : \"\");\n\tconst rows =\n\t\toptions.searchRow === undefined\n\t\t\t? visible.map((row) => row.text)\n\t\t\t: [options.searchRow, ...visible.map((row) => row.text)];\n\tconst frame = framePane({\n\t\twidth: rect.width,\n\t\ttheme,\n\t\taccent: options.active,\n\t\ttitle,\n\t\tstatus: list.range ? theme.fg(\"muted\", list.range) : undefined,\n\t\trows: Array.from({ length: rect.height - 2 }, (_, i) => rows[i] ?? \"\"),\n\t});\n\treturn { lines: frame.lines, hits, pageSize: Math.max(1, rowsHeight - 2) };\n}\n\nexport interface InspectorPaneOptions {\n\tactive: boolean;\n\tinteractive: boolean;\n}\n\n/** `Label · Status · Mode · runtime`, with the status in its own color. */\nfunction metaLine(task: TaskSnapshot, theme: Theme): string {\n\treturn [\n\t\ttheme.fg(\"muted\", cleanTaskText(task.label)),\n\t\ttheme.fg(statusView(task.status).color, statusName(task.status)),\n\t\ttheme.fg(\"muted\", modeName(task.mode)),\n\t\ttheme.fg(\"muted\", runtimeLabel(task)),\n\t]\n\t\t.filter(Boolean)\n\t\t.join(theme.fg(\"dim\", \" · \"));\n}\n\n/** The log's reading state: following, browsing a held snapshot, or finished. Other output has none. */\nfunction readingState(\n\ttask: TaskSnapshot,\n\tinspector: TaskInspector,\n\thint: (key: Keybinding) => string,\n): { text: string; followable: boolean } {\n\tconst { output } = inspector.position;\n\tconst terminal = isTaskTerminal(task.status);\n\tif (output.follow) return { text: terminal ? \"Finished\" : \"Live\", followable: false };\n\tconst follow = hint(\"app.tasks.follow\");\n\treturn {\n\t\ttext: terminal ? `Finished · ${follow} load final output` : `Browsing · ${follow} follow`,\n\t\tfollowable: true,\n\t};\n}\n\nexport function renderInspectorPane(\n\tenv: PaneEnv & { inspector: TaskInspector; hint: (key: Keybinding) => string },\n\trect: Rect,\n\toptions: InspectorPaneOptions,\n): PaneRender {\n\tconst { theme, selection, inspector, hint } = env;\n\tconst innerWidth = rect.width - 2;\n\tconst innerHeight = rect.height - 2;\n\tconst task = inspector.task;\n\tconst tab = selection.state.tab;\n\tconst hits: TaskHit[] = [];\n\t// The tabs live in the top border; hit boxes follow the rendered label widths.\n\tconst tabs: Array<[TasksTab, string]> = [\n\t\t[\"output\", \"Output\"],\n\t\t[\"info\", \"Details\"],\n\t];\n\tlet tabX = rect.x + TITLE_INDENT;\n\tconst title = tabs\n\t\t.map(([id, label]) => {\n\t\t\tif (options.interactive)\n\t\t\t\thits.push({ x: tabX, y: rect.y, width: visibleWidth(label), action: { kind: \"tab\", tab: id } });\n\t\t\ttabX += visibleWidth(label) + 2;\n\t\t\treturn theme.style(label, tab === id ? { fg: \"text\", underline: true } : { fg: \"muted\" });\n\t\t})\n\t\t.join(\" \");\n\tconst rows: string[] = [];\n\tlet status = \"\";\n\tlet followWidth = 0;\n\tlet divider: number | undefined;\n\tif (task) {\n\t\t// A bold title and the meta line form a header; a rule joined to the frame separates it from the tab.\n\t\trows.push(\n\t\t\ttheme.style(cleanTaskText(firstCommandLine(task.title)), { fg: \"text\", bold: true }),\n\t\t\tmetaLine(task, theme),\n\t\t\t\"\",\n\t\t);\n\t\tdivider = rows.length - 1;\n\t\tconst position = inspector.position;\n\t\tconst read = position.read;\n\t\tconst diagnostics = [task.error, read?.readError].filter((text): text is string => !!text).map(cleanTaskText);\n\t\tif (tab === \"output\" && diagnostics.length)\n\t\t\trows.push(theme.fg(\"warning\", diagnostics.join(\" · \").replace(/\\n/g, \" \")));\n\t\tconst body = Math.max(1, innerHeight - rows.length);\n\t\tif (tab === \"info\") {\n\t\t\trows.push(...position.info.layout(inspector.details(innerWidth), innerWidth, body));\n\t\t\tif (position.info.overflows) status = theme.fg(\"muted\", position.info.range);\n\t\t} else {\n\t\t\trows.push(...position.output.layout(inspector.output(innerWidth), innerWidth, body));\n\t\t\t// Only a log has a reading state; a published result is always the current one.\n\t\t\tif (streamsLog(task)) {\n\t\t\t\tconst reading = readingState(task, inspector, hint);\n\t\t\t\tstatus = theme.fg(\"muted\", reading.text);\n\t\t\t\tif (read?.truncated)\n\t\t\t\t\tstatus += theme.fg(\n\t\t\t\t\t\t\"muted\",\n\t\t\t\t\t\t` · Tail preview: ${formatSize(taskReadBytes(read))} of ${formatSize(read.totalBytes)}`,\n\t\t\t\t\t);\n\t\t\t\tfollowWidth = reading.followable ? visibleWidth(reading.text) : 0;\n\t\t\t} else if (position.output.overflows) status = theme.fg(\"muted\", position.output.range);\n\t\t}\n\t} else rows.push(theme.fg(\"muted\", \"Select a task to inspect its result.\"));\n\tconst frame = framePane({\n\t\twidth: rect.width,\n\t\ttheme,\n\t\taccent: options.active,\n\t\ttitle,\n\t\tstatus: status || undefined,\n\t\trows: Array.from({ length: innerHeight }, (_, i) => rows[i] ?? \"\"),\n\t\tdivider,\n\t});\n\t// The held-snapshot state is itself the control that resumes following.\n\tif (options.interactive && followWidth)\n\t\thits.push({\n\t\t\tx: rect.x + frame.status.x,\n\t\t\ty: rect.y + rect.height - 1,\n\t\t\twidth: Math.min(followWidth, frame.status.width),\n\t\t\taction: { kind: \"key\", key: \"app.tasks.follow\" },\n\t\t});\n\treturn { lines: frame.lines, hits };\n}\n"]}
|
|
@@ -8,21 +8,7 @@ import type { TasksDetails } from "../tool/types.ts";
|
|
|
8
8
|
type TasksRenderInput = Omit<TasksInput, "action"> & {
|
|
9
9
|
action?: string;
|
|
10
10
|
};
|
|
11
|
-
|
|
12
|
-
export
|
|
13
|
-
dispose?: () => void;
|
|
14
|
-
refreshTimer?: ReturnType<typeof setTimeout>;
|
|
15
|
-
/** First pending render of a wait call; anchors the elapsed display. */
|
|
16
|
-
waitStartedAt?: number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Arm a one-shot 1s refresh while a wait call is pending, clear it once
|
|
20
|
-
* settled. The timer fires → context.invalidate() → renderCall re-runs →
|
|
21
|
-
* re-arms, so at most one armed timer exists per tool row. Unref'd: it can
|
|
22
|
-
* never hold the process open.
|
|
23
|
-
*/
|
|
24
|
-
export declare function scheduleWaitRefresh(context: ToolRenderContext<TasksRenderState>, pending: boolean): void;
|
|
25
|
-
export declare function renderTasksCall(args: TasksRenderInput, theme: Theme, context: ToolRenderContext<TasksRenderState>): Component;
|
|
26
|
-
export declare function renderTasksResult(result: AgentToolResult<TasksDetails | undefined>, options: ToolRenderResultOptions, theme: Theme, context: ToolRenderContext<TasksRenderState>): Component;
|
|
11
|
+
export declare function renderTasksCall(args: TasksRenderInput, theme: Theme, _context: ToolRenderContext): Component;
|
|
12
|
+
export declare function renderTasksResult(result: AgentToolResult<TasksDetails | undefined>, options: ToolRenderResultOptions, theme: Theme, context: ToolRenderContext): Component;
|
|
27
13
|
export {};
|
|
28
14
|
//# sourceMappingURL=render.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/render.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAElG,OAAO,EAAE,KAAK,SAAS,EAAkC,MAAM,wBAAwB,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/extensions/tasks/ui/render.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAElG,OAAO,EAAE,KAAK,SAAS,EAAkC,MAAM,wBAAwB,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAGrH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,wEAAwE;AACxE,KAAK,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAazE,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,GAAG,SAAS,CAyB5G;AAID,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,eAAe,CAAC,YAAY,GAAG,SAAS,CAAC,EACjD,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,iBAAiB,GACxB,SAAS,CAoBX"}
|
|
@@ -1,55 +1,25 @@
|
|
|
1
1
|
/** Transcript rendering for the native tasks tool: its call/result rows, dispatched on action. */
|
|
2
2
|
import { Container, Text, TruncatedText } from "@earendil-works/pi-tui";
|
|
3
|
-
import { clampWaitMs } from "../../../core/tasks/limits.js";
|
|
4
3
|
import { formatSize } from "../../../core/tools/truncate.js";
|
|
5
|
-
import { formatDuration } from "../../../utils/duration.js";
|
|
6
4
|
import { statusName, statusView } from "./status.js";
|
|
7
5
|
import { fileNameOf } from "./text.js";
|
|
8
6
|
/** Cap for expanded transcript views of tool-result text (already bounded at the source). */
|
|
9
7
|
const RESULT_EXPAND_LIMIT = 4000;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* re-arms, so at most one armed timer exists per tool row. Unref'd: it can
|
|
16
|
-
* never hold the process open.
|
|
17
|
-
*/
|
|
18
|
-
export function scheduleWaitRefresh(context, pending) {
|
|
19
|
-
const state = context.state;
|
|
20
|
-
if (state === undefined)
|
|
21
|
-
return; // Standalone render without shell state: nothing to schedule.
|
|
22
|
-
if (pending) {
|
|
23
|
-
state.dispose = () => scheduleWaitRefresh(context, false);
|
|
24
|
-
if (state.refreshTimer === undefined) {
|
|
25
|
-
state.refreshTimer = setTimeout(() => {
|
|
26
|
-
state.refreshTimer = undefined;
|
|
27
|
-
context.invalidate();
|
|
28
|
-
}, WAIT_REFRESH_MS);
|
|
29
|
-
state.refreshTimer.unref?.();
|
|
30
|
-
}
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (state.refreshTimer !== undefined) {
|
|
34
|
-
clearTimeout(state.refreshTimer);
|
|
35
|
-
state.refreshTimer = undefined;
|
|
36
|
-
}
|
|
37
|
-
state.waitStartedAt = undefined;
|
|
38
|
-
}
|
|
39
|
-
/** Keep the END of oversized text — that is where the outcome lives. */
|
|
40
|
-
function capForTranscript(text, limit) {
|
|
41
|
-
return text.length > limit ? `…${text.slice(-limit)}` : text;
|
|
8
|
+
/** Keep the end that was read: a report's summary or a log's outcome. */
|
|
9
|
+
function capForTranscript(text, limit, mode = "tail") {
|
|
10
|
+
if (text.length <= limit)
|
|
11
|
+
return text;
|
|
12
|
+
return mode === "head" ? `${text.slice(0, limit)}…` : `…${text.slice(-limit)}`;
|
|
42
13
|
}
|
|
43
14
|
// ── tool call ─────────────────────────────────────────────────────────────
|
|
44
|
-
export function renderTasksCall(args, theme,
|
|
15
|
+
export function renderTasksCall(args, theme, _context) {
|
|
45
16
|
switch (args.action) {
|
|
46
17
|
case "read": {
|
|
47
|
-
|
|
18
|
+
// The default end depends on the task's output kind, which the call does not carry.
|
|
19
|
+
const mode = args.mode ? ` ${args.mode}` : "";
|
|
48
20
|
const size = args.bytes !== undefined ? ` ${formatSize(args.bytes)}` : "";
|
|
49
|
-
return new Text(`${theme.fg("toolTitle", theme.bold("tasks read "))}${theme.fg("accent", args.taskId ?? "")}${theme.fg("muted",
|
|
21
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("tasks read "))}${theme.fg("accent", args.taskId ?? "")}${theme.fg("muted", `${mode}${size}`)}`, 0, 0);
|
|
50
22
|
}
|
|
51
|
-
case "wait":
|
|
52
|
-
return renderWaitCall(args, theme, context);
|
|
53
23
|
case "kill":
|
|
54
24
|
return new Text(`${theme.fg("toolTitle", theme.bold("tasks kill "))}${theme.fg("accent", args.taskId ?? "")}`, 0, 0);
|
|
55
25
|
case "list":
|
|
@@ -60,38 +30,12 @@ export function renderTasksCall(args, theme, context) {
|
|
|
60
30
|
}
|
|
61
31
|
}
|
|
62
32
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Pending wait call: elapsed/wait-window, refreshed once per second by
|
|
65
|
-
* scheduleWaitRefresh until the result settles and takes over the row.
|
|
66
|
-
*/
|
|
67
|
-
function renderWaitCall(args, theme, context) {
|
|
68
|
-
const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
|
|
69
|
-
// isPartial alone means "not settled", which also covers argument streaming
|
|
70
|
-
// and replayed history rows that never settle; executionStarted narrows it to
|
|
71
|
-
// a call that is actually running. The live display also needs shell-owned
|
|
72
|
-
// state; without it (standalone renders) fall back to the static line.
|
|
73
|
-
const pending = context.executionStarted === true && context.isPartial === true && context.state !== undefined;
|
|
74
|
-
scheduleWaitRefresh(context, pending);
|
|
75
|
-
if (!pending) {
|
|
76
|
-
const ms = args.waitMs !== undefined ? ` ${formatDuration(clampWaitMs(args.waitMs))}` : "";
|
|
77
|
-
return new Text(`${theme.fg("toolTitle", theme.bold("tasks wait "))}${theme.fg("accent", taskId || "any")}${theme.fg("muted", ms)}`, 0, 0);
|
|
78
|
-
}
|
|
79
|
-
const state = context.state;
|
|
80
|
-
if (state.waitStartedAt === undefined)
|
|
81
|
-
state.waitStartedAt = Date.now();
|
|
82
|
-
const elapsed = `waiting ${formatDuration(Date.now() - state.waitStartedAt)} / ${formatDuration(clampWaitMs(args.waitMs))}`;
|
|
83
|
-
return new Text(`${theme.fg("toolTitle", theme.bold("tasks wait "))}${theme.fg("accent", taskId || "any")} ${theme.fg("muted", elapsed)}`, 0, 0);
|
|
84
|
-
}
|
|
85
33
|
// ── tool result ───────────────────────────────────────────────────────────
|
|
86
34
|
export function renderTasksResult(result, options, theme, context) {
|
|
87
|
-
// Results are always settled today; clear defensively so no live-refresh
|
|
88
|
-
// timer can outlive its row if a host ever streams partial results.
|
|
89
|
-
if (context.args?.action === "wait" && !options.isPartial)
|
|
90
|
-
scheduleWaitRefresh(context, false);
|
|
91
35
|
const details = result.details;
|
|
92
|
-
// Results from older tool versions, such as `bg create`, keep their saved text.
|
|
36
|
+
// Results from older tool versions, such as `bg create` or `wait`, keep their saved text.
|
|
93
37
|
const summary = details && !context.isError ? resultSummaryLine(details, theme, options.expanded) : undefined;
|
|
94
|
-
if (summary === undefined) {
|
|
38
|
+
if (summary === undefined || details === undefined) {
|
|
95
39
|
const text = result.content.find((part) => part.type === "text")?.text ?? "";
|
|
96
40
|
return new Text(theme.fg("toolOutput", text.trimEnd()), 0, 0);
|
|
97
41
|
}
|
|
@@ -99,8 +43,9 @@ export function renderTasksResult(result, options, theme, context) {
|
|
|
99
43
|
container.addChild(new TruncatedText(summary, 1, 0));
|
|
100
44
|
if (options.expanded) {
|
|
101
45
|
const text = result.content.find((part) => part.type === "text")?.text ?? "";
|
|
46
|
+
const mode = details.action === "read" ? details.mode : undefined;
|
|
102
47
|
container.addChild(new Text("", 0, 0));
|
|
103
|
-
container.addChild(new Text(theme.fg("toolOutput", capForTranscript(text, RESULT_EXPAND_LIMIT).trimEnd()), 1, 0));
|
|
48
|
+
container.addChild(new Text(theme.fg("toolOutput", capForTranscript(text, RESULT_EXPAND_LIMIT, mode).trimEnd()), 1, 0));
|
|
104
49
|
}
|
|
105
50
|
return container;
|
|
106
51
|
}
|
|
@@ -119,23 +64,6 @@ function resultSummaryLine(details, theme, expanded) {
|
|
|
119
64
|
: formatSize(details.totalBytes);
|
|
120
65
|
return `${theme.fg("muted", "→ ")}${theme.fg("accent", details.taskId)}${theme.fg("muted", `${details.status ? ` ${statusName(details.status)} ·` : ""} ${size}${details.outputPath ? ` · ${shownPath(details.outputPath)}` : ""}`)}`;
|
|
121
66
|
}
|
|
122
|
-
case "wait": {
|
|
123
|
-
const outcome = details.outcome;
|
|
124
|
-
if (outcome === "no_tasks")
|
|
125
|
-
return theme.fg("muted", "→ No background tasks running");
|
|
126
|
-
const taskId = details.taskId ?? "any";
|
|
127
|
-
if (outcome === "interrupted") {
|
|
128
|
-
const running = details.running ? ` · ${details.running} still running` : "";
|
|
129
|
-
return `${mark("cancelled")} ${theme.fg("accent", taskId)}${theme.fg("muted", ` wait interrupted after ${formatDuration(details.waitedMs)}${running}`)}`;
|
|
130
|
-
}
|
|
131
|
-
if (outcome === "timed_out") {
|
|
132
|
-
const status = details.status ?? "running";
|
|
133
|
-
const running = details.taskId ? formatSize(details.totalBytes) : `${details.running ?? 0} still running`;
|
|
134
|
-
return `${mark(status)} ${theme.fg("accent", taskId)}${theme.fg("muted", ` ${statusName(status)} · wait ended after ${formatDuration(details.waitedMs)} · ${running}`)}`;
|
|
135
|
-
}
|
|
136
|
-
const status = details.status ?? "completed";
|
|
137
|
-
return `${mark(status)} ${theme.fg("accent", taskId)}${theme.fg("muted", ` ${statusName(status)} · waited ${formatDuration(details.waitedMs)} · +${formatSize(details.deltaBytes)}`)}`;
|
|
138
|
-
}
|
|
139
67
|
case "kill": {
|
|
140
68
|
const status = details.status ?? (details.requested ? "stopping" : "cancelled");
|
|
141
69
|
return `${mark(status)} ${theme.fg("accent", details.taskId)}${theme.fg("muted", ` ${statusName(status)}${details.requested ? " · cancellation requested" : ""}`)}`;
|