@asterxsk/kiln 0.1.0 → 0.2.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 (203) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +170 -170
  3. package/agent/AGENTS.md +67 -67
  4. package/agent/README.md +5 -5
  5. package/agent/extensions/AGENTS.md +68 -68
  6. package/agent/extensions/ask-user/index.ts +418 -418
  7. package/agent/extensions/ask-user/package-lock.json +769 -769
  8. package/agent/extensions/ask-user/package.json +19 -19
  9. package/agent/extensions/ask-user/prompt.ts +45 -45
  10. package/agent/extensions/ask-user/tsconfig.json +7 -7
  11. package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
  12. package/agent/extensions/background-terminals/index.ts +627 -627
  13. package/agent/extensions/background-terminals/manager.test.ts +735 -735
  14. package/agent/extensions/background-terminals/output.test.ts +109 -109
  15. package/agent/extensions/background-terminals/package-lock.json +769 -769
  16. package/agent/extensions/background-terminals/package.json +17 -17
  17. package/agent/extensions/background-terminals/prompt.test.ts +125 -125
  18. package/agent/extensions/background-terminals/ps.test.ts +82 -82
  19. package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
  20. package/agent/extensions/background-terminals/src/domain.ts +87 -87
  21. package/agent/extensions/background-terminals/src/manager.ts +907 -907
  22. package/agent/extensions/background-terminals/src/output.ts +84 -84
  23. package/agent/extensions/background-terminals/src/prompt.ts +142 -142
  24. package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
  25. package/agent/extensions/background-terminals/src/runtime.ts +36 -36
  26. package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
  27. package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
  28. package/agent/extensions/background-terminals/tsconfig.json +7 -7
  29. package/agent/extensions/file-search/index.spec.ts +443 -443
  30. package/agent/extensions/file-search/index.ts +459 -459
  31. package/agent/extensions/file-search/package-lock.json +2253 -2253
  32. package/agent/extensions/file-search/package.json +23 -23
  33. package/agent/extensions/file-search/src/args.ts +122 -122
  34. package/agent/extensions/file-search/src/binaries.ts +422 -422
  35. package/agent/extensions/file-search/src/output.ts +126 -126
  36. package/agent/extensions/file-search/src/process.ts +146 -146
  37. package/agent/extensions/file-search/src/prompt.ts +52 -52
  38. package/agent/extensions/file-search/tsconfig.json +7 -7
  39. package/agent/extensions/modelconf/PLAN.md +915 -915
  40. package/agent/extensions/modelconf/index.ts +296 -296
  41. package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
  42. package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
  43. package/agent/extensions/pi-web-access/LICENSE +21 -21
  44. package/agent/extensions/pi-web-access/README.md +470 -470
  45. package/agent/extensions/pi-web-access/SECURITY.md +5 -5
  46. package/agent/extensions/pi-web-access/activity.ts +101 -101
  47. package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
  48. package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
  49. package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
  50. package/agent/extensions/pi-web-access/content-find.ts +139 -139
  51. package/agent/extensions/pi-web-access/credential-source.ts +191 -191
  52. package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
  53. package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
  54. package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
  55. package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
  56. package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
  57. package/agent/extensions/pi-web-access/exa.ts +526 -526
  58. package/agent/extensions/pi-web-access/extract.ts +1196 -1196
  59. package/agent/extensions/pi-web-access/feature-config.ts +29 -29
  60. package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
  61. package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
  62. package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
  63. package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
  64. package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
  65. package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
  66. package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
  67. package/agent/extensions/pi-web-access/github-api.ts +197 -197
  68. package/agent/extensions/pi-web-access/github-extract.ts +746 -746
  69. package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
  70. package/agent/extensions/pi-web-access/index.ts +1737 -1737
  71. package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
  72. package/agent/extensions/pi-web-access/package.json +64 -64
  73. package/agent/extensions/pi-web-access/page-query.ts +96 -96
  74. package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
  75. package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
  76. package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
  77. package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
  78. package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
  79. package/agent/extensions/pi-web-access/source-check.ts +282 -282
  80. package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
  81. package/agent/extensions/pi-web-access/storage.ts +521 -521
  82. package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
  83. package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
  84. package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
  85. package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
  86. package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
  87. package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
  88. package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
  89. package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
  90. package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
  91. package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
  92. package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
  93. package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
  94. package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
  95. package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
  96. package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
  97. package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
  98. package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
  99. package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
  100. package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
  101. package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
  102. package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
  103. package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
  104. package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
  105. package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
  106. package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
  107. package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
  108. package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
  109. package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
  110. package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
  111. package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
  112. package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
  113. package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
  114. package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
  115. package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
  116. package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
  117. package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
  118. package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
  119. package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
  120. package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
  121. package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
  122. package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
  123. package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
  124. package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
  125. package/agent/extensions/pi-web-access/tsconfig.json +11 -11
  126. package/agent/extensions/pi-web-access/utils.ts +451 -451
  127. package/agent/extensions/pi-web-access/video-extract.ts +392 -392
  128. package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
  129. package/agent/extensions/shared/activity-status.ts +31 -31
  130. package/agent/extensions/shared/child-session.test.ts +270 -270
  131. package/agent/extensions/shared/child-session.ts +148 -148
  132. package/agent/extensions/shared/context-utilization.test.ts +48 -48
  133. package/agent/extensions/shared/context-utilization.ts +47 -47
  134. package/agent/extensions/shared/dashboard-state.ts +99 -99
  135. package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
  136. package/agent/extensions/shared/tool-call-timeout.ts +104 -104
  137. package/agent/extensions/subagents/by-the-way.test.ts +29 -29
  138. package/agent/extensions/subagents/claude.test.ts +119 -119
  139. package/agent/extensions/subagents/codex.test.ts +102 -102
  140. package/agent/extensions/subagents/context-usage.test.ts +107 -107
  141. package/agent/extensions/subagents/docs/design-plan.md +568 -568
  142. package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
  143. package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
  144. package/agent/extensions/subagents/index.ts +779 -779
  145. package/agent/extensions/subagents/manager.test.ts +276 -276
  146. package/agent/extensions/subagents/package-lock.json +2244 -2244
  147. package/agent/extensions/subagents/package.json +19 -19
  148. package/agent/extensions/subagents/result-delivery.test.ts +27 -27
  149. package/agent/extensions/subagents/src/backend.ts +73 -73
  150. package/agent/extensions/subagents/src/backends/claude.ts +701 -701
  151. package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
  152. package/agent/extensions/subagents/src/backends/pi.ts +575 -575
  153. package/agent/extensions/subagents/src/backends/stub.ts +300 -300
  154. package/agent/extensions/subagents/src/by-the-way.ts +21 -21
  155. package/agent/extensions/subagents/src/domain.ts +253 -253
  156. package/agent/extensions/subagents/src/format.ts +74 -74
  157. package/agent/extensions/subagents/src/manager.ts +736 -736
  158. package/agent/extensions/subagents/src/prompt.ts +92 -92
  159. package/agent/extensions/subagents/src/result-delivery.ts +20 -20
  160. package/agent/extensions/subagents/src/runtime.ts +53 -53
  161. package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
  162. package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
  163. package/agent/extensions/subagents/takeover.test.ts +29 -29
  164. package/agent/extensions/subagents/tsconfig.json +7 -7
  165. package/agent/extensions/todo/AGENTS.md +38 -38
  166. package/agent/extensions/todo/LICENSE +21 -21
  167. package/agent/extensions/todo/config.ts +55 -55
  168. package/agent/extensions/todo/index.ts +151 -151
  169. package/agent/extensions/todo/locales/de.json +17 -17
  170. package/agent/extensions/todo/locales/en.json +15 -15
  171. package/agent/extensions/todo/locales/es.json +17 -17
  172. package/agent/extensions/todo/locales/fr.json +17 -17
  173. package/agent/extensions/todo/locales/pt-BR.json +17 -17
  174. package/agent/extensions/todo/locales/pt.json +17 -17
  175. package/agent/extensions/todo/locales/ru.json +17 -17
  176. package/agent/extensions/todo/locales/uk.json +17 -17
  177. package/agent/extensions/todo/locales/zh.json +17 -17
  178. package/agent/extensions/todo/package-lock.json +3358 -3358
  179. package/agent/extensions/todo/package.json +67 -67
  180. package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
  181. package/agent/extensions/todo/state/invariants.ts +20 -20
  182. package/agent/extensions/todo/state/replay.ts +38 -38
  183. package/agent/extensions/todo/state/selectors.ts +107 -107
  184. package/agent/extensions/todo/state/state-reducer.ts +326 -326
  185. package/agent/extensions/todo/state/state.ts +18 -18
  186. package/agent/extensions/todo/state/store.ts +82 -82
  187. package/agent/extensions/todo/state/task-graph.ts +57 -57
  188. package/agent/extensions/todo/todo-overlay.ts +200 -200
  189. package/agent/extensions/todo/todo.ts +155 -155
  190. package/agent/extensions/todo/tool/response-envelope.ts +109 -109
  191. package/agent/extensions/todo/tool/types.ts +206 -206
  192. package/agent/extensions/todo/view/format.ts +177 -177
  193. package/agent/install.ps1 +637 -527
  194. package/agent/install.sh +620 -511
  195. package/agent/keybindings.json +7 -7
  196. package/bin/kiln.js +124 -11
  197. package/package.json +8 -2
  198. package/agent/extensions/taste/index.ts +0 -443
  199. package/agent/extensions/taste/install.ps1 +0 -23
  200. package/agent/extensions/taste/install.sh +0 -21
  201. /package/agent/extensions/{status line → statusline}/index.ts +0 -0
  202. /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
  203. /package/agent/extensions/{status line → statusline}/install.sh +0 -0
