@geminixiang/mama 0.2.0-beta.1 → 0.2.0-beta.3
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 +133 -78
- package/dist/adapter.d.ts +22 -10
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +10 -7
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +228 -69
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +92 -34
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +23 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +57 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +19 -11
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +356 -96
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +21 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +96 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +100 -67
- 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/telegram/bot.d.ts +4 -2
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +141 -74
- 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 +49 -109
- 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 +4 -11
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +116 -196
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts +1 -20
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +1 -21
- package/dist/bindings.js.map +1 -1
- package/dist/config.d.ts +9 -27
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +89 -63
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +13 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +102 -18
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +18 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +86 -35
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +1 -3
- package/dist/execution-resolver.js.map +1 -1
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +5 -11
- package/dist/instrument.js.map +1 -1
- package/dist/{login.d.ts → login/index.d.ts} +2 -2
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +2 -2
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +6 -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} +1 -1
- 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 +175 -119
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +17 -43
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +84 -50
- package/dist/provisioner.js.map +1 -1
- package/dist/sandbox/host.d.ts +0 -2
- package/dist/sandbox/host.d.ts.map +1 -1
- package/dist/sandbox/host.js +1 -5
- package/dist/sandbox/host.js.map +1 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +2 -0
- 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 +27 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +162 -9
- 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 +9 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +766 -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 +380 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +16 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +38 -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 +15 -35
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +3 -0
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +27 -8
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- 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 -2
- package/dist/vault-routing.d.ts.map +1 -1
- package/dist/vault-routing.js +1 -7
- package/dist/vault-routing.js.map +1 -1
- package/package.json +1 -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
package/dist/provisioner.js
CHANGED
|
@@ -2,32 +2,23 @@ import { execFile } from "child_process";
|
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import * as log from "./log.js";
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
|
-
// ── DockerContainerManager ─────────────────────────────────────────────────────
|
|
6
|
-
/**
|
|
7
|
-
* Manages the lifecycle of per-user Docker containers.
|
|
8
|
-
*
|
|
9
|
-
* Tracks each container's status in memory (running / stopped / missing).
|
|
10
|
-
* State is always verified against Docker on provision(), so in-memory state
|
|
11
|
-
* stays accurate without polling.
|
|
12
|
-
*/
|
|
13
5
|
export class DockerContainerManager {
|
|
14
6
|
static { this.MANAGED_LABEL = "mama.managed=true"; }
|
|
15
7
|
static { this.IMAGE_MODE_LABEL = "mama.sandbox=image"; }
|
|
16
8
|
static { this.VAULT_ID_LABEL_KEY = "mama.vault-id"; }
|
|
17
|
-
constructor(image, workspaceDir,
|
|
9
|
+
constructor(image, workspaceDir, options = {}) {
|
|
18
10
|
this.image = image;
|
|
19
11
|
this.workspaceDir = workspaceDir;
|
|
20
|
-
this.execFileImpl = execFileImpl;
|
|
21
12
|
this.state = new Map();
|
|
22
|
-
/**
|
|
23
|
-
* In-flight provision() calls per vaultId. A concurrent second call for the
|
|
24
|
-
* same user piggybacks on the first docker start/run instead of racing —
|
|
25
|
-
* without this, two parallel messages from one user could produce duplicate
|
|
26
|
-
* containers or conflict on docker run.
|
|
27
|
-
*/
|
|
28
13
|
this.inflight = new Map();
|
|
14
|
+
if (typeof options === "function") {
|
|
15
|
+
this.execFileImpl = options;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.limits = options.limits;
|
|
19
|
+
this.execFileImpl = options.execFileImpl ?? execFileAsync;
|
|
20
|
+
}
|
|
29
21
|
}
|
|
30
|
-
/** Sanitize an identifier segment for use in vault keys and container names. */
|
|
31
22
|
static sanitizeSegment(value) {
|
|
32
23
|
const sanitized = value
|
|
33
24
|
.toLowerCase()
|
|
@@ -35,26 +26,15 @@ export class DockerContainerManager {
|
|
|
35
26
|
.replace(/^-+|-+$/g, "");
|
|
36
27
|
return sanitized || "unknown";
|
|
37
28
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Deterministic vault key for a platform user.
|
|
40
|
-
* e.g. ("slack", "U04ABC") → "slack-u04abc"
|
|
41
|
-
*/
|
|
42
29
|
static vaultId(platform, platformUserId) {
|
|
43
30
|
return `${DockerContainerManager.sanitizeSegment(platform)}-${DockerContainerManager.sanitizeSegment(platformUserId)}`;
|
|
44
31
|
}
|
|
45
|
-
/** Deterministic container name for a vault-backed user sandbox. */
|
|
46
32
|
static containerName(vaultId) {
|
|
47
33
|
return `mama-sandbox-${vaultId}`;
|
|
48
34
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* - running → no-op
|
|
53
|
-
* - stopped → docker start
|
|
54
|
-
* - missing → docker run
|
|
55
|
-
*
|
|
56
|
-
* Returns the container name.
|
|
57
|
-
*/
|
|
35
|
+
static networkName(vaultId) {
|
|
36
|
+
return `mama-sandbox-net-${vaultId}`;
|
|
37
|
+
}
|
|
58
38
|
async provision(vaultId, options = {}) {
|
|
59
39
|
const existing = this.inflight.get(vaultId);
|
|
60
40
|
if (existing)
|
|
@@ -70,8 +50,8 @@ export class DockerContainerManager {
|
|
|
70
50
|
const mounts = options.mounts ?? [];
|
|
71
51
|
const status = await this.inspectStatus(containerName);
|
|
72
52
|
try {
|
|
73
|
-
if (status !== "missing" && (await this.
|
|
74
|
-
log.logInfo(`Container ${containerName}
|
|
53
|
+
if (status !== "missing" && (await this.hasRuntimeDrift(vaultId, containerName, mounts))) {
|
|
54
|
+
log.logInfo(`Container ${containerName} configuration changed; recreating container`);
|
|
75
55
|
await this.execFileImpl("docker", ["rm", "-f", containerName]);
|
|
76
56
|
await this.runContainer(vaultId, containerName, mounts);
|
|
77
57
|
log.logInfo(`Container ${containerName} recreated`);
|
|
@@ -89,19 +69,13 @@ export class DockerContainerManager {
|
|
|
89
69
|
}
|
|
90
70
|
}
|
|
91
71
|
catch (err) {
|
|
92
|
-
// Drop cached state so the next provision() re-inspects Docker cleanly
|
|
93
|
-
// and stopIdle doesn't keep trying to stop a container that never
|
|
94
|
-
// became running. We deliberately don't bump lastUsed here.
|
|
95
72
|
this.state.delete(vaultId);
|
|
96
73
|
throw err;
|
|
97
74
|
}
|
|
98
75
|
this.setState(vaultId, "running", containerName);
|
|
76
|
+
await this.applyResourceLimits(containerName);
|
|
99
77
|
return containerName;
|
|
100
78
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Stop a running container (docker stop). Container is preserved and can be
|
|
103
|
-
* restarted via provision(). Intended for idle lifecycle management.
|
|
104
|
-
*/
|
|
105
79
|
async stop(vaultId) {
|
|
106
80
|
const containerName = this.getContainerName(vaultId);
|
|
107
81
|
try {
|
|
@@ -113,22 +87,25 @@ export class DockerContainerManager {
|
|
|
113
87
|
log.logWarning(`Failed to stop container ${containerName}`, err instanceof Error ? err.message : String(err));
|
|
114
88
|
}
|
|
115
89
|
}
|
|
116
|
-
/** Stop and remove a container permanently (e.g. on vault revocation). */
|
|
117
90
|
async remove(vaultId) {
|
|
118
91
|
const containerName = this.getContainerName(vaultId);
|
|
92
|
+
const networkName = DockerContainerManager.networkName(vaultId);
|
|
119
93
|
try {
|
|
120
94
|
await this.execFileImpl("docker", ["rm", "-f", containerName]);
|
|
121
|
-
this.state.delete(vaultId);
|
|
122
95
|
log.logInfo(`Container ${containerName} removed`);
|
|
123
96
|
}
|
|
124
97
|
catch (err) {
|
|
125
98
|
log.logWarning(`Failed to remove container ${containerName}`, err instanceof Error ? err.message : String(err));
|
|
126
99
|
}
|
|
100
|
+
try {
|
|
101
|
+
await this.execFileImpl("docker", ["network", "rm", networkName]);
|
|
102
|
+
log.logInfo(`Network ${networkName} removed`);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
log.logWarning(`Failed to remove network ${networkName}`, err instanceof Error ? err.message : String(err));
|
|
106
|
+
}
|
|
107
|
+
this.state.delete(vaultId);
|
|
127
108
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Stop all containers that have been idle for longer than maxIdleMs.
|
|
130
|
-
* Idle time is measured from the last provision() call.
|
|
131
|
-
*/
|
|
132
109
|
async stopIdle(maxIdleMs) {
|
|
133
110
|
const now = Date.now();
|
|
134
111
|
const toStop = [];
|
|
@@ -139,10 +116,6 @@ export class DockerContainerManager {
|
|
|
139
116
|
}
|
|
140
117
|
await Promise.all(toStop.map((vaultId) => this.stop(vaultId)));
|
|
141
118
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Rebuild in-memory state from existing Docker containers managed by mama image mode.
|
|
144
|
-
* Supports both new labeled containers and legacy name-prefixed containers.
|
|
145
|
-
*/
|
|
146
119
|
async reconcile() {
|
|
147
120
|
const discovered = new Set();
|
|
148
121
|
const labeledNames = await this.listContainerNamesByLabel();
|
|
@@ -182,18 +155,22 @@ export class DockerContainerManager {
|
|
|
182
155
|
return `${mount.source}:${mount.target}`;
|
|
183
156
|
}
|
|
184
157
|
async runContainer(vaultId, containerName, mounts) {
|
|
158
|
+
const networkName = await this.ensureNetwork(vaultId);
|
|
185
159
|
log.logInfo(`Creating container ${containerName} from image ${this.image}`);
|
|
186
160
|
await this.execFileImpl("docker", [
|
|
187
161
|
"run",
|
|
188
162
|
"-d",
|
|
189
163
|
"--name",
|
|
190
164
|
containerName,
|
|
165
|
+
"--network",
|
|
166
|
+
networkName,
|
|
191
167
|
"--label",
|
|
192
168
|
DockerContainerManager.MANAGED_LABEL,
|
|
193
169
|
"--label",
|
|
194
170
|
DockerContainerManager.IMAGE_MODE_LABEL,
|
|
195
171
|
"--label",
|
|
196
172
|
`${DockerContainerManager.VAULT_ID_LABEL_KEY}=${vaultId}`,
|
|
173
|
+
...this.resourceLimitArgs(),
|
|
197
174
|
"-v",
|
|
198
175
|
`${this.workspaceDir}:/workspace`,
|
|
199
176
|
...this.mountArgs(mounts),
|
|
@@ -202,6 +179,31 @@ export class DockerContainerManager {
|
|
|
202
179
|
"infinity",
|
|
203
180
|
]);
|
|
204
181
|
}
|
|
182
|
+
resourceLimitArgs() {
|
|
183
|
+
const args = [];
|
|
184
|
+
if (this.limits?.cpus)
|
|
185
|
+
args.push("--cpus", this.limits.cpus);
|
|
186
|
+
if (this.limits?.memory)
|
|
187
|
+
args.push("--memory", this.limits.memory);
|
|
188
|
+
return args;
|
|
189
|
+
}
|
|
190
|
+
async applyResourceLimits(containerName) {
|
|
191
|
+
if (!this.limits?.cpus && !this.limits?.memory)
|
|
192
|
+
return;
|
|
193
|
+
const args = ["update", ...this.resourceLimitArgs(), containerName];
|
|
194
|
+
try {
|
|
195
|
+
await this.execFileImpl("docker", args);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
log.logWarning(`Failed to apply resource limits to container ${containerName}`, err instanceof Error ? err.message : String(err));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async hasRuntimeDrift(vaultId, containerName, mounts) {
|
|
202
|
+
if (await this.hasBindMountDrift(containerName, mounts)) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
return this.hasNetworkModeDrift(vaultId, containerName);
|
|
206
|
+
}
|
|
205
207
|
async hasBindMountDrift(containerName, mounts) {
|
|
206
208
|
const expected = this.expectedBinds(mounts);
|
|
207
209
|
const actual = await this.inspectBindMounts(containerName);
|
|
@@ -235,6 +237,38 @@ export class DockerContainerManager {
|
|
|
235
237
|
}
|
|
236
238
|
return [...parsed].sort();
|
|
237
239
|
}
|
|
240
|
+
async hasNetworkModeDrift(vaultId, containerName) {
|
|
241
|
+
const expected = DockerContainerManager.networkName(vaultId);
|
|
242
|
+
const { stdout } = await this.execFileImpl("docker", [
|
|
243
|
+
"inspect",
|
|
244
|
+
"-f",
|
|
245
|
+
"{{.HostConfig.NetworkMode}}",
|
|
246
|
+
containerName,
|
|
247
|
+
]);
|
|
248
|
+
return stdout.trim() !== expected;
|
|
249
|
+
}
|
|
250
|
+
async ensureNetwork(vaultId) {
|
|
251
|
+
const networkName = DockerContainerManager.networkName(vaultId);
|
|
252
|
+
try {
|
|
253
|
+
await this.execFileImpl("docker", ["network", "inspect", networkName]);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
await this.execFileImpl("docker", [
|
|
257
|
+
"network",
|
|
258
|
+
"create",
|
|
259
|
+
"--driver",
|
|
260
|
+
"bridge",
|
|
261
|
+
"--label",
|
|
262
|
+
DockerContainerManager.MANAGED_LABEL,
|
|
263
|
+
"--label",
|
|
264
|
+
DockerContainerManager.IMAGE_MODE_LABEL,
|
|
265
|
+
"--label",
|
|
266
|
+
`${DockerContainerManager.VAULT_ID_LABEL_KEY}=${vaultId}`,
|
|
267
|
+
networkName,
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
return networkName;
|
|
271
|
+
}
|
|
238
272
|
async inspectStatus(containerName) {
|
|
239
273
|
try {
|
|
240
274
|
const { stdout } = await this.execFileImpl("docker", [
|
package/dist/provisioner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provisioner.js","sourceRoot":"","sources":["../src/provisioner.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport * as log from \"./log.js\";\n\nconst execFileAsync = promisify(execFile);\ntype ExecFileAsync = typeof execFileAsync;\n\ntype ContainerStatus = \"running\" | \"stopped\" | \"missing\";\n\ninterface ContainerState {\n status: ContainerStatus;\n lastUsed: number;\n containerName: string;\n}\n\nexport interface ContainerMount {\n source: string;\n target: string;\n}\n\nexport interface ProvisionOptions {\n containerName?: string;\n mounts?: ContainerMount[];\n}\n\n// ── DockerContainerManager ─────────────────────────────────────────────────────\n\n/**\n * Manages the lifecycle of per-user Docker containers.\n *\n * Tracks each container's status in memory (running / stopped / missing).\n * State is always verified against Docker on provision(), so in-memory state\n * stays accurate without polling.\n */\nexport class DockerContainerManager {\n private state = new Map<string, ContainerState>();\n /**\n * In-flight provision() calls per vaultId. A concurrent second call for the\n * same user piggybacks on the first docker start/run instead of racing —\n * without this, two parallel messages from one user could produce duplicate\n * containers or conflict on docker run.\n */\n private inflight = new Map<string, Promise<string>>();\n private static readonly MANAGED_LABEL = \"mama.managed=true\";\n private static readonly IMAGE_MODE_LABEL = \"mama.sandbox=image\";\n private static readonly VAULT_ID_LABEL_KEY = \"mama.vault-id\";\n\n constructor(\n private readonly image: string,\n private readonly workspaceDir: string,\n private readonly execFileImpl: ExecFileAsync = execFileAsync,\n ) {}\n\n /** Sanitize an identifier segment for use in vault keys and container names. */\n static sanitizeSegment(value: string): string {\n const sanitized = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"unknown\";\n }\n\n /**\n * Deterministic vault key for a platform user.\n * e.g. (\"slack\", \"U04ABC\") → \"slack-u04abc\"\n */\n static vaultId(platform: string, platformUserId: string): string {\n return `${DockerContainerManager.sanitizeSegment(platform)}-${DockerContainerManager.sanitizeSegment(platformUserId)}`;\n }\n\n /** Deterministic container name for a vault-backed user sandbox. */\n static containerName(vaultId: string): string {\n return `mama-sandbox-${vaultId}`;\n }\n\n /**\n * Ensure a container exists and is running for the given vaultId.\n * Always inspects the actual Docker state, then acts accordingly:\n * - running → no-op\n * - stopped → docker start\n * - missing → docker run\n *\n * Returns the container name.\n */\n async provision(vaultId: string, options: ProvisionOptions = {}): Promise<string> {\n const existing = this.inflight.get(vaultId);\n if (existing) return existing;\n\n const pending = this.provisionInner(vaultId, options).finally(() => {\n this.inflight.delete(vaultId);\n });\n this.inflight.set(vaultId, pending);\n return pending;\n }\n\n private async provisionInner(vaultId: string, options: ProvisionOptions): Promise<string> {\n const containerName = options.containerName ?? DockerContainerManager.containerName(vaultId);\n const mounts = options.mounts ?? [];\n const status = await this.inspectStatus(containerName);\n\n try {\n if (status !== \"missing\" && (await this.hasBindMountDrift(containerName, mounts))) {\n log.logInfo(`Container ${containerName} mounts changed; recreating container`);\n await this.execFileImpl(\"docker\", [\"rm\", \"-f\", containerName]);\n await this.runContainer(vaultId, containerName, mounts);\n log.logInfo(`Container ${containerName} recreated`);\n } else if (status === \"running\") {\n log.logInfo(`Container ${containerName} already running`);\n } else if (status === \"stopped\") {\n await this.execFileImpl(\"docker\", [\"start\", containerName]);\n log.logInfo(`Container ${containerName} started`);\n } else {\n await this.runContainer(vaultId, containerName, mounts);\n log.logInfo(`Container ${containerName} created`);\n }\n } catch (err) {\n // Drop cached state so the next provision() re-inspects Docker cleanly\n // and stopIdle doesn't keep trying to stop a container that never\n // became running. We deliberately don't bump lastUsed here.\n this.state.delete(vaultId);\n throw err;\n }\n\n this.setState(vaultId, \"running\", containerName);\n return containerName;\n }\n\n /**\n * Stop a running container (docker stop). Container is preserved and can be\n * restarted via provision(). Intended for idle lifecycle management.\n */\n async stop(vaultId: string): Promise<void> {\n const containerName = this.getContainerName(vaultId);\n try {\n await this.execFileImpl(\"docker\", [\"stop\", containerName]);\n this.setState(vaultId, \"stopped\", containerName);\n log.logInfo(`Container ${containerName} stopped (idle)`);\n } catch (err) {\n log.logWarning(\n `Failed to stop container ${containerName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n /** Stop and remove a container permanently (e.g. on vault revocation). */\n async remove(vaultId: string): Promise<void> {\n const containerName = this.getContainerName(vaultId);\n try {\n await this.execFileImpl(\"docker\", [\"rm\", \"-f\", containerName]);\n this.state.delete(vaultId);\n log.logInfo(`Container ${containerName} removed`);\n } catch (err) {\n log.logWarning(\n `Failed to remove container ${containerName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n /**\n * Stop all containers that have been idle for longer than maxIdleMs.\n * Idle time is measured from the last provision() call.\n */\n async stopIdle(maxIdleMs: number): Promise<void> {\n const now = Date.now();\n const toStop: string[] = [];\n for (const [vaultId, containerState] of this.state) {\n if (containerState.status === \"running\" && now - containerState.lastUsed > maxIdleMs) {\n toStop.push(vaultId);\n }\n }\n await Promise.all(toStop.map((vaultId) => this.stop(vaultId)));\n }\n\n /**\n * Rebuild in-memory state from existing Docker containers managed by mama image mode.\n * Supports both new labeled containers and legacy name-prefixed containers.\n */\n async reconcile(): Promise<void> {\n const discovered = new Set<string>();\n const labeledNames = await this.listContainerNamesByLabel();\n for (const name of labeledNames) discovered.add(name);\n const legacyNames = await this.listContainerNamesByPrefix();\n for (const name of legacyNames) discovered.add(name);\n\n this.state.clear();\n\n for (const containerName of discovered) {\n const details = await this.inspectContainerDetails(containerName);\n if (!details) continue;\n\n const vaultId = details.vaultId || this.vaultIdFromContainerName(containerName);\n if (!vaultId) {\n log.logWarning(`Skipping unmanaged-style container without vault id`, containerName);\n continue;\n }\n\n const status: ContainerStatus = details.running ? \"running\" : \"stopped\";\n const lastUsed = details.startedAtMs ?? Date.now();\n this.state.set(vaultId, { status, lastUsed, containerName });\n }\n\n const running = Array.from(this.state.values()).filter((s) => s.status === \"running\").length;\n const stopped = this.state.size - running;\n log.logInfo(\n `Reconciled ${this.state.size} managed containers (running=${running}, stopped=${stopped})`,\n );\n }\n\n private setState(vaultId: string, status: ContainerStatus, containerName: string): void {\n this.state.set(vaultId, { status, lastUsed: Date.now(), containerName });\n }\n\n private getContainerName(vaultId: string): string {\n return this.state.get(vaultId)?.containerName ?? DockerContainerManager.containerName(vaultId);\n }\n\n private mountArgs(mounts: ContainerMount[]): string[] {\n return mounts.flatMap((mount) => [\"-v\", this.toBindSpec(mount)]);\n }\n\n private toBindSpec(mount: ContainerMount): string {\n return `${mount.source}:${mount.target}`;\n }\n\n private async runContainer(\n vaultId: string,\n containerName: string,\n mounts: ContainerMount[],\n ): Promise<void> {\n log.logInfo(`Creating container ${containerName} from image ${this.image}`);\n await this.execFileImpl(\"docker\", [\n \"run\",\n \"-d\",\n \"--name\",\n containerName,\n \"--label\",\n DockerContainerManager.MANAGED_LABEL,\n \"--label\",\n DockerContainerManager.IMAGE_MODE_LABEL,\n \"--label\",\n `${DockerContainerManager.VAULT_ID_LABEL_KEY}=${vaultId}`,\n \"-v\",\n `${this.workspaceDir}:/workspace`,\n ...this.mountArgs(mounts),\n this.image,\n \"sleep\",\n \"infinity\",\n ]);\n }\n\n private async hasBindMountDrift(\n containerName: string,\n mounts: ContainerMount[],\n ): Promise<boolean> {\n const expected = this.expectedBinds(mounts);\n const actual = await this.inspectBindMounts(containerName);\n return !this.sameBinds(expected, actual);\n }\n\n private expectedBinds(mounts: ContainerMount[]): string[] {\n return [`${this.workspaceDir}:/workspace`, ...mounts.map((mount) => this.toBindSpec(mount))]\n .slice()\n .sort();\n }\n\n private sameBinds(expected: string[], actual: string[]): boolean {\n if (expected.length !== actual.length) {\n return false;\n }\n\n return expected.every((bind, index) => bind === actual[index]);\n }\n\n private async inspectBindMounts(containerName: string): Promise<string[]> {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{json .HostConfig.Binds}}\",\n containerName,\n ]);\n const payload = stdout.trim();\n const parsed = JSON.parse(payload.length > 0 ? payload : \"null\") as unknown;\n\n if (parsed === null) {\n return [];\n }\n\n if (!Array.isArray(parsed) || parsed.some((bind) => typeof bind !== \"string\")) {\n throw new Error(`Unexpected docker bind mount payload for container \"${containerName}\"`);\n }\n\n return [...parsed].sort();\n }\n\n private async inspectStatus(containerName: string): Promise<ContainerStatus> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n containerName,\n ]);\n return stdout.trim() === \"true\" ? \"running\" : \"stopped\";\n } catch {\n return \"missing\";\n }\n }\n\n private async listContainerNamesByLabel(): Promise<string[]> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"ps\",\n \"-a\",\n \"--filter\",\n `label=${DockerContainerManager.MANAGED_LABEL}`,\n \"--filter\",\n `label=${DockerContainerManager.IMAGE_MODE_LABEL}`,\n \"--format\",\n \"{{.Names}}\",\n ]);\n return this.parseNameLines(stdout);\n } catch (err) {\n log.logWarning(\n \"Failed to list labeled managed containers\",\n err instanceof Error ? err.message : String(err),\n );\n return [];\n }\n }\n\n private async listContainerNamesByPrefix(): Promise<string[]> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"ps\",\n \"-a\",\n \"--filter\",\n `name=${DockerContainerManager.containerName(\"\")}`,\n \"--format\",\n \"{{.Names}}\",\n ]);\n return this.parseNameLines(stdout);\n } catch (err) {\n log.logWarning(\n \"Failed to list legacy managed containers\",\n err instanceof Error ? err.message : String(err),\n );\n return [];\n }\n }\n\n private parseNameLines(stdout: string): string[] {\n return stdout\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n\n private async inspectContainerDetails(\n containerName: string,\n ): Promise<{ running: boolean; startedAtMs?: number; vaultId?: string } | undefined> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n `{{.State.Running}}\\t{{.State.StartedAt}}\\t{{index .Config.Labels \"${DockerContainerManager.VAULT_ID_LABEL_KEY}\"}}`,\n containerName,\n ]);\n const [runningRaw, startedAtRaw, vaultIdRaw] = stdout.trim().split(\"\\t\");\n const running = runningRaw === \"true\";\n const startedAtMs = this.parseDockerTimestamp(startedAtRaw);\n const vaultId = this.normalizeDockerValue(vaultIdRaw);\n return { running, startedAtMs, vaultId };\n } catch (err) {\n log.logWarning(\n `Failed to inspect container ${containerName} during reconcile`,\n err instanceof Error ? err.message : String(err),\n );\n return undefined;\n }\n }\n\n private normalizeDockerValue(value?: string): string | undefined {\n if (!value || value === \"<no value>\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private parseDockerTimestamp(value?: string): number | undefined {\n const normalized = this.normalizeDockerValue(value);\n if (!normalized || normalized.startsWith(\"0001-\")) return undefined;\n const parsed = Date.parse(normalized);\n return Number.isNaN(parsed) ? undefined : parsed;\n }\n\n private vaultIdFromContainerName(containerName: string): string | undefined {\n const prefix = DockerContainerManager.containerName(\"\");\n if (!containerName.startsWith(prefix)) return undefined;\n const vaultId = containerName.slice(prefix.length);\n return vaultId.length > 0 ? vaultId : undefined;\n }\n}\n\n/** @deprecated Use DockerContainerManager */\nexport const DockerProvisioner = DockerContainerManager;\n"]}
|
|
1
|
+
{"version":3,"file":"provisioner.js","sourceRoot":"","sources":["../src/provisioner.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"child_process\";\nimport { promisify } from \"util\";\nimport * as log from \"./log.js\";\n\nconst execFileAsync = promisify(execFile);\ntype ExecFileAsync = typeof execFileAsync;\n\ntype ContainerStatus = \"running\" | \"stopped\" | \"missing\";\n\ninterface ContainerState {\n status: ContainerStatus;\n lastUsed: number;\n containerName: string;\n}\n\nexport interface ContainerMount {\n source: string;\n target: string;\n}\n\nexport interface ResourceLimits {\n cpus?: string;\n memory?: string;\n}\n\nexport interface ProvisionOptions {\n containerName?: string;\n mounts?: ContainerMount[];\n}\n\nexport interface DockerContainerManagerOptions {\n limits?: ResourceLimits;\n execFileImpl?: ExecFileAsync;\n}\n\nexport class DockerContainerManager {\n private state = new Map<string, ContainerState>();\n private inflight = new Map<string, Promise<string>>();\n private static readonly MANAGED_LABEL = \"mama.managed=true\";\n private static readonly IMAGE_MODE_LABEL = \"mama.sandbox=image\";\n private static readonly VAULT_ID_LABEL_KEY = \"mama.vault-id\";\n\n private readonly limits?: ResourceLimits;\n private readonly execFileImpl: ExecFileAsync;\n\n constructor(\n private readonly image: string,\n private readonly workspaceDir: string,\n options: DockerContainerManagerOptions | ExecFileAsync = {},\n ) {\n if (typeof options === \"function\") {\n this.execFileImpl = options;\n } else {\n this.limits = options.limits;\n this.execFileImpl = options.execFileImpl ?? execFileAsync;\n }\n }\n\n static sanitizeSegment(value: string): string {\n const sanitized = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return sanitized || \"unknown\";\n }\n\n static vaultId(platform: string, platformUserId: string): string {\n return `${DockerContainerManager.sanitizeSegment(platform)}-${DockerContainerManager.sanitizeSegment(platformUserId)}`;\n }\n\n static containerName(vaultId: string): string {\n return `mama-sandbox-${vaultId}`;\n }\n\n static networkName(vaultId: string): string {\n return `mama-sandbox-net-${vaultId}`;\n }\n\n async provision(vaultId: string, options: ProvisionOptions = {}): Promise<string> {\n const existing = this.inflight.get(vaultId);\n if (existing) return existing;\n\n const pending = this.provisionInner(vaultId, options).finally(() => {\n this.inflight.delete(vaultId);\n });\n this.inflight.set(vaultId, pending);\n return pending;\n }\n\n private async provisionInner(vaultId: string, options: ProvisionOptions): Promise<string> {\n const containerName = options.containerName ?? DockerContainerManager.containerName(vaultId);\n const mounts = options.mounts ?? [];\n const status = await this.inspectStatus(containerName);\n\n try {\n if (status !== \"missing\" && (await this.hasRuntimeDrift(vaultId, containerName, mounts))) {\n log.logInfo(`Container ${containerName} configuration changed; recreating container`);\n await this.execFileImpl(\"docker\", [\"rm\", \"-f\", containerName]);\n await this.runContainer(vaultId, containerName, mounts);\n log.logInfo(`Container ${containerName} recreated`);\n } else if (status === \"running\") {\n log.logInfo(`Container ${containerName} already running`);\n } else if (status === \"stopped\") {\n await this.execFileImpl(\"docker\", [\"start\", containerName]);\n log.logInfo(`Container ${containerName} started`);\n } else {\n await this.runContainer(vaultId, containerName, mounts);\n log.logInfo(`Container ${containerName} created`);\n }\n } catch (err) {\n this.state.delete(vaultId);\n throw err;\n }\n\n this.setState(vaultId, \"running\", containerName);\n await this.applyResourceLimits(containerName);\n return containerName;\n }\n\n async stop(vaultId: string): Promise<void> {\n const containerName = this.getContainerName(vaultId);\n try {\n await this.execFileImpl(\"docker\", [\"stop\", containerName]);\n this.setState(vaultId, \"stopped\", containerName);\n log.logInfo(`Container ${containerName} stopped (idle)`);\n } catch (err) {\n log.logWarning(\n `Failed to stop container ${containerName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n async remove(vaultId: string): Promise<void> {\n const containerName = this.getContainerName(vaultId);\n const networkName = DockerContainerManager.networkName(vaultId);\n\n try {\n await this.execFileImpl(\"docker\", [\"rm\", \"-f\", containerName]);\n log.logInfo(`Container ${containerName} removed`);\n } catch (err) {\n log.logWarning(\n `Failed to remove container ${containerName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n\n try {\n await this.execFileImpl(\"docker\", [\"network\", \"rm\", networkName]);\n log.logInfo(`Network ${networkName} removed`);\n } catch (err) {\n log.logWarning(\n `Failed to remove network ${networkName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n\n this.state.delete(vaultId);\n }\n\n async stopIdle(maxIdleMs: number): Promise<void> {\n const now = Date.now();\n const toStop: string[] = [];\n for (const [vaultId, containerState] of this.state) {\n if (containerState.status === \"running\" && now - containerState.lastUsed > maxIdleMs) {\n toStop.push(vaultId);\n }\n }\n await Promise.all(toStop.map((vaultId) => this.stop(vaultId)));\n }\n\n async reconcile(): Promise<void> {\n const discovered = new Set<string>();\n const labeledNames = await this.listContainerNamesByLabel();\n for (const name of labeledNames) discovered.add(name);\n const legacyNames = await this.listContainerNamesByPrefix();\n for (const name of legacyNames) discovered.add(name);\n\n this.state.clear();\n\n for (const containerName of discovered) {\n const details = await this.inspectContainerDetails(containerName);\n if (!details) continue;\n\n const vaultId = details.vaultId || this.vaultIdFromContainerName(containerName);\n if (!vaultId) {\n log.logWarning(`Skipping unmanaged-style container without vault id`, containerName);\n continue;\n }\n\n const status: ContainerStatus = details.running ? \"running\" : \"stopped\";\n const lastUsed = details.startedAtMs ?? Date.now();\n this.state.set(vaultId, { status, lastUsed, containerName });\n }\n\n const running = Array.from(this.state.values()).filter((s) => s.status === \"running\").length;\n const stopped = this.state.size - running;\n log.logInfo(\n `Reconciled ${this.state.size} managed containers (running=${running}, stopped=${stopped})`,\n );\n }\n\n private setState(vaultId: string, status: ContainerStatus, containerName: string): void {\n this.state.set(vaultId, { status, lastUsed: Date.now(), containerName });\n }\n\n private getContainerName(vaultId: string): string {\n return this.state.get(vaultId)?.containerName ?? DockerContainerManager.containerName(vaultId);\n }\n\n private mountArgs(mounts: ContainerMount[]): string[] {\n return mounts.flatMap((mount) => [\"-v\", this.toBindSpec(mount)]);\n }\n\n private toBindSpec(mount: ContainerMount): string {\n return `${mount.source}:${mount.target}`;\n }\n\n private async runContainer(\n vaultId: string,\n containerName: string,\n mounts: ContainerMount[],\n ): Promise<void> {\n const networkName = await this.ensureNetwork(vaultId);\n log.logInfo(`Creating container ${containerName} from image ${this.image}`);\n await this.execFileImpl(\"docker\", [\n \"run\",\n \"-d\",\n \"--name\",\n containerName,\n \"--network\",\n networkName,\n \"--label\",\n DockerContainerManager.MANAGED_LABEL,\n \"--label\",\n DockerContainerManager.IMAGE_MODE_LABEL,\n \"--label\",\n `${DockerContainerManager.VAULT_ID_LABEL_KEY}=${vaultId}`,\n ...this.resourceLimitArgs(),\n \"-v\",\n `${this.workspaceDir}:/workspace`,\n ...this.mountArgs(mounts),\n this.image,\n \"sleep\",\n \"infinity\",\n ]);\n }\n\n private resourceLimitArgs(): string[] {\n const args: string[] = [];\n if (this.limits?.cpus) args.push(\"--cpus\", this.limits.cpus);\n if (this.limits?.memory) args.push(\"--memory\", this.limits.memory);\n return args;\n }\n\n private async applyResourceLimits(containerName: string): Promise<void> {\n if (!this.limits?.cpus && !this.limits?.memory) return;\n const args = [\"update\", ...this.resourceLimitArgs(), containerName];\n try {\n await this.execFileImpl(\"docker\", args);\n } catch (err) {\n log.logWarning(\n `Failed to apply resource limits to container ${containerName}`,\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n private async hasRuntimeDrift(\n vaultId: string,\n containerName: string,\n mounts: ContainerMount[],\n ): Promise<boolean> {\n if (await this.hasBindMountDrift(containerName, mounts)) {\n return true;\n }\n return this.hasNetworkModeDrift(vaultId, containerName);\n }\n\n private async hasBindMountDrift(\n containerName: string,\n mounts: ContainerMount[],\n ): Promise<boolean> {\n const expected = this.expectedBinds(mounts);\n const actual = await this.inspectBindMounts(containerName);\n return !this.sameBinds(expected, actual);\n }\n\n private expectedBinds(mounts: ContainerMount[]): string[] {\n return [`${this.workspaceDir}:/workspace`, ...mounts.map((mount) => this.toBindSpec(mount))]\n .slice()\n .sort();\n }\n\n private sameBinds(expected: string[], actual: string[]): boolean {\n if (expected.length !== actual.length) {\n return false;\n }\n\n return expected.every((bind, index) => bind === actual[index]);\n }\n\n private async inspectBindMounts(containerName: string): Promise<string[]> {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{json .HostConfig.Binds}}\",\n containerName,\n ]);\n const payload = stdout.trim();\n const parsed = JSON.parse(payload.length > 0 ? payload : \"null\") as unknown;\n\n if (parsed === null) {\n return [];\n }\n\n if (!Array.isArray(parsed) || parsed.some((bind) => typeof bind !== \"string\")) {\n throw new Error(`Unexpected docker bind mount payload for container \"${containerName}\"`);\n }\n\n return [...parsed].sort();\n }\n\n private async hasNetworkModeDrift(vaultId: string, containerName: string): Promise<boolean> {\n const expected = DockerContainerManager.networkName(vaultId);\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.HostConfig.NetworkMode}}\",\n containerName,\n ]);\n return stdout.trim() !== expected;\n }\n\n private async ensureNetwork(vaultId: string): Promise<string> {\n const networkName = DockerContainerManager.networkName(vaultId);\n try {\n await this.execFileImpl(\"docker\", [\"network\", \"inspect\", networkName]);\n } catch {\n await this.execFileImpl(\"docker\", [\n \"network\",\n \"create\",\n \"--driver\",\n \"bridge\",\n \"--label\",\n DockerContainerManager.MANAGED_LABEL,\n \"--label\",\n DockerContainerManager.IMAGE_MODE_LABEL,\n \"--label\",\n `${DockerContainerManager.VAULT_ID_LABEL_KEY}=${vaultId}`,\n networkName,\n ]);\n }\n return networkName;\n }\n\n private async inspectStatus(containerName: string): Promise<ContainerStatus> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n \"{{.State.Running}}\",\n containerName,\n ]);\n return stdout.trim() === \"true\" ? \"running\" : \"stopped\";\n } catch {\n return \"missing\";\n }\n }\n\n private async listContainerNamesByLabel(): Promise<string[]> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"ps\",\n \"-a\",\n \"--filter\",\n `label=${DockerContainerManager.MANAGED_LABEL}`,\n \"--filter\",\n `label=${DockerContainerManager.IMAGE_MODE_LABEL}`,\n \"--format\",\n \"{{.Names}}\",\n ]);\n return this.parseNameLines(stdout);\n } catch (err) {\n log.logWarning(\n \"Failed to list labeled managed containers\",\n err instanceof Error ? err.message : String(err),\n );\n return [];\n }\n }\n\n private async listContainerNamesByPrefix(): Promise<string[]> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"ps\",\n \"-a\",\n \"--filter\",\n `name=${DockerContainerManager.containerName(\"\")}`,\n \"--format\",\n \"{{.Names}}\",\n ]);\n return this.parseNameLines(stdout);\n } catch (err) {\n log.logWarning(\n \"Failed to list legacy managed containers\",\n err instanceof Error ? err.message : String(err),\n );\n return [];\n }\n }\n\n private parseNameLines(stdout: string): string[] {\n return stdout\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n }\n\n private async inspectContainerDetails(\n containerName: string,\n ): Promise<{ running: boolean; startedAtMs?: number; vaultId?: string } | undefined> {\n try {\n const { stdout } = await this.execFileImpl(\"docker\", [\n \"inspect\",\n \"-f\",\n `{{.State.Running}}\\t{{.State.StartedAt}}\\t{{index .Config.Labels \"${DockerContainerManager.VAULT_ID_LABEL_KEY}\"}}`,\n containerName,\n ]);\n const [runningRaw, startedAtRaw, vaultIdRaw] = stdout.trim().split(\"\\t\");\n const running = runningRaw === \"true\";\n const startedAtMs = this.parseDockerTimestamp(startedAtRaw);\n const vaultId = this.normalizeDockerValue(vaultIdRaw);\n return { running, startedAtMs, vaultId };\n } catch (err) {\n log.logWarning(\n `Failed to inspect container ${containerName} during reconcile`,\n err instanceof Error ? err.message : String(err),\n );\n return undefined;\n }\n }\n\n private normalizeDockerValue(value?: string): string | undefined {\n if (!value || value === \"<no value>\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private parseDockerTimestamp(value?: string): number | undefined {\n const normalized = this.normalizeDockerValue(value);\n if (!normalized || normalized.startsWith(\"0001-\")) return undefined;\n const parsed = Date.parse(normalized);\n return Number.isNaN(parsed) ? undefined : parsed;\n }\n\n private vaultIdFromContainerName(containerName: string): string | undefined {\n const prefix = DockerContainerManager.containerName(\"\");\n if (!containerName.startsWith(prefix)) return undefined;\n const vaultId = containerName.slice(prefix.length);\n return vaultId.length > 0 ? vaultId : undefined;\n }\n}\n\n/** @deprecated Use DockerContainerManager */\nexport const DockerProvisioner = DockerContainerManager;\n"]}
|
package/dist/sandbox/host.d.ts
CHANGED
|
@@ -2,8 +2,6 @@ import type { ExecOptions, ExecResult, Executor, HostSandboxConfig, SandboxAdapt
|
|
|
2
2
|
export declare function parseHostSandboxArg(value: string): HostSandboxConfig | undefined;
|
|
3
3
|
export declare function validateHostSandbox(_config: HostSandboxConfig): Promise<void>;
|
|
4
4
|
export declare class HostExecutor implements Executor {
|
|
5
|
-
private env?;
|
|
6
|
-
constructor(env?: Record<string, string> | undefined);
|
|
7
5
|
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
8
6
|
getWorkspacePath(hostPath: string): string;
|
|
9
7
|
getSandboxConfig(): HostSandboxConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAKhF;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED,qBAAa,YAAa,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAKhF;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED,qBAAa,YAAa,YAAW,QAAQ;IACrC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAuEtE;IAED,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzC;IAED,gBAAgB,IAAI,iBAAiB,CAEpC;CACF;AAED,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,iBAAiB,CAKhE,CAAC","sourcesContent":["import { spawn } from \"child_process\";\nimport type {\n ExecOptions,\n ExecResult,\n Executor,\n HostSandboxConfig,\n SandboxAdapter,\n} from \"./types.js\";\nimport { killProcessTree } from \"./utils.js\";\n\nexport function parseHostSandboxArg(value: string): HostSandboxConfig | undefined {\n if (value === \"host\") {\n return { type: \"host\" };\n }\n return undefined;\n}\n\nexport async function validateHostSandbox(_config: HostSandboxConfig): Promise<void> {\n return;\n}\n\nexport class HostExecutor implements Executor {\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n const shell = process.platform === \"win32\" ? \"cmd\" : \"sh\";\n const shellArgs = process.platform === \"win32\" ? [\"/c\"] : [\"-c\"];\n\n const child = spawn(shell, [...shellArgs, command], {\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const timeoutHandle =\n options?.timeout && options.timeout > 0\n ? setTimeout(() => {\n timedOut = true;\n killProcessTree(child.pid!);\n }, options.timeout * 1000)\n : undefined;\n\n const onAbort = () => {\n if (child.pid) killProcessTree(child.pid);\n };\n\n if (options?.signal) {\n if (options.signal.aborted) {\n onAbort();\n } else {\n options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n child.stdout?.on(\"data\", (data) => {\n stdout += data.toString();\n if (stdout.length > 10 * 1024 * 1024) {\n stdout = stdout.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.stderr?.on(\"data\", (data) => {\n stderr += data.toString();\n if (stderr.length > 10 * 1024 * 1024) {\n stderr = stderr.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.on(\"close\", (code) => {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (options?.signal) {\n options.signal.removeEventListener(\"abort\", onAbort);\n }\n\n if (options?.signal?.aborted) {\n reject(new Error(`${stdout}\\n${stderr}\\nCommand aborted`.trim()));\n return;\n }\n\n if (timedOut) {\n reject(\n new Error(\n `${stdout}\\n${stderr}\\nCommand timed out after ${options?.timeout} seconds`.trim(),\n ),\n );\n return;\n }\n\n resolve({ stdout, stderr, code: code ?? 0 });\n });\n });\n }\n\n getWorkspacePath(hostPath: string): string {\n return hostPath;\n }\n\n getSandboxConfig(): HostSandboxConfig {\n return { type: \"host\" };\n }\n}\n\nexport const hostSandboxAdapter: SandboxAdapter<HostSandboxConfig> = {\n type: \"host\",\n parse: parseHostSandboxArg,\n validate: validateHostSandbox,\n createExecutor: () => new HostExecutor(),\n};\n"]}
|
package/dist/sandbox/host.js
CHANGED
|
@@ -10,9 +10,6 @@ export async function validateHostSandbox(_config) {
|
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
export class HostExecutor {
|
|
13
|
-
constructor(env) {
|
|
14
|
-
this.env = env;
|
|
15
|
-
}
|
|
16
13
|
async exec(command, options) {
|
|
17
14
|
return new Promise((resolve, reject) => {
|
|
18
15
|
const shell = process.platform === "win32" ? "cmd" : "sh";
|
|
@@ -20,7 +17,6 @@ export class HostExecutor {
|
|
|
20
17
|
const child = spawn(shell, [...shellArgs, command], {
|
|
21
18
|
detached: true,
|
|
22
19
|
stdio: ["ignore", "pipe", "pipe"],
|
|
23
|
-
...(this.env && { env: { ...process.env, ...this.env } }),
|
|
24
20
|
});
|
|
25
21
|
let stdout = "";
|
|
26
22
|
let stderr = "";
|
|
@@ -84,6 +80,6 @@ export const hostSandboxAdapter = {
|
|
|
84
80
|
type: "host",
|
|
85
81
|
parse: parseHostSandboxArg,
|
|
86
82
|
validate: validateHostSandbox,
|
|
87
|
-
createExecutor: (
|
|
83
|
+
createExecutor: () => new HostExecutor(),
|
|
88
84
|
};
|
|
89
85
|
//# sourceMappingURL=host.js.map
|
package/dist/sandbox/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAQtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAA0B;IAClE,OAAO;AACT,CAAC;AAED,MAAM,OAAO,YAAY;IACvB,
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/sandbox/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAQtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAA0B;IAClE,OAAO;AACT,CAAC;AAED,MAAM,OAAO,YAAY;IACvB,KAAK,CAAC,IAAI,CAAC,OAAe,EAAE,OAAqB;QAC/C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAEjE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE;gBAClD,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC;YAEH,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,QAAQ,GAAG,KAAK,CAAC;YAErB,MAAM,aAAa,GACjB,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC;gBACrC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;oBACd,QAAQ,GAAG,IAAI,CAAC;oBAChB,eAAe,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC;gBAC9B,CAAC,EAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;gBAC5B,CAAC,CAAC,SAAS,CAAC;YAEhB,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,IAAI,KAAK,CAAC,GAAG;oBAAE,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC5C,CAAC,CAAC;YAEF,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;gBACpB,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC3B,OAAO,EAAE,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC;YAED,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1B,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;oBACrC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1B,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;oBACrC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,IAAI,aAAa;oBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC/C,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;oBACpB,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACvD,CAAC;gBAED,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;oBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,KAAK,MAAM,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;oBAClE,OAAO;gBACT,CAAC;gBAED,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,CACJ,IAAI,KAAK,CACP,GAAG,MAAM,KAAK,MAAM,6BAA6B,OAAO,EAAE,OAAO,UAAU,CAAC,IAAI,EAAE,CACnF,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBAED,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,QAAgB;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,gBAAgB;QACd,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAsC;IACnE,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,mBAAmB;IAC1B,QAAQ,EAAE,mBAAmB;IAC7B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,YAAY,EAAE;CACzC,CAAC","sourcesContent":["import { spawn } from \"child_process\";\nimport type {\n ExecOptions,\n ExecResult,\n Executor,\n HostSandboxConfig,\n SandboxAdapter,\n} from \"./types.js\";\nimport { killProcessTree } from \"./utils.js\";\n\nexport function parseHostSandboxArg(value: string): HostSandboxConfig | undefined {\n if (value === \"host\") {\n return { type: \"host\" };\n }\n return undefined;\n}\n\nexport async function validateHostSandbox(_config: HostSandboxConfig): Promise<void> {\n return;\n}\n\nexport class HostExecutor implements Executor {\n async exec(command: string, options?: ExecOptions): Promise<ExecResult> {\n return new Promise((resolve, reject) => {\n const shell = process.platform === \"win32\" ? \"cmd\" : \"sh\";\n const shellArgs = process.platform === \"win32\" ? [\"/c\"] : [\"-c\"];\n\n const child = spawn(shell, [...shellArgs, command], {\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n\n const timeoutHandle =\n options?.timeout && options.timeout > 0\n ? setTimeout(() => {\n timedOut = true;\n killProcessTree(child.pid!);\n }, options.timeout * 1000)\n : undefined;\n\n const onAbort = () => {\n if (child.pid) killProcessTree(child.pid);\n };\n\n if (options?.signal) {\n if (options.signal.aborted) {\n onAbort();\n } else {\n options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n child.stdout?.on(\"data\", (data) => {\n stdout += data.toString();\n if (stdout.length > 10 * 1024 * 1024) {\n stdout = stdout.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.stderr?.on(\"data\", (data) => {\n stderr += data.toString();\n if (stderr.length > 10 * 1024 * 1024) {\n stderr = stderr.slice(0, 10 * 1024 * 1024);\n }\n });\n\n child.on(\"close\", (code) => {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n if (options?.signal) {\n options.signal.removeEventListener(\"abort\", onAbort);\n }\n\n if (options?.signal?.aborted) {\n reject(new Error(`${stdout}\\n${stderr}\\nCommand aborted`.trim()));\n return;\n }\n\n if (timedOut) {\n reject(\n new Error(\n `${stdout}\\n${stderr}\\nCommand timed out after ${options?.timeout} seconds`.trim(),\n ),\n );\n return;\n }\n\n resolve({ stdout, stderr, code: code ?? 0 });\n });\n });\n }\n\n getWorkspacePath(hostPath: string): string {\n return hostPath;\n }\n\n getSandboxConfig(): HostSandboxConfig {\n return { type: \"host\" };\n }\n}\n\nexport const hostSandboxAdapter: SandboxAdapter<HostSandboxConfig> = {\n type: \"host\",\n parse: parseHostSandboxArg,\n validate: validateHostSandbox,\n createExecutor: () => new HostExecutor(),\n};\n"]}
|
package/dist/sentry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA6CpF,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,MAAM;;;;;;;;sBAS9B,UAAU,QAAQ,SAAS,GAAG,UAAU,GAAG,IAAI;iCAGpC,UAAU,GAAG,UAAU,GAAG,IAAI;EAI9D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA6CpF,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,MAAM;;;;;;;;sBAS9B,UAAU,QAAQ,SAAS,GAAG,UAAU,GAAG,IAAI;iCAGpC,UAAU,GAAG,UAAU,GAAG,IAAI;EAI9D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAyBhF;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAChE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAO3C;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC3D,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,CAAC,GAAG,IAAI,CA2CpF;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAU5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAwB9E","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"conversation_id\", context.conversationId);\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_event\", String(Boolean(context.isEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n channelId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isEvent: Boolean(context.isEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
package/dist/sentry.js
CHANGED
|
@@ -55,6 +55,7 @@ export function createSentryInitOptions(dsn) {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
export function applyRunScope(scope, context) {
|
|
58
|
+
scope.setTag("conversation_id", context.conversationId);
|
|
58
59
|
scope.setTag("channel_id", context.conversationId);
|
|
59
60
|
scope.setTag("session_key", context.sessionKey);
|
|
60
61
|
scope.setTag("platform", context.platform);
|
|
@@ -71,6 +72,7 @@ export function applyRunScope(scope, context) {
|
|
|
71
72
|
});
|
|
72
73
|
scope.setContext("agent_run", {
|
|
73
74
|
conversationId: context.conversationId,
|
|
75
|
+
channelId: context.conversationId,
|
|
74
76
|
sessionKey: context.sessionKey,
|
|
75
77
|
messageId: context.messageId,
|
|
76
78
|
threadTs: context.threadTs,
|
package/dist/sentry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_event\", String(Boolean(context.isEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isEvent: Boolean(context.isEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../src/sentry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AAEvC,MAAM,QAAQ,GAAG,YAAY,CAAC;AAC9B,MAAM,aAAa,GAAG,iBAAiB,CAAC;AACxC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,MAAM;IACN,YAAY;IACZ,aAAa;IACb,eAAe;IACf,MAAM;IACN,SAAS;IACT,UAAU;IACV,QAAQ;IACR,SAAS;IACT,UAAU;IACV,SAAS;IACT,OAAO;IACP,kBAAkB;IAClB,QAAQ;IACR,WAAW;IACX,UAAU;IACV,gBAAgB;IAChB,MAAM;IACN,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,cAAc;IACd,MAAM;IACN,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,qBAAqB,GACzB,+GAA+G,CAAC;AAClH,MAAM,cAAc,GAAG;IACrB,2BAA2B;IAC3B,gCAAgC;IAChC,4BAA4B;IAC5B,gCAAgC;CACjC,CAAC;AAeF,MAAM,UAAU,uBAAuB,CAAC,GAAY;IAClD,OAAO;QACL,GAAG;QACH,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,YAAY;QAC3D,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,OAAO;QAC/D,cAAc,EAAE,KAAK;QACrB,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;QACpE,qBAAqB,EAAE,KAAK;QAC5B,UAAU,EAAE,IAAI;QAChB,UAAU,CAAC,KAAiB,EAAE,IAAe;YAC3C,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,gBAAgB,CAAC,UAAsB;YACrC,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,OAA8B;IACxE,KAAK,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACnD,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAChD,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3C,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAExD,KAAK,CAAC,OAAO,CAAC;QACZ,EAAE,EAAE,OAAO,CAAC,MAAM;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IACH,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE;QAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,SAAS,EAAE,OAAO,CAAC,cAAc;QACjC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;KAClC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAAiE;IAEjE,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAgD,EAAE;QACxF,MAAM,CAAC,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;QACxB,OAAO,KAAK,KAAK,SAAS,CAAC;IAC7B,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,OAAe,EACf,IAA4D;IAE5D,MAAM,CAAC,aAAa,CAAC;QACnB,QAAQ,EAAE,iBAAiB;QAC3B,OAAO;QACP,KAAK,EAAE,MAAM;QACb,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAChD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,aAAa,CAAkB,KAAQ,EAAE,KAAiB;IACxE,MAAM,SAAS,GAAM;QACnB,GAAG,KAAK;QACR,WAAW,EAAE,KAAK,CAAC,WAAW;YAC5B,EAAE,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;aACpD,MAAM,CAAC,CAAC,UAAU,EAA4B,EAAE,CAAC,UAAU,KAAK,IAAI,CAAC;QACxE,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,CAAe;QAC/C,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAkB;QACxD,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC;QACvC,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,SAAS;KACvB,CAAC;IAEF,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACtB,SAAS,CAAC,OAAO,GAAG,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QACvB,SAAS,CAAC,QAAQ,GAAG;YACnB,GAAG,SAAS,CAAC,QAAQ;YACrB,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;SAC7F,CAAC;IACJ,CAAC;IAED,IAAI,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAChC,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACtE,GAAG,KAAK;YACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK;YAC9D,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC1B,CAAC,CAAC;oBACE,GAAG,KAAK,CAAC,UAAU;oBACnB,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC/C,GAAG,KAAK;wBACR,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ;wBAC1E,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ;wBAC1E,IAAI,EAAE,SAAS;qBAChB,CAAC,CAAC;iBACJ;gBACH,CAAC,CAAC,KAAK,CAAC,UAAU;SACrB,CAAC,CAAC,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,GAAG,UAAU;QACb,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO;QACrF,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,IAAI,CAAuB;KAC3D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,GAAY,EAAE,KAAK,GAAG,CAAC;IACnE,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,KAAK,GAAG,SAAS;QAAE,OAAO,aAAa,CAAC;IAE5C,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,GAAG,CAClE,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CACvF,CAAC;QACF,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,eAAe,CAAC,OAAyB;IAChD,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC;IAE7B,OAAO;QACL,GAAG,OAAO;QACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QACrE,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE,SAAS;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,GAAY;IAClC,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,GAAY;IAClD,MAAM,KAAK,GAAG,GAAG,IAAI,OAAO,CAAC;IAC7B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,aAAa,KAAK,YAAY,KAAK,CAAC,MAAM,GAAG,CAAC;IACvD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,aAAa,KAAK,WAAW,KAAK,CAAC,MAAM,GAAG,CAAC;IACtD,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,aAAa,KAAK,UAAU,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,MAAM,GAAG,CAAC;IAC7F,CAAC;IACD,OAAO,aAAa,KAAK,GAAG,CAAC;AAC/B,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,qBAAqB,EAAE,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC;IACrF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACzC,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,SAAS,CAAC,MAAM,GAAG,iBAAiB,SAAS,CAAC;IAC/G,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { Breadcrumb, ErrorEvent, Event, EventHint, Scope } from \"@sentry/node\";\nimport * as Sentry from \"@sentry/node\";\n\nconst REDACTED = \"[REDACTED]\";\nconst REDACTED_PATH = \"[REDACTED_PATH]\";\nconst MAX_STRING_LENGTH = 256;\nconst MAX_DEPTH = 4;\n\nconst SENSITIVE_KEYS = new Set([\n \"args\",\n \"attachment\",\n \"attachments\",\n \"authorization\",\n \"body\",\n \"content\",\n \"contents\",\n \"cookie\",\n \"cookies\",\n \"filePath\",\n \"headers\",\n \"image\",\n \"imageAttachments\",\n \"images\",\n \"localPath\",\n \"messages\",\n \"newUserMessage\",\n \"path\",\n \"paths\",\n \"prompt\",\n \"response\",\n \"result\",\n \"systemPrompt\",\n \"text\",\n \"thinking\",\n]);\n\nconst ABSOLUTE_PATH_PATTERN =\n /(?:\\/Users\\/[^\\s\"'`]+|\\/workspace\\/[^\\s\"'`]+|\\/tmp\\/[^\\s\"'`]+|\\/var\\/folders\\/[^\\s\"'`]+|[A-Za-z]:\\\\[^\\s\"'`]+)/;\nconst TOKEN_PATTERNS = [\n /\\bsk-[A-Za-z0-9_-]{12,}\\b/,\n /\\bxox[a-z]-[A-Za-z0-9-]{10,}\\b/,\n /\\bAIza[0-9A-Za-z_-]{20,}\\b/,\n /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/,\n];\n\nexport interface SentryRunScopeContext {\n conversationId: string;\n sessionKey: string;\n messageId: string;\n platform: string;\n userId: string;\n userName?: string;\n threadTs?: string;\n provider?: string;\n model?: string;\n isEvent?: boolean;\n}\n\nexport function createSentryInitOptions(dsn?: string) {\n return {\n dsn,\n environment: process.env.SENTRY_ENVIRONMENT ?? \"production\",\n enabled: Boolean(dsn) && process.env.SENTRY_ENABLED !== \"false\",\n sendDefaultPii: false,\n tracesSampleRate: process.env.NODE_ENV === \"development\" ? 1.0 : 1.0,\n includeLocalVariables: false,\n enableLogs: true,\n beforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null {\n return sanitizeEvent(event, hint);\n },\n beforeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n return sanitizeBreadcrumb(breadcrumb);\n },\n };\n}\n\nexport function applyRunScope(scope: Scope, context: SentryRunScopeContext): void {\n scope.setTag(\"conversation_id\", context.conversationId);\n scope.setTag(\"channel_id\", context.conversationId);\n scope.setTag(\"session_key\", context.sessionKey);\n scope.setTag(\"platform\", context.platform);\n scope.setTag(\"is_event\", String(Boolean(context.isEvent)));\n if (context.threadTs) scope.setTag(\"thread_ts\", context.threadTs);\n if (context.provider) scope.setTag(\"provider\", context.provider);\n if (context.model) scope.setTag(\"model\", context.model);\n\n scope.setUser({\n id: context.userId,\n username: context.userName,\n });\n scope.setContext(\"agent_run\", {\n conversationId: context.conversationId,\n channelId: context.conversationId,\n sessionKey: context.sessionKey,\n messageId: context.messageId,\n threadTs: context.threadTs,\n platform: context.platform,\n provider: context.provider,\n model: context.model,\n isEvent: Boolean(context.isEvent),\n });\n}\n\nexport function metricAttributes(\n attributes: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string | number | boolean] => {\n const [, value] = entry;\n return value !== undefined;\n }),\n );\n}\n\nexport function addLifecycleBreadcrumb(\n message: string,\n data?: Record<string, string | number | boolean | undefined>,\n): void {\n Sentry.addBreadcrumb({\n category: \"agent.lifecycle\",\n message,\n level: \"info\",\n data: data ? metricAttributes(data) : undefined,\n });\n}\n\nexport function sanitizeEvent<T extends Event>(event: T, _hint?: EventHint): T | null {\n const sanitized: T = {\n ...event,\n breadcrumbs: event.breadcrumbs\n ?.map((breadcrumb) => sanitizeBreadcrumb(breadcrumb))\n .filter((breadcrumb): breadcrumb is Breadcrumb => breadcrumb !== null),\n extra: sanitizeValue(event.extra) as T[\"extra\"],\n contexts: sanitizeValue(event.contexts) as T[\"contexts\"],\n request: sanitizeRequest(event.request),\n user: undefined,\n server_name: undefined,\n };\n\n if (sanitized.message) {\n sanitized.message = sanitizeString(sanitized.message);\n }\n\n if (sanitized.logentry) {\n sanitized.logentry = {\n ...sanitized.logentry,\n message: sanitized.logentry.message ? sanitizeString(sanitized.logentry.message) : undefined,\n };\n }\n\n if (sanitized.exception?.values) {\n sanitized.exception.values = sanitized.exception.values.map((value) => ({\n ...value,\n value: value.value ? sanitizeString(value.value) : value.value,\n stacktrace: value.stacktrace\n ? {\n ...value.stacktrace,\n frames: value.stacktrace.frames?.map((frame) => ({\n ...frame,\n filename: frame.filename ? sanitizeString(frame.filename) : frame.filename,\n abs_path: frame.abs_path ? sanitizeString(frame.abs_path) : frame.abs_path,\n vars: undefined,\n })),\n }\n : value.stacktrace,\n }));\n }\n\n return sanitized;\n}\n\nexport function sanitizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n if (breadcrumb.category === \"console\") {\n return null;\n }\n\n return {\n ...breadcrumb,\n message: breadcrumb.message ? sanitizeString(breadcrumb.message) : breadcrumb.message,\n data: sanitizeValue(breadcrumb.data) as Breadcrumb[\"data\"],\n };\n}\n\nexport function sanitizeValue(value: unknown, key?: string, depth = 0): unknown {\n if (value == null) return value;\n if (depth > MAX_DEPTH) return \"[Truncated]\";\n\n if (isSensitiveKey(key)) {\n return summarizeValue(value, key);\n }\n\n if (typeof value === \"string\") {\n return sanitizeString(value);\n }\n\n if (Array.isArray(value)) {\n return value.slice(0, 20).map((entry) => sanitizeValue(entry, key, depth + 1));\n }\n\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([entryKey, entryValue]) => [entryKey, sanitizeValue(entryValue, entryKey, depth + 1)],\n );\n return Object.fromEntries(entries);\n }\n\n return value;\n}\n\nfunction sanitizeRequest(request: Event[\"request\"]): Event[\"request\"] {\n if (!request) return request;\n\n return {\n ...request,\n data: request.data ? summarizeValue(request.data, \"body\") : undefined,\n headers: undefined,\n cookies: undefined,\n };\n}\n\nfunction isSensitiveKey(key?: string): boolean {\n if (!key) return false;\n return SENSITIVE_KEYS.has(key);\n}\n\nfunction summarizeValue(value: unknown, key?: string): string {\n const label = key ?? \"field\";\n if (typeof value === \"string\") {\n return `[Redacted ${label}; length=${value.length}]`;\n }\n if (Array.isArray(value)) {\n return `[Redacted ${label}; items=${value.length}]`;\n }\n if (value && typeof value === \"object\") {\n return `[Redacted ${label}; keys=${Object.keys(value as Record<string, unknown>).length}]`;\n }\n return `[Redacted ${label}]`;\n}\n\nfunction sanitizeString(value: string): string {\n let sanitized = value.replace(new RegExp(ABSOLUTE_PATH_PATTERN, \"g\"), REDACTED_PATH);\n for (const pattern of TOKEN_PATTERNS) {\n sanitized = sanitized.replace(new RegExp(pattern, \"g\"), REDACTED);\n }\n if (sanitized.length > MAX_STRING_LENGTH) {\n return `${sanitized.slice(0, MAX_STRING_LENGTH)}… [truncated ${sanitized.length - MAX_STRING_LENGTH} chars]`;\n }\n return sanitized;\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ConversationKind } from "./adapter.js";
|
|
2
|
+
export type ChatPlatform = "slack" | "telegram" | "discord" | string;
|
|
3
|
+
export interface ResolveSessionKeyOptions {
|
|
4
|
+
conversationId: string;
|
|
5
|
+
conversationKind: ConversationKind;
|
|
6
|
+
messageId: string;
|
|
7
|
+
threadTs?: string;
|
|
8
|
+
persistentTopLevel?: boolean;
|
|
9
|
+
scopeDirectThreads?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveChatSessionKey(options: ResolveSessionKeyOptions): string;
|
|
12
|
+
export declare function inferConversationKind(platform: ChatPlatform, conversationId: string): ConversationKind;
|
|
13
|
+
//# sourceMappingURL=session-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-policy.d.ts","sourceRoot":"","sources":["../src/session-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAErE,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAgB/E;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,GACrB,gBAAgB,CAclB","sourcesContent":["import type { ConversationKind } from \"./adapter.js\";\n\nexport type ChatPlatform = \"slack\" | \"telegram\" | \"discord\" | string;\n\nexport interface ResolveSessionKeyOptions {\n conversationId: string;\n conversationKind: ConversationKind;\n messageId: string;\n threadTs?: string;\n persistentTopLevel?: boolean;\n scopeDirectThreads?: boolean;\n}\n\nexport function resolveChatSessionKey(options: ResolveSessionKeyOptions): string {\n const {\n conversationId,\n conversationKind,\n messageId,\n persistentTopLevel,\n scopeDirectThreads,\n threadTs,\n } = options;\n if (conversationKind === \"direct\" && (!threadTs || !scopeDirectThreads)) {\n return conversationId;\n }\n if (!threadTs && persistentTopLevel) {\n return conversationId;\n }\n return `${conversationId}:${threadTs || messageId}`;\n}\n\nexport function inferConversationKind(\n platform: ChatPlatform,\n conversationId: string,\n): ConversationKind {\n if (platform === \"slack\") {\n return conversationId.startsWith(\"D\") ? \"direct\" : \"shared\";\n }\n\n if (platform === \"telegram\") {\n return conversationId.startsWith(\"-\") ? \"shared\" : \"direct\";\n }\n\n if (platform === \"discord\") {\n return conversationId.startsWith(\"DM\") ? \"direct\" : \"shared\";\n }\n\n return \"shared\";\n}\n"]}
|