@geminixiang/mama 0.2.0-beta.0 → 0.2.0-beta.10
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 +171 -334
- package/dist/adapter.d.ts +36 -10
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +10 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +349 -114
- 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 +102 -31
- 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 +29 -22
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +620 -186
- 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 +136 -71
- 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 +2 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +190 -123
- 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 +57 -59
- 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 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +645 -555
- 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 +53 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +320 -55
- 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 +15 -128
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +16 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +127 -58
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +24 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +115 -0
- package/dist/execution-resolver.js.map +1 -0
- 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 +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/log.d.ts +3 -7
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +20 -45
- package/dist/log.js.map +1 -1
- package/dist/login/index.d.ts +41 -0
- package/dist/login/index.d.ts.map +1 -0
- package/dist/login/index.js +202 -0
- package/dist/login/index.js.map +1 -0
- package/dist/login/portal.d.ts +19 -0
- 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/login/session.d.ts +33 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/login/session.js +68 -0
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +229 -264
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +79 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +437 -0
- package/dist/provisioner.js.map +1 -0
- 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 +16 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +126 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +17 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +212 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +11 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +89 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +22 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +54 -0
- package/dist/sandbox/index.js.map +1 -0
- 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 +67 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sandbox.d.ts +1 -39
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +1 -286
- package/dist/sandbox.js.map +1 -1
- package/dist/sentry.d.ts +2 -2
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +6 -4
- 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 +35 -8
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +182 -23
- 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 +4 -7
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +26 -52
- 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 +62 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +138 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +8 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- 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 +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +4 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +16 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +264 -0
- package/dist/vault.js.map +1 -0
- package/package.json +16 -13
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { hasMaterializedSlackBranchSession, waitForSlackBranchBootstrap, } from "../adapters/slack/branch-manager.js";
|
|
2
|
+
import { isPrivateConversation } from "../commands/utils.js";
|
|
3
|
+
import * as log from "../log.js";
|
|
4
|
+
import { addLifecycleBreadcrumb, applyRunScope } from "../sentry.js";
|
|
5
|
+
import { formatStopped } from "../ui-copy.js";
|
|
6
|
+
import * as Sentry from "@sentry/node";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
export class ConversationOrchestrator {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
async runSession({ event, bot, adapters, isSyntheticEvent, }) {
|
|
13
|
+
const conversationId = event.conversationId;
|
|
14
|
+
if (this.options.isShuttingDown()) {
|
|
15
|
+
log.logInfo(`[${conversationId}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const sessionKey = event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`;
|
|
19
|
+
const privateConversation = isPrivateConversation(event);
|
|
20
|
+
const handledCommand = await this.options.commandRegistry.handle({
|
|
21
|
+
bot,
|
|
22
|
+
responseCtx: adapters.responseCtx,
|
|
23
|
+
platform: adapters.platform.name,
|
|
24
|
+
platformUserId: event.user,
|
|
25
|
+
conversationId,
|
|
26
|
+
vaultConversationId: event.vaultConversationId,
|
|
27
|
+
sessionKey,
|
|
28
|
+
commandText: event.text,
|
|
29
|
+
privateConversation,
|
|
30
|
+
services: this.options.commandServices,
|
|
31
|
+
});
|
|
32
|
+
if (handledCommand)
|
|
33
|
+
return;
|
|
34
|
+
const conversationDir = join(this.options.workingDir, conversationId);
|
|
35
|
+
const waitedForParent = adapters.platform.name === "slack" && !isSyntheticEvent
|
|
36
|
+
? await waitForSlackBranchBootstrap({
|
|
37
|
+
parentSessionKey: conversationId,
|
|
38
|
+
sessionKey,
|
|
39
|
+
hasThreadSession: () => hasMaterializedSlackBranchSession(conversationDir, sessionKey),
|
|
40
|
+
isParentRunning: () => this.options.getState(conversationId)?.running === true,
|
|
41
|
+
})
|
|
42
|
+
: false;
|
|
43
|
+
if (waitedForParent) {
|
|
44
|
+
log.logInfo(`[${conversationId}] Delayed thread bootstrap until parent session sealed: ${sessionKey}`);
|
|
45
|
+
}
|
|
46
|
+
const state = await this.options.getOrCreateState({
|
|
47
|
+
conversationId,
|
|
48
|
+
platformName: adapters.platform.name,
|
|
49
|
+
sessionKey,
|
|
50
|
+
});
|
|
51
|
+
state.running = true;
|
|
52
|
+
state.stopRequested = false;
|
|
53
|
+
state.startedAt = Date.now();
|
|
54
|
+
state.lastActivityAt = Date.now();
|
|
55
|
+
log.logInfo(`[${conversationId}] Starting run: ${event.text.substring(0, 50)}`);
|
|
56
|
+
const runPromise = (async () => {
|
|
57
|
+
try {
|
|
58
|
+
const result = await this.runWithInstrumentation(adapters, { conversationId, sessionKey, isSyntheticEvent, startedAt: state.startedAt }, async () => {
|
|
59
|
+
await adapters.responseCtx.setTyping(true);
|
|
60
|
+
await adapters.responseCtx.setWorking(true);
|
|
61
|
+
const runnerResult = await state.runner.run(adapters.message, adapters.responseCtx, adapters.platform);
|
|
62
|
+
await adapters.responseCtx.setWorking(false);
|
|
63
|
+
return runnerResult;
|
|
64
|
+
});
|
|
65
|
+
if (result?.stopReason === "aborted" && state.stopRequested) {
|
|
66
|
+
if (state.stopMessageTs) {
|
|
67
|
+
await bot.updateMessage(conversationId, state.stopMessageTs, formatStopped(bot));
|
|
68
|
+
state.stopMessageTs = undefined;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
await bot.postMessage(conversationId, formatStopped(bot));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
state.running = false;
|
|
77
|
+
state.lastAccessedAt = Date.now();
|
|
78
|
+
this.options.onRunFinished();
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
this.options.beforeRunTracked(runPromise);
|
|
82
|
+
try {
|
|
83
|
+
await runPromise;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.options.afterRunTracked(runPromise);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async runWithInstrumentation(adapters, meta, body) {
|
|
90
|
+
const { conversationId, sessionKey, isSyntheticEvent, startedAt } = meta;
|
|
91
|
+
const { message, platform } = adapters;
|
|
92
|
+
Sentry.metrics.count("agent.run.started", 1, {
|
|
93
|
+
attributes: { channel: conversationId },
|
|
94
|
+
});
|
|
95
|
+
return Sentry.startSpan({ name: "agent.run", op: "agent", attributes: { conversationId, sessionKey } }, async () => Sentry.withScope(async (scope) => {
|
|
96
|
+
applyRunScope(scope, {
|
|
97
|
+
conversationId,
|
|
98
|
+
sessionKey,
|
|
99
|
+
messageId: message.id,
|
|
100
|
+
platform: platform.name,
|
|
101
|
+
userId: message.userId,
|
|
102
|
+
userName: message.userName,
|
|
103
|
+
threadTs: message.threadTs,
|
|
104
|
+
isSyntheticEvent,
|
|
105
|
+
});
|
|
106
|
+
addLifecycleBreadcrumb("agent.run.started", {
|
|
107
|
+
channel_id: conversationId,
|
|
108
|
+
platform: platform.name,
|
|
109
|
+
has_attachments: (message.attachments?.length ?? 0) > 0,
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
const result = await body();
|
|
113
|
+
const durationMs = Date.now() - startedAt;
|
|
114
|
+
const completionAttrs = {
|
|
115
|
+
channel: conversationId,
|
|
116
|
+
platform: platform.name,
|
|
117
|
+
stop_reason: result.stopReason,
|
|
118
|
+
};
|
|
119
|
+
Sentry.metrics.distribution("agent.run.duration", durationMs, {
|
|
120
|
+
unit: "millisecond",
|
|
121
|
+
attributes: completionAttrs,
|
|
122
|
+
});
|
|
123
|
+
Sentry.metrics.count("agent.run.completed", 1, { attributes: completionAttrs });
|
|
124
|
+
addLifecycleBreadcrumb("agent.run.completed", {
|
|
125
|
+
channel_id: conversationId,
|
|
126
|
+
platform: platform.name,
|
|
127
|
+
stop_reason: result.stopReason,
|
|
128
|
+
duration_ms: durationMs,
|
|
129
|
+
});
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
scope.setContext("agent_run_error", {
|
|
134
|
+
conversationId,
|
|
135
|
+
sessionKey,
|
|
136
|
+
platform: platform.name,
|
|
137
|
+
messageId: message.id,
|
|
138
|
+
threadTs: message.threadTs,
|
|
139
|
+
});
|
|
140
|
+
Sentry.captureException(err);
|
|
141
|
+
Sentry.metrics.count("agent.run.errors", 1, {
|
|
142
|
+
attributes: { channel: conversationId, platform: platform.name },
|
|
143
|
+
});
|
|
144
|
+
log.logWarning(`[${conversationId}] Run error`, err instanceof Error ? err.message : String(err));
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=conversation-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-orchestrator.js","sourceRoot":"","sources":["../../src/runtime/conversation-orchestrator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Bot, BotAdapters, BotEvent, PlatformName } from \"../adapter.js\";\nimport {\n hasMaterializedSlackBranchSession,\n waitForSlackBranchBootstrap,\n} from \"../adapters/slack/branch-manager.js\";\nimport type { AgentRunner } from \"../agent.js\";\nimport type { CommandRegistry } from \"../commands/index.js\";\nimport type { CommandServices } from \"../commands/index.js\";\nimport { isPrivateConversation } from \"../commands/utils.js\";\nimport * as log from \"../log.js\";\nimport { addLifecycleBreadcrumb, applyRunScope } from \"../sentry.js\";\nimport { formatStopped } from \"../ui-copy.js\";\nimport * as Sentry from \"@sentry/node\";\nimport { join } from \"path\";\n\nexport interface ConversationRuntimeState {\n running: boolean;\n runner: AgentRunner;\n stopRequested: boolean;\n stopMessageTs?: string;\n lastAccessedAt: number;\n startedAt?: number;\n lastActivityAt?: number;\n}\n\nexport interface RunConversationOptions {\n event: BotEvent;\n bot: Bot;\n adapters: BotAdapters;\n isSyntheticEvent?: boolean;\n}\n\ninterface ConversationOrchestratorOptions {\n workingDir: string;\n commandRegistry: CommandRegistry;\n commandServices: CommandServices;\n isShuttingDown: () => boolean;\n getState: (sessionKey: string) => ConversationRuntimeState | undefined;\n getOrCreateState: (options: {\n conversationId: string;\n platformName: string;\n sessionKey: string;\n }) => Promise<ConversationRuntimeState>;\n beforeRunTracked: (runPromise: Promise<void>) => void;\n afterRunTracked: (runPromise: Promise<void>) => void;\n onRunFinished: () => void;\n}\n\nexport class ConversationOrchestrator {\n constructor(private readonly options: ConversationOrchestratorOptions) {}\n\n async runSession({\n event,\n bot,\n adapters,\n isSyntheticEvent,\n }: RunConversationOptions): Promise<void> {\n const conversationId = event.conversationId;\n if (this.options.isShuttingDown()) {\n log.logInfo(\n `[${conversationId}] Rejected event during shutdown: ${event.text.substring(0, 50)}`,\n );\n return;\n }\n\n const sessionKey = event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`;\n const privateConversation = isPrivateConversation(event);\n const handledCommand = await this.options.commandRegistry.handle({\n bot,\n responseCtx: adapters.responseCtx,\n platform: adapters.platform.name as PlatformName,\n platformUserId: event.user,\n conversationId,\n vaultConversationId: event.vaultConversationId,\n sessionKey,\n commandText: event.text,\n privateConversation,\n services: this.options.commandServices,\n });\n if (handledCommand) return;\n\n const conversationDir = join(this.options.workingDir, conversationId);\n const waitedForParent =\n adapters.platform.name === \"slack\" && !isSyntheticEvent\n ? await waitForSlackBranchBootstrap({\n parentSessionKey: conversationId,\n sessionKey,\n hasThreadSession: () => hasMaterializedSlackBranchSession(conversationDir, sessionKey),\n isParentRunning: () => this.options.getState(conversationId)?.running === true,\n })\n : false;\n if (waitedForParent) {\n log.logInfo(\n `[${conversationId}] Delayed thread bootstrap until parent session sealed: ${sessionKey}`,\n );\n }\n\n const state = await this.options.getOrCreateState({\n conversationId,\n platformName: adapters.platform.name,\n sessionKey,\n });\n\n state.running = true;\n state.stopRequested = false;\n state.startedAt = Date.now();\n state.lastActivityAt = Date.now();\n\n log.logInfo(`[${conversationId}] Starting run: ${event.text.substring(0, 50)}`);\n\n const runPromise = (async () => {\n try {\n const result = await this.runWithInstrumentation(\n adapters,\n { conversationId, sessionKey, isSyntheticEvent, startedAt: state.startedAt! },\n async () => {\n await adapters.responseCtx.setTyping(true);\n await adapters.responseCtx.setWorking(true);\n const runnerResult = await state.runner.run(\n adapters.message,\n adapters.responseCtx,\n adapters.platform,\n );\n await adapters.responseCtx.setWorking(false);\n return runnerResult;\n },\n );\n\n if (result?.stopReason === \"aborted\" && state.stopRequested) {\n if (state.stopMessageTs) {\n await bot.updateMessage(conversationId, state.stopMessageTs, formatStopped(bot));\n state.stopMessageTs = undefined;\n } else {\n await bot.postMessage(conversationId, formatStopped(bot));\n }\n }\n } finally {\n state.running = false;\n state.lastAccessedAt = Date.now();\n this.options.onRunFinished();\n }\n })();\n\n this.options.beforeRunTracked(runPromise);\n try {\n await runPromise;\n } finally {\n this.options.afterRunTracked(runPromise);\n }\n }\n\n private async runWithInstrumentation(\n adapters: BotAdapters,\n meta: {\n conversationId: string;\n sessionKey: string;\n isSyntheticEvent?: boolean;\n startedAt: number;\n },\n body: () => Promise<{ stopReason: string; errorMessage?: string }>,\n ): Promise<{ stopReason: string; errorMessage?: string } | undefined> {\n const { conversationId, sessionKey, isSyntheticEvent, startedAt } = meta;\n const { message, platform } = adapters;\n\n Sentry.metrics.count(\"agent.run.started\", 1, {\n attributes: { channel: conversationId },\n });\n\n return Sentry.startSpan(\n { name: \"agent.run\", op: \"agent\", attributes: { conversationId, sessionKey } },\n async () =>\n Sentry.withScope(async (scope) => {\n applyRunScope(scope, {\n conversationId,\n sessionKey,\n messageId: message.id,\n platform: platform.name,\n userId: message.userId,\n userName: message.userName,\n threadTs: message.threadTs,\n isSyntheticEvent,\n });\n addLifecycleBreadcrumb(\"agent.run.started\", {\n channel_id: conversationId,\n platform: platform.name,\n has_attachments: (message.attachments?.length ?? 0) > 0,\n });\n\n try {\n const result = await body();\n const durationMs = Date.now() - startedAt;\n const completionAttrs = {\n channel: conversationId,\n platform: platform.name,\n stop_reason: result.stopReason,\n };\n Sentry.metrics.distribution(\"agent.run.duration\", durationMs, {\n unit: \"millisecond\",\n attributes: completionAttrs,\n });\n Sentry.metrics.count(\"agent.run.completed\", 1, { attributes: completionAttrs });\n addLifecycleBreadcrumb(\"agent.run.completed\", {\n channel_id: conversationId,\n platform: platform.name,\n stop_reason: result.stopReason,\n duration_ms: durationMs,\n });\n return result;\n } catch (err) {\n scope.setContext(\"agent_run_error\", {\n conversationId,\n sessionKey,\n platform: platform.name,\n messageId: message.id,\n threadTs: message.threadTs,\n });\n Sentry.captureException(err);\n Sentry.metrics.count(\"agent.run.errors\", 1, {\n attributes: { channel: conversationId, platform: platform.name },\n });\n log.logWarning(\n `[${conversationId}] Run error`,\n err instanceof Error ? err.message : String(err),\n );\n return undefined;\n }\n }),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n createSessionRuntime,\n type CreateSessionSandboxOptions,\n type RunSessionOptions,\n type SessionRuntime,\n type SessionRuntimeOptions,\n} from \"./session-runtime.js\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,GAKrB,MAAM,sBAAsB,CAAC","sourcesContent":["export {\n createSessionRuntime,\n type CreateSessionSandboxOptions,\n type RunSessionOptions,\n type SessionRuntime,\n type SessionRuntimeOptions,\n} from \"./session-runtime.js\";\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Bot, BotAdapters, BotEvent, BotHandler } from "../adapter.js";
|
|
2
|
+
import { type AgentRunner } from "../agent.js";
|
|
3
|
+
import { CommandRegistry } from "../commands/index.js";
|
|
4
|
+
import type { CommandServices } from "../commands/index.js";
|
|
5
|
+
export interface RunSessionOptions {
|
|
6
|
+
event: BotEvent;
|
|
7
|
+
bot: Bot;
|
|
8
|
+
adapters: BotAdapters;
|
|
9
|
+
isSyntheticEvent?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateSessionSandboxOptions {
|
|
12
|
+
conversationId: string;
|
|
13
|
+
platformName: string;
|
|
14
|
+
sessionKey: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionRuntimeOptions extends CommandServices {
|
|
17
|
+
/** Override the default command registry (e.g., to add /help, /status). */
|
|
18
|
+
commandRegistry?: CommandRegistry;
|
|
19
|
+
}
|
|
20
|
+
export interface SessionRuntime extends BotHandler {
|
|
21
|
+
runSession(options: RunSessionOptions): Promise<void>;
|
|
22
|
+
createSessionSandbox(options: CreateSessionSandboxOptions): Promise<AgentRunner>;
|
|
23
|
+
switchConversationModel(conversationId: string, provider: string, model: string): boolean;
|
|
24
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createSessionRuntime(options: SessionRuntimeOptions): SessionRuntime;
|
|
27
|
+
//# sourceMappingURL=session-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/session-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAkB,MAAM,eAAe,CAAC;AAE5F,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAgC,MAAM,sBAAsB,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAqB5D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,WAAW,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,2EAA2E;IAC3E,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjF,uBAAuB,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAiBD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAEnF","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,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"]}
|