pi-landstrip 0.19.1 → 0.19.3

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.ts CHANGED
@@ -352,16 +352,16 @@ class AsyncQueue {
352
352
  const generation = this.generation;
353
353
  let release;
354
354
  const previous = this.tail;
355
- this.tail = new Promise((resolve2) => {
356
- release = resolve2;
355
+ this.tail = new Promise((resolve) => {
356
+ release = resolve;
357
357
  });
358
358
  const finish = () => {
359
359
  release?.();
360
360
  };
361
361
  let wakeReset;
362
- const resetSignal = new Promise((resolve2) => {
363
- wakeReset = resolve2;
364
- this.resetWaiters.push(resolve2);
362
+ const resetSignal = new Promise((resolve) => {
363
+ wakeReset = resolve;
364
+ this.resetWaiters.push(resolve);
365
365
  });
366
366
  const dropResetWaiter = () => {
367
367
  if (!wakeReset)
@@ -1396,15 +1396,15 @@ async function deleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
1396
1396
  function prepareProjectAgentEditor(cwd, agent, agentDir = getAgentDir3()) {
1397
1397
  const origin = agent.origin;
1398
1398
  if (origin?.path && origin.kind === "pi-markdown") {
1399
- const target2 = projectMarkdownPath(cwd, agentDir, agent);
1399
+ const target = projectMarkdownPath(cwd, agentDir, agent);
1400
1400
  return {
1401
1401
  title: `Edit @${agent.name}`,
1402
1402
  content: readFileSync4(origin.path, "utf8"),
1403
1403
  async save(content) {
1404
1404
  validateAgentRaw(agent.name, parseMarkdownAgentRaw(content));
1405
- await withFileMutationQueue2(target2, async () => {
1406
- mkdirSync2(dirname2(target2), { recursive: true });
1407
- writeFileSync2(target2, ensureFinalNewline2(content), { mode: 384 });
1405
+ await withFileMutationQueue2(target, async () => {
1406
+ mkdirSync2(dirname2(target), { recursive: true });
1407
+ writeFileSync2(target, ensureFinalNewline2(content), { mode: 384 });
1408
1408
  });
1409
1409
  }
1410
1410
  };
@@ -1629,10 +1629,10 @@ function getLandstripArgumentCompletions(argumentPrefix, runtime, contextProvide
1629
1629
  const deps = { runtime, ctx: contextProvider?.() };
1630
1630
  if (argv.words.length === 0 && !argv.trailing) {
1631
1631
  const roots = [
1632
- ...SUBCOMMANDS.map((command2) => ({
1633
- value: command2.name,
1634
- label: command2.name,
1635
- description: command2.summary
1632
+ ...SUBCOMMANDS.map((command) => ({
1633
+ value: command.name,
1634
+ label: command.name,
1635
+ description: command.summary
1636
1636
  })),
1637
1637
  ...taskItems(runtime?.getTasks() ?? [], "")
1638
1638
  ];
@@ -1685,7 +1685,7 @@ function defaultSpawn(command, args, options) {
1685
1685
  return spawn(command, [...args], options);
1686
1686
  }
1687
1687
  function delay(milliseconds) {
1688
- return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
1688
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
1689
1689
  }
1690
1690
 
1691
1691
  class RpcProcess {
@@ -1742,7 +1742,7 @@ class RpcProcess {
1742
1742
  });
1743
1743
  child.stdin.on("error", (error) => this.handleChildError(child, error, "RPC stdin error"));
1744
1744
  child.on("error", (error) => this.handleChildError(child, error, "RPC process error"));
1745
- this.exitPromise = new Promise((resolve2) => {
1745
+ this.exitPromise = new Promise((resolve) => {
1746
1746
  child.once("exit", (code, signal) => {
1747
1747
  this.disposeAuthPipe?.();
1748
1748
  this.disposeAuthPipe = undefined;
@@ -1750,13 +1750,13 @@ class RpcProcess {
1750
1750
  this.child = null;
1751
1751
  this.failAll(new Error(this.exitMessage(code, signal)));
1752
1752
  }
1753
- resolve2();
1753
+ resolve();
1754
1754
  });
1755
1755
  });
1756
- await new Promise((resolve2, reject) => {
1756
+ await new Promise((resolve, reject) => {
1757
1757
  const onSpawn = () => {
1758
1758
  cleanup();
1759
- resolve2();
1759
+ resolve();
1760
1760
  };
1761
1761
  const onError = (error) => {
1762
1762
  cleanup();
@@ -1815,12 +1815,12 @@ class RpcProcess {
1815
1815
  if (this.fatalError)
1816
1816
  throw this.fatalError;
1817
1817
  const id = `rpc-${++this.nextRequestId}`;
1818
- const response = await new Promise((resolve2, reject) => {
1818
+ const response = await new Promise((resolve, reject) => {
1819
1819
  const timer = setTimeout(() => {
1820
1820
  this.pending.delete(id);
1821
1821
  reject(this.withStderr(`Timed out waiting for ${type} response`));
1822
1822
  }, this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
1823
- this.pending.set(id, { command: type, resolve: resolve2, reject, timer });
1823
+ this.pending.set(id, { command: type, resolve, reject, timer });
1824
1824
  this.write({ ...fields, id, type }, (error) => {
1825
1825
  if (!error)
1826
1826
  return;
@@ -2036,12 +2036,12 @@ class RpcProcess {
2036
2036
  }
2037
2037
  waitForAgentSettled() {
2038
2038
  let waiter;
2039
- const promise = new Promise((resolve2, reject) => {
2039
+ const promise = new Promise((resolve, reject) => {
2040
2040
  const timer = setTimeout(() => {
2041
2041
  this.settledWaiters.delete(waiter);
2042
2042
  reject(this.withStderr("Timed out waiting for agent_settled"));
2043
2043
  }, this.options.settleTimeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS);
2044
- waiter = { resolve: resolve2, reject, timer };
2044
+ waiter = { resolve, reject, timer };
2045
2045
  this.settledWaiters.add(waiter);
2046
2046
  });
2047
2047
  return {
@@ -2060,13 +2060,13 @@ class RpcProcess {
2060
2060
  }
2061
2061
  this.settledWaiters.clear();
2062
2062
  }
2063
- write(record2, callback) {
2063
+ write(record, callback) {
2064
2064
  const child = this.child;
2065
2065
  if (!child || child.stdin.destroyed) {
2066
2066
  callback(this.notRunningError());
2067
2067
  return;
2068
2068
  }
2069
- child.stdin.write(`${JSON.stringify(record2)}
2069
+ child.stdin.write(`${JSON.stringify(record)}
2070
2070
  `, callback);
2071
2071
  }
2072
2072
  handleChildError(child, error, prefix) {
@@ -2196,18 +2196,18 @@ async function resolveWorkerAuth(registry, provider, signal, model) {
2196
2196
  timer = setTimeout(() => reject(new Error("Subagent provider authentication lookup timed out after 30 seconds")), 30000);
2197
2197
  });
2198
2198
  try {
2199
- const resolve2 = async () => {
2199
+ const resolve = async () => {
2200
2200
  try {
2201
2201
  const providerAuth = await registry.getProviderAuth(provider);
2202
2202
  if (signal.aborted || !providerAuth)
2203
2203
  return;
2204
2204
  if (model && typeof registry.getApiKeyAndHeaders === "function") {
2205
- const result2 = await registry.getApiKeyAndHeaders(model);
2206
- if (!result2.ok)
2205
+ const result = await registry.getApiKeyAndHeaders(model);
2206
+ if (!result.ok)
2207
2207
  throw new Error;
2208
2208
  return {
2209
- auth: { apiKey: result2.apiKey, headers: result2.headers, baseUrl: result2.baseUrl },
2210
- env: result2.env
2209
+ auth: { apiKey: result.apiKey, headers: result.headers, baseUrl: result.baseUrl },
2210
+ env: result.env
2211
2211
  };
2212
2212
  }
2213
2213
  return { auth: providerAuth.auth, env: providerAuth.env };
@@ -2215,7 +2215,7 @@ async function resolveWorkerAuth(registry, provider, signal, model) {
2215
2215
  throw new Error("Could not resolve subagent provider authentication");
2216
2216
  }
2217
2217
  };
2218
- const result = await Promise.race([resolve2(), interrupted]);
2218
+ const result = await Promise.race([resolve(), interrupted]);
2219
2219
  if (signal.aborted)
2220
2220
  throw new Error("Task cancelled");
2221
2221
  try {
@@ -2807,7 +2807,8 @@ function taskProgress(task) {
2807
2807
  return progress;
2808
2808
  }
2809
2809
  function workerDialogTitle(task, title) {
2810
- return `@${task.agent} · ${task.description} · ${task.id.slice(0, 8)}
2810
+ const label = `@${task.agent} · ${task.description} · ${task.id.slice(0, 8)}`;
2811
+ return title === undefined ? label : `${label}
2811
2812
  ${title}`;
2812
2813
  }
2813
2814
  function messageContentText(message) {
@@ -2873,14 +2874,14 @@ var OAUTH_REFRESH_HOSTS = {
2873
2874
  };
2874
2875
  function modelEndpoint(ctx, selectedModel) {
2875
2876
  const models = ctx.modelRegistry?.getAll() ?? [];
2876
- const qualified = models.find((model2) => `${model2.provider}/${model2.id}` === selectedModel);
2877
- const byId = models.filter((model2) => model2.id === selectedModel);
2877
+ const qualified = models.find((model) => `${model.provider}/${model.id}` === selectedModel);
2878
+ const byId = models.filter((model) => model.id === selectedModel);
2878
2879
  const registered = qualified ?? (byId.length === 1 ? byId[0] : undefined);
2879
2880
  const activeName = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
2880
2881
  const active = selectedModel === activeName || byId.length === 0 && selectedModel === ctx.model?.id ? ctx.model : undefined;
2881
2882
  const model = registered ?? active;
2882
2883
  if (!model)
2883
- return { model: selectedModel, domains: [] };
2884
+ return { error: byId.length > 1 ? "Ambiguous model" : "Model not found" };
2884
2885
  const domains = [];
2885
2886
  const endpoint = {
2886
2887
  provider: model.provider,
@@ -2897,20 +2898,21 @@ function modelEndpoint(ctx, selectedModel) {
2897
2898
  domains.push(refreshHost);
2898
2899
  return endpoint;
2899
2900
  }
2900
- async function workerEndpoint(ctx, selectedModel, signal) {
2901
+ async function workerEndpoint(ctx, selectedModel, agentName, signal) {
2901
2902
  if (signal.aborted)
2902
2903
  throw new Error("Task cancelled");
2903
2904
  const endpoint = modelEndpoint(ctx, selectedModel);
2905
+ if ("error" in endpoint) {
2906
+ throw new Error(`${endpoint.error} for subagent ${agentName}: ${selectedModel}`);
2907
+ }
2904
2908
  const { provider, domains, selected } = endpoint;
2905
- if (provider === undefined)
2906
- return endpoint;
2907
2909
  const auth = await resolveWorkerAuth(ctx.modelRegistry, provider, signal, selected);
2908
2910
  const host = workerAuthHost(auth?.auth.baseUrl);
2909
2911
  const granted = [...new Set(host ? [...domains, host] : domains)];
2910
2912
  return {
2911
2913
  ...endpoint,
2912
2914
  domains: granted,
2913
- resolveAuth: auth && selected ? workerAuthResolver(ctx.modelRegistry, selected, granted) : undefined
2915
+ resolveAuth: auth ? workerAuthResolver(ctx.modelRegistry, selected, granted) : undefined
2914
2916
  };
2915
2917
  }
2916
2918
  function agentBootstrapPaths(agentDir, privateAuth) {
@@ -2964,7 +2966,7 @@ class Semaphore {
2964
2966
  this.active += 1;
2965
2967
  return this.releaseOnce();
2966
2968
  }
2967
- await new Promise((resolve4, reject) => {
2969
+ await new Promise((resolve, reject) => {
2968
2970
  const abort = () => {
2969
2971
  const index = this.waiters.indexOf(start);
2970
2972
  if (index >= 0)
@@ -2974,7 +2976,7 @@ class Semaphore {
2974
2976
  const start = () => {
2975
2977
  signal?.removeEventListener("abort", abort);
2976
2978
  this.active += 1;
2977
- resolve4();
2979
+ resolve();
2978
2980
  };
2979
2981
  this.waiters.push(start);
2980
2982
  signal?.addEventListener("abort", abort, { once: true });
@@ -3638,7 +3640,7 @@ ${this.primaryAgent.prompt}` };
3638
3640
  selectedTask = Math.max(0, Math.min(nextSelected < 0 ? 0 : nextSelected, tasks.length - 1));
3639
3641
  };
3640
3642
  const taskTargets = () => {
3641
- const selected = tasks.filter((task2) => selectedTaskIds.has(task2.id));
3643
+ const selected = tasks.filter((task) => selectedTaskIds.has(task.id));
3642
3644
  if (selected.length > 0)
3643
3645
  return selected;
3644
3646
  const task = tasks[selectedTask];
@@ -3770,22 +3772,22 @@ ${this.primaryAgent.prompt}` };
3770
3772
  const listedAgents = agentsForTab();
3771
3773
  const agentRows = listRowBudget(tui.terminal?.rows, AGENT_LIST_ROWS, AGENT_TAB_CHROME_ROWS);
3772
3774
  const { shown, start, status } = listWindow(listedAgents, selectedAgent, agentRows);
3773
- const nameWidth = Math.min(20, Math.max(5, ...listedAgents.map((agent2) => visibleWidth(`@${agent2.name}`))));
3775
+ const nameWidth = Math.min(20, Math.max(5, ...listedAgents.map((agent) => visibleWidth(`@${agent.name}`))));
3774
3776
  const modelWidth = Math.max(10, Math.min(24, Math.floor((contentWidth - 2) / 3)));
3775
3777
  const enabledWidth = 13;
3776
3778
  const runtimeWidth = Math.max(12, contentWidth - 2 - nameWidth - 1 - modelWidth - 1 - enabledWidth - 1);
3777
- const runtimeFor = (agent2) => {
3779
+ const runtimeFor = (agent) => {
3778
3780
  const parts = [];
3779
- if (agent2.name === this.primaryAgent?.name)
3781
+ if (agent.name === this.primaryAgent?.name)
3780
3782
  parts.push("★ primary");
3781
- const byState = (state) => [...this.tasks.values()].filter((task) => task.agent === agent2.name && task.state === state).length;
3783
+ const byState = (state) => [...this.tasks.values()].filter((task) => task.agent === agent.name && task.state === state).length;
3782
3784
  const running = byState("running");
3783
3785
  const queued = byState("queued");
3784
3786
  if (running > 0)
3785
3787
  parts.push(`${running} running`);
3786
3788
  if (queued > 0)
3787
3789
  parts.push(`${queued} queued`);
3788
- const rules = [...catalog.permissions, ...agent2.permissions].length;
3790
+ const rules = [...catalog.permissions, ...agent.permissions].length;
3789
3791
  parts.push(rules > 0 ? `${rules} rules` : "ask by default");
3790
3792
  return parts.length > 0 ? parts.join(" · ") : "—";
3791
3793
  };
@@ -3794,20 +3796,20 @@ ${this.primaryAgent.prompt}` };
3794
3796
  lines.push(`${theme.fg("accent", "Filter")} ${agentFilter}${filteringAgents ? theme.fg("accent", "█") : ""}`);
3795
3797
  }
3796
3798
  lines.push(theme.fg("dim", ` ${pad("Agent", nameWidth)} ${pad("Model", modelWidth)} ${pad("Enabled", enabledWidth)} ${pad("Runtime", runtimeWidth)}`));
3797
- for (const [offset, agent2] of shown.entries()) {
3799
+ for (const [offset, agent] of shown.entries()) {
3798
3800
  const index = start + offset;
3799
3801
  const selected = index === selectedAgent;
3800
3802
  const cursor = selected ? theme.fg("accent", "›") : " ";
3801
- const disabled = agent2.disabled;
3802
- const deleting = confirmingDeleteAgent === agent2.name;
3803
- const inherited = !disabledOverrides.project.has(agent2.name);
3803
+ const disabled = agent.disabled;
3804
+ const deleting = confirmingDeleteAgent === agent.name;
3805
+ const inherited = !disabledOverrides.project.has(agent.name);
3804
3806
  const enabled = inherited ? `inherited ${disabled ? "off" : "on"}` : disabled ? "disabled" : "enabled";
3805
3807
  const color = deleting ? "error" : disabled ? "muted" : "text";
3806
- const plainName = pad(`@${agent2.name}`, nameWidth);
3807
- const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent2.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
3808
- const runtime = runtimeFor(agent2);
3809
- const line = `${cursor} ${name} ${theme.fg(color, pad(agent2.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : disabled ? "warning" : color, pad(enabled, enabledWidth))} ${theme.fg(disabled ? "muted" : "dim", pad(runtime, runtimeWidth))}`;
3810
- lines.push(agent2.name === this.primaryAgent?.name ? theme.bold(line) : line);
3808
+ const plainName = pad(`@${agent.name}`, nameWidth);
3809
+ const name = deleting ? theme.fg("error", plainName) : disabled ? theme.fg("muted", plainName) : colorizeAgentText(agent.color, plainName, (agentColor, text) => theme.fg(agentColor, text));
3810
+ const runtime = runtimeFor(agent);
3811
+ const line = `${cursor} ${name} ${theme.fg(color, pad(agent.model ?? "current model", modelWidth))} ${theme.fg(deleting ? "error" : disabled ? "warning" : color, pad(enabled, enabledWidth))} ${theme.fg(disabled ? "muted" : "dim", pad(runtime, runtimeWidth))}`;
3812
+ lines.push(agent.name === this.primaryAgent?.name ? theme.bold(line) : line);
3811
3813
  }
3812
3814
  if (listedAgents.length === 0) {
3813
3815
  lines.push(theme.fg("muted", agentFilter ? ` No ${tab} agents match ${agentFilter}` : ` No ${tab} agents configured.`));
@@ -4584,22 +4586,22 @@ ${catalog.diagnostics.join(`
4584
4586
  if (input.task_id)
4585
4587
  task.delivered = false;
4586
4588
  if (existingRun) {
4587
- const controller2 = this.controllers.get(task.id);
4589
+ const controller = this.controllers.get(task.id);
4588
4590
  let rejectCancelled;
4589
4591
  const cancelled = new Promise((_resolve, reject) => {
4590
4592
  rejectCancelled = reject;
4591
4593
  });
4592
- const abort2 = () => {
4593
- controller2?.abort();
4594
+ const abort = () => {
4595
+ controller?.abort();
4594
4596
  rejectCancelled?.(new Error("Task cancelled"));
4595
4597
  };
4596
4598
  const waitFor = (promise) => signal && !input.background ? Promise.race([promise, cancelled]) : promise;
4597
4599
  if (!input.background && signal?.aborted) {
4598
- controller2?.abort();
4600
+ controller?.abort();
4599
4601
  throw new Error("Task cancelled");
4600
4602
  }
4601
4603
  if (!input.background)
4602
- signal?.addEventListener("abort", abort2, { once: true });
4604
+ signal?.addEventListener("abort", abort, { once: true });
4603
4605
  try {
4604
4606
  const running = this.running.get(task.id);
4605
4607
  if (running) {
@@ -4629,7 +4631,7 @@ ${catalog.diagnostics.join(`
4629
4631
  this.releaseForeground(task.id);
4630
4632
  }
4631
4633
  } finally {
4632
- signal?.removeEventListener("abort", abort2);
4634
+ signal?.removeEventListener("abort", abort);
4633
4635
  }
4634
4636
  }
4635
4637
  task.state = "queued";
@@ -4880,7 +4882,7 @@ ${catalog.diagnostics.join(`
4880
4882
  const model = agent.model ?? (ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined);
4881
4883
  if (!model)
4882
4884
  throw new Error(`No model available for subagent ${agent.name}`);
4883
- const endpoint = await workerEndpoint(ctx, model, signal);
4885
+ const endpoint = await workerEndpoint(ctx, model, agent.name, signal);
4884
4886
  if (signal.aborted)
4885
4887
  throw new Error("Task cancelled");
4886
4888
  const thinking = agent.variant && PI_THINKING_LEVELS.has(agent.variant) ? agent.variant : this.pi.getThinkingLevel();
@@ -4905,7 +4907,8 @@ ${catalog.diagnostics.join(`
4905
4907
  join7(packageDir, "index.ts"),
4906
4908
  ...workerExtensions.flatMap((extension) => ["--extension", extension.entry]),
4907
4909
  ...task.sessionFile ? ["--session", task.sessionFile] : task.sessionDir ? ["--session-dir", task.sessionDir] : [],
4908
- ...endpoint.provider === undefined ? [] : ["--provider", endpoint.provider],
4910
+ "--provider",
4911
+ endpoint.provider,
4909
4912
  "--model",
4910
4913
  endpoint.model,
4911
4914
  "--thinking",
@@ -4934,6 +4937,7 @@ ${catalog.diagnostics.join(`
4934
4937
  const modelsStorePath = join7(agentDir, "models-store.json");
4935
4938
  const cliEntry = invocation.args[0] ?? invocation.command;
4936
4939
  launch = await this.integration.prepareRpcWorker({
4940
+ permissionPromptLabel: workerDialogTitle(task),
4937
4941
  command: invocation.command,
4938
4942
  args,
4939
4943
  cwd: ctx.cwd,
@@ -5148,7 +5152,7 @@ ${catalog.diagnostics.join(`
5148
5152
  }
5149
5153
  async forwardWorkerUi(ctx, task, request, signal) {
5150
5154
  if (request.method === "select" && typeof request.title === "string" && Array.isArray(request.options)) {
5151
- const options = request.options.filter((value2) => typeof value2 === "string");
5155
+ const options = request.options.filter((value) => typeof value === "string");
5152
5156
  const value = await ctx.ui.select(workerDialogTitle(task, request.title), options, {
5153
5157
  signal
5154
5158
  });
@@ -5257,10 +5261,10 @@ ${renderTaskResult(task.id, "error", message)}`);
5257
5261
  const tree = taskTreeLines(visible, (task) => {
5258
5262
  const state = taskState(theme, task);
5259
5263
  const agent = theme.fg("accent", `@${task.agent}`);
5260
- const record2 = byId.get(task.id);
5261
- const progress = taskProgress(record2);
5264
+ const record = byId.get(task.id);
5265
+ const progress = taskProgress(record);
5262
5266
  const suffix = progress.length > 0 ? theme.fg("dim", ` · ${progress.join(" · ")}`) : "";
5263
- const indentWidth = 3 + Math.max(0, record2.depth) * 3;
5267
+ const indentWidth = 3 + Math.max(0, record.depth) * 3;
5264
5268
  const descriptionWidth = Math.max(1, width - indentWidth - visibleWidth(`${state} ${agent} ${suffix}`));
5265
5269
  const description = theme.fg("text", truncateToWidth(task.description, descriptionWidth));
5266
5270
  return `${state} ${agent} ${description}${suffix}`;
@@ -5415,8 +5419,8 @@ ${renderTaskResult(task.id, failed ? "error" : "completed", value)}`;
5415
5419
  }
5416
5420
  if (agent.model) {
5417
5421
  const models = ctx.modelRegistry.getAll();
5418
- const qualified = models.filter((model2) => `${model2.provider}/${model2.id}` === agent.model);
5419
- const matches = qualified.length > 0 ? qualified : models.filter((model2) => model2.id === agent.model);
5422
+ const qualified = models.filter((model) => `${model.provider}/${model.id}` === agent.model);
5423
+ const matches = qualified.length > 0 ? qualified : models.filter((model) => model.id === agent.model);
5420
5424
  if (matches.length === 0) {
5421
5425
  ctx.ui.notify(`Model not found for primary agent ${agent.name}: ${agent.model}`, "error");
5422
5426
  return false;
@@ -5815,8 +5819,8 @@ function matchesPattern(filePath, patterns, cwd, explicitDenyGlobRoots) {
5815
5819
  }
5816
5820
  return roots.some((target) => abs === target || abs.startsWith(target.endsWith("/") ? target : `${target}/`));
5817
5821
  }
5818
- const sep5 = absPattern.endsWith("/") ? "" : "/";
5819
- return abs === absPattern || abs.startsWith(absPattern + sep5);
5822
+ const sep = absPattern.endsWith("/") ? "" : "/";
5823
+ return abs === absPattern || abs.startsWith(absPattern + sep);
5820
5824
  });
5821
5825
  }
5822
5826
  function normalizeBlockedPath(path, cwd) {
@@ -5867,9 +5871,32 @@ function extractNativeDeniedPath(output, cwd) {
5867
5871
  return normalizePathMatch(match[1], cwd);
5868
5872
  return null;
5869
5873
  }
5874
+ function extractDeniedPathMention(output, cwd, denied) {
5875
+ const start = /(?<=^|['"`\s=:(])(?=\/|[a-zA-Z]:[\\/])/g;
5876
+ for (const line of output.split(/\r?\n/)) {
5877
+ for (const match of line.matchAll(start)) {
5878
+ const raw = line.slice(match.index);
5879
+ const quoted = raw.match(/^([^'"`]+)['"`]/);
5880
+ const words = (quoted ? quoted[1] : raw).replace(/[.,:;)\]]+$/, "").split(" ");
5881
+ for (let count = words.length;count > 0; count -= 1) {
5882
+ const candidate = words.slice(0, count).join(" ");
5883
+ if (!existsSync6(candidate))
5884
+ continue;
5885
+ const path = normalizeBlockedPath(candidate, cwd);
5886
+ if (denied(path))
5887
+ return path;
5888
+ break;
5889
+ }
5890
+ }
5891
+ }
5892
+ return null;
5893
+ }
5870
5894
  function extractRetryableNativeReadDeniedPath(output, cwd, allowRead, denyRead) {
5895
+ const denied = (path) => !readAllowed(path, allowRead, denyRead, cwd);
5871
5896
  const path = extractNativeDeniedPath(output, cwd);
5872
- return path && !readAllowed(path, allowRead, denyRead, cwd) ? path : null;
5897
+ if (path)
5898
+ return denied(path) ? path : null;
5899
+ return extractDeniedPathMention(output, cwd, denied);
5873
5900
  }
5874
5901
  function extractNativeWriteDeniedPath(output, cwd) {
5875
5902
  const denial = String.raw`(?:Access is denied\.?|Operation not permitted|Permission denied)`;
@@ -5935,23 +5962,24 @@ async function stopPreparedChild(child) {
5935
5962
  child.kill("SIGKILL");
5936
5963
  if (child.exitCode !== null || child.signalCode !== null)
5937
5964
  return;
5938
- await new Promise((resolve5) => {
5965
+ await new Promise((resolve) => {
5939
5966
  let settleTimer;
5940
5967
  const done = () => {
5941
5968
  if (settleTimer)
5942
5969
  clearTimeout(settleTimer);
5943
5970
  child.removeListener("exit", done);
5944
- resolve5();
5971
+ resolve();
5945
5972
  };
5946
5973
  child.once("exit", done);
5947
5974
  settleTimer = setTimeout(done, 1000);
5948
5975
  });
5949
5976
  }
5950
- async function showPermissionPrompt(ctx, title, options, signal) {
5977
+ async function showPermissionPrompt(ctx, title, options, signal, label) {
5951
5978
  if (!ctx.hasUI)
5952
5979
  return "abort";
5953
- const labels = options.map((option2) => option2.hint ? `${option2.label} — ${option2.hint}` : option2.label);
5954
- const selected = await ctx.ui.select(title, labels, { signal });
5980
+ const labels = options.map((option) => option.hint ? `${option.label} — ${option.hint}` : option.label);
5981
+ const selected = await ctx.ui.select(label ? `${label}
5982
+ ${title}` : title, labels, { signal });
5955
5983
  if (signal?.aborted)
5956
5984
  return "abort";
5957
5985
  const index = selected === undefined ? -1 : labels.indexOf(selected);
@@ -5959,7 +5987,8 @@ async function showPermissionPrompt(ctx, title, options, signal) {
5959
5987
  if (!option)
5960
5988
  return "abort";
5961
5989
  if (option.confirm) {
5962
- const confirmed = await ctx.ui.confirm("Save this permission?", `${title}
5990
+ const confirmed = await ctx.ui.confirm(label ? `${label}
5991
+ Save this permission?` : "Save this permission?", `${title}
5963
5992
 
5964
5993
  ${option.hint ?? "This changes persisted sandbox policy."}`, { signal });
5965
5994
  if (!confirmed || signal?.aborted)
@@ -5967,9 +5996,9 @@ ${option.hint ?? "This changes persisted sandbox policy."}`, { signal });
5967
5996
  }
5968
5997
  return option.action;
5969
5998
  }
5970
- function promptReadBlock(ctx, filePath, reason, signal) {
5999
+ function promptReadBlock(ctx, filePath, reason, signal, label) {
5971
6000
  const title = reason ? `Read blocked: "${filePath}" is in denyRead (${reason})` : `Read blocked: "${filePath}" is not in allowRead`;
5972
- return showPermissionPrompt(ctx, title, PERMISSION_OPTIONS, signal);
6001
+ return showPermissionPrompt(ctx, title, PERMISSION_OPTIONS, signal, label);
5973
6002
  }
5974
6003
  function writeEnvFile(env, proxyPort, proxyToken) {
5975
6004
  const lines = [];
@@ -6285,7 +6314,7 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
6285
6314
  target.push(scope);
6286
6315
  noteScope(ctx, "Write", choice, filePath, scope);
6287
6316
  }
6288
- async function authorizeFilesystemToolAccess(ctx, accesses, options2 = {}) {
6317
+ async function authorizeFilesystemToolAccess(ctx, accesses, options = {}) {
6289
6318
  const normalized = [
6290
6319
  ...new Map(accesses.map((access) => {
6291
6320
  const path = normalizeBlockedPath(access.path, ctx.cwd);
@@ -6317,9 +6346,9 @@ function createLandstripIntegrationWithPrompts(options, permissionPrompts) {
6317
6346
  const blocked = normalized.filter((access) => access.operation === "read" ? !readAllowed(access.path, getEffectiveAllowRead(config, ctx.cwd), getEffectiveDenyRead(config, ctx.cwd), ctx.cwd) : shouldPromptForWrite(access.path, getEffectiveAllowWrite(config), ctx.cwd));
6318
6347
  const paths = blocked.map(({ operation, path }) => `${operation}: ${path}`).join(`
6319
6348
  `);
6320
- const note = options2.authorizationNote ? `
6349
+ const note = options.authorizationNote ? `
6321
6350
 
6322
- ${options2.authorizationNote}` : "";
6351
+ ${options.authorizationNote}` : "";
6323
6352
  const choice = await showPermissionPrompt(ctx, `Filesystem policy blocked this tool call:
6324
6353
  ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6325
6354
  if (choice === "abort" || promptSignal.aborted) {
@@ -6333,7 +6362,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6333
6362
  }
6334
6363
  }
6335
6364
  return { allowed: true, prompted: true };
6336
- }, options2.signal);
6365
+ }, options.signal);
6337
6366
  }
6338
6367
  async function ensureDomainAllowed(ctx, domain, cwd, allowances, signal) {
6339
6368
  const current = () => {
@@ -6348,7 +6377,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6348
6377
  if (!ctx.hasUI)
6349
6378
  return current() ?? false;
6350
6379
  return permissionPrompts.resolve(current, async (promptSignal) => {
6351
- const choice = await showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, promptSignal);
6380
+ const choice = await showPermissionPrompt(ctx, `Network blocked: "${domain}" is not in the allowed domains list`, PERMISSION_OPTIONS, promptSignal, allowances?.permissionPromptLabel);
6352
6381
  if (choice === "abort" || promptSignal.aborted)
6353
6382
  return false;
6354
6383
  await applyDomainChoice(choice, domain, cwd, allowances);
@@ -6398,7 +6427,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6398
6427
  });
6399
6428
  }
6400
6429
  function createSocketPair() {
6401
- return new Promise((resolve5, reject) => {
6430
+ return new Promise((resolve, reject) => {
6402
6431
  const server = createServer();
6403
6432
  server.on("error", reject);
6404
6433
  let client = null;
@@ -6407,7 +6436,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6407
6436
  server.close();
6408
6437
  if (client) {
6409
6438
  client.removeListener("error", reject);
6410
- resolve5([client, serverEnd]);
6439
+ resolve([client, serverEnd]);
6411
6440
  }
6412
6441
  });
6413
6442
  server.listen(0, "127.0.0.1", () => {
@@ -6421,14 +6450,14 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6421
6450
  async function resolveTrapQuery(trap, ctx, cwd, allowances, promptOnBlock, signal) {
6422
6451
  if (!isFilesystemTrap(trap)) {
6423
6452
  const key = `${trap.operation}\x00${trap.target}`;
6424
- const current2 = () => sessionAllowedTargets.includes(key) || allowances.targets.includes(key) ? { action: "allow" } : undefined;
6425
- const existing2 = current2();
6426
- if (existing2)
6427
- return existing2;
6453
+ const current = () => sessionAllowedTargets.includes(key) || allowances.targets.includes(key) ? { action: "allow" } : undefined;
6454
+ const existing = current();
6455
+ if (existing)
6456
+ return existing;
6428
6457
  if (!ctx.hasUI || !promptOnBlock)
6429
6458
  return { action: "deny", reason: "unprompted" };
6430
- return permissionPrompts.resolve(current2, async (promptSignal) => {
6431
- const choice = await showPermissionPrompt(ctx, `Network blocked: ${trap.operation} "${trap.target}"`, NETWORK_PERMISSION_OPTIONS, promptSignal);
6459
+ return permissionPrompts.resolve(current, async (promptSignal) => {
6460
+ const choice = await showPermissionPrompt(ctx, `Network blocked: ${trap.operation} "${trap.target}"`, NETWORK_PERMISSION_OPTIONS, promptSignal, allowances.permissionPromptLabel);
6432
6461
  if (choice === "abort" || promptSignal.aborted)
6433
6462
  return { action: "deny", reason: "rejected" };
6434
6463
  const targets = choice === "once" ? allowances.targets : sessionAllowedTargets;
@@ -6462,7 +6491,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6462
6491
  return { action: "deny", reason: "unprompted" };
6463
6492
  return permissionPrompts.resolve(current, async (promptSignal) => {
6464
6493
  const config = loadConfig(cwd);
6465
- const choice = trap.operation === "read" ? await promptReadBlock(ctx, path, matchesPattern(path, config.filesystem.denyRead, cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await showPermissionPrompt(ctx, `Write blocked: "${path}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal);
6494
+ const choice = trap.operation === "read" ? await promptReadBlock(ctx, path, matchesPattern(path, config.filesystem.denyRead, cwd) ? "granting allowRead will override it" : undefined, promptSignal, allowances.permissionPromptLabel) : await showPermissionPrompt(ctx, `Write blocked: "${path}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal, allowances.permissionPromptLabel);
6466
6495
  if (choice === "abort" || promptSignal.aborted)
6467
6496
  return { action: "deny", reason: "rejected" };
6468
6497
  if (trap.operation === "read") {
@@ -6519,7 +6548,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6519
6548
  }
6520
6549
  });
6521
6550
  }
6522
- function prepareUnsandboxedRpcWorker(options2) {
6551
+ function prepareUnsandboxedRpcWorker(options) {
6523
6552
  let spawned = false;
6524
6553
  let disposed = false;
6525
6554
  let disposePromise;
@@ -6532,8 +6561,8 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6532
6561
  spawned = true;
6533
6562
  const child = spawn2(command, [...args], {
6534
6563
  ...spawnOptions,
6535
- cwd: options2.cwd,
6536
- env: options2.env,
6564
+ cwd: options.cwd,
6565
+ env: options.env,
6537
6566
  detached: true
6538
6567
  });
6539
6568
  patchProcessGroupKill(child);
@@ -6541,10 +6570,10 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6541
6570
  return child;
6542
6571
  };
6543
6572
  return {
6544
- command: options2.command,
6545
- args: options2.args,
6546
- cwd: options2.cwd,
6547
- env: options2.env,
6573
+ command: options.command,
6574
+ args: options.args,
6575
+ cwd: options.cwd,
6576
+ env: options.env,
6548
6577
  spawn: spawnWorker,
6549
6578
  dispose() {
6550
6579
  disposePromise ??= (async () => {
@@ -6555,65 +6584,66 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6555
6584
  }
6556
6585
  };
6557
6586
  }
6558
- async function prepareRpcWorker(options2) {
6559
- if (options2.signal?.aborted)
6587
+ async function prepareRpcWorker(options) {
6588
+ if (options.signal?.aborted)
6560
6589
  throw new Error("Task cancelled");
6561
- if (!ensureSandboxState(options2.ctx)) {
6590
+ if (!ensureSandboxState(options.ctx)) {
6562
6591
  if (sandboxState === "unavailable") {
6563
6592
  throw new Error("Sandbox is unavailable; refusing subagent process");
6564
6593
  }
6565
6594
  if (!unsandboxedWorkerWarningShown) {
6566
- warnUnsandboxed(options2.ctx, "Subagent processes are running without Landstrip sandboxing", "warning");
6595
+ warnUnsandboxed(options.ctx, "Subagent processes are running without Landstrip sandboxing", "warning");
6567
6596
  unsandboxedWorkerWarningShown = true;
6568
6597
  }
6569
- return prepareUnsandboxedRpcWorker(options2);
6598
+ return prepareUnsandboxedRpcWorker(options);
6570
6599
  }
6571
6600
  const allowances = {
6572
- domains: [...options2.domains ?? []],
6573
- readPaths: [...options2.readPaths],
6574
- writePaths: [...options2.writePaths],
6601
+ permissionPromptLabel: options.permissionPromptLabel,
6602
+ domains: [...options.domains ?? []],
6603
+ readPaths: [...options.readPaths],
6604
+ writePaths: [...options.writePaths],
6575
6605
  targets: [],
6576
6606
  protectedPaths: [
6577
- ...new Set((options2.protectedPaths ?? []).flatMap((path) => {
6607
+ ...new Set((options.protectedPaths ?? []).flatMap((path) => {
6578
6608
  if (/[*?[\]]/.test(path)) {
6579
6609
  throw new Error(`Worker protected path "${path}" must be a concrete path, not a glob`);
6580
6610
  }
6581
- return [expandPath(path, options2.cwd), canonicalizePath2(path, options2.cwd)];
6611
+ return [expandPath(path, options.cwd), canonicalizePath2(path, options.cwd)];
6582
6612
  }))
6583
6613
  ]
6584
6614
  };
6585
- const processAllowances = withWindowsProcessReadAccess(allowances, options2.command, options2.cwd);
6586
- const config = loadConfig(options2.cwd);
6615
+ const processAllowances = withWindowsProcessReadAccess(allowances, options.command, options.cwd);
6616
+ const config = loadConfig(options.cwd);
6587
6617
  const explicitDenyGlobRoots = new Map;
6588
6618
  processAllowances.explicitDenyRead = {
6589
6619
  patterns: config.explicitDenyRead,
6590
6620
  globRoots: explicitDenyGlobRoots
6591
6621
  };
6592
- for (const path of [options2.cwd, options2.command, ...processAllowances.readPaths]) {
6622
+ for (const path of [options.cwd, options.command, ...processAllowances.readPaths]) {
6593
6623
  if (/[*?[\]]/.test(path)) {
6594
6624
  throw new Error(`Worker startup read path "${path}" must be a concrete path, not a glob`);
6595
6625
  }
6596
- const deny = config.explicitDenyRead.find((pattern) => matchesPattern(path, [pattern], options2.cwd, explicitDenyGlobRoots));
6626
+ const deny = config.explicitDenyRead.find((pattern) => matchesPattern(path, [pattern], options.cwd, explicitDenyGlobRoots));
6597
6627
  if (deny !== undefined) {
6598
6628
  throw new Error(`Worker startup read path "${path}" conflicts with explicit filesystem.denyRead "${deny}"; change the configured deny or the worker startup paths`);
6599
6629
  }
6600
6630
  }
6601
6631
  const proxyToken = randomBytes(32).toString("base64url");
6602
6632
  const proxyAuthorization = `Basic ${Buffer.from(`landstrip:${proxyToken}`).toString("base64")}`;
6603
- const proxy = shouldStartProxy(config) ? await startProxy(options2.ctx, options2.cwd, processAllowances, options2.signal, proxyAuthorization, restrictedUserProxyPortRange()) : null;
6633
+ const proxy = shouldStartProxy(config) ? await startProxy(options.ctx, options.cwd, processAllowances, options.signal, proxyAuthorization, restrictedUserProxyPortRange()) : null;
6604
6634
  let policy;
6605
6635
  let trapSocket;
6606
6636
  let childEnd;
6607
6637
  let disposed = false;
6608
6638
  try {
6609
- if (options2.signal?.aborted)
6639
+ if (options.signal?.aborted)
6610
6640
  throw new Error("Task cancelled");
6611
- policy = writePolicyFile(options2.cwd, proxy?.port ?? null, "worker", processAllowances);
6641
+ policy = writePolicyFile(options.cwd, proxy?.port ?? null, "worker", processAllowances);
6612
6642
  if (process.platform !== "win32") {
6613
6643
  [trapSocket, childEnd] = await createSocketPair();
6614
- if (options2.signal?.aborted)
6644
+ if (options.signal?.aborted)
6615
6645
  throw new Error("Task cancelled");
6616
- attachWorkerTrap(trapSocket, options2.ctx, options2.cwd, processAllowances, options2.signal);
6646
+ attachWorkerTrap(trapSocket, options.ctx, options.cwd, processAllowances, options.signal);
6617
6647
  }
6618
6648
  } catch (error) {
6619
6649
  trapSocket?.destroy();
@@ -6626,7 +6656,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6626
6656
  const workerPolicy = policy;
6627
6657
  const workerTrapSocket = trapSocket;
6628
6658
  const workerChildEnd = childEnd;
6629
- const workerEnv = { ...options2.env };
6659
+ const workerEnv = { ...options.env };
6630
6660
  if (proxy) {
6631
6661
  const url = `http://landstrip:${proxyToken}@127.0.0.1:${proxy.port}`;
6632
6662
  for (const name of PROXY_ENVIRONMENT_VARIABLES) {
@@ -6649,8 +6679,8 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6649
6679
  "-p",
6650
6680
  workerPolicy.path,
6651
6681
  "--",
6652
- options2.command,
6653
- ...options2.args
6682
+ options.command,
6683
+ ...options.args
6654
6684
  ];
6655
6685
  const authPipe = Array.isArray(spawnOptions.stdio) && spawnOptions.stdio[WORKER_AUTH_FD] === "pipe";
6656
6686
  if (authPipe && process.platform === "win32") {
@@ -6667,7 +6697,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6667
6697
  landstripArgs.splice(1, 0, "--trap", "3");
6668
6698
  const child = spawn2(binaryPath(), landstripArgs, {
6669
6699
  ...spawnOptions,
6670
- cwd: options2.cwd,
6700
+ cwd: options.cwd,
6671
6701
  env: workerEnv,
6672
6702
  detached: true,
6673
6703
  stdio
@@ -6678,9 +6708,9 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6678
6708
  return child;
6679
6709
  };
6680
6710
  return {
6681
- command: options2.command,
6682
- args: options2.args,
6683
- cwd: options2.cwd,
6711
+ command: options.command,
6712
+ args: options.args,
6713
+ cwd: options.cwd,
6684
6714
  env: workerEnv,
6685
6715
  spawn: spawnWorker,
6686
6716
  dispose() {
@@ -6696,15 +6726,15 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6696
6726
  }
6697
6727
  };
6698
6728
  }
6699
- async function prepareProcess(options2) {
6700
- if (process.platform === "win32" && (getNoSandboxFlag() || !loadConfig(options2.cwd).enabled)) {
6729
+ async function prepareProcess(options) {
6730
+ if (process.platform === "win32" && (getNoSandboxFlag() || !loadConfig(options.cwd).enabled)) {
6701
6731
  throw new Error("Generic process preparation requires sandboxing on Windows");
6702
6732
  }
6703
6733
  return prepareRpcWorker({
6704
- ...options2,
6705
- env: options2.env ?? process.env,
6706
- readPaths: options2.readPaths ?? [],
6707
- writePaths: options2.writePaths ?? []
6734
+ ...options,
6735
+ env: options.env ?? process.env,
6736
+ readPaths: options.readPaths ?? [],
6737
+ writePaths: options.writePaths ?? []
6708
6738
  });
6709
6739
  }
6710
6740
  function createProviderBashOps() {
@@ -7096,16 +7126,16 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
7096
7126
  return null;
7097
7127
  const { globalPath, projectPath } = getPiConfigPaths(ctx.cwd, "sandbox.json");
7098
7128
  const current = () => {
7099
- const config2 = loadConfig(ctx.cwd);
7100
- if (matchesPattern(blockedPath, operation === "read" ? config2.filesystem.denyReadAlways : config2.filesystem.denyWrite, ctx.cwd)) {
7129
+ const config = loadConfig(ctx.cwd);
7130
+ if (matchesPattern(blockedPath, operation === "read" ? config.filesystem.denyReadAlways : config.filesystem.denyWrite, ctx.cwd)) {
7101
7131
  return false;
7102
7132
  }
7103
- const needsPrompt = operation === "read" ? !readAllowed(blockedPath, getEffectiveAllowRead(config2, ctx.cwd, allowances), getEffectiveDenyRead(config2, ctx.cwd), ctx.cwd) : shouldPromptForWrite(blockedPath, getEffectiveAllowWrite(config2, allowances), ctx.cwd);
7133
+ const needsPrompt = operation === "read" ? !readAllowed(blockedPath, getEffectiveAllowRead(config, ctx.cwd, allowances), getEffectiveDenyRead(config, ctx.cwd), ctx.cwd) : shouldPromptForWrite(blockedPath, getEffectiveAllowWrite(config, allowances), ctx.cwd);
7104
7134
  return needsPrompt ? undefined : true;
7105
7135
  };
7106
7136
  const access = await permissionPrompts.resolve(current, async (promptSignal) => {
7107
- const config2 = loadConfig(ctx.cwd);
7108
- const choice = operation === "read" ? await promptReadBlock(ctx, blockedPath, matchesPattern(blockedPath, config2.filesystem.denyRead, ctx.cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await showPermissionPrompt(ctx, `Write blocked: "${blockedPath}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal);
7137
+ const config = loadConfig(ctx.cwd);
7138
+ const choice = operation === "read" ? await promptReadBlock(ctx, blockedPath, matchesPattern(blockedPath, config.filesystem.denyRead, ctx.cwd) ? "granting allowRead will override it" : undefined, promptSignal) : await showPermissionPrompt(ctx, `Write blocked: "${blockedPath}" is not in allowWrite`, PERMISSION_OPTIONS, promptSignal);
7109
7139
  if (choice === "abort" || promptSignal.aborted)
7110
7140
  return false;
7111
7141
  if (operation === "read") {
@@ -7116,8 +7146,8 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
7116
7146
  return true;
7117
7147
  }, signal);
7118
7148
  if (!access) {
7119
- const config2 = loadConfig(ctx.cwd);
7120
- if (operation === "write" && matchesPattern(blockedPath, config2.filesystem.denyWrite, ctx.cwd)) {
7149
+ const config = loadConfig(ctx.cwd);
7150
+ if (operation === "write" && matchesPattern(blockedPath, config.filesystem.denyWrite, ctx.cwd)) {
7121
7151
  notify(ctx, `"${blockedPath}" is blocked by denyWrite. Check:
7122
7152
  ${projectPath}
7123
7153
  ${globalPath}`, "warning");
@@ -7153,21 +7183,21 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
7153
7183
  const errorText = formatError(error);
7154
7184
  const fallbackOutput = `${stderrOutput}
7155
7185
  ${errorText}`;
7156
- const blockedWritePath2 = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(fallbackOutput, ctx.cwd);
7157
- if (blockedWritePath2) {
7158
- const retryResult2 = await retryWithAccess("write", blockedWritePath2);
7159
- if (retryResult2)
7160
- return retryResult2;
7186
+ const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(fallbackOutput, ctx.cwd);
7187
+ if (blockedWritePath) {
7188
+ const retryResult = await retryWithAccess("write", blockedWritePath);
7189
+ if (retryResult)
7190
+ return retryResult;
7161
7191
  }
7162
- const blockedReadPath2 = blockedReadPathForRetry(fallbackOutput);
7163
- if (blockedReadPath2) {
7164
- const retryResult2 = await retryWithAccess("read", blockedReadPath2);
7165
- if (retryResult2)
7166
- return retryResult2;
7192
+ const blockedReadPath = blockedReadPathForRetry(fallbackOutput);
7193
+ if (blockedReadPath) {
7194
+ const retryResult = await retryWithAccess("read", blockedReadPath);
7195
+ if (retryResult)
7196
+ return retryResult;
7167
7197
  }
7168
- const landstripErrors2 = parseLandstripTraps(landstripErrorOutput);
7169
- if (landstripErrors2.length > 0) {
7170
- throw new Error(formatLandstripTraps(landstripErrors2));
7198
+ const landstripErrors = parseLandstripTraps(landstripErrorOutput);
7199
+ if (landstripErrors.length > 0) {
7200
+ throw new Error(formatLandstripTraps(landstripErrors));
7171
7201
  }
7172
7202
  throw error;
7173
7203
  }
@@ -7180,9 +7210,9 @@ ${message}
7180
7210
  }
7181
7211
  const blockedWritePath = extractTrapBlockedPath(landstripErrorOutput, ctx.cwd, "write") ?? extractNativeWriteDeniedPath(stderrOutput, ctx.cwd);
7182
7212
  if (blockedWritePath) {
7183
- const retryResult2 = await retryWithAccess("write", blockedWritePath);
7184
- if (retryResult2)
7185
- return retryResult2;
7213
+ const retryResult = await retryWithAccess("write", blockedWritePath);
7214
+ if (retryResult)
7215
+ return retryResult;
7186
7216
  }
7187
7217
  const blockedReadPath = blockedReadPathForRetry(stderrOutput);
7188
7218
  if (!blockedReadPath)
@@ -7240,7 +7270,7 @@ ${message}
7240
7270
  networkLabel = `${config.network.allowedDomains.length} domains`;
7241
7271
  networkColor = "accent";
7242
7272
  }
7243
- const sep5 = theme.fg("dim", "·");
7273
+ const sep = theme.fg("dim", "·");
7244
7274
  const net = theme.fg(networkColor, networkLabel);
7245
7275
  const write = theme.fg("accent", `${config.filesystem.allowWrite.length} write paths`);
7246
7276
  const fields = [`${dot} ${label}`, net, write];
@@ -7249,7 +7279,7 @@ ${message}
7249
7279
  const implementationLabel = windowsImplementation === "restrictedUser" ? "restricted user" : `${config.windows.appContainerMode} AppContainer`;
7250
7280
  fields.splice(1, 0, theme.fg(standard ? "warning" : "accent", implementationLabel));
7251
7281
  }
7252
- setTuiStatus(ctx, "sandbox", fields.join(` ${sep5} `));
7282
+ setTuiStatus(ctx, "sandbox", fields.join(` ${sep} `));
7253
7283
  }
7254
7284
  const headlessWarnings = new Set;
7255
7285
  function warnUnsandboxed(ctx, message, level) {
@@ -228,17 +228,17 @@ async function main() {
228
228
  const agentDir = getAgentDir();
229
229
  const sessionManager = args.session ? SessionManager.open(args.session, args.sessionDir) : args.noSession ? SessionManager.inMemory(cwd) : SessionManager.create(cwd, args.sessionDir);
230
230
  const absolutePaths = (paths) => paths?.map((path) => resolve(cwd, path));
231
- const runtime = await createAgentSessionRuntime(async ({ cwd: cwd2, sessionManager: sessionManager2, sessionStartEvent }) => {
231
+ const runtime = await createAgentSessionRuntime(async ({ cwd, sessionManager, sessionStartEvent }) => {
232
232
  const catalog = await ModelRuntime.create({
233
233
  credentials: new InMemoryCredentialStore,
234
234
  allowModelNetwork: false,
235
235
  refreshOnCreate: false
236
236
  });
237
237
  const services = await createAgentSessionServices({
238
- cwd: cwd2,
238
+ cwd,
239
239
  agentDir,
240
240
  modelRuntime: catalog,
241
- settingsManager: SettingsManager.create(cwd2, agentDir, {
241
+ settingsManager: SettingsManager.create(cwd, agentDir, {
242
242
  projectTrusted: args.projectTrustOverride === true
243
243
  }),
244
244
  extensionFlagValues: args.unknownFlags,
@@ -274,7 +274,7 @@ async function main() {
274
274
  return {
275
275
  ...await createAgentSessionFromServices({
276
276
  services,
277
- sessionManager: sessionManager2,
277
+ sessionManager,
278
278
  sessionStartEvent,
279
279
  model: initial.model,
280
280
  thinkingLevel: args.thinking,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.19.1",
3
+ "version": "0.19.3",
4
4
  "description": "Sandboxed Bash and process-backed agents for Pi",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -39,22 +39,22 @@
39
39
  "ci:test": "npm run test"
40
40
  },
41
41
  "dependencies": {
42
- "@landstrip/landstrip-api": "^0.19.1",
42
+ "@landstrip/landstrip-api": "^0.19.3",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "undici": "^8.9.0",
46
46
  "yaml": "^2.9.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@earendil-works/pi-ai": "^0.84.1",
49
+ "@earendil-works/pi-ai": "^0.85.1",
50
50
  "@earendil-works/pi-coding-agent": "^0.85.1",
51
51
  "@earendil-works/pi-tui": "^0.85.1",
52
52
  "@types/node": "^26.4.1",
53
- "oxfmt": "^0.64.0",
53
+ "oxfmt": "^0.67.0",
54
54
  "oxlint": "^1.68.0",
55
55
  "typebox": "^1.3.6",
56
56
  "typescript": "^5.0.0",
57
- "vitest": "^4.1.9"
57
+ "vitest": "^5.0.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@earendil-works/pi-ai": ">=0.82.0",