@gavin-hu/my-pi-agent 0.8.1 → 0.10.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.
Files changed (174) hide show
  1. package/README.md +35 -8
  2. package/README.zh-CN.md +33 -8
  3. package/assets/bytefly.schema.json +17 -1
  4. package/assets/chrome.png +0 -0
  5. package/assets/file-browse.png +0 -0
  6. package/assets/job.png +0 -0
  7. package/assets/plan-review.png +0 -0
  8. package/assets/question.png +0 -0
  9. package/assets/rewind.png +0 -0
  10. package/assets/todo.png +0 -0
  11. package/assets/tool-renderings.png +0 -0
  12. package/assets/tools/ask_user_question.png +0 -0
  13. package/assets/tools/bash.png +0 -0
  14. package/assets/tools/edit.png +0 -0
  15. package/assets/tools/enter_plan_mode.png +0 -0
  16. package/assets/tools/enter_worktree.png +0 -0
  17. package/assets/tools/exit_plan_mode.png +0 -0
  18. package/assets/tools/exit_worktree.png +0 -0
  19. package/assets/tools/find.png +0 -0
  20. package/assets/tools/goal.png +0 -0
  21. package/assets/tools/grep.png +0 -0
  22. package/assets/tools/job.png +0 -0
  23. package/assets/tools/list_worktrees.png +0 -0
  24. package/assets/tools/ls.png +0 -0
  25. package/assets/tools/memory.png +0 -0
  26. package/assets/tools/notice.png +0 -0
  27. package/assets/tools/open_path.png +0 -0
  28. package/assets/tools/prune_worktrees.png +0 -0
  29. package/assets/tools/read.png +0 -0
  30. package/assets/tools/read_doc.png +0 -0
  31. package/assets/tools/run_doctor.png +0 -0
  32. package/assets/tools/send_wechat.png +0 -0
  33. package/assets/tools/set_plan_steps.png +0 -0
  34. package/assets/tools/subagent.png +0 -0
  35. package/assets/tools/todo.png +0 -0
  36. package/assets/tools/turn-rule.png +0 -0
  37. package/assets/tools/user-message.png +0 -0
  38. package/assets/tools/watch.png +0 -0
  39. package/assets/tools/web_fetch.png +0 -0
  40. package/assets/tools/web_search.png +0 -0
  41. package/assets/tools/write.png +0 -0
  42. package/assets/tools/write_plan.png +0 -0
  43. package/extensions/bytefly/README.md +306 -68
  44. package/extensions/bytefly/block/component.ts +282 -0
  45. package/extensions/bytefly/block/config.ts +24 -0
  46. package/extensions/bytefly/block/fallback.ts +120 -0
  47. package/extensions/bytefly/block/register.ts +164 -0
  48. package/extensions/bytefly/chrome.ts +97 -0
  49. package/extensions/bytefly/doctor/format.ts +36 -10
  50. package/extensions/bytefly/doctor/tools.ts +10 -6
  51. package/extensions/bytefly/footer/config.ts +1 -1
  52. package/extensions/bytefly/footer/render/format.ts +5 -18
  53. package/extensions/bytefly/footer/render/layout.ts +2 -1
  54. package/extensions/bytefly/footer/render/segments.ts +51 -78
  55. package/extensions/bytefly/header/component.ts +49 -21
  56. package/extensions/bytefly/header/config.ts +28 -21
  57. package/extensions/bytefly/header/register.ts +6 -1
  58. package/extensions/bytefly/index.ts +32 -12
  59. package/extensions/bytefly/message/config.ts +15 -0
  60. package/extensions/bytefly/message/format.ts +42 -0
  61. package/extensions/bytefly/message/register.ts +15 -0
  62. package/extensions/bytefly/notice/component.ts +107 -0
  63. package/extensions/bytefly/notice/config.ts +41 -0
  64. package/extensions/bytefly/notice/register.ts +27 -0
  65. package/extensions/bytefly/pad.ts +42 -0
  66. package/extensions/bytefly/panel/commands.ts +69 -35
  67. package/extensions/bytefly/panel/format.ts +8 -8
  68. package/extensions/bytefly/panel/tui.ts +45 -70
  69. package/extensions/bytefly/separator/component.ts +2 -1
  70. package/extensions/bytefly/separator/config.ts +16 -8
  71. package/extensions/bytefly/separator/format.ts +18 -38
  72. package/extensions/bytefly/separator/register.ts +3 -2
  73. package/extensions/bytefly/separator/types.ts +0 -10
  74. package/extensions/bytefly/working/config.ts +20 -0
  75. package/extensions/bytefly/working/format.ts +25 -0
  76. package/extensions/bytefly/working/register.ts +88 -0
  77. package/extensions/document/README.md +12 -5
  78. package/extensions/document/paging.ts +10 -10
  79. package/extensions/document/render.ts +17 -16
  80. package/extensions/document/tool.ts +1 -1
  81. package/extensions/file-browse/README.md +54 -10
  82. package/extensions/file-browse/client.ts +110 -2
  83. package/extensions/file-browse/config.ts +12 -0
  84. package/extensions/file-browse/diff.ts +187 -0
  85. package/extensions/file-browse/git.ts +180 -8
  86. package/extensions/file-browse/html.ts +401 -25
  87. package/extensions/file-browse/index.ts +15 -4
  88. package/extensions/file-browse/markdown.ts +33 -0
  89. package/extensions/file-browse/router.ts +94 -2
  90. package/extensions/file-browse/server.ts +4 -1
  91. package/extensions/file-open/README.md +6 -0
  92. package/extensions/goal/README.md +16 -10
  93. package/extensions/goal/format.ts +13 -25
  94. package/extensions/goal/tools.ts +11 -22
  95. package/extensions/goal/tui.ts +12 -40
  96. package/extensions/job/README.md +22 -13
  97. package/extensions/job/format.ts +36 -13
  98. package/extensions/job/index.ts +26 -21
  99. package/extensions/job/tools.ts +15 -12
  100. package/extensions/job/tui.ts +148 -222
  101. package/extensions/job/ui.ts +24 -49
  102. package/extensions/memory/README.md +7 -1
  103. package/extensions/memory/format.ts +5 -3
  104. package/extensions/memory/tools.ts +4 -3
  105. package/extensions/memory/tui.ts +32 -36
  106. package/extensions/plan/README.md +86 -36
  107. package/extensions/plan/blocks.ts +44 -2
  108. package/extensions/plan/feedback.ts +20 -5
  109. package/extensions/plan/index.ts +1 -1
  110. package/extensions/plan/runtime.ts +3 -2
  111. package/extensions/plan/tools.ts +43 -27
  112. package/extensions/plan/tui.ts +21 -27
  113. package/extensions/plan/web-client.ts +386 -40
  114. package/extensions/plan/web-page.ts +138 -33
  115. package/extensions/plan/web-router.ts +46 -13
  116. package/extensions/plan/web.ts +63 -12
  117. package/extensions/question/README.md +14 -2
  118. package/extensions/question/answers.ts +25 -18
  119. package/extensions/question/tools.ts +22 -16
  120. package/extensions/question/tui.ts +132 -76
  121. package/extensions/rewind/README.md +3 -1
  122. package/extensions/rewind/format.ts +35 -2
  123. package/extensions/rewind/index.ts +3 -1
  124. package/extensions/rewind/rewind-tui.ts +66 -62
  125. package/extensions/rewind/snapshot-run.ts +1 -1
  126. package/extensions/rewind/status.ts +3 -2
  127. package/extensions/subagent/README.md +6 -0
  128. package/extensions/subagent/index.ts +1 -1
  129. package/extensions/subagent/render.ts +108 -66
  130. package/extensions/todo/README.md +20 -13
  131. package/extensions/todo/format.ts +47 -34
  132. package/extensions/todo/tools.ts +6 -4
  133. package/extensions/todo/tui.ts +46 -26
  134. package/extensions/watch/README.md +16 -8
  135. package/extensions/watch/format.ts +33 -13
  136. package/extensions/watch/index.ts +29 -24
  137. package/extensions/watch/tools.ts +16 -9
  138. package/extensions/watch/tui.ts +142 -196
  139. package/extensions/watch/ui.ts +24 -46
  140. package/extensions/web-access/README.md +16 -7
  141. package/extensions/web-access/fetch/tool.ts +12 -11
  142. package/extensions/web-access/search/tool.ts +16 -13
  143. package/extensions/wechat/README.md +8 -2
  144. package/extensions/wechat/index.ts +4 -3
  145. package/extensions/wechat/login-screen.ts +8 -5
  146. package/extensions/wechat/status-screen.ts +41 -22
  147. package/extensions/wechat/tools.ts +6 -5
  148. package/extensions/worktree/README.md +6 -0
  149. package/extensions/worktree/enter.ts +1 -1
  150. package/extensions/worktree/index.ts +1 -1
  151. package/extensions/worktree/runtime.ts +1 -1
  152. package/lib/README.md +11 -7
  153. package/lib/format.ts +17 -0
  154. package/lib/notice.ts +87 -0
  155. package/lib/requirements.ts +2 -2
  156. package/lib/{tui.ts → tui/chrome.ts} +17 -34
  157. package/lib/tui/columns.ts +96 -0
  158. package/lib/{list-cursor.ts → tui/cursor.ts} +58 -4
  159. package/lib/tui/gauge.ts +78 -0
  160. package/lib/{ui.ts → tui/glyphs.ts} +10 -11
  161. package/lib/tui/indent.ts +166 -0
  162. package/lib/tui/index.ts +27 -0
  163. package/lib/tui/line-pane.ts +173 -0
  164. package/lib/tui/rows.ts +143 -0
  165. package/lib/tui/rules.ts +94 -0
  166. package/lib/tui/status-controller.ts +107 -0
  167. package/lib/tui/tokens.ts +72 -0
  168. package/lib/web/base.ts +2 -1
  169. package/lib/web/components.ts +3 -1
  170. package/lib/web/layout.ts +5 -1
  171. package/package.json +7 -3
  172. package/themes/bytefly-dark.json +7 -8
  173. package/themes/bytefly-light.json +10 -11
  174. package/assets/bytefly-chrome.png +0 -0
