mu-agent 0.1.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 (280) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -0
  4. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
  5. package/dist/bundle/chunks/anthropic.js +75 -0
  6. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +2 -0
  7. package/dist/bundle/chunks/bedrock-converse-stream.js +83 -0
  8. package/dist/bundle/chunks/chunk-4L3WN2XY.js +2 -0
  9. package/dist/bundle/chunks/chunk-6CYCI3EJ.js +12 -0
  10. package/dist/bundle/chunks/chunk-6R7UI4IY.js +2 -0
  11. package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  12. package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
  13. package/dist/bundle/chunks/chunk-DRQDKNXA.js +2 -0
  14. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
  15. package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
  16. package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
  17. package/dist/bundle/chunks/chunk-NUHFSC37.js +25 -0
  18. package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
  19. package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
  20. package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
  21. package/dist/bundle/chunks/chunk-QB4HYZZE.js +1561 -0
  22. package/dist/bundle/chunks/chunk-RCZIEVGO.js +11 -0
  23. package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
  24. package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
  25. package/dist/bundle/chunks/chunk-TRDNDS6A.js +4 -0
  26. package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  27. package/dist/bundle/chunks/chunk-WIUZI2CJ.js +11 -0
  28. package/dist/bundle/chunks/chunk-XDUWOHPL.js +6 -0
  29. package/dist/bundle/chunks/chunk-XNGRGP62.js +2 -0
  30. package/dist/bundle/chunks/chunk-Z2DUOAN4.js +2 -0
  31. package/dist/bundle/chunks/github-copilot.js +2 -0
  32. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
  33. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
  34. package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
  35. package/dist/bundle/chunks/image-resize-worker.js +2 -0
  36. package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
  37. package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
  38. package/dist/bundle/chunks/kimi-coding.js +2 -0
  39. package/dist/bundle/chunks/lib-HGQMNYVT.js +9 -0
  40. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
  41. package/dist/bundle/chunks/multipart-parser-SQ6GDJL7.js +3 -0
  42. package/dist/bundle/chunks/node-O3RIBJRH.js +16 -0
  43. package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
  44. package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
  45. package/dist/bundle/chunks/openai-codex.js +75 -0
  46. package/dist/bundle/chunks/openai-completions-KQPOPEVM.js +7 -0
  47. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
  48. package/dist/bundle/chunks/openrouter-images-N4XVVFSD.js +2 -0
  49. package/dist/bundle/chunks/openrouter.js +75 -0
  50. package/dist/bundle/chunks/pi-messages-GFKBWJHZ.js +8 -0
  51. package/dist/bundle/chunks/radius.js +75 -0
  52. package/dist/bundle/chunks/src-UK77SYZE.js +4 -0
  53. package/dist/bundle/chunks/virtual-modules-ROULVYS3.js +2 -0
  54. package/dist/bundle/chunks/xai.js +2 -0
  55. package/dist/bundle/cli.js +3 -0
  56. package/dist/bundle/index.js +2 -0
  57. package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  58. package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  59. package/dist/bundle/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  60. package/dist/bundle/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  61. package/dist/bundle/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  62. package/dist/bundle/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  63. package/dist/bundle/rpc-entry.js +3 -0
  64. package/dist/core/export-html/template.css +1066 -0
  65. package/dist/core/export-html/template.html +55 -0
  66. package/dist/core/export-html/template.js +1864 -0
  67. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  68. package/dist/core/export-html/vendor/marked.min.js +78 -0
  69. package/dist/modes/interactive/assets/clankolas.png +0 -0
  70. package/dist/modes/interactive/theme/dark.json +91 -0
  71. package/dist/modes/interactive/theme/light.json +90 -0
  72. package/dist/modes/interactive/theme/theme-controller.d.ts +41 -0
  73. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -0
  74. package/dist/modes/interactive/theme/theme-controller.js +130 -0
  75. package/dist/modes/interactive/theme/theme-controller.js.map +1 -0
  76. package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
  77. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
  78. package/dist/modes/interactive/theme/theme-json.js +130 -0
  79. package/dist/modes/interactive/theme/theme-json.js.map +1 -0
  80. package/dist/modes/interactive/theme/theme-schema.json +356 -0
  81. package/dist/modes/interactive/theme/theme.d.ts +131 -0
  82. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  83. package/dist/modes/interactive/theme/theme.js +991 -0
  84. package/dist/modes/interactive/theme/theme.js.map +1 -0
  85. package/dist/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  86. package/dist/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  87. package/dist/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  88. package/dist/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  89. package/dist/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  90. package/dist/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  91. package/docs/compaction.md +444 -0
  92. package/docs/containerization.md +156 -0
  93. package/docs/custom-provider.md +786 -0
  94. package/docs/development.md +90 -0
  95. package/docs/docs.json +160 -0
  96. package/docs/environment-variables.md +100 -0
  97. package/docs/extensions.md +3037 -0
  98. package/docs/images/doom-extension.png +0 -0
  99. package/docs/images/exy.png +0 -0
  100. package/docs/images/interactive-mode.png +0 -0
  101. package/docs/images/tree-view.png +0 -0
  102. package/docs/index.md +84 -0
  103. package/docs/json.md +98 -0
  104. package/docs/keybindings.md +239 -0
  105. package/docs/llama-cpp.md +101 -0
  106. package/docs/models.md +605 -0
  107. package/docs/packages.md +228 -0
  108. package/docs/prompt-templates.md +96 -0
  109. package/docs/providers.md +317 -0
  110. package/docs/quickstart.md +167 -0
  111. package/docs/rpc.md +1618 -0
  112. package/docs/sdk.md +1226 -0
  113. package/docs/security.md +59 -0
  114. package/docs/session-format.md +480 -0
  115. package/docs/sessions.md +172 -0
  116. package/docs/settings.md +428 -0
  117. package/docs/shell-aliases.md +13 -0
  118. package/docs/skills.md +232 -0
  119. package/docs/terminal-setup.md +192 -0
  120. package/docs/termux.md +126 -0
  121. package/docs/themes.md +322 -0
  122. package/docs/tmux.md +63 -0
  123. package/docs/tui.md +961 -0
  124. package/docs/usage.md +312 -0
  125. package/docs/windows.md +39 -0
  126. package/examples/README.md +28 -0
  127. package/examples/extensions/README.md +212 -0
  128. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  129. package/examples/extensions/bash-spawn-hook.ts +30 -0
  130. package/examples/extensions/bookmark.ts +50 -0
  131. package/examples/extensions/border-status-editor.ts +150 -0
  132. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  133. package/examples/extensions/claude-rules.ts +86 -0
  134. package/examples/extensions/commands.ts +72 -0
  135. package/examples/extensions/confirm-destructive.ts +59 -0
  136. package/examples/extensions/custom-compaction.ts +117 -0
  137. package/examples/extensions/custom-footer.ts +64 -0
  138. package/examples/extensions/custom-header.ts +73 -0
  139. package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
  140. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  141. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  142. package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
  143. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  144. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  145. package/examples/extensions/dirty-repo-guard.ts +56 -0
  146. package/examples/extensions/doom-overlay/README.md +46 -0
  147. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  148. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  149. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  150. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  151. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  152. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  153. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  154. package/examples/extensions/doom-overlay/index.ts +74 -0
  155. package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
  156. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  157. package/examples/extensions/dynamic-resources/dynamic.json +81 -0
  158. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  159. package/examples/extensions/dynamic-resources/index.ts +15 -0
  160. package/examples/extensions/dynamic-tools.ts +74 -0
  161. package/examples/extensions/entry-renderer.ts +41 -0
  162. package/examples/extensions/event-bus.ts +43 -0
  163. package/examples/extensions/file-trigger.ts +41 -0
  164. package/examples/extensions/git-checkpoint.ts +53 -0
  165. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  166. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  167. package/examples/extensions/gondolin/index.ts +531 -0
  168. package/examples/extensions/gondolin/package-lock.json +185 -0
  169. package/examples/extensions/gondolin/package.json +19 -0
  170. package/examples/extensions/handoff.ts +190 -0
  171. package/examples/extensions/hello.ts +26 -0
  172. package/examples/extensions/hidden-thinking-label.ts +53 -0
  173. package/examples/extensions/inline-bash.ts +94 -0
  174. package/examples/extensions/input-transform-streaming.ts +39 -0
  175. package/examples/extensions/input-transform.ts +43 -0
  176. package/examples/extensions/interactive-shell.ts +196 -0
  177. package/examples/extensions/mac-system-theme.ts +47 -0
  178. package/examples/extensions/message-renderer.ts +59 -0
  179. package/examples/extensions/minimal-mode.ts +426 -0
  180. package/examples/extensions/modal-editor.ts +85 -0
  181. package/examples/extensions/model-status.ts +31 -0
  182. package/examples/extensions/notify.ts +57 -0
  183. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  184. package/examples/extensions/overlay-test.ts +153 -0
  185. package/examples/extensions/permission-gate.ts +34 -0
  186. package/examples/extensions/pirate.ts +47 -0
  187. package/examples/extensions/plan-mode/README.md +66 -0
  188. package/examples/extensions/plan-mode/index.ts +390 -0
  189. package/examples/extensions/plan-mode/utils.ts +168 -0
  190. package/examples/extensions/preset.ts +436 -0
  191. package/examples/extensions/project-trust.ts +64 -0
  192. package/examples/extensions/prompt-customizer.ts +49 -0
  193. package/examples/extensions/protected-paths.ts +30 -0
  194. package/examples/extensions/provider-payload.ts +18 -0
  195. package/examples/extensions/qna.ts +118 -0
  196. package/examples/extensions/question.ts +286 -0
  197. package/examples/extensions/questionnaire.ts +448 -0
  198. package/examples/extensions/rainbow-editor.ts +88 -0
  199. package/examples/extensions/reload-runtime.ts +37 -0
  200. package/examples/extensions/rpc-demo.ts +118 -0
  201. package/examples/extensions/sandbox/index.ts +321 -0
  202. package/examples/extensions/sandbox/package-lock.json +92 -0
  203. package/examples/extensions/sandbox/package.json +19 -0
  204. package/examples/extensions/send-user-message.ts +97 -0
  205. package/examples/extensions/session-name.ts +27 -0
  206. package/examples/extensions/shutdown-command.ts +63 -0
  207. package/examples/extensions/snake.ts +343 -0
  208. package/examples/extensions/space-invaders.ts +560 -0
  209. package/examples/extensions/ssh.ts +220 -0
  210. package/examples/extensions/status-line.ts +32 -0
  211. package/examples/extensions/structured-output.ts +65 -0
  212. package/examples/extensions/subagent/README.md +177 -0
  213. package/examples/extensions/subagent/agents/planner.md +37 -0
  214. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  215. package/examples/extensions/subagent/agents/scout.md +50 -0
  216. package/examples/extensions/subagent/agents/worker.md +24 -0
  217. package/examples/extensions/subagent/agents.ts +157 -0
  218. package/examples/extensions/subagent/index.ts +1038 -0
  219. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  220. package/examples/extensions/subagent/prompts/implement.md +10 -0
  221. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  222. package/examples/extensions/summarize.ts +199 -0
  223. package/examples/extensions/system-prompt-header.ts +17 -0
  224. package/examples/extensions/tic-tac-toe.ts +1008 -0
  225. package/examples/extensions/timed-confirm.ts +70 -0
  226. package/examples/extensions/titlebar-spinner.ts +58 -0
  227. package/examples/extensions/todo.ts +297 -0
  228. package/examples/extensions/tool-override.ts +144 -0
  229. package/examples/extensions/tools.ts +146 -0
  230. package/examples/extensions/trigger-compact.ts +50 -0
  231. package/examples/extensions/truncated-tool.ts +195 -0
  232. package/examples/extensions/widget-placement.ts +9 -0
  233. package/examples/extensions/with-deps/index.ts +32 -0
  234. package/examples/extensions/with-deps/package-lock.json +31 -0
  235. package/examples/extensions/with-deps/package.json +22 -0
  236. package/examples/extensions/working-indicator.ts +123 -0
  237. package/examples/extensions/working-message-test.ts +25 -0
  238. package/examples/plugins/pi-example-plugin/README.md +38 -0
  239. package/examples/plugins/pi-example-plugin/package.json +10 -0
  240. package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
  241. package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
  242. package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
  243. package/examples/rpc-extension-ui.ts +641 -0
  244. package/examples/sdk/01-minimal.ts +26 -0
  245. package/examples/sdk/02-custom-model.ts +49 -0
  246. package/examples/sdk/03-custom-prompt.ts +75 -0
  247. package/examples/sdk/04-skills.ts +55 -0
  248. package/examples/sdk/05-tools.ts +48 -0
  249. package/examples/sdk/06-extensions.ts +99 -0
  250. package/examples/sdk/07-context-files.ts +47 -0
  251. package/examples/sdk/08-prompt-templates.ts +51 -0
  252. package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  253. package/examples/sdk/10-settings.ts +53 -0
  254. package/examples/sdk/11-sessions.ts +52 -0
  255. package/examples/sdk/12-full-control.ts +74 -0
  256. package/examples/sdk/13-session-runtime.ts +67 -0
  257. package/examples/sdk/README.md +140 -0
  258. package/judge/THIRD_PARTY_NOTICES.md +35 -0
  259. package/judge/agents/browser.md +17 -0
  260. package/judge/agents/investigator.md +16 -0
  261. package/judge/agents/planner.md +15 -0
  262. package/judge/agents/reviewer.md +17 -0
  263. package/judge/agents/scout.md +19 -0
  264. package/judge/agents/worker.md +15 -0
  265. package/judge/dist/chrome.js +326 -0
  266. package/judge/dist/kyrn-judge.js +52542 -0
  267. package/judge/package.json +6 -0
  268. package/judge/prompts/implement-and-review.md +14 -0
  269. package/judge/prompts/implement.md +15 -0
  270. package/judge/prompts/init.md +18 -0
  271. package/judge/prompts/scout-and-plan.md +12 -0
  272. package/judge/skills/mu-browser/SKILL.md +30 -0
  273. package/judge/skills/mu-github/SKILL.md +90 -0
  274. package/kyrn/bin/kyrn-doctor +202 -0
  275. package/kyrn/bin/kyrn-judge-local +131 -0
  276. package/kyrn/bin/kyrn-ledger +90 -0
  277. package/kyrn/bin/mu.mjs +1120 -0
  278. package/kyrn/local-judge/requirements.lock.txt +26 -0
  279. package/kyrn/local-judge/server.py +332 -0
  280. package/package.json +45 -0
