@ai-sdk/harness-pi 1.0.71 → 1.0.72

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @ai-sdk/harness-pi
2
2
 
3
+ ## 1.0.72
4
+
5
+ ### Patch Changes
6
+
7
+ - 83fe754: chore(harness): simplify the `auth` param to be a simple string to choose the auth method
8
+ - 0f5de2d: fix(harness-pi): ensure Pi's `.sessions` infra directory does not pollute the agent's working directory
9
+ - Updated dependencies [8d717b3]
10
+ - @ai-sdk/harness@1.0.71
11
+
3
12
  ## 1.0.71
4
13
 
5
14
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -3,12 +3,14 @@ import { HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
3
3
  import { ExtensionFactory } from '@earendil-works/pi-coding-agent';
4
4
 
5
5
  /**
6
- * Pi auth options. Exactly one of `gateway` or `customEnv` is honoured
7
- * (precedence: explicit `customEnv`, then explicit `gateway`, then ambient
8
- * gateway from `process.env`). To use multiple providers, use `customEnv`
9
- * with the standard `<PREFIX>_API_KEY` / `<PREFIX>_BASE_URL` pattern.
6
+ * Pi auth options. Choose an explicit mode or rely on 'auto' (precedence:
7
+ * explicit gateway, then OpenAI / Anthropic / custom environment variables).
10
8
  */
11
- type PiAuthOptions = {
9
+ type PiAuthenticationMode = 'auto' | 'openai' | 'anthropic' | 'custom' | 'ai-gateway';
10
+ /**
11
+ * @deprecated Passing an object to auth options is deprecated. Use a `PiAuthenticationMode` string value ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.
12
+ */
13
+ type LegacyPiAuthOptions = {
12
14
  readonly gateway?: {
13
15
  readonly apiKey?: string;
14
16
  readonly baseUrl?: string;
@@ -25,6 +27,7 @@ type PiAuthOptions = {
25
27
  */
26
28
  readonly customEnv?: Record<string, string>;
27
29
  };
30
+ type PiAuthOptions = PiAuthenticationMode | LegacyPiAuthOptions;
28
31
 
29
32
  type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
30
33
 
@@ -140,4 +143,4 @@ declare const pi: _ai_sdk_harness.HarnessV1<{
140
143
  readonly ls: _ai_sdk_harness.HarnessV1BuiltinTool;
141
144
  }>;
142
145
 
143
- export { type PiAuthOptions, type PiHarnessSettings, VERSION, createPi, pi };
146
+ export { type PiAuthOptions, type PiAuthenticationMode, type PiHarnessSettings, VERSION, createPi, pi };
package/dist/index.js CHANGED
@@ -6,9 +6,9 @@ import { tool } from "@ai-sdk/provider-utils";
6
6
  import { z as z3 } from "zod/v4";
7
7
 
8
8
  // src/pi-resume-state.ts
9
+ import { createHash } from "crypto";
9
10
  import { readFile, writeFile, mkdir } from "fs/promises";
10
11
  import path from "path";
11
- import { shellQuote } from "@ai-sdk/harness/utils";
12
12
  import { z } from "zod/v4";
13
13
  var PI_SESSION_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/;
14
14
  function safePiSessionFileName(sessionFileName) {
@@ -24,7 +24,25 @@ var piSessionFileNameSchema = z.string().refine(
24
24
  var piResumeStateSchema = z.looseObject({
25
25
  sessionFileName: piSessionFileNameSchema.optional()
26
26
  });
27
- var PI_SESSIONS_DIR = ".pi-sessions";
27
+ function resolvePiPrivateSessionDirectory(input) {
28
+ const sessionKey = createHash("sha256").update(input.sessionId).digest("hex");
29
+ const privateSessionDir = path.posix.join(
30
+ input.sandboxHomeDir,
31
+ ".ai-sdk",
32
+ "harness-pi",
33
+ sessionKey
34
+ );
35
+ const relativePath = path.posix.relative(
36
+ input.sessionWorkDir,
37
+ privateSessionDir
38
+ );
39
+ if (relativePath === "" || !relativePath.startsWith("../") && !path.posix.isAbsolute(relativePath)) {
40
+ throw new Error(
41
+ `Pi private session directory ${JSON.stringify(privateSessionDir)} must be outside sessionWorkDir ${JSON.stringify(input.sessionWorkDir)}.`
42
+ );
43
+ }
44
+ return privateSessionDir;
45
+ }
28
46
  function resolveContainedHostPath(input) {
29
47
  const baseDir = path.resolve(input.baseDir);
30
48
  const filePath = path.resolve(
@@ -38,7 +56,7 @@ function resolveContainedHostPath(input) {
38
56
  return filePath;
39
57
  }
40
58
  function resolveContainedSandboxPath(input) {
41
- const sessionDir = path.posix.resolve(input.sessionWorkDir, PI_SESSIONS_DIR);
59
+ const sessionDir = path.posix.resolve(input.privateSessionDir);
42
60
  const filePath = path.posix.resolve(
43
61
  sessionDir,
44
62
  safePiSessionFileName(input.sessionFileName)
@@ -56,13 +74,9 @@ async function persistSessionFileToSandbox(args) {
56
74
  });
57
75
  const content = await readFile(hostPath);
58
76
  const remotePath = resolveContainedSandboxPath({
59
- sessionWorkDir: args.sessionWorkDir,
77
+ privateSessionDir: args.privateSessionDir,
60
78
  sessionFileName: args.sessionFileName
61
79
  });
62
- await args.sandbox.run({
63
- command: `mkdir -p ${shellQuote(path.posix.dirname(remotePath))}`,
64
- ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
65
- });
66
80
  await args.sandbox.writeBinaryFile({
67
81
  path: remotePath,
68
82
  content,
@@ -71,14 +85,14 @@ async function persistSessionFileToSandbox(args) {
71
85
  }
72
86
  async function pullSessionFileFromSandbox(args) {
73
87
  const remotePath = resolveContainedSandboxPath({
74
- sessionWorkDir: args.sessionWorkDir,
88
+ privateSessionDir: args.privateSessionDir,
75
89
  sessionFileName: args.sessionFileName
76
90
  });
77
91
  const bytes = await args.sandbox.readBinaryFile({
78
92
  path: remotePath,
79
93
  ...args.abortSignal ? { abortSignal: args.abortSignal } : {}
80
94
  });
81
- if (!bytes) return void 0;
95
+ if (bytes == null) return void 0;
82
96
  await mkdir(args.hostSessionDir, { recursive: true });
83
97
  const hostPath = resolveContainedHostPath({
84
98
  baseDir: args.hostSessionDir,
@@ -108,7 +122,7 @@ import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
108
122
  import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
109
123
 
110
124
  // src/version.ts
111
- var VERSION = true ? "1.0.71" : "0.0.0-test";
125
+ var VERSION = true ? "1.0.72" : "0.0.0-test";
112
126
 
113
127
  // src/pi-auth.ts
114
128
  var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
@@ -149,27 +163,75 @@ function resolvePiEnv({
149
163
  options,
150
164
  env
151
165
  }) {
152
- const customEnvConfigured = hasConfiguredValue(options?.customEnv);
166
+ const normalizedOptions = normalizePiAuthToLegacyAuth(options);
167
+ const customEnvConfigured = hasConfiguredValue(normalizedOptions?.customEnv);
153
168
  if (customEnvConfigured) {
154
- return resolveCustomEnv({ customEnv: options.customEnv ?? {} });
169
+ return resolveCustomEnv({ customEnv: normalizedOptions.customEnv ?? {} });
155
170
  }
156
- const gatewayConfigured = hasConfiguredValue(options?.gateway);
171
+ const gatewayConfigured = hasConfiguredValue(normalizedOptions?.gateway);
157
172
  const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env });
158
173
  if (gatewayConfigured) {
159
- const apiKey = options.gateway?.apiKey ?? gatewayAuthFromEnv.apiKey;
160
- const baseUrl = options.gateway?.baseUrl ?? gatewayAuthFromEnv.baseUrl;
174
+ const apiKey = normalizedOptions.gateway?.apiKey ?? gatewayAuthFromEnv.apiKey;
175
+ const baseUrl = normalizedOptions.gateway?.baseUrl ?? gatewayAuthFromEnv.baseUrl;
161
176
  if (apiKey) {
162
177
  return { AI_GATEWAY_API_KEY: apiKey, AI_GATEWAY_BASE_URL: baseUrl };
163
178
  }
164
179
  return {};
165
180
  }
181
+ if (typeof options === "string") {
182
+ switch (options) {
183
+ case "openai":
184
+ if (env.OPENAI_API_KEY) {
185
+ return {
186
+ OPENAI_API_KEY: env.OPENAI_API_KEY,
187
+ ...env.OPENAI_BASE_URL ? { OPENAI_BASE_URL: env.OPENAI_BASE_URL } : {}
188
+ };
189
+ }
190
+ return {};
191
+ case "anthropic":
192
+ if (env.ANTHROPIC_API_KEY) {
193
+ return {
194
+ ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY,
195
+ ...env.ANTHROPIC_BASE_URL ? { ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL } : {},
196
+ ...env.ANTHROPIC_AUTH_TOKEN ? { ANTHROPIC_AUTH_TOKEN: env.ANTHROPIC_AUTH_TOKEN } : {}
197
+ };
198
+ }
199
+ return {};
200
+ case "custom": {
201
+ const result = {};
202
+ for (const [key, value] of Object.entries(env)) {
203
+ if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
204
+ result[key] = value;
205
+ }
206
+ }
207
+ return result;
208
+ }
209
+ case "ai-gateway":
210
+ if (gatewayAuthFromEnv.apiKey) {
211
+ return {
212
+ AI_GATEWAY_API_KEY: gatewayAuthFromEnv.apiKey,
213
+ AI_GATEWAY_BASE_URL: gatewayAuthFromEnv.baseUrl
214
+ };
215
+ }
216
+ return {};
217
+ case "auto":
218
+ default:
219
+ break;
220
+ }
221
+ }
166
222
  if (gatewayAuthFromEnv.apiKey) {
167
223
  return {
168
224
  AI_GATEWAY_API_KEY: gatewayAuthFromEnv.apiKey,
169
225
  AI_GATEWAY_BASE_URL: gatewayAuthFromEnv.baseUrl
170
226
  };
171
227
  }
172
- return {};
228
+ const ambient = {};
229
+ for (const [key, value] of Object.entries(env)) {
230
+ if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
231
+ ambient[key] = value;
232
+ }
233
+ }
234
+ return ambient;
173
235
  }
174
236
  async function registerPiProviders({
175
237
  options,
@@ -177,14 +239,91 @@ async function registerPiProviders({
177
239
  registries,
178
240
  clientApp = HARNESS_CLIENT_APP
179
241
  }) {
180
- if (hasConfiguredValue(options?.customEnv)) {
242
+ const normalizedOptions = normalizePiAuthToLegacyAuth(options);
243
+ if (hasConfiguredValue(normalizedOptions?.customEnv)) {
181
244
  await registerCustomProviders({
182
- customEnv: options.customEnv ?? {},
245
+ customEnv: normalizedOptions.customEnv ?? {},
183
246
  registries,
184
247
  clientApp
185
248
  });
186
249
  return;
187
250
  }
251
+ const mode = typeof options === "string" ? options : options == null ? "auto" : "legacy";
252
+ switch (mode) {
253
+ case "openai": {
254
+ const env = pickOpenAIEnv(resolvedEnv);
255
+ await registerCustomProviders({
256
+ customEnv: { ...pickOpenAIEnv(process.env), ...env },
257
+ registries,
258
+ clientApp
259
+ });
260
+ return;
261
+ }
262
+ case "anthropic": {
263
+ const env = pickAnthropicEnv(resolvedEnv);
264
+ await registerCustomProviders({
265
+ customEnv: { ...pickAnthropicEnv(process.env), ...env },
266
+ registries,
267
+ clientApp
268
+ });
269
+ return;
270
+ }
271
+ case "custom": {
272
+ const env = pickProviderEnv(resolvedEnv);
273
+ await registerCustomProviders({
274
+ customEnv: { ...pickProviderEnv(process.env), ...env },
275
+ registries,
276
+ clientApp
277
+ });
278
+ return;
279
+ }
280
+ case "ai-gateway": {
281
+ const gatewayAuth = getAiGatewayAuthFromEnv({ env: process.env });
282
+ const gatewayApiKey = resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
283
+ const gatewayBaseUrl = resolvedEnv.AI_GATEWAY_BASE_URL ?? gatewayAuth.baseUrl;
284
+ if (!gatewayApiKey) return;
285
+ await register({
286
+ registries,
287
+ provider: "vercel-ai-gateway",
288
+ apiKey: gatewayApiKey,
289
+ config: createGatewayProviderConfig({
290
+ apiKey: gatewayApiKey,
291
+ baseUrl: gatewayBaseUrl,
292
+ clientApp
293
+ })
294
+ });
295
+ return;
296
+ }
297
+ case "legacy":
298
+ break;
299
+ // handled below
300
+ case "auto":
301
+ default: {
302
+ const gatewayAuth = getAiGatewayAuthFromEnv({ env: process.env });
303
+ const gatewayApiKey = resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
304
+ const gatewayBaseUrl = resolvedEnv.AI_GATEWAY_BASE_URL ?? gatewayAuth.baseUrl;
305
+ if (gatewayApiKey) {
306
+ await register({
307
+ registries,
308
+ provider: "vercel-ai-gateway",
309
+ apiKey: gatewayApiKey,
310
+ config: createGatewayProviderConfig({
311
+ apiKey: gatewayApiKey,
312
+ baseUrl: gatewayBaseUrl,
313
+ clientApp
314
+ })
315
+ });
316
+ return;
317
+ }
318
+ const env = pickProviderEnv(resolvedEnv);
319
+ await registerCustomProviders({
320
+ customEnv: { ...pickProviderEnv(process.env), ...env },
321
+ registries,
322
+ clientApp
323
+ });
324
+ return;
325
+ }
326
+ }
188
327
  const apiKey = resolvedEnv.AI_GATEWAY_API_KEY;
189
328
  const baseUrl = resolvedEnv.AI_GATEWAY_BASE_URL;
190
329
  if (!apiKey || !baseUrl) return;
@@ -195,6 +334,51 @@ async function registerPiProviders({
195
334
  config: createGatewayProviderConfig({ apiKey, baseUrl, clientApp })
196
335
  });
197
336
  }
337
+ function pickOpenAIEnv(env) {
338
+ const result = {};
339
+ if (env.OPENAI_API_KEY) result.OPENAI_API_KEY = env.OPENAI_API_KEY;
340
+ if (env.OPENAI_BASE_URL) result.OPENAI_BASE_URL = env.OPENAI_BASE_URL;
341
+ return result;
342
+ }
343
+ function pickAnthropicEnv(env) {
344
+ const result = {};
345
+ if (env.ANTHROPIC_API_KEY) result.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY;
346
+ if (env.ANTHROPIC_BASE_URL)
347
+ result.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL;
348
+ if (env.ANTHROPIC_AUTH_TOKEN)
349
+ result.ANTHROPIC_AUTH_TOKEN = env.ANTHROPIC_AUTH_TOKEN;
350
+ return result;
351
+ }
352
+ function pickProviderEnv(env) {
353
+ const result = {};
354
+ for (const [key, value] of Object.entries(env)) {
355
+ if (value && (key.endsWith("_API_KEY") || key.endsWith("_BASE_URL") || key === "ANTHROPIC_AUTH_TOKEN")) {
356
+ result[key] = value;
357
+ }
358
+ }
359
+ return result;
360
+ }
361
+ function normalizePiAuthToLegacyAuth(options) {
362
+ if (options == null || options === "auto") {
363
+ return void 0;
364
+ }
365
+ if (typeof options === "string") {
366
+ switch (options) {
367
+ case "ai-gateway":
368
+ return { gateway: {} };
369
+ case "custom":
370
+ case "openai":
371
+ case "anthropic":
372
+ return { customEnv: {} };
373
+ default:
374
+ return void 0;
375
+ }
376
+ }
377
+ console.warn(
378
+ '[pi] Passing an object to auth options is deprecated. Use a string mode ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.'
379
+ );
380
+ return options;
381
+ }
198
382
  function resolveCustomEnv({
199
383
  customEnv
200
384
  }) {
@@ -513,7 +697,7 @@ function createPiPathMapper(options) {
513
697
 
514
698
  // src/pi-remote-ops.ts
515
699
  import path3 from "path";
516
- import { shellQuote as shellQuote2 } from "@ai-sdk/harness/utils";
700
+ import { shellQuote } from "@ai-sdk/harness/utils";
517
701
  function lastOutputLine(output) {
518
702
  return output.toString("utf8").trim().split("\n").filter(Boolean).at(-1);
519
703
  }
@@ -538,7 +722,7 @@ function createPiRemoteOps(options) {
538
722
  const resolveExistingSandboxPath = async (remotePath, inputPath) => {
539
723
  const result = await runShell(
540
724
  [
541
- `target=${shellQuote2(remotePath)}`,
725
+ `target=${shellQuote(remotePath)}`,
542
726
  `if [ ! -e "$target" ]; then echo "__PI_REALPATH_NOT_FOUND__"; exit 2; fi`,
543
727
  `resolved=$(realpath "$target" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }`,
544
728
  `printf '%s\\n' "$resolved"`
@@ -563,7 +747,7 @@ function createPiRemoteOps(options) {
563
747
  const resolveWritableSandboxPath = async (remotePath, inputPath) => {
564
748
  const result = await runShell(
565
749
  [
566
- `target=${shellQuote2(remotePath)}`,
750
+ `target=${shellQuote(remotePath)}`,
567
751
  `if [ -e "$target" ] || [ -L "$target" ]; then resolved=$(realpath "$target" 2>/dev/null) || { echo "__PI_REALPATH_FAILED__"; exit 3; }; printf '%s\\n' "$resolved"; exit 0; fi`,
568
752
  `dir=$(dirname "$target")`,
569
753
  `base=$(basename "$target")`,
@@ -606,7 +790,7 @@ function createPiRemoteOps(options) {
606
790
  const previous = await options.sandbox.readBinaryFile({
607
791
  path: resolvedPath
608
792
  });
609
- await runShell(`mkdir -p ${shellQuote2(path3.posix.dirname(resolvedPath))}`);
793
+ await runShell(`mkdir -p ${shellQuote(path3.posix.dirname(resolvedPath))}`);
610
794
  await options.sandbox.writeTextFile({ path: resolvedPath, content });
611
795
  options.onFileChange?.(
612
796
  previous ? "modify" : "create",
@@ -634,9 +818,9 @@ function createPiRemoteOps(options) {
634
818
  );
635
819
  const result = await runShell(
636
820
  [
637
- `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
638
- `if [ ! -d ${shellQuote2(resolvedPath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
639
- `cd ${shellQuote2(resolvedPath)}`,
821
+ `if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_LS_NOT_FOUND__"; exit 2; fi`,
822
+ `if [ ! -d ${shellQuote(resolvedPath)} ]; then echo "__PI_LS_NOT_DIR__"; exit 3; fi`,
823
+ `cd ${shellQuote(resolvedPath)}`,
640
824
  "ls -1Ap"
641
825
  ].join("; ")
642
826
  );
@@ -659,8 +843,8 @@ function createPiRemoteOps(options) {
659
843
  );
660
844
  const result = await runShell(
661
845
  [
662
- `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
663
- `if [ -d ${shellQuote2(resolvedPath)} ]; then find ${shellQuote2(resolvedPath)} -type f -print; else printf '%s\\n' ${shellQuote2(resolvedPath)}; fi`
846
+ `if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_FIND_NOT_FOUND__"; exit 2; fi`,
847
+ `if [ -d ${shellQuote(resolvedPath)} ]; then find ${shellQuote(resolvedPath)} -type f -print; else printf '%s\\n' ${shellQuote(resolvedPath)}; fi`
664
848
  ].join("; ")
665
849
  );
666
850
  const output = result.output.toString("utf8").trim();
@@ -699,9 +883,9 @@ function createPiRemoteOps(options) {
699
883
  const limit = Math.max(1, input.limit ?? 100);
700
884
  const result = await runShell(
701
885
  [
702
- `if [ ! -e ${shellQuote2(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
703
- `cd ${shellQuote2(options.paths.sandboxWorkDir)}`,
704
- `grep ${flags.map(shellQuote2).join(" ")} -- ${shellQuote2(pattern)} ${shellQuote2(targetPath)} 2>/dev/null | head -n ${limit}`
886
+ `if [ ! -e ${shellQuote(resolvedPath)} ]; then echo "__PI_GREP_NOT_FOUND__"; exit 2; fi`,
887
+ `cd ${shellQuote(options.paths.sandboxWorkDir)}`,
888
+ `grep ${flags.map(shellQuote).join(" ")} -- ${shellQuote(pattern)} ${shellQuote(targetPath)} 2>/dev/null | head -n ${limit}`
705
889
  ].join("; ")
706
890
  );
707
891
  const output = result.output.toString("utf8").trim();
@@ -1358,7 +1542,7 @@ import {
1358
1542
  writeFile as writeFile2
1359
1543
  } from "fs/promises";
1360
1544
  import path6 from "path";
1361
- import { shellQuote as shellQuote3 } from "@ai-sdk/harness/utils";
1545
+ import { shellQuote as shellQuote2 } from "@ai-sdk/harness/utils";
1362
1546
  var PI_CONFIG_DIRS = [".pi", ".agents"];
1363
1547
  var PI_CONTEXT_FILENAMES = ["AGENTS.md", "AGENTS.MD"];
1364
1548
  function normalizeRelativePath(inputPath) {
@@ -1383,7 +1567,7 @@ async function readCommandOutput(sandbox, command) {
1383
1567
  }
1384
1568
  async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
1385
1569
  const contextPredicate = PI_CONTEXT_FILENAMES.map(
1386
- (name) => `-name ${shellQuote3(name)}`
1570
+ (name) => `-name ${shellQuote2(name)}`
1387
1571
  ).join(" -o ");
1388
1572
  const configFinds = PI_CONFIG_DIRS.map(
1389
1573
  (dir) => ` if [ -d ./${dir} ]; then find -L ./${dir} \\( -type d -o -type f \\) -print0; fi;`
@@ -1404,7 +1588,7 @@ async function listRemoteWorkspaceEntries(sandbox, sandboxWorkDir) {
1404
1588
  ].join("\n");
1405
1589
  const output = await readCommandOutput(
1406
1590
  sandbox,
1407
- [`cd ${shellQuote3(sandboxWorkDir)}`, listCommand].join(" && ")
1591
+ [`cd ${shellQuote2(sandboxWorkDir)}`, listCommand].join(" && ")
1408
1592
  );
1409
1593
  const directories = [];
1410
1594
  const files = [];
@@ -1625,14 +1809,19 @@ async function createPiSession(input) {
1625
1809
  await mkdir3(hostAgentDir, { recursive: true });
1626
1810
  await mkdir3(hostSessionDir, { recursive: true });
1627
1811
  const sandbox = input.sandboxSession.restricted();
1812
+ const sandboxHomeDir = await resolveSandboxHomeDir({
1813
+ sandbox,
1814
+ ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1815
+ });
1816
+ const privateSessionDir = resolvePiPrivateSessionDirectory({
1817
+ sandboxHomeDir,
1818
+ sessionWorkDir: input.sessionWorkDir,
1819
+ sessionId: input.sessionId
1820
+ });
1628
1821
  const permissionMode = input.permissionMode ?? "allow-all";
1629
1822
  let sandboxSkillRootDir;
1630
1823
  let harnessSkills = [];
1631
1824
  if (input.skills.length > 0) {
1632
- const sandboxHomeDir = await resolveSandboxHomeDir({
1633
- sandbox,
1634
- ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
1635
- });
1636
1825
  sandboxSkillRootDir = path7.posix.join(sandboxHomeDir, ".agents", "skills");
1637
1826
  harnessSkills = createHarnessPiSkills({
1638
1827
  skills: input.skills,
@@ -1652,7 +1841,7 @@ async function createPiSession(input) {
1652
1841
  );
1653
1842
  resumeSessionFilePath = await pullSessionFileFromSandbox({
1654
1843
  sandbox,
1655
- sessionWorkDir: input.sessionWorkDir,
1844
+ privateSessionDir,
1656
1845
  hostSessionDir,
1657
1846
  sessionFileName: resumeSessionFileName,
1658
1847
  ...input.abortSignal ? { abortSignal: input.abortSignal } : {}
@@ -1817,7 +2006,7 @@ async function createPiSession(input) {
1817
2006
  if (!sessionFileName) return;
1818
2007
  await persistSessionFileToSandbox({
1819
2008
  sandbox,
1820
- sessionWorkDir: input.sessionWorkDir,
2009
+ privateSessionDir,
1821
2010
  hostSessionDir,
1822
2011
  sessionFileName
1823
2012
  });