ds-harness-remote 0.4.8 → 0.4.10

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.
@@ -1,4 +1,5 @@
1
1
  import { CodexRemoteClient, deriveCodexCwdWorkspaces, projectCodexThread, } from '@dsh-remote/client-core';
2
+ import { codexPermissionPresetFromResponse } from './permissions.js';
2
3
  const CODEX_SESSION_PREFIX = 'codex:';
3
4
  const CODEX_WORKSPACE_PREFIX = 'codex-workspace:';
4
5
  const CODEX_PROVIDER = 'codex';
@@ -245,10 +246,13 @@ export class CodexVirtualHarness {
245
246
  return this.selectedModels.get(sessionId) ?? directory?.default ?? modelSelection();
246
247
  }
247
248
  permissionSelection(sessionId) {
248
- return this.selectedPermissions.get(sessionId) ?? CODEX_DEFAULT_PERMISSION;
249
+ return this.selectedPermissions.get(sessionId);
249
250
  }
250
251
  setPermissionSelection(sessionId, preset) {
251
- this.selectedPermissions.set(sessionId, preset);
252
+ if (preset === undefined)
253
+ this.selectedPermissions.delete(sessionId);
254
+ else
255
+ this.selectedPermissions.set(sessionId, preset);
252
256
  this.publishProjection(sessionId, 'permissions', codexPermissionsProjection(preset), this.nextProjectionSeq());
253
257
  }
254
258
  commandList(args) {
@@ -275,19 +279,25 @@ export class CodexVirtualHarness {
275
279
  result: { kind: 'error', text: 'CodeX Remote permission commands do not accept attachments.' },
276
280
  };
277
281
  const requested = (match[1] ?? '').trim();
278
- if (requested === '')
282
+ if (requested === '') {
283
+ await this.readHistoryPage(threadId, { maxMessages: 1 }, signal);
279
284
  return {
280
285
  commandId,
281
- result: { kind: 'success', text: `current preset ${this.permissionSelection(sessionId)}` },
286
+ result: { kind: 'success', text: `current preset ${this.permissionSelection(sessionId) ?? 'Host settings (not reported)'}` },
282
287
  };
288
+ }
283
289
  if (!isCodexPermissionPreset(requested))
284
290
  return {
285
291
  commandId,
286
292
  result: { kind: 'error', text: `unknown preset "${requested}" (available: workspace-write, danger-full-access)` },
287
293
  };
288
294
  const result = await this.client.request('thread/resume', { threadId, permissionPreset: requested }, signal);
289
- const effective = codexPermissionPresetFromResponse(result) ?? requested;
295
+ const effective = codexPermissionPresetFromResponse(result);
296
+ if (effective === undefined)
297
+ throw new Error('The Host did not confirm the CodeX permission preset.');
290
298
  this.setPermissionSelection(sessionId, effective);
299
+ if (effective !== requested)
300
+ throw new Error('The Host did not apply the requested CodeX permission preset.');
291
301
  return { commandId, result: { kind: 'success', text: `preset ${effective}` } };
292
302
  }
