@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,84 +1,84 @@
1
- /**
2
- * OutputBuffer — bounded in-memory capture of one process stream.
3
- *
4
- * Newest output is always retained; when the retained size exceeds the cap,
5
- * whole chunks are evicted from the head and counted in `truncatedBytes`.
6
- * A single chunk larger than the cap itself is trimmed to its tail (on a
7
- * UTF-8 boundary) so retention is strictly bounded even for one giant write.
8
- * An optional spill callback receives every chunk (in order, before any
9
- * eviction) so the caller can keep a complete on-disk copy.
10
- *
11
- * Plain TS by design: this is push-based accumulation driven by node stream
12
- * 'data' callbacks, not stream transformation.
13
- */
14
-
15
- import type { OutputView } from "./domain.ts";
16
-
17
- export class OutputBuffer {
18
- private chunks: string[] = [];
19
- /** Bytes currently retained across `chunks`. */
20
- private retainedBytes = 0;
21
- /** Cached join of `chunks`; invalidated on push so 1Hz UI ticks are cheap. */
22
- private cachedText: string | undefined = "";
23
- /** Bumped on every push; lets the UI cache derived line layouts. */
24
- version = 0;
25
- totalBytes = 0;
26
- truncatedBytes = 0;
27
- spillPath?: string;
28
-
29
- private readonly maxRetainedBytes: number;
30
- private readonly spill?: (chunk: string) => unknown;
31
-
32
- constructor(maxRetainedBytes: number, spill?: (chunk: string) => unknown) {
33
- this.maxRetainedBytes = maxRetainedBytes;
34
- this.spill = spill;
35
- }
36
-
37
- push(chunk: string) {
38
- if (chunk.length === 0) return true;
39
- let bytes = Buffer.byteLength(chunk, "utf8");
40
- this.totalBytes += bytes;
41
- const spillAccepted = this.spill?.(chunk) !== false;
42
- if (bytes > this.maxRetainedBytes) {
43
- // A single pathological chunk larger than the whole cap: everything
44
- // retained so far precedes it in the stream, so evict all of it, then
45
- // keep only the chunk's tail (cut on a UTF-8 code point boundary).
46
- // Retention stays strictly bounded even for one giant write, and the
47
- // retained view stays contiguous (no hole in the middle).
48
- this.truncatedBytes += this.retainedBytes;
49
- this.chunks = [];
50
- this.retainedBytes = 0;
51
- const raw = Buffer.from(chunk, "utf8");
52
- let start = raw.length - this.maxRetainedBytes;
53
- while (start < raw.length && (raw[start] & 0xc0) === 0x80) start++;
54
- this.truncatedBytes += start;
55
- chunk = raw.subarray(start).toString("utf8");
56
- bytes = raw.length - start;
57
- }
58
- this.chunks.push(chunk);
59
- this.retainedBytes += bytes;
60
- while (
61
- this.retainedBytes > this.maxRetainedBytes &&
62
- this.chunks.length > 1
63
- ) {
64
- const evicted = this.chunks.shift();
65
- if (evicted === undefined) break;
66
- const evictedBytes = Buffer.byteLength(evicted, "utf8");
67
- this.retainedBytes -= evictedBytes;
68
- this.truncatedBytes += evictedBytes;
69
- }
70
- this.cachedText = undefined;
71
- this.version++;
72
- return spillAccepted;
73
- }
74
-
75
- view(): OutputView {
76
- this.cachedText ??= this.chunks.join("");
77
- return {
78
- text: this.cachedText,
79
- totalBytes: this.totalBytes,
80
- truncatedBytes: this.truncatedBytes,
81
- spillPath: this.spillPath,
82
- };
83
- }
84
- }
1
+ /**
2
+ * OutputBuffer — bounded in-memory capture of one process stream.
3
+ *
4
+ * Newest output is always retained; when the retained size exceeds the cap,
5
+ * whole chunks are evicted from the head and counted in `truncatedBytes`.
6
+ * A single chunk larger than the cap itself is trimmed to its tail (on a
7
+ * UTF-8 boundary) so retention is strictly bounded even for one giant write.
8
+ * An optional spill callback receives every chunk (in order, before any
9
+ * eviction) so the caller can keep a complete on-disk copy.
10
+ *
11
+ * Plain TS by design: this is push-based accumulation driven by node stream
12
+ * 'data' callbacks, not stream transformation.
13
+ */
14
+
15
+ import type { OutputView } from "./domain.ts";
16
+
17
+ export class OutputBuffer {
18
+ private chunks: string[] = [];
19
+ /** Bytes currently retained across `chunks`. */
20
+ private retainedBytes = 0;
21
+ /** Cached join of `chunks`; invalidated on push so 1Hz UI ticks are cheap. */
22
+ private cachedText: string | undefined = "";
23
+ /** Bumped on every push; lets the UI cache derived line layouts. */
24
+ version = 0;
25
+ totalBytes = 0;
26
+ truncatedBytes = 0;
27
+ spillPath?: string;
28
+
29
+ private readonly maxRetainedBytes: number;
30
+ private readonly spill?: (chunk: string) => unknown;
31
+
32
+ constructor(maxRetainedBytes: number, spill?: (chunk: string) => unknown) {
33
+ this.maxRetainedBytes = maxRetainedBytes;
34
+ this.spill = spill;
35
+ }
36
+
37
+ push(chunk: string) {
38
+ if (chunk.length === 0) return true;
39
+ let bytes = Buffer.byteLength(chunk, "utf8");
40
+ this.totalBytes += bytes;
41
+ const spillAccepted = this.spill?.(chunk) !== false;
42
+ if (bytes > this.maxRetainedBytes) {
43
+ // A single pathological chunk larger than the whole cap: everything
44
+ // retained so far precedes it in the stream, so evict all of it, then
45
+ // keep only the chunk's tail (cut on a UTF-8 code point boundary).
46
+ // Retention stays strictly bounded even for one giant write, and the
47
+ // retained view stays contiguous (no hole in the middle).
48
+ this.truncatedBytes += this.retainedBytes;
49
+ this.chunks = [];
50
+ this.retainedBytes = 0;
51
+ const raw = Buffer.from(chunk, "utf8");
52
+ let start = raw.length - this.maxRetainedBytes;
53
+ while (start < raw.length && (raw[start] & 0xc0) === 0x80) start++;
54
+ this.truncatedBytes += start;
55
+ chunk = raw.subarray(start).toString("utf8");
56
+ bytes = raw.length - start;
57
+ }
58
+ this.chunks.push(chunk);
59
+ this.retainedBytes += bytes;
60
+ while (
61
+ this.retainedBytes > this.maxRetainedBytes &&
62
+ this.chunks.length > 1
63
+ ) {
64
+ const evicted = this.chunks.shift();
65
+ if (evicted === undefined) break;
66
+ const evictedBytes = Buffer.byteLength(evicted, "utf8");
67
+ this.retainedBytes -= evictedBytes;
68
+ this.truncatedBytes += evictedBytes;
69
+ }
70
+ this.cachedText = undefined;
71
+ this.version++;
72
+ return spillAccepted;
73
+ }
74
+
75
+ view(): OutputView {
76
+ this.cachedText ??= this.chunks.join("");
77
+ return {
78
+ text: this.cachedText,
79
+ totalBytes: this.totalBytes,
80
+ truncatedBytes: this.truncatedBytes,
81
+ spillPath: this.spillPath,
82
+ };
83
+ }
84
+ }
@@ -1,142 +1,142 @@
1
- /** All model-facing strings for the background-terminals tools. */
2
-
3
- import {
4
- DEFAULT_MAX_BYTES,
5
- DEFAULT_MAX_LINES,
6
- formatSize,
7
- truncateTail,
8
- } from "@earendil-works/pi-coding-agent";
9
- import { formatElapsed, formatExit, type TerminalSnapshot } from "./domain.ts";
10
- import { MAX_RUNNING, type KillResult } from "./manager.ts";
11
-
12
- /** bg_status stdout tail. */
13
- export const STATUS_STDOUT_MAX = 16 * 1024;
14
- /** bg_status stderr tail. */
15
- export const STATUS_STDERR_MAX = 8 * 1024;
16
- /** Completion follow-up stdout tail. Keep this concise; /ps has the detailed view. */
17
- export const RESULT_STDOUT_MAX = 8 * 1024;
18
- /** Completion follow-up stderr tail. Keep this concise; /ps has the detailed view. */
19
- export const RESULT_STDERR_MAX = 4 * 1024;
20
- const STATUS_STDOUT_MAX_LINES = 400;
21
- const STATUS_STDERR_MAX_LINES = 200;
22
- const RESULT_STDOUT_MAX_LINES = 40;
23
- const RESULT_STDERR_MAX_LINES = 20;
24
-
25
- export const BG_START_TOOL_DESCRIPTION =
26
- "Start a long-running shell command as a background terminal (executed via the platform shell — sh -c on POSIX, cmd.exe /d /s /c on Windows). " +
27
- "Fire-and-forget: this returns immediately with an id, and you get a message with the final output when the process exits. " +
28
- "The process receives NO stdin (immediate EOF) and there is no way to send input later — interactive commands will not work; use bg_kill to stop a stuck one. " +
29
- `Terminals are session-scoped: they are killed when the session ends or reloads. Output shown to you is tail-truncated (stdout ${formatSize(STATUS_STDOUT_MAX)}, stderr ${formatSize(STATUS_STDERR_MAX)}); the full logs are captured to files and in the /ps viewer. ` +
30
- `Max ${MAX_RUNNING} background terminals can run at once.`;
31
-
32
- export const BG_START_PROMPT_SNIPPET =
33
- "Run a long-lived shell command in the background (dev servers, builds, watchers); output is captured and you're notified on exit";
34
-
35
- export const BG_START_PROMPT_GUIDELINES = [
36
- "Use bg_start for commands expected to run long or indefinitely (servers, watch modes, long builds); use the regular bash tool for quick commands.",
37
- "bg_start processes receive no stdin — never start a command that requires interactive input.",
38
- "After bg_start, keep working; the exit result arrives automatically. Use bg_status only when you need current output before continuing.",
39
- ];
40
-
41
- export const BG_START_PARAMETER_DESCRIPTIONS = {
42
- command:
43
- "Shell command line to run in the background (sh -c on POSIX, cmd.exe /d /s /c on Windows). It receives no stdin (EOF immediately); interactive commands will not work.",
44
- title: "Short human-readable name shown in listings and the UI",
45
- workingDir: "Working directory (default: current working directory)",
46
- };
47
-
48
- export const BG_STATUS_TOOL_DESCRIPTION =
49
- "Peek at a background terminal's status and current output (tail-truncated) without blocking. If the terminal already exited, this returns its final state.";
50
-
51
- export const BG_STATUS_PARAMETER_DESCRIPTIONS = {
52
- id: 'Terminal id, e.g. "bt-1"',
53
- };
54
-
55
- export const BG_LIST_TOOL_DESCRIPTION =
56
- "List all background terminals (running and settled) with pid, elapsed time, exit status, and output sizes.";
57
-
58
- export const BG_KILL_TOOL_DESCRIPTION =
59
- "Stop one or more running background terminals (SIGTERM to the whole process tree, escalating to SIGKILL). Returns each terminal's final state; already-settled ids are reported as such.";
60
-
61
- export const BG_KILL_PARAMETER_DESCRIPTIONS = {
62
- ids: 'Terminal ids to stop, e.g. ["bt-1"]',
63
- };
64
-
65
- export function buildStartResult(snap: TerminalSnapshot) {
66
- return (
67
- `Started background terminal ${snap.id} "${snap.title}" (pid ${snap.pid ?? "?"}, ${snap.cwd}).\n` +
68
- `It runs in the background with no stdin. You'll get a message when it exits, ` +
69
- `or use bg_status(id: "${snap.id}") to peek, bg_kill to stop it, bg_list to see all.`
70
- );
71
- }
72
-
73
- /** One metadata line: `bt-1 [running] "dev server" (pid 12345, 3m12s, exit -, /path)`. */
74
- export function describeTerminal(snap: TerminalSnapshot) {
75
- const details = [
76
- `pid ${snap.pid ?? "?"}`,
77
- formatElapsed(snap),
78
- snap.status === "running" ? "exit -" : formatExit(snap),
79
- snap.cwd,
80
- `stdout ${formatSize(snap.stdout.totalBytes)}, stderr ${formatSize(snap.stderr.totalBytes)}`,
81
- ];
82
- return `${snap.id} [${snap.status}] "${snap.title}" (${details.join(", ")})`;
83
- }
84
-
85
- /** Tail-truncated labeled output section with a pointer at the full log. */
86
- function outputSection(
87
- label: string,
88
- view: TerminalSnapshot["stdout"],
89
- maxBytes: number,
90
- maxLines: number,
91
- ) {
92
- if (view.totalBytes === 0) return `${label}: (empty)`;
93
- const truncation = truncateTail(view.text, {
94
- maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
95
- maxLines: Math.min(maxLines, DEFAULT_MAX_LINES),
96
- });
97
- let text = `${label}:\n${truncation.content}`;
98
- const shownBytes = truncation.outputBytes;
99
- if (truncation.truncated || view.truncatedBytes > 0) {
100
- const where = view.spillPath
101
- ? `Full log: ${view.spillPath}`
102
- : "Full output in the /ps viewer";
103
- text += `\n[${label} truncated: showing last ${formatSize(shownBytes)} of ${formatSize(view.totalBytes)}. ${where}]`;
104
- }
105
- return text;
106
- }
107
-
108
- export function buildStatusResult(snap: TerminalSnapshot) {
109
- let text = describeTerminal(snap);
110
- if (snap.errorText) text += `\nError: ${snap.errorText}`;
111
- text += `\n\n${outputSection("stdout", snap.stdout, STATUS_STDOUT_MAX, STATUS_STDOUT_MAX_LINES)}`;
112
- text += `\n\n${outputSection("stderr", snap.stderr, STATUS_STDERR_MAX, STATUS_STDERR_MAX_LINES)}`;
113
- return text;
114
- }
115
-
116
- /** The async completion follow-up injected into the model's context. */
117
- export function buildTerminalResultMessage(snap: TerminalSnapshot) {
118
- const how =
119
- snap.status === "killed" ? "was killed" : `exited (${formatExit(snap)})`;
120
- let text = `Background terminal ${snap.id} "${snap.title}" ${how} after ${formatElapsed(snap)}.`;
121
- if (snap.errorText) text += `\nError: ${snap.errorText}`;
122
- text += `\n\n${outputSection("stdout", snap.stdout, RESULT_STDOUT_MAX, RESULT_STDOUT_MAX_LINES)}`;
123
- if (snap.stderr.totalBytes > 0) {
124
- text += `\n\n${outputSection("stderr", snap.stderr, RESULT_STDERR_MAX, RESULT_STDERR_MAX_LINES)}`;
125
- }
126
- return text;
127
- }
128
-
129
- export function buildKillReport(results: ReadonlyArray<KillResult>) {
130
- return results
131
- .map((entry) => {
132
- if (entry.killed) {
133
- return `Killed ${entry.id} "${entry.title}" (${entry.exit}).`;
134
- }
135
- if (entry.wasRunning) {
136
- // The natural exit won the race with the kill signal.
137
- return `${entry.id} "${entry.title}" exited on its own before the kill landed (${entry.exit}).`;
138
- }
139
- return `${entry.id} "${entry.title}" was already ${entry.status} (${entry.exit}).`;
140
- })
141
- .join("\n");
142
- }
1
+ /** All model-facing strings for the background-terminals tools. */
2
+
3
+ import {
4
+ DEFAULT_MAX_BYTES,
5
+ DEFAULT_MAX_LINES,
6
+ formatSize,
7
+ truncateTail,
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { formatElapsed, formatExit, type TerminalSnapshot } from "./domain.ts";
10
+ import { MAX_RUNNING, type KillResult } from "./manager.ts";
11
+
12
+ /** bg_status stdout tail. */
13
+ export const STATUS_STDOUT_MAX = 16 * 1024;
14
+ /** bg_status stderr tail. */
15
+ export const STATUS_STDERR_MAX = 8 * 1024;
16
+ /** Completion follow-up stdout tail. Keep this concise; /ps has the detailed view. */
17
+ export const RESULT_STDOUT_MAX = 8 * 1024;
18
+ /** Completion follow-up stderr tail. Keep this concise; /ps has the detailed view. */
19
+ export const RESULT_STDERR_MAX = 4 * 1024;
20
+ const STATUS_STDOUT_MAX_LINES = 400;
21
+ const STATUS_STDERR_MAX_LINES = 200;
22
+ const RESULT_STDOUT_MAX_LINES = 40;
23
+ const RESULT_STDERR_MAX_LINES = 20;
24
+
25
+ export const BG_START_TOOL_DESCRIPTION =
26
+ "Start a long-running shell command as a background terminal (executed via the platform shell — sh -c on POSIX, cmd.exe /d /s /c on Windows). " +
27
+ "Fire-and-forget: this returns immediately with an id, and you get a message with the final output when the process exits. " +
28
+ "The process receives NO stdin (immediate EOF) and there is no way to send input later — interactive commands will not work; use bg_kill to stop a stuck one. " +
29
+ `Terminals are session-scoped: they are killed when the session ends or reloads. Output shown to you is tail-truncated (stdout ${formatSize(STATUS_STDOUT_MAX)}, stderr ${formatSize(STATUS_STDERR_MAX)}); the full logs are captured to files and in the /ps viewer. ` +
30
+ `Max ${MAX_RUNNING} background terminals can run at once.`;
31
+
32
+ export const BG_START_PROMPT_SNIPPET =
33
+ "Run a long-lived shell command in the background (dev servers, builds, watchers); output is captured and you're notified on exit";
34
+
35
+ export const BG_START_PROMPT_GUIDELINES = [
36
+ "Use bg_start for commands expected to run long or indefinitely (servers, watch modes, long builds); use the regular bash tool for quick commands.",
37
+ "bg_start processes receive no stdin — never start a command that requires interactive input.",
38
+ "After bg_start, keep working; the exit result arrives automatically. Use bg_status only when you need current output before continuing.",
39
+ ];
40
+
41
+ export const BG_START_PARAMETER_DESCRIPTIONS = {
42
+ command:
43
+ "Shell command line to run in the background (sh -c on POSIX, cmd.exe /d /s /c on Windows). It receives no stdin (EOF immediately); interactive commands will not work.",
44
+ title: "Short human-readable name shown in listings and the UI",
45
+ workingDir: "Working directory (default: current working directory)",
46
+ };
47
+
48
+ export const BG_STATUS_TOOL_DESCRIPTION =
49
+ "Peek at a background terminal's status and current output (tail-truncated) without blocking. If the terminal already exited, this returns its final state.";
50
+
51
+ export const BG_STATUS_PARAMETER_DESCRIPTIONS = {
52
+ id: 'Terminal id, e.g. "bt-1"',
53
+ };
54
+
55
+ export const BG_LIST_TOOL_DESCRIPTION =
56
+ "List all background terminals (running and settled) with pid, elapsed time, exit status, and output sizes.";
57
+
58
+ export const BG_KILL_TOOL_DESCRIPTION =
59
+ "Stop one or more running background terminals (SIGTERM to the whole process tree, escalating to SIGKILL). Returns each terminal's final state; already-settled ids are reported as such.";
60
+
61
+ export const BG_KILL_PARAMETER_DESCRIPTIONS = {
62
+ ids: 'Terminal ids to stop, e.g. ["bt-1"]',
63
+ };
64
+
65
+ export function buildStartResult(snap: TerminalSnapshot) {
66
+ return (
67
+ `Started background terminal ${snap.id} "${snap.title}" (pid ${snap.pid ?? "?"}, ${snap.cwd}).\n` +
68
+ `It runs in the background with no stdin. You'll get a message when it exits, ` +
69
+ `or use bg_status(id: "${snap.id}") to peek, bg_kill to stop it, bg_list to see all.`
70
+ );
71
+ }
72
+
73
+ /** One metadata line: `bt-1 [running] "dev server" (pid 12345, 3m12s, exit -, /path)`. */
74
+ export function describeTerminal(snap: TerminalSnapshot) {
75
+ const details = [
76
+ `pid ${snap.pid ?? "?"}`,
77
+ formatElapsed(snap),
78
+ snap.status === "running" ? "exit -" : formatExit(snap),
79
+ snap.cwd,
80
+ `stdout ${formatSize(snap.stdout.totalBytes)}, stderr ${formatSize(snap.stderr.totalBytes)}`,
81
+ ];
82
+ return `${snap.id} [${snap.status}] "${snap.title}" (${details.join(", ")})`;
83
+ }
84
+
85
+ /** Tail-truncated labeled output section with a pointer at the full log. */
86
+ function outputSection(
87
+ label: string,
88
+ view: TerminalSnapshot["stdout"],
89
+ maxBytes: number,
90
+ maxLines: number,
91
+ ) {
92
+ if (view.totalBytes === 0) return `${label}: (empty)`;
93
+ const truncation = truncateTail(view.text, {
94
+ maxBytes: Math.min(maxBytes, DEFAULT_MAX_BYTES),
95
+ maxLines: Math.min(maxLines, DEFAULT_MAX_LINES),
96
+ });
97
+ let text = `${label}:\n${truncation.content}`;
98
+ const shownBytes = truncation.outputBytes;
99
+ if (truncation.truncated || view.truncatedBytes > 0) {
100
+ const where = view.spillPath
101
+ ? `Full log: ${view.spillPath}`
102
+ : "Full output in the /ps viewer";
103
+ text += `\n[${label} truncated: showing last ${formatSize(shownBytes)} of ${formatSize(view.totalBytes)}. ${where}]`;
104
+ }
105
+ return text;
106
+ }
107
+
108
+ export function buildStatusResult(snap: TerminalSnapshot) {
109
+ let text = describeTerminal(snap);
110
+ if (snap.errorText) text += `\nError: ${snap.errorText}`;
111
+ text += `\n\n${outputSection("stdout", snap.stdout, STATUS_STDOUT_MAX, STATUS_STDOUT_MAX_LINES)}`;
112
+ text += `\n\n${outputSection("stderr", snap.stderr, STATUS_STDERR_MAX, STATUS_STDERR_MAX_LINES)}`;
113
+ return text;
114
+ }
115
+
116
+ /** The async completion follow-up injected into the model's context. */
117
+ export function buildTerminalResultMessage(snap: TerminalSnapshot) {
118
+ const how =
119
+ snap.status === "killed" ? "was killed" : `exited (${formatExit(snap)})`;
120
+ let text = `Background terminal ${snap.id} "${snap.title}" ${how} after ${formatElapsed(snap)}.`;
121
+ if (snap.errorText) text += `\nError: ${snap.errorText}`;
122
+ text += `\n\n${outputSection("stdout", snap.stdout, RESULT_STDOUT_MAX, RESULT_STDOUT_MAX_LINES)}`;
123
+ if (snap.stderr.totalBytes > 0) {
124
+ text += `\n\n${outputSection("stderr", snap.stderr, RESULT_STDERR_MAX, RESULT_STDERR_MAX_LINES)}`;
125
+ }
126
+ return text;
127
+ }
128
+
129
+ export function buildKillReport(results: ReadonlyArray<KillResult>) {
130
+ return results
131
+ .map((entry) => {
132
+ if (entry.killed) {
133
+ return `Killed ${entry.id} "${entry.title}" (${entry.exit}).`;
134
+ }
135
+ if (entry.wasRunning) {
136
+ // The natural exit won the race with the kill signal.
137
+ return `${entry.id} "${entry.title}" exited on its own before the kill landed (${entry.exit}).`;
138
+ }
139
+ return `${entry.id} "${entry.title}" was already ${entry.status} (${entry.exit}).`;
140
+ })
141
+ .join("\n");
142
+ }
@@ -1,27 +1,27 @@
1
- /**
2
- * Deferred one-shot delivery map (same semantics as subagents'): a settled
3
- * terminal's result is held here until it is either drained into a follow-up
4
- * message or consumed by a tool call (bg_kill / bg_status) that already
5
- * returned the settlement itself. Keyed by id, so double delivery is
6
- * structurally impossible — whoever drains first wins.
7
- */
8
- export function createDeferredResultDelivery<T extends { id: string }>() {
9
- const pending = new Map<string, T>();
10
-
11
- return {
12
- defer(result: T) {
13
- pending.set(result.id, result);
14
- },
15
- consume(ids: Iterable<string>) {
16
- for (const id of ids) pending.delete(id);
17
- },
18
- drain() {
19
- const results = [...pending.values()];
20
- pending.clear();
21
- return results;
22
- },
23
- clear() {
24
- pending.clear();
25
- },
26
- };
27
- }
1
+ /**
2
+ * Deferred one-shot delivery map (same semantics as subagents'): a settled
3
+ * terminal's result is held here until it is either drained into a follow-up
4
+ * message or consumed by a tool call (bg_kill / bg_status) that already
5
+ * returned the settlement itself. Keyed by id, so double delivery is
6
+ * structurally impossible — whoever drains first wins.
7
+ */
8
+ export function createDeferredResultDelivery<T extends { id: string }>() {
9
+ const pending = new Map<string, T>();
10
+
11
+ return {
12
+ defer(result: T) {
13
+ pending.set(result.id, result);
14
+ },
15
+ consume(ids: Iterable<string>) {
16
+ for (const id of ids) pending.delete(id);
17
+ },
18
+ drain() {
19
+ const results = [...pending.values()];
20
+ pending.clear();
21
+ return results;
22
+ },
23
+ clear() {
24
+ pending.clear();
25
+ },
26
+ };
27
+ }
@@ -1,36 +1,36 @@
1
- /**
2
- * The async entry-point boundary: one ManagedRuntime shared by every tool
3
- * handler, disposed on session_shutdown (which runs the manager finalizer →
4
- * disposeAll → every process tree is killed).
5
- */
6
-
7
- import { Cause, Exit, ManagedRuntime, type Effect } from "effect";
8
- import { TerminalManagerLive } from "./manager.ts";
9
-
10
- export function createTerminalRuntime() {
11
- return ManagedRuntime.make(TerminalManagerLive);
12
- }
13
-
14
- export type TerminalRuntime = ReturnType<typeof createTerminalRuntime>;
15
-
16
- /**
17
- * Run an effect from an async tool handler. Typed failures and defects are
18
- * converted to thrown Errors (what pi's tool contract expects); interruption
19
- * (tool AbortSignal) throws `interruptMessage`.
20
- */
21
- export async function runTool<A, E>(
22
- runtime: TerminalRuntime,
23
- effect: Effect.Effect<A, E>,
24
- options: { signal?: AbortSignal; interruptMessage?: string } = {},
25
- ) {
26
- const exit = await runtime.runPromiseExit(
27
- effect,
28
- options.signal ? { signal: options.signal } : undefined,
29
- );
30
- if (Exit.isSuccess(exit)) return exit.value;
31
- if (Cause.hasInterruptsOnly(exit.cause)) {
32
- throw new Error(options.interruptMessage ?? "Operation was aborted.");
33
- }
34
- const [first] = Cause.prettyErrors(exit.cause);
35
- throw new Error(first?.message ?? Cause.pretty(exit.cause));
36
- }
1
+ /**
2
+ * The async entry-point boundary: one ManagedRuntime shared by every tool
3
+ * handler, disposed on session_shutdown (which runs the manager finalizer →
4
+ * disposeAll → every process tree is killed).
5
+ */
6
+
7
+ import { Cause, Exit, ManagedRuntime, type Effect } from "effect";
8
+ import { TerminalManagerLive } from "./manager.ts";
9
+
10
+ export function createTerminalRuntime() {
11
+ return ManagedRuntime.make(TerminalManagerLive);
12
+ }
13
+
14
+ export type TerminalRuntime = ReturnType<typeof createTerminalRuntime>;
15
+
16
+ /**
17
+ * Run an effect from an async tool handler. Typed failures and defects are
18
+ * converted to thrown Errors (what pi's tool contract expects); interruption
19
+ * (tool AbortSignal) throws `interruptMessage`.
20
+ */
21
+ export async function runTool<A, E>(
22
+ runtime: TerminalRuntime,
23
+ effect: Effect.Effect<A, E>,
24
+ options: { signal?: AbortSignal; interruptMessage?: string } = {},
25
+ ) {
26
+ const exit = await runtime.runPromiseExit(
27
+ effect,
28
+ options.signal ? { signal: options.signal } : undefined,
29
+ );
30
+ if (Exit.isSuccess(exit)) return exit.value;
31
+ if (Cause.hasInterruptsOnly(exit.cause)) {
32
+ throw new Error(options.interruptMessage ?? "Operation was aborted.");
33
+ }
34
+ const [first] = Cause.prettyErrors(exit.cause);
35
+ throw new Error(first?.message ?? Cause.pretty(exit.cause));
36
+ }