@polos/sdk 0.2.0 → 0.2.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/dist/index.d.cts CHANGED
@@ -4787,15 +4787,15 @@ declare function stripAnsi(text: string): string;
4787
4787
  declare function createExecTool(getEnv: () => Promise<ExecutionEnvironment>, config?: ExecToolConfig): ToolWorkflow;
4788
4788
 
4789
4789
  /**
4790
- * Path-based approval for read-only sandbox tools.
4790
+ * Path-based approval for sandbox tools.
4791
4791
  *
4792
- * When pathRestriction is set, read-only tools (read, glob, grep) allow
4792
+ * When pathRestriction is set, tools (read, write, edit, glob, grep) allow
4793
4793
  * operations within the restricted path without approval. Operations
4794
4794
  * outside the restriction suspend for user approval.
4795
4795
  */
4796
4796
 
4797
4797
  /**
4798
- * Configuration for path-restricted approval on read-only tools.
4798
+ * Configuration for path-restricted approval on sandbox tools.
4799
4799
  */
4800
4800
  interface PathRestrictionConfig {
4801
4801
  /** Directory to allow without approval. Paths outside require approval. */
@@ -4816,21 +4816,43 @@ declare function createReadTool(getEnv: () => Promise<ExecutionEnvironment>, pat
4816
4816
 
4817
4817
  /**
4818
4818
  * Write tool — create or overwrite files in the execution environment.
4819
+ *
4820
+ * When pathRestriction is set, writes within the restriction proceed
4821
+ * without approval. Writes outside the restriction suspend for user approval.
4822
+ * Set approval to 'always' to require approval for every write, or 'none'
4823
+ * to skip approval entirely (overrides path restriction).
4819
4824
  */
4820
4825
 
4826
+ interface WriteToolConfig {
4827
+ /** Explicit approval override. 'always' = approve every write, 'none' = never approve. */
4828
+ approval?: ToolApproval;
4829
+ /** Path restriction config — writes inside are allowed, outside require approval. */
4830
+ pathConfig?: PathRestrictionConfig;
4831
+ }
4821
4832
  /**
4822
4833
  * Create the write tool for writing file contents.
4823
4834
  */
4824
- declare function createWriteTool(getEnv: () => Promise<ExecutionEnvironment>, approval?: ToolApproval): ToolWorkflow;
4835
+ declare function createWriteTool(getEnv: () => Promise<ExecutionEnvironment>, config?: WriteToolConfig): ToolWorkflow;
4825
4836
 
4826
4837
  /**
4827
4838
  * Edit tool — find-and-replace text in files in the execution environment.
4839
+ *
4840
+ * When pathRestriction is set, edits within the restriction proceed
4841
+ * without approval. Edits outside the restriction suspend for user approval.
4842
+ * Set approval to 'always' to require approval for every edit, or 'none'
4843
+ * to skip approval entirely (overrides path restriction).
4828
4844
  */
4829
4845
 
4846
+ interface EditToolConfig {
4847
+ /** Explicit approval override. 'always' = approve every edit, 'none' = never approve. */
4848
+ approval?: ToolApproval;
4849
+ /** Path restriction config — edits inside are allowed, outside require approval. */
4850
+ pathConfig?: PathRestrictionConfig;
4851
+ }
4830
4852
  /**
4831
4853
  * Create the edit tool for find-and-replace in files.
4832
4854
  */
4833
- declare function createEditTool(getEnv: () => Promise<ExecutionEnvironment>, approval?: ToolApproval): ToolWorkflow;
4855
+ declare function createEditTool(getEnv: () => Promise<ExecutionEnvironment>, config?: EditToolConfig): ToolWorkflow;
4834
4856
 
4835
4857
  /**
4836
4858
  * Glob tool — find files by pattern in the execution environment.
package/dist/index.d.ts CHANGED
@@ -4787,15 +4787,15 @@ declare function stripAnsi(text: string): string;
4787
4787
  declare function createExecTool(getEnv: () => Promise<ExecutionEnvironment>, config?: ExecToolConfig): ToolWorkflow;
4788
4788
 
4789
4789
  /**
4790
- * Path-based approval for read-only sandbox tools.
4790
+ * Path-based approval for sandbox tools.
4791
4791
  *
4792
- * When pathRestriction is set, read-only tools (read, glob, grep) allow
4792
+ * When pathRestriction is set, tools (read, write, edit, glob, grep) allow
4793
4793
  * operations within the restricted path without approval. Operations
4794
4794
  * outside the restriction suspend for user approval.
4795
4795
  */
4796
4796
 
4797
4797
  /**
4798
- * Configuration for path-restricted approval on read-only tools.
4798
+ * Configuration for path-restricted approval on sandbox tools.
4799
4799
  */
4800
4800
  interface PathRestrictionConfig {
4801
4801
  /** Directory to allow without approval. Paths outside require approval. */
@@ -4816,21 +4816,43 @@ declare function createReadTool(getEnv: () => Promise<ExecutionEnvironment>, pat
4816
4816
 
4817
4817
  /**
4818
4818
  * Write tool — create or overwrite files in the execution environment.
4819
+ *
4820
+ * When pathRestriction is set, writes within the restriction proceed
4821
+ * without approval. Writes outside the restriction suspend for user approval.
4822
+ * Set approval to 'always' to require approval for every write, or 'none'
4823
+ * to skip approval entirely (overrides path restriction).
4819
4824
  */
4820
4825
 
4826
+ interface WriteToolConfig {
4827
+ /** Explicit approval override. 'always' = approve every write, 'none' = never approve. */
4828
+ approval?: ToolApproval;
4829
+ /** Path restriction config — writes inside are allowed, outside require approval. */
4830
+ pathConfig?: PathRestrictionConfig;
4831
+ }
4821
4832
  /**
4822
4833
  * Create the write tool for writing file contents.
4823
4834
  */
4824
- declare function createWriteTool(getEnv: () => Promise<ExecutionEnvironment>, approval?: ToolApproval): ToolWorkflow;
4835
+ declare function createWriteTool(getEnv: () => Promise<ExecutionEnvironment>, config?: WriteToolConfig): ToolWorkflow;
4825
4836
 
4826
4837
  /**
4827
4838
  * Edit tool — find-and-replace text in files in the execution environment.
4839
+ *
4840
+ * When pathRestriction is set, edits within the restriction proceed
4841
+ * without approval. Edits outside the restriction suspend for user approval.
4842
+ * Set approval to 'always' to require approval for every edit, or 'none'
4843
+ * to skip approval entirely (overrides path restriction).
4828
4844
  */
4829
4845
 
4846
+ interface EditToolConfig {
4847
+ /** Explicit approval override. 'always' = approve every edit, 'none' = never approve. */
4848
+ approval?: ToolApproval;
4849
+ /** Path restriction config — edits inside are allowed, outside require approval. */
4850
+ pathConfig?: PathRestrictionConfig;
4851
+ }
4830
4852
  /**
4831
4853
  * Create the edit tool for find-and-replace in files.
4832
4854
  */
4833
- declare function createEditTool(getEnv: () => Promise<ExecutionEnvironment>, approval?: ToolApproval): ToolWorkflow;
4855
+ declare function createEditTool(getEnv: () => Promise<ExecutionEnvironment>, config?: EditToolConfig): ToolWorkflow;
4834
4856
 
4835
4857
  /**
4836
4858
  * Glob tool — find files by pattern in the execution environment.
package/dist/index.js CHANGED
@@ -292,7 +292,7 @@ var OrchestratorClient = class {
292
292
  }
293
293
  if (attempt < retries) {
294
294
  const delay = Math.min(1e3 * Math.pow(2, attempt), 16e3);
295
- await new Promise((resolve8) => setTimeout(resolve8, delay));
295
+ await new Promise((resolve10) => setTimeout(resolve10, delay));
296
296
  }
297
297
  }
298
298
  }
@@ -744,7 +744,7 @@ var OrchestratorClient = class {
744
744
  if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled") {
745
745
  return execution;
746
746
  }
747
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
747
+ await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
748
748
  }
749
749
  throw new Error(`Execution ${executionId} timed out after ${String(timeout)}ms`);
750
750
  }
@@ -1670,7 +1670,7 @@ function calculateDelay(attempt, options) {
1670
1670
  return Math.round(delay);
1671
1671
  }
1672
1672
  function sleep(ms) {
1673
- return new Promise((resolve8) => setTimeout(resolve8, ms));
1673
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
1674
1674
  }
1675
1675
  async function retry(fn, options = {}) {
1676
1676
  const opts = { ...DEFAULT_OPTIONS, ...options };
@@ -1856,7 +1856,7 @@ function createStepHelper(options) {
1856
1856
  if (options2.days) totalSeconds += options2.days * 86400;
1857
1857
  if (options2.weeks) totalSeconds += options2.weeks * 604800;
1858
1858
  const totalMs = totalSeconds * 1e3;
1859
- await new Promise((resolve8) => setTimeout(resolve8, totalMs));
1859
+ await new Promise((resolve10) => setTimeout(resolve10, totalMs));
1860
1860
  store.set(key, { wait_until: new Date(Date.now() + totalMs).toISOString() });
1861
1861
  },
1862
1862
  async waitUntil(key, date) {
@@ -1864,7 +1864,7 @@ function createStepHelper(options) {
1864
1864
  if (cached) return;
1865
1865
  const now = Date.now();
1866
1866
  const waitMs = Math.max(0, date.getTime() - now);
1867
- await new Promise((resolve8) => setTimeout(resolve8, waitMs));
1867
+ await new Promise((resolve10) => setTimeout(resolve10, waitMs));
1868
1868
  store.set(key, { wait_until: date.toISOString() });
1869
1869
  },
1870
1870
  // eslint-disable-next-line @typescript-eslint/require-await -- stub implementation
@@ -4504,7 +4504,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4504
4504
  });
4505
4505
  const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
4506
4506
  if (totalSeconds <= waitThreshold) {
4507
- await new Promise((resolve8) => setTimeout(resolve8, totalSeconds * 1e3));
4507
+ await new Promise((resolve10) => setTimeout(resolve10, totalSeconds * 1e3));
4508
4508
  await saveStepOutput(key, { wait_until: waitUntil.toISOString() });
4509
4509
  return;
4510
4510
  }
@@ -4540,7 +4540,7 @@ function createOrchestratorStepHelper(orchestratorClient, cachedSteps, execCtx,
4540
4540
  });
4541
4541
  const waitThreshold = Number(process.env["POLOS_WAIT_THRESHOLD_SECONDS"] ?? "10");
4542
4542
  if (waitSeconds <= waitThreshold) {
4543
- await new Promise((resolve8) => setTimeout(resolve8, waitSeconds * 1e3));
4543
+ await new Promise((resolve10) => setTimeout(resolve10, waitSeconds * 1e3));
4544
4544
  await saveStepOutput(key, { wait_until: date.toISOString() });
4545
4545
  return;
4546
4546
  }
@@ -5293,7 +5293,7 @@ var DEFAULT_CONTAINER_WORKDIR = "/workspace";
5293
5293
  var DEFAULT_TIMEOUT_SECONDS = 300;
5294
5294
  var DEFAULT_MAX_OUTPUT_CHARS = 1e5;
5295
5295
  function spawnCommand(command, args, options) {
5296
- return new Promise((resolve8, reject) => {
5296
+ return new Promise((resolve10, reject) => {
5297
5297
  let settled = false;
5298
5298
  const settle = (fn) => {
5299
5299
  if (!settled) {
@@ -5322,21 +5322,42 @@ function spawnCommand(command, args, options) {
5322
5322
  killed = true;
5323
5323
  proc.kill("SIGKILL");
5324
5324
  }, timeoutMs);
5325
+ let exitGraceTimer = null;
5326
+ let exitCode = null;
5327
+ proc.on("exit", (code) => {
5328
+ exitCode = code;
5329
+ exitGraceTimer = setTimeout(() => {
5330
+ clearTimeout(timer);
5331
+ settle(() => {
5332
+ if (killed) {
5333
+ resolve10({
5334
+ exitCode: 137,
5335
+ stdout,
5336
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
5337
+ });
5338
+ } else {
5339
+ resolve10({ exitCode: exitCode ?? 1, stdout, stderr });
5340
+ }
5341
+ });
5342
+ }, 2e3);
5343
+ });
5325
5344
  proc.on("close", (code) => {
5345
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5326
5346
  clearTimeout(timer);
5327
5347
  settle(() => {
5328
5348
  if (killed) {
5329
- resolve8({
5349
+ resolve10({
5330
5350
  exitCode: 137,
5331
5351
  stdout,
5332
5352
  stderr: stderr + "\n[Process killed: timeout exceeded]"
5333
5353
  });
5334
5354
  } else {
5335
- resolve8({ exitCode: code ?? 1, stdout, stderr });
5355
+ resolve10({ exitCode: code ?? exitCode ?? 1, stdout, stderr });
5336
5356
  }
5337
5357
  });
5338
5358
  });
5339
5359
  proc.on("error", (err) => {
5360
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5340
5361
  clearTimeout(timer);
5341
5362
  settle(() => {
5342
5363
  reject(err);
@@ -5549,7 +5570,7 @@ var DockerEnvironment = class {
5549
5570
  var DEFAULT_TIMEOUT_SECONDS2 = 300;
5550
5571
  var DEFAULT_MAX_OUTPUT_CHARS2 = 1e5;
5551
5572
  function spawnLocal(command, options) {
5552
- return new Promise((resolve8, reject) => {
5573
+ return new Promise((resolve10, reject) => {
5553
5574
  let settled = false;
5554
5575
  const settle = (fn) => {
5555
5576
  if (!settled) {
@@ -5582,21 +5603,42 @@ function spawnLocal(command, options) {
5582
5603
  killed = true;
5583
5604
  proc.kill("SIGKILL");
5584
5605
  }, timeoutMs);
5606
+ let exitGraceTimer = null;
5607
+ let exitCode = null;
5608
+ proc.on("exit", (code) => {
5609
+ exitCode = code;
5610
+ exitGraceTimer = setTimeout(() => {
5611
+ clearTimeout(timer);
5612
+ settle(() => {
5613
+ if (killed) {
5614
+ resolve10({
5615
+ exitCode: 137,
5616
+ stdout,
5617
+ stderr: stderr + "\n[Process killed: timeout exceeded]"
5618
+ });
5619
+ } else {
5620
+ resolve10({ exitCode: exitCode ?? 1, stdout, stderr });
5621
+ }
5622
+ });
5623
+ }, 2e3);
5624
+ });
5585
5625
  proc.on("close", (code) => {
5626
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5586
5627
  clearTimeout(timer);
5587
5628
  settle(() => {
5588
5629
  if (killed) {
5589
- resolve8({
5630
+ resolve10({
5590
5631
  exitCode: 137,
5591
5632
  stdout,
5592
5633
  stderr: stderr + "\n[Process killed: timeout exceeded]"
5593
5634
  });
5594
5635
  } else {
5595
- resolve8({ exitCode: code ?? 1, stdout, stderr });
5636
+ resolve10({ exitCode: code ?? exitCode ?? 1, stdout, stderr });
5596
5637
  }
5597
5638
  });
5598
5639
  });
5599
5640
  proc.on("error", (err) => {
5641
+ if (exitGraceTimer) clearTimeout(exitGraceTimer);
5600
5642
  clearTimeout(timer);
5601
5643
  settle(() => {
5602
5644
  reject(err);
@@ -6000,8 +6042,8 @@ var SandboxManager = class {
6000
6042
  }
6001
6043
  let resolveLock;
6002
6044
  let rejectLock;
6003
- const lockPromise = new Promise((resolve8, reject) => {
6004
- resolveLock = resolve8;
6045
+ const lockPromise = new Promise((resolve10, reject) => {
6046
+ resolveLock = resolve10;
6005
6047
  rejectLock = reject;
6006
6048
  });
6007
6049
  this.sessionCreationLocks.set(ctx.sessionId, lockPromise);
@@ -6193,7 +6235,7 @@ var SandboxManager = class {
6193
6235
  }
6194
6236
  };
6195
6237
  function spawnSimple(command, args) {
6196
- return new Promise((resolve8, reject) => {
6238
+ return new Promise((resolve10, reject) => {
6197
6239
  const proc = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
6198
6240
  let stdout = "";
6199
6241
  let stderr = "";
@@ -6204,7 +6246,7 @@ function spawnSimple(command, args) {
6204
6246
  stderr += data.toString();
6205
6247
  });
6206
6248
  proc.on("close", (code) => {
6207
- resolve8({ exitCode: code ?? 1, stdout, stderr });
6249
+ resolve10({ exitCode: code ?? 1, stdout, stderr });
6208
6250
  });
6209
6251
  proc.on("error", reject);
6210
6252
  });
@@ -6339,10 +6381,10 @@ var Worker = class {
6339
6381
  process.on("SIGINT", signalHandler);
6340
6382
  process.on("SIGTERM", signalHandler);
6341
6383
  this.signalHandler = signalHandler;
6342
- await new Promise((resolve8) => {
6384
+ await new Promise((resolve10) => {
6343
6385
  const checkState = () => {
6344
6386
  if (this.state === "stopping" || this.state === "stopped") {
6345
- resolve8();
6387
+ resolve10();
6346
6388
  } else {
6347
6389
  setTimeout(checkState, 100);
6348
6390
  }
@@ -6376,7 +6418,7 @@ var Worker = class {
6376
6418
  const waitTimeout = 3e4;
6377
6419
  const waitStart = Date.now();
6378
6420
  while (this.activeExecutions.size > 0 && Date.now() - waitStart < waitTimeout) {
6379
- await new Promise((resolve8) => setTimeout(resolve8, 100));
6421
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
6380
6422
  }
6381
6423
  if (this.activeExecutions.size > 0) {
6382
6424
  logger9.warn(`${String(this.activeExecutions.size)} executions did not complete in time`);
@@ -7481,7 +7523,7 @@ function createReadTool(getEnv, pathConfig) {
7481
7523
  }
7482
7524
  );
7483
7525
  }
7484
- function createWriteTool(getEnv, approval) {
7526
+ function createWriteTool(getEnv, config) {
7485
7527
  return defineTool(
7486
7528
  {
7487
7529
  id: "write",
@@ -7490,16 +7532,23 @@ function createWriteTool(getEnv, approval) {
7490
7532
  path: z.string().describe("Path to the file to write"),
7491
7533
  content: z.string().describe("Content to write to the file")
7492
7534
  }),
7493
- approval
7535
+ // Only use blanket approval if explicitly set to 'always'
7536
+ approval: config?.approval === "always" ? "always" : void 0
7494
7537
  },
7495
- async (_ctx, input) => {
7538
+ async (ctx, input) => {
7496
7539
  const env = await getEnv();
7540
+ if (!config?.approval && config?.pathConfig?.pathRestriction) {
7541
+ const resolved = resolve(env.getCwd(), input.path);
7542
+ if (!isPathAllowed(resolved, config.pathConfig.pathRestriction)) {
7543
+ await requirePathApproval(ctx, "write", resolved, config.pathConfig.pathRestriction);
7544
+ }
7545
+ }
7497
7546
  await env.writeFile(input.path, input.content);
7498
7547
  return { success: true, path: input.path };
7499
7548
  }
7500
7549
  );
7501
7550
  }
7502
- function createEditTool(getEnv, approval) {
7551
+ function createEditTool(getEnv, config) {
7503
7552
  return defineTool(
7504
7553
  {
7505
7554
  id: "edit",
@@ -7509,10 +7558,17 @@ function createEditTool(getEnv, approval) {
7509
7558
  old_text: z.string().describe("Exact text to find and replace"),
7510
7559
  new_text: z.string().describe("Text to replace the old_text with")
7511
7560
  }),
7512
- approval
7561
+ // Only use blanket approval if explicitly set to 'always'
7562
+ approval: config?.approval === "always" ? "always" : void 0
7513
7563
  },
7514
- async (_ctx, input) => {
7564
+ async (ctx, input) => {
7515
7565
  const env = await getEnv();
7566
+ if (!config?.approval && config?.pathConfig?.pathRestriction) {
7567
+ const resolved = resolve(env.getCwd(), input.path);
7568
+ if (!isPathAllowed(resolved, config.pathConfig.pathRestriction)) {
7569
+ await requirePathApproval(ctx, "edit", resolved, config.pathConfig.pathRestriction);
7570
+ }
7571
+ }
7516
7572
  const content = await env.readFile(input.path);
7517
7573
  if (!content.includes(input.old_text)) {
7518
7574
  throw new Error(
@@ -7617,16 +7673,17 @@ function sandboxTools(config) {
7617
7673
  throw new Error("E2B environment is not yet implemented.");
7618
7674
  }
7619
7675
  const effectiveExecConfig = envType === "local" && !config?.exec?.security ? { ...config?.exec, security: "approval-always" } : config?.exec;
7620
- const fileApproval = config?.fileApproval ?? (envType === "local" ? "always" : void 0);
7621
7676
  const pathConfig = config?.local?.pathRestriction ? { pathRestriction: config.local.pathRestriction } : void 0;
7677
+ const fileApproval = config?.fileApproval;
7678
+ const writeEditConfig = fileApproval ? { approval: fileApproval } : pathConfig ? { pathConfig } : void 0;
7622
7679
  const include = new Set(
7623
7680
  config?.tools ?? ["exec", "read", "write", "edit", "glob", "grep"]
7624
7681
  );
7625
7682
  const tools = [];
7626
7683
  if (include.has("exec")) tools.push(createExecTool(getEnv, effectiveExecConfig));
7627
7684
  if (include.has("read")) tools.push(createReadTool(getEnv, pathConfig));
7628
- if (include.has("write")) tools.push(createWriteTool(getEnv, fileApproval));
7629
- if (include.has("edit")) tools.push(createEditTool(getEnv, fileApproval));
7685
+ if (include.has("write")) tools.push(createWriteTool(getEnv, writeEditConfig));
7686
+ if (include.has("edit")) tools.push(createEditTool(getEnv, writeEditConfig));
7630
7687
  if (include.has("glob")) tools.push(createGlobTool(getEnv, pathConfig));
7631
7688
  if (include.has("grep")) tools.push(createGrepTool(getEnv, pathConfig));
7632
7689
  return tools;