killeros 2.1.29 → 2.1.31
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 +558 -538
- package/Killeros.ts +53 -53
- package/README.md +112 -112
- package/killeros/activity.ts +110 -110
- package/killeros/auto-compaction.ts +342 -342
- package/killeros/change-receipt.ts +676 -682
- package/killeros/codex-fast-state.ts +47 -47
- package/killeros/codex-fast.ts +36 -36
- package/killeros/commands.ts +259 -259
- package/killeros/display.ts +60 -60
- package/killeros/errors.ts +19 -19
- package/killeros/footer.ts +505 -505
- package/killeros/goal-command.ts +26 -23
- package/killeros/goal-interface.ts +560 -560
- package/killeros/goal-runtime.ts +490 -490
- package/killeros/goal-settlement.ts +348 -348
- package/killeros/goal-state.ts +660 -660
- package/killeros/handoff.ts +326 -284
- package/killeros/hooks.ts +449 -449
- package/killeros/notifications.ts +135 -135
- package/killeros/passive-git-status.ts +346 -346
- package/killeros/personal-instructions.ts +122 -122
- package/killeros/question-ui.ts +590 -590
- package/killeros/question.ts +234 -234
- package/killeros/runtime.ts +150 -150
- package/killeros/secret-detector.ts +19 -19
- package/killeros/settings.ts +141 -50
- package/killeros/shell-ui.ts +418 -418
- package/killeros/variants.ts +13 -13
- package/killeros/worked-for.ts +496 -430
- package/package.json +67 -67
- package/themes/killeros.json +87 -87
package/Killeros.ts
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { registerRequestActivity } from "./killeros/activity.ts";
|
|
3
|
-
import { registerAutoCompaction } from "./killeros/auto-compaction.ts";
|
|
4
|
-
import {
|
|
5
|
-
createSlashCommandResolver,
|
|
6
|
-
registerAliases,
|
|
7
|
-
registerSlashAutocomplete,
|
|
8
|
-
} from "./killeros/commands.ts";
|
|
9
|
-
import { registerFooter } from "./killeros/footer.ts";
|
|
10
|
-
import { registerGoalInterface } from "./killeros/goal-interface.ts";
|
|
11
|
-
import { registerGoalRuntime } from "./killeros/goal-runtime.ts";
|
|
12
|
-
import { registerGoalSettlement } from "./killeros/goal-settlement.ts";
|
|
13
|
-
import { registerHandoff } from "./killeros/handoff.ts";
|
|
14
|
-
import { registerLifecycleHooks } from "./killeros/hooks.ts";
|
|
15
|
-
import {
|
|
16
|
-
registerCompletionNotifications,
|
|
17
|
-
type CompletionNotificationDependencies,
|
|
18
|
-
} from "./killeros/notifications.ts";
|
|
19
|
-
import { registerCodexFastMode } from "./killeros/codex-fast.ts";
|
|
20
|
-
import { registerPersonalInstructions } from "./killeros/personal-instructions.ts";
|
|
21
|
-
import { registerQuestionTool } from "./killeros/question.ts";
|
|
22
|
-
import { createGoalRuntime } from "./killeros/runtime.ts";
|
|
23
|
-
import { registerShellUi } from "./killeros/shell-ui.ts";
|
|
24
|
-
import { registerWorkedFor } from "./killeros/worked-for.ts";
|
|
25
|
-
|
|
26
|
-
export { contextPercentRemaining, formatCost, formatContextProgress } from "./killeros/footer.ts";
|
|
27
|
-
export { executeHook } from "./killeros/hooks.ts";
|
|
28
|
-
export interface KillerosOptions {
|
|
29
|
-
completionNotifications?: CompletionNotificationDependencies;
|
|
30
|
-
/** Output-token budget for /handoff summaries; invalid values fall back to killeros.json, then the default. */
|
|
31
|
-
handoffMaxTokens?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}): void {
|
|
35
|
-
const goalRuntime = createGoalRuntime();
|
|
36
|
-
const commandResolver = createSlashCommandResolver(pi);
|
|
37
|
-
registerShellUi(pi, commandResolver);
|
|
38
|
-
registerGoalInterface(pi, goalRuntime);
|
|
39
|
-
registerGoalRuntime(pi, goalRuntime);
|
|
40
|
-
registerPersonalInstructions(pi);
|
|
41
|
-
registerQuestionTool(pi);
|
|
42
|
-
registerAliases(pi);
|
|
43
|
-
registerHandoff(pi, goalRuntime, options.handoffMaxTokens);
|
|
44
|
-
registerSlashAutocomplete(pi, commandResolver);
|
|
45
|
-
registerFooter(pi, goalRuntime);
|
|
46
|
-
registerCodexFastMode(pi);
|
|
47
|
-
registerLifecycleHooks(pi);
|
|
48
|
-
registerWorkedFor(pi);
|
|
49
|
-
const goalCompaction = registerGoalSettlement(pi, goalRuntime);
|
|
50
|
-
registerAutoCompaction(pi, { goal: goalCompaction });
|
|
51
|
-
registerRequestActivity(pi);
|
|
52
|
-
registerCompletionNotifications(pi, options.completionNotifications);
|
|
53
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { registerRequestActivity } from "./killeros/activity.ts";
|
|
3
|
+
import { registerAutoCompaction } from "./killeros/auto-compaction.ts";
|
|
4
|
+
import {
|
|
5
|
+
createSlashCommandResolver,
|
|
6
|
+
registerAliases,
|
|
7
|
+
registerSlashAutocomplete,
|
|
8
|
+
} from "./killeros/commands.ts";
|
|
9
|
+
import { registerFooter } from "./killeros/footer.ts";
|
|
10
|
+
import { registerGoalInterface } from "./killeros/goal-interface.ts";
|
|
11
|
+
import { registerGoalRuntime } from "./killeros/goal-runtime.ts";
|
|
12
|
+
import { registerGoalSettlement } from "./killeros/goal-settlement.ts";
|
|
13
|
+
import { registerHandoff } from "./killeros/handoff.ts";
|
|
14
|
+
import { registerLifecycleHooks } from "./killeros/hooks.ts";
|
|
15
|
+
import {
|
|
16
|
+
registerCompletionNotifications,
|
|
17
|
+
type CompletionNotificationDependencies,
|
|
18
|
+
} from "./killeros/notifications.ts";
|
|
19
|
+
import { registerCodexFastMode } from "./killeros/codex-fast.ts";
|
|
20
|
+
import { registerPersonalInstructions } from "./killeros/personal-instructions.ts";
|
|
21
|
+
import { registerQuestionTool } from "./killeros/question.ts";
|
|
22
|
+
import { createGoalRuntime } from "./killeros/runtime.ts";
|
|
23
|
+
import { registerShellUi } from "./killeros/shell-ui.ts";
|
|
24
|
+
import { registerWorkedFor } from "./killeros/worked-for.ts";
|
|
25
|
+
|
|
26
|
+
export { contextPercentRemaining, formatCost, formatContextProgress } from "./killeros/footer.ts";
|
|
27
|
+
export { executeHook } from "./killeros/hooks.ts";
|
|
28
|
+
export interface KillerosOptions {
|
|
29
|
+
completionNotifications?: CompletionNotificationDependencies;
|
|
30
|
+
/** Output-token budget for /handoff summaries; invalid values fall back to killeros.json, then the default. */
|
|
31
|
+
handoffMaxTokens?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function Killeros(pi: ExtensionAPI, options: KillerosOptions = {}): void {
|
|
35
|
+
const goalRuntime = createGoalRuntime();
|
|
36
|
+
const commandResolver = createSlashCommandResolver(pi);
|
|
37
|
+
registerShellUi(pi, commandResolver);
|
|
38
|
+
registerGoalInterface(pi, goalRuntime);
|
|
39
|
+
registerGoalRuntime(pi, goalRuntime);
|
|
40
|
+
registerPersonalInstructions(pi);
|
|
41
|
+
registerQuestionTool(pi);
|
|
42
|
+
registerAliases(pi);
|
|
43
|
+
registerHandoff(pi, goalRuntime, options.handoffMaxTokens);
|
|
44
|
+
registerSlashAutocomplete(pi, commandResolver);
|
|
45
|
+
registerFooter(pi, goalRuntime);
|
|
46
|
+
registerCodexFastMode(pi);
|
|
47
|
+
registerLifecycleHooks(pi);
|
|
48
|
+
registerWorkedFor(pi);
|
|
49
|
+
const goalCompaction = registerGoalSettlement(pi, goalRuntime);
|
|
50
|
+
registerAutoCompaction(pi, { goal: goalCompaction });
|
|
51
|
+
registerRequestActivity(pi);
|
|
52
|
+
registerCompletionNotifications(pi, options.completionNotifications);
|
|
53
|
+
}
|
package/README.md
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
# KillerOS
|
|
2
|
-
|
|
3
|
-
A TypeScript extension for the [Pi coding agent](https://github.com/earendil-works/pi) that replaces the stock TUI and adds long-running goals, reasoning controls, and workflow commands.
|
|
4
|
-
|
|
5
|
-
## What you get
|
|
6
|
-
|
|
7
|
-
- A custom TUI: startup masthead with versions, model, working directory, and Git branch; a dark theme with coral accents; a multiline editor with slash-command completion; a footer that tracks model, context, and goal state; settled task receipts with duration and token usage.
|
|
8
|
-
- `/goal`: set an objective and Pi keeps working toward it across turns, compaction, reloads, and branch navigation. Each turn must record `continue` with evidence and one next action, `complete`, or the existing blocker decision; otherwise the goal pauses. New goals pause after 20 turns; `/goal resume` grants another 20.
|
|
9
|
-
- `/codex-fast`: toggles the `priority` service tier on Codex requests or reports its status.
|
|
10
|
-
- `/handoff`: starts a fresh linked session carrying visible continuation context.
|
|
11
|
-
- `/auto-compact`: reports, enables, disables, or tunes automatic context compaction.
|
|
12
|
-
- A `question` tool with single-select and multi-select modes.
|
|
13
|
-
- Lifecycle hooks (`tool_call`, `tool_result`, `agent_settled`) from `.pi/killeros-hooks.json`, plus `AGENTS.local.md` loading for trusted projects.
|
|
14
|
-
- Optional completion sounds for settled requests.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Requirements
|
|
18
|
-
|
|
19
|
-
- Node.js 22.19.0+
|
|
20
|
-
- Pi 0.85.0 or later within the 0.x release line
|
|
21
|
-
- An interactive TUI session for the custom header, editor, footer, and `question`
|
|
22
|
-
|
|
23
|
-
## Install
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pi install npm:killeros
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Or from GitHub:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pi install git:github.com/KyrosHendrix/pi-KillerOS
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Pin a release by appending its tag, for example `@v2.1.
|
|
36
|
-
|
|
37
|
-
## Commands
|
|
38
|
-
|
|
39
|
-
```text
|
|
40
|
-
/goal View the current goal
|
|
41
|
-
/goal <objective> Set an objective
|
|
42
|
-
/goal pause Stop automatic continuation
|
|
43
|
-
/goal resume Resume automatic continuation
|
|
44
|
-
/goal clear Remove the current goal
|
|
45
|
-
/codex-fast [status] Toggle or report Codex fast mode
|
|
46
|
-
/auto-compact [status|on|off|<percent>]
|
|
47
|
-
Inspect or change automatic compaction
|
|
48
|
-
/notification Configure the completion sound
|
|
49
|
-
/handoff [focus] Fresh session with continuation context
|
|
50
|
-
/clear New session after confirmation
|
|
51
|
-
/exit Quit Pi gracefully
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Behavior by mode
|
|
55
|
-
|
|
56
|
-
| Mode | What works |
|
|
57
|
-
| --- | --- |
|
|
58
|
-
| TUI | Everything |
|
|
59
|
-
| RPC | Goals, proactive compaction; no TUI components, sounds, title indicator |
|
|
60
|
-
| Print/JSON | No interactive questions, `/goal`, or proactive compaction |
|
|
61
|
-
|
|
62
|
-
## Configuration
|
|
63
|
-
|
|
64
|
-
The packaged `killeros` theme activates on TUI start. Compaction triggers by default at 15% tokens remaining, stored in global `killeros.json`:
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"autoCompaction": {
|
|
69
|
-
"enabled": true,
|
|
70
|
-
"percentRemaining": 15
|
|
71
|
-
},
|
|
72
|
-
"handoffMaxTokens": 8192
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Use `/auto-compact status` to inspect the effective KillerOS preference, `/auto-compact on` or `/auto-compact off` to toggle it, and `/auto-compact <percent>` to set an integer threshold from 0 through 100. A threshold of 0 does not disable Pi's own token reserve.
|
|
77
|
-
|
|
78
|
-
`handoffMaxTokens` caps the `/handoff` summary output at 8192 tokens by default; raise it when long sessions truncate the summary.
|
|
79
|
-
|
|
80
|
-
State proof in the objective so the agent can verify it with its normal tools:
|
|
81
|
-
|
|
82
|
-
```text
|
|
83
|
-
/goal Reduce p95 checkout latency below 120 ms, verified by the checkout benchmark, while keeping the correctness suite green
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
A direct quoted file target binds silent file proof:
|
|
87
|
-
|
|
88
|
-
```text
|
|
89
|
-
/goal Fix `killeros/footer.ts`, verified by npm test
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
KillerOS captures the file baseline at goal start and only completes when the file is created or changed. A normal response never continues a goal by itself: the agent must record `continue`, `complete`, or a blocker decision through `killeros_goal_update`. Repeated continuation reports and unavailable goal tools pause the goal. New goals pause after 20 turns without warning. An explicit `/goal resume` on an exhausted goal grants another 20 turns; compaction recovery never grants turns. Restored goals keep their persisted limit.
|
|
93
|
-
|
|
94
|
-
Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
|
|
95
|
-
|
|
96
|
-
## Development
|
|
97
|
-
|
|
98
|
-
Strict TypeScript throughout. Tests run on Node's built-in test runner:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npm ci && npm run check && npm test
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Releases go through CI on `main`; do not push version tags manually.
|
|
105
|
-
|
|
106
|
-
## Security
|
|
107
|
-
|
|
108
|
-
Pi extensions run with your user permissions. Review the source before installing globally. Hook commands run only for projects Pi marks as trusted; check `.pi/killeros-hooks.json` before enabling project trust. KillerOS accepts that configuration only as a regular, non-linked file no larger than 64 KiB in the project's real `.pi` directory.
|
|
109
|
-
|
|
110
|
-
## License
|
|
111
|
-
|
|
112
|
-
[MIT](LICENSE) © 2026 KyrosHendrix
|
|
1
|
+
# KillerOS
|
|
2
|
+
|
|
3
|
+
A TypeScript extension for the [Pi coding agent](https://github.com/earendil-works/pi) that replaces the stock TUI and adds long-running goals, reasoning controls, and workflow commands.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
- A custom TUI: startup masthead with versions, model, working directory, and Git branch; a dark theme with coral accents; a multiline editor with slash-command completion; a footer that tracks model, context, and goal state; settled task receipts with duration and token usage.
|
|
8
|
+
- `/goal`: set an objective and Pi keeps working toward it across turns, compaction, reloads, and branch navigation. Each turn must record `continue` with evidence and one next action, `complete`, or the existing blocker decision; otherwise the goal pauses. New goals pause after 20 turns; `/goal resume` grants another 20.
|
|
9
|
+
- `/codex-fast`: toggles the `priority` service tier on Codex requests or reports its status.
|
|
10
|
+
- `/handoff`: starts a fresh linked session carrying visible continuation context.
|
|
11
|
+
- `/auto-compact`: reports, enables, disables, or tunes automatic context compaction.
|
|
12
|
+
- A `question` tool with single-select and multi-select modes.
|
|
13
|
+
- Lifecycle hooks (`tool_call`, `tool_result`, `agent_settled`) from `.pi/killeros-hooks.json`, plus `AGENTS.local.md` loading for trusted projects.
|
|
14
|
+
- Optional completion sounds for settled requests.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Node.js 22.19.0+
|
|
20
|
+
- Pi 0.85.0 or later within the 0.x release line
|
|
21
|
+
- An interactive TUI session for the custom header, editor, footer, and `question`
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pi install npm:killeros
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or from GitHub:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pi install git:github.com/KyrosHendrix/pi-KillerOS
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pin a release by appending its tag, for example `@v2.1.30`. Add `-l` to install only for the current project. Restart Pi after installing.
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
/goal View the current goal
|
|
41
|
+
/goal <objective> Set an objective
|
|
42
|
+
/goal pause Stop automatic continuation
|
|
43
|
+
/goal resume Resume automatic continuation
|
|
44
|
+
/goal clear Remove the current goal
|
|
45
|
+
/codex-fast [status] Toggle or report Codex fast mode
|
|
46
|
+
/auto-compact [status|on|off|<percent>]
|
|
47
|
+
Inspect or change automatic compaction
|
|
48
|
+
/notification Configure the completion sound
|
|
49
|
+
/handoff [focus] Fresh session with continuation context
|
|
50
|
+
/clear New session after confirmation
|
|
51
|
+
/exit Quit Pi gracefully
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Behavior by mode
|
|
55
|
+
|
|
56
|
+
| Mode | What works |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| TUI | Everything |
|
|
59
|
+
| RPC | Goals, proactive compaction; no TUI components, sounds, title indicator |
|
|
60
|
+
| Print/JSON | No interactive questions, `/goal`, or proactive compaction |
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
The packaged `killeros` theme activates on TUI start. Compaction triggers by default at 15% tokens remaining, stored in global `killeros.json`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"autoCompaction": {
|
|
69
|
+
"enabled": true,
|
|
70
|
+
"percentRemaining": 15
|
|
71
|
+
},
|
|
72
|
+
"handoffMaxTokens": 8192
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Use `/auto-compact status` to inspect the effective KillerOS preference, `/auto-compact on` or `/auto-compact off` to toggle it, and `/auto-compact <percent>` to set an integer threshold from 0 through 100. A threshold of 0 does not disable Pi's own token reserve.
|
|
77
|
+
|
|
78
|
+
`handoffMaxTokens` caps the `/handoff` summary output at 8192 tokens by default; raise it when long sessions truncate the summary.
|
|
79
|
+
|
|
80
|
+
State proof in the objective so the agent can verify it with its normal tools:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
/goal Reduce p95 checkout latency below 120 ms, verified by the checkout benchmark, while keeping the correctness suite green
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A direct quoted file target binds silent file proof:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
/goal Fix `killeros/footer.ts`, verified by npm test
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
KillerOS captures the file baseline at goal start and only completes when the file is created or changed. A normal response never continues a goal by itself: the agent must record `continue`, `complete`, or a blocker decision through `killeros_goal_update`. Repeated continuation reports and unavailable goal tools pause the goal. New goals pause after 20 turns without warning. An explicit `/goal resume` on an exhausted goal grants another 20 turns; compaction recovery never grants turns. Restored goals keep their persisted limit.
|
|
93
|
+
|
|
94
|
+
Completion sounds are off by default; change with `/notification` in TUI mode. The tab-title indicator requires a Nerd Font.
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
Strict TypeScript throughout. Tests run on Node's built-in test runner:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm ci && npm run check && npm test
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Releases go through CI on `main`; do not push version tags manually.
|
|
105
|
+
|
|
106
|
+
## Security
|
|
107
|
+
|
|
108
|
+
Pi extensions run with your user permissions. Review the source before installing globally. Hook commands run only for projects Pi marks as trusted; check `.pi/killeros-hooks.json` before enabling project trust. KillerOS accepts that configuration only as a regular, non-linked file no larger than 64 KiB in the project's real `.pi` directory.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
[MIT](LICENSE) © 2026 KyrosHendrix
|
package/killeros/activity.ts
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { modelDisplayName } from "./display.ts";
|
|
4
|
-
|
|
5
|
-
export type ActivityMessage =
|
|
6
|
-
| { kind: "prompt" }
|
|
7
|
-
| { kind: "tool"; toolName: string }
|
|
8
|
-
| { kind: "tool-result"; failed: boolean }
|
|
9
|
-
| { kind: "responding" };
|
|
10
|
-
|
|
11
|
-
function safeToolName(toolName: string): string {
|
|
12
|
-
const normalized = toolName.replace(/[\u0000-\u001F\u007F]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
13
|
-
return truncateToWidth(normalized || "tool", 32, "…");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function formatActivityMessage(message: ActivityMessage, theme: Theme, modelId?: string): string {
|
|
17
|
-
let verb: string;
|
|
18
|
-
let detail: string;
|
|
19
|
-
|
|
20
|
-
switch (message.kind) {
|
|
21
|
-
case "prompt":
|
|
22
|
-
verb = "Mapping…";
|
|
23
|
-
detail = "understanding request";
|
|
24
|
-
break;
|
|
25
|
-
case "tool-result":
|
|
26
|
-
verb = message.failed ? "Recovering…" : "Reviewing…";
|
|
27
|
-
detail = message.failed ? "tool failed" : "reading the result";
|
|
28
|
-
break;
|
|
29
|
-
case "responding":
|
|
30
|
-
verb = "Responding…";
|
|
31
|
-
detail = "assembling the answer";
|
|
32
|
-
break;
|
|
33
|
-
case "tool":
|
|
34
|
-
switch (message.toolName.trim().toLowerCase()) {
|
|
35
|
-
case "read":
|
|
36
|
-
case "grep":
|
|
37
|
-
case "find":
|
|
38
|
-
case "ls":
|
|
39
|
-
verb = "Inspecting…";
|
|
40
|
-
detail = "reading relevant code";
|
|
41
|
-
break;
|
|
42
|
-
case "edit":
|
|
43
|
-
case "write":
|
|
44
|
-
verb = "Changing…";
|
|
45
|
-
detail = "editing";
|
|
46
|
-
break;
|
|
47
|
-
case "bash":
|
|
48
|
-
verb = "Running…";
|
|
49
|
-
detail = "command";
|
|
50
|
-
break;
|
|
51
|
-
default:
|
|
52
|
-
verb = "Working…";
|
|
53
|
-
detail = `using ${safeToolName(message.toolName)}`;
|
|
54
|
-
}
|
|
55
|
-
break;
|
|
56
|
-
default: {
|
|
57
|
-
const exhaustive: never = message;
|
|
58
|
-
return exhaustive;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const suffix = modelDisplayName({ id: modelId }) || "unknown model";
|
|
63
|
-
return `${theme.fg("accent", verb)} ${theme.fg("dim", `(${theme.bold("esc")} to interrupt · ${detail})`)}${theme.fg("dim", ` · ${suffix}`)}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function registerRequestActivity(pi: ExtensionAPI): void {
|
|
67
|
-
let active = false;
|
|
68
|
-
let activeModel: string | undefined;
|
|
69
|
-
|
|
70
|
-
const render = (message: ActivityMessage, ctx: ExtensionContext): void => {
|
|
71
|
-
ctx.ui.setWorkingMessage(formatActivityMessage(message, ctx.ui.theme, activeModel));
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const clear = (ctx?: ExtensionContext): void => {
|
|
75
|
-
active = false;
|
|
76
|
-
activeModel = undefined;
|
|
77
|
-
if (ctx?.mode === "tui") {
|
|
78
|
-
ctx.ui.setWorkingMessage();
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
pi.on("agent_start", (_event, ctx) => {
|
|
83
|
-
if (ctx.mode !== "tui") return;
|
|
84
|
-
if (!active) activeModel = ctx.model?.id;
|
|
85
|
-
active = true;
|
|
86
|
-
render({ kind: "prompt" }, ctx);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
pi.on("tool_execution_start", (event, ctx) => {
|
|
90
|
-
if (ctx.mode !== "tui" || !active) return;
|
|
91
|
-
render({ kind: "tool", toolName: event.toolName }, ctx);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
pi.on("tool_execution_end", (event, ctx) => {
|
|
95
|
-
if (ctx.mode !== "tui" || !active) return;
|
|
96
|
-
render({ kind: "tool-result", failed: event.isError }, ctx);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
pi.on("message_update", (event, ctx) => {
|
|
100
|
-
if (ctx.mode !== "tui" || !active || event.assistantMessageEvent.type !== "text_start") return;
|
|
101
|
-
render({ kind: "responding" }, ctx);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
pi.on("agent_settled", (_event, ctx) => {
|
|
105
|
-
if (ctx.mode !== "tui" || !active || !ctx.isIdle?.() || ctx.hasPendingMessages?.()) return;
|
|
106
|
-
clear(ctx);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
pi.on("session_shutdown", (_event, ctx) => clear(ctx));
|
|
110
|
-
}
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { modelDisplayName } from "./display.ts";
|
|
4
|
+
|
|
5
|
+
export type ActivityMessage =
|
|
6
|
+
| { kind: "prompt" }
|
|
7
|
+
| { kind: "tool"; toolName: string }
|
|
8
|
+
| { kind: "tool-result"; failed: boolean }
|
|
9
|
+
| { kind: "responding" };
|
|
10
|
+
|
|
11
|
+
function safeToolName(toolName: string): string {
|
|
12
|
+
const normalized = toolName.replace(/[\u0000-\u001F\u007F]+/gu, " ").replace(/\s+/gu, " ").trim();
|
|
13
|
+
return truncateToWidth(normalized || "tool", 32, "…");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatActivityMessage(message: ActivityMessage, theme: Theme, modelId?: string): string {
|
|
17
|
+
let verb: string;
|
|
18
|
+
let detail: string;
|
|
19
|
+
|
|
20
|
+
switch (message.kind) {
|
|
21
|
+
case "prompt":
|
|
22
|
+
verb = "Mapping…";
|
|
23
|
+
detail = "understanding request";
|
|
24
|
+
break;
|
|
25
|
+
case "tool-result":
|
|
26
|
+
verb = message.failed ? "Recovering…" : "Reviewing…";
|
|
27
|
+
detail = message.failed ? "tool failed" : "reading the result";
|
|
28
|
+
break;
|
|
29
|
+
case "responding":
|
|
30
|
+
verb = "Responding…";
|
|
31
|
+
detail = "assembling the answer";
|
|
32
|
+
break;
|
|
33
|
+
case "tool":
|
|
34
|
+
switch (message.toolName.trim().toLowerCase()) {
|
|
35
|
+
case "read":
|
|
36
|
+
case "grep":
|
|
37
|
+
case "find":
|
|
38
|
+
case "ls":
|
|
39
|
+
verb = "Inspecting…";
|
|
40
|
+
detail = "reading relevant code";
|
|
41
|
+
break;
|
|
42
|
+
case "edit":
|
|
43
|
+
case "write":
|
|
44
|
+
verb = "Changing…";
|
|
45
|
+
detail = "editing";
|
|
46
|
+
break;
|
|
47
|
+
case "bash":
|
|
48
|
+
verb = "Running…";
|
|
49
|
+
detail = "command";
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
verb = "Working…";
|
|
53
|
+
detail = `using ${safeToolName(message.toolName)}`;
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
default: {
|
|
57
|
+
const exhaustive: never = message;
|
|
58
|
+
return exhaustive;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const suffix = modelDisplayName({ id: modelId }) || "unknown model";
|
|
63
|
+
return `${theme.fg("accent", verb)} ${theme.fg("dim", `(${theme.bold("esc")} to interrupt · ${detail})`)}${theme.fg("dim", ` · ${suffix}`)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerRequestActivity(pi: ExtensionAPI): void {
|
|
67
|
+
let active = false;
|
|
68
|
+
let activeModel: string | undefined;
|
|
69
|
+
|
|
70
|
+
const render = (message: ActivityMessage, ctx: ExtensionContext): void => {
|
|
71
|
+
ctx.ui.setWorkingMessage(formatActivityMessage(message, ctx.ui.theme, activeModel));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const clear = (ctx?: ExtensionContext): void => {
|
|
75
|
+
active = false;
|
|
76
|
+
activeModel = undefined;
|
|
77
|
+
if (ctx?.mode === "tui") {
|
|
78
|
+
ctx.ui.setWorkingMessage();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
83
|
+
if (ctx.mode !== "tui") return;
|
|
84
|
+
if (!active) activeModel = ctx.model?.id;
|
|
85
|
+
active = true;
|
|
86
|
+
render({ kind: "prompt" }, ctx);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
pi.on("tool_execution_start", (event, ctx) => {
|
|
90
|
+
if (ctx.mode !== "tui" || !active) return;
|
|
91
|
+
render({ kind: "tool", toolName: event.toolName }, ctx);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
pi.on("tool_execution_end", (event, ctx) => {
|
|
95
|
+
if (ctx.mode !== "tui" || !active) return;
|
|
96
|
+
render({ kind: "tool-result", failed: event.isError }, ctx);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
pi.on("message_update", (event, ctx) => {
|
|
100
|
+
if (ctx.mode !== "tui" || !active || event.assistantMessageEvent.type !== "text_start") return;
|
|
101
|
+
render({ kind: "responding" }, ctx);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
105
|
+
if (ctx.mode !== "tui" || !active || !ctx.isIdle?.() || ctx.hasPendingMessages?.()) return;
|
|
106
|
+
clear(ctx);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
pi.on("session_shutdown", (_event, ctx) => clear(ctx));
|
|
110
|
+
}
|