@gavin-hu/my-pi-agent 0.13.1 → 0.15.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 +66 -41
- package/README.zh-CN.md +53 -36
- package/extensions/bytefly/README.md +321 -186
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/config.ts +4 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/config.ts +3 -2
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +33 -10
- package/extensions/bytefly/header/config.ts +22 -0
- package/extensions/bytefly/header/register.ts +2 -1
- package/extensions/bytefly/index.ts +6 -6
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +7 -12
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +22 -2
- package/extensions/bytefly/panel/format.ts +17 -21
- package/extensions/bytefly/panel/settings-runtime.ts +86 -22
- package/extensions/bytefly/panel/settings.ts +279 -46
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +98 -13
- package/extensions/bytefly/panel/tui.ts +176 -166
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +17 -6
- package/extensions/bytefly/separator/config.ts +15 -0
- package/extensions/bytefly/separator/format.ts +60 -6
- package/extensions/bytefly/separator/register.ts +68 -9
- package/extensions/bytefly/separator/types.ts +8 -0
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/bytefly/working/config.ts +4 -5
- package/extensions/bytefly/working/format.ts +24 -7
- package/extensions/bytefly/working/register.ts +34 -3
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +3 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +106 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +118 -8
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +62 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +116 -1
- package/extensions/subagent/orchestrate.ts +16 -3
- package/extensions/subagent/render.ts +25 -0
- package/extensions/subagent/run.ts +113 -49
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/transcripts.ts +202 -0
- package/extensions/subagent/types.ts +23 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +16 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +3 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +83 -12
- package/lib/errors.ts +12 -0
- package/lib/format.ts +56 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +23 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/themes/bytefly-dark.json +2 -3
- package/themes/bytefly-light.json +2 -3
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -1,47 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The update
|
|
3
|
-
*
|
|
2
|
+
* The update surface's lifecycle: ask Pi's package manager which configured
|
|
3
|
+
* packages are behind, cache the snapshot, and announce it once per load.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Pi would draw its own `Package Updates Available` block from the same method
|
|
6
|
+
* after `session_start`. bytefly suppresses that block, asks the captured
|
|
7
|
+
* original check itself, and draws the single notice. The request is fired
|
|
8
|
+
* without awaiting: Pi awaits `session_start` handlers, and a slow npm/git check
|
|
9
|
+
* must not hold the first prompt.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* version: an available update is not a prerequisite problem.
|
|
11
|
+
* The 24-hour cache is keyed by the working directory and the effective package
|
|
12
|
+
* sources, so a project switch or an install/removal invalidates it. A cache hit
|
|
13
|
+
* costs no package-manager call; `/bytefly update` ignores the window and re-runs
|
|
14
|
+
* the check on demand.
|
|
16
15
|
*/
|
|
17
16
|
|
|
18
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
19
18
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
20
19
|
import pkg from "../../../package.json";
|
|
21
20
|
import { isOffline } from "../../../lib/env.ts";
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
import {
|
|
22
|
+
isFresh,
|
|
23
|
+
readUpdateCache,
|
|
24
|
+
updateCacheKey,
|
|
25
|
+
updateCachePath,
|
|
26
|
+
writeUpdateCache,
|
|
27
|
+
type UpdateCacheEntry,
|
|
28
|
+
} from "./cache.ts";
|
|
29
|
+
import { fetchAvailableUpdates, fetchLatestVersion } from "./check.ts";
|
|
30
|
+
import { isOwnPackage, updateNotice } from "./format.ts";
|
|
25
31
|
import type { UpdateOutcome, UpdateSource } from "./state.ts";
|
|
32
|
+
import { suppressPiPackageUpdates, type UpdateSuppression } from "./suppress.ts";
|
|
33
|
+
import type { PackageUpdate, PackageUpdateCheck } from "./types.ts";
|
|
26
34
|
import { isNewerVersion } from "./version.ts";
|
|
27
35
|
|
|
28
36
|
/** The cache, as the lifecycle sees it: read on start, written after a read. */
|
|
29
37
|
export interface UpdateCache {
|
|
30
|
-
/** The last recorded
|
|
38
|
+
/** The last recorded snapshot, or `undefined` for a miss. */
|
|
31
39
|
read(): UpdateCacheEntry | undefined;
|
|
32
|
-
/** Record
|
|
40
|
+
/** Record a snapshot; a throw is treated as a miss. */
|
|
33
41
|
write(entry: UpdateCacheEntry): void;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
/** What `registerUpdate` returns, so the panel can re-run the check on demand. */
|
|
37
45
|
export interface UpdateHandle {
|
|
38
|
-
/** Read the
|
|
46
|
+
/** Read the package list now, ignoring the cache window. Never throws. */
|
|
39
47
|
recheck(): Promise<UpdateOutcome>;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
/** Test seams; production uses
|
|
50
|
+
/** Test seams; production uses Pi's package manager, its cache, and the agent dir. */
|
|
43
51
|
export interface UpdateDeps {
|
|
44
|
-
/** Replace the
|
|
52
|
+
/** Replace the package-manager read. */
|
|
53
|
+
readUpdates?: (ctx: ExtensionContext, check: PackageUpdateCheck) => Promise<PackageUpdate[] | undefined>;
|
|
54
|
+
/** Replace the registry read that names this package's new version. */
|
|
45
55
|
fetchLatest?: () => Promise<string | undefined>;
|
|
46
56
|
/** Replace the `PI_OFFLINE` check. */
|
|
47
57
|
isOffline?: () => boolean;
|
|
@@ -49,7 +59,9 @@ export interface UpdateDeps {
|
|
|
49
59
|
cache?: UpdateCache;
|
|
50
60
|
/** Replace the clock the cache timestamp is read from. */
|
|
51
61
|
now?: () => number;
|
|
52
|
-
/**
|
|
62
|
+
/** Replace the host suppression; defaults to patching Pi's package manager. */
|
|
63
|
+
suppress?: () => UpdateSuppression;
|
|
64
|
+
/** Called once a snapshot is recorded, so a mounted surface repaints. */
|
|
53
65
|
onUpdate?: () => void;
|
|
54
66
|
}
|
|
55
67
|
|
|
@@ -61,52 +73,112 @@ function diskCache(): UpdateCache {
|
|
|
61
73
|
try {
|
|
62
74
|
writeUpdateCache(updateCachePath(getAgentDir()), entry);
|
|
63
75
|
} catch {
|
|
64
|
-
// A read-only agent directory costs the next session one
|
|
76
|
+
// A read-only agent directory costs the next session one check, nothing more.
|
|
65
77
|
}
|
|
66
78
|
},
|
|
67
79
|
};
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
/**
|
|
71
|
-
* Register the
|
|
72
|
-
* a
|
|
83
|
+
* Register the update lifecycle. `updates` is the store the header reads, so
|
|
84
|
+
* recording a snapshot is what puts this package's marker on screen.
|
|
73
85
|
*/
|
|
74
86
|
export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: UpdateDeps = {}): UpdateHandle {
|
|
87
|
+
const readUpdates =
|
|
88
|
+
deps.readUpdates ??
|
|
89
|
+
((ctx, check) =>
|
|
90
|
+
fetchAvailableUpdates({
|
|
91
|
+
cwd: ctx.cwd,
|
|
92
|
+
agentDir: getAgentDir(),
|
|
93
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
94
|
+
checkForAvailableUpdates: check,
|
|
95
|
+
}));
|
|
75
96
|
const readLatest = deps.fetchLatest ?? (() => fetchLatestVersion());
|
|
76
97
|
const offline = deps.isOffline ?? isOffline;
|
|
77
98
|
const cache = deps.cache ?? diskCache();
|
|
78
99
|
const now = deps.now ?? Date.now;
|
|
100
|
+
const suppress = deps.suppress ?? suppressPiPackageUpdates;
|
|
79
101
|
// A session is announced at most once per load, even if `session_start` fires
|
|
80
102
|
// again after a resume or a session replacement.
|
|
81
103
|
let announced = false;
|
|
82
104
|
let generation = 0;
|
|
105
|
+
let suppression: UpdateSuppression | undefined;
|
|
106
|
+
let context: ExtensionContext | undefined;
|
|
107
|
+
const fallbackCheck: PackageUpdateCheck = (manager) => manager.checkForAvailableUpdates();
|
|
108
|
+
const activeCheck = (): PackageUpdateCheck => suppression?.check ?? fallbackCheck;
|
|
83
109
|
|
|
84
|
-
/**
|
|
85
|
-
const
|
|
86
|
-
|
|
110
|
+
/** Identity of the settings a snapshot is read under. */
|
|
111
|
+
const keyFor = (ctx: ExtensionContext): string => updateCacheKey(ctx.cwd, pi.getSettings?.()?.packages);
|
|
112
|
+
|
|
113
|
+
/** A cache write never turns a read into a failed check. */
|
|
114
|
+
const writeCache = (entry: UpdateCacheEntry): void => {
|
|
115
|
+
try {
|
|
116
|
+
cache.write(entry);
|
|
117
|
+
} catch {
|
|
118
|
+
// The next session performs one more check; this one already has its answer.
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** This package's new version in a snapshot, when it is actually newer. */
|
|
123
|
+
const ownLatestOf = (entry: UpdateCacheEntry): string | undefined => {
|
|
124
|
+
if (!entry.latest || !isNewerVersion(entry.latest, pkg.version)) return undefined;
|
|
125
|
+
return entry.updates.some(isOwnPackage) ? entry.latest : undefined;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Paint the header marker, or clear it when this package is current. */
|
|
129
|
+
const paint = (latest?: string): void => {
|
|
130
|
+
if (updates.latest() === latest) return;
|
|
87
131
|
updates.set(latest);
|
|
88
132
|
deps.onUpdate?.();
|
|
89
133
|
};
|
|
90
134
|
|
|
91
|
-
/** The once-per-load transcript notice, posted only for a
|
|
92
|
-
const announce = (
|
|
93
|
-
if (announced ||
|
|
135
|
+
/** The once-per-load transcript notice, posted only for a non-empty list. */
|
|
136
|
+
const announce = (entry: UpdateCacheEntry): void => {
|
|
137
|
+
if (announced || entry.updates.length === 0) return;
|
|
94
138
|
announced = true;
|
|
95
|
-
pi.sendMessage(updateNotice(pkg.version,
|
|
139
|
+
pi.sendMessage(updateNotice(entry.updates, pkg.version, ownLatestOf(entry)));
|
|
96
140
|
};
|
|
97
141
|
|
|
98
|
-
/** One
|
|
142
|
+
/** One snapshot: paint the marker and, when it has rows, announce it once. */
|
|
99
143
|
const record = (entry: UpdateCacheEntry): void => {
|
|
100
|
-
paint(entry
|
|
101
|
-
announce(entry
|
|
144
|
+
paint(ownLatestOf(entry));
|
|
145
|
+
announce(entry);
|
|
102
146
|
};
|
|
103
147
|
|
|
104
|
-
|
|
148
|
+
/** Offline: the cached marker is advisory, but no notice is posted. */
|
|
149
|
+
const recordMarkerOnly = (entry: UpdateCacheEntry): void => {
|
|
150
|
+
paint(ownLatestOf(entry));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Resolve this package's version label, dropping an equal or older answer. */
|
|
154
|
+
const resolveOwnLatest = async (own: PackageUpdate | undefined): Promise<string | undefined> => {
|
|
155
|
+
if (!own) return undefined;
|
|
156
|
+
let latest: string | undefined;
|
|
105
157
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
158
|
+
latest = await readLatest();
|
|
159
|
+
} catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return latest && isNewerVersion(latest, pkg.version) ? latest : undefined;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const check = async (ctx: ExtensionContext, token: number): Promise<void> => {
|
|
166
|
+
try {
|
|
167
|
+
const list = await readUpdates(ctx, activeCheck());
|
|
168
|
+
if (token !== generation) return;
|
|
169
|
+
// A check that could not run is not an empty answer: caching it would tell
|
|
170
|
+
// the next session "up to date" for a day and clear a marker that may still
|
|
171
|
+
// be right, so leave both alone and try again next time.
|
|
172
|
+
if (list === undefined) return;
|
|
173
|
+
const latest = await resolveOwnLatest(list.find(isOwnPackage));
|
|
174
|
+
if (token !== generation) return;
|
|
175
|
+
const entry: UpdateCacheEntry = {
|
|
176
|
+
key: keyFor(ctx),
|
|
177
|
+
checkedAt: now(),
|
|
178
|
+
updates: list,
|
|
179
|
+
...(latest ? { latest } : {}),
|
|
180
|
+
};
|
|
181
|
+
writeCache(entry);
|
|
110
182
|
record(entry);
|
|
111
183
|
} catch {
|
|
112
184
|
// A background check never surfaces an error: the session does not know it ran.
|
|
@@ -114,45 +186,62 @@ export function registerUpdate(pi: ExtensionAPI, updates: UpdateSource, deps: Up
|
|
|
114
186
|
};
|
|
115
187
|
|
|
116
188
|
pi.on("session_start", (_event, ctx) => {
|
|
117
|
-
//
|
|
118
|
-
//
|
|
189
|
+
// `/bytefly update` can answer in any mode; only the TUI installs the host
|
|
190
|
+
// shim and posts a notice.
|
|
191
|
+
context = ctx;
|
|
119
192
|
if (ctx.mode !== "tui") return;
|
|
193
|
+
suppression ??= suppress();
|
|
194
|
+
const cached = cache.read();
|
|
195
|
+
const key = keyFor(ctx);
|
|
120
196
|
if (offline()) {
|
|
121
|
-
// No request, so the window does not matter: the cached
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
if (cached) record(cached);
|
|
197
|
+
// No request, so the window does not matter: the cached marker is the best
|
|
198
|
+
// answer available, and no notice is posted from a stale list.
|
|
199
|
+
if (cached && cached.key === key) recordMarkerOnly(cached);
|
|
125
200
|
return;
|
|
126
201
|
}
|
|
127
|
-
|
|
128
|
-
if (cached && isFresh(cached, now())) {
|
|
202
|
+
if (cached && cached.key === key && isFresh(cached, now())) {
|
|
129
203
|
record(cached);
|
|
130
204
|
return;
|
|
131
205
|
}
|
|
132
206
|
if (announced) return;
|
|
133
|
-
void check(generation);
|
|
207
|
+
void check(ctx, generation);
|
|
134
208
|
});
|
|
135
209
|
|
|
136
210
|
pi.on("session_shutdown", () => {
|
|
137
|
-
// Invalidate any request still in flight
|
|
211
|
+
// Invalidate any request still in flight, drop what this session found, and
|
|
212
|
+
// give Pi's own check back to the host.
|
|
138
213
|
generation++;
|
|
139
214
|
updates.set();
|
|
215
|
+
suppression?.restore();
|
|
216
|
+
suppression = undefined;
|
|
217
|
+
context = undefined;
|
|
140
218
|
});
|
|
141
219
|
|
|
142
220
|
return {
|
|
143
221
|
async recheck() {
|
|
144
222
|
if (offline()) return { kind: "offline" };
|
|
145
|
-
|
|
223
|
+
if (!context) return { kind: "failed" };
|
|
224
|
+
let list: PackageUpdate[];
|
|
146
225
|
try {
|
|
147
|
-
|
|
226
|
+
const found = await readUpdates(context, activeCheck());
|
|
227
|
+
if (found === undefined) return { kind: "failed" };
|
|
228
|
+
list = found;
|
|
148
229
|
} catch {
|
|
149
230
|
return { kind: "failed" };
|
|
150
231
|
}
|
|
151
|
-
|
|
152
|
-
|
|
232
|
+
const latest = await resolveOwnLatest(list.find(isOwnPackage));
|
|
233
|
+
const entry: UpdateCacheEntry = {
|
|
234
|
+
key: keyFor(context),
|
|
235
|
+
checkedAt: now(),
|
|
236
|
+
updates: list,
|
|
237
|
+
...(latest ? { latest } : {}),
|
|
238
|
+
};
|
|
239
|
+
writeCache(entry);
|
|
153
240
|
// The caller prints the answer, so this path never posts a notice.
|
|
154
241
|
paint(latest);
|
|
155
|
-
return
|
|
242
|
+
return list.length > 0
|
|
243
|
+
? { kind: "available", updates: list, ...(latest ? { ownLatest: latest } : {}) }
|
|
244
|
+
: { kind: "current" };
|
|
156
245
|
},
|
|
157
246
|
};
|
|
158
247
|
}
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* cleared on shutdown, so a reload starts from nothing.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { PackageUpdate } from "./types.ts";
|
|
13
|
+
|
|
12
14
|
/** The store the header reads and the update lifecycle writes. */
|
|
13
15
|
export interface UpdateSource {
|
|
14
16
|
/** The newer published version, or `undefined` when none is known. */
|
|
@@ -25,8 +27,8 @@ export interface UpdateSource {
|
|
|
25
27
|
export type UpdateOutcome =
|
|
26
28
|
| { kind: "offline" }
|
|
27
29
|
| { kind: "failed" }
|
|
28
|
-
| { kind: "current"
|
|
29
|
-
| { kind: "available";
|
|
30
|
+
| { kind: "current" }
|
|
31
|
+
| { kind: "available"; updates: readonly PackageUpdate[]; ownLatest?: string };
|
|
30
32
|
|
|
31
33
|
/** A store holding at most one version. */
|
|
32
34
|
export function createUpdateState(): UpdateSource {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi suppresses its own package-update block while bytefly owns the surface.
|
|
3
|
+
*
|
|
4
|
+
* Pi draws `Package Updates Available` from `InteractiveMode.run()` after the
|
|
5
|
+
* session starts, with no setting or extension event to turn it off. The one
|
|
6
|
+
* public seam is the package manager it calls: replacing
|
|
7
|
+
* `checkForAvailableUpdates` with an empty result suppresses that block, while
|
|
8
|
+
* the captured original still serves bytefly's own query. The patch is
|
|
9
|
+
* process-wide, so it is installed at `session_start` and restored at
|
|
10
|
+
* `session_shutdown`.
|
|
11
|
+
*
|
|
12
|
+
* A future host that changes the method shape makes the patch a no-op rather
|
|
13
|
+
* than an error; the update surface then falls back to Pi's block.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { DefaultPackageManager } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { PackageUpdate, PackageUpdateCheck } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
/** A patchable class prototype, kept narrow so a test needs no host object. */
|
|
20
|
+
export interface PackageManagerPrototype {
|
|
21
|
+
checkForAvailableUpdates: PackageUpdateCheck;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The installed suppression: the real check, and how to restore Pi's method. */
|
|
25
|
+
export interface UpdateSuppression {
|
|
26
|
+
/** The original method, bound at call time so it still sees its manager. */
|
|
27
|
+
readonly check: PackageUpdateCheck;
|
|
28
|
+
/** Put Pi's method back; safe to call more than once. */
|
|
29
|
+
restore(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Marker for the installed patch, so a second registration reuses it. */
|
|
33
|
+
const SUPPRESSION = Symbol.for("@gavin-hu/my-pi-agent/pi-update-suppression");
|
|
34
|
+
|
|
35
|
+
interface MarkedPrototype extends PackageManagerPrototype {
|
|
36
|
+
[SUPPRESSION]?: UpdateSuppression;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Replace Pi's package-update check with one that reports nothing.
|
|
41
|
+
*
|
|
42
|
+
* `prototype` is a seam for tests; production passes Pi's
|
|
43
|
+
* `DefaultPackageManager.prototype`. When the method is missing, the returned
|
|
44
|
+
* suppression is inert and the original read stays `[]`.
|
|
45
|
+
*/
|
|
46
|
+
export function suppressPiPackageUpdates(
|
|
47
|
+
prototype: PackageManagerPrototype = DefaultPackageManager.prototype as PackageManagerPrototype,
|
|
48
|
+
): UpdateSuppression {
|
|
49
|
+
const marked = prototype as MarkedPrototype;
|
|
50
|
+
const existing = marked[SUPPRESSION];
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
|
|
53
|
+
const original = prototype.checkForAvailableUpdates;
|
|
54
|
+
if (typeof original !== "function") return { check: async () => [], restore: () => {} };
|
|
55
|
+
const originalCheck = original as unknown as (this: DefaultPackageManager) => Promise<PackageUpdate[]>;
|
|
56
|
+
const check: PackageUpdateCheck = (manager) => originalCheck.call(manager);
|
|
57
|
+
const patched: PackageUpdateCheck = async () => [];
|
|
58
|
+
const restore = (): void => {
|
|
59
|
+
if (prototype.checkForAvailableUpdates === patched) prototype.checkForAvailableUpdates = original;
|
|
60
|
+
delete marked[SUPPRESSION];
|
|
61
|
+
};
|
|
62
|
+
const suppression: UpdateSuppression = { check, restore };
|
|
63
|
+
prototype.checkForAvailableUpdates = patched;
|
|
64
|
+
marked[SUPPRESSION] = suppression;
|
|
65
|
+
return suppression;
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package-update shape Pi's manager returns.
|
|
3
|
+
*
|
|
4
|
+
* Pi exports the manager but not its `PackageUpdate` interface, so the type is
|
|
5
|
+
* derived from the public method. That keeps the extension on the same contract
|
|
6
|
+
* without reaching into a dist path or restating the fields.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { DefaultPackageManager } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
/** One package Pi reports as updateable. */
|
|
12
|
+
export type PackageUpdate = Awaited<ReturnType<DefaultPackageManager["checkForAvailableUpdates"]>>[number];
|
|
13
|
+
|
|
14
|
+
/** Pi's check, captured before suppression and called with a manager. */
|
|
15
|
+
export type PackageUpdateCheck = (manager: DefaultPackageManager) => Promise<PackageUpdate[]>;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tuning for bytefly's live working
|
|
2
|
+
* Tuning for bytefly's live working line.
|
|
3
3
|
*
|
|
4
|
-
* While Pi's working row is up, the
|
|
5
|
-
*
|
|
6
|
-
* the count advances.
|
|
4
|
+
* While Pi's working row is up, the message is written into the row beside Pi's
|
|
5
|
+
* spinner. Edit this file to change the words or how often the count advances.
|
|
7
6
|
*/
|
|
8
7
|
|
|
9
8
|
export const WORKING_CONFIG = {
|
|
10
|
-
/** Word before the count. */
|
|
9
|
+
/** Word before the count, before the first tool starts. */
|
|
11
10
|
working: "Working",
|
|
12
11
|
/** Between the word and the count. */
|
|
13
12
|
ellipsis: "…",
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure text for the live working
|
|
2
|
+
* Pure text for the live working line.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Before the first tool starts it is a plain count (`Working… 42s`). After that
|
|
5
|
+
* it names the last top-level tool and how many this run has started, then the
|
|
6
|
+
* elapsed time (`edit · 3 tools · 42s`). Whole seconds, because the tick
|
|
7
|
+
* advances once a second; negative and non-finite input reads as `0s`.
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
import { sanitizeLine, formatToolCount } from "../../../lib/format.ts";
|
|
11
|
+
import { SEPARATORS } from "../../../lib/tui/glyphs.ts";
|
|
9
12
|
import { WORKING_CONFIG } from "./config.ts";
|
|
10
13
|
|
|
14
|
+
/** What the current run has done so far, for the live line. */
|
|
15
|
+
export interface WorkingRun {
|
|
16
|
+
/** The last top-level tool started, or `null` before the first one. */
|
|
17
|
+
readonly tool: string | null;
|
|
18
|
+
/** Top-level tool calls started in this run. */
|
|
19
|
+
readonly tools: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
/** Compact elapsed time in whole seconds: `0s`, `42s`, `1m05s`, `1h02m`. */
|
|
12
23
|
export function formatElapsed(ms: number): string {
|
|
13
24
|
const total = Number.isFinite(ms) && ms > 0 ? Math.floor(ms / 1000) : 0;
|
|
@@ -19,7 +30,13 @@ export function formatElapsed(ms: number): string {
|
|
|
19
30
|
return `${seconds}s`;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
/** The working-row message
|
|
23
|
-
export function workingCounter(ms: number): string {
|
|
24
|
-
|
|
33
|
+
/** The working-row message: `Working… 4s` until a tool starts, then the run line. */
|
|
34
|
+
export function workingCounter(ms: number, run?: WorkingRun): string {
|
|
35
|
+
const elapsed = formatElapsed(ms);
|
|
36
|
+
const tools = run?.tools ?? 0;
|
|
37
|
+
const tool = run?.tool ? sanitizeLine(run.tool) : "";
|
|
38
|
+
if (tools > 0) {
|
|
39
|
+
return [tool, formatToolCount(tools), elapsed].filter((part) => part !== "").join(SEPARATORS.item);
|
|
40
|
+
}
|
|
41
|
+
return `${WORKING_CONFIG.working}${WORKING_CONFIG.ellipsis} ${elapsed}`;
|
|
25
42
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bytefly's live working
|
|
2
|
+
* bytefly's live working line.
|
|
3
3
|
*
|
|
4
4
|
* Pi's working row shows a static `Working`, so a long run reads the same as a
|
|
5
5
|
* short one. This surface advances the message once a second while the row is
|
|
6
|
-
* up, and hands it back to Pi when the row comes down.
|
|
6
|
+
* up, and hands it back to Pi when the row comes down. Once a top-level tool
|
|
7
|
+
* starts, the message also names that tool and how many this run has started,
|
|
8
|
+
* turning the row into a live run line (`edit · 3 tools · 12s`).
|
|
7
9
|
*
|
|
8
10
|
* The timer is tied to the row's visibility. The host shows the working
|
|
9
11
|
* indicator in its own `turn_start` handler and clears it at `agent_end`
|
|
@@ -12,6 +14,11 @@
|
|
|
12
14
|
* one run do not restart it: the host leaves the same indicator up across
|
|
13
15
|
* turns, and `start` is a no-op while it is counting.
|
|
14
16
|
*
|
|
17
|
+
* A nested tool call (one carrying `parentToolCallId`, such as a codemode
|
|
18
|
+
* script's inner call) is not counted and does not take over the line, so the
|
|
19
|
+
* number matches the tool blocks the transcript shows. The tool events repaint
|
|
20
|
+
* the row immediately; the one-second tick is still the only timer.
|
|
21
|
+
*
|
|
15
22
|
* Compaction and auto-retry also replace the row, but both run outside this
|
|
16
23
|
* window (`auto_retry_start` follows `agent_end`; the before-settle compaction
|
|
17
24
|
* runs after it), and neither is an extension event, so there is nothing to
|
|
@@ -49,6 +56,17 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
49
56
|
let startedAt = 0;
|
|
50
57
|
// A boolean, not `startedAt === 0`: an injected clock is allowed to start at 0.
|
|
51
58
|
let active = false;
|
|
59
|
+
// The last top-level tool and how many this run has started. Reset with the run.
|
|
60
|
+
let tool: string | null = null;
|
|
61
|
+
let tools = 0;
|
|
62
|
+
|
|
63
|
+
/** The current row text, from the run state and the elapsed time. */
|
|
64
|
+
const message = (): string => workingCounter(now() - startedAt, { tool, tools });
|
|
65
|
+
|
|
66
|
+
/** Repaint an active row now, so a tool start does not wait for the next tick. */
|
|
67
|
+
const refresh = (ctx: ExtensionContext): void => {
|
|
68
|
+
if (active) ctx.ui.setWorkingMessage(message());
|
|
69
|
+
};
|
|
52
70
|
|
|
53
71
|
/** Stop the tick and hand the working message back to Pi. Idempotent. */
|
|
54
72
|
const stop = (ctx: ExtensionContext): void => {
|
|
@@ -56,6 +74,8 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
56
74
|
cancel = undefined;
|
|
57
75
|
if (!active) return;
|
|
58
76
|
active = false;
|
|
77
|
+
tool = null;
|
|
78
|
+
tools = 0;
|
|
59
79
|
ctx.ui.setWorkingMessage(undefined);
|
|
60
80
|
};
|
|
61
81
|
|
|
@@ -64,8 +84,10 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
64
84
|
if (active) return;
|
|
65
85
|
startedAt = now();
|
|
66
86
|
active = true;
|
|
87
|
+
tool = null;
|
|
88
|
+
tools = 0;
|
|
67
89
|
const tick = (): void => {
|
|
68
|
-
ctx.ui.setWorkingMessage(
|
|
90
|
+
ctx.ui.setWorkingMessage(message());
|
|
69
91
|
};
|
|
70
92
|
tick();
|
|
71
93
|
cancel = schedule(tick, WORKING_CONFIG.intervalMs);
|
|
@@ -78,6 +100,15 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
|
|
|
78
100
|
start(ctx);
|
|
79
101
|
});
|
|
80
102
|
|
|
103
|
+
// A nested tool is an implementation detail of its parent run, not a
|
|
104
|
+
// transcript block, so it neither counts nor takes over the line.
|
|
105
|
+
pi.on("tool_execution_start", (event, ctx) => {
|
|
106
|
+
if (ctx.mode !== "tui" || !active || event.parentToolCallId) return;
|
|
107
|
+
tool = event.toolName;
|
|
108
|
+
tools += 1;
|
|
109
|
+
refresh(ctx);
|
|
110
|
+
});
|
|
111
|
+
|
|
81
112
|
// The host clears the working indicator at `agent_end`.
|
|
82
113
|
pi.on("agent_end", (_event, ctx) => {
|
|
83
114
|
if (ctx.mode !== "tui") return;
|