@arnilo/prism-tools 0.0.0-stage → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +28 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +545 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn / attach a managed process session (native, sandbox, or PTY).
|
|
3
|
+
*/
|
|
4
|
+
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
import { buildChildEnv, DEFAULT_CHILD_ENV_INHERIT } from "../env.js";
|
|
8
|
+
/**
|
|
9
|
+
* Start-budget for host PTY attachment (frozen cap). Timeout fails closed with
|
|
10
|
+
* ERR_PRISM_PROCESS_PTY_LIMIT; the underlying promise result is discarded.
|
|
11
|
+
*/
|
|
12
|
+
import { assertExecutionAllowed, ExecutionDeniedError } from "../execution-policy.js";
|
|
13
|
+
import { OutputAccumulator } from "../output-accumulator.js";
|
|
14
|
+
import { resolveToCwd } from "../path-utils.js";
|
|
15
|
+
import {
|
|
16
|
+
acquireRecordLease,
|
|
17
|
+
buildProcessRecoveryRecord,
|
|
18
|
+
deleteProcessRecoveryRecord,
|
|
19
|
+
ProcessRecoveryError,
|
|
20
|
+
releaseRecordLease,
|
|
21
|
+
saveProcessRecoveryRecord,
|
|
22
|
+
validateBackendRef,
|
|
23
|
+
} from "./recovery.js";
|
|
24
|
+
import {
|
|
25
|
+
assertNotDisposed,
|
|
26
|
+
commandFingerprint,
|
|
27
|
+
durableSeams,
|
|
28
|
+
emit,
|
|
29
|
+
isInsideRoot,
|
|
30
|
+
notifyOutput,
|
|
31
|
+
nowIso,
|
|
32
|
+
resolveTerminal,
|
|
33
|
+
type SessionRecord,
|
|
34
|
+
type SessionsHost,
|
|
35
|
+
} from "./sessions-host.js";
|
|
36
|
+
import { persistTransition } from "./sessions-monitor.js";
|
|
37
|
+
import { checkSandboxAlive, sweepExpired, terminateRecord } from "./sessions-teardown.js";
|
|
38
|
+
import {
|
|
39
|
+
type ProcessExitResult,
|
|
40
|
+
type ProcessSession,
|
|
41
|
+
ProcessSessionError,
|
|
42
|
+
type ProcessSessionMetadata,
|
|
43
|
+
type ProcessStartRequest,
|
|
44
|
+
} from "./types.js";
|
|
45
|
+
|
|
46
|
+
function withPtyAttachTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
|
47
|
+
return new Promise<T>((resolve, reject) => {
|
|
48
|
+
const timer = setTimeout(
|
|
49
|
+
() => reject(new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `PTY attach timed out (${timeoutMs}ms)`)),
|
|
50
|
+
timeoutMs,
|
|
51
|
+
);
|
|
52
|
+
promise.then(
|
|
53
|
+
(value) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
resolve(value);
|
|
56
|
+
},
|
|
57
|
+
(error) => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
reject(error);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function assertPolicy(
|
|
66
|
+
host: SessionsHost,
|
|
67
|
+
operation: string,
|
|
68
|
+
command: string,
|
|
69
|
+
args: readonly string[],
|
|
70
|
+
cwd: string,
|
|
71
|
+
owner: string,
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
try {
|
|
74
|
+
await assertExecutionAllowed(host.policy, {
|
|
75
|
+
kind: "shell",
|
|
76
|
+
operation,
|
|
77
|
+
command,
|
|
78
|
+
paths: [cwd],
|
|
79
|
+
risk: "high",
|
|
80
|
+
metadata: { args: [...args], owner },
|
|
81
|
+
});
|
|
82
|
+
return "allow";
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message =
|
|
85
|
+
error instanceof ExecutionDeniedError
|
|
86
|
+
? (error.decision.reason ?? error.message)
|
|
87
|
+
: error instanceof Error
|
|
88
|
+
? error.message
|
|
89
|
+
: String(error);
|
|
90
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", message);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function makeHandle(host: SessionsHost, record: SessionRecord): ProcessSession {
|
|
95
|
+
const assertAttached = (): void => {
|
|
96
|
+
if (record.state === "released") {
|
|
97
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session released");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handle: ProcessSession = {
|
|
102
|
+
get id() {
|
|
103
|
+
return record.id;
|
|
104
|
+
},
|
|
105
|
+
get state() {
|
|
106
|
+
sweepExpired(host);
|
|
107
|
+
void checkSandboxAlive(host);
|
|
108
|
+
return record.state;
|
|
109
|
+
},
|
|
110
|
+
get owner() {
|
|
111
|
+
return record.owner;
|
|
112
|
+
},
|
|
113
|
+
metadata(): ProcessSessionMetadata {
|
|
114
|
+
sweepExpired(host);
|
|
115
|
+
return {
|
|
116
|
+
id: record.id,
|
|
117
|
+
commandFingerprint: record.commandFingerprint,
|
|
118
|
+
owner: record.owner,
|
|
119
|
+
workspace: record.workspace,
|
|
120
|
+
policyDecision: record.policyDecision,
|
|
121
|
+
startedAt: record.startedAt,
|
|
122
|
+
exitedAt: record.exitedAt,
|
|
123
|
+
state: record.state,
|
|
124
|
+
releaseOnCancel: record.releaseOnCancel,
|
|
125
|
+
pty: record.ptyTerminal !== undefined,
|
|
126
|
+
terminal: record.ptyTerminal,
|
|
127
|
+
ptyBackendMetadata: record.pty?.metadata,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
onOutput(listener) {
|
|
131
|
+
record.outputListeners.add(listener);
|
|
132
|
+
return () => {
|
|
133
|
+
record.outputListeners.delete(listener);
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
async output(request) {
|
|
137
|
+
assertAttached();
|
|
138
|
+
sweepExpired(host);
|
|
139
|
+
await checkSandboxAlive(host);
|
|
140
|
+
const cursor = request?.cursor ?? 0;
|
|
141
|
+
const maxBytes = request?.maxBytes ?? host.limits.maxOutputChunkBytes;
|
|
142
|
+
if (maxBytes > host.limits.maxOutputChunkBytes) {
|
|
143
|
+
throw new ProcessSessionError(
|
|
144
|
+
"ERR_PRISM_PROCESS_LIMIT",
|
|
145
|
+
`maxBytes exceeds maxOutputChunkBytes (${host.limits.maxOutputChunkBytes})`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const { data, nextCursor } = record.accumulator.readRaw(cursor, maxBytes);
|
|
149
|
+
const eof = record.state !== "running" && record.state !== "starting" && nextCursor >= record.accumulator.getTotalRawBytes();
|
|
150
|
+
return { data: data.toString("utf8"), cursor: nextCursor, eof };
|
|
151
|
+
},
|
|
152
|
+
async input(data) {
|
|
153
|
+
assertAttached();
|
|
154
|
+
sweepExpired(host);
|
|
155
|
+
await checkSandboxAlive(host);
|
|
156
|
+
if (record.state !== "running") {
|
|
157
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot input in state ${record.state}`);
|
|
158
|
+
}
|
|
159
|
+
await assertPolicy(host, "process_input", record.command, record.args, record.workspace, record.owner);
|
|
160
|
+
const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
|
|
161
|
+
if (buf.byteLength > host.limits.maxInputBytes) {
|
|
162
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `input exceeds maxInputBytes (${host.limits.maxInputBytes})`);
|
|
163
|
+
}
|
|
164
|
+
if (record.pty) {
|
|
165
|
+
if (buf.includes(0)) {
|
|
166
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "NUL bytes are not permitted in PTY input");
|
|
167
|
+
}
|
|
168
|
+
await record.pty.write(buf);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (record.backend) {
|
|
172
|
+
await record.backend.write(buf);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (record.stdinClosed || !record.child?.stdin.writable) {
|
|
176
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "stdin closed");
|
|
177
|
+
}
|
|
178
|
+
const child = record.child;
|
|
179
|
+
if (!child) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "stdin closed");
|
|
180
|
+
await new Promise<void>((resolveWrite, rejectWrite) => {
|
|
181
|
+
child.stdin.write(buf, (err) => (err ? rejectWrite(err) : resolveWrite()));
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
async wait(waitOptions) {
|
|
185
|
+
assertAttached();
|
|
186
|
+
sweepExpired(host);
|
|
187
|
+
await checkSandboxAlive(host);
|
|
188
|
+
if (record.state !== "running" && record.state !== "starting") {
|
|
189
|
+
return { exitCode: record.exitCode, state: record.state };
|
|
190
|
+
}
|
|
191
|
+
return await new Promise<ProcessExitResult>((resolveWait, rejectWait) => {
|
|
192
|
+
const onDone = (result: ProcessExitResult) => {
|
|
193
|
+
cleanup();
|
|
194
|
+
resolveWait(result);
|
|
195
|
+
};
|
|
196
|
+
const onAbort = () => {
|
|
197
|
+
cleanup();
|
|
198
|
+
rejectWait(new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "wait aborted"));
|
|
199
|
+
};
|
|
200
|
+
let timer: NodeJS.Timeout | undefined;
|
|
201
|
+
const cleanup = () => {
|
|
202
|
+
if (timer) clearTimeout(timer);
|
|
203
|
+
waitOptions?.signal?.removeEventListener("abort", onAbort);
|
|
204
|
+
const idx = record.waiters.indexOf(onDone);
|
|
205
|
+
if (idx >= 0) record.waiters.splice(idx, 1);
|
|
206
|
+
};
|
|
207
|
+
record.waiters.push(onDone);
|
|
208
|
+
if (waitOptions?.timeoutMs !== undefined) {
|
|
209
|
+
timer = setTimeout(() => {
|
|
210
|
+
cleanup();
|
|
211
|
+
rejectWait(new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", "wait timed out"));
|
|
212
|
+
}, waitOptions.timeoutMs);
|
|
213
|
+
}
|
|
214
|
+
if (waitOptions?.signal) {
|
|
215
|
+
if (waitOptions.signal.aborted) onAbort();
|
|
216
|
+
else waitOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
async signal(name) {
|
|
221
|
+
assertAttached();
|
|
222
|
+
sweepExpired(host);
|
|
223
|
+
await checkSandboxAlive(host);
|
|
224
|
+
if (record.state !== "running") {
|
|
225
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot signal in state ${record.state}`);
|
|
226
|
+
}
|
|
227
|
+
await assertPolicy(host, "process_signal", record.command, record.args, record.workspace, record.owner);
|
|
228
|
+
if (record.pty) {
|
|
229
|
+
await record.pty.signal(name);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (record.backend) {
|
|
233
|
+
await record.backend.signal(name);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!record.pid) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "process has no pid");
|
|
237
|
+
try {
|
|
238
|
+
process.kill(-record.pid, name);
|
|
239
|
+
} catch {
|
|
240
|
+
try {
|
|
241
|
+
process.kill(record.pid, name);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", error instanceof Error ? error.message : String(error));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
async kill() {
|
|
248
|
+
assertAttached();
|
|
249
|
+
sweepExpired(host);
|
|
250
|
+
await checkSandboxAlive(host);
|
|
251
|
+
if (record.state !== "running" && record.state !== "starting") {
|
|
252
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot kill in state ${record.state}`);
|
|
253
|
+
}
|
|
254
|
+
await assertPolicy(host, "process_kill", record.command, record.args, record.workspace, record.owner);
|
|
255
|
+
const ptyHandle = record.pty;
|
|
256
|
+
const backendHandle = record.backend;
|
|
257
|
+
// Mark terminal synchronously FIRST: a backend kill may resolve the
|
|
258
|
+
// wait() promise, whose handler must not race into a fabricated
|
|
259
|
+
// 'exited' before the kill is recorded. The explicit call below is
|
|
260
|
+
// the existing parity double-kill; its result is ignored.
|
|
261
|
+
if (ptyHandle || backendHandle) {
|
|
262
|
+
terminateRecord(host, record, "killed", null);
|
|
263
|
+
if (ptyHandle) {
|
|
264
|
+
try {
|
|
265
|
+
await ptyHandle.kill();
|
|
266
|
+
} catch {
|
|
267
|
+
// still marked killed
|
|
268
|
+
}
|
|
269
|
+
} else if (backendHandle) {
|
|
270
|
+
try {
|
|
271
|
+
await backendHandle.kill();
|
|
272
|
+
} catch {
|
|
273
|
+
// still marked killed
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
terminateRecord(host, record, "killed", null);
|
|
279
|
+
},
|
|
280
|
+
async release() {
|
|
281
|
+
assertAttached();
|
|
282
|
+
sweepExpired(host);
|
|
283
|
+
if (record.state !== "running" && record.state !== "starting") {
|
|
284
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot release in state ${record.state}`);
|
|
285
|
+
}
|
|
286
|
+
const ptyRelease = record.pty;
|
|
287
|
+
const backendRelease = record.backend;
|
|
288
|
+
if (ptyRelease || backendRelease) {
|
|
289
|
+
terminateRecord(host, record, "released", null);
|
|
290
|
+
if (ptyRelease) {
|
|
291
|
+
try {
|
|
292
|
+
await ptyRelease.release();
|
|
293
|
+
} catch {
|
|
294
|
+
// still marked released
|
|
295
|
+
}
|
|
296
|
+
} else if (backendRelease) {
|
|
297
|
+
try {
|
|
298
|
+
await backendRelease.release();
|
|
299
|
+
} catch {
|
|
300
|
+
// still marked released
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
terminateRecord(host, record, "released", null);
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
if (host.ptyResizeCapable) {
|
|
309
|
+
handle.resize = async (dimensions) => {
|
|
310
|
+
assertAttached();
|
|
311
|
+
sweepExpired(host);
|
|
312
|
+
await checkSandboxAlive(host);
|
|
313
|
+
if (record.state !== "running") {
|
|
314
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot resize in state ${record.state}`);
|
|
315
|
+
}
|
|
316
|
+
const columns = dimensions.columns;
|
|
317
|
+
const rows = dimensions.rows;
|
|
318
|
+
if (!Number.isSafeInteger(columns) || columns < 1 || columns > host.limits.maxTerminalColumns) {
|
|
319
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `columns must be 1..${host.limits.maxTerminalColumns}`);
|
|
320
|
+
}
|
|
321
|
+
if (!Number.isSafeInteger(rows) || rows < 1 || rows > host.limits.maxTerminalRows) {
|
|
322
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `rows must be 1..${host.limits.maxTerminalRows}`);
|
|
323
|
+
}
|
|
324
|
+
const pty = record.pty;
|
|
325
|
+
if (!pty || typeof pty.resize !== "function") {
|
|
326
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session does not support resize");
|
|
327
|
+
}
|
|
328
|
+
const terminal = record.ptyTerminal;
|
|
329
|
+
if (!terminal) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session has no terminal geometry");
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
record.ptyResizeAt = record.ptyResizeAt.filter((t) => now - t < 60_000);
|
|
332
|
+
if (record.ptyResizeAt.length >= host.limits.maxTerminalResizesPerMinute) {
|
|
333
|
+
throw new ProcessSessionError(
|
|
334
|
+
"ERR_PRISM_PROCESS_PTY_LIMIT",
|
|
335
|
+
`resize rate exceeds maxTerminalResizesPerMinute (${host.limits.maxTerminalResizesPerMinute})`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
await assertPolicy(host, "process_resize", record.command, record.args, record.workspace, record.owner);
|
|
339
|
+
record.ptyResizeAt.push(now);
|
|
340
|
+
try {
|
|
341
|
+
await pty.resize({ columns, rows });
|
|
342
|
+
} catch {
|
|
343
|
+
terminateRecord(host, record, "unknown", null);
|
|
344
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_BACKEND", "PTY backend resize failed");
|
|
345
|
+
}
|
|
346
|
+
record.ptyTerminal = { ...terminal, columns, rows };
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return handle;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export async function startSession(host: SessionsHost, request: ProcessStartRequest): Promise<ProcessSession> {
|
|
353
|
+
assertNotDisposed(host);
|
|
354
|
+
sweepExpired(host);
|
|
355
|
+
await checkSandboxAlive(host);
|
|
356
|
+
|
|
357
|
+
if (request.pty && (!host.ptyBackend || typeof host.ptyBackend.startPty !== "function")) {
|
|
358
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_UNSUPPORTED", "PTY not supported on this host");
|
|
359
|
+
}
|
|
360
|
+
if (!request.command || typeof request.command !== "string") {
|
|
361
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "command required");
|
|
362
|
+
}
|
|
363
|
+
const ptyTerminal = request.pty ? resolveTerminal(host, request.terminal) : undefined;
|
|
364
|
+
|
|
365
|
+
if (host.sandbox && typeof host.sandbox.startProcess !== "function") {
|
|
366
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", "sandbox adapter does not support startProcess");
|
|
367
|
+
}
|
|
368
|
+
if (host.sandboxLost) {
|
|
369
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", "sandbox lost; cannot start process");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const active = [...host.sessions.values()].filter((s) => s.state === "running" || s.state === "starting").length;
|
|
373
|
+
if (active >= host.limits.maxSessions) {
|
|
374
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `maxSessions (${host.limits.maxSessions}) reached`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const cwdInput = request.cwd ?? ".";
|
|
378
|
+
const cwd = resolveToCwd(cwdInput, host.workspace);
|
|
379
|
+
if (!isInsideRoot(host.workspace, cwd)) {
|
|
380
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "cwd escapes workspace root");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const args = request.args ?? [];
|
|
384
|
+
const owner = request.owner ?? host.defaultOwner;
|
|
385
|
+
const lifetimeMs = request.lifetimeMs ?? host.limits.maxLifetimeMs;
|
|
386
|
+
if (lifetimeMs > host.limits.maxLifetimeMs) {
|
|
387
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `lifetimeMs exceeds maxLifetimeMs (${host.limits.maxLifetimeMs})`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const policyDecision = await assertPolicy(host, "process_start", request.command, args, cwd, owner);
|
|
391
|
+
|
|
392
|
+
const id = `proc_${randomBytes(8).toString("hex")}`;
|
|
393
|
+
const accumulator = new OutputAccumulator({
|
|
394
|
+
maxBytes: host.limits.maxOutputChunkBytes,
|
|
395
|
+
maxLines: 100_000,
|
|
396
|
+
maxTotalOutputBytes: host.limits.maxTotalOutputBytes,
|
|
397
|
+
tempFilePrefix: "prism-proc",
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const record: SessionRecord = {
|
|
401
|
+
id,
|
|
402
|
+
owner,
|
|
403
|
+
workspace: host.workspace,
|
|
404
|
+
command: request.command,
|
|
405
|
+
args,
|
|
406
|
+
commandFingerprint: commandFingerprint(request.command, args),
|
|
407
|
+
policyDecision,
|
|
408
|
+
startedAt: nowIso(),
|
|
409
|
+
releaseOnCancel: request.releaseOnCancel === true,
|
|
410
|
+
expiresAt: Date.now() + lifetimeMs,
|
|
411
|
+
state: "starting",
|
|
412
|
+
exitCode: null,
|
|
413
|
+
ptyTerminal,
|
|
414
|
+
ptyResizeAt: [],
|
|
415
|
+
accumulator,
|
|
416
|
+
waiters: [],
|
|
417
|
+
outputListeners: new Set(),
|
|
418
|
+
stdinClosed: false,
|
|
419
|
+
handle: null as unknown as ProcessSession,
|
|
420
|
+
recoveryFencingToken: 0,
|
|
421
|
+
recoveryVersion: 0,
|
|
422
|
+
};
|
|
423
|
+
record.handle = makeHandle(host, record);
|
|
424
|
+
host.sessions.set(id, record);
|
|
425
|
+
|
|
426
|
+
// Durable recovery: intent is persisted BEFORE spawn. The per-record lease
|
|
427
|
+
// fences replica coordination; the fencing token is stored in the record
|
|
428
|
+
// so every later CAS write is monotonic. On any write/fence failure the
|
|
429
|
+
// start fails closed (record removed, no half-durable process).
|
|
430
|
+
let recoveryLease: { token: string } | undefined;
|
|
431
|
+
if (host.durable) {
|
|
432
|
+
const seams = durableSeams(host);
|
|
433
|
+
const lease = await acquireRecordLease({
|
|
434
|
+
leases: seams.leases,
|
|
435
|
+
id,
|
|
436
|
+
ownerId: seams.ownerId,
|
|
437
|
+
ttlMs: host.recoveryLimits.leaseTtlMs,
|
|
438
|
+
ownership: host.ownership,
|
|
439
|
+
signal: request.signal,
|
|
440
|
+
});
|
|
441
|
+
if (!lease) {
|
|
442
|
+
host.sessions.delete(id);
|
|
443
|
+
throw new ProcessRecoveryError("ERR_PRISM_RECOVERY_FENCE", "recovery lease for new session is held by another replica");
|
|
444
|
+
}
|
|
445
|
+
recoveryLease = { token: lease.token };
|
|
446
|
+
record.recoveryLeaseToken = lease.token;
|
|
447
|
+
record.recoveryFencingToken = lease.fencingToken;
|
|
448
|
+
const intent = buildProcessRecoveryRecord({
|
|
449
|
+
id,
|
|
450
|
+
owner,
|
|
451
|
+
workspace: host.workspace,
|
|
452
|
+
command: request.command,
|
|
453
|
+
args,
|
|
454
|
+
commandFingerprint: commandFingerprint(request.command, args),
|
|
455
|
+
policyDecision,
|
|
456
|
+
startedAt: record.startedAt,
|
|
457
|
+
state: "starting",
|
|
458
|
+
exitCode: null,
|
|
459
|
+
releaseOnCancel: record.releaseOnCancel,
|
|
460
|
+
expiresAt: record.expiresAt,
|
|
461
|
+
...(ptyTerminal !== undefined ? { pty: ptyTerminal } : {}),
|
|
462
|
+
fencingToken: lease.fencingToken,
|
|
463
|
+
});
|
|
464
|
+
const saved = await saveProcessRecoveryRecord({
|
|
465
|
+
checkpoints: seams.checkpoints,
|
|
466
|
+
record: intent,
|
|
467
|
+
expectedVersion: 0,
|
|
468
|
+
version: 1,
|
|
469
|
+
ownership: host.ownership,
|
|
470
|
+
signal: request.signal,
|
|
471
|
+
});
|
|
472
|
+
record.recoveryVersion = saved.version;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const onData = (buf: Buffer) => {
|
|
476
|
+
// PTY hosts can deliver trailing bytes after the session already went
|
|
477
|
+
// terminal (host wait() resolving ahead of the last pty drain); ignore
|
|
478
|
+
// them instead of appending to a finished accumulator.
|
|
479
|
+
if (record.state === "running" || record.state === "starting") {
|
|
480
|
+
accumulator.append(buf);
|
|
481
|
+
notifyOutput(record);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
if (request.pty) {
|
|
487
|
+
const startPty = host.ptyBackend?.startPty;
|
|
488
|
+
if (!startPty || !ptyTerminal) {
|
|
489
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_UNSUPPORTED", "PTY not supported on this host");
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
const handle = await withPtyAttachTimeout(
|
|
493
|
+
startPty({
|
|
494
|
+
file: request.command,
|
|
495
|
+
args,
|
|
496
|
+
cwd,
|
|
497
|
+
env: request.env,
|
|
498
|
+
columns: ptyTerminal.columns,
|
|
499
|
+
rows: ptyTerminal.rows,
|
|
500
|
+
term: ptyTerminal.term,
|
|
501
|
+
onData,
|
|
502
|
+
}),
|
|
503
|
+
host.limits.maxPtyAttachTimeoutMs,
|
|
504
|
+
);
|
|
505
|
+
const metadataJson = handle.metadata !== undefined ? JSON.stringify(handle.metadata) : undefined;
|
|
506
|
+
if (metadataJson !== undefined && Buffer.byteLength(metadataJson, "utf8") > host.limits.maxPtyBackendMetadataBytes) {
|
|
507
|
+
throw new ProcessSessionError(
|
|
508
|
+
"ERR_PRISM_PROCESS_PTY_LIMIT",
|
|
509
|
+
`pty backend metadata exceeds maxPtyBackendMetadataBytes (${host.limits.maxPtyBackendMetadataBytes})`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (handle.ref !== undefined) record.backendRef = validateBackendRef(handle.ref, host.recoveryLimits);
|
|
513
|
+
record.pty = handle;
|
|
514
|
+
record.state = "running";
|
|
515
|
+
persistTransition(host, record);
|
|
516
|
+
void handle
|
|
517
|
+
.wait()
|
|
518
|
+
.then((result) => {
|
|
519
|
+
if (record.state !== "running" && record.state !== "starting") return;
|
|
520
|
+
terminateRecord(host, record, "exited", result.exitCode);
|
|
521
|
+
})
|
|
522
|
+
.catch(() => {
|
|
523
|
+
if (record.state !== "running" && record.state !== "starting") return;
|
|
524
|
+
terminateRecord(host, record, "unknown", null);
|
|
525
|
+
});
|
|
526
|
+
} catch (error) {
|
|
527
|
+
host.sessions.delete(id);
|
|
528
|
+
if (error instanceof ProcessSessionError) throw error;
|
|
529
|
+
if (error instanceof ProcessRecoveryError) throw error;
|
|
530
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_BACKEND", "PTY backend failed to start");
|
|
531
|
+
}
|
|
532
|
+
} else if (host.sandbox?.startProcess) {
|
|
533
|
+
const handle = await host.sandbox.startProcess({
|
|
534
|
+
file: request.command,
|
|
535
|
+
args,
|
|
536
|
+
cwd,
|
|
537
|
+
env: request.env,
|
|
538
|
+
onData,
|
|
539
|
+
});
|
|
540
|
+
if (handle.ref !== undefined) record.backendRef = validateBackendRef(handle.ref, host.recoveryLimits);
|
|
541
|
+
record.backend = handle;
|
|
542
|
+
record.state = "running";
|
|
543
|
+
persistTransition(host, record);
|
|
544
|
+
void handle
|
|
545
|
+
.wait()
|
|
546
|
+
.then((result) => {
|
|
547
|
+
if (record.state !== "running" && record.state !== "starting") return;
|
|
548
|
+
terminateRecord(host, record, "exited", result.exitCode);
|
|
549
|
+
})
|
|
550
|
+
.catch(() => {
|
|
551
|
+
if (record.state !== "running" && record.state !== "starting") return;
|
|
552
|
+
terminateRecord(host, record, "unknown", null);
|
|
553
|
+
});
|
|
554
|
+
} else {
|
|
555
|
+
const env = buildChildEnv({ inherit: DEFAULT_CHILD_ENV_INHERIT, set: request.env }); // allow-list; never pass process.env through
|
|
556
|
+
const child = spawn(request.command, [...args], {
|
|
557
|
+
cwd,
|
|
558
|
+
env,
|
|
559
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
560
|
+
detached: process.platform !== "win32",
|
|
561
|
+
windowsHide: true,
|
|
562
|
+
}) as ChildProcessWithoutNullStreams;
|
|
563
|
+
record.child = child;
|
|
564
|
+
record.pid = child.pid;
|
|
565
|
+
record.state = "running";
|
|
566
|
+
child.stdout.on("data", onData);
|
|
567
|
+
child.stderr.on("data", onData);
|
|
568
|
+
child.stdin.on("close", () => {
|
|
569
|
+
record.stdinClosed = true;
|
|
570
|
+
});
|
|
571
|
+
child.on("error", () => {
|
|
572
|
+
terminateRecord(host, record, "unknown", null);
|
|
573
|
+
});
|
|
574
|
+
child.on("exit", (code, signal) => {
|
|
575
|
+
if (record.state !== "running" && record.state !== "starting") return;
|
|
576
|
+
if (signal) terminateRecord(host, record, "killed", code);
|
|
577
|
+
else terminateRecord(host, record, "exited", code);
|
|
578
|
+
});
|
|
579
|
+
persistTransition(host, record);
|
|
580
|
+
}
|
|
581
|
+
} catch (error) {
|
|
582
|
+
host.sessions.delete(id);
|
|
583
|
+
if (host.durable) {
|
|
584
|
+
// No half-durable process: drop the intent/running record and release
|
|
585
|
+
// the recovery lease. The host store is authoritative.
|
|
586
|
+
const seams = durableSeams(host);
|
|
587
|
+
await deleteProcessRecoveryRecord({ checkpoints: seams.checkpoints, id, ownership: host.ownership, signal: request.signal });
|
|
588
|
+
if (recoveryLease) {
|
|
589
|
+
await releaseRecordLease({
|
|
590
|
+
leases: seams.leases,
|
|
591
|
+
id,
|
|
592
|
+
ownerId: seams.ownerId,
|
|
593
|
+
token: recoveryLease.token,
|
|
594
|
+
ownership: host.ownership,
|
|
595
|
+
signal: request.signal,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (error instanceof ProcessSessionError) throw error;
|
|
600
|
+
if (error instanceof ProcessRecoveryError) throw error;
|
|
601
|
+
throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", error instanceof Error ? error.message : String(error));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
emit(host, {
|
|
605
|
+
type: "process_started",
|
|
606
|
+
sessionId: id,
|
|
607
|
+
processId: record.pid !== undefined ? String(record.pid) : id,
|
|
608
|
+
owner,
|
|
609
|
+
at: record.startedAt,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
return record.handle;
|
|
613
|
+
}
|