ds-harness-remote 0.4.12 → 0.4.14

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 (65) hide show
  1. package/README.md +4 -4
  2. package/dist/acp.d.ts +75 -0
  3. package/dist/acp.d.ts.map +1 -0
  4. package/dist/acp.js +61 -0
  5. package/dist/acp.js.map +1 -0
  6. package/dist/client-runtime.d.ts +14 -1
  7. package/dist/client-runtime.d.ts.map +1 -1
  8. package/dist/client-runtime.js +34 -24
  9. package/dist/client-runtime.js.map +1 -1
  10. package/dist/client.github.js +224 -9
  11. package/dist/client.js +224 -9
  12. package/dist/client.js.map +1 -1
  13. package/dist/codex/method-policy.d.ts +10 -10
  14. package/dist/codex/peer-bridge.d.ts.map +1 -1
  15. package/dist/codex/peer-bridge.js +3 -4
  16. package/dist/codex/peer-bridge.js.map +1 -1
  17. package/dist/config.d.ts +24 -0
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +3 -0
  20. package/dist/config.js.map +1 -1
  21. package/dist/connection-controller.d.ts +5 -0
  22. package/dist/connection-controller.d.ts.map +1 -1
  23. package/dist/connection-controller.js +16 -1
  24. package/dist/connection-controller.js.map +1 -1
  25. package/dist/control-route.d.ts +22 -0
  26. package/dist/control-route.d.ts.map +1 -1
  27. package/dist/control-route.js +120 -0
  28. package/dist/control-route.js.map +1 -1
  29. package/dist/control-runtime.d.ts +6 -1
  30. package/dist/control-runtime.d.ts.map +1 -1
  31. package/dist/control-runtime.js +58 -5
  32. package/dist/control-runtime.js.map +1 -1
  33. package/dist/harness-api-bridge.d.ts.map +1 -1
  34. package/dist/harness-api-bridge.js +32 -11
  35. package/dist/harness-api-bridge.js.map +1 -1
  36. package/dist/harness-remote-bridge.d.ts +2 -1
  37. package/dist/harness-remote-bridge.d.ts.map +1 -1
  38. package/dist/harness-remote-bridge.js +47 -6
  39. package/dist/harness-remote-bridge.js.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +801 -147
  43. package/dist/index.js.map +4 -4
  44. package/dist/remote-typert-gateway.d.ts +3 -1
  45. package/dist/remote-typert-gateway.d.ts.map +1 -1
  46. package/dist/remote-typert-gateway.js +16 -5
  47. package/dist/remote-typert-gateway.js.map +1 -1
  48. package/dist/rpc-router.d.ts +5 -2
  49. package/dist/rpc-router.d.ts.map +1 -1
  50. package/dist/rpc-router.js +18 -1
  51. package/dist/rpc-router.js.map +1 -1
  52. package/dist/service.d.ts +9 -0
  53. package/dist/service.d.ts.map +1 -1
  54. package/dist/service.js +32 -3
  55. package/dist/service.js.map +1 -1
  56. package/dist/session-format-compat.d.ts +3 -0
  57. package/dist/session-format-compat.d.ts.map +1 -0
  58. package/dist/session-format-compat.js +183 -0
  59. package/dist/session-format-compat.js.map +1 -0
  60. package/dist/tui-command.js +5 -3
  61. package/dist/tui-command.js.map +1 -1
  62. package/dist/version.d.ts +1 -1
  63. package/dist/version.js +1 -1
  64. package/dsh-plugin.json +1 -1
  65. package/package.json +21 -21
package/dist/index.js CHANGED
@@ -4059,6 +4059,15 @@ var MAX_DEVICE_NAME_LENGTH = 128;
4059
4059
  var MAX_DEVICE_PLATFORM_LENGTH = 64;
4060
4060
  var MAX_DEVICE_VERSION_LENGTH = 64;
4061
4061
  var MAX_AUTH_TOKEN_LENGTH = 8 * 1024;
4062
+ var HOST_REGISTRATION_CODE_TTL_MS = 10 * 6e4;
4063
+ var MAX_ACTIVE_TRANSFERS_PER_DIRECTION = 2;
4064
+ var TRANSFER_IDLE_MS = 2 * 6e4;
4065
+ var MAX_RPC_TEXT_INPUT_BYTES = 64 * 1024;
4066
+ var MAX_FILEVIEWER_RANGE_BYTES = 512 * 1024;
4067
+ var MIN_REPLAY_WINDOW_MS = 15 * 6e4;
4068
+ var MAX_ALPHA_STREAMS_PER_CONNECTION = 16;
4069
+ var MAX_ACP_PROMPT_BYTES = 64 * 1024;
4070
+ var MAX_ACP_UPDATE_BYTES = 512 * 1024;
4062
4071
  var SECURE_FRAGMENT_MAGIC = new Uint8Array([68, 83, 72, 70]);
4063
4072
  var SECURE_FRAGMENT_VERSION = 1;
4064
4073
  var SECURE_FRAGMENT_HEADER_BYTES = 17;
@@ -4114,7 +4123,60 @@ var rpcMethods = [
4114
4123
  "codex.app.transfer.chunk",
4115
4124
  "codex.app.transfer.commit",
4116
4125
  "codex.app.transfer.read",
4117
- "codex.app.transfer.close"
4126
+ "codex.app.transfer.close",
4127
+ "acp.initialize",
4128
+ "acp.session.new",
4129
+ "acp.session.load",
4130
+ "acp.session.prompt",
4131
+ "acp.session.respond_permission",
4132
+ "acp.session.cancel",
4133
+ "acp.session.set_mode"
4134
+ ];
4135
+ var errorCodes = [
4136
+ // Protocol / Version
4137
+ "INVALID_MESSAGE",
4138
+ "UNSUPPORTED_VERSION",
4139
+ "CAPABILITY_NOT_SUPPORTED",
4140
+ "METHOD_NOT_FOUND",
4141
+ "METHOD_NOT_ALLOWED",
4142
+ "REQUEST_CONFLICT",
4143
+ "FRAME_TOO_LARGE",
4144
+ "RATE_LIMITED",
4145
+ // Auth / Device
4146
+ "AUTH_REQUIRED",
4147
+ "AUTH_INVALID",
4148
+ "ACCOUNT_AUTH_REQUIRED",
4149
+ "TOKEN_EXPIRED",
4150
+ "DEVICE_NOT_FOUND",
4151
+ "DEVICE_REVOKED",
4152
+ "DEVICE_OWNERSHIP_REQUIRED",
4153
+ "MEMBERSHIP_REQUIRED",
4154
+ "PEER_IDENTITY_MISMATCH",
4155
+ "HOST_REGISTRATION_CODE_NOT_FOUND",
4156
+ "HOST_REGISTRATION_CODE_EXPIRED",
4157
+ "HOST_REGISTRATION_CODE_CONSUMED",
4158
+ // Connection / Transport
4159
+ "HOST_OFFLINE",
4160
+ "CONNECTION_NOT_FOUND",
4161
+ "CONNECTION_FAILED",
4162
+ "CONNECTION_REPLACED",
4163
+ "P2P_FAILED",
4164
+ "TURN_UNAVAILABLE",
4165
+ "RELAY_UNAVAILABLE",
4166
+ "SLOW_CONSUMER",
4167
+ "SECURE_CHANNEL_FAILED",
4168
+ // Harness
4169
+ "HARNESS_UNAVAILABLE",
4170
+ "HARNESS_VERSION_INCOMPATIBLE",
4171
+ "RESPONSE_TOO_LARGE",
4172
+ "SESSION_NOT_FOUND",
4173
+ "SESSION_NOT_READY",
4174
+ "AGENT_BUSY",
4175
+ "PERMISSION_DENIED",
4176
+ "PERMISSION_NOT_PENDING",
4177
+ "RPC_TIMEOUT",
4178
+ "FULL_RESYNC_REQUIRED",
4179
+ "INTERNAL_ERROR"
4118
4180
  ];
4119
4181
  var selectedTransports = ["lan", "p2p", "turn", "relay"];
