@elie-laloum/outpost 1.1.2 → 1.1.4

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +19 -191
  3. package/dist/cli/scaffold.constants.js +1 -0
  4. package/dist/cli/scaffold.constants.js.map +1 -1
  5. package/dist/infrastructure/binary-process.d.ts +3 -0
  6. package/dist/infrastructure/binary-process.js +53 -0
  7. package/dist/infrastructure/binary-process.js.map +1 -0
  8. package/dist/infrastructure/binary-process.types.d.ts +5 -0
  9. package/dist/infrastructure/binary-process.types.js +2 -0
  10. package/dist/infrastructure/binary-process.types.js.map +1 -0
  11. package/dist/infrastructure/transfer-copy.d.ts +3 -0
  12. package/dist/infrastructure/transfer-copy.js +51 -0
  13. package/dist/infrastructure/transfer-copy.js.map +1 -0
  14. package/dist/providers/container-archive.d.ts +3 -0
  15. package/dist/providers/container-archive.js +27 -0
  16. package/dist/providers/container-archive.js.map +1 -0
  17. package/dist/providers/container-command.js +3 -3
  18. package/dist/providers/container-command.js.map +1 -1
  19. package/dist/providers/container-files.d.ts +2 -1
  20. package/dist/providers/container-files.js +100 -11
  21. package/dist/providers/container-files.js.map +1 -1
  22. package/dist/providers/container.js +11 -2
  23. package/dist/providers/container.js.map +1 -1
  24. package/package.json +9 -6
  25. package/README.fr.md +0 -251
  26. package/ROADMAP.md +0 -34
  27. package/docs/api.md +0 -147
  28. package/docs/architecture.fr.md +0 -37
  29. package/docs/architecture.md +0 -57
  30. package/docs/migration-1.1.fr.md +0 -44
  31. package/docs/migration-1.1.md +0 -38
  32. package/docs/operations.md +0 -101
  33. package/docs/providers.md +0 -119
  34. package/docs/workflows.md +0 -101
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.4
4
+
5
+ - Wait for noninteractive container commands and preserve their real exit status; reuse the runtime terminal for interactive commands.
6
+ - Stream binary archives through the running Docker/Podman container so uploads and conversation capture/restore see the same tmpfs home as agents.
7
+ - Preserve ordinary file modes and links, reject unsafe download destinations, and support GNU tar and bsdtar hosts.
8
+ - Create a private writable agent home in generated images, including when used without the sandbox tmpfs mount.
9
+ - Add real container, pseudo-terminal, cancellation, binary integrity and native conversation regression checks.
10
+ - Add English/French account-connection guides for Claude Code and Codex, plus seven progressive cookbooks.
11
+
12
+ ## 1.1.3
13
+
14
+ - Replace the flat documentation with an Astro Starlight site in English and French, with English as the default language.
15
+ - Organize usage into focused guides and generate checked reference pages for every public API export and supporting contract.
16
+ - Validate translations, TypeScript examples, rendered links, navigation and search assets in CI.
17
+ - Deploy one GitHub Pages site after successful stable releases, protecting it from older release deployments.
18
+ - Move the French getting-started content and roadmap into the documentation, retain the root changelog and remove obsolete migration and contribution files.
19
+ - Keep documentation tooling out of the published library archive.
20
+
3
21
  ## 1.1.2
4
22
 
5
23
  - Match npm package repository metadata to the GitHub build origin for provenance verification. GitLab remains the canonical source repository.
package/README.md CHANGED
@@ -1,226 +1,54 @@
1
1
  # Outpost
2
2
 
