@thoth-agents/pi-thoth-theme 0.2.0 → 0.3.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 +40 -5
- package/dist/index.ts +383 -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
|
|
|
@@ -41,11 +52,31 @@ of the module toggles below.
|
|
|
41
52
|
tool providers, and recent sessions. Resource and session details load
|
|
42
53
|
best-effort through public APIs.
|
|
43
54
|
|
|
55
|
+
## Subagent usage channel
|
|
56
|
+
|
|
57
|
+
The status line subscribes to pi-core's v1 `thoth:subagents:usage` channel and
|
|
58
|
+
requests a snapshot on `thoth:subagents:usage:request` at session start, after
|
|
59
|
+
subscribing, using `data: {}`. The producer is `@thoth-agents/pi-subagents`;
|
|
60
|
+
envelopes are `{ v, source, sessionId, at, data }`, with the parent session ID
|
|
61
|
+
in the envelope, Unix-millisecond `at`, and only cumulative `totalCost` and
|
|
62
|
+
`runCount` in data. Pre-readiness requests are answered once after usage restore.
|
|
63
|
+
Invalid, unsupported-version and foreign-session envelopes are ignored; no task
|
|
64
|
+
prompts, results, transcripts or logs are consumed.
|
|
65
|
+
|
|
66
|
+
The raw `thoth:subagent-usage` bus event/request is removed with no fallback or
|
|
67
|
+
dual publication. Upgrade pi-subagents and pi-thoth-theme to `>=0.3.0` together
|
|
68
|
+
and `/reload`; mixed old/new versions lose subagent cost display. The persisted
|
|
69
|
+
usage checkpoint custom-entry type remains `thoth:subagent-usage`, so checkpoint
|
|
70
|
+
restore and replay-duplicate suppression are unchanged. See
|
|
71
|
+
[pi-core's exact contract](../pi-core/README.md#task-summary-and-usage-channels).
|
|
72
|
+
|
|
44
73
|
## Render KIT
|
|
45
74
|
|
|
46
75
|
Provides the Render KIT v1 contract defined by `@thoth-agents/pi-core` for
|
|
47
76
|
first-party tools, custom messages and above-editor widgets. The kit shares this
|
|
48
77
|
package's frames, theme roles, collapse hints and working/elapsed indicators.
|
|
78
|
+
The Render KIT v1 semantic icon table includes `elapsed` for work-panel metrics,
|
|
79
|
+
with nerd-clock, Unicode and ASCII alternatives.
|
|
49
80
|
Producers discover it at render time, so extension load order does not matter;
|
|
50
81
|
they do not depend on this package and keep native Pi rendering when it is absent.
|
|
51
82
|
|
|
@@ -65,7 +96,7 @@ Edit `~/.pi/agent/pi-thoth-theme.json` (or
|
|
|
65
96
|
"icons": "nerd",
|
|
66
97
|
"statusLine": {
|
|
67
98
|
"enabled": true,
|
|
68
|
-
"subscriptionProviders": ["claude-bridge"]
|
|
99
|
+
"subscriptionProviders": ["claude-bridge", "antigravity"]
|
|
69
100
|
},
|
|
70
101
|
"inputBox": { "enabled": true },
|
|
71
102
|
"tools": {
|
|
@@ -82,14 +113,18 @@ or `"ascii"` (plain-text alternatives).
|
|
|
82
113
|
Missing or malformed icon values use `"nerd"`. Each module's `enabled` key
|
|
83
114
|
accepts a boolean and defaults to `true`. Set it to `false` to leave Pi's native
|
|
84
115
|
behavior on that surface. `statusLine.subscriptionProviders` accepts an array of
|
|
85
|
-
provider identifier strings (defaults to `["claude-bridge"]`) to mark
|
|
116
|
+
provider identifier strings (defaults to `["claude-bridge", "antigravity"]`) to mark
|
|
86
117
|
subscription-backed usage with `(sub)` in the status line cost segment.
|
|
118
|
+
Classification uses the current provider for the displayed total, not a
|
|
119
|
+
per-message billing split; explicit user lists are preserved. Antigravity costs
|
|
120
|
+
are API-equivalent catalog estimates, not subscription charges.
|
|
87
121
|
|
|
88
122
|
`inputBox.enabled` defaults to `true` and requires `statusLine.enabled` to be
|
|
89
123
|
`true`. It frames the native editor in a rounded `muted` (sand) box with side borders,
|
|
90
124
|
a dim `type or / for commands` placeholder, and top-left ready or native working
|
|
91
125
|
status (including elapsed seconds). Git branch and working directory appear on
|
|
92
|
-
the top border
|
|
126
|
+
the top border, using pi-core's root-exported `formatCwd` for home abbreviation
|
|
127
|
+
(shared with the sidebar); model, thinking effort and context usage appear on the bottom.
|
|
93
128
|
Native scroll indicators are preserved. The status line always stays in its
|
|
94
129
|
separate footer row below the box. Narrow widths use the native editor geometry.
|
|
95
130
|
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,6 @@ function getToolElapsedMs(options) {
|
|
|
76
54
|
state.completedElapsedMs = elapsedMs;
|
|
77
55
|
return elapsedMs;
|
|
78
56
|
}
|
|
79
|
-
var RENDER_KIT_VERSION = 1;
|
|
80
|
-
var registryKey = /* @__PURE__ */ Symbol.for(
|
|
81
|
-
`thoth-agents.pi-core.render-kit.v${RENDER_KIT_VERSION}`
|
|
82
|
-
);
|
|
83
|
-
var shared = globalThis;
|
|
84
57
|
function registration() {
|
|
85
58
|
try {
|
|
86
59
|
const record = shared[registryKey];
|
|
@@ -98,6 +71,313 @@ function registerRenderKit(kit, owner) {
|
|
|
98
71
|
function withdrawRenderKit(token) {
|
|
99
72
|
if (registration()?.token === token) delete shared[registryKey];
|
|
100
73
|
}
|
|
74
|
+
var nativeFrames, terminalFooterGlyphs, RENDER_KIT_VERSION, registryKey, shared;
|
|
75
|
+
var init_render_kit = __esm({
|
|
76
|
+
"../pi-core/src/render-kit.ts"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
init_duration();
|
|
79
|
+
nativeFrames = {
|
|
80
|
+
spinnerFrames: Object.freeze([
|
|
81
|
+
"\u280B",
|
|
82
|
+
"\u2819",
|
|
83
|
+
"\u2839",
|
|
84
|
+
"\u2838",
|
|
85
|
+
"\u283C",
|
|
86
|
+
"\u2834",
|
|
87
|
+
"\u2826",
|
|
88
|
+
"\u2827",
|
|
89
|
+
"\u2807",
|
|
90
|
+
"\u280F"
|
|
91
|
+
]),
|
|
92
|
+
workingFrames: Object.freeze(["\u25B3", "\u25ED", "\u25B2", "\u25EE"])
|
|
93
|
+
};
|
|
94
|
+
terminalFooterGlyphs = {
|
|
95
|
+
completed: "\u2713",
|
|
96
|
+
deleted: "\u2713",
|
|
97
|
+
failed: "\u2717",
|
|
98
|
+
cancelled: "\u2717",
|
|
99
|
+
interrupted: "\u2717",
|
|
100
|
+
blocked: "\u2717"
|
|
101
|
+
};
|
|
102
|
+
RENDER_KIT_VERSION = 1;
|
|
103
|
+
registryKey = /* @__PURE__ */ Symbol.for(
|
|
104
|
+
`thoth-agents.pi-core.render-kit.v${RENDER_KIT_VERSION}`
|
|
105
|
+
);
|
|
106
|
+
shared = globalThis;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ../pi-core/src/panel-frame.ts
|
|
111
|
+
var init_panel_frame = __esm({
|
|
112
|
+
"../pi-core/src/panel-frame.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
init_render_kit();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ../pi-core/src/validation.ts
|
|
119
|
+
function isRecord(value) {
|
|
120
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
+
}
|
|
122
|
+
function isNonEmptyString(value) {
|
|
123
|
+
return typeof value === "string" && value.length > 0;
|
|
124
|
+
}
|
|
125
|
+
function isNonNegativeInteger(value) {
|
|
126
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ../pi-core/src/channels.ts
|
|
130
|
+
function defineChannel(definition) {
|
|
131
|
+
return definition;
|
|
132
|
+
}
|
|
133
|
+
function publish(events, channel, snapshot) {
|
|
134
|
+
events.emit(channel.name, {
|
|
135
|
+
v: channel.version,
|
|
136
|
+
source: snapshot.source,
|
|
137
|
+
sessionId: snapshot.sessionId,
|
|
138
|
+
at: Date.now(),
|
|
139
|
+
data: snapshot.data
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function request(events, requestChannel, payload) {
|
|
143
|
+
publish(events, requestChannel, payload);
|
|
144
|
+
}
|
|
145
|
+
function subscribe(events, channel, options) {
|
|
146
|
+
return events.on(channel.name, (value) => {
|
|
147
|
+
if (!isEnvelope(value, channel, options.sessionId)) return;
|
|
148
|
+
options.onSnapshot(value);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function isEnvelope(value, channel, sessionId) {
|
|
152
|
+
try {
|
|
153
|
+
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);
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ../pi-core/src/background.ts
|
|
160
|
+
var backgroundStatuses = [
|
|
161
|
+
"running",
|
|
162
|
+
"succeeded",
|
|
163
|
+
"failed",
|
|
164
|
+
"cancelled",
|
|
165
|
+
"timed_out"
|
|
166
|
+
];
|
|
167
|
+
var BACKGROUND_PROGRESS_MAX_LENGTH = 200;
|
|
168
|
+
function isBackgroundSnapshot(value) {
|
|
169
|
+
try {
|
|
170
|
+
if (!isExactRecord(value, ["tasks", "counts"]) || !Array.isArray(value.tasks))
|
|
171
|
+
return false;
|
|
172
|
+
for (const task of value.tasks) {
|
|
173
|
+
if (!isBackgroundTask(task)) return false;
|
|
174
|
+
}
|
|
175
|
+
const { counts } = value;
|
|
176
|
+
return isExactRecord(counts, backgroundStatuses) && backgroundStatuses.every((status) => isNonNegativeInteger(counts[status]));
|
|
177
|
+
} catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function isExactRecord(value, allowedKeys) {
|
|
182
|
+
if (!isRecord(value)) return false;
|
|
183
|
+
const prototype = Object.getPrototypeOf(value);
|
|
184
|
+
return (prototype === Object.prototype || prototype === null) && Reflect.ownKeys(value).every(
|
|
185
|
+
(key) => typeof key === "string" && allowedKeys.includes(key)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function isOptionalTimestamp(value) {
|
|
189
|
+
return value === void 0 || isNonNegativeInteger(value);
|
|
190
|
+
}
|
|
191
|
+
function isProgress(value) {
|
|
192
|
+
return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.length <= BACKGROUND_PROGRESS_MAX_LENGTH;
|
|
193
|
+
}
|
|
194
|
+
function isBackgroundTask(value) {
|
|
195
|
+
return isExactRecord(value, [
|
|
196
|
+
"id",
|
|
197
|
+
"name",
|
|
198
|
+
"kind",
|
|
199
|
+
"status",
|
|
200
|
+
"createdAt",
|
|
201
|
+
"startedAt",
|
|
202
|
+
"endedAt",
|
|
203
|
+
"deadlineAt",
|
|
204
|
+
"lastCheckedAt",
|
|
205
|
+
"lastProgressAt",
|
|
206
|
+
"stopRequestedAt",
|
|
207
|
+
"dismissedAt",
|
|
208
|
+
"exitCode",
|
|
209
|
+
"signal",
|
|
210
|
+
"progress",
|
|
211
|
+
"dismissed"
|
|
212
|
+
]) && 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");
|
|
213
|
+
}
|
|
214
|
+
var BACKGROUND_STATE_CHANNEL = defineChannel({
|
|
215
|
+
name: "thoth:background:state",
|
|
216
|
+
version: 1,
|
|
217
|
+
validate: isBackgroundSnapshot
|
|
218
|
+
});
|
|
219
|
+
function isBackgroundStateRequest(value) {
|
|
220
|
+
try {
|
|
221
|
+
return isExactRecord(value, []);
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
var BACKGROUND_STATE_REQUEST = defineChannel({
|
|
227
|
+
name: "thoth:background:state:request",
|
|
228
|
+
version: 1,
|
|
229
|
+
validate: isBackgroundStateRequest
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// ../pi-core/src/cwd.ts
|
|
233
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
234
|
+
function formatCwd(cwd, home) {
|
|
235
|
+
if (!home) return cwd;
|
|
236
|
+
const relativeToHome = relative(resolve(home), resolve(cwd));
|
|
237
|
+
if (relativeToHome === ".." || relativeToHome.startsWith(`..${sep}`) || isAbsolute(relativeToHome)) {
|
|
238
|
+
return cwd;
|
|
239
|
+
}
|
|
240
|
+
return relativeToHome === "" ? "~" : `~${sep}${relativeToHome}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ../pi-core/src/index.ts
|
|
244
|
+
init_duration();
|
|
245
|
+
init_render_kit();
|
|
246
|
+
|
|
247
|
+
// ../pi-core/src/session-cost.ts
|
|
248
|
+
function computeSessionCost(entries, { subscriptionProviders = [], providerOf } = {}) {
|
|
249
|
+
let cost = 0;
|
|
250
|
+
for (const entry of entries ?? []) {
|
|
251
|
+
if (!entry) continue;
|
|
252
|
+
if (entry.type === "message") {
|
|
253
|
+
const message = entry.message;
|
|
254
|
+
if (message?.role === "assistant" || message?.role === "toolResult")
|
|
255
|
+
cost += message.usage?.cost?.total || 0;
|
|
256
|
+
} else if (["branch_summary", "compaction", "usage"].includes(entry.type)) {
|
|
257
|
+
cost += entry.usage?.cost?.total || 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const provider = providerOf?.();
|
|
261
|
+
return {
|
|
262
|
+
cost,
|
|
263
|
+
isSubscription: provider !== void 0 && subscriptionProviders.includes(provider)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function combineSessionAndSubagentCost(sessionCost, latestSubagentCost = 0) {
|
|
267
|
+
return sessionCost + latestSubagentCost;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ../pi-core/src/subagents.ts
|
|
271
|
+
var subagentStatuses = [
|
|
272
|
+
"queued",
|
|
273
|
+
"running",
|
|
274
|
+
"stopping",
|
|
275
|
+
"completed",
|
|
276
|
+
"failed",
|
|
277
|
+
"cancelled",
|
|
278
|
+
"interrupted"
|
|
279
|
+
];
|
|
280
|
+
var subagentEfforts = [
|
|
281
|
+
"off",
|
|
282
|
+
"minimal",
|
|
283
|
+
"low",
|
|
284
|
+
"medium",
|
|
285
|
+
"high",
|
|
286
|
+
"xhigh",
|
|
287
|
+
"max"
|
|
288
|
+
];
|
|
289
|
+
var SUBAGENT_PREVIEW_MAX_LENGTH = 800;
|
|
290
|
+
function isSubagentsSnapshot(value) {
|
|
291
|
+
try {
|
|
292
|
+
if (!isExactRecord2(value, ["tasks", "history", "counts", "totals"]) || !Array.isArray(value.tasks) || !Array.isArray(value.history) || value.history.length > 100)
|
|
293
|
+
return false;
|
|
294
|
+
for (const tasks of [value.tasks, value.history]) {
|
|
295
|
+
for (const task of tasks) {
|
|
296
|
+
if (!isSubagentTask(task)) return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const { counts, totals } = value;
|
|
300
|
+
return isCounts(counts) && isExactRecord2(totals, [...subagentStatuses, "total"]) && isNonNegativeInteger(totals.total) && subagentStatuses.every((status) => isNonNegativeInteger(totals[status]));
|
|
301
|
+
} catch {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function isExactRecord2(value, allowedKeys) {
|
|
306
|
+
if (!isRecord(value)) return false;
|
|
307
|
+
const prototype = Object.getPrototypeOf(value);
|
|
308
|
+
return (prototype === Object.prototype || prototype === null) && Reflect.ownKeys(value).every(
|
|
309
|
+
(key) => typeof key === "string" && allowedKeys.includes(key)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
function isCounts(value) {
|
|
313
|
+
return isExactRecord2(value, subagentStatuses) && subagentStatuses.every((status) => isNonNegativeInteger(value[status]));
|
|
314
|
+
}
|
|
315
|
+
function isOptionalString(value) {
|
|
316
|
+
return value === void 0 || typeof value === "string";
|
|
317
|
+
}
|
|
318
|
+
function isOptionalTimestamp2(value) {
|
|
319
|
+
return value === void 0 || isNonNegativeInteger(value);
|
|
320
|
+
}
|
|
321
|
+
function isNonNegativeNumber(value) {
|
|
322
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
323
|
+
}
|
|
324
|
+
function isTaskUsage(value) {
|
|
325
|
+
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));
|
|
326
|
+
}
|
|
327
|
+
function isSubagentTask(value) {
|
|
328
|
+
return isExactRecord2(value, [
|
|
329
|
+
"id",
|
|
330
|
+
"agent",
|
|
331
|
+
"displayName",
|
|
332
|
+
"mode",
|
|
333
|
+
"status",
|
|
334
|
+
"model",
|
|
335
|
+
"effort",
|
|
336
|
+
"createdAt",
|
|
337
|
+
"startedAt",
|
|
338
|
+
"endedAt",
|
|
339
|
+
"lastActivityAt",
|
|
340
|
+
"usage",
|
|
341
|
+
"preview"
|
|
342
|
+
]) && 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);
|
|
343
|
+
}
|
|
344
|
+
var SUBAGENTS_STATE_CHANNEL = defineChannel({
|
|
345
|
+
name: "thoth:subagents:state",
|
|
346
|
+
version: 2,
|
|
347
|
+
validate: isSubagentsSnapshot
|
|
348
|
+
});
|
|
349
|
+
function isSubagentsStateRequest(value) {
|
|
350
|
+
try {
|
|
351
|
+
return isExactRecord2(value, []);
|
|
352
|
+
} catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function isSubagentsUsageRequest(value) {
|
|
357
|
+
return isSubagentsStateRequest(value);
|
|
358
|
+
}
|
|
359
|
+
var SUBAGENTS_STATE_REQUEST = defineChannel({
|
|
360
|
+
name: "thoth:subagents:state:request",
|
|
361
|
+
version: 1,
|
|
362
|
+
validate: isSubagentsStateRequest
|
|
363
|
+
});
|
|
364
|
+
function isSubagentsUsageSnapshot(value) {
|
|
365
|
+
try {
|
|
366
|
+
return isExactRecord2(value, ["totalCost", "runCount"]) && isNonNegativeNumber(value.totalCost) && isNonNegativeInteger(value.runCount);
|
|
367
|
+
} catch {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
var SUBAGENTS_USAGE_CHANNEL = defineChannel({
|
|
372
|
+
name: "thoth:subagents:usage",
|
|
373
|
+
version: 1,
|
|
374
|
+
validate: isSubagentsUsageSnapshot
|
|
375
|
+
});
|
|
376
|
+
var SUBAGENTS_USAGE_REQUEST = defineChannel({
|
|
377
|
+
name: "thoth:subagents:usage:request",
|
|
378
|
+
version: 1,
|
|
379
|
+
validate: isSubagentsUsageRequest
|
|
380
|
+
});
|
|
101
381
|
|
|
102
382
|
// ../pi-core/src/todo.ts
|
|
103
383
|
var todoStatuses = [
|
|
@@ -123,11 +403,11 @@ function isTodoSnapshot(value) {
|
|
|
123
403
|
function isTaskId(value) {
|
|
124
404
|
return isNonNegativeInteger(value) && value > 0;
|
|
125
405
|
}
|
|
126
|
-
function
|
|
406
|
+
function isOptionalString2(value) {
|
|
127
407
|
return value === void 0 || typeof value === "string";
|
|
128
408
|
}
|
|
129
409
|
function isTodoTask(value) {
|
|
130
|
-
if (!isRecord(value) || !isTaskId(value.id) || typeof value.subject !== "string" || !todoStatuses.some((status) => status === value.status) || !Array.isArray(value.blockedBy) || !
|
|
410
|
+
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
411
|
return false;
|
|
132
412
|
for (const id of value.blockedBy) {
|
|
133
413
|
if (!isTaskId(id)) return false;
|
|
@@ -160,6 +440,16 @@ var registryKey2 = /* @__PURE__ */ Symbol.for(
|
|
|
160
440
|
`thoth-agents.pi-core.tool-definitions.v${TOOL_DEFINITION_REGISTRY_VERSION}`
|
|
161
441
|
);
|
|
162
442
|
|
|
443
|
+
// ../pi-core/src/work-panel-host.ts
|
|
444
|
+
init_render_kit();
|
|
445
|
+
|
|
446
|
+
// ../pi-core/src/work-panel-detail.ts
|
|
447
|
+
init_panel_frame();
|
|
448
|
+
init_render_kit();
|
|
449
|
+
|
|
450
|
+
// ../pi-core/src/work-panel-render.ts
|
|
451
|
+
init_render_kit();
|
|
452
|
+
|
|
163
453
|
// src/render-kit/index.ts
|
|
164
454
|
import {
|
|
165
455
|
stripTerminalSequences,
|
|
@@ -192,6 +482,7 @@ var ICONS = {
|
|
|
192
482
|
model: { nerd: "\u{F06A9}", unicode: "\u25CF", ascii: "*" },
|
|
193
483
|
effort: { nerd: "\u{F09D1}", unicode: "\u25D0", ascii: "o" },
|
|
194
484
|
context: { nerd: "\uF2DB", unicode: "ctx", ascii: "ctx" },
|
|
485
|
+
elapsed: { nerd: "\uF017", unicode: "\u25F7", ascii: "elapsed" },
|
|
195
486
|
cost: { nerd: "\uF155", unicode: "$", ascii: "$" },
|
|
196
487
|
tokensIn: { nerd: "\uF062", unicode: "\u2191", ascii: "^" },
|
|
197
488
|
tokensOut: { nerd: "\uF063", unicode: "\u2193", ascii: "v" },
|
|
@@ -453,7 +744,7 @@ function createComponent(renderFn) {
|
|
|
453
744
|
}
|
|
454
745
|
|
|
455
746
|
// src/tools/frame.ts
|
|
456
|
-
import { relative, resolve } from "node:path";
|
|
747
|
+
import { relative as relative2, resolve as resolve2 } from "node:path";
|
|
457
748
|
import { truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
458
749
|
var FRAME_HORIZONTAL = "\u2500";
|
|
459
750
|
var FRAME_VERTICAL = "\u2502";
|
|
@@ -467,9 +758,9 @@ function formatDisplayPath(targetPath, cwd) {
|
|
|
467
758
|
if (!targetPath) return "";
|
|
468
759
|
if (!cwd) return targetPath;
|
|
469
760
|
try {
|
|
470
|
-
const absTarget =
|
|
471
|
-
const absCwd =
|
|
472
|
-
const rel =
|
|
761
|
+
const absTarget = resolve2(cwd, targetPath);
|
|
762
|
+
const absCwd = resolve2(cwd);
|
|
763
|
+
const rel = relative2(absCwd, absTarget);
|
|
473
764
|
if (!rel.startsWith("..") && !rel.startsWith("/") && !/^[A-Za-z]:/.test(rel)) {
|
|
474
765
|
return rel.replace(/\\/g, "/") || ".";
|
|
475
766
|
}
|
|
@@ -522,6 +813,14 @@ function renderFrameDivider(theme, title, width, borderColor = "accent") {
|
|
|
522
813
|
if (safeWidth <= 4) {
|
|
523
814
|
return [truncateToWidth2(title, safeWidth)];
|
|
524
815
|
}
|
|
816
|
+
if (!title) {
|
|
817
|
+
return [
|
|
818
|
+
theme.fg(
|
|
819
|
+
borderColor,
|
|
820
|
+
`${FRAME_DIVIDER_LEFT}${FRAME_HORIZONTAL.repeat(safeWidth - 2)}${FRAME_DIVIDER_RIGHT}`
|
|
821
|
+
)
|
|
822
|
+
];
|
|
823
|
+
}
|
|
525
824
|
const innerWidth = Math.max(1, safeWidth - 2);
|
|
526
825
|
const rawTitle = ` ${title} `;
|
|
527
826
|
const maxTitleWidth = Math.max(1, innerWidth - 4);
|
|
@@ -837,7 +1136,7 @@ function parseSubscriptionProviders(value) {
|
|
|
837
1136
|
(item) => typeof item === "string" && item.trim().length > 0
|
|
838
1137
|
);
|
|
839
1138
|
}
|
|
840
|
-
return ["claude-bridge"];
|
|
1139
|
+
return ["claude-bridge", "antigravity"];
|
|
841
1140
|
}
|
|
842
1141
|
function parseRespectPackages(value) {
|
|
843
1142
|
const packages = asObject(value).respectPackages;
|
|
@@ -876,19 +1175,10 @@ import {
|
|
|
876
1175
|
} from "@earendil-works/pi-tui";
|
|
877
1176
|
|
|
878
1177
|
// src/status-line/layout.ts
|
|
879
|
-
import { isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
880
1178
|
import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
881
1179
|
function themeFg(theme, token, text) {
|
|
882
1180
|
return theme?.fg ? theme.fg(token, text) : text;
|
|
883
1181
|
}
|
|
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
1182
|
function formatTokens(count) {
|
|
893
1183
|
if (count < 1e3) return count.toString();
|
|
894
1184
|
if (count < 1e6) {
|
|
@@ -947,14 +1237,18 @@ function contextBar(percent, isAscii, theme) {
|
|
|
947
1237
|
let token = "success";
|
|
948
1238
|
if (percent >= 90) token = "error";
|
|
949
1239
|
else if (percent >= 70) token = "warning";
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1240
|
+
const halves = Math.min(
|
|
1241
|
+
16,
|
|
1242
|
+
Math.max(percent > 0 ? 1 : 0, Math.round(percent / 100 * 16))
|
|
1243
|
+
);
|
|
1244
|
+
const full = Math.floor(halves / 2);
|
|
1245
|
+
const half = halves % 2;
|
|
953
1246
|
const dim = (text) => themeFg(theme, "dim", text);
|
|
1247
|
+
const filled = isAscii ? "#".repeat(full) + (half ? "=" : "") : "\u25C6".repeat(full) + (half ? "\u2B16" : "");
|
|
1248
|
+
const empty = (isAscii ? "-" : "\u25C7").repeat(8 - full - half);
|
|
1249
|
+
const bar = isAscii ? `${dim("[")}${themeFg(theme, token, filled)}${dim(empty)}${dim("]")}` : `${themeFg(theme, token, filled)}${dim(empty)}`;
|
|
954
1250
|
return {
|
|
955
|
-
bar
|
|
956
|
-
emptyChar.repeat(10 - filled)
|
|
957
|
-
)}${dim("]")}`,
|
|
1251
|
+
bar,
|
|
958
1252
|
pct: themeFg(theme, token, `${Math.round(percent)}%`)
|
|
959
1253
|
};
|
|
960
1254
|
}
|
|
@@ -993,7 +1287,7 @@ function renderStatusLine(data, options) {
|
|
|
993
1287
|
const sep2 = ` ${themeFg(theme, "border", icon("separator", mode))} `;
|
|
994
1288
|
const segments = [];
|
|
995
1289
|
if (data.cost !== void 0) {
|
|
996
|
-
const total = data.cost
|
|
1290
|
+
const total = combineSessionAndSubagentCost(data.cost, data.subagentCost);
|
|
997
1291
|
segments.push(
|
|
998
1292
|
themeFg(
|
|
999
1293
|
theme,
|
|
@@ -1449,25 +1743,6 @@ function createWorkingState(requestRender) {
|
|
|
1449
1743
|
};
|
|
1450
1744
|
}
|
|
1451
1745
|
|
|
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
1746
|
// src/status-line/throughput.ts
|
|
1472
1747
|
function createThroughputTracker() {
|
|
1473
1748
|
let pending;
|
|
@@ -1546,7 +1821,8 @@ function calculateSessionTokens(source) {
|
|
|
1546
1821
|
// src/status-line/index.ts
|
|
1547
1822
|
function registerStatusLine(pi, ctx, config) {
|
|
1548
1823
|
const subscriptionProviders = config.statusLine.subscriptionProviders ?? [
|
|
1549
|
-
"claude-bridge"
|
|
1824
|
+
"claude-bridge",
|
|
1825
|
+
"antigravity"
|
|
1550
1826
|
];
|
|
1551
1827
|
const inputBoxEnabled = config.inputBox?.enabled !== false;
|
|
1552
1828
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
@@ -1589,12 +1865,13 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1589
1865
|
};
|
|
1590
1866
|
function readSession() {
|
|
1591
1867
|
const usage = ctx.getContextUsage?.();
|
|
1592
|
-
const provider = ctx.model?.provider;
|
|
1593
1868
|
const entries = ctx.sessionManager.getEntries();
|
|
1594
1869
|
return {
|
|
1595
|
-
|
|
1870
|
+
...computeSessionCost(entries, {
|
|
1871
|
+
subscriptionProviders,
|
|
1872
|
+
providerOf: () => ctx.model?.provider
|
|
1873
|
+
}),
|
|
1596
1874
|
tokenTotals: calculateSessionTokens(entries),
|
|
1597
|
-
isSubscription: provider !== void 0 && subscriptionProviders.includes(provider),
|
|
1598
1875
|
contextTokens: usage?.tokens ?? null,
|
|
1599
1876
|
contextPercent: usage?.percent ?? null,
|
|
1600
1877
|
contextWindow: usage?.contextWindow
|
|
@@ -1615,22 +1892,26 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1615
1892
|
sessionDirty = true;
|
|
1616
1893
|
invalidateRender();
|
|
1617
1894
|
};
|
|
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
|
-
);
|
|
1895
|
+
let offSubagentUsage;
|
|
1896
|
+
unsubs.push(() => offSubagentUsage?.());
|
|
1628
1897
|
if (footerData?.onBranchChange) {
|
|
1629
1898
|
unsubs.push(footerData.onBranchChange(invalidateRender));
|
|
1630
1899
|
}
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1900
|
+
const subscribeAndRequestSubagentUsage = () => {
|
|
1901
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
1902
|
+
offSubagentUsage?.();
|
|
1903
|
+
offSubagentUsage = subscribe(pi.events, SUBAGENTS_USAGE_CHANNEL, {
|
|
1904
|
+
sessionId,
|
|
1905
|
+
onSnapshot: ({ data }) => {
|
|
1906
|
+
if (disposed) return;
|
|
1907
|
+
subagentCost = data.totalCost;
|
|
1908
|
+
requestRender();
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
request(pi.events, SUBAGENTS_USAGE_REQUEST, {
|
|
1912
|
+
source: "@thoth-agents/pi-thoth-theme",
|
|
1913
|
+
sessionId,
|
|
1914
|
+
data: {}
|
|
1634
1915
|
});
|
|
1635
1916
|
};
|
|
1636
1917
|
if (pi?.on) {
|
|
@@ -1641,7 +1922,7 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1641
1922
|
throughput.reset();
|
|
1642
1923
|
if (inputBoxEnabled) working.end();
|
|
1643
1924
|
refreshSession();
|
|
1644
|
-
|
|
1925
|
+
subscribeAndRequestSubagentUsage();
|
|
1645
1926
|
})
|
|
1646
1927
|
);
|
|
1647
1928
|
if (inputBoxEnabled) {
|
|
@@ -1666,7 +1947,7 @@ function registerStatusLine(pi, ctx, config) {
|
|
|
1666
1947
|
unsubs.push(pi.on("model_select", refreshSession));
|
|
1667
1948
|
unsubs.push(pi.on("thinking_level_select", invalidateRender));
|
|
1668
1949
|
}
|
|
1669
|
-
|
|
1950
|
+
subscribeAndRequestSubagentUsage();
|
|
1670
1951
|
return {
|
|
1671
1952
|
getStatusSnapshot,
|
|
1672
1953
|
render(width) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoth-agents/pi-thoth-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|