ds-harness-remote 0.4.5 → 0.4.7

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.js CHANGED
@@ -4715,7 +4715,15 @@ function projectCodexItem(value, threadId, turnId, sessionId, index) {
4715
4715
  return { ...base, kind: "tool", text: commandExecutionText(item), status: projectItemStatus(item.status), details: { type } };
4716
4716
  }
4717
4717
  if (isToolItemType(type)) {
4718
- return { ...base, kind: "tool", text: toolCallText(item), status: projectItemStatus(item.status), details: { type } };
4718
+ const images = toolItemImages(item);
4719
+ return {
4720
+ ...base,
4721
+ kind: "tool",
4722
+ text: toolCallText(item),
4723
+ ...images.length === 0 ? {} : { images },
4724
+ status: projectItemStatus(item.status),
4725
+ details: { type }
4726
+ };
4719
4727
  }
4720
4728
  if (type === "fileChange") {
4721
4729
  return { ...base, kind: "file-change", text: fileChangeText(item.changes), status: projectItemStatus(item.status), details: { type } };
@@ -4786,6 +4794,22 @@ function itemImages(item) {
4786
4794
  }];
4787
4795
  });
4788
4796
  }
4797
+ function toolItemImages(item) {
4798
+ const candidates = [
4799
+ isRecord(item.result) ? item.result : void 0,
4800
+ isRecord(item.output) ? item.output : void 0,
4801
+ Array.isArray(item.contentItems) ? { content: item.contentItems } : void 0,
4802
+ Array.isArray(item.output) ? { content: item.output } : void 0
4803
+ ];
4804
+ for (const candidate of candidates) {
4805
+ if (candidate === void 0 || !Array.isArray(candidate.content))
4806
+ continue;
4807
+ const images = itemImages(candidate);
4808
+ if (images.length > 0)
4809
+ return images;
4810
+ }
4811
+ return [];
4812
+ }
4789
4813
  function isImageContent(value) {
4790
4814
  return value.type === "image" || value.type === "input_image";
4791
4815
  }
@@ -4796,7 +4820,7 @@ function imageDataUri(value) {
4796
4820
  return parsed.url;
4797
4821
  if (typeof value.data !== "string" || !isCanonicalBase64(value.data))
4798
4822
  return void 0;
4799
- const mediaType = typeof value.mediaType === "string" ? value.mediaType : sniffImageMediaType(value.data);
4823
+ const mediaType = typeof value.mediaType === "string" ? value.mediaType : typeof value.mimeType === "string" ? value.mimeType : sniffImageMediaType(value.data);
4800
4824
  return mediaType !== void 0 && CODEX_IMAGE_MEDIA_TYPES.has(mediaType) ? `data:${mediaType};base64,${value.data}` : void 0;
4801
4825
  }
4802
4826
  function parseDataImageUrl(value) {
@@ -4885,7 +4909,8 @@ function toolCallText(item) {
4885
4909
  }
4886
4910
  const label = toolLabel(item);
4887
4911
  const error = compactUnknown(item.error);
4888
- const result = compactUnknown(item.result ?? item.contentItems);
4912
+ const rawResult = item.result ?? item.contentItems;
4913
+ const result = itemText(isRecord(rawResult) ? rawResult : {}) ?? (toolItemImages(item).length > 0 ? void 0 : compactUnknown(rawResult));
4889
4914
  return boundedText([label, error, result].filter((value) => value !== void 0 && value !== "").join("\n\n")) || void 0;
4890
4915
  }
4891
4916
  function fileChangeText(value) {
@@ -14388,6 +14413,9 @@ var MAX_CODEX_IMAGE_BASE64 = 288 * 1024 * 1024;
14388
14413
  var CODEX_IMAGE_MEDIA_TYPES2 = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
14389
14414
  var CODEX_IMAGE_ATTACHMENT_PREFIX = "codex-image:";
14390
14415
  var DATA_IMAGE_URL2 = /^data:(image\/(?:png|jpeg|webp|gif));base64,([A-Za-z0-9+/]+={0,2})$/u;
14416
+ function codexProjectWorkspaceId(projectId) {
14417
+ return `${CODEX_WORKSPACE_PREFIX}project:${projectId}`;
14418
+ }
14391
14419
  async function discoverCodexVirtualWorkspaces(client, signal) {
14392
14420
  return (await loadCatalog(client, signal)).workspaces;
14393
14421
  }
@@ -14806,6 +14834,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
14806
14834
  liveItems: /* @__PURE__ */ new Map(),
14807
14835
  liveToolOutput: /* @__PURE__ */ new Map(),
14808
14836
  liveToolResultSeq: /* @__PURE__ */ new Map(),
14837
+ pendingToolImages: [],
14809
14838
  requestId: this.pendingRequestIds.get(sessionId),
14810
14839
  ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
14811
14840
  };
@@ -15015,18 +15044,24 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15015
15044
  follow.completedItems.add(itemId);
15016
15045
  this.closeItemStreamBlocks(follow, itemId, itemText2(item));
15017
15046
  if (type === "agentMessage" && follow.streamActive && follow.streamedBlocks.size === 0) this.finishStream(follow);
15047
+ const supplementalImages = type === "agentMessage" ? follow.pendingToolImages : [];
15018
15048
  for (const event of itemEvents(
15019
15049
  item,
15020
15050
  follow.turn,
15021
15051
  1,
15022
15052
  follow.requestId,
15023
15053
  this.modelSelection(follow.sessionId),
15024
- follow.sessionId
15054
+ follow.sessionId,
15055
+ supplementalImages
15025
15056
  )) {
15026
15057
  if (event.type === "tool/call" && follow.startedItems.has(itemId)) continue;
15027
15058
  if (event.type === "tool/result") this.pushToolResult(follow, itemId, event);
15028
15059
  else this.pushEvent(follow, event.type, event.data, event.view);
15029
15060
  }
