@byok-sdk/client 0.5.0 → 0.6.1

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 (42) hide show
  1. package/README.md +91 -1
  2. package/dist/adapters/index.js +183 -34
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/pi/events.d.ts +1 -1
  5. package/dist/adapters/pi/mcp-config.d.ts +1 -0
  6. package/dist/adapters/pi/mcp-extension.js +25 -0
  7. package/dist/adapters/pi/mcp-extension.js.map +1 -0
  8. package/dist/adapters/pi/permission-mapping.d.ts +1 -1
  9. package/dist/adapters/pi/pi-adapter.d.ts +5 -0
  10. package/dist/adapters/pi/resolve-extensions.d.ts +11 -0
  11. package/dist/agent-home.d.ts +106 -0
  12. package/dist/bin/byok-agent.js +12902 -9340
  13. package/dist/bin/byok-agent.js.map +1 -1
  14. package/dist/bin/byok-approval-mcp.js.map +1 -1
  15. package/dist/bin/commands/toolsets.d.ts +8 -0
  16. package/dist/bin/config.d.ts +3 -1
  17. package/dist/bin/format.d.ts +5 -1
  18. package/dist/bin/official-release.d.ts +1 -0
  19. package/dist/bin/version.d.ts +2 -0
  20. package/dist/daemon/agent-content-audit-store.d.ts +35 -0
  21. package/dist/daemon/agent-content-read.d.ts +169 -0
  22. package/dist/daemon/agent-egress-controller.d.ts +76 -0
  23. package/dist/daemon/agent-egress-policy.d.ts +36 -0
  24. package/dist/daemon/agent-egress-sanitizer.d.ts +38 -0
  25. package/dist/daemon/agent-egress-spool.d.ts +116 -0
  26. package/dist/daemon/agent-session-handoff-store.d.ts +82 -0
  27. package/dist/daemon/blob-client.d.ts +6 -2
  28. package/dist/daemon/connection-manager.d.ts +4 -2
  29. package/dist/daemon/control-protocol.d.ts +12 -0
  30. package/dist/daemon/create-daemon.d.ts +83 -3
  31. package/dist/daemon/long-poll-transport.d.ts +60 -0
  32. package/dist/daemon/presence-publisher.d.ts +9 -3
  33. package/dist/daemon/task-runner.d.ts +73 -4
  34. package/dist/daemon/toolset-registry.d.ts +30 -0
  35. package/dist/daemon/url.d.ts +21 -0
  36. package/dist/daemon/ws-transport.d.ts +32 -5
  37. package/dist/index.d.ts +14 -2
  38. package/dist/index.js +12682 -9191
  39. package/dist/index.js.map +1 -1
  40. package/dist/release-identity.d.ts +15 -0
  41. package/dist/types.d.ts +44 -0
  42. package/package.json +7 -5
package/README.md CHANGED
@@ -14,7 +14,11 @@ the separately installed `byok-pi-provider-launcher`, the local non-secret
14
14
  profile database path, and a stable Pi session directory. The client passes
15
15
  only those paths plus provider/model ids; the launcher alone reads the OS
16
16
  credential when required and spawns Pi. Both custody paths must be absolute;
17
- missing launcher configuration fails closed.
17
+ missing launcher configuration fails closed. A macOS host running under an
18
+ isolated `HOME` can additionally set `piByokLauncher.macosKeychainPath` to one
19
+ absolute keychain file. The client projects it as the launcher's reserved
20
+ `--macos-keychain-path` flag; it does not search a second credential authority
21
+ or widen the Pi child environment.
18
22
 
19
23
  Claude Code and Codex remain user-installed runtimes and use their own login
20
24
  state. Hosts that only need runtime detection/composition can import the
