@h-sandbox/deepagents 0.1.0-rc.0 → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,28 +33,33 @@ verifies the original tests and returns a diff after confirmed cleanup.
33
33
 
34
34
  ## Availability
35
35
 
36
- **Unpublished release candidate.** Native tools and an independently verified
37
- model repair have passed; final release checks and npm publication are pending.
38
- Evaluate the adapter archive with the exact published SDK below. Node 20+ is
39
- the consumer target; repository tooling uses Node 22+.
36
+ **Developer preview on npm.** Install `@h-sandbox/deepagents@next` with the exact
37
+ SDK and framework peers below. Native tools and an independently verified model
38
+ repair have passed. Use Node 22 or 24 LTS. Node 20 remains a legacy compatibility target, not a supported
39
+ Node.js production line. This optional package has its
40
+ own release cycle, separate from the SDK.
41
+
42
+ Minimal backend installation:
43
+
44
+ ```bash
45
+ npm install --save-exact @h-sandbox/deepagents@next @h-sandbox/sdk@0.5.0-rc.12 deepagents@1.14.0
46
+ ```
47
+
48
+ For the complete model and checkpoint examples, start with the full direct
49
+ dependency set together. Adding older framework pins after auto-installed peers
50
+ can leave duplicate LangGraph types in a pnpm lockfile.
40
51
 
41
52
  ```bash
42
- # In a reviewed Harakiri checkout:
43
- pnpm install --frozen-lockfile
44
- pnpm --filter @h-sandbox/deepagents build
45
- mkdir -p /tmp/harakiri-framework-candidate
46
- pnpm --filter @h-sandbox/deepagents pack --pack-destination /tmp/harakiri-framework-candidate
47
-
48
- # In your server-side application, using the resulting adapter archive path:
49
- npm install --save-exact @h-sandbox/sdk@0.5.0-rc.11 "$DEEPAGENTS_TARBALL" \
53
+ # In your server-side application:
54
+ npm install --save-exact @h-sandbox/deepagents@next @h-sandbox/sdk@0.5.0-rc.12 \
50
55
  deepagents@1.14.0 langchain@1.5.11 @langchain/core@1.2.12 \
51
56
  @langchain/langgraph@1.4.17 langsmith@0.9.0 zod@4.4.3
52
57
  ```
53
58
 
54
- The tested peers are **SDK 0.5.0-rc.11 and Deep Agents 1.14.0**, both exact because
55
- these preview contracts are evolving. The reviewed archive identifies this
56
- adapter candidate. Commit your application's lockfile. Other framework or SDK
57
- versions need a fresh compatibility run.
59
+ The tested peers are **SDK 0.5.0-rc.12 and Deep Agents 1.14.0**, both exact because
60
+ these preview contracts are evolving. `--save-exact` resolves the moving `next`
61
+ channel to a concrete version; commit your application's lockfile. Other
62
+ framework or SDK versions need a fresh compatibility run.
58
63
 
59
64
  ## Connect an Existing Sandbox
60
65
 
@@ -135,6 +140,13 @@ checkpointer, pause for approval and retrieve one command's result. It does not
135
140
  claim process-restart durability. Production applications supply a persistent
136
141
  checkpointer and their own authorized sandbox lookup.
137
142
 