@@ -14,7 +14,8 @@
14
14
 
15
15
  import type { Theme } from "@earendil-works/pi-coding-agent";
16
16
  import { truncateToWidth } from "@earendil-works/pi-tui";
17
- import { GLYPHS } from "../../lib/ui.ts";
17
+ import { GLYPHS, SEPARATORS } from "../../lib/tui/glyphs.ts";
18
+ import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
18
19
  import type { JobRecord, JobStatus } from "./types.ts";
19
20
 
20
21
  /** Longest sanitized log line kept for display; longer lines are clipped. */
@@ -76,22 +77,43 @@ export function elapsedMs(job: JobRecord, now: number): number {
76
77
  return (job.finishedAt ?? now) - job.startedAt;
77
78
  }
78
79
 
79
- /** Width-1 themed glyph for a status. */
80
- export function statusGlyph(status: JobStatus, theme: Theme): string {
80
+ /** The design role a job status is drawn in. */
81
+ export function statusRole(status: JobStatus): TuiRole {
81
82
  switch (status) {
82
83
  case "exited":
83
- return theme.fg("success", GLYPHS.check);
84
+ return "ok";
84
85
  case "failed":
85
- return theme.fg("error", GLYPHS.jobsFailure);
86
+ return "bad";
86
87
  case "killed":
87
- return theme.fg("muted", GLYPHS.killed);
88
+ return "meta";
88
89
  case "unknown":
89
- return theme.fg("dim", GLYPHS.unknown);
90
+ return "faint";
90
91
  default:
91
- return theme.fg("accent", GLYPHS.jobsRunning);
92
+ return "accent";
92
93
  }
93
94
  }
94
95
 
96
+ /** Width-1 glyph for a status, from the shared vocabulary. */
97
+ export function statusGlyphChar(status: JobStatus): string {
98
+ switch (status) {
99
+ case "exited":
100
+ return GLYPHS.check;
101
+ case "failed":
102
+ return GLYPHS.jobsFailure;
103
+ case "killed":
104
+ return GLYPHS.killed;
105
+ case "unknown":
106
+ return GLYPHS.unknown;
107
+ default:
108
+ return GLYPHS.jobsRunning;
109
+ }
110
+ }
111
+
112
+ /** Width-1 themed glyph for a status. */
113
+ export function statusGlyph(status: JobStatus, theme: Theme): string {
114
+ return paint(theme, statusRole(status), statusGlyphChar(status));
115
+ }
116
+
95
117
  /** Short status word for text output. */
96
118
  function statusWord(job: JobRecord): string {
97
119
  if (job.status === "failed" && job.exitCode !== null) return `failed (exit ${job.exitCode})`;
@@ -229,18 +251,19 @@ export function jobOutcome(job: JobRecord): string {
229
251
 
230
252
  /** One-line outcome for a single job, themed for the transcript result. */
231
253
  export function formatJobOutcomeLine(job: JobRecord, theme: Theme, now = Date.now()): string {
232
- const sep = theme.fg("dim", " · ");
233
- const outcome = job.status === "failed" ? theme.fg("error", jobOutcome(job)) : theme.fg("dim", jobOutcome(job));
254
+ const sep = paint(theme, "faint", SEPARATORS.item);
255
+ const outcome =
256
+ job.status === "failed" ? paint(theme, "bad", jobOutcome(job)) : paint(theme, "faint", jobOutcome(job));
234
257
  return (
235
258
  statusGlyph(job.status, theme) +
236
259
  " " +
237
- theme.fg("accent", job.id) +
260
+ paint(theme, "accent", job.id) +
238
261
  " " +
239
- theme.fg("muted", shortLabel(job)) +
262
+ paint(theme, "meta", shortLabel(job)) +
240
263
  sep +
241
264
  outcome +
242
265
  sep +
243
- theme.fg("dim", formatDuration(elapsedMs(job, now)))
266
+ paint(theme, "faint", formatDuration(elapsedMs(job, now)))
244
267
  );
245
268
  }
246
269
 
@@ -4,18 +4,19 @@
4
4
  * Registers the `job` tool and `/jobs` command. Jobs run as detached shell
5
5
  * processes with their output streamed to log files; the status chip shows
6
6
  * running and unreported-failure counts, and finished jobs are reported to the
7
- * model at the next turn (or, for `wake` jobs, by triggering one turn).
7
+ * model — and shown as a bytefly notice — at the next turn, or by triggering one
8
+ * turn for a `wake` job.
8
9
  *
9
10
  * Load with: pi --extension ./extensions/job
10
11
  */
11
12
 
12
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
13
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
14
14
  import { isExtensionEnabled } from "../../lib/env.ts";
15
15
  import { JOB_FINISHED_CHANNEL, type JobFinishedEvent } from "../../lib/events.ts";
16
- import { STATUS_KEYS } from "../../lib/ui.ts";
16
+ import { noticeMessage, type NoticeMessage, pruneStaleNotices } from "../../lib/notice.ts";
17
+ import { STATUS_KEYS } from "../../lib/tui/glyphs.ts";
17
18
  import { registerCommands } from "./commands.ts";
18
- import { formatCompletion } from "./format.ts";
19
+ import { formatCompletion, jobOutcome, shortLabel } from "./format.ts";
19
20
  import { createJobsRuntime, type JobsRuntime } from "./runtime.ts";
20
21
  import { registerTools } from "./tools.ts";
21
22
  import type { Job, JobRecord } from "./types.ts";
@@ -23,16 +24,26 @@ import type { Job, JobRecord } from "./types.ts";
23
24
  /** Marker embedded in the injected completion context. */
24
25
  const JOB_CONTEXT_MARKER = "[BACKGROUND JOBS]";
25
26
 
26
- /** Custom-entry type used for the injected completion message. */
27
- export const JOB_CONTEXT_TYPE = "job-context";
28
-
29
27
  function buildContext(jobs: JobRecord[]): string {
30
28
  const lines = jobs.map((job) => formatCompletion(job));
31
29
  return `${JOB_CONTEXT_MARKER}\n${jobs.length} background job${jobs.length === 1 ? "" : "s"} finished:\n${lines.join("\n")}\nRun \`job logs <id>\` for output.`;
32
30
  }
33
31
 
34
- function isJobContext(message: AgentMessage): boolean {
35
- return (message as AgentMessage & { customType?: string }).customType === JOB_CONTEXT_TYPE;
32
+ /** The shared notice for pending completions: model text plus renderer details. */
33
+ function jobNotice(jobs: JobRecord[]): NoticeMessage {
34
+ const count = jobs.length;
35
+ return noticeMessage({
36
+ content: buildContext(jobs),
37
+ details: {
38
+ source: "job",
39
+ title: `${count} background job${count === 1 ? "" : "s"} finished`,
40
+ items: jobs.map((job) => ({
41
+ label: `${job.id} ${shortLabel(job)}`,
42
+ detail: jobOutcome(job),
43
+ severity: job.status === "failed" ? "bad" : job.status === "exited" ? "ok" : "warn",
44
+ })),
45
+ },
46
+ });
36
47
  }
37
48
 
38
49
  /** Trim a finished record to the fields the event bus carries. */
@@ -67,10 +78,7 @@ export default function jobs(pi: ExtensionAPI, deps: JobsDeps = {}): void {
67
78
  const pending = runtime.pendingCompletions();
68
79
  if (pending.length === 0) return;
69
80
  try {
70
- pi.sendMessage(
71
- { customType: JOB_CONTEXT_TYPE, content: buildContext(pending), display: false },
72
- { triggerTurn: true },
73
- );
81
+ pi.sendMessage(jobNotice(pending), { triggerTurn: true });
74
82
  // Mark only once the send succeeded; a throw leaves them for the next turn.
75
83
  runtime.markReported(pending.map((job) => job.id));
76
84
  } catch {
@@ -113,17 +121,14 @@ export default function jobs(pi: ExtensionAPI, deps: JobsDeps = {}): void {
113
121
  if (pending.length === 0) return undefined;
114
122
  runtime.markReported(pending.map((job) => job.id));
115
123
  runtime.setStatus(ctx);
116
- return {
117
- message: { customType: JOB_CONTEXT_TYPE, content: buildContext(pending), display: false },
118
- };
124
+ return { message: jobNotice(pending) };
119
125
  });
120
126
 
121
- // Keep stale completion notes out of later turns.
127
+ // Keep stale completion notices out of later turns. Only this extension's
128
+ // notices are pruned; another source's are left for its own handler.
122
129
  pi.on("context", (event) => {
123
- const contexts = event.messages.filter(isJobContext);
124
- if (contexts.length <= 1) return undefined;
125
- const last = contexts[contexts.length - 1];
126
- return { messages: event.messages.filter((message) => !isJobContext(message) || message === last) };
130
+ const pruned = pruneStaleNotices(event.messages, "job");
131
+ return pruned ? { messages: pruned } : undefined;
127
132
  });
128
133
 
129
134
  pi.on("session_shutdown", async (_event, ctx) => {
@@ -9,7 +9,9 @@
9
9
 
10
10
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
11
  import { sanitizeLine } from "../../lib/format.ts";
12
- import { reuseText, toolDetails } from "../../lib/tui.ts";
12
+ import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
13
+ import { SEPARATORS } from "../../lib/tui/glyphs.ts";
14
+ import { paint } from "../../lib/tui/tokens.ts";
13
15
  import {
14
16
  compareJobs,
15
17
  formatCallText,
@@ -208,8 +210,9 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
208
210
  const text = reuseText(context.lastComponent);
209
211
  text.setText(
210
212
  theme.fg("toolTitle", theme.bold(`${TOOL_NAME} `)) +
211
- theme.fg(
212
- "muted",
213
+ paint(
214
+ theme,
215
+ "meta",
213
216
  sanitizeLine(formatCallText(action, args as Record<string, unknown>, context.argsComplete)),
214
217
  ),
215
218
  );
@@ -225,20 +228,20 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
225
228
  return text;
226
229
  }
227
230
  if (details.error) {
228
- text.setText(theme.fg("error", `Error: ${sanitizeLine(details.error)}`));
231
+ text.setText(paint(theme, "bad", `Error: ${sanitizeLine(details.error)}`));
229
232
  return text;
230
233
  }
231
234
 
232
235
  if (options.isPartial) {
233
236
  const id = details.job?.id;
234
- text.setText(theme.fg("warning", id ? `Waiting on ${id}…` : "Waiting…"));
237
+ text.setText(paint(theme, "warn", id ? `Waiting on ${id}…` : "Waiting…"));
235
238
  return text;
236
239
  }
237
240
 
238
241
  if (details.action === "logs" && details.logs !== undefined) {
239
242
  const all = details.logs === "" ? [] : details.logs.split("\n");
240
243
  if (all.length === 0) {
241
- text.setText(theme.fg("muted", "No output yet."));
244
+ text.setText(paint(theme, "meta", "No output yet."));
242
245
  return text;
243
246
  }
244
247
  const shown = options.expanded ? all : all.slice(Math.max(0, all.length - RESULT_ROWS));
@@ -250,7 +253,7 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
250
253
 
251
254
  if (details.action === "list" && details.jobs) {
252
255
  if (details.jobs.length === 0) {
253
- text.setText(theme.fg("muted", "No jobs."));
256
+ text.setText(paint(theme, "meta", "No jobs."));
254
257
  return text;
255
258
  }
256
259
  const ordered = [...details.jobs].sort(compareJobs);
@@ -264,7 +267,7 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
264
267
  if (details.action === "clear") {
265
268
  const count = details.cleared ?? 0;
266
269
  text.setText(
267
- theme.fg("muted", count === 0 ? "Nothing to clear." : `Cleared ${count} job${count === 1 ? "" : "s"}.`),
270
+ paint(theme, "meta", count === 0 ? "Nothing to clear." : `Cleared ${count} job${count === 1 ? "" : "s"}.`),
268
271
  );
269
272
  return text;
270
273
  }
@@ -272,16 +275,16 @@ export function registerTools(pi: ExtensionAPI, runtime: JobsRuntime): void {
272
275
  if (details.job) {
273
276
  let line = formatJobOutcomeLine(details.job, theme);
274
277
  if (details.action === "wait" && details.timedOut) {
275
- line += theme.fg("warning", " · still running after the timeout");
278
+ line += paint(theme, "warn", " · still running after the timeout");
276
279
  } else if (details.action === "wait" && details.cancelled) {
277
- line += theme.fg("warning", " · wait cancelled; job still running");
280
+ line += paint(theme, "warn", " · wait cancelled; job still running");
278
281
  } else if (details.action === "kill" && details.signalled) {
279
- line += theme.fg("warning", ` · ${details.signal ?? "SIGTERM"} sent`);
282
+ line += paint(theme, "warn", ` · ${details.signal ?? "SIGTERM"} sent`);
280
283
  } else if (details.action === "start") {
281
284
  const badges: string[] = [];
282
285
  if (details.job.detached) badges.push("detached");
283
286
  if (details.job.wake) badges.push("wake");
284
- if (badges.length > 0) line += theme.fg("dim", ` · ${badges.join(" · ")}`);
287
+ if (badges.length > 0) line += paint(theme, "faint", `${SEPARATORS.item}${badges.join(SEPARATORS.item)}`);
285
288
  }
286
289
  text.setText(line);
287
290
  return text;