bulkhead-runtime 0.1.0 → 2026.4.5-beta.2
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 +344 -262
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts +28 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +9 -6
- package/dist/config/index.js.map +1 -1
- package/dist/credentials/store.d.ts.map +1 -1
- package/dist/credentials/store.js +39 -15
- package/dist/credentials/store.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/warning-filter.js +1 -1
- package/dist/infra/warning-filter.js.map +1 -1
- package/dist/logging/subsystem.d.ts +15 -1
- package/dist/logging/subsystem.d.ts.map +1 -1
- package/dist/logging/subsystem.js +310 -45
- package/dist/logging/subsystem.js.map +1 -1
- package/dist/memory/embedding-batch.d.ts +38 -0
- package/dist/memory/embedding-batch.d.ts.map +1 -0
- package/dist/memory/embedding-batch.js +253 -0
- package/dist/memory/embedding-batch.js.map +1 -0
- package/dist/memory/embedding-cache.d.ts +16 -0
- package/dist/memory/embedding-cache.d.ts.map +1 -0
- package/dist/memory/embedding-cache.js +113 -0
- package/dist/memory/embedding-cache.js.map +1 -0
- package/dist/memory/embeddings-debug.js +1 -1
- package/dist/memory/embeddings.d.ts +1 -0
- package/dist/memory/embeddings.d.ts.map +1 -1
- package/dist/memory/embeddings.js +115 -92
- package/dist/memory/embeddings.js.map +1 -1
- package/dist/memory/file-indexer.d.ts +26 -0
- package/dist/memory/file-indexer.d.ts.map +1 -0
- package/dist/memory/file-indexer.js +258 -0
- package/dist/memory/file-indexer.js.map +1 -0
- package/dist/memory/hybrid.d.ts.map +1 -1
- package/dist/memory/hybrid.js +6 -2
- package/dist/memory/hybrid.js.map +1 -1
- package/dist/memory/index.d.ts +5 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +5 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/session-indexer.d.ts +41 -0
- package/dist/memory/session-indexer.d.ts.map +1 -0
- package/dist/memory/session-indexer.js +367 -0
- package/dist/memory/session-indexer.js.map +1 -0
- package/dist/memory/simple-manager.d.ts +6 -0
- package/dist/memory/simple-manager.d.ts.map +1 -1
- package/dist/memory/simple-manager.js +35 -12
- package/dist/memory/simple-manager.js.map +1 -1
- package/dist/memory/ssrf.d.ts +18 -0
- package/dist/memory/ssrf.d.ts.map +1 -0
- package/dist/memory/ssrf.js +305 -0
- package/dist/memory/ssrf.js.map +1 -0
- package/dist/package.json +8 -5
- package/dist/platform/platform.d.ts.map +1 -1
- package/dist/platform/platform.js +30 -7
- package/dist/platform/platform.js.map +1 -1
- package/dist/platform/types.d.ts +2 -0
- package/dist/platform/types.d.ts.map +1 -1
- package/dist/runtime/agent.d.ts +8 -0
- package/dist/runtime/agent.d.ts.map +1 -1
- package/dist/runtime/agent.js +194 -46
- package/dist/runtime/agent.js.map +1 -1
- package/dist/runtime/api-key-rotation.d.ts +26 -0
- package/dist/runtime/api-key-rotation.d.ts.map +1 -0
- package/dist/runtime/api-key-rotation.js +174 -0
- package/dist/runtime/api-key-rotation.js.map +1 -0
- package/dist/runtime/context-guard.d.ts +32 -0
- package/dist/runtime/context-guard.d.ts.map +1 -0
- package/dist/runtime/context-guard.js +61 -0
- package/dist/runtime/context-guard.js.map +1 -0
- package/dist/runtime/failover-error.d.ts +62 -0
- package/dist/runtime/failover-error.d.ts.map +1 -0
- package/dist/runtime/failover-error.js +733 -0
- package/dist/runtime/failover-error.js.map +1 -0
- package/dist/runtime/failover-policy.d.ts +5 -0
- package/dist/runtime/failover-policy.d.ts.map +1 -0
- package/dist/runtime/failover-policy.js +18 -0
- package/dist/runtime/failover-policy.js.map +1 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +11 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/memory-flush.d.ts +24 -0
- package/dist/runtime/memory-flush.d.ts.map +1 -0
- package/dist/runtime/memory-flush.js +64 -0
- package/dist/runtime/memory-flush.js.map +1 -0
- package/dist/runtime/memory-tools.d.ts +14 -0
- package/dist/runtime/memory-tools.d.ts.map +1 -0
- package/dist/runtime/memory-tools.js +58 -0
- package/dist/runtime/memory-tools.js.map +1 -0
- package/dist/runtime/model-fallback.d.ts +56 -0
- package/dist/runtime/model-fallback.d.ts.map +1 -0
- package/dist/runtime/model-fallback.js +301 -0
- package/dist/runtime/model-fallback.js.map +1 -0
- package/dist/runtime/model-fallback.types.d.ts +14 -0
- package/dist/runtime/model-fallback.types.d.ts.map +1 -0
- package/dist/runtime/model-fallback.types.js +3 -0
- package/dist/runtime/model-fallback.types.js.map +1 -0
- package/dist/runtime/retry.d.ts +24 -0
- package/dist/runtime/retry.d.ts.map +1 -0
- package/dist/runtime/retry.js +102 -0
- package/dist/runtime/retry.js.map +1 -0
- package/dist/runtime/session-pruning.d.ts +22 -0
- package/dist/runtime/session-pruning.d.ts.map +1 -0
- package/dist/runtime/session-pruning.js +118 -0
- package/dist/runtime/session-pruning.js.map +1 -0
- package/dist/runtime/stream-adapters.d.ts +11 -0
- package/dist/runtime/stream-adapters.d.ts.map +1 -0
- package/dist/runtime/stream-adapters.js +46 -0
- package/dist/runtime/stream-adapters.js.map +1 -0
- package/dist/runtime/subagent.d.ts +83 -0
- package/dist/runtime/subagent.d.ts.map +1 -0
- package/dist/runtime/subagent.js +190 -0
- package/dist/runtime/subagent.js.map +1 -0
- package/dist/runtime/tool-result-truncation.d.ts +25 -0
- package/dist/runtime/tool-result-truncation.d.ts.map +1 -0
- package/dist/runtime/tool-result-truncation.js +115 -0
- package/dist/runtime/tool-result-truncation.js.map +1 -0
- package/dist/sandbox/cgroup.d.ts +4 -1
- package/dist/sandbox/cgroup.d.ts.map +1 -1
- package/dist/sandbox/cgroup.js +28 -15
- package/dist/sandbox/cgroup.js.map +1 -1
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.d.ts.map +1 -1
- package/dist/sandbox/index.js +2 -1
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/ipc.d.ts +4 -1
- package/dist/sandbox/ipc.d.ts.map +1 -1
- package/dist/sandbox/ipc.js +33 -17
- package/dist/sandbox/ipc.js.map +1 -1
- package/dist/sandbox/manager.d.ts +1 -2
- package/dist/sandbox/manager.d.ts.map +1 -1
- package/dist/sandbox/manager.js +136 -130
- package/dist/sandbox/manager.js.map +1 -1
- package/dist/sandbox/namespace.d.ts +1 -1
- package/dist/sandbox/namespace.d.ts.map +1 -1
- package/dist/sandbox/namespace.js +36 -37
- package/dist/sandbox/namespace.js.map +1 -1
- package/dist/sandbox/rootfs.d.ts +6 -1
- package/dist/sandbox/rootfs.d.ts.map +1 -1
- package/dist/sandbox/rootfs.js +114 -30
- package/dist/sandbox/rootfs.js.map +1 -1
- package/dist/sandbox/seccomp-apply.d.ts +9 -0
- package/dist/sandbox/seccomp-apply.d.ts.map +1 -0
- package/dist/sandbox/seccomp-apply.js +227 -0
- package/dist/sandbox/seccomp-apply.js.map +1 -0
- package/dist/sandbox/seccomp.js +3 -3
- package/dist/sandbox/seccomp.js.map +1 -1
- package/dist/sandbox/types.d.ts +1 -3
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/sandbox/worker.d.ts +3 -0
- package/dist/sandbox/worker.d.ts.map +1 -1
- package/dist/sandbox/worker.js +84 -17
- package/dist/sandbox/worker.js.map +1 -1
- package/dist/sessions/index.d.ts +1 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +1 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -2
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +49 -27
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/transcript-events.d.ts +11 -0
- package/dist/sessions/transcript-events.d.ts.map +1 -0
- package/dist/sessions/transcript-events.js +40 -0
- package/dist/sessions/transcript-events.js.map +1 -0
- package/dist/shared/agent-session.d.ts +10 -0
- package/dist/shared/agent-session.d.ts.map +1 -0
- package/dist/shared/agent-session.js +33 -0
- package/dist/shared/agent-session.js.map +1 -0
- package/dist/shared/constants.d.ts +6 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +11 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/fs.d.ts +7 -0
- package/dist/shared/fs.d.ts.map +1 -0
- package/dist/shared/fs.js +14 -0
- package/dist/shared/fs.js.map +1 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/skills/enablement.d.ts.map +1 -1
- package/dist/skills/enablement.js +2 -2
- package/dist/skills/enablement.js.map +1 -1
- package/dist/workspace/runner.d.ts.map +1 -1
- package/dist/workspace/runner.js +436 -105
- package/dist/workspace/runner.js.map +1 -1
- package/dist/workspace/types.d.ts +1 -0
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace.d.ts.map +1 -1
- package/dist/workspace/workspace.js +12 -3
- package/dist/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enablement.d.ts","sourceRoot":"","sources":["../../src/skills/enablement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"enablement.d.ts","sourceRoot":"","sources":["../../src/skills/enablement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGvE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,WAAW,IAAI,kBAAkB,EAAE,CAAC;IACpC,cAAc,IAAI,MAAM,EAAE,CAAC;CAC5B;AAMD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,GACtB,eAAe,CAkDjB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { atomicWriteFileSync } from "../shared/index.js";
|
|
3
4
|
export function createSkillEnablement(workspaceDir, registry) {
|
|
4
5
|
const filePath = path.join(workspaceDir, "enabled-skills.json");
|
|
5
6
|
function loadState() {
|
|
@@ -12,8 +13,7 @@ export function createSkillEnablement(workspaceDir, registry) {
|
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
function saveState(state) {
|
|
15
|
-
|
|
16
|
-
fs.writeFileSync(filePath, JSON.stringify(state, null, 2));
|
|
16
|
+
atomicWriteFileSync(filePath, JSON.stringify(state, null, 2));
|
|
17
17
|
}
|
|
18
18
|
return {
|
|
19
19
|
enable(skillId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enablement.js","sourceRoot":"","sources":["../../src/skills/enablement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"enablement.js","sourceRoot":"","sources":["../../src/skills/enablement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAczD,MAAM,UAAU,qBAAqB,CACnC,YAAoB,EACpB,QAAuB;IAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;IAEhE,SAAS,SAAS;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,SAAS,SAAS,CAAC,KAAsB;QACvC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO;QACL,MAAM,CAAC,OAAO;YACZ,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,KAAK,CAAC;YACzC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAC;YACvD,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAClC,SAAS,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,CAAC,OAAO;YACb,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACjD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACnC,SAAS,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,SAAS,CAAC,OAAO;YACf,OAAO,SAAS,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACrD,CAAC;QAED,WAAW;YACT,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,aAAa;iBACvB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;iBAC7B,MAAM,CAAC,CAAC,CAAC,EAA2B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;QAC7D,CAAC;QAED,cAAc;YACZ,OAAO,SAAS,EAAE,CAAC,aAAa,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/workspace/runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/workspace/runner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAsB1D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,eAAe,CAAC;CAC9B;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,sBAAsB,GAC1B,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,cAAc,CAAC,CAgX3D"}
|
package/dist/workspace/runner.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as url from "node:url";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
import { getOrCreateSession, updateSession, } from "../sessions/index.js";
|
|
5
6
|
import { createSandboxManager } from "../sandbox/manager.js";
|
|
6
7
|
import { createIpcPeer } from "../sandbox/ipc.js";
|
|
8
|
+
import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROTECTED_SYSTEM_ENV_KEYS, buildProviderEnvKey, } from "../shared/index.js";
|
|
9
|
+
import { runWithModelFallback, createCooldownStore } from "../runtime/model-fallback.js";
|
|
10
|
+
import { resolveContextWindowInfo, evaluateContextWindowGuard, CONTEXT_WINDOW_HARD_MIN_TOKENS } from "../runtime/context-guard.js";
|
|
11
|
+
import { collectProviderApiKeys, executeWithApiKeyRotation } from "../runtime/api-key-rotation.js";
|
|
12
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
13
|
+
const log = createSubsystemLogger("workspace-runner");
|
|
7
14
|
export function createWorkspaceRunner(ctx) {
|
|
8
15
|
const sandboxManager = createSandboxManager();
|
|
16
|
+
const cooldownStore = createCooldownStore();
|
|
9
17
|
return async function run(options) {
|
|
10
18
|
const sessionId = options.sessionId ?? `session_${Date.now()}`;
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const apiKey = options.apiKey ?? ctx.config.apiKey ?? process.env[providerEnvKey] ?? "";
|
|
15
|
-
const sessionsDir = path.join(ctx.workspaceDir, "sessions");
|
|
19
|
+
const requestedModelId = options.model ?? ctx.config.model ?? DEFAULT_MODEL;
|
|
20
|
+
const requestedProvider = options.provider ?? ctx.config.provider ?? DEFAULT_PROVIDER;
|
|
21
|
+
const sessionsDir = path.join(ctx.workspaceDir, "sessions", sessionId);
|
|
16
22
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
17
|
-
getOrCreateSession(ctx.workspaceDir, sessionId, { model:
|
|
23
|
+
await getOrCreateSession(ctx.workspaceDir, sessionId, { model: requestedModelId });
|
|
18
24
|
await ctx.hooks.run("session_start", { sessionId });
|
|
19
25
|
await ctx.hooks.run("before_agent_start", {
|
|
20
26
|
sessionId,
|
|
21
27
|
message: options.message,
|
|
22
|
-
model:
|
|
28
|
+
model: requestedModelId,
|
|
23
29
|
});
|
|
24
30
|
let skillsPrompt = "";
|
|
25
31
|
if (ctx.config.skills?.enabled !== false) {
|
|
@@ -40,119 +46,444 @@ export function createWorkspaceRunner(ctx) {
|
|
|
40
46
|
]
|
|
41
47
|
.filter(Boolean)
|
|
42
48
|
.join("\n\n");
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
message: options.message,
|
|
50
|
-
model: modelId,
|
|
51
|
-
provider,
|
|
52
|
-
systemPrompt: systemPrompt || undefined,
|
|
53
|
-
enableCodingTools: hasOsIsolation,
|
|
54
|
-
};
|
|
55
|
-
const workerPath = resolveWorkerPath();
|
|
56
|
-
const workerEnv = {
|
|
57
|
-
SANDBOX_WORKER_CONFIG: JSON.stringify(workerConfig),
|
|
58
|
-
};
|
|
59
|
-
if (apiKey) {
|
|
60
|
-
workerEnv[providerEnvKey] = apiKey;
|
|
61
|
-
}
|
|
62
|
-
const sandboxConfig = {
|
|
63
|
-
memoryLimitMb: 512,
|
|
64
|
-
pidsLimit: 100,
|
|
65
|
-
timeoutMs: 5 * 60 * 1000,
|
|
66
|
-
networkIsolation: false,
|
|
67
|
-
};
|
|
68
|
-
const sandboxProcess = await sandboxManager.spawn({
|
|
69
|
-
command: process.execPath,
|
|
70
|
-
args: [
|
|
71
|
-
"--experimental-vm-modules",
|
|
72
|
-
"--no-warnings",
|
|
73
|
-
workerPath,
|
|
74
|
-
],
|
|
75
|
-
env: workerEnv,
|
|
76
|
-
cwd: ctx.workspaceDir,
|
|
77
|
-
config: sandboxConfig,
|
|
78
|
-
protectedKeys: apiKey ? [providerEnvKey] : [],
|
|
79
|
-
onStderr: (data) => {
|
|
80
|
-
process.stderr.write(`[sandbox:${ctx.userId}] ${data}`);
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
const hostPeer = createIpcPeer(sandboxProcess.stdout, sandboxProcess.stdin);
|
|
84
|
-
hostPeer.handle("memory.search", async (params) => {
|
|
85
|
-
const { query, maxResults } = params;
|
|
86
|
-
return ctx.memory.search(query, { maxResults });
|
|
87
|
-
});
|
|
88
|
-
hostPeer.handle("memory.store", async (params) => {
|
|
89
|
-
const { content, metadata } = params;
|
|
90
|
-
const id = await ctx.memory.store(content, metadata);
|
|
91
|
-
return { id };
|
|
92
|
-
});
|
|
93
|
-
hostPeer.handle("credentials.resolve", async (params) => {
|
|
94
|
-
const { skillId } = params;
|
|
95
|
-
return ctx.credentials.resolve(skillId);
|
|
96
|
-
});
|
|
97
|
-
hostPeer.handle("skill.execute", async (params) => {
|
|
98
|
-
const { skillId, params: skillParams } = params;
|
|
99
|
-
if (!ctx.skills.isEnabled(skillId)) {
|
|
100
|
-
throw new Error(`Skill "${skillId}" is not enabled for this workspace`);
|
|
49
|
+
async function executeWithModel(provider, modelId) {
|
|
50
|
+
let modelContextWindow;
|
|
51
|
+
try {
|
|
52
|
+
const { getModel } = await import("@mariozechner/pi-ai");
|
|
53
|
+
const resolvedModel = getModel(provider, modelId);
|
|
54
|
+
modelContextWindow = resolvedModel.contextWindow;
|
|
101
55
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
hostPeer.start();
|
|
111
|
-
// If the child process dies, stop the IPC peer so pending calls reject
|
|
112
|
-
// instead of hanging forever. This prevents a deadlock where
|
|
113
|
-
// hostPeer.call() awaits a response that will never come.
|
|
114
|
-
sandboxProcess.waitForExit().then(() => hostPeer.stop(), () => hostPeer.stop());
|
|
115
|
-
let result;
|
|
116
|
-
try {
|
|
117
|
-
const response = await hostPeer.call("agent.run", {
|
|
118
|
-
message: options.message,
|
|
119
|
-
sessionId,
|
|
120
|
-
model: modelId,
|
|
121
|
-
provider,
|
|
122
|
-
systemPrompt: systemPrompt || undefined,
|
|
56
|
+
catch {
|
|
57
|
+
// model resolution may fail — proceed with config/default tokens
|
|
58
|
+
}
|
|
59
|
+
const ctxInfo = resolveContextWindowInfo({
|
|
60
|
+
modelContextWindow,
|
|
61
|
+
configContextTokens: options.contextTokens,
|
|
123
62
|
});
|
|
124
|
-
|
|
125
|
-
|
|
63
|
+
const ctxGuard = evaluateContextWindowGuard({ info: ctxInfo });
|
|
64
|
+
if (ctxGuard.shouldBlock) {
|
|
65
|
+
throw new Error(`Context window too small (${ctxGuard.tokens} tokens). Minimum: ${CONTEXT_WINDOW_HARD_MIN_TOKENS}.`);
|
|
126
66
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
67
|
+
const providerEnvKey = buildProviderEnvKey(provider);
|
|
68
|
+
const apiKeys = [];
|
|
69
|
+
if (options.apiKey)
|
|
70
|
+
apiKeys.push(options.apiKey);
|
|
71
|
+
if (options.apiKeys) {
|
|
72
|
+
apiKeys.push(...options.apiKeys);
|
|
73
|
+
}
|
|
74
|
+
if (ctx.config.apiKey)
|
|
75
|
+
apiKeys.push(ctx.config.apiKey);
|
|
76
|
+
apiKeys.push(...collectProviderApiKeys(provider));
|
|
77
|
+
const uniqueKeys = [...new Set(apiKeys.filter((k) => k.length > 0))];
|
|
78
|
+
if (uniqueKeys.length === 0) {
|
|
79
|
+
const envVal = process.env[providerEnvKey];
|
|
80
|
+
if (envVal)
|
|
81
|
+
uniqueKeys.push(envVal);
|
|
82
|
+
}
|
|
83
|
+
async function runWithKey(apiKey) {
|
|
84
|
+
const workerConfig = {
|
|
85
|
+
workspaceDir: ctx.workspaceDir,
|
|
86
|
+
sessionsDir,
|
|
87
|
+
sessionId,
|
|
88
|
+
message: options.message,
|
|
89
|
+
model: modelId,
|
|
90
|
+
provider,
|
|
91
|
+
systemPrompt: systemPrompt || undefined,
|
|
92
|
+
enableCodingTools: true,
|
|
93
|
+
contextTokens: options.contextTokens,
|
|
94
|
+
maxRetries: options.maxRetries,
|
|
95
|
+
enableSubagents: options.enableSubagents,
|
|
96
|
+
};
|
|
97
|
+
const workerPath = resolveWorkerPath();
|
|
98
|
+
const projectDir = path.dirname(path.dirname(workerPath));
|
|
99
|
+
const workerConfigJson = JSON.stringify(workerConfig);
|
|
100
|
+
const MAX_ENV_SIZE = 128 * 1024;
|
|
101
|
+
if (workerConfigJson.length > MAX_ENV_SIZE) {
|
|
102
|
+
throw new Error(`SANDBOX_WORKER_CONFIG exceeds ${MAX_ENV_SIZE} bytes (${workerConfigJson.length} bytes). ` +
|
|
103
|
+
`Reduce the message or systemPrompt size.`);
|
|
104
|
+
}
|
|
105
|
+
const workerEnv = {
|
|
106
|
+
SANDBOX_WORKER_CONFIG: workerConfigJson,
|
|
107
|
+
};
|
|
108
|
+
if (apiKey) {
|
|
109
|
+
workerEnv[providerEnvKey] = apiKey;
|
|
110
|
+
}
|
|
111
|
+
const sandboxConfig = {
|
|
112
|
+
memoryLimitMb: 512,
|
|
113
|
+
pidsLimit: 100,
|
|
114
|
+
timeoutMs: 5 * 60 * 1000,
|
|
115
|
+
networkIsolation: true,
|
|
116
|
+
mountBinds: [
|
|
117
|
+
{ source: projectDir, target: projectDir, readonly: true },
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
const workerArgs = [
|
|
121
|
+
"--experimental-vm-modules",
|
|
122
|
+
"--no-warnings",
|
|
123
|
+
];
|
|
124
|
+
if (workerPath.endsWith(".ts")) {
|
|
125
|
+
workerArgs.push("--import", "tsx");
|
|
126
|
+
}
|
|
127
|
+
workerArgs.push(workerPath);
|
|
128
|
+
const sandboxProcess = await sandboxManager.spawn({
|
|
129
|
+
command: process.execPath,
|
|
130
|
+
args: workerArgs,
|
|
131
|
+
env: workerEnv,
|
|
132
|
+
cwd: ctx.workspaceDir,
|
|
133
|
+
config: sandboxConfig,
|
|
134
|
+
protectedKeys: apiKey ? [providerEnvKey] : [],
|
|
135
|
+
onStderr: (data) => {
|
|
136
|
+
process.stderr.write(`[sandbox:${ctx.userId}] ${data}`);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
const agentTimeoutMs = (sandboxConfig.timeoutMs ?? 5 * 60 * 1000) + 60_000;
|
|
140
|
+
const hostPeer = createIpcPeer(sandboxProcess.stdout, sandboxProcess.stdin, { callTimeoutMs: agentTimeoutMs });
|
|
141
|
+
const ipcRateLimiter = createIpcRateLimiter();
|
|
142
|
+
hostPeer.handle("memory.search", async (params) => {
|
|
143
|
+
ipcRateLimiter.check("memory.search");
|
|
144
|
+
const p = params;
|
|
145
|
+
const query = p?.query;
|
|
146
|
+
if (typeof query !== "string" || query.length === 0) {
|
|
147
|
+
throw new Error("memory.search: 'query' must be a non-empty string");
|
|
148
|
+
}
|
|
149
|
+
if (query.length > 10_000) {
|
|
150
|
+
throw new Error("memory.search: 'query' exceeds maximum length (10000)");
|
|
151
|
+
}
|
|
152
|
+
const maxResults = typeof p?.maxResults === "number"
|
|
153
|
+
? Math.min(Math.max(1, Math.floor(p.maxResults)), 100)
|
|
154
|
+
: undefined;
|
|
155
|
+
return ctx.memory.search(query, { maxResults });
|
|
156
|
+
});
|
|
157
|
+
hostPeer.handle("memory.store", async (params) => {
|
|
158
|
+
ipcRateLimiter.check("memory.store");
|
|
159
|
+
const p = params;
|
|
160
|
+
const content = p?.content;
|
|
161
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
162
|
+
throw new Error("memory.store: 'content' must be a non-empty string");
|
|
163
|
+
}
|
|
164
|
+
if (content.length > 1_000_000) {
|
|
165
|
+
throw new Error("memory.store: 'content' exceeds maximum length (1000000)");
|
|
166
|
+
}
|
|
167
|
+
const metadata = p?.metadata;
|
|
168
|
+
if (metadata !== undefined) {
|
|
169
|
+
const metaStr = JSON.stringify(metadata);
|
|
170
|
+
if (metaStr.length > 10_000) {
|
|
171
|
+
throw new Error("memory.store: 'metadata' exceeds maximum serialized size (10000 bytes)");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const id = await ctx.memory.store(content, metadata);
|
|
175
|
+
return { id };
|
|
176
|
+
});
|
|
177
|
+
hostPeer.handle("skill.execute", async (params) => {
|
|
178
|
+
ipcRateLimiter.check("skill.execute");
|
|
179
|
+
const p = params;
|
|
180
|
+
const skillId = p?.skillId;
|
|
181
|
+
if (typeof skillId !== "string" || skillId.length === 0) {
|
|
182
|
+
throw new Error("skill.execute: 'skillId' must be a non-empty string");
|
|
183
|
+
}
|
|
184
|
+
if (!ctx.skills.isEnabled(skillId)) {
|
|
185
|
+
throw new Error(`Skill "${skillId}" is not enabled for this workspace`);
|
|
186
|
+
}
|
|
187
|
+
const skillEntry = ctx.skillRegistry.get(skillId);
|
|
188
|
+
if (!skillEntry) {
|
|
189
|
+
throw new Error(`Skill "${skillId}" not found in registry`);
|
|
190
|
+
}
|
|
191
|
+
const execScript = findSkillExecutable(skillEntry.path);
|
|
192
|
+
if (!execScript) {
|
|
193
|
+
throw new Error(`Skill "${skillId}" has no executable script. ` +
|
|
194
|
+
`Add execute.js, execute.mjs, or execute.sh to the skill directory.`);
|
|
195
|
+
}
|
|
196
|
+
let resolvedScript;
|
|
197
|
+
try {
|
|
198
|
+
resolvedScript = fs.realpathSync(execScript);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new Error(`Skill script "${execScript}" does not exist or is a broken symlink`);
|
|
202
|
+
}
|
|
203
|
+
const resolvedSkillDir = fs.realpathSync(skillEntry.path);
|
|
204
|
+
if (!resolvedScript.startsWith(resolvedSkillDir + path.sep)) {
|
|
205
|
+
throw new Error(`Skill script "${execScript}" resolves outside skill directory`);
|
|
206
|
+
}
|
|
207
|
+
const credentials = await ctx.credentials.resolve(skillId) ?? {};
|
|
208
|
+
const execParams = (p?.params ?? {});
|
|
209
|
+
const result = await executeSkillScript(execScript, execParams, credentials, resolvedSkillDir);
|
|
210
|
+
return { result };
|
|
211
|
+
});
|
|
212
|
+
hostPeer.handle("hooks.before_tool_call", async (params) => {
|
|
213
|
+
ipcRateLimiter.check("hooks.before_tool_call");
|
|
214
|
+
const p = params;
|
|
215
|
+
const toolName = typeof p?.toolName === "string" ? p.toolName : "unknown";
|
|
216
|
+
const input = (p?.input && typeof p.input === "object") ? p.input : {};
|
|
217
|
+
await ctx.hooks.run("before_tool_call", { toolName, input });
|
|
218
|
+
return {};
|
|
219
|
+
});
|
|
220
|
+
hostPeer.handle("hooks.after_tool_call", async (params) => {
|
|
221
|
+
ipcRateLimiter.check("hooks.after_tool_call");
|
|
222
|
+
const p = params;
|
|
223
|
+
const toolName = typeof p?.toolName === "string" ? p.toolName : "unknown";
|
|
224
|
+
const input = (p?.input && typeof p.input === "object") ? p.input : {};
|
|
225
|
+
await ctx.hooks.run("after_tool_call", { toolName, input, result: p?.result });
|
|
226
|
+
return {};
|
|
227
|
+
});
|
|
228
|
+
hostPeer.handle("agent.run.subagent", async (params) => {
|
|
229
|
+
ipcRateLimiter.check("agent.run.subagent");
|
|
230
|
+
const p = (params ?? {});
|
|
231
|
+
const subMessage = typeof p.message === "string" ? p.message : "";
|
|
232
|
+
if (!subMessage) {
|
|
233
|
+
return { error: "agent.run.subagent: 'message' is required" };
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const subResult = await run({
|
|
237
|
+
message: subMessage,
|
|
238
|
+
sessionId: typeof p.sessionId === "string" ? p.sessionId : undefined,
|
|
239
|
+
model: typeof p.model === "string" ? p.model : undefined,
|
|
240
|
+
provider: typeof p.provider === "string" ? p.provider : undefined,
|
|
241
|
+
systemPrompt: typeof p.systemPrompt === "string" ? p.systemPrompt : undefined,
|
|
242
|
+
});
|
|
243
|
+
return { response: subResult.response, sessionId: subResult.sessionId };
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const onEvent = options.onEvent;
|
|
250
|
+
if (onEvent) {
|
|
251
|
+
hostPeer.handle("agent.event", async (params) => {
|
|
252
|
+
const event = (params ?? {});
|
|
253
|
+
try {
|
|
254
|
+
onEvent(event);
|
|
255
|
+
}
|
|
256
|
+
catch { /* caller error */ }
|
|
257
|
+
return {};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
hostPeer.start();
|
|
261
|
+
// If the child process dies, stop the IPC peer so pending calls reject
|
|
262
|
+
// instead of hanging forever. This prevents a deadlock where
|
|
263
|
+
// hostPeer.call() awaits a response that will never come.
|
|
264
|
+
sandboxProcess.waitForExit().then(() => hostPeer.stop(), () => hostPeer.stop());
|
|
265
|
+
let result;
|
|
266
|
+
try {
|
|
267
|
+
const response = await hostPeer.call("agent.run", {
|
|
268
|
+
message: options.message,
|
|
269
|
+
sessionId,
|
|
270
|
+
model: modelId,
|
|
271
|
+
provider,
|
|
272
|
+
systemPrompt: systemPrompt || undefined,
|
|
273
|
+
});
|
|
274
|
+
if (response.error) {
|
|
275
|
+
throw new Error(`Agent execution failed: ${response.error}`);
|
|
276
|
+
}
|
|
277
|
+
result = {
|
|
278
|
+
response: response.response ?? "",
|
|
279
|
+
sessionId: response.sessionId ?? sessionId,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
hostPeer.stop();
|
|
284
|
+
sandboxProcess.kill();
|
|
285
|
+
await sandboxProcess.waitForExit();
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
hostPeer.stop();
|
|
289
|
+
sandboxProcess.kill();
|
|
290
|
+
await sandboxProcess.waitForExit();
|
|
291
|
+
return {
|
|
292
|
+
...result,
|
|
293
|
+
provider,
|
|
294
|
+
model: modelId,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (uniqueKeys.length > 1) {
|
|
298
|
+
return executeWithApiKeyRotation({
|
|
299
|
+
provider,
|
|
300
|
+
apiKeys: uniqueKeys,
|
|
301
|
+
execute: runWithKey,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return runWithKey(uniqueKeys[0] ?? "");
|
|
137
305
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
306
|
+
const fallbackResult = await runWithModelFallback({
|
|
307
|
+
provider: requestedProvider,
|
|
308
|
+
model: requestedModelId,
|
|
309
|
+
fallbacks: options.fallbacks,
|
|
310
|
+
run: executeWithModel,
|
|
311
|
+
cooldownStore,
|
|
312
|
+
});
|
|
141
313
|
await ctx.hooks.run("after_agent_end", {
|
|
142
314
|
sessionId,
|
|
143
|
-
result: result.response,
|
|
315
|
+
result: fallbackResult.result.response,
|
|
144
316
|
});
|
|
145
317
|
await ctx.hooks.run("session_end", { sessionId });
|
|
146
|
-
updateSession(ctx.workspaceDir, sessionId, { model:
|
|
147
|
-
return
|
|
318
|
+
await updateSession(ctx.workspaceDir, sessionId, { model: fallbackResult.model });
|
|
319
|
+
return {
|
|
320
|
+
...fallbackResult.result,
|
|
321
|
+
fallbackUsed: fallbackResult.attempts.length > 0,
|
|
322
|
+
};
|
|
148
323
|
};
|
|
149
324
|
}
|
|
150
325
|
function resolveWorkerPath() {
|
|
151
326
|
const thisFile = url.fileURLToPath(import.meta.url);
|
|
152
327
|
const srcDir = path.dirname(path.dirname(thisFile));
|
|
328
|
+
const jsPath = path.join(srcDir, "sandbox", "worker.js");
|
|
329
|
+
if (fs.existsSync(jsPath))
|
|
330
|
+
return jsPath;
|
|
153
331
|
const tsPath = path.join(srcDir, "sandbox", "worker.ts");
|
|
154
332
|
if (fs.existsSync(tsPath))
|
|
155
333
|
return tsPath;
|
|
156
|
-
return
|
|
334
|
+
return jsPath;
|
|
335
|
+
}
|
|
336
|
+
function findSkillExecutable(skillDir) {
|
|
337
|
+
for (const name of ["execute.js", "execute.mjs", "execute.sh"]) {
|
|
338
|
+
const full = path.join(skillDir, name);
|
|
339
|
+
if (fs.existsSync(full))
|
|
340
|
+
return full;
|
|
341
|
+
}
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const BLOCKED_CREDENTIAL_ENV_KEYS = new Set([
|
|
345
|
+
"NODE_OPTIONS",
|
|
346
|
+
"NODE_EXTRA_CA_CERTS",
|
|
347
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
348
|
+
"NODE_DEBUG",
|
|
349
|
+
"NODE_REDIRECT_WARNINGS",
|
|
350
|
+
"LD_PRELOAD",
|
|
351
|
+
"LD_LIBRARY_PATH",
|
|
352
|
+
"DYLD_INSERT_LIBRARIES",
|
|
353
|
+
"DYLD_LIBRARY_PATH",
|
|
354
|
+
"BASH_ENV",
|
|
355
|
+
"ENV",
|
|
356
|
+
"CDPATH",
|
|
357
|
+
"PYTHONSTARTUP",
|
|
358
|
+
"PYTHONPATH",
|
|
359
|
+
"RUBYOPT",
|
|
360
|
+
"PERL5OPT",
|
|
361
|
+
"PERL5LIB",
|
|
362
|
+
"JAVA_TOOL_OPTIONS",
|
|
363
|
+
"_JAVA_OPTIONS",
|
|
364
|
+
"CLASSPATH",
|
|
365
|
+
"IFS",
|
|
366
|
+
"MAIL",
|
|
367
|
+
"MAILPATH",
|
|
368
|
+
"PROMPT_COMMAND",
|
|
369
|
+
"PS4",
|
|
370
|
+
"SHELLOPTS",
|
|
371
|
+
"BASHOPTS",
|
|
372
|
+
"GLOBIGNORE",
|
|
373
|
+
"HISTFILE",
|
|
374
|
+
"HISTCONTROL",
|
|
375
|
+
"SSH_AUTH_SOCK",
|
|
376
|
+
"GPG_AGENT_INFO",
|
|
377
|
+
"TMPDIR",
|
|
378
|
+
"TEMP",
|
|
379
|
+
"TMP",
|
|
380
|
+
"http_proxy",
|
|
381
|
+
"HTTP_PROXY",
|
|
382
|
+
"https_proxy",
|
|
383
|
+
"HTTPS_PROXY",
|
|
384
|
+
"no_proxy",
|
|
385
|
+
"NO_PROXY",
|
|
386
|
+
"ALL_PROXY",
|
|
387
|
+
]);
|
|
388
|
+
const MAX_SKILL_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
389
|
+
async function executeSkillScript(scriptPath, params, credentials, cwd, timeoutMs = 30_000) {
|
|
390
|
+
const ext = path.extname(scriptPath);
|
|
391
|
+
let command;
|
|
392
|
+
let args;
|
|
393
|
+
switch (ext) {
|
|
394
|
+
case ".js":
|
|
395
|
+
case ".mjs":
|
|
396
|
+
command = process.execPath;
|
|
397
|
+
args = ["--no-warnings", scriptPath];
|
|
398
|
+
break;
|
|
399
|
+
case ".sh":
|
|
400
|
+
command = "/bin/bash";
|
|
401
|
+
args = [scriptPath];
|
|
402
|
+
break;
|
|
403
|
+
default:
|
|
404
|
+
throw new Error(`Unsupported skill script type: ${ext}`);
|
|
405
|
+
}
|
|
406
|
+
const env = {};
|
|
407
|
+
for (const key of PROTECTED_SYSTEM_ENV_KEYS) {
|
|
408
|
+
if (process.env[key])
|
|
409
|
+
env[key] = process.env[key];
|
|
410
|
+
}
|
|
411
|
+
for (const [key, value] of Object.entries(credentials)) {
|
|
412
|
+
if (PROTECTED_SYSTEM_ENV_KEYS.has(key))
|
|
413
|
+
continue;
|
|
414
|
+
if (BLOCKED_CREDENTIAL_ENV_KEYS.has(key.toUpperCase())) {
|
|
415
|
+
log.warn(`blocked dangerous credential key "${key}" for skill execution`);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
env[key] = value;
|
|
419
|
+
}
|
|
420
|
+
return new Promise((resolve, reject) => {
|
|
421
|
+
const child = spawn(command, args, {
|
|
422
|
+
cwd,
|
|
423
|
+
env,
|
|
424
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
425
|
+
timeout: timeoutMs,
|
|
426
|
+
});
|
|
427
|
+
let stdout = "";
|
|
428
|
+
let stderr = "";
|
|
429
|
+
let outputLimitExceeded = false;
|
|
430
|
+
child.stdout.on("data", (data) => {
|
|
431
|
+
if (outputLimitExceeded)
|
|
432
|
+
return;
|
|
433
|
+
stdout += data.toString();
|
|
434
|
+
if (stdout.length > MAX_SKILL_OUTPUT_BYTES) {
|
|
435
|
+
outputLimitExceeded = true;
|
|
436
|
+
try {
|
|
437
|
+
child.kill("SIGKILL");
|
|
438
|
+
}
|
|
439
|
+
catch { /* already dead */ }
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
child.stderr.on("data", (data) => {
|
|
443
|
+
if (outputLimitExceeded)
|
|
444
|
+
return;
|
|
445
|
+
stderr += data.toString();
|
|
446
|
+
if (stderr.length > MAX_SKILL_OUTPUT_BYTES) {
|
|
447
|
+
outputLimitExceeded = true;
|
|
448
|
+
try {
|
|
449
|
+
child.kill("SIGKILL");
|
|
450
|
+
}
|
|
451
|
+
catch { /* already dead */ }
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
child.on("close", (code) => {
|
|
455
|
+
if (outputLimitExceeded) {
|
|
456
|
+
reject(new Error(`Skill script output exceeded ${MAX_SKILL_OUTPUT_BYTES} bytes limit`));
|
|
457
|
+
}
|
|
458
|
+
else if (code !== 0) {
|
|
459
|
+
reject(new Error(`Skill script exited with code ${code}: ${stderr.slice(0, 1000)}`));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
resolve(stdout);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
child.on("error", reject);
|
|
466
|
+
child.stdin.write(JSON.stringify(params));
|
|
467
|
+
child.stdin.end();
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const IPC_RATE_LIMIT = 200;
|
|
471
|
+
const IPC_RATE_WINDOW_MS = 1000;
|
|
472
|
+
function createIpcRateLimiter() {
|
|
473
|
+
const counters = new Map();
|
|
474
|
+
return {
|
|
475
|
+
check(method) {
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
let entry = counters.get(method);
|
|
478
|
+
if (!entry || now >= entry.resetAt) {
|
|
479
|
+
entry = { count: 0, resetAt: now + IPC_RATE_WINDOW_MS };
|
|
480
|
+
counters.set(method, entry);
|
|
481
|
+
}
|
|
482
|
+
entry.count++;
|
|
483
|
+
if (entry.count > IPC_RATE_LIMIT) {
|
|
484
|
+
throw new Error(`IPC rate limit exceeded for "${method}": max ${IPC_RATE_LIMIT} calls per ${IPC_RATE_WINDOW_MS}ms`);
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
};
|
|
157
488
|
}
|
|
158
489
|
//# sourceMappingURL=runner.js.map
|