143
+ The [persistent workflow guide](https://github.com/nabilblk/h-sandbox/blob/main/docs/integrations/reliable-framework-workflows.md)
144
+ adds a real `createDeepAgent`/PostgresSaver application with separate worker
145
+ processes, checkpoint-bound approval, acknowledged-command observation and
146
+ explicit retained-file recovery. Use adapter `0.1.0-rc.2` and SDK `0.5.0-rc.12`.
147
+ The application example is versioned source, not a database service in the package;
148
+ the guide shows how to run it against the published dependencies.
149
+
138
150
  The repair example requires **Node.js, Git,
139
151
  bash and GNU file tools** in the selected template. Its blocked egress policy
140
152
  requires enforcement support in your installation. It downloads no dependencies
@@ -166,11 +178,12 @@ command exercises remote shell/file operations with scripted decisions. Real
166
178
  model acceptance and publication are separate gates. On September 24, the
167
179
  digest-pinned Qwen3 4B Instruct model completed this repair on an isolated amd64
168
180
  runner: five responses, no truncated responses or invalid tool calls, unchanged
169
- original tests, a verified patch and confirmed cleanup. The overall run then
170
- failed its final operator-key revocation check after the captured browser token
171
- expired. See [the qualification run](https://github.com/nabilblk/h-sandbox/actions/runs/36004058904).
172
- The full suite must pass before publication. This is one small repair, not a
173
- model-quality or production reliability benchmark.
181
+ original tests, a verified patch and confirmed cleanup. The
182
+ [full qualification run](https://github.com/nabilblk/h-sandbox/actions/runs/36007730164)
183
+ passed all 15 gates, including operator-key revocation. This is one small repair,
184
+ not a model-quality or production reliability benchmark. The
185
+ [delivery receipt](https://github.com/nabilblk/h-sandbox/blob/main/docs/release-notes/deepagents-0.1.0-delivery.md)
186
+ distinguishes native qualification from package publication and deployment.
174
187
 
175
188
  ## Boundaries
176
189
 
@@ -181,10 +194,13 @@ model-quality or production reliability benchmark.
181
194
  exits are ordinary tool results. The adapter never retries a mutation.
182
195
  A framework or model can still request the same action again; approval and
183
196
  idempotent application operations are required where repeat effects matter.
184
- - Execution timeout, status-polling timeout and sandbox TTL are separate.
185
- Cancellation stops polling/between-file work, not a running remote command.
186
- Submission, file and log HTTP requests use the SDK transport; supply bounded
187
- `fetch` in client configuration if you need per-request transport deadlines.
197
+ - HTTP `requestTimeoutMs`, execution timeout,
198
+ observation timeout and sandbox TTL are separate. JSON HTTP requests default
199
+ to 120 seconds, including body reads. Per-call options and caller cancellation
200
+ also cover submissions, logs and transfers; the adapter's observation budget
201
+ includes final logs. None of these kill a remote command or authorize a retry.
202
+ Earlier rc.1 instead requires a bounded custom `fetch` for per-request
203
+ deadlines and its observation budget excludes final logs.
188
204
  - Output is stdout followed by stderr, not a chronological merge. The default
189
205
  combined UTF-8 log limit is 64 KiB; provider truncation flags remain visible.
190
206
  A fixed-size termination notice is outside that limit so timeouts/kills are
package/dist/backend.js CHANGED
@@ -21,7 +21,7 @@ export class HarakiriSandboxBackend extends BaseSandbox {
21
21
  constructor(sandbox, options = {}) {
22
22
  super();
23
23
  if (typeof sandbox.files.readBytes !== "function" || typeof sandbox.processes.connect !== "function") {
24
- throw new TypeError("This adapter requires Harakiri SDK 0.5.0-rc.11 or newer, not npm rc.10.");
24
+ throw new TypeError("This adapter requires its tested Harakiri SDK peer, 0.5.0-rc.12.");
25
25
  }
26
26
  this.#sandbox = sandbox;
27
27
  this.#execution = executionOptions(sandbox, options);
@@ -38,7 +38,7 @@ export class HarakiriSandboxBackend extends BaseSandbox {
38
38
  try {
39
39
  const process = await this.#sandbox.processes.start({
40
40
  command, cwd: this.#execution.cwd, timeoutMs: this.#execution.timeoutMs, detached: true
41
- });
41
+ }, { signal: this.#execution.signal, requestTimeoutMs: this.#execution.requestTimeoutMs });
42
42
  reference = process.reference;
43
43
  }
44
44
  catch (cause) {
@@ -76,7 +76,9 @@ export class HarakiriSandboxBackend extends BaseSandbox {
76
76
  results.push({ path, error: "invalid_path" });
77
77
  continue;
78
78
  }
79
- await this.#sandbox.files.write(this.#path(path), content, { createParents: true });
79
+ await this.#sandbox.files.write(this.#path(path), content, {
80
+ createParents: true, signal: this.#execution.signal, requestTimeoutMs: this.#execution.requestTimeoutMs
81
+ });
80
82
  results.push({ path, error: null });
81
83
  }
82
84
  catch (cause) {
@@ -101,13 +103,14 @@ export class HarakiriSandboxBackend extends BaseSandbox {
101
103
  continue;
102
104
  }
103
105
  const absolute = this.#path(path);
104
- const { file } = await this.#sandbox.files.stat(absolute);
106
+ const request = { signal: this.#execution.signal, requestTimeoutMs: this.#execution.requestTimeoutMs };
107
+ const { file } = await this.#sandbox.files.stat(absolute, request);
105
108
  if (file.type === "directory" || file.type === "dir") {
106
109
  results.push({ path, content: null, error: "is_directory" });
107
110
  continue;
108
111
  }
109
112
  this.#checkBytes(bytes + file.size);
110
- const content = await this.#sandbox.files.readBytes(absolute);
113
+ const content = await this.#sandbox.files.readBytes(absolute, request);
111
114
  bytes += content.byteLength;
112
115
  this.#checkBytes(bytes);
113
116
  results.push({ path, content, error: null });
@@ -1,16 +1,16 @@
1
- import type { HarakiriSandbox, SandboxCommandLogsResponse, SandboxCommandSummary } from "@h-sandbox/sdk";
1
+ import type { HarakiriSandbox, RequestOptions, SandboxCommandLogsResponse, SandboxCommandSummary } from "@h-sandbox/sdk";
2
2
  import type { ExecuteResponse } from "deepagents";
3
3
  import { type CommandReference } from "./errors.js";
4
- export type ExecutionOptions = {
4
+ export type ExecutionOptions = RequestOptions & {
5
5
  /** Remote execution budget. Defaults to the runtime's advertised command timeout. */
6
6
  timeoutMs?: number;
7
- /** Command-status polling budget, excluding log download; never extends TTL. */
7
+ /** Total observation budget, including final log download; never extends TTL. */
8
8
  observationTimeoutMs?: number;
9
9
  /** Combined UTF-8 log limit, excluding a fixed termination notice. Default 64 KiB; at most 1 MiB. */
10
10
  maxOutputBytes?: number;
11
11
  /** Must be absolute. This is a working directory, not a security boundary. */
12
12
  cwd?: string;
13
- /** Stops observation. It does not kill the remote command. */
13
+ /** Cancels local requests/observation. It does not kill the remote command. */
14
14
  signal?: AbortSignal;
15
15
  /** Persist the acknowledged reference before waiting. Never receives credentials. */
16
16
  onCommandStarted?: (reference: CommandReference) => void | Promise<void>;
@@ -25,8 +25,8 @@ export declare function executionOptions(sandbox: HarakiriSandbox, options: Exec
25
25
  timeoutMs: number;
26
26
  observationTimeoutMs: number;
27
27
  maxOutputBytes: number;
28
- /** Stops observation. It does not kill the remote command. */
29
28
  signal?: AbortSignal;
29
+ requestTimeoutMs?: number;
30
30
  /** Persist the acknowledged reference before waiting. Never receives credentials. */
31
31
  onCommandStarted?: (reference: CommandReference) => void | Promise<void>;
32
32
  };
package/dist/execution.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { HarakiriWaitTimeoutError } from "@h-sandbox/sdk";
1
2
  import { HarakiriExecutionError } from "./errors.js";
2
3
  export function positiveInteger(name, value, maximum = Number.MAX_SAFE_INTEGER) {
3
4
  if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
@@ -10,9 +11,11 @@ export function executionOptions(sandbox, options) {
10
11
  if (!cwd.startsWith("/") || cwd.includes("\0"))
11
12
  throw new TypeError("cwd must be an absolute path without NUL characters.");
12
13
  const timeoutMs = positiveInteger("timeoutMs", options.timeoutMs ?? sandbox.runtimeMetadata.limits.commandTimeoutMs);
14
+ if (options.requestTimeoutMs !== undefined)
15
+ positiveInteger("requestTimeoutMs", options.requestTimeoutMs, 2_147_483_647);
13
16
  return {
14
17
  ...options, cwd, timeoutMs,
15
- observationTimeoutMs: positiveInteger("observationTimeoutMs", options.observationTimeoutMs ?? timeoutMs + 10_000),
18
+ observationTimeoutMs: positiveInteger("observationTimeoutMs", options.observationTimeoutMs ?? timeoutMs + 10_000, 2_147_483_647),
16
19
  maxOutputBytes: positiveInteger("maxOutputBytes", options.maxOutputBytes ?? 65_536, 1_048_576)
17
20
  };
18
21
  }
@@ -54,13 +57,33 @@ export async function observeCommand(sandbox, reference, options) {
54
57
  }
55
58
  try {
56
59
  options.signal?.throwIfAborted();
60
+ const deadline = Date.now() + options.observationTimeoutMs;
57
61
  const { command } = await sandbox.processes.wait(reference.commandId, {
58
62
  statuses: ["succeeded", "failed", "killed"],
59
63
  timeoutMs: options.observationTimeoutMs,
64
+ requestTimeoutMs: options.requestTimeoutMs,
60
65
  signal: options.signal
61
66
  });
62
67
  options.signal?.throwIfAborted();
63
- const logs = await sandbox.processes.logs(reference.commandId);
68
+ const remainingMs = deadline - Date.now();
69
+ const timeout = () => new HarakiriWaitTimeoutError("Observation deadline elapsed during log retrieval. Reconnect using the command reference.", "command", reference.commandId, command.status);
70
+ if (remainingMs <= 0)
71
+ throw timeout();
72
+ const controller = new AbortController();
73
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
74
+ const timer = setTimeout(() => controller.abort(timeout()), remainingMs);
75
+ let logs;
76
+ try {
77
+ logs = await sandbox.processes.logs(reference.commandId, {
78
+ signal, requestTimeoutMs: options.requestTimeoutMs
79
+ });
80
+ signal.throwIfAborted();
81
+ if (Date.now() >= deadline)
82
+ throw timeout();
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
64
87
  options.signal?.throwIfAborted();
65
88
  const result = commandOutput(logs, options.maxOutputBytes);
66
89
  const notice = terminationNotice(command);
package/dist/lifecycle.js CHANGED
@@ -11,11 +11,13 @@ export async function withHarakiriSandbox(client, input, task, options = {}) {
11
11
  const cleanupTimeoutMs = positiveInteger("cleanupTimeoutMs", options.cleanupTimeoutMs ?? 90_000);
12
12
  options.backend?.signal?.throwIfAborted();
13
13
  // Retain the accepted handle before readiness so failures still enter cleanup.
14
- const sandbox = await client.sandboxes.create({ ...input, wait: false });
14
+ const sandbox = await client.sandboxes.create({ ...input, wait: false }, {
15
+ signal: options.backend?.signal, requestTimeoutMs: options.backend?.requestTimeoutMs
16
+ });
15
17
  let result;
16
18
  const failures = [];
17
19
  try {
18
- await sandbox.wait({ timeoutMs: readinessTimeoutMs, signal: options.backend?.signal });
20
+ await sandbox.wait({ timeoutMs: readinessTimeoutMs, signal: options.backend?.signal, requestTimeoutMs: options.backend?.requestTimeoutMs });
19
21
  const backend = new HarakiriSandboxBackend(sandbox, options.backend);
20
22
  result = await task({ sandbox, backend });
21
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-sandbox/deepagents",
3
- "version": "0.1.0-rc.0",
3
+ "version": "0.1.0-rc.2",
4
4
  "description": "Deep Agents sandbox backend for the Harakiri control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -30,18 +30,21 @@
30
30
  "LICENSE"
31
31
  ],
32
32
  "peerDependencies": {
33
- "@h-sandbox/sdk": "0.5.0-rc.11",
33
+ "@h-sandbox/sdk": "0.5.0-rc.12",
34
34
  "deepagents": "1.14.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "deepagents": "1.14.0",
38
38
  "@langchain/core": "1.2.12",
39
39
  "@langchain/langgraph": "1.4.17",
40
+ "@langchain/langgraph-checkpoint-postgres": "1.0.5",
41
+ "@types/pg": "8.20.0",
40
42
  "langchain": "1.5.11",
41
43
  "langsmith": "0.9.0",
44
+ "pg": "8.21.0",
42
45
  "zod": "4.4.3",
43
46
  "tsx": "^4.23.13",
44
- "@h-sandbox/sdk": "0.5.0-rc.11"
47
+ "@h-sandbox/sdk": "0.5.0-rc.12"
45
48
  },
46
49
  "scripts": {
47
50
  "clean": "rm -rf dist",