4120
4182
  function normalizeSdpMLineIndex(value) {
@@ -4140,6 +4202,7 @@ function acceptNegotiatedCapabilities(offered, negotiated) {
4140
4202
  var rpcMethodSchema = external_exports.enum(rpcMethods);
4141
4203
  var messageTypeSchema = external_exports.enum(messageTypes);
4142
4204
  var controlFrameTypeSchema = external_exports.enum(controlFrameTypes);
4205
+ var errorCodeSchema = external_exports.enum(errorCodes);
4143
4206
  var uniqueStrings = (values) => new Set(values).size === values.length;
4144
4207
  var uniqueNumbers = (values) => new Set(values).size === values.length;
4145
4208
  var utf8Length = (value) => new TextEncoder().encode(value).byteLength;
@@ -4181,6 +4244,10 @@ var hostRegistrationCodeRequestSchema = external_exports.object({
4181
4244
  code: external_exports.string().regex(/^[A-Z0-9]{4}-[A-Z0-9]{4}$/u),
4182
4245
  device: hostDeviceDescriptorSchema
4183
4246
  }).strict();
4247
+ var registerOwnedRoleRequestSchema = external_exports.object({
4248
+ v: external_exports.literal(PROTOCOL_VERSION),
4249
+ device: accountDeviceDescriptorSchema
4250
+ }).strict();
4184
4251
  var deviceRefreshRequestSchema = external_exports.object({
4185
4252
  deviceId: deviceIdSchema,
4186
4253
  refreshToken: authTokenSchema
@@ -13958,6 +14025,120 @@ async function waitForResponder(inner, noise) {
13958
14025
 
13959
14026
  // src/control-route.ts
13960
14027
  var CONTROL_RPC_PREFIX = "/ds-harness-remote";
14028
+ var ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
14029
+ var INVALID_REQUEST_RPC_ID = "invalid-request";
14030
+ function registerControlRoute(connection, handler, webServer) {
14031
+ if (webServer !== void 0 && connection.requestRejection !== void 0) {
14032
+ const dispose = webServer.register({
14033
+ kind: "prefix",
14034
+ path: CONTROL_RPC_PREFIX,
14035
+ handler: (req, res) => handleControlRequest(connection, handler, req, res)
14036
+ });
14037
+ return async () => {
14038
+ await dispose();
14039
+ };
14040
+ }
14041
+ return connection.rpc.handle(CONTROL_RPC_PREFIX, handler, {
14042
+ authority: "loopback"
14043
+ });
14044
+ }
14045
+ async function handleControlRequest(connection, handler, req, res) {
14046
+ const rejection = connection.requestRejection?.(req);
14047
+ if (rejection !== void 0) {
14048
+ res.writeHead(rejection);
14049
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
14050
+ return;
14051
+ }
14052
+ const endpoint = endpointFromPath(CONTROL_RPC_PREFIX, new URL(req.url ?? "/", "http://dsh.internal").pathname);
14053
+ if (req.method !== "POST" || endpoint === void 0) {
14054
+ writeText(res, 404, "not found");
14055
+ return;
14056
+ }
14057
+ if (contentType(req.headers) !== "application/json") {
14058
+ writeText(res, 415, "content type must be application/json");
14059
+ return;
14060
+ }
14061
+ let body;
14062
+ try {
14063
+ body = await readJsonBody(req);
14064
+ } catch {
14065
+ writeText(res, 400, "body is not JSON");
14066
+ return;
14067
+ }
14068
+ const message = clientRequest(body);
14069
+ if (message === void 0) {
14070
+ writeJson(res, 200, errorResponse(rpcId(body), {
14071
+ code: "gateway/bad-request",
14072
+ message: "invalid client-request message",
14073
+ details: { issues: [] }
14074
+ }));
14075
+ return;
14076
+ }
14077
+ if (message.method !== endpoint) {
14078
+ writeJson(res, 200, errorResponse(message.rpcId, {
14079
+ code: "gateway/bad-request",
14080
+ message: `method ${JSON.stringify(message.method)} does not match endpoint ${JSON.stringify(endpoint)}`,
14081
+ details: { issues: [] }
14082
+ }));
14083
+ return;
14084
+ }
14085
+ try {
14086
+ const result = await handler(endpoint, message.payload, requestSignal(req));
14087
+ writeJson(res, 200, fullResponse(message.rpcId, result));
14088
+ } catch (error) {
14089
+ writeText(res, 500, `handler failure: ${String(error)}`);
14090
+ }
14091
+ }
14092
+ function endpointFromPath(channel, pathname) {
14093
+ if (!pathname.startsWith(`${channel}/`)) return void 0;
14094
+ const endpoint = pathname.slice(channel.length + 1);
14095
+ if (endpoint.split("/").some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT_PATTERN.test(segment))) {
14096
+ return void 0;
14097
+ }
14098
+ return endpoint;
14099
+ }
14100
+ function contentType(headers) {
14101
+ const raw = headers["content-type"];
14102
+ const value = Array.isArray(raw) ? raw[0] : raw;
14103
+ return value?.split(";", 1)[0]?.trim().toLowerCase();
14104
+ }
14105
+ async function readJsonBody(req) {
14106
+ const chunks = [];
14107
+ for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
14108
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
14109
+ }
14110
+ function requestSignal(req) {
14111
+ const abort = new AbortController();
14112
+ req.on("close", () => {
14113
+ if (!req.complete) abort.abort();
14114
+ });
14115
+ return abort.signal;
14116
+ }
14117
+ function clientRequest(value) {
14118
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
14119
+ const record6 = value;
14120
+ if (record6.type !== "client-request" || typeof record6.rpcId !== "string" || typeof record6.method !== "string") return void 0;
14121
+ return { type: "client-request", rpcId: record6.rpcId, method: record6.method, payload: record6.payload };
14122
+ }
14123
+ function rpcId(value) {
14124
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return INVALID_REQUEST_RPC_ID;
14125
+ const record6 = value;
14126
+ return typeof record6.rpcId === "string" ? record6.rpcId : INVALID_REQUEST_RPC_ID;
14127
+ }
14128
+ function errorResponse(rpcId2, error) {
14129
+ return fullResponse(rpcId2, { ok: false, error });
14130
+ }
14131
+ function fullResponse(rpcId2, result) {
14132
+ return { type: "server-response", rpcId: rpcId2, result };
14133
+ }
14134
+ function writeJson(res, status2, body) {
14135
+ res.writeHead(status2, { "content-type": "application/json" });
14136
+ res.end(JSON.stringify(body));
14137
+ }
14138
+ function writeText(res, status2, body) {
14139
+ res.writeHead(status2);
14140
+ res.end(body);
14141
+ }
13961
14142
 
13962
14143
  // src/ids.ts
13963
14144
  import { randomBytes as randomBytes6 } from "node:crypto";
@@ -14310,11 +14491,168 @@ function base64ToBytes2(value) {
14310
14491
  return bytes;
14311
14492
  }
14312
14493
 
14494
+ // src/session-format-compat.ts
14495
+ function normalizeLegacySessionGatewayValue(endpoint, value) {
14496
+ if (endpoint === "session/page") return normalizePage(value);
14497
+ if (endpoint === "session/follow") return normalizeFollowFrame(value);
14498
+ return value;
14499
+ }
14500
+ function normalizeFollowFrame(value) {
14501
+ if (!isRecord3(value)) return value;
14502
+ if (value.type === "snapshot") {
14503
+ return {
14504
+ ...value,
14505
+ header: normalizeHeader(value.header),
14506
+ records: normalizeRecords(value.records),
14507
+ ...value.assistantStream === void 0 ? { assistantStream: { revision: 0 } } : {}
14508
+ };
14509
+ }
14510
+ return normalizeEntry(value);
14511
+ }
14512
+ function normalizePage(value) {
14513
+ if (!isRecord3(value)) return value;
14514
+ return { ...value, records: normalizeRecords(value.records) };
14515
+ }
14516
+ function normalizeRecords(value) {
14517
+ if (!Array.isArray(value)) return value;
14518
+ const records = [];
14519
+ let previousSeq;
14520
+ for (const record6 of value) {
14521
+ if (!isEventEntry(record6)) continue;
14522
+ const seq = entrySeq(record6);
14523
+ if (previousSeq !== void 0 && seq !== void 0 && seq > previousSeq + 1) {
14524
+ for (let fillerSeq = previousSeq + 1; fillerSeq < seq; fillerSeq += 1) {
14525
+ records.push(createLegacyGapRecord(fillerSeq, record6));
14526
+ }
14527
+ }
14528
+ records.push(normalizeEntry(record6));
14529
+ if (seq !== void 0) previousSeq = seq;
14530
+ }
14531
+ return records;
14532
+ }
14533
+ function normalizeEntry(value) {
14534
+ if (!isEventEntry(value)) return value;
14535
+ return { ...value, event: normalizeEvent(value.event) };
14536
+ }
14537
+ function entrySeq(value) {
14538
+ if (!isEventEntry(value)) return void 0;
14539
+ return isEventSeq(value.event.seq) ? value.event.seq : void 0;
14540
+ }
14541
+ function createLegacyGapRecord(seq, nextRecord) {
14542
+ return {
14543
+ type: "event",
14544
+ event: {
14545
+ type: "legacy/session-gap",
14546
+ seq,
14547
+ time: eventTime(nextRecord),
14548
+ ignorable: true,
14549
+ data: {}
14550
+ }
14551
+ };
14552
+ }
14553
+ function eventTime(value) {
14554
+ if (!isEventEntry(value)) return 0;
14555
+ return typeof value.event.time === "number" && Number.isSafeInteger(value.event.time) ? value.event.time : 0;
14556
+ }
14557
+ function isEventEntry(value) {
14558
+ return isRecord3(value) && value.type === "event" && isRecord3(value.event);
14559
+ }
14560
+ function normalizeHeader(value) {
14561
+ if (!isRecord3(value)) return value;
14562
+ const next = {
14563
+ ...value,
14564
+ version: 3,
14565
+ ...value.delegationDepth === void 0 ? { delegationDepth: 0 } : {}
14566
+ };
14567
+ return next.agentPreset === "code" ? { ...next, agentPreset: "ptc" } : next;
14568
+ }
14569
+ function normalizeEvent(value) {
14570
+ if (!isRecord3(value)) return value;
14571
+ let next = value;
14572
+ const type = normalizeEventType(value.type);
14573
+ if (type !== value.type) next = { ...next, type };
14574
+ if (type === "assistant/message" && Object.hasOwn(next, "sourceEventSeqs")) {
14575
+ const { sourceEventSeqs: _sourceEventSeqs, ...rest } = next;
14576
+ next = rest;
14577
+ }
14578
+ const data = normalizeEventData(type, next.data);
14579
+ if (data !== next.data) next = { ...next, data };
14580
+ const surfaceOp = normalizeSurfaceOp(next.surfaceOp);
14581
+ if (surfaceOp !== next.surfaceOp) next = { ...next, surfaceOp };
14582
+ return next;
14583
+ }
14584
+ function normalizeEventType(value) {
14585
+ if (value === "tool/code-dispatch-start") return "tool/ptc-dispatch-start";
14586
+ if (value === "tool/code-dispatch") return "tool/ptc-dispatch";
14587
+ return value;
14588
+ }
14589
+ function normalizeEventData(type, value) {
14590
+ if (!isRecord3(value)) return value;
14591
+ if (type === "request/header") return normalizeRequestHeaderData(value);
14592
+ if (type === "agent-preset/selected" && value.agentPreset === "code") return { ...value, agentPreset: "ptc" };
14593
+ if (type === "user/message") return normalizeMessage(value);
14594
+ if (type === "agent/inbox/spliced" && Array.isArray(value.inserted)) {
14595
+ return { ...value, inserted: value.inserted.map((item) => normalizeMessage(item)) };
14596
+ }
14597
+ if (type === "session/title-llm-request" && Array.isArray(value.messages)) {
14598
+ return { ...value, messages: value.messages.map((item) => normalizeMessage(item)) };
14599
+ }
14600
+ return value;
14601
+ }
14602
+ function normalizeRequestHeaderData(value) {
14603
+ if (!isRecord3(value.header)) return value;
14604
+ const header = normalizeRequestHeader(value.header);
14605
+ return header === value.header ? value : { ...value, header };
14606
+ }
14607
+ function normalizeRequestHeader(value) {
14608
+ let changed = false;
14609
+ const next = {};
14610
+ for (const [key, field] of Object.entries(value)) {
14611
+ if (key === "system") {
14612
+ changed = true;
14613
+ continue;
14614
+ }
14615
+ if (key === "tools" && Array.isArray(field) && field.length === 0) {
14616
+ changed = true;
14617
+ continue;
14618
+ }
14619
+ if (key === "adapterDefaults" && isRecord3(field) && Object.keys(field).length === 0) {
14620
+ changed = true;
14621
+ continue;
14622
+ }
14623
+ next[key] = field;
14624
+ }
14625
+ return changed ? next : value;
14626
+ }
14627
+ function normalizeMessage(value) {
14628
+ if (!isRecord3(value) || !isRecord3(value.source)) return value;
14629
+ if (value.source.kind !== "plugin" || value.source.plugin !== "tools-code-mode") return value;
14630
+ return { ...value, source: { ...value.source, plugin: "tools-ptc" } };
14631
+ }
14632
+ function normalizeSurfaceOp(value) {
14633
+ if (!isRecord3(value) || value.op !== "replace") return value;
14634
+ const { start, end, ...rest } = value;
14635
+ if (start === void 0 && end === void 0) return value;
14636
+ return {
14637
+ ...rest,
14638
+ op: "replace",
14639
+ ...start === void 0 ? {} : { startSeq: start },
14640
+ ...end === void 0 ? {} : { endSeq: end }
14641
+ };
14642
+ }
14643
+ function isRecord3(value) {
14644
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14645
+ }
14646
+ function isEventSeq(value) {
14647
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
14648
+ }
14649
+
14313
14650
  // src/remote-typert-gateway.ts
14314
14651
  var DIRECT_REMOTE_CALL_BYTES2 = 2 * 1024 * 1024;
14315
14652
  var RemoteTypertGateway2 = class {
14316
- constructor(client) {
14653
+ constructor(client, compatibility) {
14317
14654
  this.client = client;
14655
+ this.compatibility = compatibility;
14318
14656
  }
14319
14657
  async invoke(request) {
14320
14658
  const result = await this.dispatch(
@@ -14339,7 +14677,11 @@ var RemoteTypertGateway2 = class {
14339
14677
  response = await this.callTransferred(encoded, signal);
14340
14678
  }
14341
14679
  }
14342
- return parseRpcResult(response);
14680
+ const result = parseRpcResult(response);
14681
+ if (result.ok && this.compatibility === "legacy-to-v3") {
14682
+ return { ...result, value: normalizeLegacySessionGatewayValue(endpoint, result.value) };
14683
+ }
14684
+ return result;
14343
14685
  }
14344
14686
  async open(endpoint, payload, signal) {
14345
14687
  const streamId = uuidV7();
@@ -14358,11 +14700,13 @@ var RemoteTypertGateway2 = class {
14358
14700
  unsubscribeClose();
14359
14701
  throw error;
14360
14702
  }
14361
- return this.iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort);
14703
+ return this.iterate(streamId, endpoint, queue, unsubscribe, unsubscribeClose, signal, onAbort);
14362
14704
  }
14363
- async *iterate(streamId, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
14705
+ async *iterate(streamId, endpoint, queue, unsubscribe, unsubscribeClose, signal, onAbort) {
14364
14706
  try {
14365
- yield* queue;
14707
+ for await (const value of queue) {
14708
+ yield this.compatibility === "legacy-to-v3" ? normalizeLegacySessionGatewayValue(endpoint, value) : value;
14709
+ }
14366
14710
  } finally {
14367
14711
  signal.removeEventListener("abort", onAbort);
14368
14712
  unsubscribe();
@@ -14482,16 +14826,16 @@ function routeStreamEvent2(event, streamId, queue) {
14482
14826
  queue.fail(remoteFailure({
14483
14827
  code: typeof failure2?.code === "string" ? failure2.code : "internal",
14484
14828
  message: typeof failure2?.message === "string" ? failure2.message : "The remote Harness stream failed.",
14485
- details: isRecord3(failure2?.details) ? failure2.details : {}
14829
+ details: isRecord4(failure2?.details) ? failure2.details : {}
14486
14830
  }));
14487
14831
  } else {
14488
14832
  queue.close();
14489
14833
  }
14490
14834
  }
14491
14835
  function parseRpcResult(value) {
14492
- if (!isRecord3(value)) throw new Error("The remote Host returned an invalid Gateway result.");
14836
+ if (!isRecord4(value)) throw new Error("The remote Host returned an invalid Gateway result.");
14493
14837
  if (value.ok === true) return Object.hasOwn(value, "value") ? { ok: true, value: value.value } : { ok: true };
14494
- if (value.ok !== false || !isRecord3(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord3(value.error.details)) {
14838
+ if (value.ok !== false || !isRecord4(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string" || !isRecord4(value.error.details)) {
14495
14839
  throw new Error("The remote Host returned an invalid Gateway failure.");
14496
14840
  }
14497
14841
  return {
@@ -14525,7 +14869,7 @@ function base64ToBytes3(value) {
14525
14869
  if (bytesToBase643(bytes) !== value) throw new Error("The remote Host returned non-canonical Harness Remote transfer data.");
14526
14870
  return bytes;
14527
14871
  }
14528
- function isRecord3(value) {
14872
+ function isRecord4(value) {
14529
14873
  return typeof value === "object" && value !== null && !Array.isArray(value);
14530
14874
  }
14531
14875
  function hasErrorCode(error, code) {
@@ -15446,7 +15790,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15446
15790
  this.broadcastRcHost({ type: "host/session-status", sessionId, running: args[1] });
15447
15791
  } else if (event === "api-session/removed" && sessionId !== void 0) {
15448
15792
  this.broadcastRcHost({ type: "host/session-removed", sessionId });
15449
- } else if (event === "api-session/added" && isRecord4(args[0])) {
15793
+ } else if (event === "api-session/added" && isRecord5(args[0])) {
15450
15794
  const summary = args[0];
15451
15795
  this.broadcastRcHost({
15452
15796
  type: "host/session-added",
@@ -15831,7 +16175,7 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15831
16175
  }
15832
16176
  createApiProxy() {
15833
16177
  const call = (endpoint) => async (request, signal) => {
15834
- const payload = endpoint === "session.prompt" && isRecord4(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
16178
+ const payload = endpoint === "session.prompt" && isRecord5(request.payload) ? { ...request.payload, requestId: String(request.rpcId) } : request.payload;
15835
16179
  const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
15836
16180
  return {
15837
16181
  rpcId: request.rpcId,
@@ -15915,8 +16259,8 @@ var CodexVirtualHarness = class _CodexVirtualHarness {
15915
16259
  queue.close();
15916
16260
  }
15917
16261
  }
15918
- broadcastRcMux(payload, rpcId = `codex-mux:${Date.now()}:${Math.random()}`) {
15919
- for (const queue of this.rcMuxStreams) queue.push({ rpcId, payload });
16262
+ broadcastRcMux(payload, rpcId2 = `codex-mux:${Date.now()}:${Math.random()}`) {
16263
+ for (const queue of this.rcMuxStreams) queue.push({ rpcId: rpcId2, payload });
15920
16264
  }
15921
16265
  broadcastRcHost(payload) {
15922
16266
  const frame = { rpcId: `codex-host:${Date.now()}:${Math.random()}`, payload };
@@ -16356,7 +16700,7 @@ function collectImageBlocks(value, output = []) {
16356
16700
  for (const item of value) collectImageBlocks(item, output);
16357
16701
  return output;
16358
16702
  }
16359
- if (!isRecord4(value)) return output;
16703
+ if (!isRecord5(value)) return output;
16360
16704
  if (value.type === "image") output.push(value);
16361
16705
  for (const [key, child] of Object.entries(value)) {
16362
16706
  if (key === "attachment") continue;
@@ -16686,7 +17030,7 @@ function codexPromptInput(content) {
16686
17030
  if (content.length === 0 || content.length > MAX_CODEX_PROMPT_PARTS) return void 0;
16687
17031
  const input2 = [];
16688
17032
  for (const value of content) {
16689
- if (!isRecord4(value)) return void 0;
17033
+ if (!isRecord5(value)) return void 0;
16690
17034
  if (value.type === "text") {
16691
17035
  if (typeof value.text !== "string" || value.text.length === 0 || value.text.length > MAX_CODEX_PROMPT_TEXT) return void 0;
16692
17036
  input2.push({ type: "text", text: value.text });
@@ -16939,12 +17283,12 @@ function errorCode(error) {
16939
17283
  return "code" in error && typeof error.code === "string" ? error.code : void 0;
16940
17284
  }
16941
17285
  function errorDetails(error) {
16942
- return "details" in error && isRecord4(error.details) ? error.details : {};
17286
+ return "details" in error && isRecord5(error.details) ? error.details : {};
16943
17287
  }
16944
17288
  function record2(value) {
16945
- return isRecord4(value) ? value : {};
17289
+ return isRecord5(value) ? value : {};
16946
17290
  }
16947
- function isRecord4(value) {
17291
+ function isRecord5(value) {
16948
17292
  return typeof value === "object" && value !== null && !Array.isArray(value);
16949
17293
  }
16950
17294
  function array(value) {
@@ -17087,7 +17431,8 @@ var Config = s.object({
17087
17431
  codex: s.object({
17088
17432
  enabled: s.boolean(),
17089
17433
  binary: s.string()
17090
- })
17434
+ }),
17435
+ acp: s.object({ enabled: s.boolean(), backends: s.array(s.object({ id: s.string(), enabled: s.boolean(), command: s.string(), args: s.array(s.string()), cwd: s.string() })) })
17091
17436
  });
17092
17437
  var reconnectSchema = external_exports.union([
17093
17438
  external_exports.boolean(),
@@ -17108,7 +17453,8 @@ var configSchema = external_exports.object({
17108
17453
  codex: external_exports.object({
17109
17454
  enabled: external_exports.boolean().optional(),
17110
17455
  binary: external_exports.string().trim().min(1).max(4096).optional()
17111
- }).strict().optional()
17456
+ }).strict().optional(),
17457
+ acp: external_exports.object({ enabled: external_exports.boolean().optional(), backends: external_exports.array(external_exports.object({ id: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i), enabled: external_exports.boolean().optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict()).max(12).optional(), backend: external_exports.string().trim().regex(/^[a-z0-9][a-z0-9._-]{0,31}$/i).optional(), command: external_exports.string().trim().min(1).max(4096).optional(), args: external_exports.array(external_exports.string().max(4096)).max(32).optional(), cwd: external_exports.string().max(4096).optional() }).strict().optional()
17112
17458
  }).strict();
17113
17459
  function resolveConfig(input2 = {}, env = process.env) {
17114
17460
  const parsed = configSchema.parse(input2);
@@ -17136,7 +17482,12 @@ function resolveConfig(input2 = {}, env = process.env) {
17136
17482
  codex: {
17137
17483
  enabled: parsed.codex?.enabled ?? true,
17138
17484
  binary: parsed.codex?.binary ?? "codex"
17139
- }
17485
+ },
17486
+ acp: { enabled: parsed.acp?.enabled ?? true, backends: [.../* @__PURE__ */ new Set(["codex", "cursor", "kimi", ...parsed.acp?.backends?.map((item) => item.id) ?? []])].map((id2) => {
17487
+ const d = parsed.acp?.backends?.find((x) => x.id === id2);
17488
+ const legacy = parsed.acp?.backend === id2 ? parsed.acp : void 0;
17489
+ return { id: id2, enabled: d?.enabled ?? legacy?.enabled ?? true, command: d?.command ?? legacy?.command ?? { codex: "codex", cursor: "agent", kimi: "kimi" }[id2] ?? id2, args: d?.args ?? legacy?.args ?? ["acp"], ...d?.cwd ?? legacy?.cwd ? { cwd: d?.cwd ?? legacy?.cwd } : {} };
17490
+ }) }
17140
17491
  };
17141
17492
  }
17142
17493
  function normalizeServerUrl(value) {
@@ -17158,7 +17509,7 @@ function normalizeServerUrl(value) {
17158
17509
  }
17159
17510
 
17160
17511
  // src/version.ts
17161
- var PLUGIN_VERSION = "0.4.12";
17512
+ var PLUGIN_VERSION = "0.4.14";
17162
17513
 
17163
17514
  // src/server-api.ts
17164
17515
  var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
@@ -17677,7 +18028,7 @@ var TypertGatewaySwitch = class {
17677
18028
  if (normalized !== void 0) return normalized;
17678
18029
  const source = error instanceof Error ? error : new Error("The Harness Gateway rejected the request.");
17679
18030
  const code = "code" in source && typeof source.code === "string" ? source.code : "internal";
17680
- const details = "details" in source && isRecord5(source.details) ? source.details : {};
18031
+ const details = "details" in source && isRecord6(source.details) ? source.details : {};
17681
18032
  return { code, message: source.message, details };
17682
18033
  }
17683
18034
  };
@@ -17686,7 +18037,7 @@ function requestFromCarrier(endpoint, payload, signal) {
17686
18037
  if (segments.length !== 2 || segments.some((segment) => segment.length === 0)) {
17687
18038
  throw new Error("The Harness Gateway endpoint is invalid.");
17688
18039
  }
17689
- if (!isRecord5(payload) || !isRecord5(payload.args)) {
18040
+ if (!isRecord6(payload) || !isRecord6(payload.args)) {
17690
18041
  throw new Error("The Harness Gateway payload is invalid.");
17691
18042
  }
17692
18043
  return { namespace: segments[0], method: segments[1], args: payload.args, signal };
@@ -17701,7 +18052,7 @@ function isLocalOnlyEndpoint(endpoint) {
17701
18052
  function isRemoteCommandMethod(method) {
17702
18053
  return REMOTE_COMMAND_METHODS.includes(method);
17703
18054
  }
17704
- function isRecord5(value) {
18055
+ function isRecord6(value) {
17705
18056
  return typeof value === "object" && value !== null && !Array.isArray(value);
17706
18057
  }
17707
18058
 
@@ -18805,10 +19156,8 @@ var ClientModeRuntime = class {
18805
19156
  fingerprint: this.identity.fingerprint
18806
19157
  });
18807
19158
  }
18808
- registerControl(connection) {
18809
- return connection.rpc.handle(CONTROL_RPC_PREFIX, (endpoint, payload, signal) => this.handleControl(endpoint, payload, signal), {
18810
- authority: "loopback"
18811
- });
19159
+ registerControl(connection, webServer) {
19160
+ return registerControlRoute(connection, (endpoint, payload, signal) => this.handleControl(endpoint, payload, signal), webServer);
18812
19161
  }
18813
19162
  status() {
18814
19163
  const targetStatus = this.gatewaySwitch.supportsCarrier() ? this.gatewaySwitch.status() : this.proxySwitch?.status() ?? this.gatewaySwitch.status();
@@ -18989,9 +19338,9 @@ var ClientModeRuntime = class {
18989
19338
  async openRemoteWorkspace(targetDeviceId, path, signal) {
18990
19339
  if (path.trim() === "") throw new ClientModeError("INVALID_MESSAGE", "A remote working directory is required.");
18991
19340
  const remote = await this.ensureConnected(targetDeviceId, signal);
18992
- this.assertRemoteCompatible(remote);
19341
+ const transport = this.selectHarnessRemoteTransport(remote);
18993
19342
  let workspace;
18994
- if (remote.features.remoteGateway) {
19343
+ if (transport === "remoteGateway") {
18995
19344
  workspace = await new RemoteTypertGateway2(remote.client).invoke({
18996
19345
  namespace: "workspace",
18997
19346
  method: "create",
@@ -19007,7 +19356,7 @@ var ClientModeRuntime = class {
19007
19356
  workspace = unwrapNativeResult(response);
19008
19357
  }
19009
19358
  await this.closeCodexVirtual();
19010
- this.selectRemoteTarget(remote);
19359
+ this.selectRemoteTarget(remote, transport);
19011
19360
  const workspaceId = workspaceRecordId(workspace.workspace);
19012
19361
  this.pendingWorkspaceSelection = { targetDeviceId: remote.target.deviceId, workspaceId };
19013
19362
  this.logger.info("Remote workspace opened", { targetDeviceId: shortId(remote.target.deviceId) });
@@ -19027,7 +19376,7 @@ var ClientModeRuntime = class {
19027
19376
  if (!remote.features.codex) {
19028
19377
  throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The selected Host does not provide CodeX workspaces.");
19029
19378
  }
19030
- this.assertRemoteCompatible(remote);
19379
+ this.assertLocalHarnessCarrierAvailable();
19031
19380
  const virtual = CodexVirtualHarness.remote(remote.client, {
19032
19381
  deviceId: remote.target.deviceId,
19033
19382
  name: remote.target.name
@@ -19060,7 +19409,7 @@ var ClientModeRuntime = class {
19060
19409
  if (!remote.features.codex) {
19061
19410
  throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The selected Host does not provide CodeX workspaces.");
19062
19411
  }
19063
- this.assertRemoteCompatible(remote);
19412
+ this.assertLocalHarnessCarrierAvailable();
19064
19413
  const result = record3(await new CodexRemoteClient(remote.client).request("project/create", {
19065
19414
  name: remoteWorkspaceTitle(trimmedPath),
19066
19415
  roots: [{ path: trimmedPath }],
@@ -19148,10 +19497,10 @@ var ClientModeRuntime = class {
19148
19497
  }
19149
19498
  }
19150
19499
  stream.unsubscribe = remote.client.onEvent((event) => {
19151
- if (event.event === "codex.app.frame" && isRecord6(event.data) && event.data.streamId === value.streamId) {
19500
+ if (event.event === "codex.app.frame" && isRecord7(event.data) && event.data.streamId === value.streamId) {
19152
19501
  this.appendCodexFrame(stream, event.data);
19153
19502
  }
19154
- if (event.event === "codex.app.stream.closed" && isRecord6(event.data) && event.data.streamId === value.streamId) {
19503
+ if (event.event === "codex.app.stream.closed" && isRecord7(event.data) && event.data.streamId === value.streamId) {
19155
19504
  stream.closed = typeof event.data.reason === "string" ? event.data.reason : "closed";
19156
19505
  stream.wake();
19157
19506
  }
@@ -19178,7 +19527,7 @@ var ClientModeRuntime = class {
19178
19527
  stream.wake();
19179
19528
  };
19180
19529
  appendCodexFrame(stream, data) {
19181
- if (!isRecord6(data) || !isRecord6(data.frame) || typeof data.frame.method !== "string") return;
19530
+ if (!isRecord7(data) || !isRecord7(data.frame) || typeof data.frame.method !== "string") return;
19182
19531
  if (stream.frames.length >= 256) {
19183
19532
  stream.closed = "overflow";
19184
19533
  } else {
@@ -19235,18 +19584,25 @@ var ClientModeRuntime = class {
19235
19584
  await stream.close();
19236
19585
  }));
19237
19586
  }
19238
- selectRemoteTarget(remote) {
19587
+ selectRemoteTarget(remote, transport = this.selectHarnessRemoteTransport(remote)) {
19239
19588
  const target = { deviceId: remote.target.deviceId, name: remote.target.name };
19240
- if (this.gatewaySwitch.supportsCarrier()) {
19241
- this.gatewaySwitch.selectRemote(new RemoteTypertGateway2(remote.client), void 0, target);
19589
+ if (transport === "remoteGateway") {
19590
+ this.gatewaySwitch.selectRemote(this.remoteTypertGateway(remote), void 0, target);
19242
19591
  return;
19243
19592
  }
19244
- this.proxySwitch.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
19593
+ this.proxySwitch?.selectRemote(new RemoteHarnessApiProxy(remote.client).api, target);
19245
19594
  this.gatewaySwitch.selectRemote((request) => invokeRemoteCommand(remote.client, request), {
19246
19595
  execute: true,
19247
19596
  list: remote.features.commandList
19248
19597
  }, target);
19249
19598
  }
19599
+ remoteTypertGateway(remote) {
19600
+ const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
19601
+ return new RemoteTypertGateway2(
19602
+ remote.client,
19603
+ localSessionGeneration === "v3" && remote.features.sessionFormat !== 3 ? "legacy-to-v3" : void 0
19604
+ );
19605
+ }
19250
19606
  selectCodexTarget(virtual, remote) {
19251
19607
  const target = { deviceId: remote.target.deviceId, name: remote.target.name };
19252
19608
  if (this.gatewaySwitch.supportsCarrier()) {
@@ -19262,20 +19618,29 @@ var ClientModeRuntime = class {
19262
19618
  await virtual?.close();
19263
19619
  }
19264
19620
  assertRemoteCompatible(remote) {
19621
+ this.selectHarnessRemoteTransport(remote);
19622
+ }
19623
+ selectHarnessRemoteTransport(remote) {
19265
19624
  const localRemoteGateway = this.gatewaySwitch.supportsCarrier();
19625
+ const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
19266
19626
  if (localRemoteGateway && remote.features.remoteGateway) {
19267
- const localSessionGeneration = harnessSessionGeneration(this.host?.localHarnessVersion?.());
19268
- const remoteSessionGeneration = remote.features.sessionFormat === 3 ? "v3" : "legacy";
19269
- if (localSessionGeneration === remoteSessionGeneration) return;
19627
+ if (localSessionGeneration === "v3" || remote.features.sessionFormat !== 3) return "remoteGateway";
19270
19628
  throw new ClientModeError(
19271
19629
  "HARNESS_VERSION_INCOMPATIBLE",
19272
- `The local and remote Harness Session formats differ (${localSessionGeneration} vs ${remoteSessionGeneration}).`
19630
+ "The selected Host uses Harness Session V3, but this Client uses the legacy Typert Remote session format."
19273
19631
  );
19274
19632
  }
19275
- if (!localRemoteGateway && this.proxySwitch !== void 0 && remote.features.apiProxy) return;
19633
+ if (this.proxySwitch !== void 0 && remote.features.apiProxy) return "apiProxy";
19634
+ throw new ClientModeError(
19635
+ "HARNESS_VERSION_INCOMPATIBLE",
19636
+ localRemoteGateway ? "The selected Host does not provide a compatible Harness Typert Remote Gateway transport." : "The selected Host does not provide the legacy Harness ApiProxy transport."
19637
+ );
19638
+ }
19639
+ assertLocalHarnessCarrierAvailable() {
19640
+ if (this.gatewaySwitch.supportsCarrier() || this.proxySwitch !== void 0) return;
19276
19641
  throw new ClientModeError(
19277
19642
  "HARNESS_VERSION_INCOMPATIBLE",
19278
- 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."
19643
+ "This Client does not provide a compatible Harness carrier for the selected remote workspace."
19279
19644
  );
19280
19645
  }
19281
19646
  async connect(targetDeviceId, signal) {
@@ -19654,20 +20019,20 @@ function record3(value) {
19654
20019
  }
19655
20020
  return value;
19656
20021
  }
19657
- function isRecord6(value) {
20022
+ function isRecord7(value) {
19658
20023
  return typeof value === "object" && value !== null && !Array.isArray(value);
19659
20024
  }
19660
20025
  function ok2(value) {
19661
20026
  return { ok: true, value };
19662
20027
  }
19663
20028
  async function invokeRemoteCommand(client, request) {
19664
- const rpcId = uuidV7();
20029
+ const rpcId2 = uuidV7();
19665
20030
  const response = await client.rpc("harness.api.call", {
19666
20031
  method: `${request.namespace}.${request.method}`,
19667
- rpcId,
20032
+ rpcId: rpcId2,
19668
20033
  payload: request.args
19669
20034
  }, request.signal);
19670
- if (response.rpcId !== rpcId) {
20035
+ if (response.rpcId !== rpcId2) {
19671
20036
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid command response.");
19672
20037
  }
19673
20038
  return unwrapNativeResult(response);
@@ -19679,11 +20044,11 @@ async function readRemoteWorkspaceBaseline(gateway, signal) {
19679
20044
  const iterator = source[Symbol.asyncIterator]();
19680
20045
  try {
19681
20046
  const first = await iterator.next();
19682
- if (first.done || !isRecord6(first.value) || first.value.type !== "baseline" || !isRecord6(first.value.value) || !Array.isArray(first.value.value.items)) {
20047
+ if (first.done || !isRecord7(first.value) || first.value.type !== "baseline" || !isRecord7(first.value.value) || !Array.isArray(first.value.value.items)) {
19683
20048
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace baseline.");
19684
20049
  }
19685
20050
  return first.value.value.items.map((item) => {
19686
- if (!isRecord6(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
20051
+ if (!isRecord7(item) || typeof item.workspaceId !== "string" || typeof item.path !== "string" || typeof item.title !== "string") {
19687
20052
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned an invalid Workspace row.");
19688
20053
  }
19689
20054
  return { workspaceId: item.workspaceId, path: item.path, title: item.title };
@@ -19748,19 +20113,20 @@ async function probeRemoteHostFeatures(client, clientVersion) {
19748
20113
  if (error instanceof Error && "code" in error && error.code === "METHOD_NOT_FOUND") return fallback;
19749
20114
  throw error;
19750
20115
  }
19751
- if (!isRecord6(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
20116
+ if (!isRecord7(value) || !Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
19752
20117
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host returned invalid transport capabilities.");
19753
20118
  }
19754
20119
  const capabilities = new Set(value.capabilities);
19755
20120
  const apiProxy = capabilities.has("harness.api.v1");
19756
20121
  const remoteV1 = capabilities.has("harness.remote.v1");
19757
20122
  const remoteV3 = capabilities.has("harness.remote.v3");
20123
+ const codex = capabilities.has("codex.appserver.v1");
19758
20124
  if (remoteV1 && remoteV3) {
19759
20125
  throw new ClientModeError("INVALID_MESSAGE", "The remote Host advertised conflicting Harness Session formats.");
19760
20126
  }
19761
20127
  const sessionFormat = remoteV3 ? 3 : void 0;
19762
20128
  const remoteGateway = remoteV3 || remoteV1;
19763
- if (!apiProxy && !remoteGateway) {
20129
+ if (!apiProxy && !remoteGateway && !codex) {
19764
20130
  throw new ClientModeError("FEATURE_NOT_SUPPORTED", "The remote Host exposes no supported Harness transport.");
19765
20131
  }
19766
20132
  return {
@@ -19769,7 +20135,7 @@ async function probeRemoteHostFeatures(client, clientVersion) {
19769
20135
  apiProxy,
19770
20136
  remoteGateway,
19771
20137
  ...sessionFormat === void 0 ? {} : { sessionFormat },
19772
- codex: capabilities.has("codex.appserver.v1")
20138
+ codex
19773
20139
  };
19774
20140
  }
19775
20141
  async function waitForCodexFrames(stream, signal) {
@@ -19832,6 +20198,7 @@ function iceServersForAttempt(attempt, iceServers) {
19832
20198
 
19833
20199
  // src/control-runtime.ts
19834
20200
  import { hostname as hostname2 } from "node:os";
20201
+ import { execFileSync } from "node:child_process";
19835
20202
 
19836
20203
  // src/identity-store.ts
19837
20204
  import { createHash } from "node:crypto";
@@ -20086,10 +20453,8 @@ var PluginControlRuntime = class {
20086
20453
  this.client = client;
20087
20454
  this.host = host;
20088
20455
  }
20089
- register(connection) {
20090
- return connection.rpc.handle(CONTROL_RPC_PREFIX, (endpoint, payload, signal) => this.handle(endpoint, payload, signal), {
20091
- authority: "loopback"
20092
- });
20456
+ register(connection, webServer) {
20457
+ return registerControlRoute(connection, (endpoint, payload, signal) => this.handle(endpoint, payload, signal), webServer);
20093
20458
  }
20094
20459
  async handle(endpoint, payload, signal) {
20095
20460
  try {
@@ -20098,6 +20463,9 @@ var PluginControlRuntime = class {
20098
20463
  if (endpoint === "settings.server.set") return ok3(await this.setServer(payload));
20099
20464
  if (endpoint === "settings.role.set") return ok3(await this.setRole(payload));
20100
20465
  if (endpoint === "settings.codex.set") return ok3(await this.setCodex(payload));
20466
+ if (endpoint === "settings.acp.set") return ok3(await this.setAcp(payload));
20467
+ if (endpoint === "settings.acp.add") return ok3(await this.addAcp(payload));
20468
+ if (endpoint === "settings.acp.remove") return ok3(await this.removeAcp(payload));
20101
20469
  if (endpoint === "settings.logout") return ok3(await this.logout());
20102
20470
  if (endpoint === "host.reconnect") {
20103
20471
  if (this.host === void 0) throw new ClientModeError("METHOD_NOT_ALLOWED", "This plugin is not running as a Host.");
@@ -20209,6 +20577,37 @@ var PluginControlRuntime = class {
20209
20577
  await this.settings.replace(editableConfig(next));
20210
20578
  return this.settingsView();
20211
20579
  }
20580
+ async setAcp(payload) {
20581
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
20582
+ const value = record4(payload);
20583
+ if (typeof value.backend !== "string" || typeof value.enabled !== "boolean") throw new ClientModeError("INVALID_MESSAGE", "ACP backend and enabled are required.");
20584
+ const current = resolveConfig(this.settings.get());
20585
+ const backends = current.acp?.backends.map((item) => item.id === value.backend ? { ...item, enabled: value.enabled } : item) ?? [];
20586
+ await this.settings.replace({ ...editableConfig(current), acp: { enabled: current.acp?.enabled ?? true, backends } });
20587
+ return this.settingsView();
20588
+ }
20589
+ async addAcp(payload) {
20590
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
20591
+ const value = record4(payload);
20592
+ if (typeof value.id !== "string" || typeof value.command !== "string" || !Array.isArray(value.args) || !value.args.every((item) => typeof item === "string")) {
20593
+ throw new ClientModeError("INVALID_MESSAGE", "ACP name, command, and arguments are required.");
20594
+ }
20595
+ const current = resolveConfig(this.settings.get());
20596
+ if (current.acp?.backends.some((item) => item.id === value.id)) throw new ClientModeError("INVALID_MESSAGE", "ACP backend already exists.");
20597
+ const backends = [...current.acp?.backends ?? [], { id: value.id, command: value.command, args: value.args, enabled: false }];
20598
+ const next = resolveConfig({ ...editableConfig(current), acp: { enabled: current.acp?.enabled ?? true, backends } });
20599
+ await this.settings.replace(editableConfig(next));
20600
+ return this.settingsView();
20601
+ }
20602
+ async removeAcp(payload) {
20603
+ if (this.settings === void 0) throw new ClientModeError("SETTINGS_UNAVAILABLE", "DSH user settings are unavailable in this profile.");
20604
+ const id2 = record4(payload).id;
20605
+ if (typeof id2 !== "string" || ["codex", "cursor", "kimi"].includes(id2)) throw new ClientModeError("INVALID_MESSAGE", "Only custom ACP backends can be removed.");
20606
+ const current = resolveConfig(this.settings.get());
20607
+ const backends = (current.acp?.backends ?? []).filter((item) => item.id !== id2);
20608
+ await this.settings.replace(editableConfig({ ...current, acp: { enabled: current.acp?.enabled ?? true, backends } }));
20609
+ return this.settingsView();
20610
+ }
20212
20611
  async authorizeOwnedRole(serverUrl, sourceRole, targetRole) {
20213
20612
  const sourceDirectory = serverStorageDirectory(this.identityDirectory, serverUrl, sourceRole);
20214
20613
  const sourceIdentity = await new IdentityStore({ directory: sourceDirectory }).loadOrCreate(hostname2());
@@ -20249,6 +20648,7 @@ var PluginControlRuntime = class {
20249
20648
  writable: this.settings !== void 0,
20250
20649
  applies: "restart",
20251
20650
  associations,
20651
+ acpAvailability: Object.fromEntries((config.acp?.backends ?? []).map((item) => [item.id, commandAvailable(item.command ?? "")])),
20252
20652
  ...association === void 0 ? {} : { association }
20253
20653
  };
20254
20654
  }
@@ -20285,6 +20685,14 @@ var PluginControlRuntime = class {
20285
20685
  };
20286
20686
  }
20287
20687
  };
20688
+ function commandAvailable(command) {
20689
+ try {
20690
+ execFileSync(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
20691
+ return true;
20692
+ } catch {
20693
+ return false;
20694
+ }
20695
+ }
20288
20696
  function editableConfig(config) {
20289
20697
  return {
20290
20698
  enabled: config.enabled,
@@ -20300,14 +20708,15 @@ function editableConfig(config) {
20300
20708
  codex: {
20301
20709
  enabled: config.codex.enabled,
20302
20710
  binary: config.codex.binary
20303
- }
20711
+ },
20712
+ ...config.acp === void 0 ? {} : { acp: { enabled: config.acp.enabled, backends: config.acp.backends } }
20304
20713
  };
20305
20714
  }
20306
- function isRecord7(value) {
20715
+ function isRecord8(value) {
20307
20716
  return typeof value === "object" && value !== null && !Array.isArray(value);
20308
20717
  }
20309
20718
  function record4(value) {
20310
- if (!isRecord7(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
20719
+ if (!isRecord8(value)) throw new ClientModeError("INVALID_MESSAGE", "The control request payload is invalid.");
20311
20720
  return value;
20312
20721
  }
20313
20722
  function ok3(value) {
@@ -20431,7 +20840,21 @@ var ConnectionController = class {
20431
20840
  return this.active.size;
20432
20841
  }
20433
20842
  peerDeviceIds() {
20434
- return [...new Set([...this.active.values()].map((connection) => connection.channel.peerDeviceId))];
20843
+ return this.connectedPeers().map((peer) => peer.deviceId);
20844
+ }
20845
+ connectedPeers() {
20846
+ const peers = [];
20847
+ const seen = /* @__PURE__ */ new Set();
20848
+ for (const connection of this.active.values()) {
20849
+ const deviceId = connection.channel.peerDeviceId;
20850
+ if (seen.has(deviceId)) continue;
20851
+ seen.add(deviceId);
20852
+ peers.push({
20853
+ deviceId,
20854
+ ...connection.channel.mode === void 0 ? {} : { mode: connection.channel.mode }
20855
+ });
20856
+ }
20857
+ return peers;
20435
20858
  }
20436
20859
  peerDeviceId() {
20437
20860
  const peers = this.peerDeviceIds();
@@ -20576,7 +20999,14 @@ var apiMethods = /* @__PURE__ */ new Set([
20576
20999
  "codex.app.transfer.chunk",
20577
21000
  "codex.app.transfer.commit",
20578
21001
  "codex.app.transfer.read",
20579
- "codex.app.transfer.close"
21002
+ "codex.app.transfer.close",
21003
+ "acp.initialize",
21004
+ "acp.session.new",
21005
+ "acp.session.load",
21006
+ "acp.session.prompt",
21007
+ "acp.session.respond_permission",
21008
+ "acp.session.cancel",
21009
+ "acp.session.set_mode"
20580
21010
  ]);
20581
21011
  var HOST_CAPABILITIES = [
20582
21012
  "harness.api.v1",
@@ -20585,10 +21015,11 @@ var HOST_CAPABILITIES = [
20585
21015
  "harness.remote.transfer.v1",
20586
21016
  "fileviewer.read.v1",
20587
21017
  "codex.appserver.v1",
20588
- "codex.appserver.transfer.v1"
21018
+ "codex.appserver.transfer.v1",
21019
+ "agent.acp.v1"
20589
21020
  ];
20590
21021
  var RpcRouter = class {
20591
- constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex) {
21022
+ constructor(harnessApi, maxPending = 128, logger, fileViewer, harnessRemote, capabilities = () => HOST_CAPABILITIES, codex, acp) {
20592
21023
  this.harnessApi = harnessApi;
20593
21024
  this.maxPending = maxPending;
20594
21025
  this.logger = logger;
@@ -20596,6 +21027,7 @@ var RpcRouter = class {
20596
21027
  this.harnessRemote = harnessRemote;
20597
21028
  this.capabilities = capabilities;
20598
21029
  this.codex = codex;
21030
+ this.acp = acp;
20599
21031
  }
20600
21032
  active = 0;
20601
21033
  async closePeerStreams() {
@@ -20628,7 +21060,7 @@ var RpcRouter = class {
20628
21060
  });
20629
21061
  return createRpcResponse(request.id, result);
20630
21062
  } catch (error) {
20631
- const response = errorResponse(request.id, error);
21063
+ const response = errorResponse2(request.id, error);
20632
21064
  this.logger?.warn("host rpc failed", {
20633
21065
  method: request.payload.method,
20634
21066
  durationMs: Math.round(performance.now() - startedAt),
@@ -20704,6 +21136,20 @@ var RpcRouter = class {
20704
21136
  return this.requireCodex().readTransfer(params);
20705
21137
  case "codex.app.transfer.close":
20706
21138
  return this.requireCodex().closeTransfer(params);
21139
+ case "acp.initialize":
21140
+ return this.requireAcp().initialize(params);
21141
+ case "acp.session.new":
21142
+ return this.requireAcp().sessionNew(params);
21143
+ case "acp.session.load":
21144
+ return this.requireAcp().sessionLoad(params);
21145
+ case "acp.session.prompt":
21146
+ return this.requireAcp().prompt(params, async () => void 0);
21147
+ case "acp.session.respond_permission":
21148
+ return this.requireAcp().respondPermission(params);
21149
+ case "acp.session.cancel":
21150
+ return this.requireAcp().cancel(params);
21151
+ case "acp.session.set_mode":
21152
+ return this.requireAcp().setMode(params);
20707
21153
  default:
20708
21154
  throw new RpcError("METHOD_NOT_FOUND", "The requested method does not exist.");
20709
21155
  }
@@ -20720,6 +21166,10 @@ var RpcRouter = class {
20720
21166
  }
20721
21167
  return this.harnessRemote;
20722
21168
  }
21169
+ requireAcp() {
21170
+ if (!this.acp) throw new RpcError("CAPABILITY_NOT_SUPPORTED", "ACP is not configured on this Host.");
21171
+ return this.acp;
21172
+ }
20723
21173
  requireCodex() {
20724
21174
  if (this.codex === void 0) {
20725
21175
  throw new RpcError("FEATURE_NOT_SUPPORTED", "Codex Remote is disabled or unavailable on this Host.");
@@ -20727,7 +21177,7 @@ var RpcRouter = class {
20727
21177
  return this.codex;
20728
21178
  }
20729
21179
  };
20730
- function errorResponse(requestId, error) {
21180
+ function errorResponse2(requestId, error) {
20731
21181
  const code = safeErrorCode(error);
20732
21182
  if (error instanceof RpcError) return createRpcError(requestId, code, error.message, error.details, error.retryable);
20733
21183
  return createRpcError(
@@ -21806,11 +22256,22 @@ var transferCloseSchema = external_exports.object({ transferId: transferIdSchema
21806
22256
  var commandExecuteSchema = external_exports.object({
21807
22257
  agentId: external_exports.string().min(1).max(128),
21808
22258
  line: external_exports.string().min(1).max(2048),
21809
- // The official Harness command endpoint requires the `images` wire field.
21810
- // Remote command execution keeps attachments out of scope, so only an empty
21811
- // list is accepted and legacy clients that omit it are normalized below.
21812
- images: external_exports.array(external_exports.never()).length(0).optional()
21813
- }).strict();
22259
+ // dsh-commands <= 0.1.2 calls this field `images`; dsh-commands 0.1.5
22260
+ // renamed it to `submittedAttachments` and expanded its element type to
22261
+ // include staged file receipts. Remote command execution keeps attachments
22262
+ // out of scope, so either compatibility field is restricted to an empty
22263
+ // list. The dispatcher below chooses the matching Host descriptor.
22264
+ images: external_exports.array(external_exports.never()).length(0).optional(),
22265
+ submittedAttachments: external_exports.array(external_exports.never()).length(0).optional()
22266
+ }).strict().superRefine((value, context) => {
22267
+ if (value.images !== void 0 && value.submittedAttachments !== void 0) {
22268
+ context.addIssue({
22269
+ code: external_exports.ZodIssueCode.custom,
22270
+ message: "Use either images or submittedAttachments, not both.",
22271
+ path: ["submittedAttachments"]
22272
+ });
22273
+ }
22274
+ });
21814
22275
  var commandListSchema = external_exports.object({
21815
22276
  agentId: external_exports.string().min(1).max(128)
21816
22277
  }).strict();
@@ -21920,8 +22381,8 @@ var HARNESS_API_ALLOWLIST = [
21920
22381
  "credentials.unset"
21921
22382
  ];
21922
22383
  var NATIVE_CALL_TIMEOUT_MS = 3e4;
21923
- var MAX_ACTIVE_API_TRANSFERS = 2;
21924
- var API_TRANSFER_IDLE_MS = 2 * 6e4;
22384
+ var MAX_ACTIVE_API_TRANSFERS = MAX_ACTIVE_TRANSFERS_PER_DIRECTION;
22385
+ var API_TRANSFER_IDLE_MS = TRANSFER_IDLE_MS;
21925
22386
  var INLINE_TRANSFER_RESPONSE_BYTES = 2 * 1024 * 1024;
21926
22387
  var SESSION_HISTORY_PAGE_SIZES = [50, 30, 20, 12, 6, 3, 1];
21927
22388
  var HarnessApiBridge = class {
@@ -21931,7 +22392,7 @@ var HarnessApiBridge = class {
21931
22392
  this.maxStreams = maxStreams;
21932
22393
  this.logger = logger;
21933
22394
  this.harnessVersion = harnessVersion;
21934
- this.methods = createMethodMap(api, typertGateway);
22395
+ this.methods = createMethodMap(api, typertGateway, harnessVersion);
21935
22396
  this.mux = api.events.mux.bind(api.events);
21936
22397
  this.host = api.events.host.bind(api.events);
21937
22398
  this.answer = api.respond.bind(api);
@@ -21998,9 +22459,9 @@ var HarnessApiBridge = class {
21998
22459
  const value = await listRemoteDirectory(payload.path, signal);
21999
22460
  return { rpcId: params.rpcId, result: { ok: true, value } };
22000
22461
  }
22001
- describeFallback(rpcId) {
22462
+ describeFallback(rpcId2) {
22002
22463
  return {
22003
- rpcId,
22464
+ rpcId: rpcId2,
22004
22465
  result: {
22005
22466
  ok: true,
22006
22467
  value: {
@@ -22218,8 +22679,8 @@ var HarnessApiBridge = class {
22218
22679
  }
22219
22680
  }
22220
22681
  deleteRespondable(value) {
22221
- for (const [rpcId, correlation] of this.respondable) {
22222
- if (correlation === value) this.respondable.delete(rpcId);
22682
+ for (const [rpcId2, correlation] of this.respondable) {
22683
+ if (correlation === value) this.respondable.delete(rpcId2);
22223
22684
  }
22224
22685
  }
22225
22686
  };
@@ -22252,7 +22713,7 @@ function normalizeHostDescribe(response, harnessVersion, canListDirectory) {
22252
22713
  }
22253
22714
  };
22254
22715
  }
22255
- function createMethodMap(api, typertGateway) {
22716
+ function createMethodMap(api, typertGateway, harnessVersion) {
22256
22717
  const domains = api;
22257
22718
  const methods = /* @__PURE__ */ new Map();
22258
22719
  for (const method of HARNESS_API_ALLOWLIST) {
@@ -22262,7 +22723,17 @@ function createMethodMap(api, typertGateway) {
22262
22723
  const implementation2 = async (request, signal) => {
22263
22724
  if (commandMethod === "execute") {
22264
22725
  const payload = commandExecuteSchema.parse(request.payload);
22265
- const args2 = { ...payload, images: payload.images ?? [] };
22726
+ const legacyArgs = {
22727
+ agentId: payload.agentId,
22728
+ line: payload.line,
22729
+ images: payload.images ?? []
22730
+ };
22731
+ const currentArgs = {
22732
+ agentId: payload.agentId,
22733
+ line: payload.line,
22734
+ submittedAttachments: payload.submittedAttachments ?? []
22735
+ };
22736
+ const args2 = harnessSessionGeneration(harnessVersion) === "v3" ? currentArgs : legacyArgs;
22266
22737
  const value2 = await typertGateway.invoke({
22267
22738
  namespace,
22268
22739
  method: "execute",
@@ -22406,9 +22877,9 @@ function sanitizeModelDiscoveryResponse(response, settingsNs) {
22406
22877
  if (typeof result === "object" && result !== null && "ok" in result && result.ok === true) return response;
22407
22878
  return modelDiscoveryFailure(response.rpcId, settingsNs);
22408
22879
  }
22409
- function modelDiscoveryFailure(rpcId, settingsNs) {
22880
+ function modelDiscoveryFailure(rpcId2, settingsNs) {
22410
22881
  return {
22411
- rpcId,
22882
+ rpcId: rpcId2,
22412
22883
  result: {
22413
22884
  ok: false,
22414
22885
  error: {
@@ -22468,15 +22939,15 @@ function frameSessionId(frame) {
22468
22939
  const payload = frame.payload;
22469
22940
  return typeof payload.sessionId === "string" && payload.sessionId.length > 0 ? payload.sessionId : void 0;
22470
22941
  }
22471
- function callSessionHistory(callWithTimeout, payload, rpcId) {
22942
+ function callSessionHistory(callWithTimeout, payload, rpcId2) {
22472
22943
  const fallbackPageSizes = sessionHistoryFallbackPageSizes(payloadMaxMessages(payload));
22473
- return callHistoryWithRetry(callWithTimeout, payload, rpcId, fallbackPageSizes);
22944
+ return callHistoryWithRetry(callWithTimeout, payload, rpcId2, fallbackPageSizes);
22474
22945
  }
22475
- async function callHistoryWithRetry(callWithTimeout, payload, rpcId, pageSizes) {
22946
+ async function callHistoryWithRetry(callWithTimeout, payload, rpcId2, pageSizes) {
22476
22947
  for (const maxMessages of pageSizes) {
22477
22948
  const requestPayload = historyRequestPayload(payload, maxMessages);
22478
22949
  const response = await callWithTimeout(requestPayload);
22479
- const request = createRpcResponse(rpcId, response.result);
22950
+ const request = createRpcResponse(rpcId2, response.result);
22480
22951
  if (encodeMessage(request).byteLength <= MAX_SECURE_MESSAGE_BYTES) return response;
22481
22952
  if (maxMessages === pageSizes[pageSizes.length - 1]) {
22482
22953
  throw new RpcError(
@@ -22564,6 +23035,24 @@ var streamOpenSchema2 = external_exports.object({
22564
23035
  payload: external_exports.unknown()
22565
23036
  }).strict();
22566
23037
  var streamCloseSchema2 = external_exports.object({ streamId: external_exports.string().min(1).max(128) }).strict();
23038
+ var commandExecuteArgsSchema = external_exports.object({
23039
+ agentId: external_exports.string().min(1).max(128),
23040
+ line: external_exports.string().min(1).max(2048),
23041
+ // dsh-commands <= 0.1.2 calls this field `images`; dsh-commands 0.1.5
23042
+ // renamed it to `submittedAttachments`. Only empty arrays are exposed by
23043
+ // the Remote bridge; command attachments are deliberately out of scope.
23044
+ images: external_exports.array(external_exports.never()).length(0).optional(),
23045
+ submittedAttachments: external_exports.array(external_exports.never()).length(0).optional()
23046
+ }).strict().superRefine((value, context) => {
23047
+ if (value.images !== void 0 && value.submittedAttachments !== void 0) {
23048
+ context.addIssue({
23049
+ code: external_exports.ZodIssueCode.custom,
23050
+ message: "Use either images or submittedAttachments, not both.",
23051
+ path: ["submittedAttachments"]
23052
+ });
23053
+ }
23054
+ });
23055
+ var commandExecutePayloadSchema = external_exports.object({ args: commandExecuteArgsSchema }).strict();
22567
23056
  var transferOpenSchema2 = external_exports.object({
22568
23057
  transferId: external_exports.string().uuid(),
22569
23058
  totalBytes: external_exports.number().int().positive().max(MAX_HARNESS_API_TRANSFER_BYTES),
@@ -22582,9 +23071,8 @@ var transferReadSchema2 = external_exports.object({
22582
23071
  var directoryListSchema2 = external_exports.object({
22583
23072
  path: external_exports.string().min(1).max(4096).optional()
22584
23073
  }).strict();
22585
- var MAX_ACTIVE_STREAMS = 16;
22586
- var MAX_ACTIVE_TRANSFERS = 2;
22587
- var TRANSFER_IDLE_MS = 2 * 6e4;
23074
+ var MAX_ACTIVE_STREAMS = MAX_ALPHA_STREAMS_PER_CONNECTION;
23075
+ var MAX_ACTIVE_TRANSFERS = MAX_ACTIVE_TRANSFERS_PER_DIRECTION;
22588
23076
  var INLINE_TRANSFER_RESPONSE_BYTES2 = 2 * 1024 * 1024;
22589
23077
  var HARNESS_REMOTE_ALLOWLIST = [
22590
23078
  "$events",
@@ -22646,10 +23134,11 @@ var HARNESS_REMOTE_ALLOWLIST = [
22646
23134
  ];
22647
23135
  var allowedEndpoints = new Set(HARNESS_REMOTE_ALLOWLIST);
22648
23136
  var HarnessRemoteBridge = class {
22649
- constructor(gateway, publish, logger) {
23137
+ constructor(gateway, publish, logger, harnessVersion) {
22650
23138
  this.gateway = gateway;
22651
23139
  this.publish = publish;
22652
23140
  this.logger = logger;
23141
+ this.harnessVersion = harnessVersion;
22653
23142
  }
22654
23143
  streams = /* @__PURE__ */ new Map();
22655
23144
  incomingTransfers = /* @__PURE__ */ new Map();
@@ -22663,7 +23152,7 @@ var HarnessRemoteBridge = class {
22663
23152
  const startedAt = performance.now();
22664
23153
  const signal = AbortSignal.timeout(6e4);
22665
23154
  try {
22666
- const nativeResult = await this.gateway.dispatch(params.endpoint, params.payload, signal);
23155
+ const nativeResult = params.endpoint === "commands/execute" ? await dispatchCommandForHost(this.gateway, params.payload, signal, this.harnessVersion) : await this.gateway.dispatch(params.endpoint, params.payload, signal);
22667
23156
  const result = params.endpoint === "directoryPicker/list" && needsDirectoryFallback(nativeResult) ? await this.directoryList(params.payload, signal) : nativeResult;
22668
23157
  this.logger?.debug("harness remote call ok", {
22669
23158
  endpoint: params.endpoint,
@@ -22862,6 +23351,29 @@ var HarnessRemoteBridge = class {
22862
23351
  for (const [id2, transfer] of this.outgoingTransfers) if (transfer.touchedAt < cutoff) this.outgoingTransfers.delete(id2);
22863
23352
  }
22864
23353
  };
23354
+ async function dispatchCommandForHost(gateway, payload, signal, harnessVersion) {
23355
+ const parsed = commandExecutePayloadSchema.parse(payload);
23356
+ const legacyPayload = {
23357
+ args: {
23358
+ agentId: parsed.args.agentId,
23359
+ line: parsed.args.line,
23360
+ images: parsed.args.images ?? []
23361
+ }
23362
+ };
23363
+ const currentPayload = {
23364
+ args: {
23365
+ agentId: parsed.args.agentId,
23366
+ line: parsed.args.line,
23367
+ submittedAttachments: parsed.args.submittedAttachments ?? []
23368
+ }
23369
+ };
23370
+ const args = harnessSessionGeneration(harnessVersion) === "v3" ? currentPayload : legacyPayload;
23371
+ return gateway.dispatch(
23372
+ "commands/execute",
23373
+ args,
23374
+ signal
23375
+ );
23376
+ }
22865
23377
  function decodeCanonicalBase642(value) {
22866
23378
  const bytes = Buffer.from(value, "base64");
22867
23379
  if (bytes.toString("base64") !== value) throw new RpcError("INVALID_MESSAGE", "The Harness Remote transfer chunk is invalid.");
@@ -22884,13 +23396,13 @@ function needsDirectoryFallback(result) {
22884
23396
  }
22885
23397
  function requestArgs(payload) {
22886
23398
  const root = record5(payload);
22887
- const args = isRecord8(root.args) ? root.args : root;
23399
+ const args = isRecord9(root.args) ? root.args : root;
22888
23400
  return record5(args.request ?? args._request ?? args);
22889
23401
  }
22890
23402
  function record5(value) {
22891
- return isRecord8(value) ? value : {};
23403
+ return isRecord9(value) ? value : {};
22892
23404
  }
22893
- function isRecord8(value) {
23405
+ function isRecord9(value) {
22894
23406
  return typeof value === "object" && value !== null && !Array.isArray(value);
22895
23407
  }
22896
23408
 
@@ -23084,7 +23596,7 @@ var CodexAppServerClient = class {
23084
23596
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted invalid JSON."));
23085
23597
  return;
23086
23598
  }
23087
- if (!isRecord9(value)) {
23599
+ if (!isRecord10(value)) {
23088
23600
  this.handleProcessFailure("CODEX_INVALID_RESPONSE", new Error("Codex App Server emitted an invalid message."));
23089
23601
  return;
23090
23602
  }
@@ -23129,12 +23641,12 @@ var CodexAppServerClient = class {
23129
23641
  }
23130
23642
  };
23131
23643
  function safeUpstreamError(value) {
23132
- if (!isRecord9(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
23644
+ if (!isRecord10(value) || typeof value.message !== "string") return "Codex App Server rejected the request.";
23133
23645
  const message = value.message.toLowerCase();
23134
23646
  if (message.includes("active writer")) return "Codex thread already has an active writer.";
23135
23647
  return message.includes("not initialized") ? "Codex App Server is not initialized." : "Codex App Server rejected the request.";
23136
23648
  }
23137
- function isRecord9(value) {
23649
+ function isRecord10(value) {
23138
23650
  return typeof value === "object" && value !== null && !Array.isArray(value);
23139
23651
  }
23140
23652
 
@@ -23266,9 +23778,8 @@ var transferChunkSchema3 = external_exports.object({
23266
23778
  }).strict();
23267
23779
  var transferIdSchema3 = external_exports.object({ transferId: external_exports.string().uuid() }).strict();
23268
23780
  var transferReadSchema3 = external_exports.object({ transferId: external_exports.string().uuid(), index: external_exports.number().int().nonnegative() }).strict();
23269
- var MAX_ACTIVE_STREAMS2 = 16;
23270
- var MAX_ACTIVE_TRANSFERS2 = 2;
23271
- var TRANSFER_IDLE_MS2 = 2 * 6e4;
23781
+ var MAX_ACTIVE_STREAMS2 = MAX_ALPHA_STREAMS_PER_CONNECTION;
23782
+ var MAX_ACTIVE_TRANSFERS2 = MAX_ACTIVE_TRANSFERS_PER_DIRECTION;
23272
23783
  var INLINE_TRANSFER_RESPONSE_BYTES3 = 2 * 1024 * 1024;
23273
23784
  var CodexPeerBridge = class {
23274
23785
  constructor(domain, context, publish, logger) {
@@ -23482,7 +23993,7 @@ var CodexPeerBridge = class {
23482
23993
  await this.domain.detachPeer(this.context.connectionId);
23483
23994
  }
23484
23995
  pruneTransfers() {
23485
- const staleBefore = Date.now() - TRANSFER_IDLE_MS2;
23996
+ const staleBefore = Date.now() - TRANSFER_IDLE_MS;
23486
23997
  for (const [id2, transfer] of this.incomingTransfers) {
23487
23998
  if (transfer.touchedAt < staleBefore) this.incomingTransfers.delete(id2);
23488
23999
  }
@@ -23504,15 +24015,15 @@ function decodeCanonicalBase643(value) {
23504
24015
  }
23505
24016
  return decoded;
23506
24017
  }
23507
- function isRecord10(value) {
24018
+ function isRecord11(value) {
23508
24019
  return typeof value === "object" && value !== null && !Array.isArray(value);
23509
24020
  }
23510
24021
  function safeErrorCode2(error) {
23511
- if (isRecord10(error) && typeof error.code === "string") return error.code;
24022
+ if (isRecord11(error) && typeof error.code === "string") return error.code;
23512
24023
  return "UNKNOWN";
23513
24024
  }
23514
24025
  function safeMethod(input2) {
23515
- return isRecord10(input2) && typeof input2.method === "string" ? input2.method : "invalid";
24026
+ return isRecord11(input2) && typeof input2.method === "string" ? input2.method : "invalid";
23516
24027
  }
23517
24028
  function concatChunks3(chunks, totalBytes) {
23518
24029
  const output = new Uint8Array(totalBytes);
@@ -23929,14 +24440,14 @@ var CodexRemoteDomain = class {
23929
24440
  await this.assertResultThreadAllowed(result);
23930
24441
  if (codexPermissionPresetFromResponse(result) !== params.permissionPreset) {
23931
24442
  await this.callUpstream("thread/settings/update", { threadId, ...settings });
23932
- result = { ...isRecord11(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
24443
+ result = { ...isRecord12(result) ? result : {}, ...settings, sandbox: settings.sandboxPolicy };
23933
24444
  }
23934
24445
  }
23935
24446
  await this.publishPermission(threadId, result);
23936
24447
  return result;
23937
24448
  }
23938
24449
  async publishPermission(threadId, value) {
23939
- const source = isRecord11(value) ? value : {};
24450
+ const source = isRecord12(value) ? value : {};
23940
24451
  const threadSettings = { approvalPolicy: source.approvalPolicy, sandboxPolicy: source.sandboxPolicy ?? source.sandbox };
23941
24452
  await this.handleInbound({ kind: "notification", method: "thread/settings/updated", params: { threadId, threadSettings } });
23942
24453
  }
@@ -24032,9 +24543,9 @@ var CodexRemoteDomain = class {
24032
24543
  itemsView,
24033
24544
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
24034
24545
  });
24035
- const pageResult = isRecord11(result) ? result : {};
24546
+ const pageResult = isRecord12(result) ? result : {};
24036
24547
  for (const rawTurn of array2(pageResult.data)) {
24037
- if (!isRecord11(rawTurn)) continue;
24548
+ if (!isRecord12(rawTurn)) continue;
24038
24549
  const turnId = typeof rawTurn.id === "string" ? rawTurn.id : void 0;
24039
24550
  const items = rawTurn.itemsView === "full" || turnId === void 0 ? array2(rawTurn.items) : await this.readThreadItems(connectionId, threadId, turnId, array2(rawTurn.items));
24040
24551
  turns.push({ ...rawTurn, items });
@@ -24056,9 +24567,9 @@ var CodexRemoteDomain = class {
24056
24567
  sortDirection: "asc",
24057
24568
  ...cursor2 === void 0 ? {} : { cursor: cursor2 }
24058
24569
  });
24059
- const pageResult = isRecord11(result) ? result : {};
24570
+ const pageResult = isRecord12(result) ? result : {};
24060
24571
  for (const entry of array2(pageResult.data)) {
24061
- if (isRecord11(entry) && entry.item !== void 0) items.push(entry.item);
24572
+ if (isRecord12(entry) && entry.item !== void 0) items.push(entry.item);
24062
24573
  }
24063
24574
  cursor2 = typeof pageResult.nextCursor === "string" && pageResult.nextCursor.length > 0 ? pageResult.nextCursor : void 0;
24064
24575
  if (cursor2 === void 0) break;
@@ -24122,7 +24633,7 @@ var CodexRemoteDomain = class {
24122
24633
  return [...this.peers.values()].some((peer) => peer.hasThreadSubscription(threadId));
24123
24634
  }
24124
24635
  resolveUpstreamApproval(params) {
24125
- if (!isRecord11(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
24636
+ if (!isRecord12(params) || typeof params.requestId !== "string" && typeof params.requestId !== "number") return;
24126
24637
  for (const [handle, approval] of this.approvals) {
24127
24638
  if (approval.upstreamId === params.requestId) this.approvals.delete(handle);
24128
24639
  }
@@ -24339,24 +24850,24 @@ function codexBinaryCandidates(configured, hostPlatform = process.platform, user
24339
24850
  ])];
24340
24851
  }
24341
24852
  function parseCallEnvelope(input2) {
24342
- if (!isRecord11(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
24853
+ if (!isRecord12(input2) || typeof input2.method !== "string" || !("params" in input2) || Object.keys(input2).some((key) => key !== "method" && key !== "params")) {
24343
24854
  throw new RpcError("INVALID_MESSAGE", "The Codex call envelope is invalid.");
24344
24855
  }
24345
24856
  return { method: input2.method, params: input2.params };
24346
24857
  }
24347
24858
  function parseRespond(input2) {
24348
- if (!isRecord11(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
24859
+ if (!isRecord12(input2) || typeof input2.requestHandle !== "string" || !["accept", "decline", "cancel"].includes(String(input2.decision)) || Object.keys(input2).some((key) => key !== "requestHandle" && key !== "decision")) {
24349
24860
  throw new RpcError("INVALID_MESSAGE", "The Codex approval response is invalid.");
24350
24861
  }
24351
24862
  return input2;
24352
24863
  }
24353
24864
  function accountCanRun(result) {
24354
- if (!isRecord11(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
24355
- return result.requiresOpenaiAuth === false || isRecord11(result.account);
24865
+ if (!isRecord12(result) || typeof result.requiresOpenaiAuth !== "boolean") return false;
24866
+ return result.requiresOpenaiAuth === false || isRecord12(result.account);
24356
24867
  }
24357
24868
  function sanitizeAccount(result) {
24358
- if (!isRecord11(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
24359
- const account = isRecord11(result.account) ? result.account : void 0;
24869
+ if (!isRecord12(result)) throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned invalid account state.");
24870
+ const account = isRecord12(result.account) ? result.account : void 0;
24360
24871
  return {
24361
24872
  authenticated: account !== void 0 || result.requiresOpenaiAuth === false,
24362
24873
  requiresOpenaiAuth: result.requiresOpenaiAuth === true,
@@ -24369,11 +24880,11 @@ function sanitizeAccount(result) {
24369
24880
  };
24370
24881
  }
24371
24882
  function sanitizeThreadList(result) {
24372
- if (!isRecord11(result) || !Array.isArray(result.data)) {
24883
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
24373
24884
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
24374
24885
  }
24375
24886
  const data = result.data.flatMap((value) => {
24376
- if (!isRecord11(value) || typeof value.id !== "string") return [];
24887
+ if (!isRecord12(value) || typeof value.id !== "string") return [];
24377
24888
  return [{
24378
24889
  id: value.id,
24379
24890
  ...typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {},
@@ -24385,7 +24896,7 @@ function sanitizeThreadList(result) {
24385
24896
  ...typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) ? { updatedAt: value.updatedAt } : {},
24386
24897
  ...typeof value.archived === "boolean" ? { archived: value.archived } : {},
24387
24898
  ...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {},
24388
- ...isRecord11(value.status) ? { status: value.status } : {}
24899
+ ...isRecord12(value.status) ? { status: value.status } : {}
24389
24900
  }];
24390
24901
  });
24391
24902
  return {
@@ -24395,18 +24906,18 @@ function sanitizeThreadList(result) {
24395
24906
  };
24396
24907
  }
24397
24908
  function filterThreadListByWorkspaceAuthority(result, authority) {
24398
- if (!isRecord11(result) || !Array.isArray(result.data)) {
24909
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
24399
24910
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid thread list.");
24400
24911
  }
24401
24912
  return {
24402
24913
  ...result,
24403
- data: result.data.map((record6) => isRecord11(record6) ? record6 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24914
+ data: result.data.map((record6) => isRecord12(record6) ? record6 : void 0).filter((thread) => thread !== void 0 && isThreadAllowedByWorkspaceAuthority(thread, authority))
24404
24915
  };
24405
24916
  }
24406
24917
  function sanitizeProject(value) {
24407
- if (!isRecord11(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
24918
+ if (!isRecord12(value) || typeof value.id !== "string" || typeof value.name !== "string") return void 0;
24408
24919
  const roots = Array.isArray(value.roots) ? value.roots.flatMap((root) => {
24409
- const path = isRecord11(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
24920
+ const path = isRecord12(root) && typeof root.path === "string" && root.path.length > 0 ? root.path : void 0;
24410
24921
  return path === void 0 || !isAbsolute3(path) ? [] : [{ path }];
24411
24922
  }) : [];
24412
24923
  if (roots.length === 0) return void 0;
@@ -24420,7 +24931,7 @@ function sanitizeProject(value) {
24420
24931
  };
24421
24932
  }
24422
24933
  function sanitizeProjectList(result) {
24423
- if (!isRecord11(result) || !Array.isArray(result.data)) {
24934
+ if (!isRecord12(result) || !Array.isArray(result.data)) {
24424
24935
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid project list.");
24425
24936
  }
24426
24937
  const data = result.data.flatMap((value) => sanitizeProject(value) ?? []);
@@ -24430,7 +24941,7 @@ function sanitizeProjectList(result) {
24430
24941
  };
24431
24942
  }
24432
24943
  function sanitizeProjectCreate(result) {
24433
- const project = isRecord11(result) ? sanitizeProject(result.project) : void 0;
24944
+ const project = isRecord12(result) ? sanitizeProject(result.project) : void 0;
24434
24945
  if (project === void 0) {
24435
24946
  throw new RpcError("CODEX_INVALID_RESPONSE", "Codex App Server returned an invalid created project.");
24436
24947
  }
@@ -24462,19 +24973,19 @@ function codexDirectoryCrumbs(root, path) {
24462
24973
  return crumbs2;
24463
24974
  }
24464
24975
  function extractThread(result) {
24465
- return isRecord11(result) && isRecord11(result.thread) ? result.thread : void 0;
24976
+ return isRecord12(result) && isRecord12(result.thread) ? result.thread : void 0;
24466
24977
  }
24467
24978
  function extractTurnId(result) {
24468
- if (!isRecord11(result)) return void 0;
24979
+ if (!isRecord12(result)) return void 0;
24469
24980
  if (typeof result.turnId === "string" && result.turnId.length > 0) return result.turnId;
24470
- if (isRecord11(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
24981
+ if (isRecord12(result.turn) && typeof result.turn.id === "string" && result.turn.id.length > 0) return result.turn.id;
24471
24982
  return void 0;
24472
24983
  }
24473
24984
  function extractThreadId(params) {
24474
- if (!isRecord11(params)) return void 0;
24985
+ if (!isRecord12(params)) return void 0;
24475
24986
  if (typeof params.threadId === "string") return params.threadId;
24476
- if (isRecord11(params.thread) && typeof params.thread.id === "string") return params.thread.id;
24477
- if (isRecord11(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
24987
+ if (isRecord12(params.thread) && typeof params.thread.id === "string") return params.thread.id;
24988
+ if (isRecord12(params.turn) && typeof params.turn.threadId === "string") return params.turn.threadId;
24478
24989
  return void 0;
24479
24990
  }
24480
24991
  function optionalInteger2(value) {
@@ -24516,13 +25027,13 @@ function mapCodexImageInputs(params) {
24516
25027
  return {
24517
25028
  ...params,
24518
25029
  input: params.input.map((value) => {
24519
- if (!isRecord11(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
25030
+ if (!isRecord12(value) || value.type !== "image" || typeof value.mediaType !== "string" || typeof value.data !== "string") return value;
24520
25031
  return { type: "image", url: `data:${value.mediaType};base64,${value.data}` };
24521
25032
  })
24522
25033
  };
24523
25034
  }
24524
25035
  function sanitizeApprovalParams(params, requestHandle) {
24525
- if (!isRecord11(params)) return { requestHandle };
25036
+ if (!isRecord12(params)) return { requestHandle };
24526
25037
  const safe = { ...params };
24527
25038
  delete safe.proposedExecpolicyAmendment;
24528
25039
  delete safe.additionalPermissions;
@@ -24553,7 +25064,7 @@ function isProjectListFallbackError(error) {
24553
25064
  function canTryNextBinary(error) {
24554
25065
  return !(error instanceof RpcError) || !["CODEX_AUTH_REQUIRED", "CODEX_CLOSED"].includes(error.code);
24555
25066
  }
24556
- function isRecord11(value) {
25067
+ function isRecord12(value) {
24557
25068
  return typeof value === "object" && value !== null && !Array.isArray(value);
24558
25069
  }
24559
25070
  function array2(value) {
@@ -24566,7 +25077,119 @@ function isActiveWriterMessage(message) {
24566
25077
  return message.toLowerCase().includes("active writer");
24567
25078
  }
24568
25079
 
25080
+ // src/acp.ts
25081
+ import { spawn as spawn3 } from "node:child_process";
25082
+ var AcpGateway = class {
25083
+ constructor(adapters) {
25084
+ this.adapters = adapters;
25085
+ }
25086
+ get(p) {
25087
+ const list = this.adapters instanceof Object && "backend" in this.adapters ? [this.adapters] : [...this.adapters];
25088
+ const a = list.find((x) => !p.backend || x.backend === p.backend);
25089
+ if (!a) throw new Error("CAPABILITY_NOT_SUPPORTED");
25090
+ return a;
25091
+ }
25092
+ initialize(p) {
25093
+ return this.get(p).initialize(p);
25094
+ }
25095
+ sessionNew(p) {
25096
+ return this.get(p).sessionNew(p);
25097
+ }
25098
+ sessionLoad(p) {
25099
+ const a = this.get(p);
25100
+ if (!a.sessionLoad) throw new Error("CAPABILITY_NOT_SUPPORTED");
25101
+ return a.sessionLoad(p);
25102
+ }
25103
+ prompt(p, emit) {
25104
+ return this.get(p).prompt(p, emit);
25105
+ }
25106
+ respondPermission(p) {
25107
+ const a = this.get(p);
25108
+ if (!a.respondPermission) throw new Error("CAPABILITY_NOT_SUPPORTED");
25109
+ return a.respondPermission(p);
25110
+ }
25111
+ cancel(p) {
25112
+ const a = this.get(p);
25113
+ if (!a.cancel) throw new Error("CAPABILITY_NOT_SUPPORTED");
25114
+ return a.cancel(p);
25115
+ }
25116
+ setMode(p) {
25117
+ const a = this.get(p);
25118
+ if (!a.setMode) throw new Error("CAPABILITY_NOT_SUPPORTED");
25119
+ return a.setMode(p);
25120
+ }
25121
+ };
25122
+ var StdioAcpAdapter = class {
25123
+ constructor(config) {
25124
+ this.config = config;
25125
+ this.backend = config.id;
25126
+ }
25127
+ backend;
25128
+ child;
25129
+ nextId = 1;
25130
+ ensure() {
25131
+ if (!this.child) this.child = spawn3(this.config.command, this.config.args ?? [], { cwd: this.config.cwd, stdio: "pipe" });
25132
+ return this.child;
25133
+ }
25134
+ async initialize(params) {
25135
+ await this.call("initialize", params);
25136
+ return { protocolVersion: 1, capability: "agent.acp.v1", backend: this.backend, capabilities: ["session.new", "session.load", "session.prompt", "session.cancel"] };
25137
+ }
25138
+ async sessionNew(params) {
25139
+ const r = await this.call("session/new", params);
25140
+ if (!r.sessionId) throw new Error("INVALID_MESSAGE");
25141
+ return { sessionId: r.sessionId };
25142
+ }
25143
+ async sessionLoad(params) {
25144
+ const r = await this.call("session/load", params);
25145
+ if (!r.sessionId) throw new Error("INVALID_MESSAGE");
25146
+ return { sessionId: r.sessionId };
25147
+ }
25148
+ async prompt(params, emit) {
25149
+ await this.call("session/prompt", params, async (n) => emit({ sessionId: params.sessionId, update: n, seq: this.nextId++ }));
25150
+ }
25151
+ async cancel(p) {
25152
+ await this.call("session/cancel", p);
25153
+ }
25154
+ async setMode(p) {
25155
+ await this.call("session/set_mode", p);
25156
+ }
25157
+ async respondPermission(p) {
25158
+ await this.call("session/request_permission", p);
25159
+ }
25160
+ async close() {
25161
+ this.child?.kill();
25162
+ this.child = void 0;
25163
+ }
25164
+ call(method, params, onNotification) {
25165
+ const c = this.ensure();
25166
+ const id2 = this.nextId++;
25167
+ c.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id2, method, params }) + "\n");
25168
+ return new Promise((resolve3, reject) => {
25169
+ let buf = "";
25170
+ const onData = async (d) => {
25171
+ buf += d;
25172
+ const lines = buf.split("\n");
25173
+ buf = lines.pop() ?? "";
25174
+ for (const l of lines) {
25175
+ try {
25176
+ const m = JSON.parse(l);
25177
+ if (m.id === id2) {
25178
+ c.stdout.off("data", onData);
25179
+ m.error ? reject(new Error(m.error.message ?? "ACP error")) : resolve3(m.result);
25180
+ } else if (m.method && onNotification) await onNotification(m.params);
25181
+ } catch {
25182
+ }
25183
+ }
25184
+ };
25185
+ c.stdout.on("data", onData);
25186
+ c.once("error", reject);
25187
+ });
25188
+ }
25189
+ };
25190
+
24569
25191
  // src/service.ts
25192
+ import { execFileSync as execFileSync2 } from "node:child_process";
24570
25193
  var HostPluginRuntime = class {
24571
25194
  constructor(config, identities, apiProxy, logger, localGateway, fileViewerHost) {
24572
25195
  this.config = config;
@@ -24588,7 +25211,8 @@ var HostPluginRuntime = class {
24588
25211
  const harnessRemote = this.localGateway?.supportsCarrier === true ? new HarnessRemoteBridge(
24589
25212
  this.localGateway,
24590
25213
  (event, data) => send(createEvent(event, data)),
24591
- this.logger
25214
+ this.logger,
25215
+ this.harnessVersion
24592
25216
  ) : void 0;
24593
25217
  const fileViewer = new RemoteFileViewerBridge(
24594
25218
  () => this.fileViewerHost?.(),
@@ -24598,6 +25222,8 @@ var HostPluginRuntime = class {
24598
25222
  context,
24599
25223
  (event, data) => send(createEvent(event, data))
24600
25224
  );
25225
+ const adapters = (config.acp?.backends ?? []).filter((item) => item.enabled && this.acpAvailable(item.command)).map((item) => new StdioAcpAdapter(item));
25226
+ const acp = config.acp?.enabled && adapters.length > 0 ? new AcpGateway(adapters) : void 0;
24601
25227
  return new RpcRouter(
24602
25228
  harnessApi,
24603
25229
  void 0,
@@ -24605,7 +25231,8 @@ var HostPluginRuntime = class {
24605
25231
  fileViewer,
24606
25232
  harnessRemote,
24607
25233
  () => this.hostCapabilities(),
24608
- codex
25234
+ codex,
25235
+ acp
24609
25236
  );
24610
25237
  }, this.logger);
24611
25238
  if (config.serverUrl !== void 0) {
@@ -24658,9 +25285,21 @@ var HostPluginRuntime = class {
24658
25285
  ...error === void 0 ? {} : { error },
24659
25286
  ...authorization?.account === void 0 ? {} : { account: authorization.account },
24660
25287
  authorized: authorization !== void 0,
24661
- accountRequired: error === "ACCOUNT_AUTH_REQUIRED" || error === "AUTH_INVALID" || error === "TOKEN_EXPIRED"
25288
+ accountRequired: error === "ACCOUNT_AUTH_REQUIRED" || error === "AUTH_INVALID" || error === "TOKEN_EXPIRED",
25289
+ connectedClients: this.listConnectedClients()
24662
25290
  };
24663
25291
  }
25292
+ listConnectedClients() {
25293
+ return this.connections.connectedPeers().map((peer) => {
25294
+ const trusted = this.identities.trustedPeer(peer.deviceId);
25295
+ return {
25296
+ deviceId: peer.deviceId,
25297
+ name: trusted?.name.trim() ?? "",
25298
+ ...trusted === void 0 ? {} : { platform: trusted.platform },
25299
+ ...peer.mode === void 0 ? {} : { mode: peer.mode }
25300
+ };
25301
+ });
25302
+ }
24664
25303
  localHarnessVersion() {
24665
25304
  return this.harnessVersion;
24666
25305
  }
@@ -24838,13 +25477,25 @@ var HostPluginRuntime = class {
24838
25477
  harnessSessionGeneration(this.harnessVersion) === "v3" ? "harness.remote.v3" : "harness.remote.v1",
24839
25478
  "harness.remote.transfer.v1"
24840
25479
  );
24841
- } else if (this.apiProxy !== void 0) {
25480
+ }
25481
+ if (this.apiProxy !== void 0) {
24842
25482
  capabilities.push("harness.api.v1", "harness.api.transfer.v1");
24843
25483
  }
24844
25484
  if (this.fileViewerHost?.() !== void 0) capabilities.push("fileviewer.read.v1");
24845
25485
  if (this.codex.isAvailable()) capabilities.push("codex.appserver.v1", "codex.appserver.transfer.v1");
25486
+ if (this.config.acp?.enabled) {
25487
+ for (const item of this.config.acp.backends) if (item.enabled && this.acpAvailable(item.command)) capabilities.push(`agent.acp.v1.${item.id}`);
25488
+ }
24846
25489
  return capabilities;
24847
25490
  }
25491
+ acpAvailable(command) {
25492
+ try {
25493
+ execFileSync2(process.platform === "win32" ? "where" : "which", [command], { stdio: "ignore" });
25494
+ return true;
25495
+ } catch {
25496
+ return false;
25497
+ }
25498
+ }
24848
25499
  requireLocalCodexPeer() {
24849
25500
  if (!this.codex.isAvailable()) {
24850
25501
  throw new RpcError("CODEX_UNAVAILABLE", "Local CodeX is disabled or unavailable on this Host.");
@@ -25293,7 +25944,7 @@ function remoteStatusLines(target) {
25293
25944
  `Device: ${status2.deviceId ?? "not initialized"}`,
25294
25945
  `Authorization: ${status2.authorized ? status2.account === void 0 ? "logged in" : `logged in (${status2.account})` : "logged out"}`,
25295
25946
  `Server connection: ${connection}`,
25296
- `Harness Remote API: ${capabilities.has("harness.api.v1") ? "available (ApiProxy)" : capabilities.has("harness.remote.v1") ? "available (Typert Remote)" : "unavailable"}`,
25947
+ `Harness Remote API: ${capabilities.has("harness.api.v1") ? "available (ApiProxy)" : capabilities.has("harness.remote.v3") ? "available (Typert Remote Session V3)" : capabilities.has("harness.remote.v1") ? "available (Typert Remote)" : "unavailable"}`,
25297
25948
  `Remote clients: ${diagnostics.activeConnections}`,
25298
25949
  `Codex Remote: ${codex.enabled ? codex.state : "disabled"}`,
25299
25950
  "",
@@ -25566,6 +26217,7 @@ function apply(ctx, input2 = {}) {
25566
26217
  const activateWhenCarrierReady = (runtimeContext) => {
25567
26218
  const gateway = runtimeContext.get("typertGateway");
25568
26219
  const connection = runtimeContext.get("connection");
26220
+ const webServer = runtimeContext.get("webServer");
25569
26221
  let disposePendingControl;
25570
26222
  const installPendingControl = () => {
25571
26223
  if (connection === void 0 || disposePendingControl !== void 0) return;
@@ -25577,7 +26229,7 @@ function apply(ctx, input2 = {}) {
25577
26229
  void 0,
25578
26230
  void 0
25579
26231
  );
25580
- return control.register(connection);
26232
+ return control.register(connection, webServer);
25581
26233
  }, "dsh-remote pending control");
25582
26234
  };
25583
26235
  const startActiveRuntime = async (activeContext) => {
@@ -25611,13 +26263,14 @@ async function activate(ctx, input2, tuiBinding) {
25611
26263
  reportSettingsMigration(ctx, await migration);
25612
26264
  }
25613
26265
  const connection = ctx.get("connection");
26266
+ const webServer = ctx.get("webServer");
25614
26267
  const resolvedConfig = resolveConfig(settingsScope?.get() ?? input2);
25615
26268
  const config = connection === void 0 && resolvedConfig.serverUrl === void 0 ? { ...resolvedConfig, serverUrl: DEFAULT_REMOTE_SERVER_URL } : resolvedConfig;
25616
26269
  const defaultIdentityDirectory = new IdentityStore().directory;
25617
26270
  if (!config.enabled) {
25618
26271
  if (connection !== void 0) {
25619
26272
  const controlRuntime2 = new PluginControlRuntime(config, defaultIdentityDirectory, settingsScope, void 0, void 0);
25620
- ctx.effect(() => controlRuntime2.register(connection), "dsh-remote disabled control");
26273
+ ctx.effect(() => controlRuntime2.register(connection, webServer), "dsh-remote disabled control");
25621
26274
  }
25622
26275
  return;
25623
26276
  }
@@ -25676,7 +26329,7 @@ async function activate(ctx, input2, tuiBinding) {
25676
26329
  if (tuiBinding !== void 0) tuiBinding.target = tuiTarget;
25677
26330
  await disableLegacyLoaderEntries(ctx, logger);
25678
26331
  await ctx.effect(async () => {
25679
- const disposeControl = controlRuntime?.register(connection);
26332
+ const disposeControl = controlRuntime?.register(connection, webServer);
25680
26333
  try {
25681
26334
  await runtime.start();
25682
26335
  if (clientRuntime !== void 0) {
@@ -25759,6 +26412,7 @@ function isPlainRecord2(value) {
25759
26412
  return typeof value === "object" && value !== null && !Array.isArray(value);
25760
26413
  }
25761
26414
  export {
26415
+ AcpGateway,
25762
26416
  ApiProxySwitch,
25763
26417
  CODEX_APP_ALLOWLIST,
25764
26418
  ClientModeError,