3
- [Français](README.fr.md) · [API](docs/api.md) · [Providers](docs/providers.md) · [Workflows](docs/workflows.md) · [Operations](docs/operations.md) · [Roadmap](ROADMAP.md)
3
+ [Documentation](https://elie-laloum.github.io/outpost/) · [Français](https://elie-laloum.github.io/outpost/fr/) · [API](https://elie-laloum.github.io/outpost/reference/) · [Changelog](CHANGELOG.md)
4
4
 
5
- Outpost is a TypeScript library for running coding agents in reusable sandboxes, managing their Git workspaces and composing typed workflows. V1 supports **Codex** and **Claude Code**, with Docker, Podman, Vercel, Daytona and explicit host execution. Agent adapters and sandbox providers are separate extension points.
5
+ Outpost is a TypeScript library for running coding agents in reusable sandboxes, managing their Git workspaces and composing typed workflows. Claude Code and Codex adapters work with Docker, Podman, Vercel, Daytona or explicit host execution.
6
6
 
7
- The source repository is [GitLab](https://gitlab.elielaloum.com/elielaloum/outpost). [GitHub](https://github.com/elie-laloum/outpost) is its push mirror and runs all CI and package releases.
7
+ ## Get started
8
8
 
9
- ## Requirements
10
-
11
- - Node.js **24 or later**, Git, and an existing repository with at least one commit.
12
- - Docker or Podman for local isolation; the appropriate account and optional SDK for a cloud sandbox.
13
- - An authenticated coding agent or its API key. Git hosting credentials are not model credentials.
14
- - ESM imports. TypeScript declarations ship with the package; Node can execute the generated `.ts`/`.mts` scripts directly.
15
-
16
- ## Install and run
17
-
18
- Build from the source checkout:
19
-
20
- ```sh
21
- npm ci
22
- npm run build
23
- npm pack
24
- ```
25
-
26
- Install the resulting tarball in your target repository, or use the package from [GitHub Packages](https://github.com/elie-laloum/outpost/packages). Registry configuration is documented in [Operations](docs/operations.md).
9
+ Requires Node.js **24+**, Git, an existing repository with a commit, and the credentials of your chosen agent. This example uses Docker.
27
10
 
28
11
  ```sh
29
- npm install --save-dev /path/to/elie-laloum-outpost-1.1.2.tgz
12
+ npm install --save-dev @elie-laloum/outpost
30
13
  npx outpost init --yes --agent codex --provider docker --template blank --build
31
14
  ```
32
15
 
33
- Copy `.outpost/.env.example` to `.outpost/.env`. Set `OPENAI_API_KEY` there, or leave its declaration empty to inherit the process value. For Claude, select `--agent claude` and declare `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`. Only declared process variables are imported into isolated sandboxes. Run the generated script:
16
+ Copy `.outpost/.env.example` to `.outpost/.env` and declare `OPENAI_API_KEY`. An empty declaration inherits the matching process variable. Then run the generated script:
34
17
 
35
18
  ```sh
36
- node .outpost/run.mts "Add validation and tests for the configuration loader"
19
+ node .outpost/run.mts "Add validation, run tests and commit the change"
37
20
  ```
38
21
 
39
- Initialization uses `run.ts` when the host package declares `"type": "module"`, and `run.mts` otherwise. It never overwrites an existing scaffold. `--install` installs the library and optional provider SDK with the detected package manager. Omit `--build` to build the image later with `npx outpost image build`.
22
+ Use `run.ts` for a project with `"type": "module"`; initialization prints the exact command.
40
23
 
41
- ## One dispatch
24
+ ## Use the library
42
25
 
43
26
  ```ts
44
27
  import { dispatch, codex } from "@elie-laloum/outpost";
45
- import { docker } from "@elie-laloum/outpost/providers/docker";
46
28
 
47
29
  const result = await dispatch({
48
30
  agent: codex(),
49
- provider: docker(),
50
31
  branch: { mode: "integrate" },
51
- brief: { text: "Fix the failing tests, verify the result and commit it." },
52
- });
53
-
54
- console.log(result.branch, result.commits, result.conversation);
55
- ```
56
-
57
- `dispatch` provisions a sandbox, executes the agent, collects changes and native conversation files, integrates commits when requested, and closes owned resources. Uncommitted work is retained. Failure paths preserve separate workspaces for inspection.
58
-
59
- | Branch policy | Behavior |
60
- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
61
- | `{ mode: "current" }` | Uses the current checkout. This is the mounted/host default. |
62
- | `{ mode: "named", name: "feature/validation", from: "main" }` | Creates or reuses a managed worktree for that branch. |
63
- | `{ mode: "integrate", from: "main" }` | Creates a temporary branch and merges its commits into the original host branch. |
64
-
65
- Remote providers require `named` or `integrate`. `from` is optional. Integration refuses a changed host branch and retains the workspace on conflict. A branch checked out elsewhere produces an actionable error.
66
-
67
- ## Keep a sandbox warm
68
-
69
- ```ts
70
- import { createSandbox, claude } from "@elie-laloum/outpost";
71
-
72
- await using sandbox = await createSandbox({
73
- agent: claude({ model: "sonnet", reasoning: "high" }),
74
- branch: { mode: "named", name: "feature/settings" },
75
- });
76
-
77
- const first = await sandbox.dispatch({
78
- brief: { text: "Implement settings validation." },
79
- });
80
- const tests = await sandbox.command({ executable: "npm", arguments: ["test"] });
81
- if (tests.status !== 0)
82
- await first.resume({ brief: { text: "Fix the failing tests." } });
83
- await sandbox.attach();
84
- ```
85
-
86
- Commands return nonzero statuses; agent dispatches throw on agent failure. `AbortSignal`, command deadlines and idle watchdogs stop the current operation while leaving a warm sandbox usable. A sandbox accepts one active operation at a time. Use separate sandboxes for parallel work.
87
-
88
- The default agent is optional for a warm sandbox. Pass `agent` to individual `sandbox.dispatch` or `sandbox.attach` calls to switch between Codex and Claude, or change models, without recreating the environment. Each job receives only its selected adapter's variables.
89
-
90
- ## Own the workspace separately
91
-
92
- ```ts
93
- import { openWorkspace, codex, claude } from "@elie-laloum/outpost";
94
-
95
- await using workspace = await openWorkspace({
96
- branch: { mode: "named", name: "feature/shared-work" },
97
- copies: [".env.test"],
98
- });
99
-
100
- await workspace.dispatch({
101
- agent: codex(),
102
- brief: { text: "Implement the feature and commit." },
103
- });
104
- await workspace.dispatch({
105
- agent: claude(),
106
- brief: { text: "Review the implementation, test and commit fixes." },
107
- });
108
- ```
109
-
110
- A supplied workspace outlives each sandbox. Its owner closes it. Closing a dirty workspace returns `retainedDirectory`; closing a clean managed workspace removes the worktree. Named branches remain. `workspace.sandbox()` and `workspace.attach()` provide the other lifecycles. Warm integration workspaces can call `sandbox.workspace.integrate()` explicitly.
111
-
112
- ## Prompts and iteration
113
-
114
- Provide exactly one `brief`: literal `text`, or a `file` with optional primitive `values`. Files are reread each pass; inline text is never expanded.
115
-
116
- ```ts
117
- const result = await dispatch({
118
- agent: codex(),
119
- brief: { file: ".outpost/brief.md", values: { OBJECTIVE: "Fix validation" } },
120
- passes: 5,
121
- until: ["<outpost>done</outpost>"],
122
- idleMs: 600_000,
123
- settleMs: 60_000,
32
+ brief: { text: "Fix the failing tests, verify and commit." },
124
33
  });
34
+ console.log(result.branch, result.commits);
125
35
  ```
126
36
 
127
- Files support `{{OBJECTIVE}}`, reserved `{{WORK_BRANCH}}` and `{{BASE_BRANCH}}`, and shell expansion such as `` !`git status --short` ``. Original file commands run concurrently inside the sandbox after setup hooks. Substituted text cannot introduce additional commands. Values inserted into an existing command are shell input: only interpolate trusted values there. Missing variables fail; unused variables warn through `warn`.
128
-
129
- Completion markers stop the loop. If the process hangs after a marker, the completion grace period resets on subsequent output and then stops that command. Every turn includes its duration, native transcript path when available and raw token counts (input, cache read, cache creation, output); the result includes aggregate usage and the matched completion marker.
37
+ Learn about [sandboxes](https://elie-laloum.github.io/outpost/sandboxes/lifecycle/), [workflows](https://elie-laloum.github.io/outpost/workflows/graph/), [providers](https://elie-laloum.github.io/outpost/providers/overview/) and [recovery](https://elie-laloum.github.io/outpost/operations/recovery/) in the English/French documentation. The [roadmap](https://elie-laloum.github.io/outpost/project/roadmap/) describes future work.
130
38
 
131
- ## Structured responses and conversations
132
-
133
- ```ts
134
- import { response } from "@elie-laloum/outpost";
135
-
136
- const result = await dispatch({
137
- agent: codex(),
138
- brief: {
139
- text: "Analyze the repository. Return <report>JSON with an ok boolean</report>.",
140
- },
141
- response: response.json({
142
- tag: "report",
143
- repairs: 2,
144
- schema(input) {
145
- if (
146
- !input ||
147
- typeof input !== "object" ||
148
- !("ok" in input) ||
149
- typeof input.ok !== "boolean"
150
- ) {
151
- throw new Error("Expected an ok boolean");
152
- }
153
- return { ok: input.ok };
154
- },
155
- }),
156
- });
157
-
158
- console.log(result.value.ok);
159
- await result.resume({ brief: { text: "Explain the result." } });
160
- await result.fork({ brief: { text: "Explore a different solution." } });
161
- ```
162
-
163
- `response.text` extracts tagged text. `response.json` accepts a function or a Standard Schema validator, including asynchronous validation. Structured responses and conversation continuations require one pass. Repairs resume the same conversation. `ResponseError.recovery` exposes the conversation, commits and recovery paths. `recoveryDetails(error)` also retrieves recovery metadata for other failures without replacing cancellation reasons.
164
-
165
- Native conversation capture is enabled by default. Transcripts are saved to the host agent's own storage and working-directory fields are rewritten for host resumption. Claude child transcripts are copied on a best-effort basis. Set `saveConversations: false` on an adapter to opt out. Forks create a new conversation; use a separate workspace when filesystem isolation is also required.
166
-
167
- Each cold `dispatch({ passes })` provisions a fresh sandbox per pass. `sandbox.dispatch({ passes })` deliberately reuses its environment. Relative brief filenames resolve from the caller’s working directory. Cold result `resume`/`fork` accepts new branch/provider/hook settings; warm result methods retain the sandbox’s settings. See [1.1 migration notes](docs/migration-1.1.md).
168
-
169
- ## Typed workflows
170
-
171
- ```ts
172
- import { task, workflow } from "@elie-laloum/outpost";
173
-
174
- const inspect = task({
175
- key: "inspect",
176
- perform: async () => ({ ready: true }),
177
- });
178
- const implement = task({
179
- key: "implement",
180
- after: [inspect],
181
- retry: { attempts: 2, delayMs: 500 },
182
- perform: async (context) => context.value(inspect).ready,
183
- });
184
- const result = await workflow("delivery", [inspect, implement]).start({
185
- concurrency: 2,
186
- });
187
- result.unwrap();
188
- console.log(result.value(implement));
189
- ```
190
-
191
- The graph validates dependencies and cycles before running. Results are typed and scoped to one execution. Conditions, cooperative cancellation, retries, concurrency limits, observer events and Mermaid diagrams are built in. `agentTask`, `commandTask` and `isolatedTask` connect workflows to sandbox operations. [Workflow guide](docs/workflows.md).
192
-
193
- ## Issue campaigns
194
-
195
- `campaign({ agent, provider, backlog })` loads a backlog, validates a typed plan, allocates a branch per issue and bounds parallel implementation. Review shares each issue sandbox. A merge phase validates the combined work; issue closure occurs only after integration into the host branch. The backlog is refreshed for the next cycle. GitHub and Beads connectors expose list, detail and close operations; custom trackers implement `Backlog`.
196
-
197
- [Campaign configuration and starter examples](docs/workflows.md#issue-campaigns).
198
-
199
- ## Configuration and extension
200
-
201
- Provider settings cover environment variables, file/directory mounts, networks, resource limits, devices, extra groups and SELinux labels. Hooks run at workspace readiness and after sandbox creation. The two post-creation hook groups run concurrently. Host commands run in order; sandbox commands run concurrently and cancel their siblings on failure. [Complete API](docs/api.md) and [provider reference](docs/providers.md).
202
-
203
- Outpost reads only `.outpost/.env`. Nonempty file values win; empty declarations inherit matching process values. The repository-root `.env` is not imported. Explicit provider and adapter variables override those values. The same variable cannot be declared by both provider and adapter. Logs default to `.outpost/logs`, with `false`, `"stdout"`, custom files and verbose modes available.
204
-
205
- Implement `AgentAdapter` to add an agent. Implement `SandboxProvider` directly or use `mountedProvider`/`remoteProvider` to add an execution backend. Domain contracts do not depend on a specific cloud SDK. [Architecture](docs/architecture.md).
206
-
207
- ## Verification and releases
39
+ ## Development
208
40
 
209
41
  ```sh
210
42
  npm ci
211
43
  npm run check
212
44
  npm run coverage
213
45
  npm run test:package
46
+ npm ci --prefix docs
47
+ npm run docs:sync
48
+ npm run docs:build
49
+ npm run docs:test
214
50
  ```
215
51
 
216
- GitHub Actions runs unit and functional tests on Linux, Windows and macOS, enforces 80% line/function/branch coverage, installs the packed package, and runs real Docker and Podman tests. Cloud contract tests use controlled SDK doubles; live cloud/model checks require the corresponding credentials. No GitLab CI pipeline is required.
217
-
218
- Version tags are created on GitLab and mirrored. The release workflow validates the tag, runs CI, publishes GitHub Packages and attaches the installable tarball to a GitHub release. Optional npm publication uses trusted publishing when enabled. [Release procedure and registry setup](docs/operations.md).
219
-
220
- ## Execution boundary
221
-
222
- Docker and Podman mount the selected worktree and Git metadata. Agents can edit that repository and its Git state. This protects unrelated host paths but is not a boundary against a hostile agent attacking the shared repository. Mount only what the task needs. `local()` runs directly on your machine without isolation. Cloud providers synchronize via Git bundles and file transfers; concurrent host changes cause a recovery error instead of being overwritten.
223
-
224
- Logs, conversation files and recovery files can contain private prompts or source. Runtime folders and `.env` are excluded from version control. See [Security](SECURITY.md) for the precise boundaries.
52
+ [GitLab](https://gitlab.elielaloum.com/elielaloum/outpost) is the canonical repository. [GitHub](https://github.com/elie-laloum/outpost) is the mirror running tests, package releases and GitHub Pages. Documentation is validated on main and deployed only after a successful stable release.
225
53
 
226
- MIT licensed. See [LICENSE](LICENSE).
54
+ See [SECURITY.md](SECURITY.md) for execution boundaries and [LICENSE](LICENSE) for the MIT license.
@@ -5,6 +5,7 @@ ARG AGENT_GID=1000
5
5
  RUN apt-get update && apt-get install -y --no-install-recommends git gh openssh-client ca-certificates curl procps util-linux python3 && rm -rf /var/lib/apt/lists/*
6
6
  RUN npm install -g --allow-scripts=@anthropic-ai/claude-code @openai/codex@${agentVersions.codex} @anthropic-ai/claude-code@${agentVersions.claude}
7
7
  RUN groupmod -o -g "$AGENT_GID" node && usermod -o -u "$AGENT_UID" -g "$AGENT_GID" node
8
+ RUN mkdir -p /home/agent && chown "$AGENT_UID:$AGENT_GID" /home/agent && chmod 700 /home/agent
8
9
  ENV HOME=/home/agent
9
10
  USER $AGENT_UID:$AGENT_GID
10
11
  WORKDIR /workspace
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.constants.js","sourceRoot":"","sources":["../../src/cli/scaffold.constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,MAAM,CAAC,MAAM,WAAW,GAAG;;;;6EAIkD,aAAa,CAAC,KAAK,8BAA8B,aAAa,CAAC,MAAM;;;;;CAKjJ,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;CACR,CAAC;AACF,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,OAAO;IACP,SAAS;IACT,QAAQ;IACR,MAAM;IACN,aAAa;CACd,CAAC;AACF,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,QAAQ;IACR,OAAO;IACP,QAAQ;CACT,CAAC;AACF,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;CACN,CAAC;AACF,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAC1B,CAAC,WAAW,EAAE,MAAM,CAAC;IACrB,CAAC,UAAU,EAAE,KAAK,CAAC;IACnB,CAAC,WAAW,EAAE,KAAK,CAAC;CACZ,CAAC;AACX,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,EAAE;IACV,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,CAAC,iBAAiB,CAAC;IAC3B,OAAO,EAAE,CAAC,cAAc,CAAC;CACjB,CAAC","sourcesContent":["import { agentVersions } from \"../providers/versions.ts\";\n\nexport const imageRecipe = `FROM node:24-bookworm-slim\nARG AGENT_UID=1000\nARG AGENT_GID=1000\nRUN apt-get update && apt-get install -y --no-install-recommends git gh openssh-client ca-certificates curl procps util-linux python3 && rm -rf /var/lib/apt/lists/*\nRUN npm install -g --allow-scripts=@anthropic-ai/claude-code @openai/codex@${agentVersions.codex} @anthropic-ai/claude-code@${agentVersions.claude}\nRUN groupmod -o -g \"$AGENT_GID\" node && usermod -o -u \"$AGENT_UID\" -g \"$AGENT_GID\" node\nENV HOME=/home/agent\nUSER $AGENT_UID:$AGENT_GID\nWORKDIR /workspace\n`;\n\nexport const supportedAgents: readonly string[] = [\"codex\", \"claude\"];\nexport const supportedProviders: readonly string[] = [\n \"docker\",\n \"podman\",\n \"vercel\",\n \"daytona\",\n \"local\",\n];\nexport const supportedTemplates: readonly string[] = [\n \"blank\",\n \"iterate\",\n \"review\",\n \"plan\",\n \"plan-review\",\n];\nexport const supportedTrackers: readonly string[] = [\n \"github\",\n \"beads\",\n \"custom\",\n];\nexport const supportedManagers: readonly string[] = [\n \"npm\",\n \"pnpm\",\n \"yarn\",\n \"bun\",\n];\nexport const managerLocks = [\n [\"pnpm-lock.yaml\", \"pnpm\"],\n [\"yarn.lock\", \"yarn\"],\n [\"bun.lock\", \"bun\"],\n [\"bun.lockb\", \"bun\"],\n] as const;\nexport const providerPackages = {\n docker: [],\n podman: [],\n local: [],\n vercel: [\"@vercel/sandbox\"],\n daytona: [\"@daytona/sdk\"],\n} as const;\n"]}
1
+ {"version":3,"file":"scaffold.constants.js","sourceRoot":"","sources":["../../src/cli/scaffold.constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,MAAM,CAAC,MAAM,WAAW,GAAG;;;;6EAIkD,aAAa,CAAC,KAAK,8BAA8B,aAAa,CAAC,MAAM;;;;;;CAMjJ,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;CACR,CAAC;AACF,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,OAAO;IACP,SAAS;IACT,QAAQ;IACR,MAAM;IACN,aAAa;CACd,CAAC;AACF,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,QAAQ;IACR,OAAO;IACP,QAAQ;CACT,CAAC;AACF,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;CACN,CAAC;AACF,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAC1B,CAAC,WAAW,EAAE,MAAM,CAAC;IACrB,CAAC,UAAU,EAAE,KAAK,CAAC;IACnB,CAAC,WAAW,EAAE,KAAK,CAAC;CACZ,CAAC;AACX,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,EAAE;IACV,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,CAAC,iBAAiB,CAAC;IAC3B,OAAO,EAAE,CAAC,cAAc,CAAC;CACjB,CAAC","sourcesContent":["import { agentVersions } from \"../providers/versions.ts\";\n\nexport const imageRecipe = `FROM node:24-bookworm-slim\nARG AGENT_UID=1000\nARG AGENT_GID=1000\nRUN apt-get update && apt-get install -y --no-install-recommends git gh openssh-client ca-certificates curl procps util-linux python3 && rm -rf /var/lib/apt/lists/*\nRUN npm install -g --allow-scripts=@anthropic-ai/claude-code @openai/codex@${agentVersions.codex} @anthropic-ai/claude-code@${agentVersions.claude}\nRUN groupmod -o -g \"$AGENT_GID\" node && usermod -o -u \"$AGENT_UID\" -g \"$AGENT_GID\" node\nRUN mkdir -p /home/agent && chown \"$AGENT_UID:$AGENT_GID\" /home/agent && chmod 700 /home/agent\nENV HOME=/home/agent\nUSER $AGENT_UID:$AGENT_GID\nWORKDIR /workspace\n`;\n\nexport const supportedAgents: readonly string[] = [\"codex\", \"claude\"];\nexport const supportedProviders: readonly string[] = [\n \"docker\",\n \"podman\",\n \"vercel\",\n \"daytona\",\n \"local\",\n];\nexport const supportedTemplates: readonly string[] = [\n \"blank\",\n \"iterate\",\n \"review\",\n \"plan\",\n \"plan-review\",\n];\nexport const supportedTrackers: readonly string[] = [\n \"github\",\n \"beads\",\n \"custom\",\n];\nexport const supportedManagers: readonly string[] = [\n \"npm\",\n \"pnpm\",\n \"yarn\",\n \"bun\",\n];\nexport const managerLocks = [\n [\"pnpm-lock.yaml\", \"pnpm\"],\n [\"yarn.lock\", \"yarn\"],\n [\"bun.lock\", \"bun\"],\n [\"bun.lockb\", \"bun\"],\n] as const;\nexport const providerPackages = {\n docker: [],\n podman: [],\n local: [],\n vercel: [\"@vercel/sandbox\"],\n daytona: [\"@daytona/sdk\"],\n} as const;\n"]}
@@ -0,0 +1,3 @@
1
+ import type { BinaryStreams } from "./binary-process.types.ts";
2
+ import type { Executor } from "./process.types.ts";
3
+ export declare function binaryExecutor(streams: BinaryStreams): Executor;
@@ -0,0 +1,53 @@
1
+ import { spawn } from "node:child_process";
2
+ import { pipeline } from "node:stream/promises";
3
+ import { OutpostError } from "../domain/errors.js";
4
+ import { processDefaults } from "./process.constants.js";
5
+ import { transfer } from "./transfer.js";
6
+ export function binaryExecutor(streams) {
7
+ return (command) => transfer(command, async (signal) => {
8
+ signal.throwIfAborted();
9
+ const child = spawn(command.executable, [...(command.arguments ?? [])], {
10
+ cwd: command.directory,
11
+ env: { ...process.env, ...command.variables },
12
+ windowsHide: true,
13
+ stdio: ["pipe", "pipe", "pipe"],
14
+ signal,
15
+ killSignal: "SIGKILL",
16
+ });
17
+ let stderr = "";
18
+ child.stderr.on("data", (data) => {
19
+ stderr = (stderr + data.toString("utf8")).slice(-processDefaults.retainBytes);
20
+ });
21
+ const completion = new Promise((resolve, reject) => {
22
+ child.on("error", reject);
23
+ child.on("close", (status) => {
24
+ if (status !== 0)
25
+ reject(new OutpostError("process", `${command.executable} exited with status ${status}`, { status, stderr }));
26
+ else
27
+ resolve({ status: 0, stdout: "", stderr });
28
+ });
29
+ });
30
+ const pending = [completion];
31
+ if (streams.input)
32
+ pending.push(pipeline(streams.input, child.stdin, { signal }));
33
+ else
34
+ child.stdin.end();
35
+ if (streams.output)
36
+ pending.push(pipeline(child.stdout, streams.output, { signal }));
37
+ else
38
+ child.stdout.resume();
39
+ try {
40
+ await Promise.all(pending);
41
+ return await completion;
42
+ }
43
+ catch (cause) {
44
+ child.kill("SIGKILL");
45
+ streams.input?.destroy();
46
+ streams.output?.destroy();
47
+ await Promise.allSettled(pending);
48
+ signal.throwIfAborted();
49
+ throw cause;
50
+ }
51
+ });
52
+ }
53
+ //# sourceMappingURL=binary-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-process.js","sourceRoot":"","sources":["../../src/infrastructure/binary-process.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { CommandResult } from \"../domain/command.types.ts\";\nimport { OutpostError } from \"../domain/errors.ts\";\nimport type { BinaryStreams } from \"./binary-process.types.ts\";\nimport { processDefaults } from \"./process.constants.ts\";\nimport type { Executor } from \"./process.types.ts\";\nimport { transfer } from \"./transfer.ts\";\n\nexport function binaryExecutor(streams: BinaryStreams): Executor {\n return (command) =>\n transfer(command, async (signal) => {\n signal.throwIfAborted();\n const child = spawn(command.executable, [...(command.arguments ?? [])], {\n cwd: command.directory,\n env: { ...process.env, ...command.variables },\n windowsHide: true,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n signal,\n killSignal: \"SIGKILL\",\n });\n let stderr = \"\";\n child.stderr.on(\"data\", (data: Buffer) => {\n stderr = (stderr + data.toString(\"utf8\")).slice(\n -processDefaults.retainBytes,\n );\n });\n const completion = new Promise<CommandResult>((resolve, reject) => {\n child.on(\"error\", reject);\n child.on(\"close\", (status) => {\n if (status !== 0)\n reject(\n new OutpostError(\n \"process\",\n `${command.executable} exited with status ${status}`,\n { status, stderr },\n ),\n );\n else resolve({ status: 0, stdout: \"\", stderr });\n });\n });\n const pending: Promise<unknown>[] = [completion];\n if (streams.input)\n pending.push(pipeline(streams.input, child.stdin, { signal }));\n else child.stdin.end();\n if (streams.output)\n pending.push(pipeline(child.stdout, streams.output, { signal }));\n else child.stdout.resume();\n try {\n await Promise.all(pending);\n return await completion;\n } catch (cause) {\n child.kill(\"SIGKILL\");\n streams.input?.destroy();\n streams.output?.destroy();\n await Promise.allSettled(pending);\n signal.throwIfAborted();\n throw cause;\n }\n });\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ export interface BinaryStreams {
3
+ readonly input?: Readable;
4
+ readonly output?: Writable;
5
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=binary-process.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-process.types.js","sourceRoot":"","sources":["../../src/infrastructure/binary-process.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Readable, Writable } from \"node:stream\";\n\nexport interface BinaryStreams {\n readonly input?: Readable;\n readonly output?: Writable;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare function transferDestination(destination: string): Promise<void>;
2
+ export declare function copyTransfer(source: string, destination: string, signal: AbortSignal): Promise<void>;
3
+ export declare function removeTransferDirectory(root: string): Promise<void>;
@@ -0,0 +1,51 @@
1
+ import { chmod, copyFile, rm, lstat, mkdir, readdir, readlink, symlink, } from "node:fs/promises";
2
+ import { dirname, join, parse, resolve } from "node:path";
3
+ import { OutpostError } from "../domain/errors.js";
4
+ export async function transferDestination(destination) {
5
+ const absolute = resolve(destination);
6
+ for (let path = absolute; path !== parse(path).root; path = dirname(path)) {
7
+ const info = await lstat(path).catch((cause) => {
8
+ if (cause.code === "ENOENT")
9
+ return undefined;
10
+ throw cause;
11
+ });
12
+ if (info?.isSymbolicLink())
13
+ throw new OutpostError("provider", "Transfer destination traverses a symlink", { destination });
14
+ }
15
+ }
16
+ export async function copyTransfer(source, destination, signal) {
17
+ signal.throwIfAborted();
18
+ await transferDestination(destination);
19
+ const info = await lstat(source);
20
+ if (info.isDirectory()) {
21
+ await mkdir(destination, { recursive: true });
22
+ for (const name of await readdir(source))
23
+ await copyTransfer(join(source, name), join(destination, name), signal);
24
+ await chmod(destination, info.mode & 0o777);
25
+ return;
26
+ }
27
+ await mkdir(dirname(destination), { recursive: true });
28
+ if (info.isSymbolicLink()) {
29
+ await symlink(await readlink(source), destination);
30
+ return;
31
+ }
32
+ if (!info.isFile())
33
+ throw new OutpostError("provider", "Unsupported transfer file type", {
34
+ source,
35
+ });
36
+ await copyFile(source, destination);
37
+ await chmod(destination, info.mode & 0o777);
38
+ }
39
+ export async function removeTransferDirectory(root) {
40
+ async function writable(path) {
41
+ const info = await lstat(path);
42
+ if (!info.isDirectory() || info.isSymbolicLink())
43
+ return;
44
+ await chmod(path, 0o700);
45
+ for (const name of await readdir(path))
46
+ await writable(join(path, name));
47
+ }
48
+ await writable(root);
49
+ await rm(root, { recursive: true, force: true });
50
+ }
51
+ //# sourceMappingURL=transfer-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-copy.js","sourceRoot":"","sources":["../../src/infrastructure/transfer-copy.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n chmod,\n copyFile,\n rm,\n lstat,\n mkdir,\n readdir,\n readlink,\n symlink,\n} from \"node:fs/promises\";\nimport { dirname, join, parse, resolve } from \"node:path\";\nimport { OutpostError } from \"../domain/errors.ts\";\n\nexport async function transferDestination(destination: string): Promise<void> {\n const absolute = resolve(destination);\n for (let path = absolute; path !== parse(path).root; path = dirname(path)) {\n const info = await lstat(path).catch((cause: NodeJS.ErrnoException) => {\n if (cause.code === \"ENOENT\") return undefined;\n throw cause;\n });\n if (info?.isSymbolicLink())\n throw new OutpostError(\n \"provider\",\n \"Transfer destination traverses a symlink\",\n { destination },\n );\n }\n}\n\nexport async function copyTransfer(\n source: string,\n destination: string,\n signal: AbortSignal,\n): Promise<void> {\n signal.throwIfAborted();\n await transferDestination(destination);\n const info = await lstat(source);\n if (info.isDirectory()) {\n await mkdir(destination, { recursive: true });\n for (const name of await readdir(source))\n await copyTransfer(join(source, name), join(destination, name), signal);\n await chmod(destination, info.mode & 0o777);\n return;\n }\n await mkdir(dirname(destination), { recursive: true });\n if (info.isSymbolicLink()) {\n await symlink(await readlink(source), destination);\n return;\n }\n if (!info.isFile())\n throw new OutpostError(\"provider\", \"Unsupported transfer file type\", {\n source,\n });\n await copyFile(source, destination);\n await chmod(destination, info.mode & 0o777);\n}\n\nexport async function removeTransferDirectory(root: string): Promise<void> {\n async function writable(path: string): Promise<void> {\n const info = await lstat(path);\n if (!info.isDirectory() || info.isSymbolicLink()) return;\n await chmod(path, 0o700);\n for (const name of await readdir(path)) await writable(join(path, name));\n }\n await writable(root);\n await rm(root, { recursive: true, force: true });\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "../domain/command.types.ts";
2
+ import type { ContainerRuntime } from "./container.types.ts";
3
+ export declare function containerArchive(runtime: ContainerRuntime, local: Command, remote: Command, upload: boolean, signal: AbortSignal): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { PassThrough } from "node:stream";
2
+ import { binaryExecutor } from "../infrastructure/binary-process.js";
3
+ import { containerCommand } from "./container-command.js";
4
+ export async function containerArchive(runtime, local, remote, upload, signal) {
5
+ const pipe = new PassThrough();
6
+ const stop = new AbortController();
7
+ const combined = AbortSignal.any([signal, stop.signal]);
8
+ const host = binaryExecutor(upload ? { output: pipe } : { input: pipe });
9
+ const guest = containerCommand({
10
+ ...runtime,
11
+ executor: binaryExecutor(upload ? { input: pipe } : { output: pipe }),
12
+ });
13
+ const pending = [
14
+ host({ ...local, signal: combined }),
15
+ guest({ ...remote, signal: combined }),
16
+ ];
17
+ try {
18
+ await Promise.all(pending);
19
+ }
20
+ catch (cause) {
21
+ stop.abort(cause);
22
+ pipe.destroy();
23
+ await Promise.allSettled(pending);
24
+ throw cause;
25
+ }
26
+ }
27
+ //# sourceMappingURL=container-archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-archive.js","sourceRoot":"","sources":["../../src/providers/container-archive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAyB,EACzB,KAAc,EACd,MAAe,EACf,MAAe,EACf,MAAmB;IAEnB,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,gBAAgB,CAAC;QAC7B,GAAG,OAAO;QACV,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;KACtE,CAAC,CAAC;IACH,MAAM,OAAO,GAAG;QACd,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACpC,KAAK,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACvC,CAAC;IACF,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { PassThrough } from \"node:stream\";\nimport type { Command } from \"../domain/command.types.ts\";\nimport { binaryExecutor } from \"../infrastructure/binary-process.ts\";\nimport { containerCommand } from \"./container-command.ts\";\nimport type { ContainerRuntime } from \"./container.types.ts\";\n\nexport async function containerArchive(\n runtime: ContainerRuntime,\n local: Command,\n remote: Command,\n upload: boolean,\n signal: AbortSignal,\n): Promise<void> {\n const pipe = new PassThrough();\n const stop = new AbortController();\n const combined = AbortSignal.any([signal, stop.signal]);\n const host = binaryExecutor(upload ? { output: pipe } : { input: pipe });\n const guest = containerCommand({\n ...runtime,\n executor: binaryExecutor(upload ? { input: pipe } : { output: pipe }),\n });\n const pending = [\n host({ ...local, signal: combined }),\n guest({ ...remote, signal: combined }),\n ];\n try {\n await Promise.all(pending);\n } catch (cause) {\n stop.abort(cause);\n pipe.destroy();\n await Promise.allSettled(pending);\n throw cause;\n }\n}\n"]}
@@ -37,9 +37,9 @@ export function containerCommand(runtime) {
37
37
  executable: engine,
38
38
  arguments: [
39
39
  ...flags,
40
- "setsid",
41
- ...(command.interactive ? ["--wait"] : []),
42
- ...(command.interactive && process.stdin.isTTY ? ["--ctty"] : []),
40
+ ...(command.interactive && process.stdin.isTTY
41
+ ? []
42
+ : ["setsid", "--wait"]),
43
43
  "sh",
44
44
  "-c",
45
45
  wrapper,
@@ -1 +1 @@
1
- {"version":3,"file":"container-command.js","sourceRoot":"","sources":["../../src/providers/container-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { invariant, OutpostError } from \"../domain/errors.ts\";\nimport type { SandboxLease } from \"../domain/sandbox.types.ts\";\nimport { quote } from \"../infrastructure/process.ts\";\nimport { containerDefaults } from \"./container.constants.ts\";\nimport type { ContainerRuntime } from \"./container.types.ts\";\n\nexport function containerCommand(\n runtime: ContainerRuntime,\n): SandboxLease[\"invoke\"] {\n const { engine, config, executor, root, name, env, call, isClosed } = runtime;\n return async (command) => {\n if (isClosed())\n throw new OutpostError(\"provider\", \"Container sandbox is closed\");\n command.signal?.throwIfAborted();\n const id = randomUUID();\n const pidFile = `/tmp/outpost-${id}.pid`;\n const variables = { ...env, ...command.variables };\n invariant(\n Object.keys(variables).every((key) =>\n /^[A-Za-z_][A-Za-z0-9_]*$/.test(key),\n ),\n \"Invalid environment key\",\n );\n const environment = Object.fromEntries(\n Object.entries(variables).map(([, value], index) => [\n `OUTPOST_VALUE_${index}`,\n value,\n ]),\n );\n const exports = Object.keys(variables)\n .map(\n (key, index) =>\n `export ${key}=\"$OUTPOST_VALUE_${index}\"; unset OUTPOST_VALUE_${index};`,\n )\n .join(\" \");\n const flags = [\n \"exec\",\n command.interactive && process.stdin.isTTY ? \"-it\" : \"-i\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n \"--workdir\",\n command.directory ?? root,\n ...Object.keys(environment).flatMap((key) => [\"--env\", key]),\n name,\n ];\n const wrapper = `${exports} echo $$ > ${quote(pidFile)}; test ! -f ${quote(pidFile + \".cancel\")} || exit 130; exec \"$@\"`;\n const { directory: _directory, ...invocation } = command;\n let interrupted = false;\n try {\n return await executor({\n ...invocation,\n executable: engine,\n arguments: [\n ...flags,\n \"setsid\",\n ...(command.interactive ? [\"--wait\"] : []),\n ...(command.interactive && process.stdin.isTTY ? [\"--ctty\"] : []),\n \"sh\",\n \"-c\",\n wrapper,\n \"outpost\",\n command.executable,\n ...(command.arguments ?? []),\n ],\n variables: environment,\n retain:\n command.retain ?? config.retain ?? containerDefaults.retainBytes,\n });\n } catch (cause) {\n interrupted = true;\n try {\n await call([\n \"exec\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n name,\n \"sh\",\n \"-c\",\n `touch ${quote(pidFile + \".cancel\")}; if [ -f ${quote(pidFile)} ]; then p=$(cat ${quote(pidFile)}); kill -TERM -\"$p\" 2>/dev/null || true; sleep 0.1; kill -KILL -\"$p\" 2>/dev/null || true; fi`,\n ]);\n } catch (cleanup) {\n throw new AggregateError(\n [cause, cleanup],\n \"Command cancellation could not be confirmed\",\n );\n }\n throw cause;\n } finally {\n if (!isClosed() && !interrupted)\n await call([\"exec\", name, \"rm\", \"-f\", pidFile]).catch(() => undefined);\n }\n };\n}\n"]}
1
+ {"version":3,"file":"container-command.js","sourceRoot":"","sources":["../../src/providers/container-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG7D,MAAM,UAAU,gBAAgB,CAC9B,OAAyB;IAEzB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC9E,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,QAAQ,EAAE;YACZ,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,6BAA6B,CAAC,CAAC;QACpE,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,gBAAgB,EAAE,MAAM,CAAC;QACzC,MAAM,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;QACnD,SAAS,CACP,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CACnC,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,CACrC,EACD,yBAAyB,CAC1B,CAAC;QACF,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CACpC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YAClD,iBAAiB,KAAK,EAAE;YACxB,KAAK;SACN,CAAC,CACH,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;aACnC,GAAG,CACF,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CACb,UAAU,GAAG,oBAAoB,KAAK,0BAA0B,KAAK,GAAG,CAC3E;aACA,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,MAAM,KAAK,GAAG;YACZ,MAAM;YACN,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YACzD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,WAAW;YACX,OAAO,CAAC,SAAS,IAAI,IAAI;YACzB,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YAC5D,IAAI;SACL,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,OAAO,cAAc,KAAK,CAAC,OAAO,CAAC,eAAe,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,yBAAyB,CAAC;QACzH,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,GAAG,OAAO,CAAC;QACzD,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC;gBACpB,GAAG,UAAU;gBACb,UAAU,EAAE,MAAM;gBAClB,SAAS,EAAE;oBACT,GAAG,KAAK;oBACR,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;wBAC5C,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;oBACzB,IAAI;oBACJ,IAAI;oBACJ,OAAO;oBACP,SAAS;oBACT,OAAO,CAAC,UAAU;oBAClB,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;iBAC7B;gBACD,SAAS,EAAE,WAAW;gBACtB,MAAM,EACJ,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,iBAAiB,CAAC,WAAW;aACnE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC;oBACT,MAAM;oBACN,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9C,IAAI;oBACJ,IAAI;oBACJ,IAAI;oBACJ,SAAS,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,aAAa,KAAK,CAAC,OAAO,CAAC,oBAAoB,KAAK,CAAC,OAAO,CAAC,8FAA8F;iBAC/L,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,OAAO,EAAE,CAAC;gBACjB,MAAM,IAAI,cAAc,CACtB,CAAC,KAAK,EAAE,OAAO,CAAC,EAChB,6CAA6C,CAC9C,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW;gBAC7B,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { invariant, OutpostError } from \"../domain/errors.ts\";\nimport type { SandboxLease } from \"../domain/sandbox.types.ts\";\nimport { quote } from \"../infrastructure/process.ts\";\nimport { containerDefaults } from \"./container.constants.ts\";\nimport type { ContainerRuntime } from \"./container.types.ts\";\n\nexport function containerCommand(\n runtime: ContainerRuntime,\n): SandboxLease[\"invoke\"] {\n const { engine, config, executor, root, name, env, call, isClosed } = runtime;\n return async (command) => {\n if (isClosed())\n throw new OutpostError(\"provider\", \"Container sandbox is closed\");\n command.signal?.throwIfAborted();\n const id = randomUUID();\n const pidFile = `/tmp/outpost-${id}.pid`;\n const variables = { ...env, ...command.variables };\n invariant(\n Object.keys(variables).every((key) =>\n /^[A-Za-z_][A-Za-z0-9_]*$/.test(key),\n ),\n \"Invalid environment key\",\n );\n const environment = Object.fromEntries(\n Object.entries(variables).map(([, value], index) => [\n `OUTPOST_VALUE_${index}`,\n value,\n ]),\n );\n const exports = Object.keys(variables)\n .map(\n (key, index) =>\n `export ${key}=\"$OUTPOST_VALUE_${index}\"; unset OUTPOST_VALUE_${index};`,\n )\n .join(\" \");\n const flags = [\n \"exec\",\n command.interactive && process.stdin.isTTY ? \"-it\" : \"-i\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n \"--workdir\",\n command.directory ?? root,\n ...Object.keys(environment).flatMap((key) => [\"--env\", key]),\n name,\n ];\n const wrapper = `${exports} echo $$ > ${quote(pidFile)}; test ! -f ${quote(pidFile + \".cancel\")} || exit 130; exec \"$@\"`;\n const { directory: _directory, ...invocation } = command;\n let interrupted = false;\n try {\n return await executor({\n ...invocation,\n executable: engine,\n arguments: [\n ...flags,\n ...(command.interactive && process.stdin.isTTY\n ? []\n : [\"setsid\", \"--wait\"]),\n \"sh\",\n \"-c\",\n wrapper,\n \"outpost\",\n command.executable,\n ...(command.arguments ?? []),\n ],\n variables: environment,\n retain:\n command.retain ?? config.retain ?? containerDefaults.retainBytes,\n });\n } catch (cause) {\n interrupted = true;\n try {\n await call([\n \"exec\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n name,\n \"sh\",\n \"-c\",\n `touch ${quote(pidFile + \".cancel\")}; if [ -f ${quote(pidFile)} ]; then p=$(cat ${quote(pidFile)}); kill -TERM -\"$p\" 2>/dev/null || true; sleep 0.1; kill -KILL -\"$p\" 2>/dev/null || true; fi`,\n ]);\n } catch (cleanup) {\n throw new AggregateError(\n [cause, cleanup],\n \"Command cancellation could not be confirmed\",\n );\n }\n throw cause;\n } finally {\n if (!isClosed() && !interrupted)\n await call([\"exec\", name, \"rm\", \"-f\", pidFile]).catch(() => undefined);\n }\n };\n}\n"]}
@@ -1,3 +1,4 @@
1
1
  import type { SandboxLease } from "../domain/sandbox.types.ts";
2
+ import { containerArchive } from "./container-archive.ts";
2
3
  import type { ContainerRuntime } from "./container.types.ts";
3
- export declare function containerFiles(call: ContainerRuntime["call"], name: string): Pick<SandboxLease, "upload" | "download">;
4
+ export declare function containerFiles(runtime: ContainerRuntime, archive?: typeof containerArchive): Pick<SandboxLease, "upload" | "download">;