@@ -0,0 +1,13 @@
1
+ import { type Context, defineService, type ReplicatedState } from "@earendil-works/chord";
2
+
3
+ export interface ExampleFacetReply {
4
+ readonly message: string;
5
+ readonly workerActivations: number;
6
+ }
7
+
8
+ export interface ExampleFacetService {
9
+ readonly workerActivations: ReplicatedState<{ count: number }>;
10
+ greet(input: { readonly name: string }, context: Context): Promise<ExampleFacetReply>;
11
+ }
12
+
13
+ export const ExampleFacetService = defineService<ExampleFacetService>("pi.example-plugin.greeting");
@@ -0,0 +1,23 @@
1
+ import { defineFacet } from "@earendil-works/chord";
2
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
+ import { ExampleFacetService } from "./contract.ts";
4
+
5
+ export default defineFacet({
6
+ id: "@earendil-works/pi-example-plugin/session",
7
+ setup(env) {
8
+ const workerActivations = env.replicatedState({ count: 0 });
9
+ env.provide(ExampleFacetService, {
10
+ workerActivations,
11
+ async greet({ name }) {
12
+ return {
13
+ message: `Hello ${name} from the bundled Session worker facet!!!`,
14
+ workerActivations: workerActivations.value.count,
15
+ };
16
+ },
17
+ });
18
+ env.onActivate(() => {
19
+ workerActivations.state.count += 1;
20
+ workerActivations.publish(BACKGROUND_CONTEXT);
21
+ });
22
+ },
23
+ });
@@ -0,0 +1,31 @@
1
+ import { defineFacet } from "@earendil-works/chord";
2
+ import { AgentController, PresentationUI, SlashCommands } from "@earendil-works/pi-coding-agent/experimental/plugin";
3
+ import { ExampleFacetService } from "./contract.ts";
4
+
5
+ export default defineFacet({
6
+ id: "@earendil-works/pi-example-plugin/tui",
7
+ setup(env) {
8
+ const example = env.use(ExampleFacetService);
9
+ const commands = env.use(SlashCommands);
10
+ const controller = env.use(AgentController);
11
+ const ui = env.use(PresentationUI);
12
+ env.onActivate(() => {
13
+ env.own(
14
+ commands.replace({
15
+ name: "hello",
16
+ description: "Call the bundled Session worker facet",
17
+ argumentHint: "<name>",
18
+ async run(args, context) {
19
+ const message = args.length === 0 ? "from the TUI" : args;
20
+ const reply = await example.greet({ name: message }, context);
21
+ ui.showStatus(
22
+ `We got this: ${reply.message} Worker activations: ${reply.workerActivations}.`,
23
+ context,
24
+ );
25
+ return controller.prompt({ message: `pong: ${message}`, images: null }, context);
26
+ },
27
+ }),
28
+ );
29
+ });
30
+ },
31
+ });
@@ -0,0 +1,641 @@
1
+ /**
2
+ * RPC Extension UI Example (TUI)
3
+ *
4
+ * A lightweight TUI chat client that spawns the agent in RPC mode.
5
+ * Demonstrates how to build a custom UI on top of the RPC protocol,
6
+ * including handling extension UI requests (select, confirm, input, editor).
7
+ *
8
+ * Usage: npx tsx examples/rpc-extension-ui.ts
9
+ *
10
+ * Slash commands:
11
+ * /select - demo select dialog
12
+ * /confirm - demo confirm dialog
13
+ * /input - demo input dialog
14
+ * /editor - demo editor dialog
15
+ */
16
+
17
+ import { spawn } from "node:child_process";
18
+ import { dirname, join } from "node:path";
19
+ import * as readline from "node:readline";
20
+ import { fileURLToPath } from "node:url";
21
+ import {
22
+ type Component,
23
+ Container,
24
+ Input,
25
+ matchesKey,
26
+ ProcessTerminal,
27
+ SelectList,
28
+ type TUI,
29
+ TuiMainScreen,
30
+ } from "@earendil-works/pi-tui";
31
+
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+
34
+ // ============================================================================
35
+ // ANSI helpers
36
+ // ============================================================================
37
+
38
+ const GREEN = "\x1b[32m";
39
+ const YELLOW = "\x1b[33m";
40
+ const BLUE = "\x1b[34m";
41
+ const MAGENTA = "\x1b[35m";
42
+ const RED = "\x1b[31m";
43
+ const DIM = "\x1b[2m";
44
+ const BOLD = "\x1b[1m";
45
+ const RESET = "\x1b[0m";
46
+
47
+ // ============================================================================
48
+ // Extension UI request type (subset of rpc-types.ts)
49
+ // ============================================================================
50
+
51
+ interface ExtensionUIRequest {
52
+ type: "extension_ui_request";
53
+ id: string;
54
+ method: string;
55
+ title?: string;
56
+ options?: string[];
57
+ message?: string;
58
+ placeholder?: string;
59
+ prefill?: string;
60
+ notifyType?: "info" | "warning" | "error";
61
+ statusKey?: string;
62
+ statusText?: string;
63
+ widgetKey?: string;
64
+ widgetLines?: string[];
65
+ text?: string;
66
+ }
67
+
68
+ // ============================================================================
69
+ // Output log: accumulates styled lines, renders the tail that fits
70
+ // ============================================================================
71
+
72
+ class OutputLog implements Component {
73
+ private lines: string[] = [];
74
+ private maxLines = 1000;
75
+ private visibleLines = 0;
76
+
77
+ setVisibleLines(n: number): void {
78
+ this.visibleLines = n;
79
+ }
80
+
81
+ append(line: string): void {
82
+ this.lines.push(line);
83
+ if (this.lines.length > this.maxLines) {
84
+ this.lines = this.lines.slice(-this.maxLines);
85
+ }
86
+ }
87
+
88
+ appendRaw(text: string): void {
89
+ if (this.lines.length === 0) {
90
+ this.lines.push(text);
91
+ } else {
92
+ this.lines[this.lines.length - 1] += text;
93
+ }
94
+ }
95
+
96
+ invalidate(): void {}
97
+
98
+ render(width: number): string[] {
99
+ if (this.lines.length === 0) return [""];
100
+ const n = this.visibleLines > 0 ? this.visibleLines : this.lines.length;
101
+ return this.lines.slice(-n).map((l) => l.slice(0, width));
102
+ }
103
+ }
104
+
105
+ // ============================================================================
106
+ // Loading indicator: "Agent: Working." -> ".." -> "..." -> "."
107
+ // ============================================================================
108
+
109
+ class LoadingIndicator implements Component {
110
+ private dots = 1;
111
+ private intervalId: NodeJS.Timeout | null = null;
112
+ private tui: TUI | null = null;
113
+
114
+ start(tui: TUI): void {
115
+ this.tui = tui;
116
+ this.dots = 1;
117
+ this.intervalId = setInterval(() => {
118
+ this.dots = (this.dots % 3) + 1;
119
+ this.tui?.requestRender();
120
+ }, 400);
121
+ }
122
+
123
+ stop(): void {
124
+ if (this.intervalId) {
125
+ clearInterval(this.intervalId);
126
+ this.intervalId = null;
127
+ }
128
+ }
129
+
130
+ invalidate(): void {}
131
+
132
+ render(_width: number): string[] {
133
+ return [`${BLUE}${BOLD}Agent:${RESET} ${DIM}Working${".".repeat(this.dots)}${RESET}`];
134
+ }
135
+ }
136
+
137
+ // ============================================================================
138
+ // Prompt input: label + single-line input
139
+ // ============================================================================
140
+
141
+ class PromptInput implements Component {
142
+ readonly input: Input;
143
+ onCtrlD?: () => void;
144
+
145
+ constructor() {
146
+ this.input = new Input();
147
+ }
148
+
149
+ handleInput(data: string): void {
150
+ if (matchesKey(data, "ctrl+d")) {
151
+ this.onCtrlD?.();
152
+ return;
153
+ }
154
+ this.input.handleInput(data);
155
+ }
156
+
157
+ invalidate(): void {
158
+ this.input.invalidate();
159
+ }
160
+
161
+ render(width: number): string[] {
162
+ return [`${GREEN}${BOLD}You:${RESET}`, ...this.input.render(width)];
163
+ }
164
+ }
165
+
166
+ // ============================================================================
167
+ // Dialog components: replace the prompt input during interactive requests
168
+ // ============================================================================
169
+
170
+ class SelectDialog implements Component {
171
+ private list: SelectList;
172
+ private title: string;
173
+ onSelect?: (value: string) => void;
174
+ onCancel?: () => void;
175
+
176
+ constructor(title: string, options: string[]) {
177
+ this.title = title;
178
+ const items = options.map((o) => ({ value: o, label: o }));
179
+ this.list = new SelectList(items, Math.min(items.length, 8), {
180
+ selectedPrefix: (t) => `${MAGENTA}${t}${RESET}`,
181
+ selectedText: (t) => `${MAGENTA}${t}${RESET}`,
182
+ description: (t) => `${DIM}${t}${RESET}`,
183
+ scrollInfo: (t) => `${DIM}${t}${RESET}`,
184
+ noMatch: (t) => `${YELLOW}${t}${RESET}`,
185
+ });
186
+ this.list.onSelect = (item) => this.onSelect?.(item.value);
187
+ this.list.onCancel = () => this.onCancel?.();
188
+ }
189
+
190
+ handleInput(data: string): void {
191
+ this.list.handleInput(data);
192
+ }
193
+
194
+ invalidate(): void {
195
+ this.list.invalidate();
196
+ }
197
+
198
+ render(width: number): string[] {
199
+ return [
200
+ `${MAGENTA}${BOLD}${this.title}${RESET}`,
201
+ ...this.list.render(width),
202
+ `${DIM}Up/Down, Enter to select, Esc to cancel${RESET}`,
203
+ ];
204
+ }
205
+ }
206
+
207
+ class InputDialog implements Component {
208
+ private dialogInput: Input;
209
+ private title: string;
210
+ onCtrlD?: () => void;
211
+
212
+ constructor(title: string, prefill?: string) {
213
+ this.title = title;
214
+ this.dialogInput = new Input();
215
+ if (prefill) this.dialogInput.setValue(prefill);
216
+ }
217
+
218
+ set onSubmit(fn: ((value: string) => void) | undefined) {
219
+ this.dialogInput.onSubmit = fn;
220
+ }
221
+
222
+ set onEscape(fn: (() => void) | undefined) {
223
+ this.dialogInput.onEscape = fn;
224
+ }
225
+
226
+ get inputComponent(): Input {
227
+ return this.dialogInput;
228
+ }
229
+
230
+ handleInput(data: string): void {
231
+ if (matchesKey(data, "ctrl+d")) {
232
+ this.onCtrlD?.();
233
+ return;
234
+ }
235
+ this.dialogInput.handleInput(data);
236
+ }
237
+
238
+ invalidate(): void {
239
+ this.dialogInput.invalidate();
240
+ }
241
+
242
+ render(width: number): string[] {
243
+ return [
244
+ `${MAGENTA}${BOLD}${this.title}${RESET}`,
245
+ ...this.dialogInput.render(width),
246
+ `${DIM}Enter to submit, Esc to cancel${RESET}`,
247
+ ];
248
+ }
249
+ }
250
+
251
+ // ============================================================================
252
+ // Main
253
+ // ============================================================================
254
+
255
+ async function main() {
256
+ const extensionPath = join(__dirname, "extensions/rpc-demo.ts");
257
+ const cliPath = join(__dirname, "../dist/cli.js");
258
+
259
+ const agent = spawn(
260
+ "node",
261
+ [cliPath, "--mode", "rpc", "--no-session", "--no-extension", "--extension", extensionPath],
262
+ { stdio: ["pipe", "pipe", "pipe"] },
263
+ );
264
+
265
+ let stderr = "";
266
+ agent.stderr?.on("data", (data: Buffer) => {
267
+ stderr += data.toString();
268
+ });
269
+
270
+ await new Promise((resolve) => setTimeout(resolve, 500));
271
+ if (agent.exitCode !== null) {
272
+ console.error(`Agent exited immediately. Stderr:\n${stderr}`);
273
+ process.exit(1);
274
+ }
275
+
276
+ // -- TUI setup --
277
+
278
+ const terminal = new ProcessTerminal();
279
+ const tui: TUI = new TuiMainScreen(terminal);
280
+
281
+ const outputLog = new OutputLog();
282
+ const loadingIndicator = new LoadingIndicator();
283
+ const promptInput = new PromptInput();
284
+
285
+ const root = new Container();
286
+ root.addChild(outputLog);
287
+ root.addChild(promptInput);
288
+
289
+ tui.addChild(root);
290
+ tui.setFocus(promptInput.input);
291
+
292
+ // -- Agent communication --
293
+
294
+ function send(obj: Record<string, unknown>): void {
295
+ agent.stdin!.write(`${JSON.stringify(obj)}\n`);
296
+ }
297
+
298
+ let isStreaming = false;
299
+ let hasTextOutput = false;
300
+
301
+ function exit(): void {
302
+ tui.stop();
303
+ agent.kill("SIGTERM");
304
+ process.exit(0);
305
+ }
306
+
307
+ // -- Bottom area management --
308
+ // The bottom of the screen is either the prompt input or a dialog.
309
+ // These helpers swap between them.
310
+
311
+ let activeDialog: Component | null = null;
312
+
313
+ function setBottomComponent(component: Component): void {
314
+ root.clear();
315
+ root.addChild(outputLog);
316
+ if (isStreaming) root.addChild(loadingIndicator);
317
+ root.addChild(component);
318
+ tui.setFocus(component);
319
+ tui.requestRender();
320
+ }
321
+
322
+ function showPrompt(): void {
323
+ activeDialog = null;
324
+ setBottomComponent(promptInput);
325
+ tui.setFocus(promptInput.input);
326
+ }
327
+
328
+ function showDialog(dialog: Component): void {
329
+ activeDialog = dialog;
330
+ setBottomComponent(dialog);
331
+ }
332
+
333
+ function showLoading(): void {
334
+ if (!isStreaming) {
335
+ isStreaming = true;
336
+ hasTextOutput = false;
337
+ root.clear();
338
+ root.addChild(outputLog);
339
+ root.addChild(loadingIndicator);
340
+ root.addChild(activeDialog ?? promptInput);
341
+ if (!activeDialog) tui.setFocus(promptInput.input);
342
+ loadingIndicator.start(tui);
343
+ tui.requestRender();
344
+ }
345
+ }
346
+
347
+ function hideLoading(): void {
348
+ loadingIndicator.stop();
349
+ root.clear();
350
+ root.addChild(outputLog);
351
+ root.addChild(activeDialog ?? promptInput);
352
+ if (!activeDialog) tui.setFocus(promptInput.input);
353
+ tui.requestRender();
354
+ }
355
+
356
+ // -- Extension UI dialog handling --
357
+
358
+ function showSelectDialog(title: string, options: string[], onDone: (value: string | undefined) => void): void {
359
+ const dialog = new SelectDialog(title, options);
360
+ dialog.onSelect = (value) => {
361
+ showPrompt();
362
+ onDone(value);
363
+ };
364
+ dialog.onCancel = () => {
365
+ showPrompt();
366
+ onDone(undefined);
367
+ };
368
+ showDialog(dialog);
369
+ }
370
+
371
+ function showInputDialog(title: string, prefill?: string, onDone?: (value: string | undefined) => void): void {
372
+ const dialog = new InputDialog(title, prefill);
373
+ dialog.onSubmit = (value) => {
374
+ showPrompt();
375
+ onDone?.(value.trim() || undefined);
376
+ };
377
+ dialog.onEscape = () => {
378
+ showPrompt();
379
+ onDone?.(undefined);
380
+ };
381
+ dialog.onCtrlD = exit;
382
+ showDialog(dialog);
383
+ tui.setFocus(dialog.inputComponent);
384
+ }
385
+
386
+ function handleExtensionUI(req: ExtensionUIRequest): void {
387
+ const { id, method } = req;
388
+
389
+ switch (method) {
390
+ // Dialog methods: replace prompt with interactive component
391
+ case "select": {
392
+ showSelectDialog(req.title ?? "Select", req.options ?? [], (value) => {
393
+ if (value !== undefined) {
394
+ send({ type: "extension_ui_response", id, value });
395
+ } else {
396
+ send({ type: "extension_ui_response", id, cancelled: true });
397
+ }
398
+ });
399
+ break;
400
+ }
401
+
402
+ case "confirm": {
403
+ const title = req.message ? `${req.title}: ${req.message}` : (req.title ?? "Confirm");
404
+ showSelectDialog(title, ["Yes", "No"], (value) => {
405
+ send({ type: "extension_ui_response", id, confirmed: value === "Yes" });
406
+ });
407
+ break;
408
+ }
409
+
410
+ case "input": {
411
+ const title = req.placeholder ? `${req.title} (${req.placeholder})` : (req.title ?? "Input");
412
+ showInputDialog(title, undefined, (value) => {
413
+ if (value !== undefined) {
414
+ send({ type: "extension_ui_response", id, value });
415
+ } else {
416
+ send({ type: "extension_ui_response", id, cancelled: true });
417
+ }
418
+ });
419
+ break;
420
+ }
421
+
422
+ case "editor": {
423
+ const prefill = req.prefill?.replace(/\n/g, " ");
424
+ showInputDialog(req.title ?? "Editor", prefill, (value) => {
425
+ if (value !== undefined) {
426
+ send({ type: "extension_ui_response", id, value });
427
+ } else {
428
+ send({ type: "extension_ui_response", id, cancelled: true });
429
+ }
430
+ });
431
+ break;
432
+ }
433
+
434
+ // Fire-and-forget methods: display as notification
435
+ case "notify": {
436
+ const notifyType = (req.notifyType as string) ?? "info";
437
+ const color = notifyType === "error" ? RED : notifyType === "warning" ? YELLOW : MAGENTA;
438
+ outputLog.append(`${color}${BOLD}Notification:${RESET} ${req.message}`);
439
+ tui.requestRender();
440
+ break;
441
+ }
442
+
443
+ case "setStatus":
444
+ outputLog.append(
445
+ `${MAGENTA}${BOLD}Notification:${RESET} ${DIM}[status: ${req.statusKey}]${RESET} ${req.statusText ?? "(cleared)"}`,
446
+ );
447
+ tui.requestRender();
448
+ break;
449
+
450
+ case "setWidget": {
451
+ const lines = req.widgetLines;
452
+ if (lines && lines.length > 0) {
453
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} ${DIM}[widget: ${req.widgetKey}]${RESET}`);
454
+ for (const wl of lines) {
455
+ outputLog.append(` ${DIM}${wl}${RESET}`);
456
+ }
457
+ tui.requestRender();
458
+ }
459
+ break;
460
+ }
461
+
462
+ case "set_editor_text":
463
+ promptInput.input.setValue((req.text as string) ?? "");
464
+ tui.requestRender();
465
+ break;
466
+ }
467
+ }
468
+
469
+ // -- Slash commands (local, not sent to agent) --
470
+
471
+ function handleSlashCommand(cmd: string): boolean {
472
+ switch (cmd) {
473
+ case "/select":
474
+ showSelectDialog("Pick a color", ["Red", "Green", "Blue", "Yellow"], (value) => {
475
+ if (value) {
476
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} You picked: ${value}`);
477
+ } else {
478
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Selection cancelled`);
479
+ }
480
+ tui.requestRender();
481
+ });
482
+ return true;
483
+
484
+ case "/confirm":
485
+ showSelectDialog("Are you sure?", ["Yes", "No"], (value) => {
486
+ const confirmed = value === "Yes";
487
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Confirmed: ${confirmed}`);
488
+ tui.requestRender();
489
+ });
490
+ return true;
491
+
492
+ case "/input":
493
+ showInputDialog("Enter your name", undefined, (value) => {
494
+ if (value) {
495
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} You entered: ${value}`);
496
+ } else {
497
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Input cancelled`);
498
+ }
499
+ tui.requestRender();
500
+ });
501
+ return true;
502
+
503
+ case "/editor":
504
+ showInputDialog("Edit text", "Hello, world!", (value) => {
505
+ if (value) {
506
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Submitted: ${value}`);
507
+ } else {
508
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Editor cancelled`);
509
+ }
510
+ tui.requestRender();
511
+ });
512
+ return true;
513
+
514
+ default:
515
+ return false;
516
+ }
517
+ }
518
+
519
+ // -- Process agent stdout --
520
+
521
+ const stdoutRl = readline.createInterface({ input: agent.stdout!, terminal: false });
522
+
523
+ stdoutRl.on("line", (line) => {
524
+ let data: Record<string, unknown>;
525
+ try {
526
+ data = JSON.parse(line);
527
+ } catch {
528
+ return;
529
+ }
530
+
531
+ if (data.type === "response" && !data.success) {
532
+ outputLog.append(`${RED}[error]${RESET} ${data.command}: ${data.error}`);
533
+ tui.requestRender();
534
+ return;
535
+ }
536
+
537
+ if (data.type === "agent_start") {
538
+ showLoading();
539
+ return;
540
+ }
541
+
542
+ if (data.type === "extension_ui_request") {
543
+ handleExtensionUI(data as unknown as ExtensionUIRequest);
544
+ return;
545
+ }
546
+
547
+ if (data.type === "message_update") {
548
+ const evt = data.assistantMessageEvent as Record<string, unknown> | undefined;
549
+ if (evt?.type === "text_delta") {
550
+ if (!hasTextOutput) {
551
+ hasTextOutput = true;
552
+ outputLog.append("");
553
+ outputLog.append(`${BLUE}${BOLD}Agent:${RESET}`);
554
+ }
555
+ const delta = evt.delta as string;
556
+ const parts = delta.split("\n");
557
+ for (let i = 0; i < parts.length; i++) {
558
+ if (i > 0) outputLog.append("");
559
+ if (parts[i]) outputLog.appendRaw(parts[i]);
560
+ }
561
+ tui.requestRender();
562
+ }
563
+ return;
564
+ }
565
+
566
+ if (data.type === "tool_execution_start") {
567
+ outputLog.append(`${DIM}[tool: ${data.toolName}]${RESET}`);
568
+ tui.requestRender();
569
+ return;
570
+ }
571
+
572
+ if (data.type === "tool_execution_end") {
573
+ const result = JSON.stringify(data.result).slice(0, 120);
574
+ outputLog.append(`${DIM}[result: ${result}...]${RESET}`);
575
+ tui.requestRender();
576
+ return;
577
+ }
578
+
579
+ if (data.type === "agent_settled") {
580
+ isStreaming = false;
581
+ hideLoading();
582
+ outputLog.append("");
583
+ tui.requestRender();
584
+ return;
585
+ }
586
+ });
587
+
588
+ // -- User input --
589
+
590
+ promptInput.input.onSubmit = (value) => {
591
+ const trimmed = value.trim();
592
+ if (!trimmed) return;
593
+
594
+ promptInput.input.setValue("");
595
+
596
+ if (handleSlashCommand(trimmed)) {
597
+ outputLog.append(`${GREEN}${BOLD}You:${RESET} ${trimmed}`);
598
+ tui.requestRender();
599
+ return;
600
+ }
601
+
602
+ outputLog.append(`${GREEN}${BOLD}You:${RESET} ${trimmed}`);
603
+ send({ type: "prompt", message: trimmed });
604
+ tui.requestRender();
605
+ };
606
+
607
+ promptInput.onCtrlD = exit;
608
+
609
+ promptInput.input.onEscape = () => {
610
+ if (isStreaming) {
611
+ send({ type: "abort" });
612
+ outputLog.append(`${YELLOW}[aborted]${RESET}`);
613
+ tui.requestRender();
614
+ } else {
615
+ exit();
616
+ }
617
+ };
618
+
619
+ // -- Agent exit --
620
+
621
+ agent.on("exit", (code) => {
622
+ tui.stop();
623
+ if (stderr) console.error(stderr);
624
+ console.log(`Agent exited with code ${code}`);
625
+ process.exit(code ?? 0);
626
+ });
627
+
628
+ // -- Start --
629
+
630
+ outputLog.append(`${BOLD}RPC Chat${RESET}`);
631
+ outputLog.append(`${DIM}Type a message and press Enter. Esc to abort or exit. Ctrl+D to quit.${RESET}`);
632
+ outputLog.append(`${DIM}Slash commands: /select /confirm /input /editor${RESET}`);
633
+ outputLog.append("");
634
+
635
+ tui.start();
636
+ }
637
+
638
+ main().catch((err) => {
639
+ console.error(err);
640
+ process.exit(1);
641
+ });