293
303
  sessionTitle(sessionId) {
@@ -499,8 +509,7 @@ export class CodexVirtualHarness {
499
509
  }
500
510
  if (frame.method === 'thread/settings/updated') {
501
511
  const preset = codexPermissionPresetFromResponse(params);
502
- if (preset !== undefined)
503
- this.setPermissionSelection(follow.sessionId, preset);
512
+ this.setPermissionSelection(follow.sessionId, preset);
504
513
  return;
505
514
  }
506
515
  if (frame.method === 'turn/started') {
@@ -1006,17 +1015,15 @@ export class CodexVirtualHarness {
1006
1015
  }
1007
1016
  async forkSession(request, signal) {
1008
1017
  const sessionId = requiredString(request.sessionId, 'sessionId');
1009
- let permissionPreset = this.selectedPermissions.get(sessionId);
1010
1018
  const result = record(await this.client.request('thread/fork', {
1011
1019
  threadId: nativeThreadId(sessionId),
1012
- ...(permissionPreset === undefined ? {} : { permissionPreset }),
1013
1020
  }, signal));
1014
- permissionPreset = codexPermissionPresetFromResponse(result) ?? permissionPreset ?? CODEX_DEFAULT_PERMISSION;
1021
+ const permissionPreset = codexPermissionPresetFromResponse(result);
1015
1022
  const projected = projectCodexThread(record(result.thread));
1016
1023
  if (projected === undefined)
1017
1024
  return failure('internal', 'CodeX returned an invalid forked Thread.');
1018
1025
  this.selectedModels.set(projected.id, this.modelSelection(sessionId, await this.models(signal)));
1019
- this.selectedPermissions.set(projected.id, permissionPreset);
1026
+ this.setPermissionSelection(projected.id, permissionPreset);
1020
1027
  await this.refreshAndPublishWorkspaces();
1021
1028
  const seq = this.nextProjectionSeq();
1022
1029
  this.publishProjection(projected.id, 'title', displayTitle(projected), seq);
@@ -1030,7 +1037,6 @@ export class CodexVirtualHarness {
1030
1037
  return failure('attachment-error', 'CodeX virtual Sessions accept text and pasted PNG, JPEG, WebP, or GIF images only.');
1031
1038
  const threadId = nativeThreadId(sessionId);
1032
1039
  const selection = this.modelSelection(sessionId, await this.models(signal));
1033
- let permissionPreset = this.selectedPermissions.get(sessionId);
1034
1040
  const isFreshBlankThread = this.blankThreads.has(threadId) || this.pendingThreads.has(threadId);
1035
1041
  await this.ensureRcFollow(sessionId);
1036
1042
  if (!isFreshBlankThread) {
@@ -1039,8 +1045,7 @@ export class CodexVirtualHarness {
1039
1045
  model: selection.model,
1040
1046
  }, signal);
1041
1047
  const serverPreset = codexPermissionPresetFromResponse(resumeResult);
1042
- if (permissionPreset === undefined && serverPreset !== undefined) {
1043
- permissionPreset = serverPreset;
1048
+ if (serverPreset !== undefined) {
1044
1049
  this.setPermissionSelection(sessionId, serverPreset);
1045
1050
  }
1046
1051
  }
@@ -1057,7 +1062,6 @@ export class CodexVirtualHarness {
1057
1062
  threadId,
1058
1063
  input,
1059
1064
  ...codexModelParams(selection),
1060
- ...(permissionPreset === undefined ? {} : { permissionPreset }),
1061
1065
  }, signal);
1062
1066
  }
1063
1067
  this.blankThreads.delete(threadId);
@@ -1153,6 +1157,10 @@ export class CodexVirtualHarness {
1153
1157
  if (!isCodexNativeHistoryPage(value, `codex:${threadId}`)) {
1154
1158
  throw new Error('CodeX Remote returned an invalid paginated History.');
1155
1159
  }
1160
+ if (Object.hasOwn(value, 'permissionPreset')) {
1161
+ const preset = value.permissionPreset;
1162
+ this.setPermissionSelection(`codex:${threadId}`, preset === 'workspace-write' || preset === 'danger-full-access' ? preset : undefined);
1163
+ }
1156
1164
  this.cacheHistoryImages(value);
1157
1165
  return value;
1158
1166
  }
@@ -2256,7 +2264,7 @@ function isCanonicalBase64(value) {
2256
2264
  function modelSelectionProjection(selection) {
2257
2265
  return { lastUsed: selection, next: selection };
2258
2266
  }
2259
- function codexPermissionsProjection(currentValue) {
2267
+ function codexPermissionsProjection(preset) {
2260
2268
  return {
2261
2269
  options: [
2262
2270
  {
@@ -2270,7 +2278,7 @@ function codexPermissionsProjection(currentValue) {
2270
2278
  description: 'Full Host file access without approval prompts for subsequent turns in this virtual Session.',
2271
2279
  },
2272
2280
  ],
2273
- currentValue,
2281
+ currentValue: preset ?? 'Host settings (not reported)',
2274
2282
  };
2275
2283
  }
2276
2284
  function codexImageLimitsProjection() {
@@ -2286,25 +2294,6 @@ function codexImageLimitsProjection() {
2286
2294
  function isCodexPermissionPreset(value) {
2287
2295
  return value === 'workspace-write' || value === 'danger-full-access';
2288
2296
  }
2289
- function codexPermissionPresetFromResponse(value) {
2290
- const root = record(value);
2291
- return codexPermissionPresetFromPolicyRecord(root)
2292
- ?? codexPermissionPresetFromPolicyRecord(record(root.threadSettings));
2293
- }
2294
- function codexPermissionPresetFromPolicyRecord(value) {
2295
- const sandbox = value.sandbox ?? value.sandboxPolicy;
2296
- if (value.approvalPolicy === 'never' && isDangerFullAccessSandbox(sandbox))
2297
- return 'danger-full-access';
2298
- if (value.approvalPolicy === 'on-request' && isWorkspaceWriteSandbox(sandbox))
2299
- return 'workspace-write';
2300
- return undefined;
2301
- }
2302
- function isDangerFullAccessSandbox(value) {
2303
- return value === 'danger-full-access' || record(value).type === 'dangerFullAccess';
2304
- }
2305
- function isWorkspaceWriteSandbox(value) {
2306
- return value === 'workspace-write' || record(value).type === 'workspaceWrite';
2307
- }
2308
2297
  function modelCatalog(directory) {
2309
2298
  return {
2310
2299
  default: directory.default,