@@ -1,55 +1,55 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- // ---------------------------------------------------------------------------
6
- // Self-contained config utilities — inlined from @juicesharp/rpiv-config so
7
- // this extension has zero npm dependencies and can be freely edited.
8
- // ---------------------------------------------------------------------------
9
-
10
- export interface GuidanceFields {
11
- promptSnippet?: string;
12
- promptGuidelines?: string[];
13
- }
14
-
15
- function configPath(name: string, file: string = "config.json"): string {
16
- return join(homedir(), ".config", name, file);
17
- }
18
-
19
- function loadJsonConfig<T>(path: string): T {
20
- if (!existsSync(path)) return {} as T;
21
- try {
22
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
23
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {} as T;
24
- return parsed as T;
25
- } catch {
26
- return {} as T;
27
- }
28
- }
29
-
30
- export function validateGuidanceFields(fields: unknown): GuidanceFields {
31
- if (!fields || typeof fields !== "object") return {};
32
- const g = fields as Record<string, unknown>;
33
- const result: GuidanceFields = {};
34
- if (typeof g.promptSnippet === "string" && g.promptSnippet.length > 0) {
35
- result.promptSnippet = g.promptSnippet;
36
- }
37
- if (
38
- Array.isArray(g.promptGuidelines) &&
39
- g.promptGuidelines.length > 0 &&
40
- g.promptGuidelines.every((s) => typeof s === "string" && s.length > 0)
41
- ) {
42
- result.promptGuidelines = g.promptGuidelines;
43
- }
44
- return result;
45
- }
46
-
47
- const CONFIG_PATH = configPath("rpiv-todo");
48
-
49
- interface TodoConfig {
50
- guidance?: GuidanceFields;
51
- }
52
-
53
- export function loadConfig(): TodoConfig {
54
- return loadJsonConfig<TodoConfig>(CONFIG_PATH);
55
- }
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Self-contained config utilities — inlined from @juicesharp/rpiv-config so
7
+ // this extension has zero npm dependencies and can be freely edited.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface GuidanceFields {
11
+ promptSnippet?: string;
12
+ promptGuidelines?: string[];
13
+ }
14
+
15
+ function configPath(name: string, file: string = "config.json"): string {
16
+ return join(homedir(), ".config", name, file);
17
+ }
18
+
19
+ function loadJsonConfig<T>(path: string): T {
20
+ if (!existsSync(path)) return {} as T;
21
+ try {
22
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
23
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {} as T;
24
+ return parsed as T;
25
+ } catch {
26
+ return {} as T;
27
+ }
28
+ }
29
+
30
+ export function validateGuidanceFields(fields: unknown): GuidanceFields {
31
+ if (!fields || typeof fields !== "object") return {};
32
+ const g = fields as Record<string, unknown>;
33
+ const result: GuidanceFields = {};
34
+ if (typeof g.promptSnippet === "string" && g.promptSnippet.length > 0) {
35
+ result.promptSnippet = g.promptSnippet;
36
+ }
37
+ if (
38
+ Array.isArray(g.promptGuidelines) &&
39
+ g.promptGuidelines.length > 0 &&
40
+ g.promptGuidelines.every((s) => typeof s === "string" && s.length > 0)
41
+ ) {
42
+ result.promptGuidelines = g.promptGuidelines;
43
+ }
44
+ return result;
45
+ }
46
+
47
+ const CONFIG_PATH = configPath("rpiv-todo");
48
+
49
+ interface TodoConfig {
50
+ guidance?: GuidanceFields;
51
+ }
52
+
53
+ export function loadConfig(): TodoConfig {
54
+ return loadJsonConfig<TodoConfig>(CONFIG_PATH);
55
+ }
@@ -1,151 +1,151 @@
1
- /**
2
- * rpiv-todo — Pi extension. Registers the `todo` tool, `/todos` slash
3
- * command, and the persistent TodoOverlay widget.
4
- *
5
- * TUI chrome strings localize at render time via the i18n bridge. Strings are
6
- * registered with rpiv-i18n here, once, at module init — but only when the
7
- * SDK is actually installed. If `@juicesharp/rpiv-i18n` is missing (standalone
8
- * install of just this package), the dynamic-load shim no-ops and the bridge's
9
- * `t(key, fallback)` returns the inline English literal at every call site.
10
- * The extension stays online either way.
11
- *
12
- * Adding a locale: drop `locales/<code>.json` next to en.json (mirroring the
13
- * key set). No edit needed here — `registerLocalesFromDir` iterates
14
- * `SUPPORTED_LOCALES` from the SDK. See `@juicesharp/rpiv-i18n` README →
15
- * "Contributing translations" for the full convention.
16
- *
17
- * Extracted from rpiv-pi@7525a5d. Tool name "todo" and widget key
18
- * "rpiv-todos" preserved verbatim so existing session history replays
19
- * correctly after upgrade.
20
- */
21
-
22
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
23
- import { I18N_NAMESPACE } from "./state/i18n-bridge.js";
24
- import { replayFromBranch } from "./state/replay.js";
25
- import { dropState, emptyState, getState, replaceState } from "./state/store.js";
26
- import { registerTodosCommand, registerTodoTool, TOOL_NAME } from "./todo.js";
27
- import { TodoOverlay } from "./todo-overlay.js";
28
-
29
- type I18nLoader = {
30
- registerLocalesFromDir: (namespace: string, packageUrl: string, options?: { label?: string }) => void;
31
- };
32
-
33
- // Dynamic import keeps `@juicesharp/rpiv-i18n` a soft optional peer: when the
34
- // SDK is installed alongside this package the strings register and
35
- // `/languages` flips them live; when it isn't, the import rejects here, we
36
- // no-op, and the bridge's English-fallback shim keeps the extension online.
37
- //
38
- // The `/loader` subpath is used instead of the SDK entry so the i18n-ui +
39
- // pi-tui modules are not pulled into our load graph just to register strings.
40
- try {
41
- const sdk = (await import("@juicesharp/rpiv-i18n/loader")) as I18nLoader;
42
- sdk.registerLocalesFromDir(I18N_NAMESPACE, import.meta.url, { label: "rpiv-todo" });
43
- } catch {
44
- // SDK absent — extension still loads with English-only UI.
45
- }
46
-
47
- // pi-core's ExtensionRunner throws this exact phrase from an invalidated ctx
48
- // proxy after session replacement/reload. Match the stable substring so genuine
49
- // replay bugs still propagate instead of being silently swallowed.
50
- function isStaleCtxError(e: unknown): boolean {
51
- return /stale after session replacement/.test(String(e));
52
- }
53
-
54
- export default function (pi: ExtensionAPI) {
55
- // Todo overlay widget — constructed lazily at the first session_start with UI.
56
- let todoOverlay: TodoOverlay | undefined;
57
-
58
- /**
59
- * If no active tasks remain (all completed, deleted, or empty), reset the
60
- * state to empty. Called at agent_end and after replay on session lifecycle
61
- * events so stale completed tasks don't persist across turns.
62
- *
63
- * Note: clearing is in-memory only (no tool result written to the branch).
64
- * Session lifecycle handlers re-check on replay so the empty state survives
65
- * compaction and session transitions. If a future pi-core ordering change
66
- * skips those lifecycle events, completed tasks would reappear.
67
- */
68
- function autoClearIfNoActiveTasks(sessionId: string): void {
69
- const state = getState(sessionId);
70
- if (!state.tasks.some((t) => t.status === "pending" || t.status === "in_progress")) {
71
- replaceState(sessionId, emptyState());
72
- }
73
- }
74
-
75
- registerTodoTool(pi);
76
- registerTodosCommand(pi);
77
-
78
- pi.on("session_start", async (_event, ctx) => {
79
- const sessionId = ctx.sessionManager.getSessionId();
80
- replaceState(sessionId, replayFromBranch(ctx));
81
- autoClearIfNoActiveTasks(sessionId);
82
- if (ctx.hasUI) {
83
- todoOverlay ??= new TodoOverlay();
84
- todoOverlay.setSession(sessionId);
85
- todoOverlay.setUICtx(ctx.ui);
86
- todoOverlay.resetCompletedDisplayState();
87
- todoOverlay.update();
88
- }
89
- });
90
-
91
- pi.on("session_compact", async (_event, ctx) => {
92
- // Auto-compaction races session disposal: pi-core invalidates the
93
- // extension runner while still emitting session_compact, so `ctx` may be
94
- // a dead proxy whose getters throw the stale error. The compacting session
95
- // is being discarded — the replacement session's session_start replays
96
- // state — so keep current state on a stale ctx. Other errors are real
97
- // replay bugs and must propagate.
98
- try {
99
- replaceState(ctx.sessionManager.getSessionId(), replayFromBranch(ctx));
100
- } catch (e) {
101
- if (!isStaleCtxError(e)) throw e;
102
- }
103
- autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
104
- todoOverlay?.resetCompletedDisplayState();
105
- todoOverlay?.update();
106
- });
107
-
108
- pi.on("session_tree", async (_event, ctx) => {
109
- try {
110
- replaceState(ctx.sessionManager.getSessionId(), replayFromBranch(ctx));
111
- } catch (e) {
112
- if (!isStaleCtxError(e)) throw e;
113
- }
114
- autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
115
- todoOverlay?.resetCompletedDisplayState();
116
- todoOverlay?.update();
117
- });
118
-
119
- pi.on("session_shutdown", async (_event, ctx) => {
120
- try {
121
- dropState(ctx.sessionManager.getSessionId());
122
- } catch (e) {
123
- // A stale ctx proxy after session replacement may throw on any getter;
124
- // the cell for a replaced session is abandoned regardless.
125
- if (!isStaleCtxError(e)) throw e;
126
- }
127
- todoOverlay?.dispose();
128
- todoOverlay = undefined;
129
- });
130
-
131
- // Reads getTodos() at render time; do NOT call replayFromBranch here
132
- // (branch is stale — message_end runs after tool_execution_end).
133
- pi.on("tool_execution_end", async (event) => {
134
- if (event.toolName !== TOOL_NAME || event.isError) return;
135
- todoOverlay?.update();
136
- });
137
-
138
- pi.on("agent_start", async () => {
139
- todoOverlay?.hideCompletedTasksFromPreviousTurn();
140
- todoOverlay?.onAgentTurnStart();
141
- });
142
-
143
- // Run auto-clear BEFORE onAgentTurnEnd so the overlay's completed-tracking
144
- // sets (pendingHide, hiddenCompleted) are still valid for any future logic
145
- // in onAgentTurnEnd that references them. The overlay resets its display
146
- // state separately at session lifecycle boundaries.
147
- pi.on("agent_end", async (_event, ctx) => {
148
- autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
149
- todoOverlay?.onAgentTurnEnd();
150
- });
151
- }
1
+ /**
2
+ * rpiv-todo — Pi extension. Registers the `todo` tool, `/todos` slash
3
+ * command, and the persistent TodoOverlay widget.
4
+ *
5
+ * TUI chrome strings localize at render time via the i18n bridge. Strings are
6
+ * registered with rpiv-i18n here, once, at module init — but only when the
7
+ * SDK is actually installed. If `@juicesharp/rpiv-i18n` is missing (standalone
8
+ * install of just this package), the dynamic-load shim no-ops and the bridge's
9
+ * `t(key, fallback)` returns the inline English literal at every call site.
10
+ * The extension stays online either way.
11
+ *
12
+ * Adding a locale: drop `locales/<code>.json` next to en.json (mirroring the
13
+ * key set). No edit needed here — `registerLocalesFromDir` iterates
14
+ * `SUPPORTED_LOCALES` from the SDK. See `@juicesharp/rpiv-i18n` README →
15
+ * "Contributing translations" for the full convention.
16
+ *
17
+ * Extracted from rpiv-pi@7525a5d. Tool name "todo" and widget key
18
+ * "rpiv-todos" preserved verbatim so existing session history replays
19
+ * correctly after upgrade.
20
+ */
21
+
22
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
23
+ import { I18N_NAMESPACE } from "./state/i18n-bridge.js";
24
+ import { replayFromBranch } from "./state/replay.js";
25
+ import { dropState, emptyState, getState, replaceState } from "./state/store.js";
26
+ import { registerTodosCommand, registerTodoTool, TOOL_NAME } from "./todo.js";
27
+ import { TodoOverlay } from "./todo-overlay.js";
28
+
29
+ type I18nLoader = {
30
+ registerLocalesFromDir: (namespace: string, packageUrl: string, options?: { label?: string }) => void;
31
+ };
32
+
33
+ // Dynamic import keeps `@juicesharp/rpiv-i18n` a soft optional peer: when the
34
+ // SDK is installed alongside this package the strings register and
35
+ // `/languages` flips them live; when it isn't, the import rejects here, we
36
+ // no-op, and the bridge's English-fallback shim keeps the extension online.
37
+ //
38
+ // The `/loader` subpath is used instead of the SDK entry so the i18n-ui +
39
+ // pi-tui modules are not pulled into our load graph just to register strings.
40
+ try {
41
+ const sdk = (await import("@juicesharp/rpiv-i18n/loader")) as I18nLoader;
42
+ sdk.registerLocalesFromDir(I18N_NAMESPACE, import.meta.url, { label: "rpiv-todo" });
43
+ } catch {
44
+ // SDK absent — extension still loads with English-only UI.
45
+ }
46
+
47
+ // pi-core's ExtensionRunner throws this exact phrase from an invalidated ctx
48
+ // proxy after session replacement/reload. Match the stable substring so genuine
49
+ // replay bugs still propagate instead of being silently swallowed.
50
+ function isStaleCtxError(e: unknown): boolean {
51
+ return /stale after session replacement/.test(String(e));
52
+ }
53
+
54
+ export default function (pi: ExtensionAPI) {
55
+ // Todo overlay widget — constructed lazily at the first session_start with UI.
56
+ let todoOverlay: TodoOverlay | undefined;
57
+
58
+ /**
59
+ * If no active tasks remain (all completed, deleted, or empty), reset the
60
+ * state to empty. Called at agent_end and after replay on session lifecycle
61
+ * events so stale completed tasks don't persist across turns.
62
+ *
63
+ * Note: clearing is in-memory only (no tool result written to the branch).
64
+ * Session lifecycle handlers re-check on replay so the empty state survives
65
+ * compaction and session transitions. If a future pi-core ordering change
66
+ * skips those lifecycle events, completed tasks would reappear.
67
+ */
68
+ function autoClearIfNoActiveTasks(sessionId: string): void {
69
+ const state = getState(sessionId);
70
+ if (!state.tasks.some((t) => t.status === "pending" || t.status === "in_progress")) {
71
+ replaceState(sessionId, emptyState());
72
+ }
73
+ }
74
+
75
+ registerTodoTool(pi);
76
+ registerTodosCommand(pi);
77
+
78
+ pi.on("session_start", async (_event, ctx) => {
79
+ const sessionId = ctx.sessionManager.getSessionId();
80
+ replaceState(sessionId, replayFromBranch(ctx));
81
+ autoClearIfNoActiveTasks(sessionId);
82
+ if (ctx.hasUI) {
83
+ todoOverlay ??= new TodoOverlay();
84
+ todoOverlay.setSession(sessionId);
85
+ todoOverlay.setUICtx(ctx.ui);
86
+ todoOverlay.resetCompletedDisplayState();
87
+ todoOverlay.update();
88
+ }
89
+ });
90
+
91
+ pi.on("session_compact", async (_event, ctx) => {
92
+ // Auto-compaction races session disposal: pi-core invalidates the
93
+ // extension runner while still emitting session_compact, so `ctx` may be
94
+ // a dead proxy whose getters throw the stale error. The compacting session
95
+ // is being discarded — the replacement session's session_start replays
96
+ // state — so keep current state on a stale ctx. Other errors are real
97
+ // replay bugs and must propagate.
98
+ try {
99
+ replaceState(ctx.sessionManager.getSessionId(), replayFromBranch(ctx));
100
+ } catch (e) {
101
+ if (!isStaleCtxError(e)) throw e;
102
+ }
103
+ autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
104
+ todoOverlay?.resetCompletedDisplayState();
105
+ todoOverlay?.update();
106
+ });
107
+
108
+ pi.on("session_tree", async (_event, ctx) => {
109
+ try {
110
+ replaceState(ctx.sessionManager.getSessionId(), replayFromBranch(ctx));
111
+ } catch (e) {
112
+ if (!isStaleCtxError(e)) throw e;
113
+ }
114
+ autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
115
+ todoOverlay?.resetCompletedDisplayState();
116
+ todoOverlay?.update();
117
+ });
118
+
119
+ pi.on("session_shutdown", async (_event, ctx) => {
120
+ try {
121
+ dropState(ctx.sessionManager.getSessionId());
122
+ } catch (e) {
123
+ // A stale ctx proxy after session replacement may throw on any getter;
124
+ // the cell for a replaced session is abandoned regardless.
125
+ if (!isStaleCtxError(e)) throw e;
126
+ }
127
+ todoOverlay?.dispose();
128
+ todoOverlay = undefined;
129
+ });
130
+
131
+ // Reads getTodos() at render time; do NOT call replayFromBranch here
132
+ // (branch is stale — message_end runs after tool_execution_end).
133
+ pi.on("tool_execution_end", async (event) => {
134
+ if (event.toolName !== TOOL_NAME || event.isError) return;
135
+ todoOverlay?.update();
136
+ });
137
+
138
+ pi.on("agent_start", async () => {
139
+ todoOverlay?.hideCompletedTasksFromPreviousTurn();
140
+ todoOverlay?.onAgentTurnStart();
141
+ });
142
+
143
+ // Run auto-clear BEFORE onAgentTurnEnd so the overlay's completed-tracking
144
+ // sets (pendingHide, hiddenCompleted) are still valid for any future logic
145
+ // in onAgentTurnEnd that references them. The overlay resets its display
146
+ // state separately at session lifecycle boundaries.
147
+ pi.on("agent_end", async (_event, ctx) => {
148
+ autoClearIfNoActiveTasks(ctx.sessionManager.getSessionId());
149
+ todoOverlay?.onAgentTurnEnd();
150
+ });
151
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated. Note: 'overlay.more' renders as '+5 weitere' which is grammatically loose for plural agreement — accepted for badge brevity.",
3
-
4
- "status.pending": "ausstehend",
5
- "status.in_progress": "in Bearbeitung",
6
- "status.completed": "erledigt",
7
- "status.deleted": "gelöscht",
8
-
9
- "overlay.heading": "Todos",
10
- "overlay.more": "weitere",
11
-
12
- "command.no_todos": "Noch keine Todos. Bitte den Agenten, welche hinzuzufügen!",
13
- "command.requires_interactive": "/todos erfordert den interaktiven Modus",
14
- "command.section.pending": "── Ausstehend ──",
15
- "command.section.in_progress": "── In Bearbeitung ──",
16
- "command.section.completed": "── Erledigt ──"
17
- }
1
+ {
2
+ "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated. Note: 'overlay.more' renders as '+5 weitere' which is grammatically loose for plural agreement — accepted for badge brevity.",
3
+
4
+ "status.pending": "ausstehend",
5
+ "status.in_progress": "in Bearbeitung",
6
+ "status.completed": "erledigt",
7
+ "status.deleted": "gelöscht",
8
+
9
+ "overlay.heading": "Todos",
10
+ "overlay.more": "weitere",
11
+
12
+ "command.no_todos": "Noch keine Todos. Bitte den Agenten, welche hinzuzufügen!",
13
+ "command.requires_interactive": "/todos erfordert den interaktiven Modus",
14
+ "command.section.pending": "── Ausstehend ──",
15
+ "command.section.in_progress": "── In Bearbeitung ──",
16
+ "command.section.completed": "── Erledigt ──"
17
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "status.pending": "pending",
3
- "status.in_progress": "in progress",
4
- "status.completed": "completed",
5
- "status.deleted": "deleted",
6
-
7
- "overlay.heading": "Todos",
8
- "overlay.more": "more",
9
-
10
- "command.no_todos": "No todos yet. Ask the agent to add some!",
11
- "command.requires_interactive": "/todos requires interactive mode",
12
- "command.section.pending": "── Pending ──",
13
- "command.section.in_progress": "── In Progress ──",
14
- "command.section.completed": "── Completed ──"
15
- }
1
+ {
2
+ "status.pending": "pending",
3
+ "status.in_progress": "in progress",
4
+ "status.completed": "completed",
5
+ "status.deleted": "deleted",
6
+
7
+ "overlay.heading": "Todos",
8
+ "overlay.more": "more",
9
+
10
+ "command.no_todos": "No todos yet. Ask the agent to add some!",
11
+ "command.requires_interactive": "/todos requires interactive mode",
12
+ "command.section.pending": "── Pending ──",
13
+ "command.section.in_progress": "── In Progress ──",
14
+ "command.section.completed": "── Completed ──"
15
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
-
4
- "status.pending": "pendientes",
5
- "status.in_progress": "en curso",
6
- "status.completed": "completadas",
7
- "status.deleted": "eliminada",
8
-
9
- "overlay.heading": "Tareas",
10
- "overlay.more": "más",
11
-
12
- "command.no_todos": "Sin tareas aún. ¡Pídele al agente que añada algunas!",
13
- "command.requires_interactive": "/todos requiere modo interactivo",
14
- "command.section.pending": "── Pendientes ──",
15
- "command.section.in_progress": "── En curso ──",
16
- "command.section.completed": "── Completadas ──"
17
- }
1
+ {
2
+ "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
+
4
+ "status.pending": "pendientes",
5
+ "status.in_progress": "en curso",
6
+ "status.completed": "completadas",
7
+ "status.deleted": "eliminada",
8
+
9
+ "overlay.heading": "Tareas",
10
+ "overlay.more": "más",
11
+
12
+ "command.no_todos": "Sin tareas aún. ¡Pídele al agente que añada algunas!",
13
+ "command.requires_interactive": "/todos requiere modo interactivo",
14
+ "command.section.pending": "── Pendientes ──",
15
+ "command.section.in_progress": "── En curso ──",
16
+ "command.section.completed": "── Completadas ──"
17
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
-
4
- "status.pending": "en attente",
5
- "status.in_progress": "en cours",
6
- "status.completed": "terminées",
7
- "status.deleted": "supprimée",
8
-
9
- "overlay.heading": "Tâches",
10
- "overlay.more": "autres",
11
-
12
- "command.no_todos": "Pas encore de tâches. Demandez à l'agent d'en ajouter !",
13
- "command.requires_interactive": "/todos nécessite le mode interactif",
14
- "command.section.pending": "── En attente ──",
15
- "command.section.in_progress": "── En cours ──",
16
- "command.section.completed": "── Terminées ──"
17
- }
1
+ {
2
+ "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
+
4
+ "status.pending": "en attente",
5
+ "status.in_progress": "en cours",
6
+ "status.completed": "terminées",
7
+ "status.deleted": "supprimée",
8
+
9
+ "overlay.heading": "Tâches",
10
+ "overlay.more": "autres",
11
+
12
+ "command.no_todos": "Pas encore de tâches. Demandez à l'agent d'en ajouter !",
13
+ "command.requires_interactive": "/todos nécessite le mode interactif",
14
+ "command.section.pending": "── En attente ──",
15
+ "command.section.in_progress": "── En cours ──",
16
+ "command.section.completed": "── Terminées ──"
17
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
-
4
- "status.pending": "pendentes",
5
- "status.in_progress": "em andamento",
6
- "status.completed": "concluídas",
7
- "status.deleted": "excluída",
8
-
9
- "overlay.heading": "Tarefas",
10
- "overlay.more": "mais",
11
-
12
- "command.no_todos": "Nenhuma tarefa ainda. Peça ao agente para adicionar algumas!",
13
- "command.requires_interactive": "/todos requer modo interativo",
14
- "command.section.pending": "── Pendentes ──",
15
- "command.section.in_progress": "── Em andamento ──",
16
- "command.section.completed": "── Concluídas ──"
17
- }
1
+ {
2
+ "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
+
4
+ "status.pending": "pendentes",
5
+ "status.in_progress": "em andamento",
6
+ "status.completed": "concluídas",
7
+ "status.deleted": "excluída",
8
+
9
+ "overlay.heading": "Tarefas",
10
+ "overlay.more": "mais",
11
+
12
+ "command.no_todos": "Nenhuma tarefa ainda. Peça ao agente para adicionar algumas!",
13
+ "command.requires_interactive": "/todos requer modo interativo",
14
+ "command.section.pending": "── Pendentes ──",
15
+ "command.section.in_progress": "── Em andamento ──",
16
+ "command.section.completed": "── Concluídas ──"
17
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
-
4
- "status.pending": "pendentes",
5
- "status.in_progress": "em curso",
6
- "status.completed": "concluídas",
7
- "status.deleted": "eliminada",
8
-
9
- "overlay.heading": "Tarefas",
10
- "overlay.more": "mais",
11
-
12
- "command.no_todos": "Sem tarefas ainda. Peça ao agente para adicionar algumas!",
13
- "command.requires_interactive": "/todos requer modo interativo",
14
- "command.section.pending": "── Pendentes ──",
15
- "command.section.in_progress": "── Em curso ──",
16
- "command.section.completed": "── Concluídas ──"
17
- }
1
+ {
2
+ "_meta.notes": "Auto-translated draft. Native-speaker review welcome via PR. Unicode box-drawing characters (──) stay untranslated.",
3
+
4
+ "status.pending": "pendentes",
5
+ "status.in_progress": "em curso",
6
+ "status.completed": "concluídas",
7
+ "status.deleted": "eliminada",
8
+
9
+ "overlay.heading": "Tarefas",
10
+ "overlay.more": "mais",
11
+
12
+ "command.no_todos": "Sem tarefas ainda. Peça ao agente para adicionar algumas!",
13
+ "command.requires_interactive": "/todos requer modo interativo",
14
+ "command.section.pending": "── Pendentes ──",
15
+ "command.section.in_progress": "── Em curso ──",
16
+ "command.section.completed": "── Concluídas ──"
17
+ }