@thoth-agents/pi-thoth-theme 0.2.0 → 0.4.0
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/README.md +44 -7
- package/dist/index.ts +570 -102
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,12 +7,23 @@ dependencies are pinned to `1.0.2` in the root workspace lockfile.
|
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Thoth's complete Pi Install and applied Update install and individually verify
|
|
11
|
+
`npm:@thoth-agents/pi-thoth-theme@>=0.3.0`. An existing copy from any source at or
|
|
12
|
+
above that floor is preserved without reinstalling; an older copy remains
|
|
13
|
+
untouched and blocks completion with manual upgrade guidance. Ambiguous identity
|
|
14
|
+
fails closed. Upgrade a local/Git checkout in place, or review ownership before
|
|
15
|
+
switching its configured source with Pi's native remove/install commands; verify
|
|
16
|
+
the installed manifest and rerun setup. Dry-run performs no mutation.
|
|
17
|
+
|
|
18
|
+
For a standalone install:
|
|
11
19
|
|
|
12
20
|
```sh
|
|
13
|
-
pi install
|
|
21
|
+
pi install 'npm:@thoth-agents/pi-thoth-theme@>=0.3.0'
|
|
14
22
|
```
|
|
15
23
|
|
|
24
|
+
For workspace development, build first and run `pi install ./pi-packages/pi-thoth-theme`
|
|
25
|
+
from the repository root. Restart Pi after installation.
|
|
26
|
+
|
|
16
27
|
Select the `thoth` theme in Pi's theme settings. Theme selection is independent
|
|
17
28
|
of the module toggles below.
|
|
18
29
|
|
|
@@ -26,8 +37,10 @@ of the module toggles below.
|
|
|
26
37
|
drop speed, then cache, then tokens before truncating cost. Editor-border
|
|
27
38
|
metadata is decorated in place without claiming the custom editor slot.
|
|
28
39
|
- **Tools**: boxed calls and results for `read`, `bash`, `powershell`, `ls`,
|
|
29
|
-
`grep`, `find`, `edit` and `
|
|
30
|
-
glyphs or ASCII alternatives. `
|
|
40
|
+
`grep`, `find`, `edit`, `write` and `codemode`, with Nerd Font icons, native Unicode
|
|
41
|
+
glyphs or ASCII alternatives. `codemode` previews up to eight nested calls
|
|
42
|
+
with status and duration; expanded results show the script, output and errors
|
|
43
|
+
without replacing Pi's builtin codemode extension. `bash` and `powershell` show a live elapsed time
|
|
31
44
|
while running. Execution and parameters delegate to Pi's built-in tools;
|
|
32
45
|
Pi's native output toggle still
|
|
33
46
|
collapses and expands results. Registered non-built-in tools keep their own
|
|
@@ -41,11 +54,31 @@ of the module toggles below.
|
|
|
41
54
|
tool providers, and recent sessions. Resource and session details load
|
|
42
55
|
best-effort through public APIs.
|
|
43
56
|
|
|
57
|
+
## Subagent usage channel
|
|
58
|
+
|
|
59
|
+
The status line subscribes to pi-core's v1 `thoth:subagents:usage` channel and
|
|
60
|
+
requests a snapshot on `thoth:subagents:usage:request` at session start, after
|
|
61
|
+
subscribing, using `data: {}`. The producer is `@thoth-agents/pi-subagents`;
|
|
62
|
+
envelopes are `{ v, source, sessionId, at, data }`, with the parent session ID
|
|
63
|
+
in the envelope, Unix-millisecond `at`, and only cumulative `totalCost` and
|
|
64
|
+
`runCount` in data. Pre-readiness requests are answered once after usage restore.
|
|
65
|
+
Invalid, unsupported-version and foreign-session envelopes are ignored; no task
|
|
66
|
+
prompts, results, transcripts or logs are consumed.
|
|
67
|
+
|
|
68
|
+
The raw `thoth:subagent-usage` bus event/request is removed with no fallback or
|
|
69
|
+
dual publication. Upgrade pi-subagents and pi-thoth-theme to `>=0.3.0` together
|
|
70
|
+
and `/reload`; mixed old/new versions lose subagent cost display. The persisted
|
|
71
|
+
usage checkpoint custom-entry type remains `thoth:subagent-usage`, so checkpoint
|
|
72
|
+
restore and replay-duplicate suppression are unchanged. See
|
|
73
|
+
[pi-core's exact contract](../pi-core/README.md#task-summary-and-usage-channels).
|
|
74
|
+
|
|
44
75
|
## Render KIT
|
|
45
76
|
|
|
46
77
|
Provides the Render KIT v1 contract defined by `@thoth-agents/pi-core` for
|
|
47
78
|
first-party tools, custom messages and above-editor widgets. The kit shares this
|
|
48
79
|
package's frames, theme roles, collapse hints and working/elapsed indicators.
|
|
80
|
+
The Render KIT v1 semantic icon table includes `elapsed` for work-panel metrics,
|
|
81
|
+
with nerd-clock, Unicode and ASCII alternatives.
|
|
49
82
|
Producers discover it at render time, so extension load order does not matter;
|
|
50
83
|
they do not depend on this package and keep native Pi rendering when it is absent.
|
|
51
84
|
|
|
@@ -65,7 +98,7 @@ Edit `~/.pi/agent/pi-thoth-theme.json` (or
|
|
|
65
98
|
"icons": "nerd",
|
|
66
99
|
"statusLine": {
|
|
67
100
|
"enabled": true,
|
|
68
|
-
"subscriptionProviders": ["claude-bridge"]
|
|
101
|
+
"subscriptionProviders": ["claude-bridge", "antigravity"]
|
|
69
102
|
},
|
|
70
103
|
"inputBox": { "enabled": true },
|
|
71
104
|
"tools": {
|
|
@@ -82,14 +115,18 @@ or `"ascii"` (plain-text alternatives).
|
|
|
82
115
|
Missing or malformed icon values use `"nerd"`. Each module's `enabled` key
|
|
83
116
|
accepts a boolean and defaults to `true`. Set it to `false` to leave Pi's native
|
|
84
117
|
behavior on that surface. `statusLine.subscriptionProviders` accepts an array of
|
|
85
|
-
provider identifier strings (defaults to `["claude-bridge"]`) to mark
|
|
118
|
+
provider identifier strings (defaults to `["claude-bridge", "antigravity"]`) to mark
|
|
86
119
|
subscription-backed usage with `(sub)` in the status line cost segment.
|
|
120
|
+
Classification uses the current provider for the displayed total, not a
|
|
121
|
+
per-message billing split; explicit user lists are preserved. Antigravity costs
|
|
122
|
+
are API-equivalent catalog estimates, not subscription charges.
|
|
87
123
|
|
|
88
124
|
`inputBox.enabled` defaults to `true` and requires `statusLine.enabled` to be
|
|
89
125
|
`true`. It frames the native editor in a rounded `muted` (sand) box with side borders,
|
|
90
126
|
a dim `type or / for commands` placeholder, and top-left ready or native working
|
|
91
127
|
status (including elapsed seconds). Git branch and working directory appear on
|
|
92
|
-
the top border
|
|
128
|
+
the top border, using pi-core's root-exported `formatCwd` for home abbreviation
|
|
129
|
+
(shared with the sidebar); model, thinking effort and context usage appear on the bottom.
|
|
93
130
|
Native scroll indicators are preserved. The status line always stays in its
|
|
94
131
|
separate footer row below the box. Narrow widths use the native editor geometry.
|
|
95
132
|
Disable `inputBox` to keep the native editor and the same footer.
|
package/dist/index.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// ../pi-core/src/channels.ts
|
|
12
|
-
function defineChannel(definition) {
|
|
13
|
-
return definition;
|
|
14
|
-
}
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
15
11
|
|
|
16
12
|
// ../pi-core/src/duration.ts
|
|
17
13
|
function formatDuration(ms) {
|
|
@@ -26,31 +22,13 @@ function formatDuration(ms) {
|
|
|
26
22
|
const hours = Math.floor(ms / 36e5);
|
|
27
23
|
return `${hours}h ${String(totalMinutes % 60).padStart(2, "0")}m`;
|
|
28
24
|
}
|
|
25
|
+
var init_duration = __esm({
|
|
26
|
+
"../pi-core/src/duration.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
}
|
|
29
|
+
});
|
|
29
30
|
|
|
30
31
|
// ../pi-core/src/render-kit.ts
|
|
31
|
-
var nativeFrames = {
|
|
32
|
-
spinnerFrames: Object.freeze([
|
|
33
|
-
"\u280B",
|
|
34
|
-
"\u2819",
|
|
35
|
-
"\u2839",
|
|
36
|
-
"\u2838",
|
|
37
|
-
"\u283C",
|
|
38
|
-
"\u2834",
|
|
39
|
-
"\u2826",
|
|
40
|
-
"\u2827",
|
|
41
|
-
"\u2807",
|
|
42
|
-
"\u280F"
|
|
43
|
-
]),
|
|
44
|
-
workingFrames: Object.freeze(["\u25B3", "\u25ED", "\u25B2", "\u25EE"])
|
|
45
|
-
};
|
|
46
|
-
var terminalFooterGlyphs = {
|
|
47
|
-
completed: "\u2713",
|
|
48
|
-
deleted: "\u2713",
|
|
49
|
-
failed: "\u2717",
|
|
50
|
-
cancelled: "\u2717",
|
|
51
|
-
interrupted: "\u2717",
|
|
52
|
-
blocked: "\u2717"
|
|
53
|
-
};
|
|
54
32
|
function getToolElapsedMs(options) {
|
|
55
33
|
const context = options.context;
|
|
56
34
|
const state = context?.state;
|
|
@@ -76,11 +54,15 @@ function getToolElapsedMs(options) {
|
|
|
76
54
|
state.completedElapsedMs = elapsedMs;
|
|
77
55
|
return elapsedMs;
|
|
78
56
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
|
|
57
|
+
function renderToolFooter(kit, theme, options) {
|
|
58
|
+
if (kit?.toolFooter) return kit.toolFooter(theme, options);
|
|
59
|
+
const elapsedMs = getToolElapsedMs(options);
|
|
60
|
+
const elapsed = elapsedMs === void 0 ? "" : formatDuration(Math.floor(elapsedMs / 1e3) * 1e3);
|
|
61
|
+
const terminalGlyph = terminalFooterGlyphs[options.status];
|
|
62
|
+
const status = terminalGlyph ?? (options.status === "in_progress" ? "running" : options.status);
|
|
63
|
+
const summary = typeof options.summary === "string" ? [options.summary] : options.summary ?? [];
|
|
64
|
+
return [status, elapsed, ...terminalGlyph ? summary : []].filter(Boolean).join(" \xB7 ");
|
|
65
|
+
}
|
|
84
66
|
function registration() {
|
|
85
67
|
try {
|
|
86
68
|
const record = shared[registryKey];
|
|
@@ -98,6 +80,348 @@ function registerRenderKit(kit, owner) {
|
|
|
98
80
|
function withdrawRenderKit(token) {
|
|
99
81
|
if (registration()?.token === token) delete shared[registryKey];
|
|
100
82
|
}
|
|
83
|
+
function getRenderKit() {
|
|
84
|
+
try {
|
|
85
|
+
const kit = registration()?.kit;
|
|
86
|
+
if (kit?.version === RENDER_KIT_VERSION && (!("resolveToolRenderers" in kit) || typeof kit.resolveToolRenderers === "function") && (!("toolFooter" in kit) || typeof kit.toolFooter === "function") && (!("icon" in kit) || typeof kit.icon === "function") && [
|
|
87
|
+
"card",
|
|
88
|
+
"collapse",
|
|
89
|
+
"cachedComponent",
|
|
90
|
+
"indicator",
|
|
91
|
+
"statusGlyph",
|
|
92
|
+
"widgetHeading",
|
|
93
|
+
"treeRow",
|
|
94
|
+
"fg"
|
|
95
|
+
].every(
|
|
96
|
+
(method2) => typeof kit[method2] === "function"
|
|
97
|
+
))
|
|
98
|
+
return kit;
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
function createKitRenderMemo() {
|
|
104
|
+
let cached;
|
|
105
|
+
return {
|
|
106
|
+
render(width, build) {
|
|
107
|
+
const kit = getRenderKit();
|
|
108
|
+
if (!cached || cached.width !== width || cached.kit !== kit) {
|
|
109
|
+
cached = { width, kit, lines: build(kit) };
|
|
110
|
+
}
|
|
111
|
+
return cached.lines;
|
|
112
|
+
},
|
|
113
|
+
invalidate() {
|
|
114
|
+
cached = void 0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
var nativeFrames, terminalFooterGlyphs, RENDER_KIT_VERSION, registryKey, shared;
|
|
119
|
+
var init_render_kit = __esm({
|
|
120
|
+
"../pi-core/src/render-kit.ts"() {
|
|
121
|
+
"use strict";
|
|
122
|
+
init_duration();
|
|
123
|
+
nativeFrames = {
|
|
124
|
+
spinnerFrames: Object.freeze([
|
|
125
|
+
"\u280B",
|
|
126
|
+
"\u2819",
|
|
127
|
+
"\u2839",
|
|
128
|
+
"\u2838",
|
|
129
|
+
"\u283C",
|
|
130
|
+
"\u2834",
|
|
131
|
+
"\u2826",
|
|
132
|
+
"\u2827",
|
|
133
|
+
"\u2807",
|
|
134
|
+
"\u280F"
|
|
135
|
+
]),
|
|
136
|
+
workingFrames: Object.freeze(["\u25B3", "\u25ED", "\u25B2", "\u25EE"])
|
|
137
|
+
};
|
|
138
|
+
terminalFooterGlyphs = {
|
|
139
|
+
completed: "\u2713",
|
|
140
|
+
deleted: "\u2713",
|
|
141
|
+
failed: "\u2717",
|
|
142
|
+
cancelled: "\u2717",
|
|
143
|
+
interrupted: "\u2717",
|
|
144
|
+
blocked: "\u2717"
|
|
145
|
+
};
|
|
146
|
+
RENDER_KIT_VERSION = 1;
|
|
147
|
+
registryKey = /* @__PURE__ */ Symbol.for(
|
|
148
|
+
`thoth-agents.pi-core.render-kit.v${RENDER_KIT_VERSION}`
|
|
149
|
+
);
|
|
150
|
+
shared = globalThis;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ../pi-core/src/panel-frame.ts
|
|
155
|
+
var init_panel_frame = __esm({
|
|
156
|
+
"../pi-core/src/panel-frame.ts"() {
|
|
157
|
+
"use strict";
|
|
158
|
+
init_render_kit();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ../pi-core/src/validation.ts
|
|
163
|
+
function isRecord(value) {
|
|
164
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
165
|
+
}
|
|
166
|
+
function isNonEmptyString(value) {
|
|
167
|
+
return typeof value === "string" && value.length > 0;
|
|
168
|
+
}
|
|
169
|
+
function isNonNegativeInteger(value) {
|
|
170
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ../pi-core/src/channels.ts
|
|
174
|
+
function defineChannel(definition) {
|
|
175
|
+
return definition;
|
|
176
|
+
}
|
|
177
|
+
function publish(events, channel, snapshot) {
|
|
178
|
+
events.emit(channel.name, {
|
|
179
|
+
v: channel.version,
|
|
180
|
+
source: snapshot.source,
|
|
181
|
+
sessionId: snapshot.sessionId,
|
|
182
|
+
at: Date.now(),
|
|
183
|
+
data: snapshot.data
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function request(events, requestChannel, payload) {
|
|
187
|
+
publish(events, requestChannel, payload);
|
|
188
|
+
}
|
|
189
|
+
function subscribe(events, channel, options) {
|
|
190
|
+
return events.on(channel.name, (value) => {
|
|
191
|
+
if (!isEnvelope(value, channel, options.sessionId)) return;
|
|
192
|
+
options.onSnapshot(value);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function isEnvelope(value, channel, sessionId) {
|
|
196
|
+
try {
|
|
197
|
+
return isRecord(value) && value.v === channel.version && isNonEmptyString(value.source) && isNonEmptyString(value.sessionId) && (sessionId === void 0 || value.sessionId === sessionId) && typeof value.at === "number" && Number.isFinite(value.at) && value.at >= 0 && channel.validate(value.data);
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ../pi-core/src/background.ts
|
|
204
|
+
var backgroundStatuses = [
|
|
205
|
+
"running",
|
|
206
|
+
"succeeded",
|
|
207
|
+
"failed",
|
|
208
|
+
"cancelled",
|
|
209
|
+
"timed_out"
|
|
210
|
+
];
|
|
211
|
+
var BACKGROUND_PROGRESS_MAX_LENGTH = 200;
|
|
212
|
+
function isBackgroundSnapshot(value) {
|
|
213
|
+
try {
|
|
214
|
+
if (!isExactRecord(value, ["tasks", "counts"]) || !Array.isArray(value.tasks))
|
|
215
|
+
return false;
|
|
216
|
+
for (const task of value.tasks) {
|
|
217
|
+
if (!isBackgroundTask(task)) return false;
|
|
218
|
+
}
|
|
219
|
+
const { counts } = value;
|
|
220
|
+
return isExactRecord(counts, backgroundStatuses) && backgroundStatuses.every((status) => isNonNegativeInteger(counts[status]));
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function isExactRecord(value, allowedKeys) {
|
|
226
|
+
if (!isRecord(value)) return false;
|
|
227
|
+
const prototype = Object.getPrototypeOf(value);
|
|
228
|
+
return (prototype === Object.prototype || prototype === null) && Reflect.ownKeys(value).every(
|
|
229
|
+
(key) => typeof key === "string" && allowedKeys.includes(key)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
function isOptionalTimestamp(value) {
|
|
233
|
+
return value === void 0 || isNonNegativeInteger(value);
|
|
234
|
+
}
|
|
235
|
+
function isProgress(value) {
|
|
236
|
+
return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.length <= BACKGROUND_PROGRESS_MAX_LENGTH;
|
|
237
|
+
}
|
|
238
|
+
function isBackgroundTask(value) {
|
|
239
|
+
return isExactRecord(value, [
|
|
240
|
+
"id",
|
|
241
|
+
"name",
|
|
242
|
+
"kind",
|
|
243
|
+
"status",
|
|
244
|
+
"createdAt",
|
|
245
|
+
"startedAt",
|
|
246
|
+
"endedAt",
|
|
247
|
+
"deadlineAt",
|
|
248
|
+
"lastCheckedAt",
|
|
249
|
+
"lastProgressAt",
|
|
250
|
+
"stopRequestedAt",
|
|
251
|
+
"dismissedAt",
|
|
252
|
+
"exitCode",
|
|
253
|
+
"signal",
|
|
254
|
+
"progress",
|
|
255
|
+
"dismissed"
|
|
256
|
+
]) && isNonEmptyString(value.id) && (value.name === void 0 || typeof value.name === "string") && (value.kind === "process" || value.kind === "command_watch") && backgroundStatuses.some((status) => status === value.status) && isNonNegativeInteger(value.createdAt) && isNonNegativeInteger(value.startedAt) && isOptionalTimestamp(value.endedAt) && isOptionalTimestamp(value.deadlineAt) && isOptionalTimestamp(value.lastCheckedAt) && isOptionalTimestamp(value.lastProgressAt) && isOptionalTimestamp(value.stopRequestedAt) && isOptionalTimestamp(value.dismissedAt) && (value.exitCode === void 0 || value.exitCode === null || typeof value.exitCode === "number" && Number.isSafeInteger(value.exitCode)) && (value.signal === void 0 || value.signal === null || isNonEmptyString(value.signal)) && (value.progress === void 0 || isProgress(value.progress)) && (value.dismissed === void 0 || typeof value.dismissed === "boolean");
|
|
257
|
+
}
|
|
258
|
+
var BACKGROUND_STATE_CHANNEL = defineChannel({
|
|
259
|
+
name: "thoth:background:state",
|
|
260
|
+
version: 1,
|
|
261
|
+
validate: isBackgroundSnapshot
|
|
262
|
+
});
|
|
263
|
+
function isBackgroundStateRequest(value) {
|
|
264
|
+
try {
|
|
265
|
+
return isExactRecord(value, []);
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
var BACKGROUND_STATE_REQUEST = defineChannel({
|
|
271
|
+
name: "thoth:background:state:request",
|
|
272
|
+
version: 1,
|
|
273
|
+
validate: isBackgroundStateRequest
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ../pi-core/src/cwd.ts
|
|
277
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
278
|
+
function formatCwd(cwd, home) {
|
|
279
|
+
if (!home) return cwd;
|
|
280
|
+
const relativeToHome = relative(resolve(home), resolve(cwd));
|
|
281
|
+
if (relativeToHome === ".." || relativeToHome.startsWith(`..${sep}`) || isAbsolute(relativeToHome)) {
|
|
282
|
+
return cwd;
|
|
283
|
+
}
|
|
284
|
+
return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ../pi-core/src/index.ts
|
|
288
|
+
init_duration();
|
|
289
|
+
init_render_kit();
|
|
290
|
+
|
|
291
|
+
// ../pi-core/src/session-cost.ts
|
|
292
|
+
function computeSessionCost(entries, { subscriptionProviders = [], providerOf } = {}) {
|
|
293
|
+
let cost = 0;
|
|
294
|
+
for (const entry of entries ?? []) {
|
|
295
|
+
if (!entry) continue;
|
|
296
|
+
if (entry.type === "message") {
|
|
297
|
+
const message = entry.message;
|
|
298
|
+
if (message?.role === "assistant" || message?.role === "toolResult")
|
|
299
|
+
cost += message.usage?.cost?.total || 0;
|
|
300
|
+
} else if (["branch_summary", "compaction", "usage"].includes(entry.type)) {
|
|
301
|
+
cost += entry.usage?.cost?.total || 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const provider = providerOf?.();
|
|
305
|
+
return {
|
|
306
|
+
cost,
|
|
307
|
+
isSubscription: provider !== void 0 && subscriptionProviders.includes(provider)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function combineSessionAndSubagentCost(sessionCost, latestSubagentCost = 0) {
|
|
311
|
+
return sessionCost + latestSubagentCost;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ../pi-core/src/subagents.ts
|
|
315
|
+
var subagentStatuses = [
|
|
316
|
+
"queued",
|
|
317
|
+
"running",
|
|
318
|
+
"stopping",
|
|
319
|
+
"completed",
|
|
320
|
+
"failed",
|
|
321
|
+
"cancelled",
|
|
322
|
+
"interrupted"
|
|
323
|
+
];
|
|
324
|
+
var subagentEfforts = [
|
|
325
|
+
"off",
|
|
326
|
+
"minimal",
|
|
327
|
+
"low",
|
|
328
|
+
"medium",
|
|
329
|
+
"high",
|
|
330
|
+
"xhigh",
|
|
331
|
+
"max"
|
|
332
|
+
];
|
|
333
|
+
var SUBAGENT_PREVIEW_MAX_LENGTH = 800;
|
|
334
|
+
function isSubagentsSnapshot(value) {
|
|
335
|
+
try {
|
|
336
|
+
if (!isExactRecord2(value, ["tasks", "history", "counts", "totals"]) || !Array.isArray(value.tasks) || !Array.isArray(value.history) || value.history.length > 100)
|
|
337
|
+
return false;
|
|
338
|
+
for (const tasks of [value.tasks, value.history]) {
|
|
339
|
+
for (const task of tasks) {
|
|
340
|
+
if (!isSubagentTask(task)) return false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const { counts, totals } = value;
|
|
344
|
+
return isCounts(counts) && isExactRecord2(totals, [...subagentStatuses, "total"]) && isNonNegativeInteger(totals.total) && subagentStatuses.every((status) => isNonNegativeInteger(totals[status]));
|
|
345
|
+
} catch {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function isExactRecord2(value, allowedKeys) {
|
|
350
|
+
if (!isRecord(value)) return false;
|
|
351
|
+
const prototype = Object.getPrototypeOf(value);
|
|
352
|
+
return (prototype === Object.prototype || prototype === null) && Reflect.ownKeys(value).every(
|
|
353
|
+
(key) => typeof key === "string" && allowedKeys.includes(key)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function isCounts(value) {
|
|
357
|
+
return isExactRecord2(value, subagentStatuses) && subagentStatuses.every((status) => isNonNegativeInteger(value[status]));
|
|
358
|
+
}
|
|
359
|
+
function isOptionalString(value) {
|
|
360
|
+
return value === void 0 || typeof value === "string";
|
|
361
|
+
}
|
|
362
|
+
function isOptionalTimestamp2(value) {
|
|
363
|
+
return value === void 0 || isNonNegativeInteger(value);
|
|
364
|
+
}
|
|
365
|
+
function isNonNegativeNumber(value) {
|
|
366
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
367
|
+
}
|
|
368
|
+
function isTaskUsage(value) {
|
|
369
|
+
return isExactRecord2(value, ["input", "output", "cost"]) && (value.input === void 0 || isNonNegativeInteger(value.input)) && (value.output === void 0 || isNonNegativeInteger(value.output)) && (value.cost === void 0 || isNonNegativeNumber(value.cost));
|
|
370
|
+
}
|
|
371
|
+
function isSubagentTask(value) {
|
|
372
|
+
return isExactRecord2(value, [
|
|
373
|
+
"id",
|
|
374
|
+
"agent",
|
|
375
|
+
"displayName",
|
|
376
|
+
"mode",
|
|
377
|
+
"status",
|
|
378
|
+
"model",
|
|
379
|
+
"effort",
|
|
380
|
+
"createdAt",
|
|
381
|
+
"startedAt",
|
|
382
|
+
"endedAt",
|
|
383
|
+
"lastActivityAt",
|
|
384
|
+
"usage",
|
|
385
|
+
"preview"
|
|
386
|
+
]) && isNonEmptyString(value.id) && isNonEmptyString(value.agent) && isOptionalString(value.displayName) && (value.mode === "task" || value.mode === "background") && subagentStatuses.some((status) => status === value.status) && isOptionalString(value.model) && (value.effort === void 0 || subagentEfforts.some((effort) => effort === value.effort)) && isNonNegativeInteger(value.createdAt) && isOptionalTimestamp2(value.startedAt) && isOptionalTimestamp2(value.endedAt) && isOptionalTimestamp2(value.lastActivityAt) && (value.usage === void 0 || isTaskUsage(value.usage)) && (value.preview === void 0 || typeof value.preview === "string" && value.preview.length <= SUBAGENT_PREVIEW_MAX_LENGTH);
|
|
387
|
+
}
|
|
388
|
+
var SUBAGENTS_STATE_CHANNEL = defineChannel({
|
|
389
|
+
name: "thoth:subagents:state",
|
|
390
|
+
version: 2,
|
|
391
|
+
validate: isSubagentsSnapshot
|
|
392
|
+
});
|
|
393
|
+
function isSubagentsStateRequest(value) {
|
|
394
|
+
try {
|
|
395
|
+
return isExactRecord2(value, []);
|
|
396
|
+
} catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function isSubagentsUsageRequest(value) {
|
|
401
|
+
return isSubagentsStateRequest(value);
|
|
402
|
+
}
|
|
403
|
+
var SUBAGENTS_STATE_REQUEST = defineChannel({
|
|
404
|
+
name: "thoth:subagents:state:request",
|
|
405
|
+
version: 1,
|
|
406
|
+
validate: isSubagentsStateRequest
|
|
407
|
+
});
|
|
408
|
+
function isSubagentsUsageSnapshot(value) {
|
|
409
|
+
try {
|
|
410
|
+
return isExactRecord2(value, ["totalCost", "runCount"]) && isNonNegativeNumber(value.totalCost) && isNonNegativeInteger(value.runCount);
|
|
411
|
+
} catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
var SUBAGENTS_USAGE_CHANNEL = defineChannel({
|
|
416
|
+
name: "thoth:subagents:usage",
|
|
417
|
+
version: 1,
|
|
418
|
+
validate: isSubagentsUsageSnapshot
|
|
419
|
+
});
|
|
420
|
+
var SUBAGENTS_USAGE_REQUEST = defineChannel({
|
|
421
|
+
name: "thoth:subagents:usage:request",
|
|
422
|
+
version: 1,
|
|
423
|
+
validate: isSubagentsUsageRequest
|
|
424
|
+
});
|
|
101
425
|
|
|
102
426
|
// ../pi-core/src/todo.ts
|
|
103
427
|
var todoStatuses = [
|
|
@@ -123,11 +447,11 @@ function isTodoSnapshot(value) {
|
|
|
123
447
|
function isTaskId(value) {
|
|
124
448
|
return isNonNegativeInteger(value) && value > 0;
|
|
125
449
|
}
|
|
126
|
-
function
|
|
450
|
+
function isOptionalString2(value) {
|
|
127
451
|
return value === void 0 || typeof value === "string";
|
|
128
452
|
}
|
|
129
453
|
function isTodoTask(value) {
|
|
130
|
-
if (!isRecord(value) || !isTaskId(value.id) || typeof value.subject !== "string" || !todoStatuses.some((status) => status === value.status) || !Array.isArray(value.blockedBy) || !
|
|
454
|
+
if (!isRecord(value) || !isTaskId(value.id) || typeof value.subject !== "string" || !todoStatuses.some((status) => status === value.status) || !Array.isArray(value.blockedBy) || !isOptionalString2(value.description) || !isOptionalString2(value.activeForm) || !isOptionalString2(value.owner))
|
|
131
455
|
return false;
|
|
132
456
|
for (const id of value.blockedBy) {
|
|
133
457
|
if (!isTaskId(id)) return false;
|
|
@@ -160,6 +484,16 @@ var registryKey2 = /* @__PURE__ */ Symbol.for(
|
|
|
160
484
|
`thoth-agents.pi-core.tool-definitions.v${TOOL_DEFINITION_REGISTRY_VERSION}`
|
|
161
485
|
);
|
|
162
486
|
|
|
487
|
+
// ../pi-core/src/work-panel-host.ts
|
|
488
|
+
init_render_kit();
|
|
489
|
+
|
|
490
|
+
// ../pi-core/src/work-panel-detail.ts
|
|
491
|
+
init_panel_frame();
|
|
492
|
+
init_render_kit();
|
|
493
|
+
|
|
494
|
+
// ../pi-core/src/work-panel-render.ts
|
|
495
|
+
init_render_kit();
|
|
496
|
+
|
|
163
497
|
// src/render-kit/index.ts
|
|
164
498
|
import {
|
|
165
499
|
stripTerminalSequences,
|
|
@@ -192,6 +526,7 @@ var ICONS = {
|
|
|
192
526
|
model: { nerd: "\u{F06A9}", unicode: "\u25CF", ascii: "*" },
|
|
193
527
|
effort: { nerd: "\u{F09D1}", unicode: "\u25D0", ascii: "o" },
|
|
194
528
|
context: { nerd: "\uF2DB", unicode: "ctx", ascii: "ctx" },
|
|
529
|
+
elapsed: { nerd: "\uF017", unicode: "\u25F7", ascii: "elapsed" },
|
|
195
530
|
cost: { nerd: "\uF155", unicode: "$", ascii: "$" },
|
|
196
531
|
tokensIn: { nerd: "\uF062", unicode: "\u2191", ascii: "^" },
|
|
197
532
|
tokensOut: { nerd: "\uF063", unicode: "\u2193", ascii: "v" },
|
|
@@ -453,7 +788,7 @@ function createComponent(renderFn) {
|
|
|
453
788
|
}
|
|
454
789
|
|
|
455
790
|
// src/tools/frame.ts
|
|
456
|
-
import { relative, resolve } from "node:path";
|
|
791
|
+
import { relative as relative2, resolve as resolve2 } from "node:path";
|
|
457
792
|
import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
458
793
|
var FRAME_HORIZONTAL = "\u2500";
|
|
459
794
|
var FRAME_VERTICAL = "\u2502";
|
|
@@ -467,9 +802,9 @@ function formatDisplayPath(targetPath, cwd) {
|
|
|
467
802
|
if (!targetPath) return "";
|
|
468
803
|
if (!cwd) return targetPath;
|
|
469
804
|
try {
|
|
470
|
-
const absTarget =
|
|
471
|
-
const absCwd =
|
|
472
|
-
const rel =
|
|
805
|
+
const absTarget = resolve2(cwd, targetPath);
|
|
806
|
+
const absCwd = resolve2(cwd);
|
|
807
|
+
const rel = relative2(absCwd, absTarget);
|
|
473
808
|
if (!rel.startsWith("..") && !rel.startsWith("/") && !/^[A-Za-z]:/.test(rel)) {
|
|
474
809
|
return rel.replace(/\\/g, "/") || ".";
|
|
475
810
|
}
|
|
@@ -522,6 +857,14 @@ function renderFrameDivider(theme, title, width, borderColor = "accent") {
|
|
|
522
857
|
if (safeWidth <= 4) {
|
|
523
858
|
return [truncateToWidth2(title, safeWidth)];
|
|
524
859
|
}
|
|
860
|
+
if (!title) {
|
|
861
|
+
return [
|
|
862
|
+
theme.fg(
|
|
863
|
+
borderColor,
|
|
864
|
+
`${FRAME_DIVIDER_LEFT}${FRAME_HORIZONTAL.repeat(safeWidth - 2)}${FRAME_DIVIDER_RIGHT}`
|
|
865
|
+
)
|
|
866
|
+
];
|
|
867
|
+
}
|
|
525
868
|
const innerWidth = Math.max(1, safeWidth - 2);
|
|
526
869
|
const rawTitle = ` ${title} `;
|
|
527
870
|
const maxTitleWidth = Math.max(1, innerWidth - 4);
|
|
@@ -837,7 +1180,7 @@ function parseSubscriptionProviders(value) {
|
|
|
837
1180
|
(item) => typeof item === "string" && item.trim().length > 0
|
|
838
1181
|
);
|
|
839
1182
|
}
|
|
840
|
-
return ["claude-bridge"];
|
|
1183
|
+
return ["claude-bridge", "antigravity"];
|
|
841
1184
|
}
|
|
842
1185
|
function parseRespectPackages(value) {
|
|
843
1186
|
const packages = asObject(value).respectPackages;
|
|
@@ -876,19 +1219,10 @@ import {
|
|
|
876
1219
|
} from "@earendil-works/pi-tui";
|
|
877
1220
|
|
|
878
1221
|
// src/status-line/layout.ts
|
|
879
|
-
import { isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
880
1222
|
import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
881
1223
|
function themeFg(theme, token, text) {
|
|
882
1224
|
return theme?.fg ? theme.fg(token, text) : text;
|
|
883
1225
|
}
|
|
884
|
-
function formatCwd(cwd, home) {
|
|
885
|
-
if (!home) return cwd;
|
|
886
|
-
const relativeToHome = relative2(resolve2(home), resolve2(cwd));
|
|
887
|
-
if (relativeToHome === ".." || relativeToHome.startsWith(`..${sep}`) || isAbsolute(relativeToHome)) {
|
|
888
|
-
return cwd;
|
|
889
|
-
}
|
|
890
|
-
return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
|
|
891
|
-
}
|
|
892
1226
|
function formatTokens(count) {
|
|
893
1227
|
if (count < 1e3) return count.toString();
|
|
894
1228
|
if (count < 1e6) {
|
|
@@ -947,14 +1281,18 @@ function contextBar(percent, isAscii, theme) {
|
|
|
947
1281
|
let token = "success";
|
|
948
1282
|
if (percent >= 90) token = "error";
|
|
949
1283
|
else if (percent >= 70) token = "warning";
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1284
|
+
const halves = Math.min(
|
|
1285
|
+
16,
|
|
1286
|
+
Math.max(percent > 0 ? 1 : 0, Math.round(percent / 100 * 16))
|
|
1287
|
+
);
|
|
1288
|
+
const full = Math.floor(halves / 2);
|
|
1289
|
+
const half = halves % 2;
|
|
953
1290
|
const dim = (text) => themeFg(theme, "dim", text);
|
|
1291
|
+
const filled = isAscii ? "#".repeat(full) + (half ? "=" : "") : "\u25C6".repeat(full) + (half ? "\u2B16" : "");
|
|
1292
|
+
const empty = (isAscii ? "-" : "\u25C7").repeat(8 - full - half);
|
|
1293
|
+
const bar = isAscii ? `${dim("[")}${themeFg(theme, token, filled)}${dim(empty)}${dim("]")}` : `${themeFg(theme, token, filled)}${dim(empty)}`;
|
|
954
1294
|
return {
|
|
955
|
-
bar
|
|
956
|
-
emptyChar.repeat(10 - filled)
|
|
957
|
-
)}${dim("]")}`,
|
|
1295
|
+
bar,
|
|
958
1296
|
pct: themeFg(theme, token, `${Math.round(percent)}%`)
|
|
959
1297
|
};
|
|
960
1298
|
}
|
|
@@ -993,7 +1331,7 @@ function renderStatusLine(data, options) {
|
|
|
993
1331
|
const sep2 = ` ${themeFg(theme, "border", icon("separator", mode))} `;
|
|
994
1332
|
const segments = [];
|
|
995
1333
|
if (data.cost !== void 0) {
|
|
996
|
-
const total = data.cost
|
|
1334
|
+
const total = combineSessionAndSubagentCost(data.cost, data.subagentCost);
|
|
997
1335
|
segments.push(
|
|
998
1336
|
themeFg(
|
|
999
1337
|
theme,
|
|
@@ -1449,25 +1787,6 @@ function createWorkingState(requestRender) {
|
|
|
1449
1787
|
};
|
|
1450
1788
|
}
|
|
1451
1789
|
|
|
1452
|
-
// src/status-line/cost.ts
|
|
1453
|
-
function calculateSessionCost(source) {
|
|
1454
|
-
if (!source) return 0;
|
|
1455
|
-
const entries = Array.isArray(source) ? source : typeof source.getEntries === "function" ? source.getEntries() : [];
|
|
1456
|
-
let totalCost = 0;
|
|
1457
|
-
for (const entry of entries) {
|
|
1458
|
-
if (!entry) continue;
|
|
1459
|
-
if (entry.type === "message") {
|
|
1460
|
-
const msg = entry.message;
|
|
1461
|
-
if ((msg?.role === "assistant" || msg?.role === "toolResult") && "usage" in msg && msg.usage?.cost?.total) {
|
|
1462
|
-
totalCost += msg.usage.cost.total;
|
|
1463
|
-
}
|
|
1464
|
-
} else if ((entry.type === "branch_summary" || entry.type === "compaction" || entry.type === "usage") && "usage" in entry && entry.usage?.cost?.total) {
|
|
1465
|
-
totalCost += entry.usage.cost.total;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
return totalCost;
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
1790
|
// src/status-line/throughput.ts
|
|
1472
1791
|
function createThroughputTracker() {
|
|
1473
1792
|
let pending;
|
|
@@ -1546,7 +1865,8 @@ function calculateSessionTokens(source) {
|
|
|
1546
1865
|
// src/status-line/index.ts
|
|
1547
1866
|
function registerStatusLine(pi, ctx, config) {
|
|
1548
1867
|
const subscriptionProviders = config.statusLine.subscriptionProviders ?? [
|
|
1549
|
-
"claude-bridge"
|
|
1868
|
+
"claude-bridge",
|
|
1869
|
+
"antigravity"
|
|
1550
1870
|
];
|
|
1551
1871
|
const inputBoxEnabled = config.inputBox?.enabled !== false;
|
|
1552
1872
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
@@ -1589,12 +1909,13 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1589
1909
|
};
|
|
1590
1910
|
function readSession() {
|
|
1591
1911
|
const usage = ctx.getContextUsage?.();
|
|
1592
|
-
const provider = ctx.model?.provider;
|
|
1593
1912
|
const entries = ctx.sessionManager.getEntries();
|
|
1594
1913
|
return {
|
|
1595
|
-
|
|
1914
|
+
...computeSessionCost(entries, {
|
|
1915
|
+
subscriptionProviders,
|
|
1916
|
+
providerOf: () => ctx.model?.provider
|
|
1917
|
+
}),
|
|
1596
1918
|
tokenTotals: calculateSessionTokens(entries),
|
|
1597
|
-
isSubscription: provider !== void 0 && subscriptionProviders.includes(provider),
|
|
1598
1919
|
contextTokens: usage?.tokens ?? null,
|
|
1599
1920
|
contextPercent: usage?.percent ?? null,
|
|
1600
1921
|
contextWindow: usage?.contextWindow
|
|
@@ -1615,22 +1936,26 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1615
1936
|
sessionDirty = true;
|
|
1616
1937
|
invalidateRender();
|
|
1617
1938
|
};
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if (disposed || !data || typeof data !== "object") return;
|
|
1621
|
-
const { parentSessionId, totalCost } = data;
|
|
1622
|
-
if (parentSessionId !== ctx.sessionManager.getSessionId() || typeof totalCost !== "number" || !Number.isFinite(totalCost) || totalCost < 0)
|
|
1623
|
-
return;
|
|
1624
|
-
subagentCost = totalCost;
|
|
1625
|
-
requestRender();
|
|
1626
|
-
})
|
|
1627
|
-
);
|
|
1939
|
+
let offSubagentUsage;
|
|
1940
|
+
unsubs.push(() => offSubagentUsage?.());
|
|
1628
1941
|
if (footerData?.onBranchChange) {
|
|
1629
1942
|
unsubs.push(footerData.onBranchChange(invalidateRender));
|
|
1630
1943
|
}
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1944
|
+
const subscribeAndRequestSubagentUsage = () => {
|
|
1945
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
1946
|
+
offSubagentUsage?.();
|
|
1947
|
+
offSubagentUsage = subscribe(pi.events, SUBAGENTS_USAGE_CHANNEL, {
|
|
1948
|
+
sessionId,
|
|
1949
|
+
onSnapshot: ({ data }) => {
|
|
1950
|
+
if (disposed) return;
|
|
1951
|
+
subagentCost = data.totalCost;
|
|
1952
|
+
requestRender();
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
request(pi.events, SUBAGENTS_USAGE_REQUEST, {
|
|
1956
|
+
source: "@thoth-agents/pi-thoth-theme",
|
|
1957
|
+
sessionId,
|
|
1958
|
+
data: {}
|
|
1634
1959
|
});
|
|
1635
1960
|
};
|
|
1636
1961
|
if (pi?.on) {
|
|
@@ -1641,7 +1966,7 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1641
1966
|
throughput.reset();
|
|
1642
1967
|
if (inputBoxEnabled) working.end();
|
|
1643
1968
|
refreshSession();
|
|
1644
|
-
|
|
1969
|
+
subscribeAndRequestSubagentUsage();
|
|
1645
1970
|
})
|
|
1646
1971
|
);
|
|
1647
1972
|
if (inputBoxEnabled) {
|
|
@@ -1666,7 +1991,7 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1666
1991
|
unsubs.push(pi.on("model_select", refreshSession));
|
|
1667
1992
|
unsubs.push(pi.on("thinking_level_select", invalidateRender));
|
|
1668
1993
|
}
|
|
1669
|
-
|
|
1994
|
+
subscribeAndRequestSubagentUsage();
|
|
1670
1995
|
return {
|
|
1671
1996
|
getStatusSnapshot,
|
|
1672
1997
|
render(width) {
|
|
@@ -1973,6 +2298,148 @@ function createCustomPowerShellTool(_cwd, config) {
|
|
|
1973
2298
|
return createCustomShellTool(POWERSHELL_CONFIG, config);
|
|
1974
2299
|
}
|
|
1975
2300
|
|
|
2301
|
+
// src/tools/codemode.ts
|
|
2302
|
+
var COLLAPSED_CALLS = 8;
|
|
2303
|
+
var RESULT_PRESENT = "codemodeResultPresent";
|
|
2304
|
+
var RESULT_BORDER = "codemodeResultBorder";
|
|
2305
|
+
var CALL_COMPONENT = "codemodeCallComponent";
|
|
2306
|
+
var CALL_STATUS = {
|
|
2307
|
+
running: "running",
|
|
2308
|
+
ok: "completed",
|
|
2309
|
+
error: "failed",
|
|
2310
|
+
cancelled: "cancelled"
|
|
2311
|
+
};
|
|
2312
|
+
function displayLines(text) {
|
|
2313
|
+
return text.split("\n").map(escapeControlCharacters);
|
|
2314
|
+
}
|
|
2315
|
+
function scriptText(args) {
|
|
2316
|
+
return args && typeof args === "object" && "code" in args && typeof args.code === "string" ? args.code : "(script unavailable)";
|
|
2317
|
+
}
|
|
2318
|
+
function displayCalls(details) {
|
|
2319
|
+
if (!details || typeof details !== "object" || !("calls" in details) || !Array.isArray(details.calls))
|
|
2320
|
+
return [];
|
|
2321
|
+
return details.calls.flatMap((call) => {
|
|
2322
|
+
if (!call || typeof call !== "object" || !("name" in call) || typeof call.name !== "string" || !("status" in call) || call.status !== "running" && call.status !== "ok" && call.status !== "error" && call.status !== "cancelled")
|
|
2323
|
+
return [];
|
|
2324
|
+
return [
|
|
2325
|
+
{
|
|
2326
|
+
name: call.name,
|
|
2327
|
+
status: call.status,
|
|
2328
|
+
durationMs: "durationMs" in call && typeof call.durationMs === "number" && Number.isFinite(call.durationMs) && call.durationMs >= 0 ? call.durationMs : void 0,
|
|
2329
|
+
error: "error" in call && typeof call.error === "string" ? call.error : void 0
|
|
2330
|
+
}
|
|
2331
|
+
];
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
function createCodemodeRenderer(config) {
|
|
2335
|
+
const fallbackKit = createRenderKit({}, void 0, config.icons);
|
|
2336
|
+
return {
|
|
2337
|
+
renderShell: "self",
|
|
2338
|
+
renderCall(_args, theme, context) {
|
|
2339
|
+
if (context?.state) {
|
|
2340
|
+
context.state[RESULT_PRESENT] = false;
|
|
2341
|
+
context.state[RESULT_BORDER] = void 0;
|
|
2342
|
+
}
|
|
2343
|
+
const memo = createKitRenderMemo();
|
|
2344
|
+
const component = {
|
|
2345
|
+
invalidate: () => memo.invalidate(),
|
|
2346
|
+
render(width) {
|
|
2347
|
+
return memo.render(width, (registeredKit) => {
|
|
2348
|
+
const kit = registeredKit ?? fallbackKit;
|
|
2349
|
+
const hasResult = context?.state?.[RESULT_PRESENT] === true;
|
|
2350
|
+
const status = context?.executionStarted ? "running" : "pending";
|
|
2351
|
+
return kit.card(
|
|
2352
|
+
theme,
|
|
2353
|
+
{
|
|
2354
|
+
title: theme.fg("toolTitle", "codemode"),
|
|
2355
|
+
footer: hasResult ? "" : renderToolFooter(kit, theme, { status, context }),
|
|
2356
|
+
part: hasResult ? "start" : "full",
|
|
2357
|
+
...context?.state?.[RESULT_BORDER]
|
|
2358
|
+
},
|
|
2359
|
+
width
|
|
2360
|
+
);
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
if (context?.state) context.state[CALL_COMPONENT] = component;
|
|
2365
|
+
return component;
|
|
2366
|
+
},
|
|
2367
|
+
renderResult(result, options, theme, context) {
|
|
2368
|
+
const status = options.isPartial ? "running" : context?.isError ? "failed" : "completed";
|
|
2369
|
+
const border = {
|
|
2370
|
+
isError: status === "failed",
|
|
2371
|
+
isSuccess: status === "completed" && context?.executionStarted !== false
|
|
2372
|
+
};
|
|
2373
|
+
if (context?.state) {
|
|
2374
|
+
context.state[RESULT_PRESENT] = true;
|
|
2375
|
+
context.state[RESULT_BORDER] = border;
|
|
2376
|
+
context.state[CALL_COMPONENT]?.invalidate?.();
|
|
2377
|
+
}
|
|
2378
|
+
const calls = displayCalls(result.details);
|
|
2379
|
+
const visibleCalls = options.expanded ? calls : calls.slice(0, COLLAPSED_CALLS);
|
|
2380
|
+
const output = displayLines(getResultText(result) || "(no output)");
|
|
2381
|
+
const errors = calls.flatMap(
|
|
2382
|
+
(call) => call.error ? displayLines(`${call.name}: ${call.error}`).map(
|
|
2383
|
+
(line) => theme.fg("error", line)
|
|
2384
|
+
) : []
|
|
2385
|
+
);
|
|
2386
|
+
const memo = createKitRenderMemo();
|
|
2387
|
+
return {
|
|
2388
|
+
invalidate: () => memo.invalidate(),
|
|
2389
|
+
render(width) {
|
|
2390
|
+
return memo.render(width, (registeredKit) => {
|
|
2391
|
+
const kit = registeredKit ?? fallbackKit;
|
|
2392
|
+
return kit.card(
|
|
2393
|
+
theme,
|
|
2394
|
+
{
|
|
2395
|
+
part: context?.state?.[CALL_COMPONENT] ? "end" : "full",
|
|
2396
|
+
title: theme.fg("toolTitle", "codemode"),
|
|
2397
|
+
body: (bodyWidth) => {
|
|
2398
|
+
if (calls.length === 0) {
|
|
2399
|
+
return options.expanded ? [] : kit.collapse(theme, output, options);
|
|
2400
|
+
}
|
|
2401
|
+
const rows = visibleCalls.map((call, i) => {
|
|
2402
|
+
const duration = call.durationMs === void 0 ? "" : ` \xB7 ${formatDuration(call.durationMs)}`;
|
|
2403
|
+
return kit.treeRow(
|
|
2404
|
+
theme,
|
|
2405
|
+
{
|
|
2406
|
+
text: `${kit.statusGlyph(theme, CALL_STATUS[call.status])} ${escapeControlCharacters(call.name)}${duration}`,
|
|
2407
|
+
last: i === visibleCalls.length - 1
|
|
2408
|
+
},
|
|
2409
|
+
bodyWidth
|
|
2410
|
+
);
|
|
2411
|
+
});
|
|
2412
|
+
if (!options.expanded && calls.length > COLLAPSED_CALLS) {
|
|
2413
|
+
rows.push(
|
|
2414
|
+
theme.fg(
|
|
2415
|
+
"dim",
|
|
2416
|
+
`+${calls.length - COLLAPSED_CALLS} more \xB7 ctrl+o to expand`
|
|
2417
|
+
)
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
return rows;
|
|
2421
|
+
},
|
|
2422
|
+
sections: options.expanded ? [
|
|
2423
|
+
{
|
|
2424
|
+
title: "Script",
|
|
2425
|
+
rows: displayLines(scriptText(context?.args))
|
|
2426
|
+
},
|
|
2427
|
+
{ title: "Output", rows: output },
|
|
2428
|
+
...errors.length ? [{ title: "Errors", rows: errors }] : []
|
|
2429
|
+
] : void 0,
|
|
2430
|
+
wrap: options.expanded,
|
|
2431
|
+
footer: renderToolFooter(kit, theme, { status, context }),
|
|
2432
|
+
...border
|
|
2433
|
+
},
|
|
2434
|
+
width
|
|
2435
|
+
);
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
|
|
1976
2443
|
// src/tools/edit.ts
|
|
1977
2444
|
var COLLAPSED_DIFF_LINES = 6;
|
|
1978
2445
|
function parseDiffStats(diffText) {
|
|
@@ -3064,6 +3531,7 @@ function createCustomWriteTool(cwd, config) {
|
|
|
3064
3531
|
// src/tools/index.ts
|
|
3065
3532
|
function createToolRendererResolver(pi, config, cwd = process.cwd()) {
|
|
3066
3533
|
const renderers = /* @__PURE__ */ new Map([
|
|
3534
|
+
["codemode", createCodemodeRenderer(config)],
|
|
3067
3535
|
["read", createCustomReadTool(cwd, config)],
|
|
3068
3536
|
["bash", createCustomBashTool(cwd, config)],
|
|
3069
3537
|
["powershell", createCustomPowerShellTool(cwd, config)],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoth-agents/pi-thoth-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Modular Thoth theme, status line, tool styling, and welcome header for Pi.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@earendil-works/pi-tui": "1.0.2",
|
|
51
51
|
"@earendil-works/pi-ai": "1.0.2",
|
|
52
52
|
"@rolldown/binding-wasm32-wasi": "^1.2.4",
|
|
53
|
-
"@thoth-agents/pi-core": "^0.
|
|
53
|
+
"@thoth-agents/pi-core": "^0.3.0"
|
|
54
54
|
},
|
|
55
55
|
"upstream": {
|
|
56
56
|
"repository": "https://github.com/QuangThai/pi-omp-theme",
|