@gavin-hu/my-pi-agent 0.12.0 → 0.13.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 +28 -21
- package/README.zh-CN.md +30 -22
- package/extensions/bytefly/README.md +139 -67
- package/extensions/bytefly/chrome.ts +7 -2
- package/extensions/bytefly/doctor/register.ts +6 -0
- package/extensions/bytefly/doctor/runtime.ts +36 -6
- package/extensions/bytefly/footer/collect/snapshot.ts +12 -2
- package/extensions/bytefly/footer/config.ts +7 -1
- package/extensions/bytefly/footer/delegation.ts +100 -0
- package/extensions/bytefly/footer/register.ts +13 -2
- package/extensions/bytefly/footer/render/lines.ts +2 -0
- package/extensions/bytefly/footer/render/segments.ts +27 -0
- package/extensions/bytefly/footer/types.ts +11 -0
- package/extensions/bytefly/index.ts +4 -3
- package/extensions/bytefly/notify/config.ts +2 -0
- package/extensions/bytefly/notify/format.ts +19 -1
- package/extensions/bytefly/notify/register.ts +37 -13
- package/extensions/bytefly/notify/types.ts +2 -0
- package/extensions/bytefly/panel/commands.ts +34 -13
- package/extensions/bytefly/panel/settings.ts +98 -16
- package/extensions/bytefly/update/cache.ts +73 -0
- package/extensions/bytefly/update/format.ts +65 -10
- package/extensions/bytefly/update/register.ts +99 -13
- package/extensions/bytefly/update/state.ts +13 -1
- package/extensions/document/README.md +22 -12
- package/extensions/document/config.ts +4 -0
- package/extensions/document/deadline.ts +61 -0
- package/extensions/document/tool.ts +13 -2
- package/extensions/file-browse/README.md +12 -8
- package/extensions/file-browse/client.ts +6 -7
- package/extensions/file-browse/files.ts +0 -4
- package/extensions/file-browse/git.ts +9 -10
- package/extensions/file-browse/html.ts +17 -7
- package/extensions/file-browse/router.ts +24 -15
- package/extensions/goal/README.md +7 -1
- package/extensions/goal/schema.ts +6 -2
- package/extensions/goal/types.ts +6 -2
- package/extensions/job/README.md +16 -12
- package/extensions/job/runtime.ts +23 -6
- package/extensions/job/status.ts +4 -2
- package/extensions/memory/README.md +55 -13
- package/extensions/memory/commands.ts +20 -2
- package/extensions/memory/doctor.ts +137 -0
- package/extensions/memory/format.ts +24 -7
- package/extensions/memory/runtime.ts +49 -0
- package/extensions/memory/store.ts +31 -0
- package/extensions/memory/tui.ts +136 -12
- package/extensions/plan/README.md +60 -29
- package/extensions/plan/commands.ts +67 -8
- package/extensions/plan/index.ts +35 -11
- package/extensions/plan/plan-index.ts +234 -0
- package/extensions/plan/plan-text.ts +95 -0
- package/extensions/plan/plans.ts +90 -1
- package/extensions/plan/review.ts +12 -9
- package/extensions/plan/runtime.ts +72 -27
- package/extensions/plan/steps.ts +35 -25
- package/extensions/plan/todos.ts +24 -2
- package/extensions/plan/tools.ts +124 -24
- package/extensions/plan/tui.ts +7 -28
- package/extensions/plan/types.ts +9 -0
- package/extensions/plan/web-page.ts +2 -2
- package/extensions/plan/web.ts +12 -11
- package/extensions/rewind/README.md +28 -8
- package/extensions/rewind/commands.ts +1 -1
- package/extensions/rewind/format.ts +4 -4
- package/extensions/rewind/git.ts +56 -18
- package/extensions/rewind/index.ts +3 -5
- package/extensions/rewind/path-picker.ts +198 -0
- package/extensions/rewind/paths.ts +128 -0
- package/extensions/rewind/restore.ts +69 -28
- package/extensions/rewind/rewind.ts +80 -3
- package/extensions/rewind/runtime.ts +13 -5
- package/extensions/rewind/snapshot-run.ts +5 -2
- package/extensions/rewind/types.ts +2 -0
- package/extensions/subagent/README.md +7 -1
- package/extensions/subagent/accounting.ts +34 -0
- package/extensions/subagent/index.ts +23 -1
- package/extensions/subagent/stream.ts +2 -0
- package/extensions/subagent/types.ts +2 -0
- package/extensions/watch/README.md +8 -3
- package/extensions/watch/index.ts +19 -0
- package/extensions/web-access/README.md +43 -13
- package/extensions/web-access/charset.ts +58 -0
- package/extensions/web-access/fetch/find.ts +11 -7
- package/extensions/web-access/fetch/format.ts +64 -18
- package/extensions/web-access/fetch/page.ts +5 -1
- package/extensions/web-access/fetch/tool.ts +19 -18
- package/extensions/web-access/http.ts +49 -8
- package/extensions/web-access/search/facts.ts +38 -0
- package/extensions/web-access/search/tool.ts +13 -0
- package/extensions/web-access/text.ts +50 -11
- package/extensions/worktree/README.md +74 -15
- package/extensions/worktree/config.ts +11 -0
- package/extensions/worktree/exit.ts +35 -3
- package/extensions/worktree/gaps.ts +74 -0
- package/extensions/worktree/guard.ts +50 -4
- package/extensions/worktree/handoff.ts +55 -0
- package/extensions/worktree/index.ts +12 -2
- package/extensions/worktree/runtime.ts +20 -0
- package/extensions/worktree/status.ts +15 -2
- package/extensions/worktree/tools.ts +3 -2
- package/lib/README.md +10 -8
- package/lib/events.ts +86 -1
- package/lib/goal-state.ts +95 -0
- package/lib/requirements.ts +282 -62
- package/lib/tui/README.md +91 -0
- package/lib/tui/glyphs.ts +1 -0
- package/lib/web/layout.ts +12 -2
- package/package.json +5 -3
- package/themes/bytefly-dark.json +1 -1
- package/assets/chrome.png +0 -0
- package/assets/file-browse.png +0 -0
- package/assets/job.png +0 -0
- package/assets/plan-review.png +0 -0
- package/assets/question.png +0 -0
- package/assets/rewind.png +0 -0
- package/assets/todo.png +0 -0
- package/assets/tool-renderings.png +0 -0
- package/assets/tools/ask_user_question-cancelled.png +0 -0
- package/assets/tools/ask_user_question.png +0 -0
- package/assets/tools/bash.png +0 -0
- package/assets/tools/edit.png +0 -0
- package/assets/tools/enter_plan_mode.png +0 -0
- package/assets/tools/enter_worktree.png +0 -0
- package/assets/tools/exit_plan_mode.png +0 -0
- package/assets/tools/exit_worktree.png +0 -0
- package/assets/tools/find.png +0 -0
- package/assets/tools/goal.png +0 -0
- package/assets/tools/grep.png +0 -0
- package/assets/tools/job.png +0 -0
- package/assets/tools/list_worktrees.png +0 -0
- package/assets/tools/ls.png +0 -0
- package/assets/tools/memory-long-subject.png +0 -0
- package/assets/tools/memory.png +0 -0
- package/assets/tools/notice.png +0 -0
- package/assets/tools/open_path.png +0 -0
- package/assets/tools/prune_worktrees.png +0 -0
- package/assets/tools/read.png +0 -0
- package/assets/tools/read_doc-running.png +0 -0
- package/assets/tools/read_doc.png +0 -0
- package/assets/tools/run_doctor.png +0 -0
- package/assets/tools/send_wechat.png +0 -0
- package/assets/tools/set_plan_steps.png +0 -0
- package/assets/tools/subagent.png +0 -0
- package/assets/tools/todo.png +0 -0
- package/assets/tools/turn-rule.png +0 -0
- package/assets/tools/user-message.png +0 -0
- package/assets/tools/watch.png +0 -0
- package/assets/tools/web_fetch-rejected.png +0 -0
- package/assets/tools/web_fetch.png +0 -0
- package/assets/tools/web_search.png +0 -0
- package/assets/tools/write.png +0 -0
- package/assets/tools/write_plan.png +0 -0
package/lib/requirements.ts
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
-
import { type JsonReadResult, SHARED_CONFIG_FILE } from "./config.ts";
|
|
18
|
+
import { configSection, configValueAt, type JsonReadResult, SHARED_CONFIG_FILE } from "./config.ts";
|
|
19
|
+
import type { WebSearchProviderEvent } from "./events.ts";
|
|
19
20
|
import { getPiInvocation, type PiInvocation } from "./pi-invocation.ts";
|
|
20
21
|
|
|
21
22
|
/** How a check came out. Only `missing` is worth interrupting a session for. */
|
|
@@ -73,7 +74,51 @@ export interface NotifyCheck {
|
|
|
73
74
|
hint: string;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The resolved `web_search` provider, as `web-access` publishes it.
|
|
79
|
+
*
|
|
80
|
+
* The provider list, each provider's configuration test, and the `auto` rule live
|
|
81
|
+
* in `web-access`, so the check reads a published summary rather than rebuilding
|
|
82
|
+
* any of it here; the payload never carries the key itself.
|
|
83
|
+
*/
|
|
84
|
+
export interface ProviderCheck {
|
|
85
|
+
kind: "provider";
|
|
86
|
+
enables: string;
|
|
87
|
+
hint: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A config switch whose "off" value trades a safety net away.
|
|
92
|
+
*
|
|
93
|
+
* A prerequisite table can already say "this is missing"; this says "you turned
|
|
94
|
+
* this protection off", which is the other way an extension ends up degraded
|
|
95
|
+
* without anything being absent. The value is read from `bytefly.json` (global,
|
|
96
|
+
* then project, project wins), so the check needs no extension to publish
|
|
97
|
+
* anything — the switch is the whole fact.
|
|
98
|
+
*/
|
|
99
|
+
export interface SafetyCheck {
|
|
100
|
+
kind: "safety";
|
|
101
|
+
/** The `bytefly.json` section that owns the switch. */
|
|
102
|
+
section: string;
|
|
103
|
+
/** The key path inside that section. */
|
|
104
|
+
keyPath: readonly string[];
|
|
105
|
+
/** The value that means the net is off. */
|
|
106
|
+
off: boolean;
|
|
107
|
+
/** What the switch protects, for the passing case. */
|
|
108
|
+
enables: string;
|
|
109
|
+
/** What is true once it is off, for the degraded case. */
|
|
110
|
+
disabled: string;
|
|
111
|
+
hint: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type RequirementCheck =
|
|
115
|
+
| BinaryCheck
|
|
116
|
+
| ModuleCheck
|
|
117
|
+
| StateCheck
|
|
118
|
+
| InvocationCheck
|
|
119
|
+
| NotifyCheck
|
|
120
|
+
| ProviderCheck
|
|
121
|
+
| SafetyCheck;
|
|
77
122
|
|
|
78
123
|
/** What an extension needs, split by how badly it needs it. */
|
|
79
124
|
export interface ExtensionRequirements {
|
|
@@ -138,7 +183,22 @@ function gitCheck(enables: string): BinaryCheck {
|
|
|
138
183
|
export const REQUIREMENTS: Readonly<Record<string, ExtensionRequirements>> = {
|
|
139
184
|
worktree: {
|
|
140
185
|
requires: [gitCheck("worktree isolation")],
|
|
141
|
-
|
|
186
|
+
optional: [
|
|
187
|
+
{
|
|
188
|
+
kind: "safety",
|
|
189
|
+
section: "worktree",
|
|
190
|
+
keyPath: ["guard", "blockPowershell"],
|
|
191
|
+
off: false,
|
|
192
|
+
enables: "`powershell` is refused while the session is isolated",
|
|
193
|
+
disabled: "`powershell` runs unguarded, with its relative paths against the main checkout",
|
|
194
|
+
hint: "set `worktree.guard.blockPowershell` back to true, or use `bash`, which is re-rooted and guarded.",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
notes: [
|
|
198
|
+
"`.worktreeinclude` symlinks need symlink support (Windows Developer Mode).",
|
|
199
|
+
"Your own `!` shell is not re-rooted or guarded; only the model's own tools are.",
|
|
200
|
+
"Commands whose git behavior cannot be verified pass through unless `worktree.guard.blockUnparsableCommands` is set.",
|
|
201
|
+
],
|
|
142
202
|
},
|
|
143
203
|
rewind: {
|
|
144
204
|
requires: [gitCheck("rewind snapshots")],
|
|
@@ -164,7 +224,7 @@ export const REQUIREMENTS: Readonly<Record<string, ExtensionRequirements>> = {
|
|
|
164
224
|
},
|
|
165
225
|
job: {
|
|
166
226
|
notes: [
|
|
167
|
-
"Windows:
|
|
227
|
+
"Windows: a job cannot be detached (`detached: true` is refused and `job.detachedByDefault` is inert), and a job reported only after its process is gone stays `unknown` — there is no shell `EXIT` trap.",
|
|
168
228
|
"`ps` is used for the pid-reuse guard and is POSIX-only.",
|
|
169
229
|
],
|
|
170
230
|
},
|
|
@@ -175,7 +235,14 @@ export const REQUIREMENTS: Readonly<Record<string, ExtensionRequirements>> = {
|
|
|
175
235
|
],
|
|
176
236
|
},
|
|
177
237
|
"web-access": {
|
|
178
|
-
optional: [
|
|
238
|
+
optional: [
|
|
239
|
+
{ kind: "module", name: "unpdf", enables: "PDF text extraction in `web_fetch`", hint: pdfGuidance() },
|
|
240
|
+
{
|
|
241
|
+
kind: "provider",
|
|
242
|
+
enables: "`web_search` through the configured provider",
|
|
243
|
+
hint: "set `web-access.search.provider` and its `endpoint`/`apiKeyEnv`/`apiKey` in bytefly.json, or export the key variable.",
|
|
244
|
+
},
|
|
245
|
+
],
|
|
179
246
|
},
|
|
180
247
|
"file-browse": {
|
|
181
248
|
optional: [gitCheck("the branch chip and the diff view")],
|
|
@@ -253,6 +320,14 @@ export interface ProbeSeams {
|
|
|
253
320
|
notify?: () => { enabled: boolean; method: string } | undefined;
|
|
254
321
|
/** Whether stdout is a terminal, which the OSC methods need; defaults to true. */
|
|
255
322
|
isTTY?: boolean;
|
|
323
|
+
/** The search provider `web-access` resolved for this session, when it published one. */
|
|
324
|
+
searchProvider?: () => WebSearchProviderEvent | undefined;
|
|
325
|
+
/** Read an environment variable; defaults to the process environment. */
|
|
326
|
+
env?: (name: string) => string | undefined;
|
|
327
|
+
/** Probe a URL's reachability; only called when `refresh` is true. */
|
|
328
|
+
reach?: (url: string) => Promise<{ ok: boolean; error?: string }>;
|
|
329
|
+
/** True when the caller asked for an explicit refresh, which is when a probe may touch the network. */
|
|
330
|
+
refresh?: boolean;
|
|
256
331
|
}
|
|
257
332
|
|
|
258
333
|
/**
|
|
@@ -292,13 +367,140 @@ async function probeCode(invocation: PiInvocation, seams: ProbeSeams): Promise<n
|
|
|
292
367
|
}
|
|
293
368
|
}
|
|
294
369
|
|
|
370
|
+
/**
|
|
371
|
+
* The findings for the `web_search` provider check.
|
|
372
|
+
*
|
|
373
|
+
* Up to three separate facts, because they have three different fixes: which
|
|
374
|
+
* provider is in use, whether its key variable is set, and — only on an explicit
|
|
375
|
+
* refresh — whether its configurable endpoint answers.
|
|
376
|
+
*/
|
|
377
|
+
async function providerFindings(
|
|
378
|
+
extension: string,
|
|
379
|
+
check: ProviderCheck,
|
|
380
|
+
failSeverity: FindingSeverity,
|
|
381
|
+
seams: ProbeSeams,
|
|
382
|
+
): Promise<Finding[]> {
|
|
383
|
+
const facts = seams.searchProvider?.();
|
|
384
|
+
if (!facts) {
|
|
385
|
+
return [
|
|
386
|
+
{
|
|
387
|
+
extension,
|
|
388
|
+
check: "web-access.search.provider",
|
|
389
|
+
severity: "info",
|
|
390
|
+
detail: "web-access has not reported a search provider in this session",
|
|
391
|
+
},
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const findings: Finding[] = [];
|
|
396
|
+
findings.push({
|
|
397
|
+
extension,
|
|
398
|
+
check: "web-access.search.provider",
|
|
399
|
+
severity: facts.configured ? "ok" : failSeverity,
|
|
400
|
+
detail: facts.configured
|
|
401
|
+
? `resolved to ${facts.provider} (${facts.reason})`
|
|
402
|
+
: `resolved to ${facts.provider} (${facts.reason}), which is not configured — ${check.enables}`,
|
|
403
|
+
...(!facts.configured ? { hint: check.hint } : {}),
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const env = seams.env ?? ((name: string) => process.env[name]);
|
|
407
|
+
if (facts.apiKeyEnv && !facts.keyConfigured) {
|
|
408
|
+
const present = (env(facts.apiKeyEnv) ?? "").trim() !== "";
|
|
409
|
+
findings.push({
|
|
410
|
+
extension,
|
|
411
|
+
check: facts.apiKeyEnv,
|
|
412
|
+
severity: present ? "ok" : failSeverity,
|
|
413
|
+
detail: present
|
|
414
|
+
? `${facts.apiKeyEnv} is set — the ${facts.provider} API key`
|
|
415
|
+
: `the API key for ${facts.provider} is missing — ${facts.apiKeyEnv} is not set`,
|
|
416
|
+
...(present ? {} : { hint: `export ${facts.apiKeyEnv}, or set web-access.search.apiKey in bytefly.json.` }),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Reachability is the one probe that touches the network, so it runs only when
|
|
421
|
+
// the caller asked for an explicit refresh.
|
|
422
|
+
if (seams.refresh === true && facts.endpoint !== null) {
|
|
423
|
+
const reach = seams.reach;
|
|
424
|
+
if (!reach) {
|
|
425
|
+
findings.push({
|
|
426
|
+
extension,
|
|
427
|
+
check: facts.endpoint,
|
|
428
|
+
severity: "info",
|
|
429
|
+
detail: "no reachability probe is available in this context",
|
|
430
|
+
});
|
|
431
|
+
} else {
|
|
432
|
+
const outcome = await reach(facts.endpoint).catch((error: unknown) => ({
|
|
433
|
+
ok: false,
|
|
434
|
+
error: error instanceof Error ? error.message : String(error),
|
|
435
|
+
}));
|
|
436
|
+
findings.push({
|
|
437
|
+
extension,
|
|
438
|
+
check: facts.endpoint,
|
|
439
|
+
severity: outcome.ok ? "ok" : failSeverity,
|
|
440
|
+
detail: outcome.ok
|
|
441
|
+
? `${facts.endpoint} answered`
|
|
442
|
+
: `${facts.endpoint} did not answer (${outcome.error ?? "no response"})`,
|
|
443
|
+
...(outcome.ok
|
|
444
|
+
? {}
|
|
445
|
+
: { hint: "check web-access.search.endpoint in bytefly.json, or that the SearXNG server is running." }),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return findings;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The effective value of one config leaf, global then project. An absent or
|
|
455
|
+
* unparseable document contributes nothing — the malformed-JSON check owns that
|
|
456
|
+
* case — so this returns `undefined` rather than a guess.
|
|
457
|
+
*/
|
|
458
|
+
function configLeaf(check: SafetyCheck, seams: ProbeSeams): { value: unknown; path: string } | undefined {
|
|
459
|
+
const candidates = [
|
|
460
|
+
join(seams.dirs.agentDir, SHARED_CONFIG_FILE),
|
|
461
|
+
join(seams.dirs.cwd, seams.dirs.configDirName, SHARED_CONFIG_FILE),
|
|
462
|
+
];
|
|
463
|
+
let found: { value: unknown; path: string } | undefined;
|
|
464
|
+
for (const path of candidates) {
|
|
465
|
+
const result = seams.readJson(path);
|
|
466
|
+
if (result.state !== "ok") continue;
|
|
467
|
+
const section = configSection(result.value, check.section);
|
|
468
|
+
const value = configValueAt(section, check.keyPath);
|
|
469
|
+
if (value !== undefined) found = { value, path };
|
|
470
|
+
}
|
|
471
|
+
return found;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** The findings for a switch whose off value trades a safety net away. */
|
|
475
|
+
function safetyFindings(
|
|
476
|
+
extension: string,
|
|
477
|
+
check: SafetyCheck,
|
|
478
|
+
failSeverity: FindingSeverity,
|
|
479
|
+
seams: ProbeSeams,
|
|
480
|
+
): Finding[] {
|
|
481
|
+
const leaf = configLeaf(check, seams);
|
|
482
|
+
const label = [check.section, ...check.keyPath].join(".");
|
|
483
|
+
if (leaf?.value === check.off) {
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
extension,
|
|
487
|
+
check: label,
|
|
488
|
+
severity: failSeverity,
|
|
489
|
+
detail: `is ${String(check.off)} in ${leaf.path} — ${check.disabled}`,
|
|
490
|
+
hint: check.hint,
|
|
491
|
+
},
|
|
492
|
+
];
|
|
493
|
+
}
|
|
494
|
+
return [{ extension, check: label, severity: "ok", detail: check.enables }];
|
|
495
|
+
}
|
|
496
|
+
|
|
295
497
|
/** Run one check. `failSeverity` comes from which list the check sits in. */
|
|
296
498
|
async function runCheck(
|
|
297
499
|
extension: string,
|
|
298
500
|
check: RequirementCheck,
|
|
299
501
|
failSeverity: FindingSeverity,
|
|
300
502
|
seams: ProbeSeams,
|
|
301
|
-
): Promise<Finding> {
|
|
503
|
+
): Promise<Finding[]> {
|
|
302
504
|
switch (check.kind) {
|
|
303
505
|
case "binary": {
|
|
304
506
|
const invocation = check.invocation?.() ?? {
|
|
@@ -306,91 +508,109 @@ async function runCheck(
|
|
|
306
508
|
args: [...(check.probeArgs ?? ["--version"])],
|
|
307
509
|
};
|
|
308
510
|
const code = await probeCode(invocation, seams);
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
511
|
+
return [
|
|
512
|
+
code === 0
|
|
513
|
+
? { extension, check: check.name, severity: "ok", detail: check.enables }
|
|
514
|
+
: {
|
|
515
|
+
extension,
|
|
516
|
+
check: check.name,
|
|
517
|
+
severity: failSeverity,
|
|
518
|
+
detail: `not found on PATH — ${check.enables}`,
|
|
519
|
+
hint: check.hint,
|
|
520
|
+
},
|
|
521
|
+
];
|
|
318
522
|
}
|
|
319
523
|
case "module": {
|
|
320
524
|
const load = seams.loadModule ?? defaultLoadModule;
|
|
321
525
|
try {
|
|
322
526
|
await load(check.name);
|
|
323
527
|
} catch {
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
528
|
+
return [
|
|
529
|
+
{
|
|
530
|
+
extension,
|
|
531
|
+
check: check.name,
|
|
532
|
+
severity: failSeverity,
|
|
533
|
+
detail: `not installed — ${check.enables}`,
|
|
534
|
+
hint: check.hint,
|
|
535
|
+
},
|
|
536
|
+
];
|
|
331
537
|
}
|
|
332
|
-
return { extension, check: check.name, severity: "ok", detail: check.enables };
|
|
538
|
+
return [{ extension, check: check.name, severity: "ok", detail: check.enables }];
|
|
333
539
|
}
|
|
334
540
|
case "state": {
|
|
335
541
|
const path = join(scopeDir(check.scope, seams.dirs), check.file);
|
|
336
542
|
const result = seams.readJson(path);
|
|
337
|
-
if (result.state === "ok") return { extension, check: path, severity: "ok", detail: check.enables };
|
|
543
|
+
if (result.state === "ok") return [{ extension, check: path, severity: "ok", detail: check.enables }];
|
|
338
544
|
if (result.state === "absent") {
|
|
339
|
-
return
|
|
545
|
+
return [
|
|
546
|
+
{
|
|
547
|
+
extension,
|
|
548
|
+
check: path,
|
|
549
|
+
severity: failSeverity,
|
|
550
|
+
detail: `missing — ${check.enables}`,
|
|
551
|
+
hint: check.hint,
|
|
552
|
+
},
|
|
553
|
+
];
|
|
554
|
+
}
|
|
555
|
+
return [
|
|
556
|
+
{
|
|
340
557
|
extension,
|
|
341
558
|
check: path,
|
|
342
559
|
severity: failSeverity,
|
|
343
|
-
detail: `
|
|
560
|
+
detail: `unreadable (${result.error}) — ${check.enables}`,
|
|
344
561
|
hint: check.hint,
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
extension,
|
|
349
|
-
check: path,
|
|
350
|
-
severity: failSeverity,
|
|
351
|
-
detail: `unreadable (${result.error}) — ${check.enables}`,
|
|
352
|
-
hint: check.hint,
|
|
353
|
-
};
|
|
562
|
+
},
|
|
563
|
+
];
|
|
354
564
|
}
|
|
355
565
|
case "invocation": {
|
|
356
566
|
const invocation = seams.invocation?.() ?? getPiInvocation(["--version"]);
|
|
357
567
|
// Reusing the running interpreter cannot fail: the process is running.
|
|
358
568
|
const code = invocation.command === process.execPath ? 0 : await probeCode(invocation, seams);
|
|
359
|
-
return
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
569
|
+
return [
|
|
570
|
+
code === 0
|
|
571
|
+
? { extension, check: invocation.command, severity: "ok", detail: check.enables }
|
|
572
|
+
: {
|
|
573
|
+
extension,
|
|
574
|
+
check: invocation.command,
|
|
575
|
+
severity: failSeverity,
|
|
576
|
+
detail: `cannot be run — ${check.enables}`,
|
|
577
|
+
hint: check.hint,
|
|
578
|
+
},
|
|
579
|
+
];
|
|
368
580
|
}
|
|
369
581
|
case "notify": {
|
|
370
582
|
const notify = seams.notify?.();
|
|
371
583
|
if (!notify?.enabled) {
|
|
372
|
-
return { extension, check: "notify.method", severity: "ok", detail: "notifications are off" };
|
|
584
|
+
return [{ extension, check: "notify.method", severity: "ok", detail: "notifications are off" }];
|
|
373
585
|
}
|
|
374
586
|
const env = { platform: seams.platform, isTTY: seams.isTTY ?? true };
|
|
375
587
|
if (canDeliver(notify.method, env)) {
|
|
376
|
-
return
|
|
588
|
+
return [
|
|
589
|
+
{
|
|
590
|
+
extension,
|
|
591
|
+
check: "notify.method",
|
|
592
|
+
severity: "ok",
|
|
593
|
+
detail:
|
|
594
|
+
notify.method === "auto"
|
|
595
|
+
? "auto picks a route from the environment"
|
|
596
|
+
: `"${notify.method}" delivers on ${seams.platform}`,
|
|
597
|
+
},
|
|
598
|
+
];
|
|
599
|
+
}
|
|
600
|
+
return [
|
|
601
|
+
{
|
|
377
602
|
extension,
|
|
378
603
|
check: "notify.method",
|
|
379
|
-
severity:
|
|
380
|
-
detail:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
return {
|
|
387
|
-
extension,
|
|
388
|
-
check: "notify.method",
|
|
389
|
-
severity: failSeverity,
|
|
390
|
-
detail: `"${notify.method}" cannot deliver on ${seams.platform} — ${check.enables}`,
|
|
391
|
-
hint: check.hint,
|
|
392
|
-
};
|
|
604
|
+
severity: failSeverity,
|
|
605
|
+
detail: `"${notify.method}" cannot deliver on ${seams.platform} — ${check.enables}`,
|
|
606
|
+
hint: check.hint,
|
|
607
|
+
},
|
|
608
|
+
];
|
|
393
609
|
}
|
|
610
|
+
case "provider":
|
|
611
|
+
return providerFindings(extension, check, failSeverity, seams);
|
|
612
|
+
case "safety":
|
|
613
|
+
return safetyFindings(extension, check, failSeverity, seams);
|
|
394
614
|
}
|
|
395
615
|
}
|
|
396
616
|
|
|
@@ -461,8 +681,8 @@ export async function probeExtension(extension: string, seams: ProbeSeams): Prom
|
|
|
461
681
|
}
|
|
462
682
|
|
|
463
683
|
const findings: Finding[] = [];
|
|
464
|
-
for (const check of spec.requires ?? []) findings.push(await runCheck(extension, check, "missing", seams));
|
|
465
|
-
for (const check of spec.optional ?? []) findings.push(await runCheck(extension, check, "degraded", seams));
|
|
684
|
+
for (const check of spec.requires ?? []) findings.push(...(await runCheck(extension, check, "missing", seams)));
|
|
685
|
+
for (const check of spec.optional ?? []) findings.push(...(await runCheck(extension, check, "degraded", seams)));
|
|
466
686
|
findings.push(...configFindings(extension, spec, seams));
|
|
467
687
|
for (const note of spec.notes ?? []) {
|
|
468
688
|
findings.push({ extension, check: "platform", severity: "info", detail: note });
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# lib/tui — the shared terminal design system
|
|
2
|
+
|
|
3
|
+
The primitives every interactive surface in this package draws with: rules,
|
|
4
|
+
rows, columns, gauges, panes, glyphs, color roles, and the tool-transcript kit.
|
|
5
|
+
The module-by-module inventory is the `tui/` row of [`../README.md`](../README.md);
|
|
6
|
+
this file is the conventions those modules exist to make easy.
|
|
7
|
+
|
|
8
|
+
Pi's [Terminal UI](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/docs/tui.md)
|
|
9
|
+
and [Themes](https://github.com/earendil-works/pi/tree/main/packages/coding-agent/docs/themes.md)
|
|
10
|
+
docs are normative. Follow Pi's built-in design system and do not invent a
|
|
11
|
+
private visual language; a surface that re-derives chrome, glyphs, or scroll math
|
|
12
|
+
instead of importing it is the bug this directory exists to prevent.
|
|
13
|
+
|
|
14
|
+
## Host before custom code
|
|
15
|
+
|
|
16
|
+
Reach down the ladder in order and stop at the first rung that works:
|
|
17
|
+
`ctx.ui.select` / `confirm` / `input` / `editor`, then `notify` / `setStatus` /
|
|
18
|
+
`setWidget`, then the pi-tui components. Reach for `ctx.ui.custom()` or a
|
|
19
|
+
tool/entry renderer only when a surface needs its own layout or input, and never
|
|
20
|
+
build a second terminal renderer.
|
|
21
|
+
|
|
22
|
+
## Block grammar
|
|
23
|
+
|
|
24
|
+
- Match the built-in block grammar: one leading blank line per transcript block,
|
|
25
|
+
nothing trailing (the host owns spacing).
|
|
26
|
+
- Frame screens with a plain `─` rule, not box corners.
|
|
27
|
+
- Compact by default, expand for detail: label-first one-line widgets, with more
|
|
28
|
+
revealed on demand or on a dedicated screen.
|
|
29
|
+
|
|
30
|
+
## Primitives
|
|
31
|
+
|
|
32
|
+
Reach for a `lib/tui/` primitive before composing your own: `plainRule` /
|
|
33
|
+
`titleRule` for a heading, `listRow` / `checkbox` for a list, `gaugeBar` for a
|
|
34
|
+
meter, `emptyState` for an empty screen, `splitRow` / `fitCells` for two columns,
|
|
35
|
+
`LinePane` for a scrollable log, and `ListCursor` / `IdCursor` for selection.
|
|
36
|
+
|
|
37
|
+
## Color is a role
|
|
38
|
+
|
|
39
|
+
Resolve colors through a [`tokens.ts`](./tokens.ts) role
|
|
40
|
+
(`paint(theme, role, text)`), never a literal ANSI sequence, hex value, or Pi
|
|
41
|
+
token named inline; a theme swap must re-skin every surface. Use the shared
|
|
42
|
+
`selectionMarker`, `GLYPHS`, `SEPARATORS`, and `STATUS_KEYS` instead of retyping
|
|
43
|
+
their literals, and `screenHint` / Pi's `keyHint` for the hint row.
|
|
44
|
+
|
|
45
|
+
## Width is columns
|
|
46
|
+
|
|
47
|
+
Width is terminal columns, not string length. Use `visibleWidth`,
|
|
48
|
+
`truncateToWidth`, and `wrapTextWithAnsi`; every returned line must fit the
|
|
49
|
+
width, and width arithmetic starts from `Math.max(1, width)`. Size from the live
|
|
50
|
+
viewport — `titleRule`, `viewportRows`, `fitRows`, and `ListCursor`, fed by a
|
|
51
|
+
`ViewportRowsSource` (number or getter) with a sensible fallback — so a resize is
|
|
52
|
+
picked up. List screens stay docked; only the plan review uses
|
|
53
|
+
`FULL_SCREEN_OVERLAY`.
|
|
54
|
+
|
|
55
|
+
## Sanitize at the boundary
|
|
56
|
+
|
|
57
|
+
Run model- or file-authored text through `sanitizeLine` before it reaches a
|
|
58
|
+
widget or the terminal: it neutralizes control characters and then collapses the
|
|
59
|
+
text to one line. `stripControlChars` alone is for multi-line text that must keep
|
|
60
|
+
its newlines.
|
|
61
|
+
|
|
62
|
+
## Keyboard first
|
|
63
|
+
|
|
64
|
+
Use `matchesKey` / `Key`, give every mouse interaction a keyboard path, and keep
|
|
65
|
+
the shared bindings: Esc closes, `j` / `k` and arrows scroll, `g` / `G` and
|
|
66
|
+
Home/End jump.
|
|
67
|
+
|
|
68
|
+
## Components are derived and cheap
|
|
69
|
+
|
|
70
|
+
Implement `Component` with `invalidate()`, update an instance in place, read live
|
|
71
|
+
state on each render, and call `requestRender()` only after state changes. Never
|
|
72
|
+
hold themed strings in state unless `invalidate()` rebuilds them. A widget is
|
|
73
|
+
derived from state, never authoritative.
|
|
74
|
+
|
|
75
|
+
Pi renders the whole tree every frame, so a component that derives its lines from
|
|
76
|
+
another one memoizes them with `RenderCache` (keyed on width and the source
|
|
77
|
+
lines' identity) and drops the memo in `invalidate()`. Re-deriving every line per
|
|
78
|
+
frame is what makes a long transcript feel slow.
|
|
79
|
+
|
|
80
|
+
## Focus
|
|
81
|
+
|
|
82
|
+
Reserve the name `focused` for a real focus `get` / `set` pair. Pi's `setFocus`
|
|
83
|
+
duck-types a focusable with `"focused" in component` and then assigns a boolean
|
|
84
|
+
there, so any other member of that name — a method, say — is silently shadowed
|
|
85
|
+
and the first call throws at runtime.
|
|
86
|
+
|
|
87
|
+
## Tests
|
|
88
|
+
|
|
89
|
+
A module here gets a sibling `<module>.test.ts` covering fields, width bounds,
|
|
90
|
+
ordering, and glyphs — never literal column padding. An extension's screen test
|
|
91
|
+
is the `tui.test.ts` beside its `tui.ts`.
|
package/lib/tui/glyphs.ts
CHANGED
package/lib/web/layout.ts
CHANGED
|
@@ -14,15 +14,25 @@
|
|
|
14
14
|
*
|
|
15
15
|
* The shell is a definite full-height column so the panes scroll inside it; the
|
|
16
16
|
* 860px breakpoint drops that back to normal page scroll for narrow screens.
|
|
17
|
+
*
|
|
18
|
+
* The sidebar scrolls on its own box rather than on the `.bf-panel` it holds:
|
|
19
|
+
* a `<details>` lays its content out in a block wrapper with no definite
|
|
20
|
+
* height that takes no part in the element's flex layout, so a flex child there
|
|
21
|
+
* keeps its content height and the sidebar would only clip it. A page that
|
|
22
|
+
* needs pinned chrome above its own scroll pane (file-browse) fills that
|
|
23
|
+
* wrapper absolutely and scrolls an inner region instead.
|
|
17
24
|
*/
|
|
18
25
|
|
|
26
|
+
/** Viewport width, in px, at and below which the sidebar becomes a disclosure. */
|
|
27
|
+
export const SIDEBAR_COLLAPSE_MAX = 860;
|
|
28
|
+
|
|
19
29
|
export const WEB_LAYOUT_CSS = `.bf-app{display:flex;flex-direction:column;height:100vh;min-height:0;overflow:hidden;background:var(--canvas);color:var(--text);font:var(--fs-base)/var(--lh-normal) var(--font-ui)}
|
|
20
30
|
.bf-app__header{position:sticky;top:0;z-index:2;flex:0 0 auto;background:var(--surface);border-bottom:1px solid var(--border)}
|
|
21
31
|
.bf-app__body{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:260px minmax(0,1fr)}
|
|
22
|
-
.bf-app__sidebar{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;background:var(--surface);border-right:1px solid var(--border)}
|
|
32
|
+
.bf-app__sidebar{display:flex;flex-direction:column;min-width:0;min-height:0;overflow-x:hidden;overflow-y:auto;background:var(--surface);border-right:1px solid var(--border)}
|
|
23
33
|
.bf-app__main{min-width:0;min-height:0;display:flex;flex-direction:column;overflow:auto;padding:var(--space-5) var(--space-6)}
|
|
24
34
|
.bf-app__footer{flex:0 0 auto;border-top:1px solid var(--border);color:var(--muted);font-size:var(--fs-sm);padding:var(--space-3) var(--space-6)}
|
|
25
|
-
@media (max-width
|
|
35
|
+
@media (max-width:${SIDEBAR_COLLAPSE_MAX}px){
|
|
26
36
|
.bf-app{height:auto;min-height:100vh;overflow:visible}
|
|
27
37
|
.bf-app__body{flex:none;grid-template-columns:1fr}
|
|
28
38
|
.bf-app__sidebar{display:block;position:static;max-height:none;overflow:visible;border-right:0;border-bottom:1px solid var(--border)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gavin-hu/my-pi-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Personal Pi customizations, packaged as a Pi package: worktree isolation and rewind snapshots, question, todo, goal, plan, subagent, job, watch, web-access, file-browse, file-open, document, memory, wechat, and bytefly.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"url": "https://github.com/gavin-hu/my-pi-agent.git"
|
|
60
60
|
},
|
|
61
61
|
"files": [
|
|
62
|
-
"assets/**",
|
|
63
62
|
"extensions/**",
|
|
64
63
|
"lib/**",
|
|
65
64
|
"themes/**",
|
|
66
65
|
"!**/*.test.ts"
|
|
67
66
|
],
|
|
68
67
|
"publishConfig": {
|
|
69
|
-
"access": "public"
|
|
68
|
+
"access": "public",
|
|
69
|
+
"provenance": true
|
|
70
70
|
},
|
|
71
71
|
"pi": {
|
|
72
72
|
"extensions": [
|
|
@@ -103,6 +103,8 @@
|
|
|
103
103
|
"check:serial": "bun run format:check && bun run typecheck && bun run test:serial",
|
|
104
104
|
"release:notes": "bun run scripts/release-notes.ts",
|
|
105
105
|
"config:migrate": "bun run scripts/migrate-config.ts",
|
|
106
|
+
"compat:versions": "bun run scripts/peer-versions.ts",
|
|
107
|
+
"bench:render": "bun run scripts/render-bench.ts",
|
|
106
108
|
"screenshot:plan": "bun run scripts/plan-review-shot.ts",
|
|
107
109
|
"screenshot:tools": "bun run scripts/tool-renderings.ts gallery assets/tool-renderings.png",
|
|
108
110
|
"screenshot:tool-pngs": "bun run scripts/tool-renderings.ts tools assets/tools",
|
package/themes/bytefly-dark.json
CHANGED
package/assets/chrome.png
DELETED
|
Binary file
|
package/assets/file-browse.png
DELETED
|
Binary file
|
package/assets/job.png
DELETED
|
Binary file
|
package/assets/plan-review.png
DELETED
|
Binary file
|
package/assets/question.png
DELETED
|
Binary file
|
package/assets/rewind.png
DELETED
|
Binary file
|
package/assets/todo.png
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/assets/tools/bash.png
DELETED
|
Binary file
|
package/assets/tools/edit.png
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/assets/tools/find.png
DELETED
|
Binary file
|
package/assets/tools/goal.png
DELETED
|
Binary file
|
package/assets/tools/grep.png
DELETED
|
Binary file
|
package/assets/tools/job.png
DELETED
|
Binary file
|
|
Binary file
|
package/assets/tools/ls.png
DELETED
|
Binary file
|
|
Binary file
|