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.
- package/README.md +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- 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.
|
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
|
+
}
|