@geminixiang/mama 0.2.0-beta.1 → 0.2.0-beta.11
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 +168 -371
- package/dist/adapter.d.ts +36 -12
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +12 -7
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +358 -135
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +100 -36
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +71 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +168 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +30 -24
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +613 -224
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +22 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +97 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +127 -72
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +16 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts +4 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +193 -147
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +58 -111
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/agent.d.ts +9 -13
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +601 -567
- package/dist/agent.js.map +1 -1
- package/dist/commands/auto-reply.d.ts +16 -0
- package/dist/commands/auto-reply.d.ts.map +1 -0
- package/dist/commands/auto-reply.js +69 -0
- package/dist/commands/auto-reply.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +76 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +112 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +14 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +88 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts +5 -0
- package/dist/commands/session-view.d.ts.map +1 -0
- package/dist/commands/session-view.js +62 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +41 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +8 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +14 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +49 -30
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +313 -75
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +10 -42
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +14 -127
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +13 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +118 -64
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +9 -5
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +82 -18
- package/dist/execution-resolver.js.map +1 -1
- package/dist/file-guards.d.ts +6 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +48 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +4 -11
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +1 -5
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +13 -38
- package/dist/log.js.map +1 -1
- package/dist/{login.d.ts → login/index.d.ts} +16 -4
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +55 -17
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +7 -4
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1453 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/{link-token.d.ts → login/session.d.ts} +4 -3
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +1 -1
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +151 -373
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +42 -52
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +256 -111
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +42 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +150 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +27 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +211 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +15 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +137 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/container.d.ts +2 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +5 -1
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sandbox/firecracker.d.ts +2 -1
- package/dist/sandbox/firecracker.d.ts.map +1 -1
- package/dist/sandbox/firecracker.js +6 -0
- package/dist/sandbox/firecracker.js.map +1 -1
- package/dist/sandbox/host.d.ts +2 -3
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +5 -5
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sandbox/index.d.ts +6 -4
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +9 -6
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/path-context.d.ts +4 -0
- package/dist/sandbox/path-context.d.ts.map +1 -0
- package/dist/sandbox/path-context.js +20 -0
- package/dist/sandbox/path-context.js.map +1 -0
- package/dist/sandbox/types.d.ts +17 -1
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sentry.d.ts +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +4 -2
- package/dist/sentry.js.map +1 -1
- package/dist/session-policy.d.ts +13 -0
- package/dist/session-policy.d.ts.map +1 -0
- package/dist/session-policy.js +23 -0
- package/dist/session-policy.js.map +1 -0
- package/dist/session-store.d.ts +34 -3
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +184 -22
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +11 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +16 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +1742 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +427 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +18 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +39 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +3 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +22 -48
- package/dist/store.js.map +1 -1
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +43 -2
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +48 -13
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/trigger.d.ts +31 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +98 -0
- package/dist/trigger.js.map +1 -0
- package/dist/ui-copy.d.ts +1 -0
- package/dist/ui-copy.d.ts.map +1 -1
- package/dist/ui-copy.js +3 -0
- package/dist/ui-copy.js.map +1 -1
- package/dist/vault-routing.d.ts +1 -7
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +6 -48
- package/dist/vault-routing.js.map +1 -1
- package/dist/vault.d.ts +21 -55
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +144 -263
- package/dist/vault.js.map +1 -1
- package/package.json +12 -10
- package/dist/bindings.d.ts +0 -63
- package/dist/bindings.d.ts.map +0 -1
- package/dist/bindings.js +0 -94
- package/dist/bindings.js.map +0 -1
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -839
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts.map +0 -1
- package/dist/link-token.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
- package/dist/vault.test.d.ts +0 -2
- package/dist/vault.test.d.ts.map +0 -1
- package/dist/vault.test.js +0 -67
- package/dist/vault.test.js.map +0 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { resolveSlackSessionScope } from "../adapters/slack/branch-manager.js";
|
|
2
|
+
import { createRunner } from "../agent.js";
|
|
3
|
+
import { createDefaultCommandRegistry } from "../commands/index.js";
|
|
4
|
+
import * as log from "../log.js";
|
|
5
|
+
import { createManagedSessionFile, createManagedSessionFileAtPath, getChannelSessionDir, getThreadSessionFile, resolveGenericSessionScope, } from "../session-store.js";
|
|
6
|
+
import { formatNothingRunning, formatStopping } from "../ui-copy.js";
|
|
7
|
+
import { ConversationOrchestrator, } from "./conversation-orchestrator.js";
|
|
8
|
+
import * as Sentry from "@sentry/node";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { getUnresolvedSandboxPathContext } from "../agent.js";
|
|
11
|
+
const MAX_SESSIONS = 500;
|
|
12
|
+
const IDLE_TIMEOUT_MS = 3_600_000;
|
|
13
|
+
function runtimeCwdForSandbox(sandbox, hostWorkspaceRoot, conversationId) {
|
|
14
|
+
const runtimeWorkspaceRoot = getUnresolvedSandboxPathContext(sandbox, hostWorkspaceRoot).runtimeWorkspaceRoot;
|
|
15
|
+
return `${runtimeWorkspaceRoot.replace(/\/+$/, "")}/${conversationId}`;
|
|
16
|
+
}
|
|
17
|
+
export function createSessionRuntime(options) {
|
|
18
|
+
return new MamaSessionRuntime(options);
|
|
19
|
+
}
|
|
20
|
+
class MamaSessionRuntime {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.conversationStates = new Map();
|
|
24
|
+
this.sessionQueues = new Map();
|
|
25
|
+
this.inFlightRuns = new Set();
|
|
26
|
+
this.isShuttingDown = false;
|
|
27
|
+
this.options.runtime = this;
|
|
28
|
+
this.commandRegistry = options.commandRegistry ?? createDefaultCommandRegistry();
|
|
29
|
+
this.orchestrator = new ConversationOrchestrator({
|
|
30
|
+
workingDir: options.workingDir,
|
|
31
|
+
commandRegistry: this.commandRegistry,
|
|
32
|
+
commandServices: this.options,
|
|
33
|
+
isShuttingDown: () => this.isShuttingDown,
|
|
34
|
+
getState: (sessionKey) => this.conversationStates.get(sessionKey),
|
|
35
|
+
getOrCreateState: (createOptions) => this.getOrCreateState(createOptions),
|
|
36
|
+
beforeRunTracked: (runPromise) => {
|
|
37
|
+
this.inFlightRuns.add(runPromise);
|
|
38
|
+
Sentry.metrics.gauge("agent.sessions.active", this.inFlightRuns.size);
|
|
39
|
+
},
|
|
40
|
+
afterRunTracked: (runPromise) => {
|
|
41
|
+
this.inFlightRuns.delete(runPromise);
|
|
42
|
+
},
|
|
43
|
+
onRunFinished: () => {
|
|
44
|
+
Sentry.metrics.gauge("agent.sessions.active", this.inFlightRuns.size - 1);
|
|
45
|
+
this.evictIdleSessions();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
isRunning(sessionKey) {
|
|
50
|
+
const state = this.conversationStates.get(sessionKey);
|
|
51
|
+
return !!state?.running;
|
|
52
|
+
}
|
|
53
|
+
getRunningSessions() {
|
|
54
|
+
const sessions = [];
|
|
55
|
+
for (const [sessionKey, state] of this.conversationStates) {
|
|
56
|
+
if (state.running && state.startedAt) {
|
|
57
|
+
const currentStep = state.runner.getCurrentStep();
|
|
58
|
+
sessions.push({
|
|
59
|
+
sessionKey,
|
|
60
|
+
startedAt: state.startedAt,
|
|
61
|
+
lastActivityAt: state.lastActivityAt,
|
|
62
|
+
currentTool: currentStep?.label || currentStep?.toolName,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sessions;
|
|
67
|
+
}
|
|
68
|
+
async handleStop(sessionKey, conversationId, bot) {
|
|
69
|
+
const state = this.conversationStates.get(sessionKey);
|
|
70
|
+
if (state?.running) {
|
|
71
|
+
state.stopRequested = true;
|
|
72
|
+
state.runner.abort();
|
|
73
|
+
const ts = await bot.postMessage(conversationId, formatStopping(bot));
|
|
74
|
+
state.stopMessageTs = ts;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
await bot.postMessage(conversationId, formatNothingRunning(bot));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
forceStop(sessionKey) {
|
|
81
|
+
const state = this.conversationStates.get(sessionKey);
|
|
82
|
+
if (state?.running) {
|
|
83
|
+
log.logInfo(`[Force Stop] Force stopping session: ${sessionKey}`);
|
|
84
|
+
state.stopRequested = true;
|
|
85
|
+
state.runner.abort();
|
|
86
|
+
state.running = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async handleNewCommand(sessionKey, conversationId, bot) {
|
|
90
|
+
const state = this.conversationStates.get(sessionKey);
|
|
91
|
+
if (state?.running) {
|
|
92
|
+
state.stopRequested = true;
|
|
93
|
+
state.runner.abort();
|
|
94
|
+
}
|
|
95
|
+
const conversationDir = join(this.options.workingDir, conversationId);
|
|
96
|
+
const runtimeCwd = runtimeCwdForSandbox(this.options.sandbox, this.options.workingDir, conversationId);
|
|
97
|
+
if (sessionKey.includes(":")) {
|
|
98
|
+
createManagedSessionFileAtPath(getThreadSessionFile(conversationDir, sessionKey), runtimeCwd);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
createManagedSessionFile(getChannelSessionDir(conversationDir), runtimeCwd);
|
|
102
|
+
}
|
|
103
|
+
this.conversationStates.delete(sessionKey);
|
|
104
|
+
log.logInfo(`[${conversationId}] Session reset: ${sessionKey}`);
|
|
105
|
+
await bot.postMessage(conversationId, "Conversation reset. Send a new message to start fresh.");
|
|
106
|
+
}
|
|
107
|
+
async handleEvent(event, bot, adapters, isSyntheticEvent) {
|
|
108
|
+
const sessionKey = event.sessionKey ?? `${event.conversationId}:${event.thread_ts ?? event.ts}`;
|
|
109
|
+
const previous = this.sessionQueues.get(sessionKey) ?? Promise.resolve();
|
|
110
|
+
const next = previous
|
|
111
|
+
.catch(() => { })
|
|
112
|
+
.then(() => this.runSession({ event, bot, adapters, isSyntheticEvent }));
|
|
113
|
+
this.sessionQueues.set(sessionKey, next);
|
|
114
|
+
try {
|
|
115
|
+
await next;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
if (this.sessionQueues.get(sessionKey) === next) {
|
|
119
|
+
this.sessionQueues.delete(sessionKey);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async runSession({ event, bot, adapters, isSyntheticEvent }) {
|
|
124
|
+
await this.orchestrator.runSession({ event, bot, adapters, isSyntheticEvent });
|
|
125
|
+
}
|
|
126
|
+
async createSessionSandbox(options) {
|
|
127
|
+
const state = await this.getOrCreateState(options);
|
|
128
|
+
return state.runner;
|
|
129
|
+
}
|
|
130
|
+
switchConversationModel(conversationId, _provider, _model) {
|
|
131
|
+
for (const [sessionKey, state] of this.conversationStates) {
|
|
132
|
+
if (this.isConversationSession(sessionKey, conversationId) && state.running) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const sessionKey of Array.from(this.conversationStates.keys())) {
|
|
137
|
+
if (this.isConversationSession(sessionKey, conversationId)) {
|
|
138
|
+
this.conversationStates.delete(sessionKey);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
log.logInfo(`[${conversationId}] Model switched; cleared cached session runners`);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
isConversationSession(sessionKey, conversationId) {
|
|
145
|
+
return sessionKey === conversationId || sessionKey.startsWith(`${conversationId}:`);
|
|
146
|
+
}
|
|
147
|
+
async getOrCreateState({ conversationId, platformName, sessionKey, }) {
|
|
148
|
+
const existing = this.conversationStates.get(sessionKey);
|
|
149
|
+
if (existing) {
|
|
150
|
+
existing.lastAccessedAt = Date.now();
|
|
151
|
+
return existing;
|
|
152
|
+
}
|
|
153
|
+
const conversationDir = join(this.options.workingDir, conversationId);
|
|
154
|
+
const runtimeCwd = runtimeCwdForSandbox(this.options.sandbox, this.options.workingDir, conversationId);
|
|
155
|
+
const sessionScope = await this.resolveSessionScope(platformName, conversationDir, sessionKey, runtimeCwd);
|
|
156
|
+
const state = {
|
|
157
|
+
running: false,
|
|
158
|
+
runner: await createRunner(this.options.sandbox, sessionKey, conversationId, conversationDir, this.options.workingDir, sessionScope, this.options.vaultManager, this.options.provisioner),
|
|
159
|
+
stopRequested: false,
|
|
160
|
+
lastAccessedAt: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
this.conversationStates.set(sessionKey, state);
|
|
163
|
+
return state;
|
|
164
|
+
}
|
|
165
|
+
async shutdown(timeoutMs = 30_000) {
|
|
166
|
+
if (this.isShuttingDown)
|
|
167
|
+
return;
|
|
168
|
+
this.isShuttingDown = true;
|
|
169
|
+
log.logInfo("Shutting down gracefully...");
|
|
170
|
+
const timeout = Date.now() + timeoutMs;
|
|
171
|
+
while (this.inFlightRuns.size > 0 && Date.now() < timeout) {
|
|
172
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
173
|
+
}
|
|
174
|
+
if (this.inFlightRuns.size > 0) {
|
|
175
|
+
log.logWarning(`Forcing exit with ${this.inFlightRuns.size} runs still in progress`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async resolveSessionScope(platformName, conversationDir, sessionKey, cwd) {
|
|
179
|
+
if (sessionKey.includes(":event:")) {
|
|
180
|
+
const sessionDir = getChannelSessionDir(conversationDir);
|
|
181
|
+
const contextFile = createManagedSessionFileAtPath(getThreadSessionFile(conversationDir, sessionKey), cwd);
|
|
182
|
+
return { sessionDir, contextFile, threadRootMessage: null };
|
|
183
|
+
}
|
|
184
|
+
if (platformName === "slack") {
|
|
185
|
+
return resolveSlackSessionScope({ conversationDir, sessionKey, cwd });
|
|
186
|
+
}
|
|
187
|
+
return resolveGenericSessionScope({ conversationDir, sessionKey, cwd });
|
|
188
|
+
}
|
|
189
|
+
evictIdleSessions() {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
for (const [key, state] of this.conversationStates) {
|
|
192
|
+
if (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {
|
|
193
|
+
this.conversationStates.delete(key);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (this.conversationStates.size > MAX_SESSIONS) {
|
|
197
|
+
const idleSessions = [];
|
|
198
|
+
for (const [key, state] of this.conversationStates) {
|
|
199
|
+
if (!state.running) {
|
|
200
|
+
idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);
|
|
204
|
+
const toEvict = this.conversationStates.size - MAX_SESSIONS;
|
|
205
|
+
for (let i = 0; i < toEvict && i < idleSessions.length; i++) {
|
|
206
|
+
this.conversationStates.delete(idleSessions[i].key);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=session-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-runtime.js","sourceRoot":"","sources":["../../src/runtime/session-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bot, BotAdapters, BotEvent, BotHandler, RunningSession } from \"../adapter.js\";\nimport { resolveSlackSessionScope } from \"../adapters/slack/branch-manager.js\";\nimport { type AgentRunner, createRunner } from \"../agent.js\";\nimport { CommandRegistry, createDefaultCommandRegistry } from \"../commands/index.js\";\nimport type { CommandServices } from \"../commands/index.js\";\nimport * as log from \"../log.js\";\nimport {\n createManagedSessionFile,\n createManagedSessionFileAtPath,\n getChannelSessionDir,\n getThreadSessionFile,\n resolveGenericSessionScope,\n type ResolvedSessionScope,\n} from \"../session-store.js\";\nimport { formatNothingRunning, formatStopping } from \"../ui-copy.js\";\nimport {\n ConversationOrchestrator,\n type ConversationRuntimeState,\n} from \"./conversation-orchestrator.js\";\nimport * as Sentry from \"@sentry/node\";\nimport { join } from \"path\";\nimport { getUnresolvedSandboxPathContext } from \"../agent.js\";\n\ntype ConversationState = ConversationRuntimeState;\n\nexport interface RunSessionOptions {\n event: BotEvent;\n bot: Bot;\n adapters: BotAdapters;\n isSyntheticEvent?: boolean;\n}\n\nexport interface CreateSessionSandboxOptions {\n conversationId: string;\n platformName: string;\n sessionKey: string;\n}\n\nexport interface SessionRuntimeOptions extends CommandServices {\n /** Override the default command registry (e.g., to add /help, /status). */\n commandRegistry?: CommandRegistry;\n}\n\nexport interface SessionRuntime extends BotHandler {\n runSession(options: RunSessionOptions): Promise<void>;\n createSessionSandbox(options: CreateSessionSandboxOptions): Promise<AgentRunner>;\n switchConversationModel(conversationId: string, provider: string, model: string): boolean;\n shutdown(timeoutMs?: number): Promise<void>;\n}\n\nconst MAX_SESSIONS = 500;\nconst IDLE_TIMEOUT_MS = 3_600_000;\n\nfunction runtimeCwdForSandbox(\n sandbox: SessionRuntimeOptions[\"sandbox\"],\n hostWorkspaceRoot: string,\n conversationId: string,\n): string {\n const runtimeWorkspaceRoot = getUnresolvedSandboxPathContext(\n sandbox,\n hostWorkspaceRoot,\n ).runtimeWorkspaceRoot;\n return `${runtimeWorkspaceRoot.replace(/\\/+$/, \"\")}/${conversationId}`;\n}\n\nexport function createSessionRuntime(options: SessionRuntimeOptions): SessionRuntime {\n return new MamaSessionRuntime(options);\n}\n\nclass MamaSessionRuntime implements SessionRuntime {\n private readonly conversationStates = new Map<string, ConversationState>();\n private readonly sessionQueues = new Map<string, Promise<void>>();\n private readonly inFlightRuns = new Set<Promise<void>>();\n private readonly commandRegistry: CommandRegistry;\n private readonly orchestrator: ConversationOrchestrator;\n private isShuttingDown = false;\n\n constructor(private readonly options: SessionRuntimeOptions) {\n this.options.runtime = this;\n this.commandRegistry = options.commandRegistry ?? createDefaultCommandRegistry();\n this.orchestrator = new ConversationOrchestrator({\n workingDir: options.workingDir,\n commandRegistry: this.commandRegistry,\n commandServices: this.options,\n isShuttingDown: () => this.isShuttingDown,\n getState: (sessionKey) => this.conversationStates.get(sessionKey),\n getOrCreateState: (createOptions) => this.getOrCreateState(createOptions),\n beforeRunTracked: (runPromise) => {\n this.inFlightRuns.add(runPromise);\n Sentry.metrics.gauge(\"agent.sessions.active\", this.inFlightRuns.size);\n },\n afterRunTracked: (runPromise) => {\n this.inFlightRuns.delete(runPromise);\n },\n onRunFinished: () => {\n Sentry.metrics.gauge(\"agent.sessions.active\", this.inFlightRuns.size - 1);\n this.evictIdleSessions();\n },\n });\n }\n\n isRunning(sessionKey: string): boolean {\n const state = this.conversationStates.get(sessionKey);\n return !!state?.running;\n }\n\n getRunningSessions(): RunningSession[] {\n const sessions: RunningSession[] = [];\n for (const [sessionKey, state] of this.conversationStates) {\n if (state.running && state.startedAt) {\n const currentStep = state.runner.getCurrentStep();\n sessions.push({\n sessionKey,\n startedAt: state.startedAt,\n lastActivityAt: state.lastActivityAt,\n currentTool: currentStep?.label || currentStep?.toolName,\n });\n }\n }\n return sessions;\n }\n\n async handleStop(sessionKey: string, conversationId: string, bot: Bot): Promise<void> {\n const state = this.conversationStates.get(sessionKey);\n if (state?.running) {\n state.stopRequested = true;\n state.runner.abort();\n const ts = await bot.postMessage(conversationId, formatStopping(bot));\n state.stopMessageTs = ts;\n } else {\n await bot.postMessage(conversationId, formatNothingRunning(bot));\n }\n }\n\n forceStop(sessionKey: string): void {\n const state = this.conversationStates.get(sessionKey);\n if (state?.running) {\n log.logInfo(`[Force Stop] Force stopping session: ${sessionKey}`);\n state.stopRequested = true;\n state.runner.abort();\n state.running = false;\n }\n }\n\n async handleNewCommand(sessionKey: string, conversationId: string, bot: Bot): Promise<void> {\n const state = this.conversationStates.get(sessionKey);\n if (state?.running) {\n state.stopRequested = true;\n state.runner.abort();\n }\n\n const conversationDir = join(this.options.workingDir, conversationId);\n const runtimeCwd = runtimeCwdForSandbox(\n this.options.sandbox,\n this.options.workingDir,\n conversationId,\n );\n if (sessionKey.includes(\":\")) {\n createManagedSessionFileAtPath(getThreadSessionFile(conversationDir, sessionKey), runtimeCwd);\n } else {\n createManagedSessionFile(getChannelSessionDir(conversationDir), runtimeCwd);\n }\n\n this.conversationStates.delete(sessionKey);\n\n log.logInfo(`[${conversationId}] Session reset: ${sessionKey}`);\n await bot.postMessage(conversationId, \"Conversation reset. Send a new message to start fresh.\");\n }\n\n async handleEvent(\n event: BotEvent,\n bot: Bot,\n adapters: BotAdapters,\n isSyntheticEvent?: boolean,\n ): Promise<void> {\n const sessionKey = event.sessionKey ?? `${event.conversationId}:${event.thread_ts ?? event.ts}`;\n const previous = this.sessionQueues.get(sessionKey) ?? Promise.resolve();\n const next = previous\n .catch(() => {})\n .then(() => this.runSession({ event, bot, adapters, isSyntheticEvent }));\n this.sessionQueues.set(sessionKey, next);\n try {\n await next;\n } finally {\n if (this.sessionQueues.get(sessionKey) === next) {\n this.sessionQueues.delete(sessionKey);\n }\n }\n }\n\n async runSession({ event, bot, adapters, isSyntheticEvent }: RunSessionOptions): Promise<void> {\n await this.orchestrator.runSession({ event, bot, adapters, isSyntheticEvent });\n }\n\n async createSessionSandbox(options: CreateSessionSandboxOptions): Promise<AgentRunner> {\n const state = await this.getOrCreateState(options);\n return state.runner;\n }\n\n switchConversationModel(conversationId: string, _provider: string, _model: string): boolean {\n for (const [sessionKey, state] of this.conversationStates) {\n if (this.isConversationSession(sessionKey, conversationId) && state.running) {\n return false;\n }\n }\n\n for (const sessionKey of Array.from(this.conversationStates.keys())) {\n if (this.isConversationSession(sessionKey, conversationId)) {\n this.conversationStates.delete(sessionKey);\n }\n }\n log.logInfo(`[${conversationId}] Model switched; cleared cached session runners`);\n return true;\n }\n\n private isConversationSession(sessionKey: string, conversationId: string): boolean {\n return sessionKey === conversationId || sessionKey.startsWith(`${conversationId}:`);\n }\n\n private async getOrCreateState({\n conversationId,\n platformName,\n sessionKey,\n }: CreateSessionSandboxOptions): Promise<ConversationState> {\n const existing = this.conversationStates.get(sessionKey);\n if (existing) {\n existing.lastAccessedAt = Date.now();\n return existing;\n }\n\n const conversationDir = join(this.options.workingDir, conversationId);\n const runtimeCwd = runtimeCwdForSandbox(\n this.options.sandbox,\n this.options.workingDir,\n conversationId,\n );\n const sessionScope = await this.resolveSessionScope(\n platformName,\n conversationDir,\n sessionKey,\n runtimeCwd,\n );\n const state: ConversationState = {\n running: false,\n runner: await createRunner(\n this.options.sandbox,\n sessionKey,\n conversationId,\n conversationDir,\n this.options.workingDir,\n sessionScope,\n this.options.vaultManager,\n this.options.provisioner,\n ),\n stopRequested: false,\n lastAccessedAt: Date.now(),\n };\n this.conversationStates.set(sessionKey, state);\n return state;\n }\n\n async shutdown(timeoutMs = 30_000): Promise<void> {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n log.logInfo(\"Shutting down gracefully...\");\n\n const timeout = Date.now() + timeoutMs;\n while (this.inFlightRuns.size > 0 && Date.now() < timeout) {\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n\n if (this.inFlightRuns.size > 0) {\n log.logWarning(`Forcing exit with ${this.inFlightRuns.size} runs still in progress`);\n }\n }\n\n private async resolveSessionScope(\n platformName: string,\n conversationDir: string,\n sessionKey: string,\n cwd: string,\n ): Promise<ResolvedSessionScope> {\n if (sessionKey.includes(\":event:\")) {\n const sessionDir = getChannelSessionDir(conversationDir);\n const contextFile = createManagedSessionFileAtPath(\n getThreadSessionFile(conversationDir, sessionKey),\n cwd,\n );\n return { sessionDir, contextFile, threadRootMessage: null };\n }\n\n if (platformName === \"slack\") {\n return resolveSlackSessionScope({ conversationDir, sessionKey, cwd });\n }\n return resolveGenericSessionScope({ conversationDir, sessionKey, cwd });\n }\n\n private evictIdleSessions(): void {\n const now = Date.now();\n\n for (const [key, state] of this.conversationStates) {\n if (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {\n this.conversationStates.delete(key);\n }\n }\n\n if (this.conversationStates.size > MAX_SESSIONS) {\n const idleSessions: Array<{ key: string; lastAccessedAt: number }> = [];\n for (const [key, state] of this.conversationStates) {\n if (!state.running) {\n idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });\n }\n }\n\n idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);\n\n const toEvict = this.conversationStates.size - MAX_SESSIONS;\n for (let i = 0; i < toEvict && i < idleSessions.length; i++) {\n this.conversationStates.delete(idleSessions[i].key);\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CloudflareSandboxConfig, ExecOptions, ExecResult, Executor, RuntimePathContext, SandboxAdapter } from "./types.js";
|
|
2
|
+
export declare function parseCloudflareSandboxArg(value: string): CloudflareSandboxConfig | undefined;
|
|
3
|
+
export declare function validateCloudflareSandbox(_config: CloudflareSandboxConfig): Promise<void>;
|
|
4
|
+
export declare class CloudflareSandboxExecutor implements Executor {
|
|
5
|
+
private readonly sandboxId;
|
|
6
|
+
private readonly env?;
|
|
7
|
+
private readonly cwd;
|
|
8
|
+
constructor(sandboxId: string, env?: Record<string, string> | undefined, _ensureReady?: () => Promise<void>);
|
|
9
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
10
|
+
getWorkspacePath(_hostPath: string): string;
|
|
11
|
+
getPathContext(hostWorkspaceRoot: string): RuntimePathContext;
|
|
12
|
+
getSandboxConfig(): CloudflareSandboxConfig;
|
|
13
|
+
}
|
|
14
|
+
export declare const cloudflareSandboxAdapter: SandboxAdapter<CloudflareSandboxConfig>;
|
|
15
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/sandbox/cloudflare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,cAAc,EACf,MAAM,YAAY,CAAC;AAoBpB,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAa5F;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB/F;AAED,qBAAa,yBAA0B,YAAW,QAAQ;IAItD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IAJvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YACmB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC7C,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAGnC;IAEK,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAiEtE;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1C;IAED,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,kBAAkB,CAK5D;IAED,gBAAgB,IAAI,uBAAuB,CAE1C;CACF;AAED,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,uBAAuB,CAM5E,CAAC","sourcesContent":["import type {\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport { SandboxError } from \"./errors.js\";\n\nconst DEFAULT_CLOUDFLARE_CWD = \"/workspace\";\n\ninterface CloudflareExecPayload {\n sandboxId: string;\n command: string;\n timeoutSeconds?: number;\n cwd?: string;\n env?: Record<string, string>;\n}\n\ninterface CloudflareExecResponse {\n stdout?: string;\n stderr?: string;\n code?: number;\n error?: string;\n}\n\nexport function parseCloudflareSandboxArg(value: string): CloudflareSandboxConfig | undefined {\n if (!value.startsWith(\"cloudflare:\")) {\n return undefined;\n }\n\n const sandboxId = value.slice(\"cloudflare:\".length).trim();\n if (!sandboxId) {\n throw new SandboxError(\n \"Error: cloudflare sandbox requires sandbox id (e.g., cloudflare:slack-u123)\",\n );\n }\n\n return { type: \"cloudflare\", sandboxId };\n}\n\nexport async function validateCloudflareSandbox(_config: CloudflareSandboxConfig): Promise<void> {\n const url = resolveCloudflareSandboxUrl();\n try {\n const response = await fetch(new URL(\"/health\", url), {\n headers: buildCloudflareHeaders(),\n });\n if (!response.ok) {\n throw new SandboxError(\n `Error: Cloudflare sandbox bridge health check failed with HTTP ${response.status}`,\n );\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n const detail = error instanceof Error ? error.message : String(error);\n throw new SandboxError(`Error: Cloudflare sandbox bridge is not reachable: ${detail}`);\n }\n\n console.log(\n ` Cloudflare sandbox bridge enabled. Base URL: ${url.toString().replace(/\\/$/, \"\")}`,\n );\n}\n\nexport class CloudflareSandboxExecutor implements Executor {\n private readonly cwd: string;\n\n constructor(\n private readonly sandboxId: string,\n private readonly env?: Record<string, string>,\n _ensureReady?: () => Promise<void>,\n ) {\n this.cwd = process.env.MAMA_CLOUDFLARE_SANDBOX_CWD?.trim() || DEFAULT_CLOUDFLARE_CWD;\n }\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const controller = new AbortController();\n const timeoutHandle =\n options?.timeout && options.timeout > 0\n ? setTimeout(() => controller.abort(), options.timeout * 1000)\n : undefined;\n\n const onAbort = () => controller.abort();\n if (options?.signal) {\n if (options.signal.aborted) {\n controller.abort();\n } else {\n options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n try {\n const payload: CloudflareExecPayload = {\n sandboxId: this.sandboxId,\n command,\n cwd: this.cwd,\n };\n if (options?.timeout) payload.timeoutSeconds = options.timeout;\n if (this.env && Object.keys(this.env).length > 0) payload.env = this.env;\n\n const response = await fetch(new URL(\"/exec\", resolveCloudflareSandboxUrl()), {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...buildCloudflareHeaders(),\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n const raw = (await response.text()).trim();\n const parsed = raw ? (JSON.parse(raw) as CloudflareExecResponse) : {};\n\n if (!response.ok) {\n throw new Error(\n parsed.error ||\n parsed.stderr ||\n `Cloudflare sandbox bridge returned HTTP ${response.status}`,\n );\n }\n\n return {\n stdout: parsed.stdout || \"\",\n stderr: parsed.stderr || \"\",\n code: parsed.code ?? 0,\n };\n } catch (error) {\n if (controller.signal.aborted) {\n if (options?.signal?.aborted) {\n throw new Error(\"Command aborted\", { cause: error });\n }\n throw new Error(`Command timed out after ${options?.timeout} seconds`, { cause: error });\n }\n throw error;\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (options?.signal) {\n options.signal.removeEventListener(\"abort\", onAbort);\n }\n }\n }\n\n getWorkspacePath(_hostPath: string): string {\n return this.cwd;\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return {\n hostWorkspaceRoot,\n runtimeWorkspaceRoot: this.cwd,\n };\n }\n\n getSandboxConfig(): CloudflareSandboxConfig {\n return { type: \"cloudflare\", sandboxId: this.sandboxId };\n }\n}\n\nexport const cloudflareSandboxAdapter: SandboxAdapter<CloudflareSandboxConfig> = {\n type: \"cloudflare\",\n parse: parseCloudflareSandboxArg,\n validate: validateCloudflareSandbox,\n createExecutor: (config, env, ensureReady) =>\n new CloudflareSandboxExecutor(config.sandboxId, env, ensureReady),\n};\n\nfunction resolveCloudflareSandboxUrl(): URL {\n const raw = process.env.MAMA_CLOUDFLARE_SANDBOX_URL?.trim();\n if (!raw) {\n throw new SandboxError(\n \"Error: MAMA_CLOUDFLARE_SANDBOX_URL is required for cloudflare sandbox mode\",\n );\n }\n\n try {\n return new URL(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new SandboxError(`Error: invalid MAMA_CLOUDFLARE_SANDBOX_URL: ${detail}`);\n }\n}\n\nfunction buildCloudflareHeaders(): Record<string, string> {\n const token = process.env.MAMA_CLOUDFLARE_SANDBOX_TOKEN?.trim();\n return token ? { authorization: `Bearer ${token}` } : {};\n}\n"]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { SandboxError } from "./errors.js";
|
|
2
|
+
const DEFAULT_CLOUDFLARE_CWD = "/workspace";
|
|
3
|
+
export function parseCloudflareSandboxArg(value) {
|
|
4
|
+
if (!value.startsWith("cloudflare:")) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const sandboxId = value.slice("cloudflare:".length).trim();
|
|
8
|
+
if (!sandboxId) {
|
|
9
|
+
throw new SandboxError("Error: cloudflare sandbox requires sandbox id (e.g., cloudflare:slack-u123)");
|
|
10
|
+
}
|
|
11
|
+
return { type: "cloudflare", sandboxId };
|
|
12
|
+
}
|
|
13
|
+
export async function validateCloudflareSandbox(_config) {
|
|
14
|
+
const url = resolveCloudflareSandboxUrl();
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(new URL("/health", url), {
|
|
17
|
+
headers: buildCloudflareHeaders(),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new SandboxError(`Error: Cloudflare sandbox bridge health check failed with HTTP ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof SandboxError) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
28
|
+
throw new SandboxError(`Error: Cloudflare sandbox bridge is not reachable: ${detail}`);
|
|
29
|
+
}
|
|
30
|
+
console.log(` Cloudflare sandbox bridge enabled. Base URL: ${url.toString().replace(/\/$/, "")}`);
|
|
31
|
+
}
|
|
32
|
+
export class CloudflareSandboxExecutor {
|
|
33
|
+
constructor(sandboxId, env, _ensureReady) {
|
|
34
|
+
this.sandboxId = sandboxId;
|
|
35
|
+
this.env = env;
|
|
36
|
+
this.cwd = process.env.MAMA_CLOUDFLARE_SANDBOX_CWD?.trim() || DEFAULT_CLOUDFLARE_CWD;
|
|
37
|
+
}
|
|
38
|
+
async exec(command, options) {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeoutHandle = options?.timeout && options.timeout > 0
|
|
41
|
+
? setTimeout(() => controller.abort(), options.timeout * 1000)
|
|
42
|
+
: undefined;
|
|
43
|
+
const onAbort = () => controller.abort();
|
|
44
|
+
if (options?.signal) {
|
|
45
|
+
if (options.signal.aborted) {
|
|
46
|
+
controller.abort();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const payload = {
|
|
54
|
+
sandboxId: this.sandboxId,
|
|
55
|
+
command,
|
|
56
|
+
cwd: this.cwd,
|
|
57
|
+
};
|
|
58
|
+
if (options?.timeout)
|
|
59
|
+
payload.timeoutSeconds = options.timeout;
|
|
60
|
+
if (this.env && Object.keys(this.env).length > 0)
|
|
61
|
+
payload.env = this.env;
|
|
62
|
+
const response = await fetch(new URL("/exec", resolveCloudflareSandboxUrl()), {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"content-type": "application/json",
|
|
66
|
+
...buildCloudflareHeaders(),
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify(payload),
|
|
69
|
+
signal: controller.signal,
|
|
70
|
+
});
|
|
71
|
+
const raw = (await response.text()).trim();
|
|
72
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(parsed.error ||
|
|
75
|
+
parsed.stderr ||
|
|
76
|
+
`Cloudflare sandbox bridge returned HTTP ${response.status}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
stdout: parsed.stdout || "",
|
|
80
|
+
stderr: parsed.stderr || "",
|
|
81
|
+
code: parsed.code ?? 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (controller.signal.aborted) {
|
|
86
|
+
if (options?.signal?.aborted) {
|
|
87
|
+
throw new Error("Command aborted", { cause: error });
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Command timed out after ${options?.timeout} seconds`, { cause: error });
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
if (timeoutHandle)
|
|
95
|
+
clearTimeout(timeoutHandle);
|
|
96
|
+
if (options?.signal) {
|
|
97
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getWorkspacePath(_hostPath) {
|
|
102
|
+
return this.cwd;
|
|
103
|
+
}
|
|
104
|
+
getPathContext(hostWorkspaceRoot) {
|
|
105
|
+
return {
|
|
106
|
+
hostWorkspaceRoot,
|
|
107
|
+
runtimeWorkspaceRoot: this.cwd,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
getSandboxConfig() {
|
|
111
|
+
return { type: "cloudflare", sandboxId: this.sandboxId };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export const cloudflareSandboxAdapter = {
|
|
115
|
+
type: "cloudflare",
|
|
116
|
+
parse: parseCloudflareSandboxArg,
|
|
117
|
+
validate: validateCloudflareSandbox,
|
|
118
|
+
createExecutor: (config, env, ensureReady) => new CloudflareSandboxExecutor(config.sandboxId, env, ensureReady),
|
|
119
|
+
};
|
|
120
|
+
function resolveCloudflareSandboxUrl() {
|
|
121
|
+
const raw = process.env.MAMA_CLOUDFLARE_SANDBOX_URL?.trim();
|
|
122
|
+
if (!raw) {
|
|
123
|
+
throw new SandboxError("Error: MAMA_CLOUDFLARE_SANDBOX_URL is required for cloudflare sandbox mode");
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return new URL(raw);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
130
|
+
throw new SandboxError(`Error: invalid MAMA_CLOUDFLARE_SANDBOX_URL: ${detail}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function buildCloudflareHeaders() {
|
|
134
|
+
const token = process.env.MAMA_CLOUDFLARE_SANDBOX_TOKEN?.trim();
|
|
135
|
+
return token ? { authorization: `Bearer ${token}` } : {};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/sandbox/cloudflare.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CloudflareSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport { SandboxError } from \"./errors.js\";\n\nconst DEFAULT_CLOUDFLARE_CWD = \"/workspace\";\n\ninterface CloudflareExecPayload {\n sandboxId: string;\n command: string;\n timeoutSeconds?: number;\n cwd?: string;\n env?: Record<string, string>;\n}\n\ninterface CloudflareExecResponse {\n stdout?: string;\n stderr?: string;\n code?: number;\n error?: string;\n}\n\nexport function parseCloudflareSandboxArg(value: string): CloudflareSandboxConfig | undefined {\n if (!value.startsWith(\"cloudflare:\")) {\n return undefined;\n }\n\n const sandboxId = value.slice(\"cloudflare:\".length).trim();\n if (!sandboxId) {\n throw new SandboxError(\n \"Error: cloudflare sandbox requires sandbox id (e.g., cloudflare:slack-u123)\",\n );\n }\n\n return { type: \"cloudflare\", sandboxId };\n}\n\nexport async function validateCloudflareSandbox(_config: CloudflareSandboxConfig): Promise<void> {\n const url = resolveCloudflareSandboxUrl();\n try {\n const response = await fetch(new URL(\"/health\", url), {\n headers: buildCloudflareHeaders(),\n });\n if (!response.ok) {\n throw new SandboxError(\n `Error: Cloudflare sandbox bridge health check failed with HTTP ${response.status}`,\n );\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n const detail = error instanceof Error ? error.message : String(error);\n throw new SandboxError(`Error: Cloudflare sandbox bridge is not reachable: ${detail}`);\n }\n\n console.log(\n ` Cloudflare sandbox bridge enabled. Base URL: ${url.toString().replace(/\\/$/, \"\")}`,\n );\n}\n\nexport class CloudflareSandboxExecutor implements Executor {\n private readonly cwd: string;\n\n constructor(\n private readonly sandboxId: string,\n private readonly env?: Record<string, string>,\n _ensureReady?: () => Promise<void>,\n ) {\n this.cwd = process.env.MAMA_CLOUDFLARE_SANDBOX_CWD?.trim() || DEFAULT_CLOUDFLARE_CWD;\n }\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n const controller = new AbortController();\n const timeoutHandle =\n options?.timeout && options.timeout > 0\n ? setTimeout(() => controller.abort(), options.timeout * 1000)\n : undefined;\n\n const onAbort = () => controller.abort();\n if (options?.signal) {\n if (options.signal.aborted) {\n controller.abort();\n } else {\n options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n try {\n const payload: CloudflareExecPayload = {\n sandboxId: this.sandboxId,\n command,\n cwd: this.cwd,\n };\n if (options?.timeout) payload.timeoutSeconds = options.timeout;\n if (this.env && Object.keys(this.env).length > 0) payload.env = this.env;\n\n const response = await fetch(new URL(\"/exec\", resolveCloudflareSandboxUrl()), {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...buildCloudflareHeaders(),\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n const raw = (await response.text()).trim();\n const parsed = raw ? (JSON.parse(raw) as CloudflareExecResponse) : {};\n\n if (!response.ok) {\n throw new Error(\n parsed.error ||\n parsed.stderr ||\n `Cloudflare sandbox bridge returned HTTP ${response.status}`,\n );\n }\n\n return {\n stdout: parsed.stdout || \"\",\n stderr: parsed.stderr || \"\",\n code: parsed.code ?? 0,\n };\n } catch (error) {\n if (controller.signal.aborted) {\n if (options?.signal?.aborted) {\n throw new Error(\"Command aborted\", { cause: error });\n }\n throw new Error(`Command timed out after ${options?.timeout} seconds`, { cause: error });\n }\n throw error;\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (options?.signal) {\n options.signal.removeEventListener(\"abort\", onAbort);\n }\n }\n }\n\n getWorkspacePath(_hostPath: string): string {\n return this.cwd;\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return {\n hostWorkspaceRoot,\n runtimeWorkspaceRoot: this.cwd,\n };\n }\n\n getSandboxConfig(): CloudflareSandboxConfig {\n return { type: \"cloudflare\", sandboxId: this.sandboxId };\n }\n}\n\nexport const cloudflareSandboxAdapter: SandboxAdapter<CloudflareSandboxConfig> = {\n type: \"cloudflare\",\n parse: parseCloudflareSandboxArg,\n validate: validateCloudflareSandbox,\n createExecutor: (config, env, ensureReady) =>\n new CloudflareSandboxExecutor(config.sandboxId, env, ensureReady),\n};\n\nfunction resolveCloudflareSandboxUrl(): URL {\n const raw = process.env.MAMA_CLOUDFLARE_SANDBOX_URL?.trim();\n if (!raw) {\n throw new SandboxError(\n \"Error: MAMA_CLOUDFLARE_SANDBOX_URL is required for cloudflare sandbox mode\",\n );\n }\n\n try {\n return new URL(raw);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new SandboxError(`Error: invalid MAMA_CLOUDFLARE_SANDBOX_URL: ${detail}`);\n }\n}\n\nfunction buildCloudflareHeaders(): Record<string, string> {\n const token = process.env.MAMA_CLOUDFLARE_SANDBOX_TOKEN?.trim();\n return token ? { authorization: `Bearer ${token}` } : {};\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContainerSandboxConfig, ExecOptions, ExecResult, Executor, SandboxAdapter } from "./types.js";
|
|
1
|
+
import type { ContainerSandboxConfig, ExecOptions, ExecResult, Executor, RuntimePathContext, SandboxAdapter } from "./types.js";
|
|
2
2
|
export declare function parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined;
|
|
3
3
|
export declare function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void>;
|
|
4
4
|
export declare function buildContainerExecCommand(container: string, command: string, envFilePath?: string): string;
|
|
@@ -9,6 +9,7 @@ export declare class ContainerExecutor implements Executor {
|
|
|
9
9
|
constructor(container: string, env?: Record<string, string> | undefined, ensureReady?: (() => Promise<void>) | undefined);
|
|
10
10
|
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
11
11
|
getWorkspacePath(_hostPath: string): string;
|
|
12
|
+
getPathContext(hostWorkspaceRoot: string): RuntimePathContext;
|
|
12
13
|
getSandboxConfig(): ContainerSandboxConfig;
|
|
13
14
|
}
|
|
14
15
|
export declare const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACf,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,kBAAkB,EAClB,cAAc,EACf,MAAM,YAAY,CAAC;AASpB,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAY1F;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B5F;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAGR;AAED,qBAAa,iBAAkB,YAAW,QAAQ;IAE9C,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,GAAG,CAAC;IACZ,OAAO,CAAC,WAAW,CAAC;IAHtB,YACU,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAA,EAC5B,WAAW,CAAC,GAAE,MAAM,OAAO,CAAC,IAAI,CAAC,aAAA,EACvC;IAEE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAetE;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1C;IAED,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,kBAAkB,CAE5D;IAED,gBAAgB,IAAI,sBAAsB,CAEzC;CACF;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,CAAC,sBAAsB,CAM1E,CAAC","sourcesContent":["import { chmodSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport { SandboxError } from \"./errors.js\";\nimport { execSimple, shellEscape } from \"./utils.js\";\nimport { HostExecutor } from \"./host.js\";\nimport { createMountedRuntimePathContext } from \"./path-context.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport function parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mama-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nexport async function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: docker start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: docker run -d --name ${config.container} -v <workspace>:/workspace alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running.`);\n}\n\nexport function buildContainerExecCommand(\n container: string,\n command: string,\n envFilePath?: string,\n): string {\n const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : \"\";\n return `docker exec ${envPart}-w /workspace ${container} sh -c ${shellEscape(command)}`;\n}\n\nexport class ContainerExecutor implements Executor {\n constructor(\n private container: string,\n private env?: Record<string, string>,\n private ensureReady?: () => Promise<void>,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n if (this.ensureReady) {\n await this.ensureReady();\n } else {\n await ensureContainerRunning(this.container);\n }\n\n const hostExecutor = new HostExecutor();\n const temp = this.env ? createSecureEnvFile(this.env) : undefined;\n try {\n const dockerCmd = buildContainerExecCommand(this.container, command, temp?.envFilePath);\n return await hostExecutor.exec(dockerCmd, options);\n } finally {\n temp?.cleanup();\n }\n }\n\n getWorkspacePath(_hostPath: string): string {\n return \"/workspace\";\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return createMountedRuntimePathContext(hostWorkspaceRoot, \"/workspace\");\n }\n\n getSandboxConfig(): ContainerSandboxConfig {\n return { type: \"container\", container: this.container };\n }\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createExecutor: (config, env, ensureReady) =>\n new ContainerExecutor(config.container, env, ensureReady),\n};\n\nasync function ensureContainerRunning(container: string): Promise<void> {\n try {\n const running = await execSimple(\"docker\", [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(\"docker\", [\"start\", container]);\n } catch (error) {\n const details = error instanceof Error ? error.message : String(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n\nfunction createSecureEnvFile(env: Record<string, string>): {\n envFilePath: string;\n cleanup: () => void;\n} {\n const tempDir = mkdtempSync(join(tmpdir(), \"mama-docker-env-\"));\n chmodSync(tempDir, PRIVATE_DIR_MODE);\n const envFilePath = join(tempDir, \"env.list\");\n const content =\n Object.entries(env)\n .map(([key, value]) => `${key}=${sanitizeEnvValue(value)}`)\n .join(\"\\n\") + \"\\n\";\n writeFileSync(envFilePath, content, { encoding: \"utf-8\", mode: PRIVATE_FILE_MODE });\n chmodSync(envFilePath, PRIVATE_FILE_MODE);\n\n return {\n envFilePath,\n cleanup: () => {\n rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n\nfunction sanitizeEnvValue(value: string): string {\n return value.replace(/\\r?\\n/g, \"\");\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { SandboxError } from "./errors.js";
|
|
5
5
|
import { execSimple, shellEscape } from "./utils.js";
|
|
6
6
|
import { HostExecutor } from "./host.js";
|
|
7
|
+
import { createMountedRuntimePathContext } from "./path-context.js";
|
|
7
8
|
const PRIVATE_DIR_MODE = 0o700;
|
|
8
9
|
const PRIVATE_FILE_MODE = 0o600;
|
|
9
10
|
export function parseContainerSandboxArg(value) {
|
|
@@ -76,6 +77,9 @@ export class ContainerExecutor {
|
|
|
76
77
|
getWorkspacePath(_hostPath) {
|
|
77
78
|
return "/workspace";
|
|
78
79
|
}
|
|
80
|
+
getPathContext(hostWorkspaceRoot) {
|
|
81
|
+
return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
|
|
82
|
+
}
|
|
79
83
|
getSandboxConfig() {
|
|
80
84
|
return { type: "container", container: this.container };
|
|
81
85
|
}
|
|
@@ -97,7 +101,7 @@ async function ensureContainerRunning(container) {
|
|
|
97
101
|
catch (error) {
|
|
98
102
|
const details = error instanceof Error ? error.message : String(error);
|
|
99
103
|
throw new Error(`Container "${container}" is not available. ` +
|
|
100
|
-
`Expected a pre-existing container or image provisioning to keep it running.\n${details}`.trim());
|
|
104
|
+
`Expected a pre-existing container or image provisioning to keep it running.\n${details}`.trim(), { cause: error });
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
function createSecureEnvFile(env) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/sandbox/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n ContainerSandboxConfig,\n ExecOptions,\n ExecResult,\n Executor,\n RuntimePathContext,\n SandboxAdapter,\n} from \"./types.js\";\nimport { SandboxError } from \"./errors.js\";\nimport { execSimple, shellEscape } from \"./utils.js\";\nimport { HostExecutor } from \"./host.js\";\nimport { createMountedRuntimePathContext } from \"./path-context.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nexport function parseContainerSandboxArg(value: string): ContainerSandboxConfig | undefined {\n if (!value.startsWith(\"container:\")) {\n return undefined;\n }\n\n const container = value.slice(\"container:\".length);\n if (!container) {\n throw new SandboxError(\n \"Error: container sandbox requires container name (e.g., container:mama-sandbox)\",\n );\n }\n return { type: \"container\", container };\n}\n\nexport async function validateContainerSandbox(config: ContainerSandboxConfig): Promise<void> {\n try {\n await execSimple(\"docker\", [\"--version\"]);\n } catch {\n throw new SandboxError(\"Error: Docker is not installed or not in PATH\");\n }\n\n try {\n const result = await execSimple(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n config.container,\n ]);\n if (result.trim() !== \"true\") {\n throw new SandboxError(`Error: Container '${config.container}' is not running.`, [\n `Start it with: docker start ${config.container}`,\n ]);\n }\n } catch (error) {\n if (error instanceof SandboxError) {\n throw error;\n }\n throw new SandboxError(`Error: Container '${config.container}' does not exist.`, [\n `Create it with: docker run -d --name ${config.container} -v <workspace>:/workspace alpine:latest sleep infinity`,\n ]);\n }\n\n console.log(` Container '${config.container}' is running.`);\n}\n\nexport function buildContainerExecCommand(\n container: string,\n command: string,\n envFilePath?: string,\n): string {\n const envPart = envFilePath ? `--env-file ${shellEscape(envFilePath)} ` : \"\";\n return `docker exec ${envPart}-w /workspace ${container} sh -c ${shellEscape(command)}`;\n}\n\nexport class ContainerExecutor implements Executor {\n constructor(\n private container: string,\n private env?: Record<string, string>,\n private ensureReady?: () => Promise<void>,\n ) {}\n\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n if (this.ensureReady) {\n await this.ensureReady();\n } else {\n await ensureContainerRunning(this.container);\n }\n\n const hostExecutor = new HostExecutor();\n const temp = this.env ? createSecureEnvFile(this.env) : undefined;\n try {\n const dockerCmd = buildContainerExecCommand(this.container, command, temp?.envFilePath);\n return await hostExecutor.exec(dockerCmd, options);\n } finally {\n temp?.cleanup();\n }\n }\n\n getWorkspacePath(_hostPath: string): string {\n return \"/workspace\";\n }\n\n getPathContext(hostWorkspaceRoot: string): RuntimePathContext {\n return createMountedRuntimePathContext(hostWorkspaceRoot, \"/workspace\");\n }\n\n getSandboxConfig(): ContainerSandboxConfig {\n return { type: \"container\", container: this.container };\n }\n}\n\nexport const containerSandboxAdapter: SandboxAdapter<ContainerSandboxConfig> = {\n type: \"container\",\n parse: parseContainerSandboxArg,\n validate: validateContainerSandbox,\n createExecutor: (config, env, ensureReady) =>\n new ContainerExecutor(config.container, env, ensureReady),\n};\n\nasync function ensureContainerRunning(container: string): Promise<void> {\n try {\n const running = await execSimple(\"docker\", [\"inspect\", \"-f\", \"{{.State.Running}}\", container]);\n if (running.trim() === \"true\") {\n return;\n }\n await execSimple(\"docker\", [\"start\", container]);\n } catch (error) {\n const details = error instanceof Error ? error.message : String(error);\n throw new Error(\n `Container \"${container}\" is not available. ` +\n `Expected a pre-existing container or image provisioning to keep it running.\\n${details}`.trim(),\n { cause: error },\n );\n }\n}\n\nfunction createSecureEnvFile(env: Record<string, string>): {\n envFilePath: string;\n cleanup: () => void;\n} {\n const tempDir = mkdtempSync(join(tmpdir(), \"mama-docker-env-\"));\n chmodSync(tempDir, PRIVATE_DIR_MODE);\n const envFilePath = join(tempDir, \"env.list\");\n const content =\n Object.entries(env)\n .map(([key, value]) => `${key}=${sanitizeEnvValue(value)}`)\n .join(\"\\n\") + \"\\n\";\n writeFileSync(envFilePath, content, { encoding: \"utf-8\", mode: PRIVATE_FILE_MODE });\n chmodSync(envFilePath, PRIVATE_FILE_MODE);\n\n return {\n envFilePath,\n cleanup: () => {\n rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n\nfunction sanitizeEnvValue(value: string): string {\n return value.replace(/\\r?\\n/g, \"\");\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExecOptions, ExecResult, Executor, FirecrackerSandboxConfig, SandboxAdapter } from "./types.js";
|
|
1
|
+
import type { ExecOptions, ExecResult, Executor, FirecrackerSandboxConfig, RuntimePathContext, SandboxAdapter } from "./types.js";
|
|
2
2
|
export declare function parseFirecrackerSandboxArg(value: string): FirecrackerSandboxConfig | undefined;
|
|
3
3
|
export declare function validateFirecrackerSandbox(config: FirecrackerSandboxConfig): Promise<void>;
|
|
4
4
|
export declare class FirecrackerExecutor implements Executor {
|
|
@@ -10,6 +10,7 @@ export declare class FirecrackerExecutor implements Executor {
|
|
|
10
10
|
constructor(vmId: string, hostPath: string, sshUser?: string, sshPort?: number, env?: Record<string, string> | undefined);
|
|
11
11
|
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
12
12
|
getWorkspacePath(_hostPath: string): string;
|
|
13
|
+
getPathContext(hostWorkspaceRoot: string): RuntimePathContext;
|
|
13
14
|
getSandboxConfig(): FirecrackerSandboxConfig;
|
|
14
15
|
}
|
|
15
16
|
export declare const firecrackerSandboxAdapter: SandboxAdapter<FirecrackerSandboxConfig>;
|