ubume 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 (182) hide show
  1. package/README.md +88 -0
  2. package/bin/codexa-local-harness-bridge.js +3 -0
  3. package/bin/codexa.js +28 -0
  4. package/bin/ubume-local-harness-bridge.js +325 -0
  5. package/bin/ubume.js +398 -0
  6. package/package.json +66 -0
  7. package/src/app.tsx +5759 -0
  8. package/src/commands/handler.ts +889 -0
  9. package/src/config/appVersion.ts +69 -0
  10. package/src/config/buildInfo.ts +3 -0
  11. package/src/config/launchArgs.ts +196 -0
  12. package/src/config/layeredConfig.ts +853 -0
  13. package/src/config/legacyEnv.ts +16 -0
  14. package/src/config/persistence.ts +377 -0
  15. package/src/config/runtimeConfig.ts +558 -0
  16. package/src/config/settings.ts +405 -0
  17. package/src/config/toml-serialize.ts +109 -0
  18. package/src/config/trustStore.ts +84 -0
  19. package/src/config/updateCheckCache.ts +85 -0
  20. package/src/core/README.md +55 -0
  21. package/src/core/agent/loop.ts +464 -0
  22. package/src/core/agent/protocol.ts +345 -0
  23. package/src/core/agent/tools.ts +423 -0
  24. package/src/core/auth/codexAuth.ts +359 -0
  25. package/src/core/codex/codexExecArgs.ts +166 -0
  26. package/src/core/codex/codexLaunch.ts +163 -0
  27. package/src/core/codex/codexPrompt.ts +429 -0
  28. package/src/core/debug/inputDebug.ts +51 -0
  29. package/src/core/debug/localStreamDebug.ts +50 -0
  30. package/src/core/debug/modelStateDebug.ts +35 -0
  31. package/src/core/executables/antigravityExecutable.ts +48 -0
  32. package/src/core/executables/claudeExecutable.ts +63 -0
  33. package/src/core/executables/codexExecutable.ts +160 -0
  34. package/src/core/executables/executableResolver.ts +164 -0
  35. package/src/core/executables/geminiExecutable.ts +78 -0
  36. package/src/core/models/codexCapabilities.ts +97 -0
  37. package/src/core/models/codexModelCapabilities.ts +624 -0
  38. package/src/core/models/codexModelsCacheSeed.ts +153 -0
  39. package/src/core/models/modelSpecs.ts +303 -0
  40. package/src/core/models/providerModelCache.ts +94 -0
  41. package/src/core/perf/profiler.ts +125 -0
  42. package/src/core/perf/renderDebug.ts +398 -0
  43. package/src/core/process/CommandRunner.ts +280 -0
  44. package/src/core/process/processValidation.ts +111 -0
  45. package/src/core/providerLauncher/launcher.ts +220 -0
  46. package/src/core/providerLauncher/registry.ts +354 -0
  47. package/src/core/providerLauncher/types.ts +95 -0
  48. package/src/core/providerLauncher/workspaceConfig.ts +487 -0
  49. package/src/core/providerRuntime/anthropic.ts +580 -0
  50. package/src/core/providerRuntime/antigravity.ts +500 -0
  51. package/src/core/providerRuntime/capabilityProfile.ts +383 -0
  52. package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
  53. package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
  54. package/src/core/providerRuntime/codexaCupy.ts +97 -0
  55. package/src/core/providerRuntime/codexaNative.ts +425 -0
  56. package/src/core/providerRuntime/contextMetadata.ts +397 -0
  57. package/src/core/providerRuntime/gemini.ts +789 -0
  58. package/src/core/providerRuntime/lmstudio.ts +118 -0
  59. package/src/core/providerRuntime/local.ts +770 -0
  60. package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
  61. package/src/core/providerRuntime/localOutputBudget.ts +17 -0
  62. package/src/core/providerRuntime/mistralVibe.ts +667 -0
  63. package/src/core/providerRuntime/models.ts +175 -0
  64. package/src/core/providerRuntime/reasoning.ts +20 -0
  65. package/src/core/providerRuntime/registry.ts +286 -0
  66. package/src/core/providerRuntime/types.ts +142 -0
  67. package/src/core/providerRuntime/unsloth.ts +216 -0
  68. package/src/core/providers/codexJsonStream.ts +305 -0
  69. package/src/core/providers/codexSubprocess.ts +378 -0
  70. package/src/core/providers/codexTranscript.ts +695 -0
  71. package/src/core/providers/openaiNative.ts +13 -0
  72. package/src/core/providers/registry.ts +21 -0
  73. package/src/core/providers/types.ts +91 -0
  74. package/src/core/shared/attachments.ts +101 -0
  75. package/src/core/shared/cleanupFastFail.ts +67 -0
  76. package/src/core/shared/clipboard.ts +24 -0
  77. package/src/core/shared/clipboardImage.ts +111 -0
  78. package/src/core/shared/githubDiagnostics.ts +222 -0
  79. package/src/core/shared/hollowResponseFormat.ts +39 -0
  80. package/src/core/terminal/clearFrameBoundary.ts +852 -0
  81. package/src/core/terminal/frameLock.ts +110 -0
  82. package/src/core/terminal/inkRenderReset.ts +123 -0
  83. package/src/core/terminal/startupClear.ts +20 -0
  84. package/src/core/terminal/terminalCapabilities.ts +100 -0
  85. package/src/core/terminal/terminalControl.ts +169 -0
  86. package/src/core/terminal/terminalSanitize.ts +147 -0
  87. package/src/core/terminal/terminalTitle.ts +400 -0
  88. package/src/core/version/channel.ts +27 -0
  89. package/src/core/version/packageManager.ts +119 -0
  90. package/src/core/version/updateCheck.ts +203 -0
  91. package/src/core/workspace/appData.ts +107 -0
  92. package/src/core/workspace/conversationStore.ts +335 -0
  93. package/src/core/workspace/launchContext.ts +259 -0
  94. package/src/core/workspace/planStorage.ts +135 -0
  95. package/src/core/workspace/projectInstructions.ts +54 -0
  96. package/src/core/workspace/scratchDir.ts +64 -0
  97. package/src/core/workspace/workspaceActivity.ts +384 -0
  98. package/src/core/workspace/workspaceGuard.ts +377 -0
  99. package/src/core/workspace/workspaceRoot.ts +47 -0
  100. package/src/exec.ts +73 -0
  101. package/src/headless/execArgs.ts +296 -0
  102. package/src/headless/execRunner.ts +304 -0
  103. package/src/index.tsx +270 -0
  104. package/src/legacyEnvBootstrap.ts +5 -0
  105. package/src/session/appSession.ts +771 -0
  106. package/src/session/chatLifecycle.ts +994 -0
  107. package/src/session/conversation.ts +107 -0
  108. package/src/session/liveRenderScheduler.ts +214 -0
  109. package/src/session/persistedResponse.ts +93 -0
  110. package/src/session/planFlow.ts +159 -0
  111. package/src/session/planTranscript.ts +19 -0
  112. package/src/session/promptRunSchedule.ts +26 -0
  113. package/src/session/types.ts +234 -0
  114. package/src/test/runtimeTestUtils.ts +14 -0
  115. package/src/types/react-dom.d.ts +3 -0
  116. package/src/ui/chrome/ActivityBars.tsx +68 -0
  117. package/src/ui/chrome/ActivityIndicator.tsx +58 -0
  118. package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
  119. package/src/ui/chrome/AppShell.tsx +474 -0
  120. package/src/ui/chrome/BottomComposer.tsx +1135 -0
  121. package/src/ui/chrome/DashCard.tsx +82 -0
  122. package/src/ui/chrome/RunFooter.tsx +65 -0
  123. package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
  124. package/src/ui/chrome/Spinner.tsx +25 -0
  125. package/src/ui/chrome/TopHeader.tsx +439 -0
  126. package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
  127. package/src/ui/chrome/busyStatusAnimation.ts +11 -0
  128. package/src/ui/input/commandNormalize.ts +66 -0
  129. package/src/ui/input/focus.ts +73 -0
  130. package/src/ui/input/imageAttachments.ts +18 -0
  131. package/src/ui/input/inputBuffer.ts +203 -0
  132. package/src/ui/input/pastedContent.ts +75 -0
  133. package/src/ui/input/rawArrowKeys.ts +28 -0
  134. package/src/ui/input/slashCommands.ts +43 -0
  135. package/src/ui/input/useStdinRawModeLease.ts +23 -0
  136. package/src/ui/layout.ts +560 -0
  137. package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
  138. package/src/ui/panels/AuthPanel.tsx +149 -0
  139. package/src/ui/panels/BackendPicker.tsx +28 -0
  140. package/src/ui/panels/ModePicker.tsx +31 -0
  141. package/src/ui/panels/ModelPicker.tsx +31 -0
  142. package/src/ui/panels/ModelPickerScreen.tsx +761 -0
  143. package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
  144. package/src/ui/panels/Panel.tsx +51 -0
  145. package/src/ui/panels/PermissionsPanel.tsx +78 -0
  146. package/src/ui/panels/PlanActionPicker.tsx +187 -0
  147. package/src/ui/panels/ProviderPicker.tsx +753 -0
  148. package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
  149. package/src/ui/panels/ReasoningPicker.tsx +46 -0
  150. package/src/ui/panels/ResumePicker.tsx +90 -0
  151. package/src/ui/panels/SelectionPanel.tsx +138 -0
  152. package/src/ui/panels/SettingsPanel.tsx +156 -0
  153. package/src/ui/panels/TextEntryPanel.tsx +139 -0
  154. package/src/ui/panels/ThemePicker.tsx +32 -0
  155. package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
  156. package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
  157. package/src/ui/panels/responsivePickerViewport.ts +64 -0
  158. package/src/ui/render/Markdown.tsx +331 -0
  159. package/src/ui/render/diffRenderer.ts +116 -0
  160. package/src/ui/render/logoVariants.ts +113 -0
  161. package/src/ui/render/modeDisplay.ts +52 -0
  162. package/src/ui/render/outputPipeline.ts +64 -0
  163. package/src/ui/render/runtimeDisplay.ts +128 -0
  164. package/src/ui/render/terminalAnswerFormat.ts +128 -0
  165. package/src/ui/render/textLayout.ts +392 -0
  166. package/src/ui/theme.tsx +274 -0
  167. package/src/ui/themeFlow.ts +41 -0
  168. package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
  169. package/src/ui/timeline/AgentBlock.tsx +130 -0
  170. package/src/ui/timeline/StaticIntroItem.tsx +54 -0
  171. package/src/ui/timeline/ThinkingBlock.tsx +100 -0
  172. package/src/ui/timeline/Timeline.tsx +1410 -0
  173. package/src/ui/timeline/TranscriptShell.tsx +302 -0
  174. package/src/ui/timeline/TurnGroup.tsx +673 -0
  175. package/src/ui/timeline/layoutListWindow.ts +145 -0
  176. package/src/ui/timeline/liveViewportWindow.ts +68 -0
  177. package/src/ui/timeline/progressEntries.ts +156 -0
  178. package/src/ui/timeline/runActivityView.ts +37 -0
  179. package/src/ui/timeline/staticTranscriptCache.ts +174 -0
  180. package/src/ui/timeline/streamCoalesce.ts +53 -0
  181. package/src/ui/timeline/timelineMeasure.ts +3273 -0
  182. package/src/ui/useThrottledValue.ts +31 -0
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Ubume CLI
2
+
3
+ Ubume is a terminal coding workspace for provider CLIs and local
4
+ OpenAI-compatible models.
5
+
6
+ *Ubume* (IsiXhosa) conveys structure, form, embodiment, and configuration — reflecting how Ubume coordinates external provider engines and local models into a unified, stateful CLI environment.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install -g ubume
12
+ ubume
13
+ ```
14
+
15
+ Requires Node.js and one supported provider CLI, unless you use a local model.
16
+
17
+ Update Ubume with:
18
+
19
+ ```bash
20
+ npm install -g ubume@latest
21
+ ubume --version
22
+ ```
23
+
24
+ ## Providers
25
+
26
+ Ubume can use these routes:
27
+
28
+ | Provider | Setup |
29
+ | --- | --- |
30
+ | OpenAI / Codex | Install and authenticate the `codex` CLI. |
31
+ | Anthropic / Claude | Install and authenticate the `claude` CLI. |
32
+ | Mistral Vibe | Install and authenticate the `vibe` CLI. |
33
+ | Antigravity | Install and authenticate the `agy` CLI. |
34
+ | Local model | Open Local, choose LM Studio or Unsloth, and use a model loaded in that server. |
35
+ | Codexa Native | Available only from the local `ubume-dev` channel. |
36
+
37
+ Credentials remain with the provider CLI or local server.
38
+
39
+ ## Usage
40
+
41
+ ```bash
42
+ ubume --version
43
+ ubume exec "summarize this project"
44
+ ubume --model gpt-5.4
45
+ ```
46
+
47
+ Useful commands inside Ubume:
48
+
49
+ ```text
50
+ /help Show commands
51
+ /model Choose a model
52
+ /providers Choose a provider
53
+ /permissions Configure safety
54
+ /settings Open settings
55
+ /update Check for updates
56
+ ```
57
+
58
+ Press Shift+Tab to rotate Plan, Read-only, Auto, and Full Access without
59
+ opening a panel. Large pastes are displayed as `[Pasted Content … chars]`
60
+ while their complete content is sent to the model.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ bun install
66
+ bun run dev
67
+ bun test
68
+ bun run build
69
+ ```
70
+
71
+ Install the separate local development launcher with:
72
+
73
+ ```bash
74
+ bun run install:dev-bin
75
+ ubume-dev
76
+ ```
77
+
78
+ Developer references:
79
+
80
+ - [Architecture](docs/ARCHITECTURE.md)
81
+ - [Source guide](docs/SOURCE_GUIDE.md)
82
+ - [Documentation guide](docs/DOCUMENTATION.md)
83
+ - [Release guide](docs/RELEASING.md)
84
+
85
+ ## Versions
86
+
87
+ Read [VERSIONS.md](VERSIONS.md) for a plain-language explanation of each
88
+ release. [CHANGELOG.md](CHANGELOG.md) contains the detailed technical record.
@@ -0,0 +1,3 @@
1
+ // Backwards-compatibility wrapper for ubume-local-harness-bridge
2
+ export * from "./ubume-local-harness-bridge.js";
3
+ export { name as defaultName } from "./ubume-local-harness-bridge.js";
package/bin/codexa.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "child_process";
4
+ import { dirname, join } from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ process.stderr.write("The `codexa` command has been renamed to `ubume`.\nPlease use `ubume` instead.\n\n");
8
+
9
+ const currentFile = fileURLToPath(import.meta.url);
10
+ const ubumeScript = join(dirname(currentFile), "ubume.js");
11
+
12
+ const child = spawn(process.execPath, [ubumeScript, ...process.argv.slice(2)], {
13
+ stdio: "inherit",
14
+ env: process.env,
15
+ });
16
+
17
+ child.on("error", (error) => {
18
+ console.error(`Failed to launch ubume: ${error.message}`);
19
+ process.exit(1);
20
+ });
21
+
22
+ child.on("close", (code, signal) => {
23
+ if (signal) {
24
+ process.kill(process.pid, signal);
25
+ return;
26
+ }
27
+ process.exit(code ?? 0);
28
+ });
@@ -0,0 +1,325 @@
1
+ import { JsonRpcLineTransport } from "@deepseek-ai/dsh-sdk-protocol";
2
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
3
+ import { SessionId } from "@deepseek-ai/dsh-session";
4
+ import { carrierKeyOf } from "@deepseek-ai/dsh-scope";
5
+
6
+ export const name = "ubume-local-harness-bridge";
7
+ export const inject = ["agents"];
8
+ const MAX_PENDING_STDOUT_BYTES = 16 * 1024 * 1024;
9
+ const MAX_TOOL_ARGUMENT_CHARS = 2_000;
10
+ const MAX_TOOL_RESULT_CHARS = 2_000;
11
+ const configuredMaxRssBytes = Number(
12
+ process.env.UBUME_DSH_MAX_RSS_BYTES || process.env.CODEXA_DSH_MAX_RSS_BYTES,
13
+ );
14
+ const maxRssBytes = Number.isSafeInteger(configuredMaxRssBytes) && configuredMaxRssBytes > 0
15
+ ? configuredMaxRssBytes
16
+ : 1024 * 1024 * 1024;
17
+
18
+ function contentPreview(blocks, limit = MAX_TOOL_RESULT_CHARS) {
19
+ if (!Array.isArray(blocks)) return "";
20
+ let result = "";
21
+ for (const block of blocks) {
22
+ if (!block || typeof block !== "object") continue;
23
+ const part = typeof block.text === "string" ? block.text : contentPreview(block.content, limit - result.length);
24
+ result += part.slice(0, limit - result.length);
25
+ if (result.length >= limit) break;
26
+ }
27
+ return result;
28
+ }
29
+
30
+ function toolDisplayArguments(raw) {
31
+ try {
32
+ if (typeof raw === "string" && raw.length > 1024 * 1024) return "{}";
33
+ const args = typeof raw === "string" ? JSON.parse(raw) : raw;
34
+ if (!args || typeof args !== "object") return "{}";
35
+ return JSON.stringify(Object.fromEntries(
36
+ ["command", "path", "file_path"].filter((key) => typeof args[key] === "string")
37
+ .map((key) => [key, args[key].slice(0, MAX_TOOL_ARGUMENT_CHARS)]),
38
+ ));
39
+ } catch {
40
+ return "{}";
41
+ }
42
+ }
43
+
44
+ export function policyArguments(args) {
45
+ if (!args || typeof args !== "object") return {};
46
+ return Object.fromEntries(
47
+ ["command", "path", "file_path", "old_path", "new_path"]
48
+ .filter((key) => typeof args[key] === "string")
49
+ .map((key) => [key, args[key]]),
50
+ );
51
+ }
52
+
53
+ function outputContent(blocks) {
54
+ if (!Array.isArray(blocks)) return [];
55
+ return blocks.flatMap((block) => {
56
+ if (!block || typeof block !== "object") return [];
57
+ if ((block.type === "text" || block.type === "output_text") && typeof block.text === "string") return [block];
58
+ return outputContent(block.content);
59
+ });
60
+ }
61
+
62
+ export function projectHarnessEvent(event) {
63
+ const data = event?.data ?? {};
64
+ if (event?.type?.startsWith("compaction/")) return { type: event.type };
65
+ if (event?.type === "turn/end") {
66
+ const reason = data.reason ?? {};
67
+ return { type: event.type, data: { reason: {
68
+ kind: reason.kind,
69
+ ...(reason.error ? { error: { message: String(reason.error.message ?? "").slice(0, 4_000) } } : {}),
70
+ } } };
71
+ }
72
+ if (event?.type === "assistant/chunk") {
73
+ const chunk = data.chunk;
74
+ if (!chunk || !["text-delta", "reasoning-delta", "usage", "finish"].includes(chunk.type)) return null;
75
+ if (chunk.type === "finish") {
76
+ return { type: event.type, data: { chunk: {
77
+ type: "finish",
78
+ reason: chunk.reason,
79
+ replayState: { response: { stopReason: chunk.replayState?.response?.stopReason } },
80
+ } } };
81
+ }
82
+ return { type: event.type, data: { step: data.step, chunk } };
83
+ }
84
+ if (event?.type === "assistant/message") {
85
+ const content = outputContent(data.message?.content);
86
+ return { type: event.type, data: { usage: data.usage, message: { content } } };
87
+ }
88
+ if (event?.type === "tool/call") {
89
+ return { type: event.type, seq: event.seq, data: {
90
+ callId: data.callId,
91
+ name: data.name,
92
+ arguments: toolDisplayArguments(data.arguments),
93
+ } };
94
+ }
95
+ if (event?.type === "tool/result") {
96
+ return { type: event.type, seq: event.seq, data: {
97
+ message: { source: { callId: data.message?.source?.callId }, content: [{ type: "text", text: contentPreview(data.message?.content) }] },
98
+ ...(data.error ? { error: { message: "Tool failed" } } : {}),
99
+ } };
100
+ }
101
+ return null;
102
+ }
103
+
104
+ export function notifyBounded(transport, method, params, output = process.stdout, abort = (code) => {
105
+ process.stderr.write("Ubume Local Harness output safety buffer exceeded.\n");
106
+ process.exit(code);
107
+ }) {
108
+ transport.notify(method, params);
109
+ if (output.writableLength > MAX_PENDING_STDOUT_BYTES) abort(86);
110
+ }
111
+
112
+ class UbumeHarnessServer {
113
+ constructor(ctx, transport) {
114
+ this.ctx = ctx;
115
+ this.transport = transport;
116
+ this.cwd = process.cwd();
117
+ this.provider = "ubume-local";
118
+ this.model = "";
119
+ this.maxTokens = undefined;
120
+ this.sessions = new Map();
121
+ this.creations = new Map();
122
+ this.disposers = [];
123
+ this.shuttingDown = false;
124
+
125
+ this.disposers.push(ctx.on("session/event", (session, event) => {
126
+ if (!this.sessions.has(String(session.id))) return;
127
+ const projected = projectHarnessEvent(event);
128
+ if (projected) notifyBounded(this.transport, "session.event", { sessionId: String(session.id), event: projected });
129
+ }));
130
+ this.disposers.push(ctx.on("agent/status", ({ agent, status }) => {
131
+ notifyBounded(this.transport, "session.status", { sessionId: String(agent.id), status });
132
+ }));
133
+ this.disposers.push(ctx.on("session/created", (session) => {
134
+ if (session.header.parentSession === undefined) return;
135
+ notifyBounded(this.transport, "subagent.started", {
136
+ parentSessionId: String(session.header.parentSession),
137
+ childSessionId: String(session.id),
138
+ });
139
+ }));
140
+ const notificationTransport = this.transport;
141
+ this.disposers.push(ctx.on("subagent/end", function(info) {
142
+ if (!info.local) return;
143
+ const parent = carrierKeyOf(this);
144
+ notifyBounded(notificationTransport, "subagent.finished", {
145
+ parentSessionId: String(parent.session.id),
146
+ childSessionId: String(info.id),
147
+ status: info.stopReason === "completed" ? "ok" : "error",
148
+ stopReason: info.stopReason,
149
+ });
150
+ }));
151
+ this.disposers.push(ctx.on("tools/pre-execute", async (execution, next) => {
152
+ const agent = execution.agent;
153
+ if (!agent || !this.sessions.has(String(agent.id))) return next();
154
+ const result = await this.transport.request("tool/policy", {
155
+ sessionId: String(agent.id),
156
+ callId: execution.callId === undefined ? undefined : String(execution.callId),
157
+ tool: execution.name,
158
+ arguments: policyArguments(execution.arguments),
159
+ });
160
+ if (!result || typeof result !== "object") {
161
+ return { kind: "deny", reason: "Ubume returned an invalid tool-policy decision." };
162
+ }
163
+ if (result.kind === "allow") return { kind: "allow" };
164
+ if (result.kind === "ask") {
165
+ return { kind: "ask", ...(typeof result.reason === "string" ? { reason: result.reason } : {}) };
166
+ }
167
+ return {
168
+ kind: "deny",
169
+ reason: typeof result.reason === "string" ? result.reason : "Ubume denied this tool call.",
170
+ };
171
+ }));
172
+ this.disposers.push(ctx.on("approval/request", async (request, next) => {
173
+ if (!this.sessions.has(String(request.agent.id))) return next();
174
+ const result = await this.transport.request("approval/request", {
175
+ sessionId: String(request.agent.id),
176
+ tool: request.toolName,
177
+ callId: request.callId === undefined ? undefined : String(request.callId),
178
+ reason: request.reason,
179
+ });
180
+ if (!result || typeof result !== "object") return "unavailable";
181
+ if (result.outcome === "allowed-once") return "allowed-once";
182
+ if (result.outcome === "cancelled") return "cancelled";
183
+ return "rejected";
184
+ }));
185
+ }
186
+
187
+ async initialize(params) {
188
+ this.cwd = typeof params.cwd === "string" ? params.cwd : process.cwd();
189
+ this.provider = typeof params.provider === "string" ? params.provider : "ubume-local";
190
+ this.model = typeof params.model === "string" ? params.model : "";
191
+ this.maxTokens = Number.isSafeInteger(params.maxTokens) && params.maxTokens > 0
192
+ ? params.maxTokens
193
+ : undefined;
194
+ return { serverInfo: { name: "ubume-local-harness-runtime", version: "1" } };
195
+ }
196
+
197
+ async open(params) {
198
+ const sessionId = String(params.sessionId ?? "");
199
+ if (!sessionId) throw new Error("session/open requires a sessionId");
200
+ const record = await this.getOrCreate(sessionId, params.resume === true, Array.isArray(params.seed) ? params.seed : undefined);
201
+ return { sessionId: String(record.handle.agent.id), resumed: record.resumed };
202
+ }
203
+
204
+ async prompt(params) {
205
+ const record = await this.getOrCreate(String(params.sessionId ?? ""), params.resume === true);
206
+ const content = Array.isArray(params.contentBlocks)
207
+ ? params.contentBlocks
208
+ : [{ type: "text", text: String(params.content ?? "") }];
209
+ const message = createUserMessage({ content, source: { kind: "user" } });
210
+ record.handle.agent.followup(message);
211
+ return { messageId: String(message.id) };
212
+ }
213
+
214
+ async cancel(params) {
215
+ const record = this.sessions.get(String(params.sessionId ?? ""));
216
+ if (!record) return { cancelled: false };
217
+ record.handle.agent.cancel({ kind: "user" });
218
+ await record.handle.agent.whenIdle();
219
+ return { cancelled: true };
220
+ }
221
+
222
+ async close(params) {
223
+ const sessionId = String(params.sessionId ?? "");
224
+ const record = this.sessions.get(sessionId);
225
+ if (!record) return {};
226
+ this.sessions.delete(sessionId);
227
+ await record.handle.dispose();
228
+ return {};
229
+ }
230
+
231
+ async getOrCreate(sessionId, resume = false, seed) {
232
+ if (!sessionId) throw new Error("A non-empty Harness session id is required.");
233
+ if (this.shuttingDown) throw new Error("Local Harness is shutting down.");
234
+ const existing = this.sessions.get(sessionId);
235
+ if (existing) return existing;
236
+ const pending = this.creations.get(sessionId);
237
+ if (pending) return pending;
238
+ const creation = this.create(sessionId, resume, seed);
239
+ this.creations.set(sessionId, creation);
240
+ creation.finally(() => this.creations.delete(sessionId));
241
+ return creation;
242
+ }
243
+
244
+ async create(sessionId, resume, seed) {
245
+ const agentOptions = {
246
+ provider: this.provider,
247
+ model: this.model,
248
+ ...(this.maxTokens === undefined ? {} : { maxTokens: this.maxTokens }),
249
+ };
250
+ let handle;
251
+ let resumed = false;
252
+ if (resume) {
253
+ try {
254
+ handle = await this.ctx.agents.resume({ resumeSessionId: SessionId(sessionId), agentOptions });
255
+ resumed = true;
256
+ } catch (error) {
257
+ if (!seed) throw error;
258
+ }
259
+ }
260
+ handle ??= await this.ctx.agents.create({
261
+ sessionId: SessionId(sessionId),
262
+ meta: { cwd: this.cwd, ...(seed ? { seedLength: seed.length } : {}) },
263
+ ...(seed ? { seed } : {}),
264
+ agentOptions,
265
+ });
266
+ const record = { handle, resumed };
267
+ this.sessions.set(sessionId, record);
268
+ return record;
269
+ }
270
+
271
+ async shutdown() {
272
+ this.shuttingDown = true;
273
+ await Promise.allSettled([...this.creations.values()]);
274
+ this.creations.clear();
275
+ const records = [...this.sessions.values()];
276
+ this.sessions.clear();
277
+ while (this.disposers.length > 0) this.disposers.pop()?.();
278
+ await Promise.allSettled(records.map((record) => record.handle.dispose()));
279
+ return {};
280
+ }
281
+
282
+ handle(method, params) {
283
+ if (method === "initialize") return this.initialize(params);
284
+ if (method === "session/open") return this.open(params);
285
+ if (method === "session/prompt") return this.prompt(params);
286
+ if (method === "session/cancel") return this.cancel(params);
287
+ if (method === "session/close") return this.close(params);
288
+ if (method === "shutdown") return this.shutdown();
289
+ throw new Error(`Unknown Ubume Local Harness method: ${method}`);
290
+ }
291
+ }
292
+
293
+ export function apply(ctx) {
294
+ const transport = new JsonRpcLineTransport(process.stdin, process.stdout);
295
+ const server = new UbumeHarnessServer(ctx, transport);
296
+ const rootFiber = ctx.root.fiber;
297
+ transport.onRequest(async (method, params) => {
298
+ if (method === "initialize") await ctx.get("loader")?.await();
299
+ const result = await server.handle(method, params);
300
+ if (method === "shutdown") {
301
+ setImmediate(async () => {
302
+ await Promise.allSettled([transport.flush(), rootFiber.dispose()]);
303
+ process.exit(0);
304
+ });
305
+ }
306
+ return result;
307
+ });
308
+ ctx.effect(() => {
309
+ transport.start();
310
+ const memoryTimer = setInterval(() => {
311
+ const rssBytes = process.memoryUsage().rss;
312
+ if (rssBytes >= maxRssBytes) {
313
+ process.stderr.write("Ubume Local Harness RAM safety limit reached.\n");
314
+ process.exit(85);
315
+ }
316
+ notifyBounded(transport, "harness.memory", { rssBytes });
317
+ }, 500);
318
+ memoryTimer.unref();
319
+ return async () => {
320
+ clearInterval(memoryTimer);
321
+ await server.shutdown();
322
+ transport.close();
323
+ };
324
+ }, "ubume-local-harness.serve");
325
+ }