@gavin-hu/my-pi-agent 0.11.4 → 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 +38 -26
- package/README.zh-CN.md +33 -23
- package/extensions/bytefly/README.md +381 -225
- package/extensions/bytefly/chrome.ts +7 -2
- package/extensions/bytefly/doctor/register.ts +6 -0
- package/extensions/bytefly/doctor/runtime.ts +52 -6
- package/extensions/bytefly/doctor/tools.ts +1 -3
- 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/price/config.ts +54 -15
- package/extensions/bytefly/footer/price/price.ts +20 -7
- package/extensions/bytefly/footer/price/types.ts +13 -7
- package/extensions/bytefly/footer/register.ts +13 -2
- package/extensions/bytefly/footer/render/format.ts +3 -2
- package/extensions/bytefly/footer/render/lines.ts +2 -0
- package/extensions/bytefly/footer/render/segments.ts +30 -2
- package/extensions/bytefly/footer/types.ts +12 -0
- package/extensions/bytefly/header/component.ts +21 -6
- package/extensions/bytefly/header/config.ts +3 -0
- package/extensions/bytefly/header/register.ts +34 -3
- package/extensions/bytefly/index.ts +15 -4
- 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-runtime.ts +71 -28
- package/extensions/bytefly/panel/settings.ts +293 -48
- package/extensions/bytefly/panel/tui/entries.ts +58 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +86 -0
- package/extensions/bytefly/panel/tui/frame.ts +71 -0
- package/extensions/bytefly/panel/tui/nav.ts +75 -0
- package/extensions/bytefly/panel/tui/settings-view.ts +115 -0
- package/extensions/bytefly/panel/tui.ts +205 -319
- package/extensions/bytefly/picker/runtime.ts +61 -2
- package/extensions/bytefly/picker/toggle.ts +56 -0
- package/extensions/bytefly/update/cache.ts +73 -0
- package/extensions/bytefly/update/check.ts +59 -0
- package/extensions/bytefly/update/format.ts +87 -0
- package/extensions/bytefly/update/register.ts +158 -0
- package/extensions/bytefly/update/state.ts +40 -0
- package/extensions/bytefly/update/version.ts +83 -0
- package/extensions/document/README.md +24 -14
- package/extensions/document/config.ts +4 -0
- package/extensions/document/deadline.ts +61 -0
- package/extensions/document/tool.ts +14 -5
- 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/file-open/README.md +30 -15
- package/extensions/file-open/index.ts +4 -3
- package/extensions/file-open/open.ts +52 -21
- package/extensions/goal/README.md +45 -13
- package/extensions/goal/clarify-prompt.ts +5 -36
- package/extensions/goal/clarify.ts +55 -86
- package/extensions/goal/commands.ts +41 -35
- package/extensions/goal/format.ts +12 -4
- package/extensions/goal/schema.ts +6 -2
- package/extensions/goal/tools.ts +28 -8
- package/extensions/goal/tui.ts +1 -1
- 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 +56 -14
- 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 +83 -36
- package/extensions/plan/blocks.ts +1 -35
- package/extensions/plan/commands.ts +84 -19
- 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 +91 -2
- package/extensions/plan/policy.ts +4 -3
- package/extensions/plan/review.ts +246 -0
- package/extensions/plan/runtime.ts +73 -24
- package/extensions/plan/steps.ts +60 -39
- package/extensions/plan/todos.ts +29 -60
- package/extensions/plan/tools.ts +188 -244
- package/extensions/plan/tui.ts +18 -31
- package/extensions/plan/types.ts +12 -0
- package/extensions/plan/web-client.ts +51 -18
- package/extensions/plan/web-page.ts +7 -2
- package/extensions/plan/web-router.ts +11 -3
- package/extensions/plan/web.ts +106 -41
- package/extensions/question/README.md +1 -1
- package/extensions/question/tools.ts +1 -1
- 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 -3
- package/extensions/subagent/stream.ts +2 -0
- package/extensions/subagent/types.ts +2 -0
- package/extensions/todo/README.md +19 -11
- package/extensions/todo/index.ts +5 -3
- package/extensions/todo/nudge.ts +58 -9
- package/extensions/todo/runtime.ts +28 -17
- package/extensions/watch/README.md +8 -3
- package/extensions/watch/index.ts +19 -0
- package/extensions/web-access/README.md +46 -16
- 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 +20 -21
- package/extensions/web-access/http.ts +49 -8
- package/extensions/web-access/search/facts.ts +38 -0
- package/extensions/web-access/search/tool.ts +14 -3
- package/extensions/web-access/text.ts +50 -11
- package/extensions/wechat/README.md +5 -4
- package/extensions/wechat/tools.ts +0 -2
- 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 +12 -9
- package/lib/config.ts +43 -1
- package/lib/env.ts +12 -0
- package/lib/events.ts +86 -1
- package/lib/goal-state.ts +95 -0
- package/lib/requirements.ts +340 -46
- package/lib/todo-state.ts +62 -0
- package/lib/tui/README.md +91 -0
- package/lib/tui/glyphs.ts +2 -0
- package/lib/web/layout.ts +12 -2
- package/package.json +5 -3
- package/themes/bytefly-dark.json +1 -1
- package/assets/bytefly.schema.json +0 -785
- 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
|
@@ -28,12 +28,14 @@ pi install ./ # install the package for a user
|
|
|
28
28
|
## What it does
|
|
29
29
|
|
|
30
30
|
- Opens a file or directory with the OS-native application: `open` on macOS,
|
|
31
|
-
`
|
|
31
|
+
`explorer` on Windows, `xdg-open` on Linux.
|
|
32
32
|
- Reveals it in the file manager instead with `-r` (or `reveal: true`): `open -R`
|
|
33
33
|
on macOS, `explorer /select,` on Windows, and the containing directory on
|
|
34
34
|
Linux, which has no "select" verb.
|
|
35
35
|
- Unwraps the editor's `@` file-mention prefix and surrounding quotes, so
|
|
36
36
|
`/open @src/app.ts` and `/open @"my file.txt"` both work.
|
|
37
|
+
- Reports a launcher that cannot be started, and (on macOS and Linux) a launcher
|
|
38
|
+
that exits non-zero because no handler accepted the target.
|
|
37
39
|
- Resolves relative paths against the effective working directory, so under the
|
|
38
40
|
`worktree` extension `/open .` opens the isolated worktree.
|
|
39
41
|
- Launches the handler detached, so it outlives the Pi session.
|
|
@@ -47,14 +49,17 @@ pi install ./ # install the package for a user
|
|
|
47
49
|
|
|
48
50
|
The `-r` flag must come first. A missing path is reported through a notification
|
|
49
51
|
and nothing is launched. An empty argument means the working directory. The
|
|
50
|
-
command
|
|
52
|
+
command first reports `Opening <path>` (or `Revealing <path>`); a launcher that
|
|
53
|
+
starts but then fails adds a `Could not open` error notification. The command
|
|
54
|
+
needs an interactive terminal UI.
|
|
51
55
|
|
|
52
56
|
## Tool
|
|
53
57
|
|
|
54
58
|
`open_path` — `exposure: "codemode"`, inactive by default, not read-only,
|
|
55
|
-
open-world (`openWorldHint: true` because it starts an OS process). It is
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
open-world (`openWorldHint: true` because it starts an OS process). It is listed
|
|
60
|
+
by the `codemode` tool for a script to call as `tools.open_path(...)`, and an
|
|
61
|
+
extension can reach it through `ctx.executeTool`; it is not declared to the
|
|
62
|
+
model unless activated.
|
|
58
63
|
|
|
59
64
|
| Parameter | Type | Meaning |
|
|
60
65
|
|---|---|---|
|
|
@@ -68,10 +73,11 @@ Result (`details` / `structuredContent`):
|
|
|
68
73
|
| `target` | The absolute path that was handled. |
|
|
69
74
|
| `reveal` | Whether it was revealed rather than opened. |
|
|
70
75
|
|
|
71
|
-
The model-facing `content` is `Opened <target>` or `Revealed <target
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
and
|
|
76
|
+
The model-facing `content` is `Opened <target>` or `Revealed <target>` — the
|
|
77
|
+
handler was started, not proof that it succeeded. A path that does not exist is
|
|
78
|
+
thrown as an error naming the resolved path; a launcher that cannot be started,
|
|
79
|
+
or (on macOS and Linux) exits non-zero, is reported through `ctx.ui.notify`
|
|
80
|
+
because the handler runs detached, and the tool result still names the target.
|
|
75
81
|
|
|
76
82
|
## Behaviour by mode
|
|
77
83
|
|
|
@@ -94,12 +100,18 @@ JSON, and print modes; a codemode script can call it in any of them. Only the
|
|
|
94
100
|
- **Not confined.** Unlike `read_doc`, the tool opens any path on the machine:
|
|
95
101
|
confinement would defeat its purpose of showing the user a file wherever it
|
|
96
102
|
lives. Relative paths resolve against the effective worktree root.
|
|
97
|
-
- **Best-effort launch.** A
|
|
98
|
-
|
|
103
|
+
- **Best-effort launch.** A launcher that cannot be started is reported, and so
|
|
104
|
+
is a non-zero exit on macOS and Linux, where `open` and `xdg-open` return a
|
|
105
|
+
failure status when no handler accepts the target. Windows `explorer` can exit
|
|
106
|
+
non-zero after a successful open, so only a failure to start is reported
|
|
107
|
+
there. Nothing is retried.
|
|
99
108
|
|
|
100
109
|
## Limitations
|
|
101
110
|
|
|
102
111
|
- Reveal has no native meaning on Linux, so it opens the containing directory.
|
|
112
|
+
- On Windows a refused target is not reported: `explorer` does not relay the
|
|
113
|
+
failure and its exit status is unreliable, so `/open` and `open_path` can only
|
|
114
|
+
report that the launcher started.
|
|
103
115
|
- A launch that the OS accepts but the user never sees (a backgrounded app, a
|
|
104
116
|
hidden window) is not detected: `spawn` reports only that the process started.
|
|
105
117
|
- `-r` is parsed only as the first token of `/open`, so a path that genuinely
|
|
@@ -115,7 +127,7 @@ JSON, and print modes; a codemode script can call it in any of them. Only the
|
|
|
115
127
|
| Command | `/open [path]` and `/open -r [path]`; reports through `ctx.ui.notify`. |
|
|
116
128
|
| State | None persisted, and none needed: every call is independent. |
|
|
117
129
|
| Lifecycle | No `session_start` / `session_shutdown` hooks; the factory holds no long-lived resources. |
|
|
118
|
-
| Host seams | `resolveEffectiveCwd` and `isExtensionEnabled` (`lib/env.ts`), `stripControlChars` (`lib/format.ts`), `spawn` / `open` / `xdg-open` / `
|
|
130
|
+
| Host seams | `resolveEffectiveCwd` and `isExtensionEnabled` (`lib/env.ts`), `stripControlChars` (`lib/format.ts`), `spawn` / `open` / `xdg-open` / `explorer` (via the injected launcher in `open.ts`). |
|
|
119
131
|
| Config | None. |
|
|
120
132
|
|
|
121
133
|
## Design notes
|
|
@@ -123,8 +135,11 @@ JSON, and print modes; a codemode script can call it in any of them. Only the
|
|
|
123
135
|
`buildCommand` and `normalizeTarget` are pure and `openTarget` takes an injected
|
|
124
136
|
launcher, so the command for each platform is asserted without a process and a
|
|
125
137
|
test launches nothing. The launch is best-effort: `spawn` reports a missing
|
|
126
|
-
|
|
127
|
-
rather than being thrown from a synchronous call
|
|
138
|
+
launcher asynchronously and the child reports its exit, so both failures go to
|
|
139
|
+
`onError` (and a notification) rather than being thrown from a synchronous call
|
|
140
|
+
the tool could catch. `exitMeansFailure` opts Windows out because `explorer` can
|
|
141
|
+
exit non-zero after a successful open. Windows opens through `explorer` rather
|
|
142
|
+
than `cmd /c start` so cmd cannot re-parse a path containing `%`.
|
|
128
143
|
|
|
129
144
|
The tool is `codemode`-exposed, matching how it was configured before the move:
|
|
130
145
|
opening a native app is a side effect the user should trigger, so it stays out
|
|
@@ -135,7 +150,7 @@ of the model's active tool set unless a script explicitly reaches for it.
|
|
|
135
150
|
| Module | Responsibility |
|
|
136
151
|
|---|---|
|
|
137
152
|
| `index.ts` | The extension factory: registers `open_path` and the `/open` command, resolves the target, and routes a launch failure to `ctx.ui.notify`. |
|
|
138
|
-
| `open.ts` | `normalizeTarget` and `buildCommand` (pure), `resolveTarget` (existence check), and `openTarget` with the injectable launcher. |
|
|
153
|
+
| `open.ts` | `normalizeTarget` and `buildCommand` (pure), `resolveTarget` (existence check), and `openTarget` with the injectable launcher, which reports a start failure and a meaningful non-zero exit. |
|
|
139
154
|
| `schema.ts` | `TOOL_NAME`, the TypeBox `parameters` / `outputSchema`, and the `OpenPathArgs` / `OpenPathResult` types. |
|
|
140
155
|
| `open.test.ts` | Pure-module tests for normalization, resolution, per-platform commands, and the launcher seam. |
|
|
141
156
|
| `extension.test.ts` | Extension-surface tests: registration, disabling, the tool call, and the command. |
|
|
@@ -25,8 +25,9 @@ export default function fileOpen(pi: ExtensionAPI, deps: FileOpenDeps = {}): voi
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Resolve `raw` against the effective root and hand it to the native
|
|
28
|
-
* handler. A
|
|
29
|
-
*
|
|
28
|
+
* handler. A start failure, or a non-zero exit where the platform reports
|
|
29
|
+
* one, goes through `ctx.ui.notify` because the handler runs detached; the
|
|
30
|
+
* target is returned for the caller's message.
|
|
30
31
|
*/
|
|
31
32
|
const launch = (mode: OpenMode, raw: string, ctx: ExtensionContext): string => {
|
|
32
33
|
const root = resolveEffectiveCwd(ctx.cwd);
|
|
@@ -84,7 +85,7 @@ export default function fileOpen(pi: ExtensionAPI, deps: FileOpenDeps = {}): voi
|
|
|
84
85
|
const rest = trimmed.replace(/^-r\b\s*/, "").trim();
|
|
85
86
|
try {
|
|
86
87
|
const target = launch(reveal ? "reveal" : "open", rest, ctx);
|
|
87
|
-
ctx.ui.notify(`${reveal ? "
|
|
88
|
+
ctx.ui.notify(`${reveal ? "Revealing" : "Opening"} ${target}`, "info");
|
|
88
89
|
} catch (error) {
|
|
89
90
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
90
91
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* runs is asserted without a process. `openTarget` takes an injectable spawn so
|
|
6
6
|
* a test launches nothing. Opening is best-effort: the caller decides what to do
|
|
7
7
|
* with a failure, which is reported through `onError` rather than thrown, since
|
|
8
|
-
*
|
|
8
|
+
* the launcher reports a start failure and a non-zero exit asynchronously.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
@@ -19,6 +19,11 @@ export type OpenMode = "open" | "reveal";
|
|
|
19
19
|
export interface NativeCommand {
|
|
20
20
|
command: string;
|
|
21
21
|
args: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Whether a non-zero exit means the target was not opened. Windows
|
|
24
|
+
* `explorer` can exit non-zero after a successful open, so it opts out.
|
|
25
|
+
*/
|
|
26
|
+
exitMeansFailure: boolean;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
/**
|
|
@@ -51,49 +56,75 @@ export function buildCommand(
|
|
|
51
56
|
platform: NodeJS.Platform = process.platform,
|
|
52
57
|
): NativeCommand {
|
|
53
58
|
if (platform === "darwin") {
|
|
54
|
-
return mode === "reveal"
|
|
59
|
+
return mode === "reveal"
|
|
60
|
+
? { command: "open", args: ["-R", target], exitMeansFailure: true }
|
|
61
|
+
: { command: "open", args: [target], exitMeansFailure: true };
|
|
55
62
|
}
|
|
56
63
|
if (platform === "win32") {
|
|
64
|
+
// `explorer` opens with the default handler without routing the path
|
|
65
|
+
// through cmd, which would expand `%NAME%` inside it.
|
|
57
66
|
return mode === "reveal"
|
|
58
|
-
? { command: "explorer", args: [`/select,${target}`] }
|
|
59
|
-
: { command: "
|
|
67
|
+
? { command: "explorer", args: [`/select,${target}`], exitMeansFailure: false }
|
|
68
|
+
: { command: "explorer", args: [target], exitMeansFailure: false };
|
|
60
69
|
}
|
|
61
70
|
// Linux et al. xdg-open has no "select" verb, so reveal opens the parent.
|
|
62
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
command: "xdg-open",
|
|
73
|
+
args: [mode === "reveal" ? dirname(target) : target],
|
|
74
|
+
exitMeansFailure: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The child surface `openTarget` listens on; an injected launcher may return none. */
|
|
79
|
+
export interface LauncherChild {
|
|
80
|
+
on(event: "error", listener: (error: Error) => void): unknown;
|
|
81
|
+
on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
/** A launcher seam; the default spawns the command detached. */
|
|
66
|
-
export type SpawnFn = (command: string, args: string[]) =>
|
|
85
|
+
export type SpawnFn = (command: string, args: string[]) => LauncherChild | undefined;
|
|
67
86
|
|
|
68
87
|
export interface OpenTargetOptions {
|
|
69
88
|
/** Platform to build the command for; defaults to the running one. */
|
|
70
89
|
platform?: NodeJS.Platform;
|
|
71
90
|
/** Override the launcher (tests). */
|
|
72
91
|
spawn?: SpawnFn;
|
|
73
|
-
/** Called when the handler cannot
|
|
92
|
+
/** Called once when the handler cannot start, or exits non-zero where that is meaningful. */
|
|
74
93
|
onError?: (error: Error) => void;
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
/** Spawn the handler detached so the application outlives the session. */
|
|
97
|
+
const spawnDetached: SpawnFn = (command, args) => {
|
|
98
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
99
|
+
child.unref();
|
|
100
|
+
return child;
|
|
101
|
+
};
|
|
102
|
+
|
|
77
103
|
/** Start the native handler for `target`, detached so it outlives the session. */
|
|
78
104
|
export function openTarget(mode: OpenMode, target: string, options: OpenTargetOptions = {}): void {
|
|
79
|
-
const { command, args } = buildCommand(mode, target, options.platform);
|
|
80
|
-
const
|
|
105
|
+
const { command, args, exitMeansFailure } = buildCommand(mode, target, options.platform);
|
|
106
|
+
const launch = options.spawn ?? spawnDetached;
|
|
81
107
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
108
|
+
// A start failure and an exit both arrive asynchronously; report at most
|
|
109
|
+
// one, since both can fire for the same launch.
|
|
110
|
+
let reported = false;
|
|
111
|
+
const report = (error: Error): void => {
|
|
112
|
+
if (reported) return;
|
|
113
|
+
reported = true;
|
|
114
|
+
options.onError?.(error);
|
|
115
|
+
};
|
|
90
116
|
|
|
91
117
|
try {
|
|
92
|
-
const child =
|
|
93
|
-
child
|
|
94
|
-
child
|
|
118
|
+
const child = launch(command, args);
|
|
119
|
+
child?.on("error", report);
|
|
120
|
+
if (child && exitMeansFailure) {
|
|
121
|
+
child.on("exit", (code, signal) => {
|
|
122
|
+
if (signal) report(new Error(`the OS handler was stopped by ${signal}`));
|
|
123
|
+
else if (code !== 0) report(new Error(`the OS handler exited with status ${code}`));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
95
126
|
} catch (error) {
|
|
96
|
-
|
|
127
|
+
report(toError(error));
|
|
97
128
|
}
|
|
98
129
|
}
|
|
99
130
|
|
|
@@ -21,7 +21,11 @@ How the `goal` call and its result look in the transcript — the call line as a
|
|
|
21
21
|
objective. Send the complete objective every call; an empty objective clears
|
|
22
22
|
it.
|
|
23
23
|
- Tracks `status`: `active` while the goal is being pursued, `achieved` when it
|
|
24
|
-
is done.
|
|
24
|
+
is done. Achieving it while the branch's todo list still has open items names
|
|
25
|
+
the count in the result (`1 todo item is still open…`), so the model closes the
|
|
26
|
+
*how* instead of declaring the *what* done over an unfinished list. The count
|
|
27
|
+
is read from the branch through `lib/todo-state.ts`; the structured result is
|
|
28
|
+
unchanged.
|
|
25
29
|
- Shows a persistent one-line rail above the editor whenever a goal exists,
|
|
26
30
|
label-first to match the [`todo`](../todo/) widget, pinned at the top of the
|
|
27
31
|
`Goal / Todos` stack when both are loaded:
|
|
@@ -44,8 +48,9 @@ How the `goal` call and its result look in the transcript — the call line as a
|
|
|
44
48
|
- Understands the goal before recording it: `/goal <prompt>` asks the session
|
|
45
49
|
model to restate the goal as it understands it and, when it cannot yet, to ask
|
|
46
50
|
up to three questions — which the command asks directly through host dialogs
|
|
47
|
-
(`select` / `input`). The
|
|
48
|
-
restatement
|
|
51
|
+
(`select` / `input`). The answers are carried into the editor under the
|
|
52
|
+
restatement as `Also: <question> → <answer>` notes, and the user confirms,
|
|
53
|
+
folds them in, or corrects the goal there. `/goal set
|
|
49
54
|
<prompt>` reuses the loop but records without starting a turn, `/goal edit`
|
|
50
55
|
changes the current goal in place, and `/goal raw <prompt>` records the typed
|
|
51
56
|
words verbatim. The loop is best-effort and needs a dialog UI plus a model; the
|
|
@@ -79,8 +84,19 @@ line: control characters (including `ESC`) become spaces and any whitespace run
|
|
|
79
84
|
(newlines, tabs, repeated spaces) collapses to one space. This keeps the widget
|
|
80
85
|
to one logical line and stops model text from injecting terminal sequences.
|
|
81
86
|
|
|
87
|
+
A successful `status: "achieved"` call also appends one line to the model-facing
|
|
88
|
+
text when the branch's todo list still has open items — `3 todo items are still
|
|
89
|
+
open; close or clear the list if that work is done.` The list is replayed from
|
|
90
|
+
the branch, so the note appears whether the todos were written by the model, by
|
|
91
|
+
`/todos`, or by an approved plan. It is text only: `details`,
|
|
92
|
+
`structuredContent`, and the transcript header are unaffected.
|
|
93
|
+
|
|
82
94
|
`goal` is allowlisted in `plan-mode` (alongside `todo`), so the model can record
|
|
83
95
|
or update the objective during read-only exploration without leaving plan mode.
|
|
96
|
+
Plan mode also reads the goal: it restates an active one in its injected context,
|
|
97
|
+
and when a plan is approved with no goal at all it offers to record the plan's
|
|
98
|
+
title as the goal (`lib/goal-state.ts` is the reader). An existing goal — active
|
|
99
|
+
or achieved — is never replaced by that offer.
|
|
84
100
|
|
|
85
101
|
### `/goal` command
|
|
86
102
|
|
|
@@ -90,9 +106,9 @@ entry; `reconstructGoal` replays both entry kinds in branch order.
|
|
|
90
106
|
| Command | What it does |
|
|
91
107
|
|---|---|
|
|
92
108
|
| `/goal` | Show the current goal, or the command usage when none is set. |
|
|
93
|
-
| `/goal <prompt>` | Run the understanding loop — restate, ask any questions the model needs,
|
|
109
|
+
| `/goal <prompt>` | Run the understanding loop — restate, ask any questions the model needs, then confirm/edit in the editor, which carries the answers as notes — and start the run. Replacing a live goal is confirmed first. |
|
|
94
110
|
| `/goal raw <prompt>` | Skip the loop: record the typed words verbatim and start the run. |
|
|
95
|
-
| `/goal set <prompt>` (or `/goal edit <prompt>`) | The same loop, but record the goal without starting a run. |
|
|
111
|
+
| `/goal set <prompt>` (or `/goal edit <prompt>`) | The same loop, but record the goal without starting a run. An explicit `set`/`edit <text>` replaces an active goal without asking, since the command already says so. |
|
|
96
112
|
| `/goal set` (or `/goal edit`) | Open the editor prefilled with the current goal and save it as the active goal, without starting a run. |
|
|
97
113
|
| `/goal done` (or `/goal achieved`) | Mark the current goal achieved (idempotent). |
|
|
98
114
|
| `/goal clear` | Clear the goal. |
|
|
@@ -113,7 +129,7 @@ without one they show the usage instead. `/goal raw` never needs one.
|
|
|
113
129
|
|
|
114
130
|
The tool works in every mode. The persistent widget requires interactive
|
|
115
131
|
(`tui`) mode; the reminder and the `/goal` command work everywhere. The
|
|
116
|
-
`/goal <prompt>` runs the loop:
|
|
132
|
+
`/goal <prompt>` runs the loop: its model call uses a cancellable `tui` spinner
|
|
117
133
|
in interactive mode and runs directly in RPC; the questions and the confirm step
|
|
118
134
|
are host dialogs, forwarded by RPC. In print and JSON mode the loop is skipped
|
|
119
135
|
and the typed text is sent. The widget is always a single line; the transcript
|
|
@@ -166,8 +182,8 @@ depends on config.
|
|
|
166
182
|
| State storage | tool-result `details` plus a `pi.appendEntry("goal", { goal })` custom entry for command-set goals |
|
|
167
183
|
| Lifecycle hooks | `session_start` (load config, reconstruct), `session_tree` (reconstruct), `session_shutdown` (clear widget); `before_agent_start` (inject `goal-context`), `context` (filter stale/duplicate reminders) |
|
|
168
184
|
| UI | `ctx.ui.setWidget` one-line rail in `tui` mode; coordinated through `pi.events` so it stays the top rail |
|
|
169
|
-
| Model calls |
|
|
170
|
-
| Questions | When the goal is not yet understood, the command asks the model's questions through `ctx.ui.select` / `ctx.ui.input` and
|
|
185
|
+
| Model calls | One `ctx.modelRegistry.complete` call on the session model with a 30 s timeout and `cacheRetention: "none"`; skipped without a dialog UI or a model; its usage is not counted in session totals |
|
|
186
|
+
| Questions | When the goal is not yet understood, the command asks the model's questions through `ctx.ui.select` / `ctx.ui.input` and carries the answers into the editor prefill. No cross-extension tool is used. |
|
|
171
187
|
|
|
172
188
|
## Design notes
|
|
173
189
|
|
|
@@ -181,8 +197,10 @@ depends on config.
|
|
|
181
197
|
user-driven path: one comprehension call (`clarify-prompt.ts` + `clarify.ts`)
|
|
182
198
|
returns `{ understood, goal, questions }`. An understood goal opens a prefilled
|
|
183
199
|
`ctx.ui.editor`, then `pi.sendUserMessage` sets and runs it. A not-understood
|
|
184
|
-
goal has its questions asked through host dialogs and its answers
|
|
185
|
-
the
|
|
200
|
+
goal has its questions asked through host dialogs, and its answers ride into
|
|
201
|
+
the editor as `Also:` notes under the restatement: no second model call
|
|
202
|
+
rewrites the goal, so the user is the one who folds them into the sentence.
|
|
203
|
+
`/goal set`
|
|
186
204
|
reuses the loop but skips the send; a bare `/goal set` or `/goal edit` opens
|
|
187
205
|
the editor prefilled with the current goal; and `/goal raw` skips the loop
|
|
188
206
|
entirely. The loop is best-effort (a failure keeps the typed text) and a
|
|
@@ -214,6 +232,20 @@ depends on config.
|
|
|
214
232
|
the status header alone (`Goal · active`), because the call line above it
|
|
215
233
|
already carries the objective; it does not restate it. `achieved: "show"`
|
|
216
234
|
restores the dimmed widget line; config only affects presentation.
|
|
235
|
+
- **Completing the *what* checks the *how*.** Marking a goal achieved while the
|
|
236
|
+
todo list still holds open items appends one line to the model-facing result
|
|
237
|
+
naming the count. The list is replayed from the branch by
|
|
238
|
+
`lib/todo-state.ts` (shared with plan's approval-time merge, which reads the
|
|
239
|
+
same channel), never by importing the `todo` extension. The same reasoning
|
|
240
|
+
applies to the goal itself: `lib/goal-state.ts` owns the entry type and the
|
|
241
|
+
objective limit, and plan reads the published goal through it. Only the text is
|
|
242
|
+
affected: `details` and `structuredContent` stay the goal alone, and the
|
|
243
|
+
transcript result is still the status header.
|
|
244
|
+
- **One verb table.** `GOAL_VERBS` is the only list of reserved words: the
|
|
245
|
+
parser looks a first word up in it, the completions are generated from it, and
|
|
246
|
+
a test asserts every word is offered as a completion and named in `GOAL_HELP`.
|
|
247
|
+
Adding a verb without documenting or completing it fails that test instead of
|
|
248
|
+
drifting silently.
|
|
217
249
|
- **Sanitize at the boundary.** `normalizeGoal` and `parseClarification` both route
|
|
218
250
|
model text through `lib/format.ts`'s `sanitizeLine`, so an embedded newline
|
|
219
251
|
cannot corrupt the widget and raw escapes cannot restyle the terminal.
|
|
@@ -225,16 +257,16 @@ depends on config.
|
|
|
225
257
|
| `index.ts` | Factory: wire the runtime, tool, command, reminder, and session events. |
|
|
226
258
|
| `types.ts` | `Goal`, `GoalStatus`, `GoalDetails`. |
|
|
227
259
|
| `schema.ts` | TypeBox parameters and pure validation/normalization. |
|
|
228
|
-
| `clarify-prompt.ts` |
|
|
260
|
+
| `clarify-prompt.ts` | The comprehension prompt and its message builder (pure). |
|
|
229
261
|
| `clarify-parse.ts` | Parse the `{ understood, goal, questions }` reply (and the plain-text fallback), normalize questions, and expose the limits (pure). |
|
|
230
|
-
| `clarify.ts` | The
|
|
262
|
+
| `clarify.ts` | The model call, its cancellable loader, the in-command questionnaire, and the confirm/edit dialog (host). |
|
|
231
263
|
| `state.ts` | Branch reconstruction (pure). |
|
|
232
264
|
| `format.ts` | Model-facing and terminal text plus the header and rail vocabulary (pure). |
|
|
233
265
|
| `config.ts` | Widget config load/validation (the `goal` section of `bytefly.json`). |
|
|
234
266
|
| `tui.ts` | The persistent goal widget and the transcript status header. |
|
|
235
267
|
| `runtime.ts` | Session-scoped state and widget synchronization. |
|
|
236
268
|
| `tools.ts` | `goal` tool registration and rendering. |
|
|
237
|
-
| `commands.ts` | `/goal
|
|
269
|
+
| `commands.ts` | `/goal`: the `GOAL_VERBS` table, the argument parser, the completions, and the command handler. |
|
|
238
270
|
|
|
239
271
|
## Testing
|
|
240
272
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Prompt text for the goal comprehension step (pure).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* final restatement.
|
|
4
|
+
* Sent only on the nested clarifier call, so it is not part of Pi's always-on
|
|
5
|
+
* prompt budget. The model's job is to understand and restate the user's goal —
|
|
6
|
+
* not to improve it — and, when it cannot yet, to ask only what it needs from
|
|
7
|
+
* the user.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { UserMessage } from "@earendil-works/pi-ai";
|
|
12
|
-
import { MAX_QUESTIONS
|
|
11
|
+
import { MAX_QUESTIONS } from "./clarify-parse.ts";
|
|
13
12
|
|
|
14
13
|
/** The comprehension system prompt: understand the goal, ask only what is missing. */
|
|
15
14
|
export const CLARIFY_SYSTEM_PROMPT = `You are establishing a shared understanding of the user's goal for this session.
|
|
@@ -33,12 +32,6 @@ Rules:
|
|
|
33
32
|
- Ask only what only the user can answer: purpose, scope, preference, or priority. Never ask for a fact the work itself would reveal, and never ask the user to restate the goal.
|
|
34
33
|
- Give a question 2-4 concrete options when the answers are a small known set, or no options at all when free-form.`;
|
|
35
34
|
|
|
36
|
-
/** The re-comprehension system prompt: fold the answers into one goal statement. */
|
|
37
|
-
export const REFINE_SYSTEM_PROMPT = `The user has answered questions about their goal. Restate the goal as you now understand it.
|
|
38
|
-
|
|
39
|
-
Reply with ONLY the goal as one sentence, no prose and no code fence.
|
|
40
|
-
Use their answers and the original request; add nothing they did not state.`;
|
|
41
|
-
|
|
42
35
|
/** The user message sent to the clarifier alongside {@link CLARIFY_SYSTEM_PROMPT}. */
|
|
43
36
|
export function buildClarifyUserText(text: string): string {
|
|
44
37
|
return `Understand this session goal:\n\n${text}`;
|
|
@@ -54,27 +47,3 @@ export function buildClarifyMessages(
|
|
|
54
47
|
messages: [{ role: "user", content: [{ type: "text", text: buildClarifyUserText(text) }], timestamp: now }],
|
|
55
48
|
};
|
|
56
49
|
}
|
|
57
|
-
|
|
58
|
-
/** The user message that carries the original request, the first restatement, and the answers. */
|
|
59
|
-
export function buildRefineUserText(original: string, goal: string, answers: readonly ClarifyAnswer[]): string {
|
|
60
|
-
const lines = [`Original request:\n${original}`];
|
|
61
|
-
if (goal) lines.push(`First restatement: ${goal}`);
|
|
62
|
-
lines.push("The user was asked, and answered:");
|
|
63
|
-
for (const item of answers) lines.push(`- ${item.question}\n ${item.answer}`);
|
|
64
|
-
return lines.join("\n");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** The request sent to the clarifier after the user answers. */
|
|
68
|
-
export function buildRefineMessages(
|
|
69
|
-
original: string,
|
|
70
|
-
goal: string,
|
|
71
|
-
answers: readonly ClarifyAnswer[],
|
|
72
|
-
now = Date.now(),
|
|
73
|
-
): { systemPrompt: string; messages: UserMessage[] } {
|
|
74
|
-
return {
|
|
75
|
-
systemPrompt: REFINE_SYSTEM_PROMPT,
|
|
76
|
-
messages: [
|
|
77
|
-
{ role: "user", content: [{ type: "text", text: buildRefineUserText(original, goal, answers) }], timestamp: now },
|
|
78
|
-
],
|
|
79
|
-
};
|
|
80
|
-
}
|