@@ -81,6 +85,92 @@ createDaemon({
81
85
  The value is intentionally host-owned and has no SDK default because it is a
82
86
  deployment/read-model policy, not a frozen protocol limit.
83
87
 
88
+ ## Durable Agent homes
89
+
90
+ An Agent-capable daemon receives one absolute branded storage root. The SDK,
91
+ not the host, composes `agents/<agentId>`, validates canonical containment,
92
+ creates missing `MEMORY.md` and `notes/` without overwriting existing bytes,
93
+ and binds the resulting Agent home as runtime cwd.
94
+
95
+ ```ts
96
+ import { createAgentHomeProjection, createDaemon } from '@byok-sdk/client';
97
+
98
+ createDaemon({
99
+ // ...normal device and transport configuration
100
+ agentHome: {
101
+ hostStorageRoot: '/Users/alice/.salesko',
102
+ projection: createAgentHomeProjection(async ({ agentRef, cwd }) => {
103
+ // Host code receives the canonical home. It supplies redacted profile
104
+ // content but never joins `agents/<agentId>` and never writes secrets.
105
+ await profileProjection.write({ agentRef, canonicalAgentHome: cwd });
106
+ }),
107
+ },
108
+ });
109
+ ```
110
+
111
+ Startup materializes and write-probes the canonical root before publishing
112
+ `agent-home-contract`. `agentHome` and `gitWorkspace` are mutually exclusive;
113
+ strict Agent execution has one workspace authority and never falls back to a
114
+ task-scoped Git workspace.
115
+
116
+ Successful startup with this configuration advertises `agent-home-contract`. Agent offers are distinct
117
+ from legacy task offers and fail closed when identity, profile revision,
118
+ session/runtime/cwd evidence, or the one-writer lease does not match. Agent
119
+ files other than the SDK-reserved `.byok` namespace are opaque; there is no
120
+ required `artifacts/` directory and the client does not parse or index their
121
+ contents.
122
+
123
+ ## Agent egress and explicit content reads
124
+
125
+ `agentEgress` is consumed configuration, not a profile projection. The host
126
+ must select one exact policy revision and an authenticated tenant id. Omitting
127
+ contentful mode keeps runtime activity metadata/status-only; enabling it is an
128
+ explicit product decision and requires the server capability. Reliable events
129
+ are fsynced under the canonical Agent home and retire only after an exact ack.
130
+
131
+ ```ts
132
+ createDaemon({
133
+ // ...normal device, transport and agentHome configuration
134
+ agentEgress: {
135
+ tenantId: 'tenant-authority-from-salesko',
136
+ policy: {
137
+ policyRevision: 'salesko-agent-egress-r1',
138
+ activity: { mode: 'metadata-status', delivery: 'latest-value' },
139
+ reliable: {
140
+ maxPendingEventsPerAgent: 256,
141
+ maxPendingBytesPerAgent: 4 * 1024 * 1024,
142
+ maxPendingBytesPerTenant: 16 * 1024 * 1024,
143
+ },
144
+ transfers: {
145
+ workspace: { maxBytes: 1024 * 1024, allowedMimeTypes: ['text/plain'] },
146
+ transcript: 'disabled',
147
+ artifact: 'disabled',
148
+ },
149
+ },
150
+ contentRead: {
151
+ workspace: {
152
+ root: { kind: 'agent-home' },
153
+ maxTextBytes: 1024 * 1024,
154
+ textMimeTypes: ['text/plain'],
155
+ },
156
+ },
157
+ },
158
+ });
159
+ ```
160
+
161
+ Each content surface requires both the matching non-disabled wire policy and
162
+ its local supplement. The local supplement can only narrow root, text, MIME,
163
+ size and sensitive-name behavior; it cannot enable a wire-disabled surface.
164
+ The SDK derives `agents/<agentId>`, `.byok/egress`, runtime-session evidence and
165
+ the per-Agent content-read audit path. Salesko must not compose those paths.
166
+ Tenant/device identity comes from authenticated host state; a request cannot
167
+ override it. Transcript reads additionally require the exact persisted
168
+ AgentRef/session/runtime/cwd handoff. Allowed content is uploaded through the
169
+ authenticated blob channel. The content-free receipt is fsynced into the
170
+ Agent-local reliable spool with stable event/cursor identity before send and
171
+ retires only after an exact ack; an allowed receipt carries the exact
172
+ `BlobRef`. No API recursively mirrors an Agent home.
173
+
84
174
  For a concrete private host composition, see the
85
175
  [`examples/salesko-connector-broker`](../../examples/salesko-connector-broker)
86
176
  reference. It keeps `@byok-sdk/client` credential-blind while combining
@@ -1,9 +1,9 @@
1
1
  import { execFile, spawn } from 'child_process';
2
- import { promisify } from 'util';
3
2
  import { promises, existsSync, readFileSync, realpathSync } from 'fs';
4
- import path3 from 'path';
5
- import { fileURLToPath } from 'url';
6
3
  import os from 'os';
4
+ import path5, { isAbsolute } from 'path';
5
+ import { promisify } from 'util';
6
+ import { fileURLToPath } from 'url';
7
7
  import 'readline';
8
8
 
9
9
  // src/runtime-failure.ts
@@ -95,9 +95,12 @@ var SteerUnsupportedError = class extends Error {
95
95
  this.runtimeId = runtimeId;
96
96
  }
97
97
  };
98
+
99
+ // src/adapters/pi/mcp-config.ts
100
+ var BYOK_PI_MCP_CONFIG_PATH = "BYOK_PI_MCP_CONFIG_PATH";
98
101
  var PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
99
102
  function readPackageJson(dir) {
100
- const candidate = path3.join(dir, "package.json");
103
+ const candidate = path5.join(dir, "package.json");
101
104
  if (!existsSync(candidate)) return void 0;
102
105
  try {
103
106
  return JSON.parse(readFileSync(candidate, "utf8"));
@@ -112,17 +115,17 @@ function resolvePiBin() {
112
115
  }
113
116
  try {
114
117
  const mainEntryUrl = import.meta.resolve(PI_PACKAGE_NAME);
115
- let dir = path3.dirname(fileURLToPath(mainEntryUrl));
118
+ let dir = path5.dirname(fileURLToPath(mainEntryUrl));
116
119
  for (let depth = 0; depth < 6; depth++) {
117
120
  const pkg = readPackageJson(dir);
118
121
  if (pkg?.name === PI_PACKAGE_NAME) {
119
122
  const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.pi;
120
123
  if (binRel) {
121
- return { command: path3.join(dir, binRel), source: "package" };
124
+ return { command: path5.join(dir, binRel), source: "package" };
122
125
  }
123
126
  break;
124
127
  }
125
- const parent = path3.dirname(dir);
128
+ const parent = path5.dirname(dir);
126
129
  if (parent === dir) break;
127
130
  dir = parent;
128
131
  }
@@ -136,6 +139,13 @@ function resolvePiBin() {
136
139
  `Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.22+ pi sidecar`
137
140
  );
138
141
  }
142
+ function resolvePiExtensions() {
143
+ const clientManifest = fileURLToPath(import.meta.resolve("@byok-sdk/client/package.json"));
144
+ return {
145
+ webAccess: fileURLToPath(import.meta.resolve("pi-web-access/index.ts")),
146
+ mcpAdapter: path5.join(path5.dirname(clientManifest), "dist", "adapters", "pi", "mcp-extension.js")
147
+ };
148
+ }
139
149
 
140
150
  // src/adapters/pi/permission-mapping.ts
141
151
  var READONLY_TOOLS = ["read", "grep", "find", "ls"];
@@ -888,9 +898,73 @@ var DETECT_TIMEOUT_MS = 5e3;
888
898
  function errorMessage(err) {
889
899
  return err instanceof Error ? err.message : String(err);
890
900
  }
901
+ async function cleanupMcpConfigDir(dir) {
902
+ if (!dir) return;
903
+ try {
904
+ await promises.rm(dir, { recursive: true, force: true });
905
+ } catch (cause) {
906
+ throw new RuntimeDisposalFailure({
907
+ stage: "cleanup",
908
+ reason: "pi task-scoped MCP configuration could not be removed"
909
+ }, { cause });
910
+ }
911
+ }
912
+ function validatePiByokLauncherConfig(launcher) {
913
+ if (launcher === void 0) return;
914
+ for (const [field, value] of [
915
+ ["command", launcher.command],
916
+ ["profileDbPath", launcher.profileDbPath],
917
+ ["sessionDir", launcher.sessionDir]
918
+ ]) {
919
+ if (value.trim().length === 0 || /[\u0000\r\n]/u.test(value)) {
920
+ throw new Error(`DaemonConfig.piByokLauncher.${field} must be a non-empty single-line string`);
921
+ }
922
+ }
923
+ if (!isAbsolute(launcher.profileDbPath) || !isAbsolute(launcher.sessionDir)) {
924
+ throw new Error(
925
+ "DaemonConfig.piByokLauncher profileDbPath and sessionDir must be absolute paths"
926
+ );
927
+ }
928
+ if (launcher.macosKeychainPath !== void 0 && (launcher.macosKeychainPath.trim().length === 0 || /[\u0000\r\n]/u.test(launcher.macosKeychainPath))) {
929
+ throw new Error(
930
+ "DaemonConfig.piByokLauncher.macosKeychainPath must be a non-empty single-line string"
931
+ );
932
+ }
933
+ if (launcher.macosKeychainPath !== void 0 && !isAbsolute(launcher.macosKeychainPath)) {
934
+ throw new Error(
935
+ "DaemonConfig.piByokLauncher.macosKeychainPath must be an absolute path"
936
+ );
937
+ }
938
+ if (launcher.secretServicePrefix !== void 0 && (launcher.secretServicePrefix.trim().length === 0 || /[\u0000\r\n]/u.test(launcher.secretServicePrefix))) {
939
+ throw new Error(
940
+ "DaemonConfig.piByokLauncher.secretServicePrefix must be a non-empty single-line string"
941
+ );
942
+ }
943
+ const reserved = /* @__PURE__ */ new Set([
944
+ "--",
945
+ "--pi-bin",
946
+ "--profile-db",
947
+ "--session-dir",
948
+ "--macos-keychain-path",
949
+ "--secret-service-prefix",
950
+ "--provider",
951
+ "--model"
952
+ ]);
953
+ const conflicting = launcher.args?.find((arg) => reserved.has(arg));
954
+ if (conflicting !== void 0) {
955
+ throw new Error(
956
+ `DaemonConfig.piByokLauncher.args must not override reserved launcher argument ${conflicting}`
957
+ );
958
+ }
959
+ const invalidArg = launcher.args?.find((arg) => arg.length === 0 || /[\u0000\r\n]/u.test(arg));
960
+ if (invalidArg !== void 0) {
961
+ throw new Error("DaemonConfig.piByokLauncher.args must contain only non-empty single-line strings");
962
+ }
963
+ }
891
964
  var PiAdapter = class {
892
965
  constructor(options = {}) {
893
966
  this.options = options;
967
+ validatePiByokLauncherConfig(options.byokLauncher);
894
968
  }
895
969
  options;
896
970
  descriptor = freezeRuntimeAdapterDescriptor({
@@ -899,6 +973,7 @@ var PiAdapter = class {
899
973
  capabilities: {
900
974
  steer: true,
901
975
  resume: true,
976
+ mcpToolsets: true,
902
977
  approvalInteractive: false,
903
978
  permissionModes: ["auto", "readonly"]
904
979
  },
@@ -920,7 +995,15 @@ var PiAdapter = class {
920
995
  if (!mapping.ok) {
921
996
  return { kind: "reject", reason: mapping.reason ?? "policy rejected by pi adapter", retryable: false };
922
997
  }
998
+ if (input.requiredToolsetIds.length > 0 && input.policy.mode !== "auto") {
999
+ return {
1000
+ kind: "reject",
1001
+ reason: 'pi MCP toolsets require permission mode "auto" because pi-mcp-adapter exposes one proxy across read and mutation tools',
1002
+ retryable: false
1003
+ };
1004
+ }
923
1005
  const bin = this.resolveBin();
1006
+ const extensions = (this.options.resolveExtensions ?? resolvePiExtensions)();
924
1007
  const selection = input.offer.dispatchSelection;
925
1008
  const pinnedSelection = selection === void 0 ? void 0 : Object.freeze({ ...selection });
926
1009
  let command = bin.command;
@@ -942,6 +1025,7 @@ var PiAdapter = class {
942
1025
  launcher.profileDbPath,
943
1026
  "--session-dir",
944
1027
  launcher.sessionDir,
1028
+ ...launcher.macosKeychainPath !== void 0 ? ["--macos-keychain-path", launcher.macosKeychainPath] : [],
945
1029
  ...launcher.secretServicePrefix ? ["--secret-service-prefix", launcher.secretServicePrefix] : [],
946
1030
  "--provider",
947
1031
  pinnedSelection.providerId,
@@ -971,18 +1055,48 @@ var PiAdapter = class {
971
1055
  });
972
1056
  }
973
1057
  const resumeSessionId = startInput.manifest.sessionRef;
974
- const piArgs = ["--mode", "rpc", ...resumeSessionId ? ["--session", resumeSessionId] : [], ...mapping.args];
1058
+ const manifestCwd = startInput.manifest.cwd;
1059
+ if (manifestCwd === void 0) {
1060
+ throw new RuntimeExecutionFailure({
1061
+ phase: "start",
1062
+ category: "authority",
1063
+ retry: "non-retryable",
1064
+ reason: "prepared pi operation received a manifest without a sealed cwd"
1065
+ });
1066
+ }
1067
+ let mcpConfigDir;
1068
+ let runtimeEnv = manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env);
1069
+ const taskMcpServers = startInput.mcpServers ?? {};
1070
+ const hasMcpServers = Object.keys(taskMcpServers).length > 0;
1071
+ if (hasMcpServers) {
1072
+ mcpConfigDir = await promises.mkdtemp(path5.join(os.tmpdir(), "byok-pi-mcp-"));
1073
+ await promises.chmod(mcpConfigDir, 448).catch(() => {
1074
+ });
1075
+ const mcpConfigPath = path5.join(mcpConfigDir, "mcp-config.json");
1076
+ await promises.writeFile(mcpConfigPath, JSON.stringify({ mcpServers: taskMcpServers }), { mode: 384 });
1077
+ runtimeEnv = { ...runtimeEnv, [BYOK_PI_MCP_CONFIG_PATH]: mcpConfigPath };
1078
+ }
1079
+ const piArgs = [
1080
+ "--mode",
1081
+ "rpc",
1082
+ "--extension",
1083
+ extensions.webAccess,
1084
+ ...hasMcpServers ? ["--extension", extensions.mcpAdapter] : [],
1085
+ ...resumeSessionId ? ["--session", resumeSessionId] : [],
1086
+ ...mapping.args
1087
+ ];
975
1088
  const args = launcherArgs === void 0 ? piArgs : [...launcherArgs, "--", ...piArgs];
976
1089
  let rpc;
977
1090
  try {
978
1091
  rpc = new PiRpcClient({
979
1092
  command,
980
1093
  args,
981
- cwd: startInput.manifest.workspace.workspaceDir,
982
- env: manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env),
1094
+ cwd: manifestCwd,
1095
+ env: runtimeEnv,
983
1096
  spawnFn: this.options.spawnFn
984
1097
  });
985
1098
  } catch (cause) {
1099
+ await cleanupMcpConfigDir(mcpConfigDir);
986
1100
  throw new RuntimeExecutionFailure({
987
1101
  phase: "start",
988
1102
  category: "infrastructure",
@@ -995,6 +1109,7 @@ var PiAdapter = class {
995
1109
  response = await rpc.send({ type: "prompt", message: startInput.instruction });
996
1110
  } catch (cause) {
997
1111
  rpc.kill();
1112
+ await cleanupMcpConfigDir(mcpConfigDir);
998
1113
  throw new RuntimeExecutionFailure({
999
1114
  phase: "start",
1000
1115
  category: "infrastructure",
@@ -1004,6 +1119,7 @@ var PiAdapter = class {
1004
1119
  }
1005
1120
  if (response.success === false) {
1006
1121
  rpc.kill();
1122
+ await cleanupMcpConfigDir(mcpConfigDir);
1007
1123
  throw new RuntimeExecutionFailure({
1008
1124
  phase: "start",
1009
1125
  category: "semantic",
@@ -1016,10 +1132,12 @@ var PiAdapter = class {
1016
1132
  sessionRef = await resolveAuthoritativeSessionId(rpc);
1017
1133
  } catch (err) {
1018
1134
  rpc.kill();
1135
+ await cleanupMcpConfigDir(mcpConfigDir);
1019
1136
  throw err;
1020
1137
  }
1021
1138
  if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
1022
1139
  rpc.kill();
1140
+ await cleanupMcpConfigDir(mcpConfigDir);
1023
1141
  throw new RuntimeExecutionFailure({
1024
1142
  phase: "start",
1025
1143
  category: "authority",
@@ -1027,7 +1145,7 @@ var PiAdapter = class {
1027
1145
  reason: "pi resumed a different authoritative session than requested"
1028
1146
  });
1029
1147
  }
1030
- return new PiSession(sessionRef, rpc, manifestSelection);
1148
+ return new PiSession(sessionRef, rpc, manifestSelection, mcpConfigDir);
1031
1149
  }
1032
1150
  }
1033
1151
  };
@@ -1076,14 +1194,17 @@ async function resolveAuthoritativeSessionId(rpc) {
1076
1194
  });
1077
1195
  }
1078
1196
  var PiSession = class {
1079
- constructor(sessionRef, rpc, selection) {
1197
+ constructor(sessionRef, rpc, selection, mcpConfigDir) {
1080
1198
  this.sessionRef = sessionRef;
1081
1199
  this.rpc = rpc;
1082
1200
  this.selection = selection;
1201
+ this.mcpConfigDir = mcpConfigDir;
1083
1202
  }
1084
1203
  sessionRef;
1085
1204
  rpc;
1086
1205
  selection;
1206
+ mcpConfigDir;
1207
+ closeAttempt;
1087
1208
  get events() {
1088
1209
  const rpc = this.rpc;
1089
1210
  return {
@@ -1152,7 +1273,17 @@ var PiSession = class {
1152
1273
  await this.rpc.send({ type: "abort" });
1153
1274
  }
1154
1275
  async close() {
1155
- await this.rpc.dispose();
1276
+ if (!this.closeAttempt) {
1277
+ const attempt = (async () => {
1278
+ await this.rpc.dispose();
1279
+ await cleanupMcpConfigDir(this.mcpConfigDir);
1280
+ })();
1281
+ this.closeAttempt = attempt.catch((error) => {
1282
+ this.closeAttempt = void 0;
1283
+ throw error;
1284
+ });
1285
+ }
1286
+ await this.closeAttempt;
1156
1287
  }
1157
1288
  async resolveApproval() {
1158
1289
  throw new Error("pi adapter does not support approval resume: pi never emits needs_approval in M0/M1");
@@ -1172,7 +1303,7 @@ function resolveApprovalMcpBin() {
1172
1303
  if (override) {
1173
1304
  return { command: override, args: [], source: "env" };
1174
1305
  }
1175
- const distBin = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "bin", "byok-approval-mcp.js");
1306
+ const distBin = path5.join(path5.dirname(fileURLToPath(import.meta.url)), "bin", "byok-approval-mcp.js");
1176
1307
  return { command: process.execPath, args: [distBin], source: "dist" };
1177
1308
  }
1178
1309
 
@@ -1263,7 +1394,7 @@ var EXTENSION_CONTENT_TYPES = {
1263
1394
  ".yml": "application/yaml"
1264
1395
  };
1265
1396
  function guessContentType(filePath) {
1266
- const ext = path3.extname(filePath).toLowerCase();
1397
+ const ext = path5.extname(filePath).toLowerCase();
1267
1398
  return EXTENSION_CONTENT_TYPES[ext] ?? "application/octet-stream";
1268
1399
  }
1269
1400
  function mapAssistant(msg, correlation) {
@@ -1347,11 +1478,11 @@ function tryBuildArtifactEvent(msg, workspaceDir) {
1347
1478
  const filePath = toolUseResult && typeof toolUseResult.filePath === "string" ? toolUseResult.filePath : void 0;
1348
1479
  if (!filePath) return void 0;
1349
1480
  const realWorkspaceDir = tryRealpath(workspaceDir) ?? workspaceDir;
1350
- const fileDir = path3.dirname(filePath);
1481
+ const fileDir = path5.dirname(filePath);
1351
1482
  const realFileDir = tryRealpath(fileDir) ?? fileDir;
1352
- const realFilePath = path3.join(realFileDir, path3.basename(filePath));
1353
- const relative = path3.relative(realWorkspaceDir, realFilePath);
1354
- if (relative === "" || relative.startsWith("..") || path3.isAbsolute(relative)) {
1483
+ const realFilePath = path5.join(realFileDir, path5.basename(filePath));
1484
+ const relative = path5.relative(realWorkspaceDir, realFilePath);
1485
+ if (relative === "" || relative.startsWith("..") || path5.isAbsolute(relative)) {
1355
1486
  return void 0;
1356
1487
  }
1357
1488
  return { type: "artifact", name: relative, contentType: guessContentType(filePath) };
@@ -1637,7 +1768,7 @@ var DETECT_TIMEOUT_MS2 = 5e3;
1637
1768
  function errorMessage2(err) {
1638
1769
  return err instanceof Error ? err.message : String(err);
1639
1770
  }
1640
- async function cleanupMcpConfigDir(dir) {
1771
+ async function cleanupMcpConfigDir2(dir) {
1641
1772
  if (!dir) return;
1642
1773
  try {
1643
1774
  await promises.rm(dir, { recursive: true, force: true });
@@ -1745,10 +1876,10 @@ var ClaudeAdapter = class {
1745
1876
  });
1746
1877
  }
1747
1878
  if (needsMcpConfig) {
1748
- mcpConfigDir = await promises.mkdtemp(path3.join(os.tmpdir(), "byok-mcp-"));
1879
+ mcpConfigDir = await promises.mkdtemp(path5.join(os.tmpdir(), "byok-mcp-"));
1749
1880
  await promises.chmod(mcpConfigDir, 448).catch(() => {
1750
1881
  });
1751
- const mcpConfigPath = path3.join(mcpConfigDir, "mcp-config.json");
1882
+ const mcpConfigPath = path5.join(mcpConfigDir, "mcp-config.json");
1752
1883
  const mcpServers = { ...taskMcpServers };
1753
1884
  if (mapping.needsApprovalMcp) {
1754
1885
  const approvalChannel = startInput.approvalChannel;
@@ -1807,6 +1938,15 @@ var ClaudeAdapter = class {
1807
1938
  reason: "prepared claude operation received a manifest with different runtime selection"
1808
1939
  });
1809
1940
  }
1941
+ const manifestCwd = startInput.manifest.cwd;
1942
+ if (manifestCwd === void 0) {
1943
+ throw new RuntimeExecutionFailure({
1944
+ phase: "start",
1945
+ category: "authority",
1946
+ retry: "non-retryable",
1947
+ reason: "prepared claude operation received a manifest without a sealed cwd"
1948
+ });
1949
+ }
1810
1950
  const args = [
1811
1951
  "-p",
1812
1952
  "--input-format",
@@ -1827,12 +1967,12 @@ var ClaudeAdapter = class {
1827
1967
  client = new ClaudeProcessClient({
1828
1968
  command: bin.command,
1829
1969
  args,
1830
- cwd: startInput.manifest.workspace.workspaceDir,
1970
+ cwd: manifestCwd,
1831
1971
  env: withoutProviderCredentials(startInput.env),
1832
1972
  spawnFn: this.options.spawnFn
1833
1973
  });
1834
1974
  } catch (cause) {
1835
- await cleanupMcpConfigDir(mcpConfigDir);
1975
+ await cleanupMcpConfigDir2(mcpConfigDir);
1836
1976
  throw new RuntimeExecutionFailure({
1837
1977
  phase: "start",
1838
1978
  category: "infrastructure",
@@ -1844,7 +1984,7 @@ var ClaudeAdapter = class {
1844
1984
  client.writeUserMessage(startInput.instruction);
1845
1985
  } catch (cause) {
1846
1986
  client.kill();
1847
- await cleanupMcpConfigDir(mcpConfigDir);
1987
+ await cleanupMcpConfigDir2(mcpConfigDir);
1848
1988
  throw new RuntimeExecutionFailure({
1849
1989
  phase: "start",
1850
1990
  category: "infrastructure",
@@ -1857,7 +1997,7 @@ var ClaudeAdapter = class {
1857
1997
  sessionRef = await client.waitForInit();
1858
1998
  } catch (err) {
1859
1999
  client.kill();
1860
- await cleanupMcpConfigDir(mcpConfigDir);
2000
+ await cleanupMcpConfigDir2(mcpConfigDir);
1861
2001
  if (isRuntimeExecutionFailure(err)) throw err;
1862
2002
  throw new RuntimeExecutionFailure({
1863
2003
  phase: "start",
@@ -1868,7 +2008,7 @@ var ClaudeAdapter = class {
1868
2008
  }
1869
2009
  if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
1870
2010
  client.kill();
1871
- await cleanupMcpConfigDir(mcpConfigDir);
2011
+ await cleanupMcpConfigDir2(mcpConfigDir);
1872
2012
  throw new RuntimeExecutionFailure({
1873
2013
  phase: "start",
1874
2014
  category: "authority",
@@ -1879,7 +2019,7 @@ var ClaudeAdapter = class {
1879
2019
  return new ClaudeSession(
1880
2020
  sessionRef,
1881
2021
  client,
1882
- startInput.manifest.workspace.workspaceDir,
2022
+ manifestCwd,
1883
2023
  startInput.approvalChannel,
1884
2024
  mcpConfigDir,
1885
2025
  manifestModelId
@@ -2042,7 +2182,7 @@ var ClaudeSession = class {
2042
2182
  if (!this.closeAttempt) {
2043
2183
  const attempt = (async () => {
2044
2184
  await this.client.dispose();
2045
- await cleanupMcpConfigDir(this.mcpConfigDir);
2185
+ await cleanupMcpConfigDir2(this.mcpConfigDir);
2046
2186
  })();
2047
2187
  this.closeAttempt = attempt.catch((error) => {
2048
2188
  this.closeAttempt = void 0;
@@ -2225,8 +2365,8 @@ function extractArtifactEvents(changes, workspaceDir) {
2225
2365
  const absolutePath = typeof change.path === "string" ? change.path : void 0;
2226
2366
  const kind = typeof change.kind === "string" ? change.kind : void 0;
2227
2367
  if (!absolutePath || kind === "delete") continue;
2228
- const relative = path3.relative(workspaceDir, absolutePath);
2229
- if (relative.length === 0 || relative.startsWith("..") || path3.isAbsolute(relative)) continue;
2368
+ const relative = path5.relative(workspaceDir, absolutePath);
2369
+ if (relative.length === 0 || relative.startsWith("..") || path5.isAbsolute(relative)) continue;
2230
2370
  events.push({ type: "artifact", name: relative, contentType: guessContentType2(relative) });
2231
2371
  }
2232
2372
  return events;
@@ -2247,7 +2387,7 @@ var CONTENT_TYPE_BY_EXTENSION = {
2247
2387
  ".csv": "text/csv"
2248
2388
  };
2249
2389
  function guessContentType2(relativePath) {
2250
- return CONTENT_TYPE_BY_EXTENSION[path3.extname(relativePath).toLowerCase()] ?? "application/octet-stream";
2390
+ return CONTENT_TYPE_BY_EXTENSION[path5.extname(relativePath).toLowerCase()] ?? "application/octet-stream";
2251
2391
  }
2252
2392
  function extractErrorMessage(rawError) {
2253
2393
  if (typeof rawError === "string") return rawError;
@@ -2497,9 +2637,18 @@ ${withStreams.stderr ?? ""}`);
2497
2637
  const queue = new AsyncQueue();
2498
2638
  const terminal = {};
2499
2639
  const recordUnmapped = makeUnmappedFrameRecorder(/* @__PURE__ */ new Map());
2640
+ const manifestCwd = startInput.manifest.cwd;
2641
+ if (manifestCwd === void 0) {
2642
+ throw new RuntimeExecutionFailure({
2643
+ phase: "start",
2644
+ category: "authority",
2645
+ retry: "non-retryable",
2646
+ reason: "prepared codex operation received a manifest without a sealed cwd"
2647
+ });
2648
+ }
2500
2649
  let workspaceDir;
2501
2650
  try {
2502
- workspaceDir = await resolveRealWorkspaceDir(startInput.manifest.workspace.workspaceDir);
2651
+ workspaceDir = await resolveRealWorkspaceDir(manifestCwd);
2503
2652
  } catch (cause) {
2504
2653
  throw new RuntimeExecutionFailure({
2505
2654
  phase: "start",
@@ -2534,7 +2683,7 @@ ${withStreams.stderr ?? ""}`);
2534
2683
  instruction: startInput.instruction,
2535
2684
  modelId: manifestModelId,
2536
2685
  policyArgs: [...policyArgs],
2537
- cwd: startInput.manifest.workspace.workspaceDir,
2686
+ cwd: manifestCwd,
2538
2687
  env: runtimeEnv,
2539
2688
  spawnFn: this.options.spawnFn,
2540
2689
  workspaceDir,