15061
+ if (type === "agentMessage" && supplementalImages.length > 0) follow.pendingToolImages = [];
15062
+ else if (type !== void 0 && isToolItemType2(type)) {
15063
+ follow.pendingToolImages.push(...toolOutputImageBlocks(item));
15064
+ }
15030
15065
  follow.liveItems.delete(itemId);
15031
15066
  follow.liveToolOutput.delete(itemId);
15032
15067
  follow.liveToolResultSeq.delete(itemId);
@@ -15167,6 +15202,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15167
15202
  follow.liveItems.clear();
15168
15203
  follow.liveToolOutput.clear();
15169
15204
  follow.liveToolResultSeq.clear();
15205
+ follow.pendingToolImages = [];
15170
15206
  }
15171
15207
  emitRemoteEvent(event, args) {
15172
15208
  for (const queue of this.eventStreams.values()) queue.push({ type: "emit", event, args });
@@ -15512,6 +15548,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15512
15548
  liveItems: /* @__PURE__ */ new Map(),
15513
15549
  liveToolOutput: /* @__PURE__ */ new Map(),
15514
15550
  liveToolResultSeq: /* @__PURE__ */ new Map(),
15551
+ pendingToolImages: [],
15515
15552
  requestId: this.pendingRequestIds.get(sessionId),
15516
15553
  rcOnly: true,
15517
15554
  ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
@@ -15813,15 +15850,29 @@ function projectCodexNativeHistory(thread, sessionId) {
15813
15850
  };
15814
15851
  for (const rawTurn of array(thread.turns)) {
15815
15852
  const turn = record(rawTurn);
15853
+ const turnItems = array(turn.items).map(record);
15854
+ const toolImages = turnItems.flatMap(toolOutputImageBlocks);
15855
+ let imageOwner = -1;
15856
+ for (let index = 0; index < turnItems.length; index += 1) {
15857
+ if (turnItems[index].type === "agentMessage") imageOwner = index;
15858
+ }
15816
15859
  turnNumber += 1;
15817
15860
  const turnActive = isActiveCodexTurn(turn);
15818
15861
  const time = normalizeTime(turn.createdAt) || normalizeTime(turn.startedAt) || normalizeTime(thread.createdAt) || Date.now();
15819
15862
  append("turn/start", { turn: turnNumber }, time);
15820
15863
  append("step/start", { turn: turnNumber, step: 1 }, time);
15821
- for (const rawItem of array(turn.items)) {
15822
- const item = record(rawItem);
15864
+ for (let itemIndex = 0; itemIndex < turnItems.length; itemIndex += 1) {
15865
+ const item = turnItems[itemIndex];
15823
15866
  let toolCallSeq;
15824
- for (const event of itemEvents(item, turnNumber, 1, void 0, modelSelection(), sessionId)) {
15867
+ for (const event of itemEvents(
15868
+ item,
15869
+ turnNumber,
15870
+ 1,
15871
+ void 0,
15872
+ modelSelection(),
15873
+ sessionId,
15874
+ itemIndex === imageOwner ? toolImages : []
15875
+ )) {
15825
15876
  const eventSeq = append(
15826
15877
  event.type,
15827
15878
  event.data,
@@ -15878,7 +15929,7 @@ function paginateCodexNativeHistory(history, request) {
15878
15929
  ...history.activeTurnId === void 0 ? {} : { activeTurnId: history.activeTurnId }
15879
15930
  };
15880
15931
  }
15881
- function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX) {
15932
+ function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX, supplementalImages = []) {
15882
15933
  const type = string(item.type);
15883
15934
  const id2 = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
15884
15935
  const text = itemText2(item);
@@ -15899,7 +15950,13 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
15899
15950
  message: {
15900
15951
  id: id2,
15901
15952
  role: "assistant",
15902
- content: messageContent(item, `${sessionId}:${id2}`, type === "reasoning" ? "reasoning" : "text", text),
15953
+ content: messageContent(
15954
+ item,
15955
+ `${sessionId}:${id2}`,
15956
+ type === "reasoning" ? "reasoning" : "text",
15957
+ text,
15958
+ type === "agentMessage" ? supplementalImages : []
15959
+ ),
15903
15960
  source: { kind: "model", provider: selection.provider, model: selection.model }
15904
15961
  }
15905
15962
  }
@@ -15931,12 +15988,32 @@ function itemEvents(item, turn, step, requestId, selection = modelSelection(), s
15931
15988
  }];
15932
15989
  return [];
15933
15990
  }
15934
- function messageContent(item, attachmentSeed, fallbackType, fallbackText) {
15991
+ function messageContent(item, attachmentSeed, fallbackType, fallbackText, supplementalImages = []) {
15935
15992
  const source = item.content ?? item.input;
15936
15993
  const blocks = contentBlocks(source, attachmentSeed);
15937
- if (blocks.length > 0) return blocks;
15994
+ const images = contentBlocks(supplementalImages, `${attachmentSeed}:tool-output`);
15995
+ if (blocks.length > 0 || images.length > 0) {
15996
+ return [...blocks.length > 0 ? blocks : [{ type: fallbackType, text: fallbackText ?? "" }], ...images];
15997
+ }
15938
15998
  return [{ type: fallbackType, text: fallbackText ?? "" }];
15939
15999
  }
16000
+ function toolOutputImageBlocks(item) {
16001
+ const candidates = [
16002
+ record(item.result).content,
16003
+ record(item.output).content,
16004
+ item.contentItems,
16005
+ Array.isArray(item.output) ? item.output : void 0
16006
+ ];
16007
+ for (const candidate of candidates) {
16008
+ if (!Array.isArray(candidate)) continue;
16009
+ const images = candidate.flatMap((value) => {
16010
+ const block = record(value);
16011
+ return isImageContent2(block) && parseImageBlock(block) !== void 0 ? [block] : [];
16012
+ });
16013
+ if (images.length > 0) return images;
16014
+ }
16015
+ return [];
16016
+ }
15940
16017
  function contentBlocks(value, attachmentSeed) {
15941
16018
  if (typeof value === "string") return [{ type: "text", text: value }];
15942
16019
  if (!Array.isArray(value)) return [];
@@ -15991,7 +16068,8 @@ function parseImageBlock(block) {
15991
16068
  if (parsed !== void 0) return parsed;
15992
16069
  const data = string(block.data);
15993
16070
  if (data === void 0 || !isCanonicalBase642(data)) return void 0;
15994
- const mediaType = string(block.mediaType) ?? sniffImageMediaType2(data);
16071
+ if (data.length > MAX_CODEX_IMAGE_BASE64) return void 0;
16072
+ const mediaType = string(block.mediaType) ?? string(block.mimeType) ?? sniffImageMediaType2(data);
15995
16073
  return mediaType !== void 0 && CODEX_IMAGE_MEDIA_TYPES2.has(mediaType) ? { mediaType, data, url: `data:${mediaType};base64,${data}` } : void 0;
15996
16074
  }
15997
16075
  function parseDataImageUrl2(value) {
@@ -16274,7 +16352,7 @@ function nativeVisibleWorkspaces(catalog, selectedWorkspaceId) {
16274
16352
  function projectWorkspace(project) {
16275
16353
  const root = project.roots[0];
16276
16354
  return {
16277
- workspaceId: `${CODEX_WORKSPACE_PREFIX}project:${project.id}`,
16355
+ workspaceId: codexProjectWorkspaceId(project.id),
16278
16356
  path: root.path,
16279
16357
  title: project.name.trim() || basename(root.path),
16280
16358
  sessionIds: [],
@@ -16825,7 +16903,7 @@ function normalizeServerUrl(value) {
16825
16903
  }
16826
16904
 
16827
16905
  // src/version.ts
16828
- var PLUGIN_VERSION = "0.4.5";
16906
+ var PLUGIN_VERSION = "0.4.7";
16829
16907
 
16830
16908
  // src/server-api.ts
16831
16909
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -18703,6 +18781,26 @@ var ClientModeRuntime = class {
18703
18781
  this.logger.info("CodeX virtual workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
18704
18782
  return { ...this.status(), workspace };
18705
18783
  }
18784
+ async createCodexWorkspace(targetDeviceId, path, signal) {
18785
+ const trimmedPath = path.trim();
18786
+ if (trimmedPath === "") throw new ClientModeError("INVALID_MESSAGE", "A CodeX project directory is required.");
18787
+ const remote = await this.ensureConnected(targetDeviceId, signal);
18788
+ remote.features = await probeRemoteHostFeatures(remote.client, remote.clientVersion);
18789
+ if (!remote.features.codex) {
18790
+ throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The selected Host does not provide CodeX workspaces.");
18791
+ }
18792
+ this.assertRemoteCompatible(remote);
18793
+ const result = record2(await new CodexRemoteClient(remote.client).request("project/create", {
18794
+ name: remoteWorkspaceTitle(trimmedPath),
18795
+ roots: [{ path: trimmedPath }],
18796
+ idempotencyKey: uuidV7()
18797
+ }, signal));
18798
+ const project = record2(result.project);
18799
+ if (typeof project.id !== "string" || project.id.length === 0) {
18800
+ throw new ClientModeError("INVALID_MESSAGE", "The Host returned an invalid CodeX project.");
18801
+ }
18802
+ return this.openCodexWorkspace(targetDeviceId, codexProjectWorkspaceId(project.id), signal);
18803
+ }
18706
18804
  consumeWorkspaceSelection(selection) {
18707
18805
  const pending = this.pendingWorkspaceSelection;
18708
18806
  if (pending?.targetDeviceId === selection.targetDeviceId && pending.workspaceId === selection.workspaceId && (pending.backend ?? "harness") === (selection.backend ?? "harness")) {
@@ -18896,7 +18994,7 @@ var ClientModeRuntime = class {
18896
18994
  if (localRemoteGateway && remote.features.remoteGateway || !localRemoteGateway && this.proxySwitch !== void 0 && remote.features.apiProxy) return;
18897
18995
  throw new ClientModeError(
18898
18996
  "HARNESS_VERSION_INCOMPATIBLE",
18899
- localRemoteGateway ? "The selected Host does not provide the Harness alpha Remote Gateway transport." : "The selected Host does not provide the legacy Harness ApiProxy transport."
18997
+ localRemoteGateway ? "The selected Host does not provide the Harness v0.1.2 Typert Remote Gateway transport." : "The selected Host does not provide the legacy Harness ApiProxy transport."
18900
18998
  );
18901
18999
  }
18902
19000
  async connect(targetDeviceId, signal) {
@@ -19079,6 +19177,13 @@ var ClientModeRuntime = class {
19079
19177
  }
19080
19178
  return ok2(await this.openCodexWorkspace(value.targetDeviceId, value.workspaceId, signal));
19081
19179
  }
19180
+ if (endpoint === "codex.workspace.create") {
19181
+ const value = record2(payload);
19182
+ if (typeof value.targetDeviceId !== "string" || typeof value.path !== "string") {
19183
+ throw new ClientModeError("INVALID_MESSAGE", "A Host and CodeX project directory are required.");
19184
+ }
19185
+ return ok2(await this.createCodexWorkspace(value.targetDeviceId, value.path, signal));
19186
+ }
19082
19187
  if (endpoint === "workspace.selection.consume") {
19083
19188
  const value = record2(payload);
19084
19189
  if (typeof value.targetDeviceId !== "string" || typeof value.workspaceId !== "string") {
@@ -19279,6 +19384,10 @@ function workspaceRecordId(value) {
19279
19384
  }
19280
19385
  return value.workspaceId;
19281
19386
  }
19387
+ function remoteWorkspaceTitle(path) {
19388
+ const normalized = path.replace(/[\\/]+$/u, "");
19389
+ return normalized.split(/[\\/]+/u).filter(Boolean).at(-1) ?? path;
19390
+ }
19282
19391
  function fail2(error) {
19283
19392
  const source = error instanceof Error ? error : void 0;
19284
19393
  const remoteCode = source !== void 0 && "code" in source && typeof source.code === "string" ? source.code : source instanceof ClientModeError ? source.code : void 0;
@@ -19431,24 +19540,24 @@ var IdentityStore = class {
19431
19540
  }
19432
19541
  if (!hasDevice) {
19433
19542
  const keys = generateKeyPair();
19434
- const record4 = { schemaVersion: 1, deviceId: uuidV7(), name: deviceName, publicKey: keys.publicKey };
19435
- await atomicJsonWrite(devicePath, record4, 384);
19543
+ const record5 = { schemaVersion: 1, deviceId: uuidV7(), name: deviceName, publicKey: keys.publicKey };
19544
+ await atomicJsonWrite(devicePath, record5, 384);
19436
19545
  await atomicTextWrite(keyPath, `${keys.privateKey}
19437
19546
  `, 384);
19438
19547
  }
19439
19548
  await assertPrivateMode(keyPath);
19440
19549
  try {
19441
- let record4 = identitySchema.parse(JSON.parse(await readFile(devicePath, "utf8")));
19550
+ let record5 = identitySchema.parse(JSON.parse(await readFile(devicePath, "utf8")));
19442
19551
  const privateKey = (await readFile(keyPath, "utf8")).trim();
19443
19552
  const regenerated = generateKeyPair(fromBase64Url2(privateKey));
19444
- if (regenerated.publicKey !== record4.publicKey) {
19553
+ if (regenerated.publicKey !== record5.publicKey) {
19445
19554
  throw new IdentityInvalidError("device public and private keys do not match");
19446
19555
  }
19447
- if (record4.name !== deviceName) {
19448
- record4 = { ...record4, name: deviceName };
19449
- await atomicJsonWrite(devicePath, record4, 384);
19556
+ if (record5.name !== deviceName) {
19557
+ record5 = { ...record5, name: deviceName };
19558
+ await atomicJsonWrite(devicePath, record5, 384);
19450
19559
  }
19451
- this.identity = { ...record4, privateKey, fingerprint: fingerprint(record4.publicKey) };
19560
+ this.identity = { ...record5, privateKey, fingerprint: fingerprint(record5.publicKey) };
19452
19561
  await this.loadPeers();
19453
19562
  return this.identity;
19454
19563
  } catch (error) {
@@ -19590,10 +19699,10 @@ var ServerCredentialStore = class {
19590
19699
  return parsed.serverUrl === serverUrl && parsed.deviceId === deviceId ? parsed : void 0;
19591
19700
  }
19592
19701
  async save(credentials) {
19593
- const record4 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
19594
- await atomicWrite(this.path, `${JSON.stringify(record4, null, 2)}
19702
+ const record5 = credentialSchema.parse({ schemaVersion: 1, ...credentials });
19703
+ await atomicWrite(this.path, `${JSON.stringify(record5, null, 2)}
19595
19704
  `);
19596
- return record4;
19705
+ return record5;
19597
19706
  }
19598
19707
  async clear() {
19599
19708
  await rm2(this.path, { force: true });
@@ -21395,6 +21504,9 @@ var commandExecuteSchema = external_exports.object({
21395
21504
  var commandListSchema = external_exports.object({
21396
21505
  agentId: external_exports.string().min(1).max(128)
21397
21506
  }).strict();
21507
+ var directoryListSchema = external_exports.object({
21508
+ path: external_exports.string().min(1).max(4096).optional()
21509
+ }).strict();
21398
21510
  var credentialRefSchema = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Invalid credential reference name").max(128);
21399
21511
  var CONFIG_PLANE_SETTINGS_BYTES = 64 * 1024;
21400
21512
  var CONFIG_PLANE_MAX_OPS = 64;
@@ -21524,10 +21636,18 @@ var HarnessApiBridge = class {
21524
21636
  answer;
21525
21637
  async call(input2) {
21526
21638
  const params = callSchema2.parse(input2);
21639
+ const signal = AbortSignal.timeout(NATIVE_CALL_TIMEOUT_MS);
21527
21640
  const method = this.methods.get(params.method);
21528
- if (method === void 0) throw deniedMethod(params.method);
21641
+ if (method === void 0) {
21642
+ if (params.method === "host.listDirectory") {
21643
+ return this.callDirectoryFallback(params, signal);
21644
+ }
21645
+ if (params.method === "host.describe") {
21646
+ return this.describeFallback(params.rpcId);
21647
+ }
21648
+ throw deniedMethod(params.method);
21649
+ }
21529
21650
  const startedAt = performance.now();
21530
- const signal = AbortSignal.timeout(NATIVE_CALL_TIMEOUT_MS);
21531
21651
  const request = { rpcId: params.rpcId, payload: params.payload };
21532
21652
  try {
21533
21653
  const callWithTimeout = (overridePayload) => withTimeout(
@@ -21542,12 +21662,10 @@ var HarnessApiBridge = class {
21542
21662
  response = await callWithTimeout(request.payload);
21543
21663
  }
21544
21664
  if (params.method === "host.listDirectory" && needsRemoteDirectoryFallback(response)) {
21545
- const payload = typeof params.payload === "object" && params.payload !== null ? params.payload : {};
21546
- const value = await listRemoteDirectory(typeof payload.path === "string" ? payload.path : void 0, signal);
21547
- response = { rpcId: params.rpcId, result: { ok: true, value } };
21665
+ response = await this.callDirectoryFallback(params, signal);
21548
21666
  }
21549
21667
  if (params.method === "host.describe") {
21550
- response = normalizeHostDescribe(response, this.harnessVersion, this.methods.has("host.listDirectory"));
21668
+ response = normalizeHostDescribe(response, this.harnessVersion, true);
21551
21669
  }
21552
21670
  this.logger?.debug("harness api call ok", {
21553
21671
  method: params.method,
@@ -21565,6 +21683,26 @@ var HarnessApiBridge = class {
21565
21683
  throw error;
21566
21684
  }
21567
21685
  }
21686
+ async callDirectoryFallback(params, signal) {
21687
+ const payload = directoryListSchema.parse(params.payload);
21688
+ const value = await listRemoteDirectory(payload.path, signal);
21689
+ return { rpcId: params.rpcId, result: { ok: true, value } };
21690
+ }
21691
+ describeFallback(rpcId) {
21692
+ return {
21693
+ rpcId,
21694
+ result: {
21695
+ ok: true,
21696
+ value: {
21697
+ version: this.harnessVersion ?? "0.0.1",
21698
+ cwd: "",
21699
+ home: "",
21700
+ attachedSessions: 0,
21701
+ canOpenPath: true
21702
+ }
21703
+ }
21704
+ };
21705
+ }
21568
21706
  openTransfer(input2) {
21569
21707
  this.pruneTransfers();
21570
21708
  const params = transferOpenSchema.parse(input2);
@@ -22131,6 +22269,9 @@ var transferReadSchema2 = external_exports.object({
22131
22269
  transferId: external_exports.string().uuid(),
22132
22270
  index: external_exports.number().int().nonnegative()
22133
22271
  }).strict();
22272
+ var directoryListSchema2 = external_exports.object({
22273
+ path: external_exports.string().min(1).max(4096).optional()
22274
+ }).strict();
22134
22275
  var MAX_ACTIVE_STREAMS = 16;
22135
22276
  var MAX_ACTIVE_TRANSFERS = 2;
22136
22277
  var TRANSFER_IDLE_MS = 2 * 6e4;
@@ -22206,15 +22347,29 @@ var HarnessRemoteBridge = class {
22206
22347
  async call(input2) {
22207
22348
  const params = callSchema3.parse(input2);
22208
22349
  this.assertAllowed(params.endpoint);
22350
+ if (params.endpoint === "session/canOpenWorkspacePath") {
22351
+ return { ok: true, value: true };
22352
+ }
22209
22353
  const startedAt = performance.now();
22354
+ const signal = AbortSignal.timeout(6e4);
22210
22355
  try {
22211
- const result = await this.gateway.dispatch(params.endpoint, params.payload, AbortSignal.timeout(6e4));
22356
+ const nativeResult = await this.gateway.dispatch(params.endpoint, params.payload, signal);
22357
+ const result = params.endpoint === "directoryPicker/list" && needsDirectoryFallback(nativeResult) ? await this.directoryList(params.payload, signal) : nativeResult;
22212
22358
  this.logger?.debug("harness remote call ok", {
22213
22359
  endpoint: params.endpoint,
22214
22360
  durationMs: Math.round(performance.now() - startedAt)
22215
22361
  });
22216
22362
  return result;
22217
22363
  } catch (error) {
22364
+ if (params.endpoint === "directoryPicker/list") {
22365
+ const result = await this.directoryList(params.payload, signal);
22366
+ this.logger?.debug("harness remote call ok", {
22367
+ endpoint: params.endpoint,
22368
+ durationMs: Math.round(performance.now() - startedAt),
22369
+ fallback: "directory-browser"
22370
+ });
22371
+ return result;
22372
+ }
22218
22373
  this.logger?.warn("harness remote call failed", {
22219
22374
  endpoint: params.endpoint,
22220
22375
  durationMs: Math.round(performance.now() - startedAt),
@@ -22223,6 +22378,11 @@ var HarnessRemoteBridge = class {
22223
22378
  throw error;
22224
22379
  }
22225
22380
  }
22381
+ async directoryList(payload, signal) {
22382
+ const args = requestArgs(payload);
22383
+ const params = directoryListSchema2.parse(args);
22384
+ return { ok: true, value: await listRemoteDirectory(params.path, signal) };
22385
+ }
22226
22386
  openTransfer(input2) {
22227
22387
  this.pruneTransfers();
22228
22388
  const params = transferOpenSchema2.parse(input2);
@@ -22406,6 +22566,23 @@ function concatChunks2(chunks, totalBytes) {
22406
22566
  }
22407
22567
  return result;
22408
22568
  }
22569
+ function needsDirectoryFallback(result) {
22570
+ if (result.ok) return false;
22571
+ const code = result.error.code.toLocaleLowerCase();
22572
+ const message = result.error.message.toLocaleLowerCase();
22573
+ return code.includes("capability") || code === "directory-picker/unavailable" || code === "directory-picker-unavailable" || message.includes("browse capability") || message.includes("browser capability") || message.includes("brower capability") || message.includes("directory-picker-unavailable");
22574
+ }
22575
+ function requestArgs(payload) {
22576
+ const root = record4(payload);
22577
+ const args = isRecord7(root.args) ? root.args : root;
22578
+ return record4(args.request ?? args._request ?? args);
22579
+ }
22580
+ function record4(value) {
22581
+ return isRecord7(value) ? value : {};
22582
+ }
22583
+ function isRecord7(value) {
22584
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22585
+ }
22409
22586
 
22410
22587
  // src/codex/domain.ts
22411
22588
  import { randomUUID } from "node:crypto";
@@ -22597,7 +22774,7 @@ var CodexAppServerClient = class {
22597
22774
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted invalid JSON."));
22598
22775
  return;
22599
22776
  }
22600
- if (!isRecord7(value)) {
22777
+ if (!isRecord8(value)) {
22601
22778
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted an invalid message."));
22602
22779
  return;
22603
22780
  }
@@ -22642,12 +22819,12 @@ var CodexAppServerClient = class {
22642
22819
  }
22643
22820
  };
22644
22821
  function safeUpstreamError(value) {
22645
- if (!isRecord7(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
22822
+ if (!isRecord8(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
22646
22823
  const message = value.message.toLowerCase();
22647
22824
  if (message.includes("active writer")) return "Codex thread already has an active writer.";
22648
22825
  return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
22649
22826
  }
22650
- function isRecord7(value) {
22827
+ function isRecord8(value) {
22651
22828
  return typeof value === "object" && value !== null && !Array.isArray(value);
22652
22829
  }
22653
22830
 
@@ -22669,6 +22846,9 @@ var imageInput = external_exports.object({
22669
22846
  }).strict();
22670
22847
  var input = external_exports.array(external_exports.union([textInput, imageInput])).min(1).max(16);
22671
22848
  var permissionPreset = external_exports.enum(["workspace-write", "danger-full-access"]);
22849
+ var projectRoot = external_exports.object({
22850
+ path: external_exports.string().min(1).max(4096)
22851
+ }).strict();
22672
22852
  var schemas = {
22673
22853
  "account/read": external_exports.object({ refreshToken: external_exports.literal(false).optional() }).strict(),
22674
22854
  "model/list": external_exports.object({
@@ -22680,6 +22860,11 @@ var schemas = {
22680
22860
  cursor,
22681
22861
  limit: external_exports.number().int().min(1).max(100).optional()
22682
22862
  }).strict(),
22863
+ "project/create": external_exports.object({
22864
+ name: external_exports.string().trim().min(1).max(256),
22865
+ roots: external_exports.array(projectRoot).length(1),
22866
+ idempotencyKey: external_exports.string().min(16).max(256)
22867
+ }).strict(),
22683
22868
  "thread/list": external_exports.object({
22684
22869
  cursor,
22685
22870
  limit: external_exports.number().int().min(1).max(100).optional(),
@@ -23009,15 +23194,15 @@ function decodeCanonicalBase643(value) {
23009
23194
  }
23010
23195
  return decoded;
23011
23196
  }
23012
- function isRecord8(value) {
23197
+ function isRecord9(value) {
23013
23198
  return typeof value === "object" && value !== null && !Array.isArray(value);
23014
23199
  }
23015
23200
  function safeErrorCode2(error) {
23016
- if (isRecord8(error) && typeof error.code === "string") return error.code;
23201
+ if (isRecord9(error) && typeof error.code === "string") return error.code;
23017
23202
  return "UNKNOWN";
23018
23203
  }
23019
23204
  function safeMethod(input2) {
23020
- return isRecord8(input2) && typeof input2.method === "string" ? input2.method : "invalid";
23205
+ return isRecord9(input2) && typeof input2.method === "string" ? input2.method : "invalid";
23021
23206
  }
23022
23207
  function concatChunks3(chunks, totalBytes) {
23023
23208
  const output = new Uint8Array(totalBytes);
@@ -23101,6 +23286,15 @@ var CodexRemoteDomain = class {
23101
23286
  if (call.method === "project/list") {
23102
23287
  return sanitizeProjectList(await this.callUpstream(call.method, call.params));
23103
23288
  }
23289
+ if (call.method === "project/create") {
23290
+ const roots = call.params.roots;
23291
+ const path = await this.requireNewCodexProjectPath(roots[0].path);
23292
+ return sanitizeProjectCreate(await this.callUpstream(call.method, {
23293
+ name: call.params.name,
23294
+ roots: [{ path }],
23295
+ idempotencyKey: call.params.idempotencyKey
23296
+ }));
23297
+ }
23104
23298
  if (call.method === "thread/list") {
23105
23299
  const result = sanitizeThreadList(await this.callUpstream(call.method, call.params));
23106
23300
  return filterThreadListByWorkspaceAuthority(
@@ -23463,9 +23657,9 @@ var CodexRemoteDomain = class {
23463
23657
  itemsView,
23464
23658
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
23465
23659
  });
23466
- const pageResult = isRecord9(result) ? result : {};
23660
+ const pageResult = isRecord10(result) ? result : {};
23467
23661
  for (const rawTurn of array2(pageResult.data)) {
23468
- if (!isRecord9(rawTurn)) continue;
23662
+ if (!isRecord10(rawTurn)) continue;
23469
23663
  const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
23470
23664
  const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
23471
23665
  turns.push({ ...rawTurn, items });
@@ -23487,9 +23681,9 @@ var CodexRemoteDomain = class {
23487
23681
  sortDirection: "asc",
23488
23682
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
23489
23683
  });
23490
- const pageResult = isRecord9(result) ? result : {};
23684
+ const pageResult = isRecord10(result) ? result : {};
23491
23685
  for (const entry of array2(pageResult.data)) {
23492
- if (isRecord9(entry) && entry.item !== void 0) items.push(entry.item);
23686
+ if (isRecord10(entry) && entry.item !== void 0) items.push(entry.item);
23493
23687
  }
23494
23688
  cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
23495
23689
  if (cursor2 === void 0) break;
@@ -23553,7 +23747,7 @@ var CodexRemoteDomain = class {
23553
23747
  return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
23554
23748
  }
23555
23749
  resolveUpstreamApproval(params) {
23556
- if (!isRecord9(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
23750
+ if (!isRecord10(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
23557
23751
  for (const [handle, approval] of this.approvals) {
23558
23752
  if (approval.upstreamId === params.requestId) this.approvals.delete(handle);
23559
23753
  }
@@ -23680,6 +23874,18 @@ var CodexRemoteDomain = class {
23680
23874
  }
23681
23875
  throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX working directory is not available as a Workspace.");
23682
23876
  }
23877
+ async requireNewCodexProjectPath(path) {
23878
+ if (!isAbsolute3(path)) {
23879
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX project directory must be an existing absolute directory.");
23880
+ }
23881
+ try {
23882
+ const canonical = await realpath(resolve2(path));
23883
+ if (!(await stat4(canonical)).isDirectory()) throw new Error("not a directory");
23884
+ return canonical;
23885
+ } catch {
23886
+ throw new RpcError("CODEX_PATH_NOT_ALLOWED", "The CodeX project directory must be an existing absolute directory.");
23887
+ }
23888
+ }
23683
23889
  async listCodexDirectory(path) {
23684
23890
  const target = await this.resolveCodexDirectory(path);
23685
23891
  const rows = await readdir2(target.path, { withFileTypes: true }).catch(() => void 0);
@@ -23758,24 +23964,24 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
23758
23964
  ])];
23759
23965
  }
23760
23966
  function parseCallEnvelope(input2) {
23761
- if (!isRecord9(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
23967
+ if (!isRecord10(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
23762
23968
  throw new RpcError("INVALID_MESSAGE", "The Codex call envelope is invalid.");
23763
23969
  }
23764
23970
  return { method: input2.method, params: input2.params };
23765
23971
  }
23766
23972
  function parseRespond(input2) {
23767
- if (!isRecord9(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
23973
+ if (!isRecord10(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
23768
23974
  throw new RpcError("INVALID_MESSAGE", "The Codex approval response is invalid.");
23769
23975
  }
23770
23976
  return input2;
23771
23977
  }
23772
23978
  function accountCanRun(result) {
23773
- if (!isRecord9(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
23774
- return result.requiresOpenaiAuth === false || isRecord9(result.account);
23979
+ if (!isRecord10(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
23980
+ return result.requiresOpenaiAuth === false || isRecord10(result.account);
23775
23981
  }
23776
23982
  function sanitizeAccount(result) {
23777
- if (!isRecord9(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
23778
- const account = isRecord9(result.account) ? result.account : void 0;
23983
+ if (!isRecord10(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
23984
+ const account = isRecord10(result.account) ? result.account : void 0;
23779
23985
  return {
23780
23986
  authenticated: account !== void 0 || result.requiresOpenaiAuth === false,
23781
23987
  requiresOpenaiAuth: result.requiresOpenaiAuth === true,
@@ -23788,11 +23994,11 @@ function sanitizeAccount(result) {
23788
23994
  };
23789
23995
  }
23790
23996
  function sanitizeThreadList(result) {
23791
- if (!isRecord9(result) || !Array.isArray(result.data)) {
23997
+ if (!isRecord10(result) || !Array.isArray(result.data)) {
23792
23998
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
23793
23999
  }
23794
24000
  const data = result.data.flatMap((value) => {
23795
- if (!isRecord9(value) || typeof value.id !== "string") return [];
24001
+ if (!isRecord10(value) || typeof value.id !== "string") return [];
23796
24002
  return [{
23797
24003
  id: value.id,
23798
24004
  ...typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {},
@@ -23804,7 +24010,7 @@ function sanitizeThreadList(result) {
23804
24010
  ...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {},
23805
24011
  ...typeof value.archived === "boolean" ? { archived: value.archived } : {},
23806
24012
  ...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {},
23807
- ...isRecord9(value.status) ? { status: value.status } : {}
24013
+ ...isRecord10(value.status) ? { status: value.status } : {}
23808
24014
  }];
23809
24015
  });
23810
24016
  return {
@@ -23814,39 +24020,47 @@ function sanitizeThreadList(result) {
23814
24020
  };
23815
24021
  }
23816
24022
  function filterThreadListByWorkspaceAuthority(result, authority) {
23817
- if (!isRecord9(result) || !Array.isArray(result.data)) {
24023
+ if (!isRecord10(result) || !Array.isArray(result.data)) {
23818
24024
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
23819
24025
  }
23820
24026
  return {
23821
24027
  ...result,
23822
- data: result.data.map((record4) => isRecord9(record4) ? record4 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24028
+ data: result.data.map((record5) => isRecord10(record5) ? record5 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24029
+ };
24030
+ }
24031
+ function sanitizeProject(value) {
24032
+ if (!isRecord10(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
24033
+ const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
24034
+ const path = isRecord10(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
24035
+ return path === void 0 || !isAbsolute3(path) ? [] : [{ path }];
24036
+ }) : [];
24037
+ if (roots.length === 0) return void 0;
24038
+ return {
24039
+ id: value.id,
24040
+ name: value.name,
24041
+ roots,
24042
+ ...typeof value.position === "number" && Number.isFinite(value.position) ? { position: value.position } : {},
24043
+ ...typeof value.createdAt === "number" && Number.isFinite(value.createdAt) ? { createdAt: value.createdAt } : {},
24044
+ ...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {}
23823
24045
  };
23824
24046
  }
23825
24047
  function sanitizeProjectList(result) {
23826
- if (!isRecord9(result) || !Array.isArray(result.data)) {
24048
+ if (!isRecord10(result) || !Array.isArray(result.data)) {
23827
24049
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
23828
24050
  }
23829
- const data = result.data.flatMap((value) => {
23830
- if (!isRecord9(value) || typeof value.id !== "string" || typeof value.name !== "string") return [];
23831
- const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
23832
- const path = isRecord9(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
23833
- return path === void 0 || !isAbsolute3(path) ? [] : [{ path }];
23834
- }) : [];
23835
- if (roots.length === 0) return [];
23836
- return [{
23837
- id: value.id,
23838
- name: value.name,
23839
- roots,
23840
- ...typeof value.position === "number" && Number.isFinite(value.position) ? { position: value.position } : {},
23841
- ...typeof value.createdAt === "number" && Number.isFinite(value.createdAt) ? { createdAt: value.createdAt } : {},
23842
- ...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {}
23843
- }];
23844
- });
24051
+ const data = result.data.flatMap((value) => sanitizeProject(value) ?? []);
23845
24052
  return {
23846
24053
  data,
23847
24054
  ...typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? { nextCursor: result.nextCursor } : { nextCursor: null }
23848
24055
  };
23849
24056
  }
24057
+ function sanitizeProjectCreate(result) {
24058
+ const project = isRecord10(result) ? sanitizeProject(result.project) : void 0;
24059
+ if (project === void 0) {
24060
+ throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid created project.");
24061
+ }
24062
+ return { project };
24063
+ }
23850
24064
  function isThreadAllowedByWorkspaceAuthority(thread, authority) {
23851
24065
  const projectId = typeof thread.projectId === "string" ? thread.projectId : void 0;
23852
24066
  if (projectId !== void 0 && authority.projectIds.has(projectId)) return true;
@@ -23873,19 +24087,19 @@ function codexDirectoryCrumbs(root, path) {
23873
24087
  return crumbs2;
23874
24088
  }
23875
24089
  function extractThread(result) {
23876
- return isRecord9(result) && isRecord9(result.thread) ? result.thread : void 0;
24090
+ return isRecord10(result) && isRecord10(result.thread) ? result.thread : void 0;
23877
24091
  }
23878
24092
  function extractTurnId(result) {
23879
- if (!isRecord9(result)) return void 0;
24093
+ if (!isRecord10(result)) return void 0;
23880
24094
  if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
23881
- if (isRecord9(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
24095
+ if (isRecord10(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
23882
24096
  return void 0;
23883
24097
  }
23884
24098
  function extractThreadId(params) {
23885
- if (!isRecord9(params)) return void 0;
24099
+ if (!isRecord10(params)) return void 0;
23886
24100
  if (typeof params.threadId === "string") return params.threadId;
23887
- if (isRecord9(params.thread) && typeof params.thread.id === "string") return params.thread.id;
23888
- if (isRecord9(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
24101
+ if (isRecord10(params.thread) && typeof params.thread.id === "string") return params.thread.id;
24102
+ if (isRecord10(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
23889
24103
  return void 0;
23890
24104
  }
23891
24105
  function optionalInteger2(value) {
@@ -23927,13 +24141,13 @@ function mapCodexImageInputs(params) {
23927
24141
  return {
23928
24142
  ...params,
23929
24143
  input: params.input.map((value) => {
23930
- if (!isRecord9(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
24144
+ if (!isRecord10(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
23931
24145
  return { type: "image", url: `data:${value.mediaType};base64,${value.data}` };
23932
24146
  })
23933
24147
  };
23934
24148
  }
23935
24149
  function sanitizeApprovalParams(params, requestHandle) {
23936
- if (!isRecord9(params)) return { requestHandle };
24150
+ if (!isRecord10(params)) return { requestHandle };
23937
24151
  const safe = { ...params };
23938
24152
  delete safe.proposedExecpolicyAmendment;
23939
24153
  delete safe.additionalPermissions;
@@ -23964,7 +24178,7 @@ function isProjectListFallbackError(error) {
23964
24178
  function canTryNextBinary(error) {
23965
24179
  return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
23966
24180
  }
23967
- function isRecord9(value) {
24181
+ function isRecord10(value) {
23968
24182
  return typeof value === "object" && value !== null && !Array.isArray(value);
23969
24183
  }
23970
24184
  function array2(value) {