pi-landstrip 0.19.1 → 0.19.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.
Files changed (2) hide show
  1. package/dist/index.ts +47 -17
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -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) {
@@ -2880,7 +2881,7 @@ function modelEndpoint(ctx, selectedModel) {
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) {
@@ -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,
@@ -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 = (path2) => !readAllowed(path2, 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)`;
@@ -5947,11 +5974,12 @@ async function stopPreparedChild(child) {
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
5980
  const labels = options.map((option2) => option2.hint ? `${option2.label} — ${option2.hint}` : option2.label);
5954
- const selected = await ctx.ui.select(title, labels, { signal });
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 = [];
@@ -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);
@@ -6428,7 +6457,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6428
6457
  if (!ctx.hasUI || !promptOnBlock)
6429
6458
  return { action: "deny", reason: "unprompted" };
6430
6459
  return permissionPrompts.resolve(current2, async (promptSignal) => {
6431
- const choice = await showPermissionPrompt(ctx, `Network blocked: ${trap.operation} "${trap.target}"`, NETWORK_PERMISSION_OPTIONS, 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") {
@@ -6569,6 +6598,7 @@ ${paths}${note}`, PERMISSION_OPTIONS, promptSignal);
6569
6598
  return prepareUnsandboxedRpcWorker(options2);
6570
6599
  }
6571
6600
  const allowances = {
6601
+ permissionPromptLabel: options2.permissionPromptLabel,
6572
6602
  domains: [...options2.domains ?? []],
6573
6603
  readPaths: [...options2.readPaths],
6574
6604
  writePaths: [...options2.writePaths],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Sandboxed Bash and process-backed agents for Pi",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -39,7 +39,7 @@
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.2",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "undici": "^8.9.0",