@codexhost/cli-darwin-x64 0.6.1 → 0.7.0

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.
@@ -2278,7 +2278,7 @@ var require_websocket = __commonJS({
2278
2278
  var http = __require("http");
2279
2279
  var net5 = __require("net");
2280
2280
  var tls = __require("tls");
2281
- var { randomBytes: randomBytes3, createHash: createHash8 } = __require("crypto");
2281
+ var { randomBytes: randomBytes3, createHash: createHash9 } = __require("crypto");
2282
2282
  var { Duplex: Duplex2, Readable } = __require("stream");
2283
2283
  var { URL: URL2 } = __require("url");
2284
2284
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2946,7 +2946,7 @@ var require_websocket = __commonJS({
2946
2946
  abortHandshake(websocket, socket, "Invalid Upgrade header");
2947
2947
  return;
2948
2948
  }
2949
- const digest2 = createHash8("sha1").update(key + GUID).digest("base64");
2949
+ const digest2 = createHash9("sha1").update(key + GUID).digest("base64");
2950
2950
  if (res.headers["sec-websocket-accept"] !== digest2) {
2951
2951
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
2952
2952
  return;
@@ -3315,7 +3315,7 @@ var require_websocket_server = __commonJS({
3315
3315
  var EventEmitter = __require("events");
3316
3316
  var http = __require("http");
3317
3317
  var { Duplex: Duplex2 } = __require("stream");
3318
- var { createHash: createHash8 } = __require("crypto");
3318
+ var { createHash: createHash9 } = __require("crypto");
3319
3319
  var extension2 = require_extension();
3320
3320
  var PerMessageDeflate2 = require_permessage_deflate();
3321
3321
  var subprotocol2 = require_subprotocol();
@@ -3622,7 +3622,7 @@ var require_websocket_server = __commonJS({
3622
3622
  );
3623
3623
  }
3624
3624
  if (this._state > RUNNING) return abortHandshake(socket, 503);
3625
- const digest2 = createHash8("sha1").update(key + GUID).digest("base64");
3625
+ const digest2 = createHash9("sha1").update(key + GUID).digest("base64");
3626
3626
  const headers = [
3627
3627
  "HTTP/1.1 101 Switching Protocols",
3628
3628
  "Upgrade: websocket",
@@ -3709,6 +3709,92 @@ var require_websocket_server = __commonJS({
3709
3709
  }
3710
3710
  });
3711
3711
 
3712
+ // packages/host-runtime/src/delegation-cli-help.ts
3713
+ var COMMAND_HELP = {
3714
+ "harness list": `codexhost harness list [--format json|compact]
3715
+ List Harnesses available in the active Runtime.`,
3716
+ "harness inspect": `codexhost harness inspect <harness> [--cwd <path>] [--refresh true|false] [--format json|compact]
3717
+ Read available Models, native defaults, Thinking options, and configuration capabilities.
3718
+ Use the returned Model and Thinking IDs for explicit selections.`,
3719
+ "delegate start": `codexhost delegate start --harness <id> --task <text> [--cwd <path>] [--model <opaque-ref>] [--thinking <option-id>] [--parent-thread <thread>] [--request-id <id>] [--format json|compact]
3720
+ Create an independent child Thread, submit the task, and return immediately.
3721
+ Omit --model and --thinking to use the Harness native defaults.
3722
+ --cwd overrides the child workspace. Otherwise use the resolved parent Thread workspace, then the Host Runtime process cwd.
3723
+ --parent-thread overrides caller inference. PARENT_THREAD_AMBIGUOUS requires an explicit parent.
3724
+ Reuse --request-id for an idempotent retry. Identical recent parent/target/task/configuration requests are also deduplicated briefly.
3725
+ The response confirms cwd and parent. Use its Thread reference with thread read, wait, send, or cancel.`,
3726
+ "thread send": `codexhost thread send <thread> --message <text> [--format json|compact]
3727
+ Start a new Turn in an idle writable Thread and return immediately.
3728
+ THREAD_BUSY means the current Turn is still active: wait or cancel before sending. Messages are not queued.`,
3729
+ "thread cancel": `codexhost thread cancel <thread> [--format json|compact]
3730
+ Request cancellation of the active Turn while preserving the Thread and history.
3731
+ cancelled=true (compact: cancelRequested=true) means the cancellation request was accepted. Read or wait to confirm the terminal state. An idle Thread returns false.`,
3732
+ "thread read": `codexhost thread read <thread> [--view result|messages] [--cursor <cursor>] [--limit <n>] [--format json|compact]
3733
+ Read immediately without starting a Turn. The default result view reports the latest Turn's status and result.
3734
+ The messages view pages visible user/Agent messages, oldest first. Default limit 25, maximum 100; --cursor and --limit require --view messages.
3735
+ hasMore describes remaining messages now. Save nextCursor for later incremental reads even when hasMore=false.
3736
+ Compact messages output contains only the message page and status; compact result output includes the latest nonempty progress while running.
3737
+ Full JSON retains the complete snapshot. Tool calls/output, file activity, and reasoning are not included in either format.`,
3738
+ "thread wait": `codexhost thread wait <thread> [--timeout-ms <n>] [--view result|messages] [--cursor <cursor>] [--limit <n>] [--format json|compact]
3739
+ Wait until the Thread is terminal or the timeout expires (default 30000 ms), then return the same snapshot as thread read plus timedOut.
3740
+ timedOut=true is a running checkpoint: the child keeps running. The response already includes the result when available; another read is unnecessary unless more information is needed.
3741
+ Message pagination uses --view messages, default limit 25, maximum 100. hasMore is for current pages; nextCursor also supports future incremental reads.`,
3742
+ "thread list": `codexhost thread list [--cwd <path>] [--parent <thread>] [--limit <n>] [--cursor <cursor>] [--sort created-asc|created-desc|updated-asc|updated-desc|recency-asc|recency-desc] [--format json|compact]
3743
+ Find existing Threads by workspace, or use --parent to list a Thread's delegated children.
3744
+ Workspace listing defaults to the caller process cwd. Default limit 25 (maximum 100), sorted created-desc.
3745
+ --parent uses Delegation relationships. A null nextCursor ends the list.
3746
+ Compact output keeps task links, Harness, status, title, and workspace.`
3747
+ };
3748
+ var COMMON_HELP = `Thread references accept a bare ID or codex://threads/<id>.
3749
+ --format json is the compatible full JSON output (default); --format compact returns concise JSON using task links instead of internal IDs.
3750
+ Success is written to stdout; errors {"error":{"code":"...","message":"...","details":{...}}} go to stderr with exit code 1. Exit code 0 means the command succeeded, not that the delegated task succeeded.
3751
+ read/wait are non-consuming. Native Codex callers need local Runtime access; RUNTIME_UNREACHABLE requires the Host-provided environment and a sandbox that permits that connection.`;
3752
+ var DELEGATION_HELP = `usage:
3753
+ codexhost harness list
3754
+ codexhost harness inspect <harness>
3755
+ codexhost delegate start --harness <id> --task <text>
3756
+ codexhost thread send <thread> --message <text>
3757
+ codexhost thread cancel <thread>
3758
+ codexhost thread read <thread>
3759
+ codexhost thread wait <thread>
3760
+ codexhost thread list
3761
+
3762
+ Use <command> --help for its options. Use harness list to discover targets.
3763
+ ${COMMON_HELP}
3764
+ `;
3765
+ function hasHelpOption(arguments_2) {
3766
+ for (let index = 0; index < arguments_2.length; index += 1) {
3767
+ const argument = arguments_2[index];
3768
+ if (argument === "--help" || argument === "-h") return true;
3769
+ if (argument?.startsWith("--")) index += 1;
3770
+ }
3771
+ return false;
3772
+ }
3773
+ function delegationCliHelp(arguments_2) {
3774
+ const [group, command, ...rest] = arguments_2;
3775
+ if (!group || group === "--help" || group === "-h") return DELEGATION_HELP;
3776
+ if (!command || command === "--help" || command === "-h" || command === "help") {
3777
+ if (group === "delegate") return DELEGATION_HELP;
3778
+ if (group === "harness" || group === "thread") {
3779
+ const usages = Object.entries(COMMAND_HELP).filter(([name]) => name.startsWith(`${group} `)).map(([, help]) => help.split("\n")[0]);
3780
+ return `${usages.join("\n")}
3781
+
3782
+ ${COMMON_HELP}
3783
+ `;
3784
+ }
3785
+ }
3786
+ if (hasHelpOption(rest)) {
3787
+ const name = `${group} ${command}`;
3788
+ if (Object.hasOwn(COMMAND_HELP, name)) {
3789
+ return `${COMMAND_HELP[name]}
3790
+
3791
+ ${COMMON_HELP}
3792
+ `;
3793
+ }
3794
+ }
3795
+ return void 0;
3796
+ }
3797
+
3712
3798
  // packages/host-runtime/src/delegation-types.ts
3713
3799
  var DELEGATION_RUNTIME_ENDPOINT_ENV = "CODEXHOST_RUNTIME_ENDPOINT";
3714
3800
  var DELEGATION_RUNTIME_TOKEN_ENV = "CODEXHOST_RUNTIME_TOKEN";
@@ -3725,6 +3811,114 @@ var DelegationControlError = class extends Error {
3725
3811
  details;
3726
3812
  };
3727
3813
 
3814
+ // packages/host-runtime/src/delegation-cli-output.ts
3815
+ function threadLink(threadId3) {
3816
+ return `codex://threads/${threadId3}`;
3817
+ }
3818
+ function inspectOutput({ harnessId, inspection }) {
3819
+ if (inspection.status !== "ready") return { harnessId, ...inspection };
3820
+ const { catalog } = inspection;
3821
+ return {
3822
+ harnessId,
3823
+ status: inspection.status,
3824
+ models: catalog.models.map(({ ref, label, supportedThinkingOptionIds }) => ({
3825
+ id: ref.id,
3826
+ label,
3827
+ ...supportedThinkingOptionIds ? { thinking: supportedThinkingOptionIds } : {}
3828
+ })),
3829
+ ...catalog.defaultModel ? { defaultModel: catalog.defaultModel.id } : {},
3830
+ thinkingOptions: catalog.thinkingOptions,
3831
+ ...catalog.defaultThinkingOptionId ? { defaultThinkingOptionId: catalog.defaultThinkingOptionId } : {},
3832
+ capabilities: inspection.capabilities
3833
+ };
3834
+ }
3835
+ function snapshotOutput(snapshot, view) {
3836
+ const common = {
3837
+ thread: threadLink(snapshot.threadId),
3838
+ harnessId: snapshot.harnessId,
3839
+ status: snapshot.status,
3840
+ ...snapshot.timedOut !== void 0 ? { timedOut: snapshot.timedOut } : {}
3841
+ };
3842
+ if (view === "messages") {
3843
+ if (typeof snapshot.hasMore !== "boolean") {
3844
+ throw new DelegationControlError(
3845
+ "INTERNAL_ERROR",
3846
+ "Compact message pagination requires an updated Host Runtime; use --format json with this Runtime."
3847
+ );
3848
+ }
3849
+ return {
3850
+ ...common,
3851
+ messages: (snapshot.messages ?? []).map(({ role, phase, text }) => ({
3852
+ role,
3853
+ ...phase ? { phase } : {},
3854
+ text
3855
+ })),
3856
+ hasMore: snapshot.hasMore,
3857
+ nextCursor: snapshot.nextCursor,
3858
+ ...snapshot.result.message ? { error: snapshot.result.message } : {}
3859
+ };
3860
+ }
3861
+ const progress = snapshot.progress.filter(({ text }) => text.trim()).at(-1)?.text;
3862
+ return {
3863
+ ...common,
3864
+ result: snapshot.result,
3865
+ ...snapshot.status === "running" && progress ? { progress } : {}
3866
+ };
3867
+ }
3868
+ function compactDelegationOutput(command, body, view = "result") {
3869
+ switch (command) {
3870
+ case "harness list":
3871
+ return body;
3872
+ case "harness inspect":
3873
+ return inspectOutput(body);
3874
+ case "delegate start": {
3875
+ const result = body;
3876
+ const effective = result.configuration?.effective;
3877
+ return {
3878
+ thread: result.deepLink,
3879
+ harnessId: result.harnessId,
3880
+ status: result.status,
3881
+ ...result.cwd ? { cwd: result.cwd } : {},
3882
+ ...result.parentThreadId ? { parent: threadLink(result.parentThreadId) } : {},
3883
+ ...effective?.resolvedModelLabel ? { model: effective.resolvedModelLabel } : {},
3884
+ ...effective?.effectiveThinkingOptionId ? { thinking: effective.effectiveThinkingOptionId } : {}
3885
+ };
3886
+ }
3887
+ case "thread send": {
3888
+ const result = body;
3889
+ return {
3890
+ thread: threadLink(result.threadId),
3891
+ harnessId: result.harnessId,
3892
+ status: result.status
3893
+ };
3894
+ }
3895
+ case "thread cancel": {
3896
+ const result = body;
3897
+ return {
3898
+ thread: threadLink(result.threadId),
3899
+ harnessId: result.harnessId,
3900
+ cancelRequested: result.cancelled
3901
+ };
3902
+ }
3903
+ case "thread read":
3904
+ case "thread wait":
3905
+ return snapshotOutput(body, view);
3906
+ case "thread list": {
3907
+ const result = body;
3908
+ return {
3909
+ threads: result.threads.map(({ deepLink, harnessId, status, title, cwd }) => ({
3910
+ thread: deepLink,
3911
+ harnessId,
3912
+ status,
3913
+ ...title ? { title } : {},
3914
+ ...cwd ? { cwd } : {}
3915
+ })),
3916
+ nextCursor: result.nextCursor
3917
+ };
3918
+ }
3919
+ }
3920
+ }
3921
+
3728
3922
  // packages/host-runtime/src/delegation-cli.ts
3729
3923
  var DEFAULT_WAIT_TIMEOUT_MS = 3e4;
3730
3924
  var DEFAULT_LIMIT = 25;
@@ -3771,44 +3965,12 @@ function value(parsed, name) {
3771
3965
  return parsed.options.get(name);
3772
3966
  }
3773
3967
  function rejectUnknown(parsed, allowed) {
3774
- const known = new Set(allowed);
3968
+ const known = /* @__PURE__ */ new Set([...allowed, "--format"]);
3775
3969
  for (const name of parsed.options.keys()) {
3776
3970
  if (!known.has(name))
3777
3971
  throw new DelegationControlError("INVALID_ARGUMENT", `Unknown option '${name}'`);
3778
3972
  }
3779
3973
  }
3780
- var DELEGATION_HELP = `usage:
3781
- codexhost harness inspect <harness> [--cwd <path>] [--refresh true|false]
3782
- codexhost delegate start --harness <id> --task <text> [--model <opaque-ref>] [--thinking <option-id>] [--parent-thread <thread>] [--request-id <id>]
3783
- codexhost thread send <thread> --message <text>
3784
- codexhost thread cancel <thread>
3785
- codexhost thread read <thread> [--view result|messages] [--cursor <cursor>] [--limit <n>]
3786
- codexhost thread wait <thread> [--timeout-ms <n>] [--view result|messages] [--cursor <cursor>] [--limit <n>]
3787
- codexhost thread list [--cwd <path>] [--parent <thread>] [--limit <n>] [--cursor <cursor>] [--sort created-asc|created-desc|updated-asc|updated-desc|recency-asc|recency-desc]
3788
-
3789
- Thread identifiers accept a bare ID or codex://threads/<id>. Output is JSON by default.
3790
- harness inspect returns the target Model catalog, default Model, Thinking options, and configuration capabilities without creating a Thread. Use opaque IDs exactly as returned.
3791
- delegate start requires --harness and --task, creates and submits the child Thread, then returns immediately. --model and --thinking select values returned by harness inspect. Omit either option to preserve that target's current default behavior. --parent-thread overrides caller inference. Reuse --request-id for idempotent retries; without it, identical recent parent/target/task/configuration requests are deduplicated briefly.
3792
- Successful start fields: delegationId, threadId, turnId, harnessId, deepLink, status, next.read, next.wait.
3793
- thread send starts a new Turn in an idle writable Thread and returns immediately. It fails with THREAD_BUSY instead of queueing or starting a concurrent Turn.
3794
- thread cancel requests cancellation of the current Turn while preserving the Thread. An idle Thread returns cancelled=false.
3795
- thread read is non-blocking. Its default result view returns threadId, harnessId, status, latest turn, visible progress, result.availability/result.text, and nextCursor.
3796
- thread read --view messages additionally returns paginated user/Agent-visible messages. The default page is 25 and --limit is capped at 100; --cursor and --limit require the messages view. Tool calls, tool output, file activity, reasoning summaries, hidden reasoning, and private Harness transcripts are never returned.
3797
- thread wait defaults to 30000 ms and waits only until the Thread reaches a terminal state or the bounded timeout expires. A timeout is a successful running checkpoint with timedOut=true; the child keeps running.
3798
- thread list defaults to the caller cwd, limit 25, created-desc; limit is capped at 100. --parent uses Delegation lineage, not Codex Subagent relationships.
3799
- read and wait are non-consuming: they do not start a Turn, send input, wake an Agent, mark messages read, or inject a result into the parent Session.
3800
- Native Codex as caller requires a session sandbox that permits local Runtime connections; otherwise RUNTIME_UNREACHABLE is returned. Native Codex as a target uses brokered official requests and is unaffected.
3801
-
3802
- Errors are JSON: {"error":{"code":"...","message":"...","details":{...}}}.
3803
- INVALID_ARGUMENT: fix the named argument or incompatible option combination.
3804
- HARNESS_NOT_FOUND: choose a Harness ID listed in error.details.validHarnessIds.
3805
- THREAD_NOT_FOUND: verify the bare ID or codex:// deep link.
3806
- THREAD_BUSY: wait for or cancel the active Turn before sending another message.
3807
- PARENT_THREAD_AMBIGUOUS: pass --parent-thread explicitly.
3808
- RUNTIME_UNREACHABLE: run inside the Host-provided environment and, for native Codex, allow local Runtime connections; codexhost never falls back to PATH or another Runtime.
3809
- DELEGATION_FAILED: the target Session or initial task delivery failed and no successful child was published.
3810
- INTERNAL_ERROR: retry after checking the Host Runtime diagnostics.
3811
- `;
3812
3974
  async function requestRuntime(input) {
3813
3975
  const endpoint = input.environment[DELEGATION_RUNTIME_ENDPOINT_ENV];
3814
3976
  const token = input.environment[DELEGATION_RUNTIME_TOKEN_ENV];
@@ -3854,12 +4016,37 @@ async function runDelegationCli(input) {
3854
4016
  const environment = input.environment ?? process.env;
3855
4017
  try {
3856
4018
  const [group, command, ...rest] = input.arguments;
3857
- if (group === "delegate" && (!command || command === "--help" || command === "help") || group === "--help" || group === "-h") {
3858
- output.write(DELEGATION_HELP);
4019
+ const help = delegationCliHelp(input.arguments);
4020
+ if (help !== void 0) {
4021
+ output.write(help);
4022
+ return 0;
4023
+ }
4024
+ const parsed = options(rest);
4025
+ const format = value(parsed, "--format") ?? "json";
4026
+ if (format !== "json" && format !== "compact") {
4027
+ throw new DelegationControlError("INVALID_ARGUMENT", "--format must be json or compact");
4028
+ }
4029
+ const writeResult = (name, body, view = "result") => writeJson(output, format === "json" ? body : compactDelegationOutput(name, body, view));
4030
+ if (group === "harness" && command === "list") {
4031
+ rejectUnknown(parsed, []);
4032
+ if (parsed.positionals.length > 0) {
4033
+ throw new DelegationControlError(
4034
+ "INVALID_ARGUMENT",
4035
+ "harness list accepts no positional arguments"
4036
+ );
4037
+ }
4038
+ writeResult(
4039
+ "harness list",
4040
+ await requestRuntime({
4041
+ environment,
4042
+ path: "/v1/harness/list",
4043
+ body: {},
4044
+ ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
4045
+ })
4046
+ );
3859
4047
  return 0;
3860
4048
  }
3861
4049
  if (group === "harness" && command === "inspect") {
3862
- const parsed = options(rest);
3863
4050
  rejectUnknown(parsed, ["--cwd", "--refresh"]);
3864
4051
  if (parsed.positionals.length !== 1) {
3865
4052
  throw new DelegationControlError(
@@ -3875,8 +4062,8 @@ async function runDelegationCli(input) {
3875
4062
  if (refresh !== void 0 && refresh !== "true" && refresh !== "false") {
3876
4063
  throw new DelegationControlError("INVALID_ARGUMENT", "--refresh must be true or false");
3877
4064
  }
3878
- writeJson(
3879
- output,
4065
+ writeResult(
4066
+ "harness inspect",
3880
4067
  await requestRuntime({
3881
4068
  environment,
3882
4069
  path: "/v1/harness/inspect",
@@ -3891,10 +4078,10 @@ async function runDelegationCli(input) {
3891
4078
  return 0;
3892
4079
  }
3893
4080
  if (group === "delegate" && command === "start") {
3894
- const parsed = options(rest);
3895
4081
  rejectUnknown(parsed, [
3896
4082
  "--harness",
3897
4083
  "--task",
4084
+ "--cwd",
3898
4085
  "--model",
3899
4086
  "--thinking",
3900
4087
  "--parent-thread",
@@ -3910,15 +4097,15 @@ async function runDelegationCli(input) {
3910
4097
  if (!harnessId || !task)
3911
4098
  throw new DelegationControlError("INVALID_ARGUMENT", "--harness and --task are required");
3912
4099
  const parentThread = value(parsed, "--parent-thread") ?? environment[DELEGATION_THREAD_ID_ENV];
3913
- writeJson(
3914
- output,
4100
+ writeResult(
4101
+ "delegate start",
3915
4102
  await requestRuntime({
3916
4103
  environment,
3917
4104
  path: "/v1/delegate/start",
3918
4105
  body: {
3919
4106
  harnessId,
3920
4107
  task,
3921
- cwd: process.cwd(),
4108
+ ...value(parsed, "--cwd") ? { cwd: value(parsed, "--cwd") } : {},
3922
4109
  ...value(parsed, "--model") ? { model: { id: value(parsed, "--model") } } : {},
3923
4110
  ...value(parsed, "--thinking") ? { thinkingOptionId: value(parsed, "--thinking") } : {},
3924
4111
  ...parentThread ? { parentThreadId: normalizeThreadId(parentThread) } : {},
@@ -3930,7 +4117,6 @@ async function runDelegationCli(input) {
3930
4117
  return 0;
3931
4118
  }
3932
4119
  if (group === "thread" && command === "send") {
3933
- const parsed = options(rest);
3934
4120
  rejectUnknown(parsed, ["--message"]);
3935
4121
  if (parsed.positionals.length !== 1) {
3936
4122
  throw new DelegationControlError(
@@ -3946,8 +4132,8 @@ async function runDelegationCli(input) {
3946
4132
  "Thread identifier and --message are required"
3947
4133
  );
3948
4134
  }
3949
- writeJson(
3950
- output,
4135
+ writeResult(
4136
+ "thread send",
3951
4137
  await requestRuntime({
3952
4138
  environment,
3953
4139
  path: "/v1/thread/send",
@@ -3958,7 +4144,6 @@ async function runDelegationCli(input) {
3958
4144
  return 0;
3959
4145
  }
3960
4146
  if (group === "thread" && command === "cancel") {
3961
- const parsed = options(rest);
3962
4147
  rejectUnknown(parsed, []);
3963
4148
  if (parsed.positionals.length !== 1) {
3964
4149
  throw new DelegationControlError(
@@ -3970,8 +4155,8 @@ async function runDelegationCli(input) {
3970
4155
  if (!threadId3) {
3971
4156
  throw new DelegationControlError("INVALID_ARGUMENT", "Thread identifier is required");
3972
4157
  }
3973
- writeJson(
3974
- output,
4158
+ writeResult(
4159
+ "thread cancel",
3975
4160
  await requestRuntime({
3976
4161
  environment,
3977
4162
  path: "/v1/thread/cancel",
@@ -3982,7 +4167,6 @@ async function runDelegationCli(input) {
3982
4167
  return 0;
3983
4168
  }
3984
4169
  if (group === "thread" && (command === "read" || command === "wait")) {
3985
- const parsed = options(rest);
3986
4170
  rejectUnknown(parsed, ["--view", "--cursor", "--limit", "--timeout-ms"]);
3987
4171
  if (parsed.positionals.length !== 1)
3988
4172
  throw new DelegationControlError(
@@ -4014,19 +4198,19 @@ async function runDelegationCli(input) {
4014
4198
  timeoutMs: value(parsed, "--timeout-ms") ? positiveInteger(value(parsed, "--timeout-ms"), "--timeout-ms") : DEFAULT_WAIT_TIMEOUT_MS
4015
4199
  } : {}
4016
4200
  };
4017
- writeJson(
4018
- output,
4201
+ writeResult(
4202
+ command === "read" ? "thread read" : "thread wait",
4019
4203
  await requestRuntime({
4020
4204
  environment,
4021
4205
  path: command === "read" ? "/v1/thread/read" : "/v1/thread/wait",
4022
4206
  body,
4023
4207
  ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
4024
- })
4208
+ }),
4209
+ view
4025
4210
  );
4026
4211
  return 0;
4027
4212
  }
4028
4213
  if (group === "thread" && command === "list") {
4029
- const parsed = options(rest);
4030
4214
  rejectUnknown(parsed, ["--cwd", "--parent", "--limit", "--cursor", "--sort"]);
4031
4215
  if (parsed.positionals.length > 0)
4032
4216
  throw new DelegationControlError(
@@ -4044,8 +4228,8 @@ async function runDelegationCli(input) {
4044
4228
  ])).has(sort))
4045
4229
  throw new DelegationControlError("INVALID_ARGUMENT", "--sort is invalid");
4046
4230
  const parentThread = value(parsed, "--parent");
4047
- writeJson(
4048
- output,
4231
+ writeResult(
4232
+ "thread list",
4049
4233
  await requestRuntime({
4050
4234
  environment,
4051
4235
  path: "/v1/thread/list",
@@ -4083,7 +4267,7 @@ async function runDelegationCli(input) {
4083
4267
 
4084
4268
  // packages/host-runtime/src/run-host-runtime.ts
4085
4269
  import { randomBytes } from "node:crypto";
4086
- import path20 from "node:path";
4270
+ import path21 from "node:path";
4087
4271
  import { homedir } from "node:os";
4088
4272
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4089
4273
 
@@ -4429,9 +4613,85 @@ function selectInstallerReleaseArtifact(release, target) {
4429
4613
  });
4430
4614
  }
4431
4615
 
4616
+ // packages/update-manager/dist/github-cli-release.js
4617
+ import { execFile } from "node:child_process";
4618
+ import path2 from "node:path";
4619
+ var GITHUB_LATEST_RELEASE_ENDPOINT = "repos/BytePioneer-AI/codex-host/releases/latest";
4620
+ var MAX_RELEASE_RESPONSE_BYTES = 1024 * 1024;
4621
+ var GITHUB_CLI_TIMEOUT_MS = 5e3;
4622
+ function defaultExecutableCandidates(environment, platform) {
4623
+ if (environment.CODEXHOST_GH_COMMAND)
4624
+ return [environment.CODEXHOST_GH_COMMAND];
4625
+ const candidates = ["gh"];
4626
+ if (platform === "darwin") {
4627
+ candidates.push("/opt/homebrew/bin/gh", "/usr/local/bin/gh");
4628
+ } else if (platform === "win32") {
4629
+ const programFiles = environment.ProgramFiles ?? environment.PROGRAMFILES;
4630
+ const localAppData = environment.LOCALAPPDATA;
4631
+ if (programFiles)
4632
+ candidates.push(path2.win32.join(programFiles, "GitHub CLI", "gh.exe"));
4633
+ if (localAppData) {
4634
+ candidates.push(path2.win32.join(localAppData, "Programs", "GitHub CLI", "gh.exe"));
4635
+ }
4636
+ } else {
4637
+ candidates.push("/usr/local/bin/gh", "/usr/bin/gh", "/snap/bin/gh");
4638
+ }
4639
+ return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
4640
+ }
4641
+ function runGitHubCli(executable2, arguments_2, options2) {
4642
+ return new Promise((resolve, reject) => {
4643
+ execFile(executable2, [...arguments_2], {
4644
+ encoding: "utf8",
4645
+ env: options2.environment,
4646
+ maxBuffer: MAX_RELEASE_RESPONSE_BYTES,
4647
+ timeout: GITHUB_CLI_TIMEOUT_MS,
4648
+ windowsHide: true,
4649
+ ...options2.signal ? { signal: options2.signal } : {}
4650
+ }, (error51, stdout) => {
4651
+ if (error51)
4652
+ reject(error51);
4653
+ else
4654
+ resolve(stdout);
4655
+ });
4656
+ });
4657
+ }
4658
+ async function fetchLatestGitHubReleaseWithGitHubCli(options2 = {}) {
4659
+ const environment = options2.environment ?? process.env;
4660
+ const platform = options2.platform ?? process.platform;
4661
+ const candidates = options2.executableCandidates ?? defaultExecutableCandidates(environment, platform);
4662
+ const run = options2.run ?? runGitHubCli;
4663
+ const arguments_2 = [
4664
+ "api",
4665
+ "--hostname",
4666
+ "github.com",
4667
+ "--method",
4668
+ "GET",
4669
+ "--header",
4670
+ "Accept: application/vnd.github+json",
4671
+ "--header",
4672
+ "X-GitHub-Api-Version: 2022-11-28",
4673
+ GITHUB_LATEST_RELEASE_ENDPOINT
4674
+ ];
4675
+ for (const executable2 of candidates) {
4676
+ options2.signal?.throwIfAborted();
4677
+ try {
4678
+ return parseLatestGitHubRelease(JSON.parse(await run(executable2, arguments_2, {
4679
+ environment,
4680
+ ...options2.signal ? { signal: options2.signal } : {}
4681
+ })));
4682
+ } catch (error51) {
4683
+ options2.signal?.throwIfAborted();
4684
+ const code = error51?.code;
4685
+ if (code !== "ENOENT" && code !== "EACCES")
4686
+ return null;
4687
+ }
4688
+ }
4689
+ return null;
4690
+ }
4691
+
4432
4692
  // packages/update-manager/dist/operation-state.js
4433
4693
  import { lstat as lstat2, mkdir, open, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
4434
- import path2 from "node:path";
4694
+ import path3 from "node:path";
4435
4695
  var LOCK_FILE = "active-update-v1.lock";
4436
4696
  var STATUS_FILE = "status-v1.json";
4437
4697
  var TERMINAL_PHASES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
@@ -4446,12 +4706,12 @@ async function regularFile(filePath) {
4446
4706
  }
4447
4707
  }
4448
4708
  async function isUpdateOperationActive(stateDirectory) {
4449
- if (!path2.isAbsolute(stateDirectory))
4709
+ if (!path3.isAbsolute(stateDirectory))
4450
4710
  throw new Error("update state directory must be absolute");
4451
- return regularFile(path2.join(stateDirectory, LOCK_FILE));
4711
+ return regularFile(path3.join(stateDirectory, LOCK_FILE));
4452
4712
  }
4453
4713
  async function discoverLatestUpdateStatus(stateDirectory) {
4454
- if (!path2.isAbsolute(stateDirectory))
4714
+ if (!path3.isAbsolute(stateDirectory))
4455
4715
  throw new Error("update state directory must be absolute");
4456
4716
  let entries;
4457
4717
  try {
@@ -4465,7 +4725,7 @@ async function discoverLatestUpdateStatus(stateDirectory) {
4465
4725
  for (const entry of entries) {
4466
4726
  if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
4467
4727
  continue;
4468
- const statusPath = path2.join(stateDirectory, entry.name, STATUS_FILE);
4728
+ const statusPath = path3.join(stateDirectory, entry.name, STATUS_FILE);
4469
4729
  if (!await regularFile(statusPath))
4470
4730
  continue;
4471
4731
  try {
@@ -4493,8 +4753,8 @@ async function cleanupTerminalUpdateState(stateDirectory, options2 = {}) {
4493
4753
  for (const entry of entries) {
4494
4754
  if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
4495
4755
  continue;
4496
- const directory = path2.join(stateDirectory, entry.name);
4497
- const statusPath = path2.join(directory, STATUS_FILE);
4756
+ const directory = path3.join(stateDirectory, entry.name);
4757
+ const statusPath = path3.join(directory, STATUS_FILE);
4498
4758
  try {
4499
4759
  const status = parseUpdateStatus(JSON.parse(await readFile2(statusPath, "utf8")));
4500
4760
  if (TERMINAL_PHASES.has(status.phase) && now - status.updatedAt > retentionSeconds) {
@@ -4505,10 +4765,10 @@ async function cleanupTerminalUpdateState(stateDirectory, options2 = {}) {
4505
4765
  }
4506
4766
  }
4507
4767
  async function acquireUpdateOperationLock(stateDirectory) {
4508
- if (!path2.isAbsolute(stateDirectory))
4768
+ if (!path3.isAbsolute(stateDirectory))
4509
4769
  throw new Error("update state directory must be absolute");
4510
4770
  await mkdir(stateDirectory, { recursive: true, mode: 448 });
4511
- const lockPath = path2.join(stateDirectory, LOCK_FILE);
4771
+ const lockPath = path3.join(stateDirectory, LOCK_FILE);
4512
4772
  let handle;
4513
4773
  try {
4514
4774
  handle = await open(lockPath, "wx", 384);
@@ -4527,9 +4787,9 @@ async function acquireUpdateOperationLock(stateDirectory) {
4527
4787
  async setStatusPath(statusPath) {
4528
4788
  if (released)
4529
4789
  throw new Error("update operation lock is released");
4530
- if (!path2.isAbsolute(statusPath))
4790
+ if (!path3.isAbsolute(statusPath))
4531
4791
  throw new Error("update status path must be absolute");
4532
- await writeFile(lockPath, `${JSON.stringify({ ownerPid: process.pid, statusPath: path2.normalize(statusPath) })}
4792
+ await writeFile(lockPath, `${JSON.stringify({ ownerPid: process.pid, statusPath: path3.normalize(statusPath) })}
4533
4793
  `, { encoding: "utf8", mode: 384 });
4534
4794
  },
4535
4795
  async release() {
@@ -4551,7 +4811,7 @@ function processIsAlive(processId) {
4551
4811
  }
4552
4812
  }
4553
4813
  async function recoverUpdateOperationLock(stateDirectory) {
4554
- const lockPath = path2.join(stateDirectory, LOCK_FILE);
4814
+ const lockPath = path3.join(stateDirectory, LOCK_FILE);
4555
4815
  if (!await regularFile(lockPath))
4556
4816
  return;
4557
4817
  let ownerPid;
@@ -4563,7 +4823,7 @@ async function recoverUpdateOperationLock(stateDirectory) {
4563
4823
  } catch {
4564
4824
  return;
4565
4825
  }
4566
- if (typeof statusPath !== "string" || !path2.isAbsolute(statusPath))
4826
+ if (typeof statusPath !== "string" || !path3.isAbsolute(statusPath))
4567
4827
  return;
4568
4828
  try {
4569
4829
  const status = parseUpdateStatus(JSON.parse(await readFile2(statusPath, "utf8")));
@@ -4578,7 +4838,7 @@ async function recoverUpdateOperationLock(stateDirectory) {
4578
4838
  import { spawn } from "node:child_process";
4579
4839
  import { randomUUID } from "node:crypto";
4580
4840
  import { chmod, copyFile, lstat as lstat4, mkdir as mkdir2, readFile as readFile3, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
4581
- import path3 from "node:path";
4841
+ import path4 from "node:path";
4582
4842
 
4583
4843
  // packages/update-manager/dist/artifact.js
4584
4844
  import { createHash } from "node:crypto";
@@ -4699,9 +4959,9 @@ async function replaceStatusFile(temporaryPath, statusPath) {
4699
4959
  }
4700
4960
  }
4701
4961
  function requireAbsolutePath(value2, label) {
4702
- if (!path3.isAbsolute(value2))
4962
+ if (!path4.isAbsolute(value2))
4703
4963
  throw new Error(`${label} must be an absolute path`);
4704
- return path3.normalize(value2);
4964
+ return path4.normalize(value2);
4705
4965
  }
4706
4966
  async function requireRegularFile(value2, label) {
4707
4967
  const filePath = requireAbsolutePath(value2, label);
@@ -4744,7 +5004,7 @@ function createBackgroundUpdateManager(dependencies2 = {}) {
4744
5004
  const now = dependencies2.now ?? Date.now;
4745
5005
  const preparedRequests = /* @__PURE__ */ new Set();
4746
5006
  async function writeStatusSnapshot(statusPath, status) {
4747
- const temporaryPath = path3.join(path3.dirname(statusPath), `.update-status-${randomId()}.tmp`);
5007
+ const temporaryPath = path4.join(path4.dirname(statusPath), `.update-status-${randomId()}.tmp`);
4748
5008
  try {
4749
5009
  await writeFile2(temporaryPath, `${JSON.stringify(status)}
4750
5010
  `, {
@@ -4804,15 +5064,15 @@ function createBackgroundUpdateManager(dependencies2 = {}) {
4804
5064
  const updaterExecutable = await requireRegularFile(options2.updaterExecutable, "Updater executable");
4805
5065
  const stateDirectory = requireAbsolutePath(options2.stateDirectory, "update state directory");
4806
5066
  await mkdir2(stateDirectory, { recursive: true, mode: 448 });
4807
- const workDirectory = path3.join(stateDirectory, `update-${version2}-${randomId()}`);
5067
+ const workDirectory = path4.join(stateDirectory, `update-${version2}-${randomId()}`);
4808
5068
  await mkdir2(workDirectory, { recursive: false, mode: 448 });
4809
5069
  const executableSuffix = platform === "win32" ? ".exe" : "";
4810
- const helperPath = path3.join(workDirectory, `codexhost-updater${executableSuffix}`);
5070
+ const helperPath = path4.join(workDirectory, `codexhost-updater${executableSuffix}`);
4811
5071
  await copyFile(updaterExecutable, helperPath);
4812
5072
  if (platform !== "win32")
4813
5073
  await chmod(helperPath, 448);
4814
- const requestPath = path3.join(workDirectory, "request-v1.json");
4815
- const statusPath = path3.join(workDirectory, "status-v1.json");
5074
+ const requestPath = path4.join(workDirectory, "request-v1.json");
5075
+ const statusPath = path4.join(workDirectory, "status-v1.json");
4816
5076
  await writePrivateJson(statusPath, preparedStatus(version2, installation, now()));
4817
5077
  await options2.onPrepared?.({ version: version2, installation, statusPath });
4818
5078
  return {
@@ -4828,8 +5088,8 @@ function createBackgroundUpdateManager(dependencies2 = {}) {
4828
5088
  }
4829
5089
  async function prepareArtifact(common, installation, sourceValue, fileName) {
4830
5090
  const source = validateArtifact(sourceValue);
4831
- const temporaryPath = path3.join(common.workDirectory, `.${fileName}.download`);
4832
- const artifactPath = path3.join(common.workDirectory, fileName);
5091
+ const temporaryPath = path4.join(common.workDirectory, `.${fileName}.download`);
5092
+ const artifactPath = path4.join(common.workDirectory, fileName);
4833
5093
  const progress = progressReporter(common.statusPath, common.version, installation, source.size);
4834
5094
  try {
4835
5095
  const result = await download(source, temporaryPath, progress.update);
@@ -4903,7 +5163,7 @@ function createBackgroundUpdateManager(dependencies2 = {}) {
4903
5163
  throw new Error("macOS DMG updates require macOS");
4904
5164
  const common = await prepareCommon(options2, "macos-dmg");
4905
5165
  const appPath = requireAbsolutePath(options2.appPath, "macOS application path");
4906
- if (path3.extname(appPath) !== ".app") {
5166
+ if (path4.extname(appPath) !== ".app") {
4907
5167
  throw new Error("macOS application path must end in .app");
4908
5168
  }
4909
5169
  const artifact = await prepareArtifact(common, "macos-dmg", options2.artifact, "update.dmg");
@@ -5702,10 +5962,10 @@ function mergeDefs(...defs) {
5702
5962
  function cloneDef(schema) {
5703
5963
  return mergeDefs(schema._zod.def);
5704
5964
  }
5705
- function getElementAtPath(obj, path25) {
5706
- if (!path25)
5965
+ function getElementAtPath(obj, path26) {
5966
+ if (!path26)
5707
5967
  return obj;
5708
- return path25.reduce((acc, key) => acc?.[key], obj);
5968
+ return path26.reduce((acc, key) => acc?.[key], obj);
5709
5969
  }
5710
5970
  function promiseAllObject(promisesObj) {
5711
5971
  const keys = Object.keys(promisesObj);
@@ -6114,11 +6374,11 @@ function explicitlyAborted(x, startIndex = 0) {
6114
6374
  }
6115
6375
  return false;
6116
6376
  }
6117
- function prefixIssues(path25, issues) {
6377
+ function prefixIssues(path26, issues) {
6118
6378
  return issues.map((iss) => {
6119
6379
  var _a3;
6120
6380
  (_a3 = iss).path ?? (_a3.path = []);
6121
- iss.path.unshift(path25);
6381
+ iss.path.unshift(path26);
6122
6382
  return iss;
6123
6383
  });
6124
6384
  }
@@ -6265,16 +6525,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
6265
6525
  }
6266
6526
  function formatError(error51, mapper = (issue2) => issue2.message) {
6267
6527
  const fieldErrors = { _errors: [] };
6268
- const processError = (error52, path25 = []) => {
6528
+ const processError = (error52, path26 = []) => {
6269
6529
  for (const issue2 of error52.issues) {
6270
6530
  if (issue2.code === "invalid_union" && issue2.errors.length) {
6271
- issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
6531
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
6272
6532
  } else if (issue2.code === "invalid_key") {
6273
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
6533
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
6274
6534
  } else if (issue2.code === "invalid_element") {
6275
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
6535
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
6276
6536
  } else {
6277
- const fullpath = [...path25, ...issue2.path];
6537
+ const fullpath = [...path26, ...issue2.path];
6278
6538
  if (fullpath.length === 0) {
6279
6539
  fieldErrors._errors.push(mapper(issue2));
6280
6540
  } else {
@@ -6301,17 +6561,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
6301
6561
  }
6302
6562
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
6303
6563
  const result = { errors: [] };
6304
- const processError = (error52, path25 = []) => {
6564
+ const processError = (error52, path26 = []) => {
6305
6565
  var _a3, _b;
6306
6566
  for (const issue2 of error52.issues) {
6307
6567
  if (issue2.code === "invalid_union" && issue2.errors.length) {
6308
- issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
6568
+ issue2.errors.map((issues) => processError({ issues }, [...path26, ...issue2.path]));
6309
6569
  } else if (issue2.code === "invalid_key") {
6310
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
6570
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
6311
6571
  } else if (issue2.code === "invalid_element") {
6312
- processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
6572
+ processError({ issues: issue2.issues }, [...path26, ...issue2.path]);
6313
6573
  } else {
6314
- const fullpath = [...path25, ...issue2.path];
6574
+ const fullpath = [...path26, ...issue2.path];
6315
6575
  if (fullpath.length === 0) {
6316
6576
  result.errors.push(mapper(issue2));
6317
6577
  continue;
@@ -6343,8 +6603,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
6343
6603
  }
6344
6604
  function toDotPath(_path) {
6345
6605
  const segs = [];
6346
- const path25 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6347
- for (const seg of path25) {
6606
+ const path26 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6607
+ for (const seg of path26) {
6348
6608
  if (typeof seg === "number")
6349
6609
  segs.push(`[${seg}]`);
6350
6610
  else if (typeof seg === "symbol")
@@ -19036,13 +19296,13 @@ function resolveRef(ref, ctx) {
19036
19296
  if (!ref.startsWith("#")) {
19037
19297
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
19038
19298
  }
19039
- const path25 = ref.slice(1).split("/").filter(Boolean);
19040
- if (path25.length === 0) {
19299
+ const path26 = ref.slice(1).split("/").filter(Boolean);
19300
+ if (path26.length === 0) {
19041
19301
  return ctx.rootSchema;
19042
19302
  }
19043
19303
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
19044
- if (path25[0] === defsKey) {
19045
- const key = path25[1];
19304
+ if (path26[0] === defsKey) {
19305
+ const key = path26[1];
19046
19306
  if (!key || !ctx.defs[key]) {
19047
19307
  throw new Error(`Reference not found: ${ref}`);
19048
19308
  }
@@ -20465,7 +20725,7 @@ import { execFileSync } from "node:child_process";
20465
20725
  import { randomUUID as randomUUID2 } from "node:crypto";
20466
20726
  import { constants } from "node:fs";
20467
20727
  import { copyFile as copyFile2, mkdir as mkdir3, open as open3, readFile as readFile4, readdir as readdir2, rename as rename2, rm as rm3, stat } from "node:fs/promises";
20468
- import path4 from "node:path";
20728
+ import path5 from "node:path";
20469
20729
 
20470
20730
  // packages/mapping-store/dist/records.js
20471
20731
  var nonBlankTextSchema6 = external_exports.string().refine((value2) => value2.trim().length > 0, {
@@ -20716,12 +20976,12 @@ var MappingStore = class {
20716
20976
  #initialized = false;
20717
20977
  #lockHandle = null;
20718
20978
  constructor(options2) {
20719
- this.#directory = path4.resolve(options2.directory);
20720
- this.#threadsDirectory = path4.join(this.#directory, "threads");
20721
- this.#delegationsDirectory = path4.join(this.#directory, "delegations");
20722
- this.#backupsDirectory = path4.join(this.#directory, "backups");
20723
- this.#quarantineDirectory = path4.join(this.#directory, "quarantine");
20724
- this.#lockPath = path4.join(this.#directory, "store.lock");
20979
+ this.#directory = path5.resolve(options2.directory);
20980
+ this.#threadsDirectory = path5.join(this.#directory, "threads");
20981
+ this.#delegationsDirectory = path5.join(this.#directory, "delegations");
20982
+ this.#backupsDirectory = path5.join(this.#directory, "backups");
20983
+ this.#quarantineDirectory = path5.join(this.#directory, "quarantine");
20984
+ this.#lockPath = path5.join(this.#directory, "store.lock");
20725
20985
  this.#instanceId = options2.instanceId ?? randomUUID2();
20726
20986
  this.#now = options2.now ?? (() => /* @__PURE__ */ new Date());
20727
20987
  this.#beforeReplace = options2.beforeReplace;
@@ -20740,8 +21000,8 @@ var MappingStore = class {
20740
21000
  await this.#cleanupResidue();
20741
21001
  const names = (await readdir2(this.#threadsDirectory)).filter((name) => name.endsWith(".json"));
20742
21002
  for (const name of names) {
20743
- const primary = path4.join(this.#threadsDirectory, name);
20744
- const backup = path4.join(this.#backupsDirectory, name);
21003
+ const primary = path5.join(this.#threadsDirectory, name);
21004
+ const backup = path5.join(this.#backupsDirectory, name);
20745
21005
  let record3 = null;
20746
21006
  try {
20747
21007
  record3 = await this.#readRecord(primary, name);
@@ -20750,7 +21010,7 @@ var MappingStore = class {
20750
21010
  record3 = await this.#readRecord(backup, name);
20751
21011
  await this.#replaceFile(primary, record3, false);
20752
21012
  } catch (backupError) {
20753
- const quarantine = path4.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid`);
21013
+ const quarantine = path5.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid`);
20754
21014
  await rename2(primary, quarantine).catch(() => void 0);
20755
21015
  void primaryError;
20756
21016
  void backupError;
@@ -20766,14 +21026,14 @@ var MappingStore = class {
20766
21026
  }
20767
21027
  const delegationNames = (await readdir2(this.#delegationsDirectory)).filter((name) => name.endsWith(".json"));
20768
21028
  for (const name of delegationNames) {
20769
- const file2 = path4.join(this.#delegationsDirectory, name);
21029
+ const file2 = path5.join(this.#delegationsDirectory, name);
20770
21030
  try {
20771
21031
  const parsed = storedDelegationRecordV1Schema.parse(JSON.parse(await readFile4(file2, "utf8")));
20772
21032
  if (`${parsed.delegationId}.json` !== name)
20773
21033
  throw new Error("filename mismatch");
20774
21034
  this.#delegations.set(parsed.delegationId, parsed);
20775
21035
  } catch {
20776
- const quarantine = path4.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid-delegation`);
21036
+ const quarantine = path5.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid-delegation`);
20777
21037
  await rename2(file2, quarantine).catch(() => void 0);
20778
21038
  }
20779
21039
  }
@@ -20931,10 +21191,35 @@ var MappingStore = class {
20931
21191
  turnMappings: this.#mergeMappings(current.turnMappings, input.turnMappings ?? [])
20932
21192
  }));
20933
21193
  }
21194
+ // Keep native ref, Turn mappings and the indexed create request in one serialized
21195
+ // record mutation; separate setters could leave a child pointing at mixed Sessions.
21196
+ async rebindSubagentSession(input) {
21197
+ return this.#update(input.hostThreadId, (current) => {
21198
+ const parent = this.#records.get(input.parentHostThreadId);
21199
+ if (current.state !== "ready" || !current.nativeSessionRef || current.subagent?.parentHostThreadId !== input.parentHostThreadId || parent?.state !== "ready" || parent.harnessId !== current.harnessId || input.previousNativeSessionRef.harnessId !== current.harnessId || input.nativeSessionRef.harnessId !== current.harnessId || !sameJson(parent.nativeSessionRef, input.nativeSessionRef))
21200
+ throw new MappingStoreError("MAPPING_CONFLICT", "Subagent replacement must belong to its current parent Session");
21201
+ if (sameJson(current.nativeSessionRef, input.nativeSessionRef) && current.createRequestId === input.createRequestId)
21202
+ return null;
21203
+ if (!sameJson(current.nativeSessionRef, input.previousNativeSessionRef)) {
21204
+ throw new MappingStoreError("MAPPING_CONFLICT", "Subagent replacement source Session is stale");
21205
+ }
21206
+ const nativeSessionId = input.nativeSessionRef.nativeSessionId;
21207
+ return {
21208
+ ...current,
21209
+ createRequestId: input.createRequestId,
21210
+ nativeSessionRef: input.nativeSessionRef,
21211
+ turnMappings: current.turnMappings.map((mapping) => ({
21212
+ ...mapping,
21213
+ nativeTurnRef: { ...mapping.nativeTurnRef, nativeSessionId },
21214
+ ...mapping.nativeCheckpointRef ? { nativeCheckpointRef: { ...mapping.nativeCheckpointRef, nativeSessionId } } : {}
21215
+ }))
21216
+ };
21217
+ });
21218
+ }
20934
21219
  async replaceReadySession(input) {
20935
21220
  return this.#update(input.hostThreadId, (current) => {
20936
- if (current.state !== "ready" || !current.nativeSessionRef || !current.forkSource || current.forkSource.hostThreadId !== input.forkSource.hostThreadId || current.nativeSessionRef.nativeSessionId === input.nativeSessionRef.nativeSessionId || input.turnMappings.length < 1 || input.turnMappings.length >= current.turnMappings.length || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
20937
- throw new MappingStoreError("MAPPING_CONFLICT", "Ready Session replacement must retain an exact shorter derived prefix");
21221
+ if (current.state !== "ready" || !current.nativeSessionRef || current.revision !== input.expectedRevision || !sameJson(current.nativeSessionRef, input.expectedNativeSessionRef) || !current.forkSource || current.forkSource.hostThreadId !== input.forkSource.hostThreadId || current.nativeSessionRef.nativeSessionId === input.nativeSessionRef.nativeSessionId || input.turnMappings.length < 1 || input.turnMappings.length >= current.turnMappings.length || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
21222
+ throw new MappingStoreError("MAPPING_CONFLICT", "Ready Session replacement must match the expected record and retain an exact shorter derived prefix");
20938
21223
  }
20939
21224
  return {
20940
21225
  ...current,
@@ -20946,8 +21231,8 @@ var MappingStore = class {
20946
21231
  }
20947
21232
  async replaceReadySessionAfterLastTurn(input) {
20948
21233
  return this.#update(input.hostThreadId, (current) => {
20949
- if (current.state !== "ready" || !current.nativeSessionRef || input.turnMappings.length !== current.turnMappings.length - 1 || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
20950
- throw new MappingStoreError("MAPPING_CONFLICT", "Last-Turn Session replacement must retain the exact shorter Host Turn prefix");
21234
+ if (current.state !== "ready" || !current.nativeSessionRef || current.revision !== input.expectedRevision || !sameJson(current.nativeSessionRef, input.expectedNativeSessionRef) || input.turnMappings.length !== current.turnMappings.length - 1 || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
21235
+ throw new MappingStoreError("MAPPING_CONFLICT", "Last-Turn Session replacement must match the expected record and retain the exact shorter Host Turn prefix");
20951
21236
  }
20952
21237
  return {
20953
21238
  ...current,
@@ -21168,10 +21453,10 @@ var MappingStore = class {
21168
21453
  readdir2(this.#directory)
21169
21454
  ]);
21170
21455
  await Promise.all([
21171
- ...threadNames.filter((name) => name.includes(".tmp-")).map((name) => rm3(path4.join(this.#threadsDirectory, name), { force: true })),
21172
- ...delegationNames.filter((name) => name.includes(".tmp-")).map((name) => rm3(path4.join(this.#delegationsDirectory, name), { force: true })),
21456
+ ...threadNames.filter((name) => name.includes(".tmp-")).map((name) => rm3(path5.join(this.#threadsDirectory, name), { force: true })),
21457
+ ...delegationNames.filter((name) => name.includes(".tmp-")).map((name) => rm3(path5.join(this.#delegationsDirectory, name), { force: true })),
21173
21458
  // Renamed aside by #acquireLock; nothing reads them back, and they accumulate one per run.
21174
- ...rootNames.filter((name) => name.startsWith(`${path4.basename(this.#lockPath)}.stale-`)).map((name) => rm3(path4.join(this.#directory, name), { force: true }))
21459
+ ...rootNames.filter((name) => name.startsWith(`${path5.basename(this.#lockPath)}.stale-`)).map((name) => rm3(path5.join(this.#directory, name), { force: true }))
21175
21460
  ]);
21176
21461
  }
21177
21462
  async #acquireLock() {
@@ -21272,13 +21557,13 @@ var MappingStore = class {
21272
21557
  await next;
21273
21558
  }
21274
21559
  #recordPath(hostThreadId) {
21275
- return path4.join(this.#threadsDirectory, `${hostThreadId}.json`);
21560
+ return path5.join(this.#threadsDirectory, `${hostThreadId}.json`);
21276
21561
  }
21277
21562
  #delegationPath(delegationId) {
21278
- return path4.join(this.#delegationsDirectory, `${delegationId}.json`);
21563
+ return path5.join(this.#delegationsDirectory, `${delegationId}.json`);
21279
21564
  }
21280
21565
  #backupPath(hostThreadId) {
21281
- return path4.join(this.#backupsDirectory, `${hostThreadId}.json`);
21566
+ return path5.join(this.#backupsDirectory, `${hostThreadId}.json`);
21282
21567
  }
21283
21568
  #requireInitialized() {
21284
21569
  if (!this.#initialized) {
@@ -22031,8 +22316,8 @@ function projectFileChangeKind(kind) {
22031
22316
  return { type: kind };
22032
22317
  }
22033
22318
  function projectFileChanges(changes) {
22034
- return changes.map(({ path: path25, kind, unifiedDiff }) => ({
22035
- path: path25,
22319
+ return changes.map(({ path: path26, kind, unifiedDiff }) => ({
22320
+ path: path26,
22036
22321
  kind: projectFileChangeKind(kind),
22037
22322
  diff: unifiedDiff
22038
22323
  }));
@@ -22253,7 +22538,9 @@ function projectItem(item, outcome, defaultCwd, includeCommandOutput = true, sen
22253
22538
  changes: projectFileChanges(item.changes),
22254
22539
  status: itemStatus(outcome)
22255
22540
  };
22256
- case "subagentDelegation":
22541
+ case "subagentDelegation": {
22542
+ const primary = item.subagents[0];
22543
+ const sameConfiguration = item.subagents.every((subagent) => subagent.model === primary?.model && subagent.reasoningEffort === primary?.reasoningEffort);
22257
22544
  return {
22258
22545
  id: item.itemId,
22259
22546
  type: "collabAgentToolCall",
@@ -22262,13 +22549,14 @@ function projectItem(item, outcome, defaultCwd, includeCommandOutput = true, sen
22262
22549
  senderThreadId: senderThreadId ?? "",
22263
22550
  receiverThreadIds: item.subagents.map(({ subagentId }) => subagentId),
22264
22551
  prompt: item.prompt ?? null,
22265
- model: null,
22266
- reasoningEffort: null,
22552
+ model: sameConfiguration ? primary?.model ?? null : null,
22553
+ reasoningEffort: sameConfiguration ? primary?.reasoningEffort ?? null : null,
22267
22554
  agentsStates: Object.fromEntries(item.subagents.map(({ subagentId, status, resultSummary }) => [
22268
22555
  subagentId,
22269
22556
  { status: collabAgentStatus(status), message: resultSummary ?? null }
22270
22557
  ]))
22271
22558
  };
22559
+ }
22272
22560
  }
22273
22561
  }
22274
22562
  function reasoningPreviewItemId(itemId) {
@@ -22344,9 +22632,9 @@ function projectHistoricalTurn(input) {
22344
22632
  return [];
22345
22633
  }
22346
22634
  return item.type === "reasoning" ? [
22347
- projectItem(item, outcome, cwd, true, ""),
22635
+ projectItem(item, outcome, cwd, true, input.threadId ?? ""),
22348
22636
  projectReasoningTranscriptItem(item, outcome, cwd)
22349
- ] : [projectItem(item, outcome, cwd, true, "")];
22637
+ ] : [projectItem(item, outcome, cwd, true, input.threadId ?? "")];
22350
22638
  })
22351
22639
  ],
22352
22640
  error: error51,
@@ -22978,13 +23266,13 @@ function decodeThreadForkRequest(request) {
22978
23266
  if (runtimeWorkspaceRoots !== void 0 && runtimeWorkspaceRoots !== null && (!Array.isArray(runtimeWorkspaceRoots) || runtimeWorkspaceRoots.some((root) => typeof root !== "string" || root.length === 0))) {
22979
23267
  throw new Error("thread/fork params.runtimeWorkspaceRoots must be text paths or null");
22980
23268
  }
22981
- const path25 = optionalText(params, "path", { allowEmpty: true });
23269
+ const path26 = optionalText(params, "path", { allowEmpty: true });
22982
23270
  const ephemeral = optionalBoolean(params, "ephemeral");
22983
23271
  return {
22984
23272
  threadId: threadId3,
22985
23273
  ...lastTurnText ? { lastTurnId: hostTurnIdSchema.parse(lastTurnText) } : {},
22986
23274
  ...beforeTurnText ? { beforeTurnId: hostTurnIdSchema.parse(beforeTurnText) } : {},
22987
- ...path25 ? { path: path25 } : {},
23275
+ ...path26 ? { path: path26 } : {},
22988
23276
  ...optionalField(params, "model"),
22989
23277
  ...optionalField(params, "modelProvider"),
22990
23278
  ...optionalField(params, "cwd"),
@@ -23995,18 +24283,149 @@ async function inspectHarnessAccounts(adapters, descriptors, timeoutMs = 12e3) {
23995
24283
  }
23996
24284
 
23997
24285
  // packages/host-runtime/src/app-server-host.ts
23998
- import { createHash as createHash5, randomUUID as randomUUID8 } from "node:crypto";
24286
+ import { createHash as createHash6, randomUUID as randomUUID9 } from "node:crypto";
23999
24287
  import { rm as rm4 } from "node:fs/promises";
24000
24288
  import os2 from "node:os";
24001
- import path11 from "node:path";
24289
+ import path12 from "node:path";
24002
24290
 
24003
24291
  // packages/host-runtime/src/external-thread-fork.ts
24004
24292
  import nodePath from "node:path";
24005
24293
 
24006
24294
  // packages/host-runtime/src/external-thread-repository.ts
24007
- import { randomUUID as randomUUID3 } from "node:crypto";
24295
+ import { randomUUID as randomUUID4 } from "node:crypto";
24008
24296
  import os from "node:os";
24009
- import path5 from "node:path";
24297
+ import path6 from "node:path";
24298
+
24299
+ // packages/host-runtime/src/external-subagent-threads.ts
24300
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
24301
+ function childCreateRequest(parent, nativeRef, id2) {
24302
+ const key = createHash3("sha256").update(JSON.stringify([parent.hostThreadId, parent.harnessId, nativeRef.nativeSessionId, id2])).digest("hex");
24303
+ return `subagent:${key}`;
24304
+ }
24305
+ function subagentMaterializer(store, parent, previousParent) {
24306
+ if (previousParent && (previousParent.hostThreadId !== parent.hostThreadId || previousParent.harnessId !== parent.harnessId)) {
24307
+ throw new Error("Subagent Session replacement must stay in the same Host Thread");
24308
+ }
24309
+ const previousRef = previousParent?.nativeSessionRef;
24310
+ let legacy;
24311
+ const legacyRecords = () => legacy ??= store.listThreads().then((records) => {
24312
+ const byRequest = /* @__PURE__ */ new Map();
24313
+ const children = /* @__PURE__ */ new Map();
24314
+ for (const record3 of records) {
24315
+ if (!record3.subagent || record3.harnessId !== parent.harnessId || !record3.nativeSessionRef)
24316
+ continue;
24317
+ const owner = {
24318
+ hostThreadId: record3.subagent.parentHostThreadId,
24319
+ harnessId: record3.harnessId
24320
+ };
24321
+ const key = childCreateRequest(
24322
+ owner,
24323
+ record3.nativeSessionRef,
24324
+ record3.subagent.nativeSubagentId
24325
+ );
24326
+ if (!byRequest.has(key) || record3.state === "ready") byRequest.set(key, record3);
24327
+ const siblings = children.get(owner.hostThreadId) ?? [];
24328
+ siblings.push(record3);
24329
+ children.set(owner.hostThreadId, siblings);
24330
+ }
24331
+ return { byRequest, children };
24332
+ });
24333
+ const rebind = async (existing, owner, visited = /* @__PURE__ */ new Set()) => {
24334
+ if (!previousRef || !owner.nativeSessionRef || !existing.subagent || visited.has(existing.hostThreadId)) {
24335
+ throw new Error("Invalid retained Subagent Session tree");
24336
+ }
24337
+ visited.add(existing.hostThreadId);
24338
+ const rebound = await store.rebindSubagentSession({
24339
+ hostThreadId: existing.hostThreadId,
24340
+ parentHostThreadId: owner.hostThreadId,
24341
+ previousNativeSessionRef: previousRef,
24342
+ nativeSessionRef: owner.nativeSessionRef,
24343
+ createRequestId: childCreateRequest(
24344
+ owner,
24345
+ owner.nativeSessionRef,
24346
+ existing.subagent.nativeSubagentId
24347
+ )
24348
+ });
24349
+ for (const descendant of (await legacyRecords()).children.get(existing.hostThreadId) ?? []) {
24350
+ if (descendant.state === "ready" && descendant.nativeSessionRef?.nativeSessionId === previousRef.nativeSessionId) {
24351
+ await rebind(descendant, rebound, visited);
24352
+ }
24353
+ }
24354
+ return rebound;
24355
+ };
24356
+ return async (child) => {
24357
+ if (!child.nativeSubagentId || !parent.nativeSessionRef || parent.state !== "ready")
24358
+ return null;
24359
+ const nativeRef = parent.nativeSessionRef;
24360
+ const createRequestId = childCreateRequest(parent, nativeRef, child.nativeSubagentId);
24361
+ const previousRequest = previousRef && previousRef.nativeSessionId !== nativeRef.nativeSessionId ? childCreateRequest(parent, previousRef, child.nativeSubagentId) : void 0;
24362
+ let existing = await store.getThreadByCreateRequest(createRequestId);
24363
+ if (!existing && previousRequest)
24364
+ existing = await store.getThreadByCreateRequest(previousRequest);
24365
+ if (!existing) {
24366
+ const { byRequest } = await legacyRecords();
24367
+ existing = byRequest.get(createRequestId) ?? (previousRequest ? byRequest.get(previousRequest) : void 0) ?? null;
24368
+ }
24369
+ if (existing?.state === "ready") {
24370
+ if (existing.nativeSessionRef?.nativeSessionId === nativeRef.nativeSessionId) return existing;
24371
+ if (!previousRef) throw new Error("Subagent belongs to a different Native Session");
24372
+ return rebind(existing, parent);
24373
+ }
24374
+ const provisional = existing ?? await store.createProvisional({
24375
+ hostThreadId: hostThreadIdSchema.parse(randomUUID3()),
24376
+ createRequestId,
24377
+ harnessId: parent.harnessId,
24378
+ cwd: parent.cwd,
24379
+ title: child.description,
24380
+ transportModelId: parent.transportModelId,
24381
+ ephemeral: parent.ephemeral,
24382
+ historyMode: "paginated",
24383
+ subagent: {
24384
+ parentHostThreadId: parent.hostThreadId,
24385
+ nativeSubagentId: child.nativeSubagentId,
24386
+ ...child.role ? { role: child.role } : {}
24387
+ }
24388
+ });
24389
+ return provisional.state === "ready" ? provisional : store.commitReady({
24390
+ hostThreadId: provisional.hostThreadId,
24391
+ nativeSessionRef: nativeRef
24392
+ });
24393
+ };
24394
+ }
24395
+ async function materializeExternalSubagent(store, parent, child) {
24396
+ return subagentMaterializer(store, parent)(child);
24397
+ }
24398
+ async function projectExternalSnapshot(store, record3, snapshot, previousParent) {
24399
+ const materialize = subagentMaterializer(store, record3, previousParent);
24400
+ const turns = [];
24401
+ for (const [index, turn] of snapshot.turns.entries()) {
24402
+ const mapping = record3.turnMappings[index];
24403
+ if (!mapping) throw new Error("External Snapshot mapping is incomplete");
24404
+ const items = await Promise.all(
24405
+ turn.items.map(async (entry) => {
24406
+ if (entry.item.type !== "subagentDelegation") return entry;
24407
+ const subagents = await Promise.all(
24408
+ entry.item.subagents.map(async (child) => {
24409
+ const stored = await materialize(child);
24410
+ return stored ? { ...child, subagentId: stored.hostThreadId } : child;
24411
+ })
24412
+ );
24413
+ return { ...entry, item: { ...entry.item, subagents } };
24414
+ })
24415
+ );
24416
+ turns.push(
24417
+ projectHistoricalTurn({
24418
+ threadId: record3.hostThreadId,
24419
+ turnId: mapping.hostTurnId,
24420
+ cwd: record3.cwd,
24421
+ snapshot: { ...turn, items }
24422
+ })
24423
+ );
24424
+ }
24425
+ return turns;
24426
+ }
24427
+
24428
+ // packages/host-runtime/src/external-thread-repository.ts
24010
24429
  function nativeTurnKey2(ref) {
24011
24430
  return `${ref.harnessId}\0${ref.nativeSessionId}\0${ref.nativeTurnKey}\0${ref.formatVersion}`;
24012
24431
  }
@@ -24015,8 +24434,8 @@ function sameMapping(left, right) {
24015
24434
  }
24016
24435
  function defaultMappingStoreDirectory(environment) {
24017
24436
  const dataDirectory = environment.CODEXHOST_DATA_DIR;
24018
- return path5.join(
24019
- dataDirectory ? path5.resolve(dataDirectory) : path5.join(os.homedir(), ".codexhost"),
24437
+ return path6.join(
24438
+ dataDirectory ? path6.resolve(dataDirectory) : path6.join(os.homedir(), ".codexhost"),
24020
24439
  "mapping-store"
24021
24440
  );
24022
24441
  }
@@ -24041,6 +24460,9 @@ var ExternalThreadRepository = class {
24041
24460
  list() {
24042
24461
  return this.store.listThreads();
24043
24462
  }
24463
+ materializeSubagent(parent, child) {
24464
+ return materializeExternalSubagent(this.store, parent, child);
24465
+ }
24044
24466
  findByCreateRequest(createRequestId) {
24045
24467
  return this.store.getThreadByCreateRequest(createRequestId);
24046
24468
  }
@@ -24110,7 +24532,7 @@ var ExternalThreadRepository = class {
24110
24532
  throw new Error("Derived Snapshot identity does not belong to its Native Session");
24111
24533
  }
24112
24534
  return {
24113
- hostTurnId: hostTurnIdSchema.parse(randomUUID3()),
24535
+ hostTurnId: hostTurnIdSchema.parse(randomUUID4()),
24114
24536
  nativeTurnRef: turn.nativeTurnRef,
24115
24537
  ...turn.checkpoint ? { nativeCheckpointRef: turn.checkpoint } : {}
24116
24538
  };
@@ -24122,15 +24544,7 @@ var ExternalThreadRepository = class {
24122
24544
  });
24123
24545
  return {
24124
24546
  record: nextRecord,
24125
- turns: snapshot.turns.map((turn, index) => {
24126
- const mapping = mappings[index];
24127
- if (!mapping) throw new Error("Derived Snapshot mapping is incomplete");
24128
- return projectHistoricalTurn({
24129
- turnId: mapping.hostTurnId,
24130
- cwd: record3.cwd,
24131
- snapshot: turn
24132
- });
24133
- })
24547
+ turns: await projectExternalSnapshot(this.store, nextRecord, snapshot)
24134
24548
  };
24135
24549
  }
24136
24550
  async commitForkRollback(derived, source, nativeSessionRef, snapshot) {
@@ -24164,6 +24578,8 @@ var ExternalThreadRepository = class {
24164
24578
  });
24165
24579
  const nextRecord = await this.store.replaceReadySession({
24166
24580
  hostThreadId: derived.hostThreadId,
24581
+ expectedRevision: derived.revision,
24582
+ expectedNativeSessionRef: derived.nativeSessionRef,
24167
24583
  nativeSessionRef,
24168
24584
  turnMappings: mappings,
24169
24585
  forkSource: {
@@ -24173,15 +24589,7 @@ var ExternalThreadRepository = class {
24173
24589
  });
24174
24590
  return {
24175
24591
  record: nextRecord,
24176
- turns: snapshot.turns.map((turn, index) => {
24177
- const mapping = mappings[index];
24178
- if (!mapping) throw new Error("External rollback Snapshot mapping is incomplete");
24179
- return projectHistoricalTurn({
24180
- turnId: mapping.hostTurnId,
24181
- cwd: derived.cwd,
24182
- snapshot: turn
24183
- });
24184
- })
24592
+ turns: await projectExternalSnapshot(this.store, nextRecord, snapshot, derived)
24185
24593
  };
24186
24594
  }
24187
24595
  async commitLastTurnRollback(current, nativeSessionRef, snapshot) {
@@ -24201,30 +24609,26 @@ var ExternalThreadRepository = class {
24201
24609
  });
24202
24610
  const nextRecord = await this.store.replaceReadySessionAfterLastTurn({
24203
24611
  hostThreadId: current.hostThreadId,
24612
+ expectedRevision: current.revision,
24613
+ expectedNativeSessionRef: current.nativeSessionRef,
24204
24614
  nativeSessionRef,
24205
24615
  turnMappings: mappings
24206
24616
  });
24207
24617
  return {
24208
24618
  record: nextRecord,
24209
- turns: snapshot.turns.map((turn, index) => {
24210
- const mapping = mappings[index];
24211
- if (!mapping) throw new Error("Last-Turn rollback Snapshot mapping is incomplete");
24212
- return projectHistoricalTurn({
24213
- turnId: mapping.hostTurnId,
24214
- cwd: current.cwd,
24215
- snapshot: turn
24216
- });
24217
- })
24619
+ turns: await projectExternalSnapshot(this.store, nextRecord, snapshot, current)
24218
24620
  };
24219
24621
  }
24220
24622
  async sessionTreeId(record3) {
24221
24623
  let current = record3;
24222
24624
  const visited = /* @__PURE__ */ new Set();
24223
- while (current.forkSource) {
24625
+ while (current.subagent || current.forkSource) {
24224
24626
  if (visited.has(current.hostThreadId))
24225
24627
  throw new Error("External Thread Fork tree contains a cycle");
24226
24628
  visited.add(current.hostThreadId);
24227
- const source = await this.find(current.forkSource.hostThreadId);
24629
+ const parentId = current.subagent?.parentHostThreadId ?? current.forkSource?.hostThreadId;
24630
+ if (!parentId) break;
24631
+ const source = await this.find(parentId);
24228
24632
  if (!source) break;
24229
24633
  current = source;
24230
24634
  }
@@ -24248,7 +24652,7 @@ var ExternalThreadRepository = class {
24248
24652
  const aligned = snapshot.turns.map((turn) => {
24249
24653
  const existing = mappingsByNative.get(nativeTurnKey2(turn.nativeTurnRef));
24250
24654
  const mapping = existing ?? {
24251
- hostTurnId: hostTurnIdSchema.parse(randomUUID3()),
24655
+ hostTurnId: hostTurnIdSchema.parse(randomUUID4()),
24252
24656
  nativeTurnRef: turn.nativeTurnRef,
24253
24657
  ...turn.checkpoint ? { nativeCheckpointRef: turn.checkpoint } : {}
24254
24658
  };
@@ -24269,16 +24673,14 @@ var ExternalThreadRepository = class {
24269
24673
  const nextRecord = mappingsChanged ? await this.store.reconcileTurnMappings(record3.hostThreadId, orderedMappings) : record3;
24270
24674
  return {
24271
24675
  record: nextRecord,
24272
- turns: aligned.map(
24273
- ({ mapping, snapshot: turn }) => projectHistoricalTurn({ turnId: mapping.hostTurnId, cwd: record3.cwd, snapshot: turn })
24274
- )
24676
+ turns: await projectExternalSnapshot(this.store, nextRecord, snapshot)
24275
24677
  };
24276
24678
  }
24277
24679
  };
24278
24680
  function createExternalThreadRecordInput(input) {
24279
24681
  return {
24280
- hostThreadId: input.hostThreadId ?? hostThreadIdSchema.parse(randomUUID3()),
24281
- createRequestId: input.createRequestId ?? randomUUID3(),
24682
+ hostThreadId: input.hostThreadId ?? hostThreadIdSchema.parse(randomUUID4()),
24683
+ createRequestId: input.createRequestId ?? randomUUID4(),
24282
24684
  harnessId: input.harnessId,
24283
24685
  cwd: input.cwd,
24284
24686
  ...input.title ? { title: input.title } : {},
@@ -24925,13 +25327,14 @@ async function restoreCurrentConfiguration(session, configuration) {
24925
25327
  }
24926
25328
  async function executeCurrentLastTurnRollback(input) {
24927
25329
  const { current, adapters, repository, runtime } = input;
24928
- if (current.record.turnMappings.length === 0) {
25330
+ const currentRecord = current.record;
25331
+ if (currentRecord.turnMappings.length === 0) {
24929
25332
  return {
24930
25333
  ok: false,
24931
25334
  error: { code: -32076, message: "External Thread has no Turn to roll back" }
24932
25335
  };
24933
25336
  }
24934
- const currentNativeRef = current.record.nativeSessionRef;
25337
+ const currentNativeRef = currentRecord.nativeSessionRef;
24935
25338
  const adapter = adapters.get(current.harnessId);
24936
25339
  if (!currentNativeRef || !adapter) {
24937
25340
  return {
@@ -24979,7 +25382,7 @@ async function executeCurrentLastTurnRollback(input) {
24979
25382
  await session.close().catch(() => void 0);
24980
25383
  return { ok: false, error: mapExternalThreadHarnessError(snapshot.error, "read") };
24981
25384
  }
24982
- if (snapshot.value.turns.length !== current.record.turnMappings.length - 1) {
25385
+ if (snapshot.value.turns.length !== currentRecord.turnMappings.length - 1) {
24983
25386
  await session.close().catch(() => void 0);
24984
25387
  return {
24985
25388
  ok: false,
@@ -24997,7 +25400,7 @@ async function executeCurrentLastTurnRollback(input) {
24997
25400
  let aligned;
24998
25401
  try {
24999
25402
  aligned = await repository.commitLastTurnRollback(
25000
- current.record,
25403
+ currentRecord,
25001
25404
  finalNativeRef,
25002
25405
  snapshot.value
25003
25406
  );
@@ -25045,7 +25448,8 @@ async function executeExternalThreadRollback(input) {
25045
25448
  ...input.environment ? { environment: input.environment } : {}
25046
25449
  });
25047
25450
  }
25048
- const forkSource = derived.record.forkSource;
25451
+ const derivedRecord = derived.record;
25452
+ const forkSource = derivedRecord.forkSource;
25049
25453
  if (!forkSource) {
25050
25454
  return {
25051
25455
  ok: false,
@@ -25076,10 +25480,11 @@ async function executeExternalThreadRollback(input) {
25076
25480
  const sourceRefreshError = await runtime.refresh(source);
25077
25481
  if (sourceRefreshError) return { ok: false, error: sourceRefreshError };
25078
25482
  }
25079
- const sourceBoundaryIndex = source.record.turnMappings.findIndex(
25483
+ const sourceRecord = source.record;
25484
+ const sourceBoundaryIndex = sourceRecord.turnMappings.findIndex(
25080
25485
  ({ hostTurnId }) => hostTurnId === forkSource.hostTurnId
25081
25486
  );
25082
- if (sourceBoundaryIndex < 0 || derived.record.turnMappings.length !== sourceBoundaryIndex + 1) {
25487
+ if (sourceBoundaryIndex < 0 || derivedRecord.turnMappings.length !== sourceBoundaryIndex + 1) {
25083
25488
  return {
25084
25489
  ok: false,
25085
25490
  error: {
@@ -25088,19 +25493,19 @@ async function executeExternalThreadRollback(input) {
25088
25493
  }
25089
25494
  };
25090
25495
  }
25091
- const excludedActiveTurnCount = source.running || source.record.turnMappings.length > derived.record.turnMappings.length ? 1 : 0;
25092
- const retainedCount = derived.record.turnMappings.length - rollback.numTurns + excludedActiveTurnCount;
25093
- if (retainedCount === derived.record.turnMappings.length) {
25496
+ const excludedActiveTurnCount = source.running || sourceRecord.turnMappings.length > derivedRecord.turnMappings.length ? 1 : 0;
25497
+ const retainedCount = derivedRecord.turnMappings.length - rollback.numTurns + excludedActiveTurnCount;
25498
+ if (retainedCount === derivedRecord.turnMappings.length) {
25094
25499
  return { ok: true, thread: derived.thread };
25095
25500
  }
25096
- const boundary = source.record.turnMappings[retainedCount - 1];
25501
+ const boundary = sourceRecord.turnMappings[retainedCount - 1];
25097
25502
  if (retainedCount < 1 || !boundary?.nativeCheckpointRef) {
25098
25503
  return {
25099
25504
  ok: false,
25100
25505
  error: { code: -32080, message: "External Fork Checkpoint is unavailable" }
25101
25506
  };
25102
25507
  }
25103
- const sourceNativeRef = source.record.nativeSessionRef;
25508
+ const sourceNativeRef = sourceRecord.nativeSessionRef;
25104
25509
  const adapter = adapters.get(source.harnessId);
25105
25510
  if (!sourceNativeRef || !adapter) {
25106
25511
  return {
@@ -25128,7 +25533,7 @@ async function executeExternalThreadRollback(input) {
25128
25533
  }
25129
25534
  const session = opened.value;
25130
25535
  const finalNativeRef = session.initialState.nativeRef;
25131
- if (!finalNativeRef || finalNativeRef.nativeSessionId === sourceNativeRef.nativeSessionId || finalNativeRef.nativeSessionId === derived.record.nativeSessionRef?.nativeSessionId) {
25536
+ if (!finalNativeRef || finalNativeRef.nativeSessionId === sourceNativeRef.nativeSessionId || finalNativeRef.nativeSessionId === derivedRecord.nativeSessionRef?.nativeSessionId) {
25132
25537
  await session.close().catch(() => void 0);
25133
25538
  return {
25134
25539
  ok: false,
@@ -25150,8 +25555,8 @@ async function executeExternalThreadRollback(input) {
25150
25555
  let aligned;
25151
25556
  try {
25152
25557
  aligned = await repository.commitForkRollback(
25153
- derived.record,
25154
- source.record,
25558
+ derivedRecord,
25559
+ sourceRecord,
25155
25560
  finalNativeRef,
25156
25561
  snapshot.value
25157
25562
  );
@@ -25396,9 +25801,43 @@ var ExternalThreadRuntime = class {
25396
25801
  } catch (error51) {
25397
25802
  this.#diagnose(error51);
25398
25803
  }
25804
+ await this.#retireSubagents(current.id);
25399
25805
  this.#threads.delete(current.id);
25400
25806
  return this.register(input);
25401
25807
  }
25808
+ async #retireSubagents(parentId) {
25809
+ const ancestors = /* @__PURE__ */ new Map();
25810
+ const descendant = async (record3) => {
25811
+ const visited = /* @__PURE__ */ new Set();
25812
+ let ownerId = record3?.subagent?.parentHostThreadId;
25813
+ while (ownerId && !visited.has(ownerId)) {
25814
+ if (ownerId === parentId) return true;
25815
+ visited.add(ownerId);
25816
+ let ancestor = ancestors.get(ownerId);
25817
+ if (!ancestor) {
25818
+ ancestor = this.#repository.find(ownerId);
25819
+ ancestors.set(ownerId, ancestor);
25820
+ }
25821
+ ownerId = (await ancestor)?.subagent?.parentHostThreadId;
25822
+ }
25823
+ return false;
25824
+ };
25825
+ for (const [id2, restoring] of [...this.#restores]) {
25826
+ if (await descendant(await this.#repository.find(id2))) {
25827
+ await restoring.catch(this.#diagnose);
25828
+ }
25829
+ }
25830
+ for (const child of this.#threads.values()) {
25831
+ if (!await descendant(child.record)) continue;
25832
+ this.#threads.delete(child.id);
25833
+ try {
25834
+ await child.session.close();
25835
+ await child.outputTask;
25836
+ } catch (error51) {
25837
+ this.#diagnose(error51);
25838
+ }
25839
+ }
25840
+ }
25402
25841
  async locate(threadId3) {
25403
25842
  const loaded = this.#threads.get(threadId3);
25404
25843
  if (loaded) return { kind: "external", record: loaded.record, thread: loaded };
@@ -25509,6 +25948,10 @@ var ExternalThreadRuntime = class {
25509
25948
  nativeSubagentId: subagent.nativeSubagentId,
25510
25949
  cwd: record3.cwd
25511
25950
  });
25951
+ const latest = await this.#repository.find(record3.hostThreadId);
25952
+ if (latest?.state === "ready" && latest.nativeSessionRef && JSON.stringify(latest.nativeSessionRef) !== JSON.stringify(parent)) {
25953
+ return this.#restore(latest);
25954
+ }
25512
25955
  if (!snapshot.ok) {
25513
25956
  throw new ExternalThreadOpenError(mapExternalThreadHarnessError(snapshot.error, "read"));
25514
25957
  }
@@ -25785,11 +26228,11 @@ var ExternalTurnSteering = class {
25785
26228
  };
25786
26229
 
25787
26230
  // packages/host-runtime/src/harness-delegation-coordinator.ts
25788
- import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
25789
- import path6 from "node:path";
26231
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
26232
+ import path7 from "node:path";
25790
26233
 
25791
26234
  // packages/host-runtime/src/delegation-snapshot.ts
25792
- import { createHash as createHash3 } from "node:crypto";
26235
+ import { createHash as createHash4 } from "node:crypto";
25793
26236
  var CURSOR_PREFIX = "codexhost:thread-messages:v1:";
25794
26237
  var DEFAULT_MESSAGE_LIMIT = 25;
25795
26238
  var MAX_MESSAGE_LIMIT = 100;
@@ -25843,7 +26286,7 @@ function allVisibleMessages(turns) {
25843
26286
  return messages;
25844
26287
  }
25845
26288
  function cursorFingerprint(threadId3) {
25846
- return createHash3("sha256").update(threadId3).digest("hex");
26289
+ return createHash4("sha256").update(threadId3).digest("hex");
25847
26290
  }
25848
26291
  function encodeCursor(threadId3, offset) {
25849
26292
  const payload = JSON.stringify({ version: 1, fingerprint: cursorFingerprint(threadId3), offset });
@@ -25898,15 +26341,21 @@ function projectDelegationThreadSnapshot(input) {
25898
26341
  const latestTurnStatus = latestTurn ? turnStatus2(latestTurn.status) : null;
25899
26342
  const status = input.running ? "running" : latestTurnStatus === "failed" || latestTurnStatus === "interrupted" ? latestTurnStatus : threadStatus(input.thread.status, input.running);
25900
26343
  const latestTurnMessages = latestTurnId ? visible.filter((message) => message.turnId === latestTurnId && message.role === "agent") : [];
25901
- const final = latestTurnMessages.filter((message) => message.phase === "final").at(-1);
25902
- const progress = latestTurnMessages.filter((message) => message.phase !== "final").map(({ id: id2, turnId, text }) => ({ id: id2, turnId, text }));
26344
+ const final = latestTurnMessages.filter((message) => message.phase === "final" && message.text.trim()).at(-1);
26345
+ const progress = latestTurnMessages.filter((message) => message.phase !== "final" && message.text.trim()).map(({ id: id2, turnId, text }) => ({ id: id2, turnId, text }));
25903
26346
  const result = input.running ? { availability: "pending" } : final ? { availability: "available", text: final.text } : {
25904
26347
  availability: "unavailable",
25905
26348
  ...isRecord8(latestTurn?.error) && typeof latestTurn.error.message === "string" ? { message: latestTurn.error.message } : {}
25906
26349
  };
25907
26350
  const offset = options2.view === "messages" ? decodeCursor(input.threadId, options2.cursor) : visible.length;
25908
- const page = options2.view === "messages" ? visible.slice(offset, offset + options2.limit) : void 0;
25909
- const nextOffset = options2.view === "messages" ? offset + (page?.length ?? 0) : visible.length;
26351
+ const page = options2.view === "messages" ? [] : void 0;
26352
+ let nextOffset = offset;
26353
+ if (page) {
26354
+ while (nextOffset < visible.length && page.length < options2.limit) {
26355
+ const message = visible[nextOffset++];
26356
+ if (message?.text.trim()) page.push(message);
26357
+ }
26358
+ }
25910
26359
  return {
25911
26360
  threadId: input.threadId,
25912
26361
  harnessId: input.harnessId,
@@ -25914,7 +26363,10 @@ function projectDelegationThreadSnapshot(input) {
25914
26363
  turn: latestTurnId && latestTurnStatus ? { turnId: latestTurnId, status: latestTurnStatus } : null,
25915
26364
  progress,
25916
26365
  result,
25917
- ...page ? { messages: page } : {},
26366
+ ...page ? {
26367
+ messages: page,
26368
+ hasMore: visible.slice(nextOffset).some((message) => message.text.trim())
26369
+ } : {},
25918
26370
  nextCursor: encodeCursor(input.threadId, nextOffset)
25919
26371
  };
25920
26372
  }
@@ -25929,10 +26381,10 @@ function terminal(status) {
25929
26381
  return status === "completed" || status === "failed" || status === "interrupted";
25930
26382
  }
25931
26383
  function taskDigest(input) {
25932
- return createHash4("sha256").update(
26384
+ return createHash5("sha256").update(
25933
26385
  JSON.stringify({
25934
26386
  task: input.task,
25935
- cwd: path6.resolve(input.cwd),
26387
+ cwd: path7.resolve(input.cwd),
25936
26388
  modelId: input.model?.id ?? null,
25937
26389
  thinkingOptionId: input.thinkingOptionId ?? null
25938
26390
  })
@@ -25946,11 +26398,11 @@ function statusFromThread(thread) {
25946
26398
  return last ? "completed" : "creating";
25947
26399
  }
25948
26400
  function validateStart(input) {
25949
- if (!input.task?.trim())
26401
+ if (typeof input.task !== "string" || !input.task.trim())
25950
26402
  throw new DelegationControlError("INVALID_ARGUMENT", "Task must not be empty");
25951
- if (!input.cwd?.trim())
26403
+ if (input.cwd !== void 0 && (typeof input.cwd !== "string" || !input.cwd.trim()))
25952
26404
  throw new DelegationControlError("INVALID_ARGUMENT", "cwd must not be empty");
25953
- if (input.requestId !== void 0 && !input.requestId.trim()) {
26405
+ if (input.requestId !== void 0 && (typeof input.requestId !== "string" || !input.requestId.trim())) {
25954
26406
  throw new DelegationControlError("INVALID_ARGUMENT", "Request ID must not be empty");
25955
26407
  }
25956
26408
  }
@@ -25968,6 +26420,7 @@ var HarnessDelegationCoordinator = class {
25968
26420
  #cancelOfficial;
25969
26421
  #startOfficial;
25970
26422
  #listOfficial;
26423
+ #officialThreadCwd;
25971
26424
  #activeOfficialParents;
25972
26425
  constructor(input) {
25973
26426
  this.#adapters = input.adapters;
@@ -25983,8 +26436,12 @@ var HarnessDelegationCoordinator = class {
25983
26436
  this.#cancelOfficial = input.cancelOfficial;
25984
26437
  this.#startOfficial = input.startOfficial;
25985
26438
  this.#listOfficial = input.listOfficial;
26439
+ this.#officialThreadCwd = input.officialThreadCwd;
25986
26440
  this.#activeOfficialParents = input.activeOfficialParents;
25987
26441
  }
26442
+ async listHarnesses() {
26443
+ return { harnesses: ["codex", ...this.#adapters.keys()] };
26444
+ }
25988
26445
  async inspect(input) {
25989
26446
  if (input.harnessId === "codex") return this.#inspectOfficial(input);
25990
26447
  const adapter = this.#adapters.get(input.harnessId);
@@ -25998,7 +26455,7 @@ var HarnessDelegationCoordinator = class {
25998
26455
  return {
25999
26456
  harnessId: input.harnessId,
26000
26457
  inspection: await adapter.inspect({
26001
- ...input.cwd ? { cwd: path6.resolve(input.cwd) } : {},
26458
+ ...input.cwd ? { cwd: path7.resolve(input.cwd) } : {},
26002
26459
  ...input.refresh !== void 0 ? { refresh: input.refresh } : {}
26003
26460
  })
26004
26461
  };
@@ -26006,7 +26463,13 @@ var HarnessDelegationCoordinator = class {
26006
26463
  async start(input) {
26007
26464
  validateStart(input);
26008
26465
  const parentThreadId = await this.#resolveParent(input.parentThreadId);
26009
- if (input.harnessId === "codex") return this.#startOfficial({ ...input, parentThreadId });
26466
+ const parent = await this.#parentMetadata(parentThreadId);
26467
+ const selectedCwd = input.cwd ?? parent.cwd ?? process.cwd();
26468
+ if (input.harnessId === "codex") {
26469
+ const result = await this.#startOfficial({ ...input, parentThreadId, cwd: selectedCwd });
26470
+ return { ...result, parentThreadId, cwd: result.cwd ?? selectedCwd };
26471
+ }
26472
+ const startInput = { ...input, parentThreadId, cwd: path7.resolve(selectedCwd) };
26010
26473
  if (!this.#adapters.has(input.harnessId)) {
26011
26474
  throw new DelegationControlError(
26012
26475
  "HARNESS_NOT_FOUND",
@@ -26015,7 +26478,7 @@ var HarnessDelegationCoordinator = class {
26015
26478
  );
26016
26479
  }
26017
26480
  const targetHarnessId = input.harnessId;
26018
- const digest2 = taskDigest(input);
26481
+ const digest2 = taskDigest(startInput);
26019
26482
  const duplicate = input.requestId ? await this.#repository.findDelegationByRequest(input.requestId) : await this.#repository.findRecentDelegation({
26020
26483
  parentHostThreadId: hostThreadIdSchema.parse(parentThreadId),
26021
26484
  targetHarnessId: harnessIdSchema.parse(targetHarnessId),
@@ -26042,21 +26505,20 @@ var HarnessDelegationCoordinator = class {
26042
26505
  if (input.model || input.thinkingOptionId) {
26043
26506
  const inspected = await this.inspect({
26044
26507
  harnessId: targetHarnessId,
26045
- cwd: input.cwd
26508
+ cwd: startInput.cwd
26046
26509
  });
26047
26510
  this.#validateConfiguration(inspected.inspection, input.model, input.thinkingOptionId);
26048
26511
  }
26049
- const parent = await this.#parentMetadata(parentThreadId);
26050
- const delegationId = hostThreadIdSchema.parse(randomUUID4());
26051
- const childThreadId = hostThreadIdSchema.parse(randomUUID4());
26052
- const turnId = hostTurnIdSchema.parse(randomUUID4());
26053
- const createRequestId = input.requestId ? `delegation:${input.requestId}` : randomUUID4();
26512
+ const delegationId = hostThreadIdSchema.parse(randomUUID5());
26513
+ const childThreadId = hostThreadIdSchema.parse(randomUUID5());
26514
+ const turnId = hostTurnIdSchema.parse(randomUUID5());
26515
+ const createRequestId = input.requestId ? `delegation:${input.requestId}` : randomUUID5();
26054
26516
  let record3 = await this.#repository.createProvisional(
26055
26517
  createExternalThreadRecordInput({
26056
26518
  hostThreadId: childThreadId,
26057
26519
  createRequestId,
26058
26520
  harnessId: harnessIdSchema.parse(targetHarnessId),
26059
- cwd: path6.resolve(input.cwd),
26521
+ cwd: startInput.cwd,
26060
26522
  title: input.task.trim().slice(0, 120),
26061
26523
  transportModelId: input.model || input.thinkingOptionId ? encodeExternalTransportSelection(targetHarnessId, {
26062
26524
  ...input.model ? { model: input.model } : {},
@@ -26127,17 +26589,21 @@ var HarnessDelegationCoordinator = class {
26127
26589
  }
26128
26590
  await this.#repository.setDelegationStatus(delegationId, "running");
26129
26591
  await this.#notifyThreadStarted(thread.thread);
26130
- return this.#result(delegationId, childThreadId, turnId, targetHarnessId, "running", {
26131
- requested: {
26132
- ...input.model ? { model: input.model } : {},
26133
- ...input.thinkingOptionId ? { thinkingOptionId: input.thinkingOptionId } : {}
26134
- },
26135
- effective: {
26136
- ...thread.stateObserver.state.effectiveModel ? { effectiveModel: thread.stateObserver.state.effectiveModel } : {},
26137
- ...thread.stateObserver.state.resolvedModelLabel ? { resolvedModelLabel: thread.stateObserver.state.resolvedModelLabel } : {},
26138
- ...thread.stateObserver.state.effectiveThinkingOptionId ? { effectiveThinkingOptionId: thread.stateObserver.state.effectiveThinkingOptionId } : {}
26139
- }
26140
- });
26592
+ return {
26593
+ ...this.#result(delegationId, childThreadId, turnId, targetHarnessId, "running", {
26594
+ requested: {
26595
+ ...input.model ? { model: input.model } : {},
26596
+ ...input.thinkingOptionId ? { thinkingOptionId: input.thinkingOptionId } : {}
26597
+ },
26598
+ effective: {
26599
+ ...thread.stateObserver.state.effectiveModel ? { effectiveModel: thread.stateObserver.state.effectiveModel } : {},
26600
+ ...thread.stateObserver.state.resolvedModelLabel ? { resolvedModelLabel: thread.stateObserver.state.resolvedModelLabel } : {},
26601
+ ...thread.stateObserver.state.effectiveThinkingOptionId ? { effectiveThinkingOptionId: thread.stateObserver.state.effectiveThinkingOptionId } : {}
26602
+ }
26603
+ }),
26604
+ cwd: record3.cwd,
26605
+ parentThreadId
26606
+ };
26141
26607
  } catch (error51) {
26142
26608
  if (session) await session.close().catch(() => void 0);
26143
26609
  this.#externalRuntime.remove(childThreadId);
@@ -26171,7 +26637,7 @@ var HarnessDelegationCoordinator = class {
26171
26637
  if (thread.running || thread.activeTurnId) {
26172
26638
  throw new DelegationControlError("THREAD_BUSY", "Thread already has an active Turn");
26173
26639
  }
26174
- const turnId = hostTurnIdSchema.parse(randomUUID4());
26640
+ const turnId = hostTurnIdSchema.parse(randomUUID5());
26175
26641
  try {
26176
26642
  await this.#startExternalTurn(thread, input.message, turnId);
26177
26643
  } catch (error51) {
@@ -26366,18 +26832,24 @@ var HarnessDelegationCoordinator = class {
26366
26832
  }
26367
26833
  async #parentMetadata(parentThreadId) {
26368
26834
  const record3 = await this.#repository.find(parentThreadId);
26369
- return { harnessId: record3 ? record3.harnessId : "codex" };
26835
+ if (record3) return { harnessId: record3.harnessId, cwd: record3.cwd };
26836
+ const cwd = await this.#officialThreadCwd(parentThreadId).catch(() => void 0);
26837
+ return { harnessId: "codex", ...cwd ? { cwd } : {} };
26370
26838
  }
26371
26839
  async #existingResult(delegation) {
26372
26840
  const record3 = await this.#repository.find(delegation.childHostThreadId);
26373
26841
  const turnId = record3?.turnMappings.at(-1)?.hostTurnId ?? "pending";
26374
- return this.#result(
26375
- delegation.delegationId,
26376
- delegation.childHostThreadId,
26377
- turnId,
26378
- delegation.targetHarnessId,
26379
- delegation.status
26380
- );
26842
+ return {
26843
+ ...this.#result(
26844
+ delegation.delegationId,
26845
+ delegation.childHostThreadId,
26846
+ turnId,
26847
+ delegation.targetHarnessId,
26848
+ delegation.status
26849
+ ),
26850
+ ...record3 ? { cwd: record3.cwd } : {},
26851
+ parentThreadId: delegation.parentHostThreadId
26852
+ };
26381
26853
  }
26382
26854
  #turnResult(threadId3, turnId, harnessId) {
26383
26855
  return {
@@ -26410,7 +26882,7 @@ var HarnessDelegationCoordinator = class {
26410
26882
 
26411
26883
  // packages/host-runtime/src/harness-plugin-loader.ts
26412
26884
  import { readdir as readdir3, realpath as realpath2 } from "node:fs/promises";
26413
- import path8 from "node:path";
26885
+ import path9 from "node:path";
26414
26886
  import { pathToFileURL } from "node:url";
26415
26887
 
26416
26888
  // packages/host-runtime/src/harness-plugin-registry.ts
@@ -26448,16 +26920,16 @@ var HarnessPluginRegistry = class {
26448
26920
 
26449
26921
  // packages/host-runtime/src/plugin-files.ts
26450
26922
  import { open as open4, realpath, stat as stat2 } from "node:fs/promises";
26451
- import path7 from "node:path";
26923
+ import path8 from "node:path";
26452
26924
  function inside(root, candidate) {
26453
- const relative = path7.relative(root, candidate);
26454
- return relative !== "" && !path7.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path7.sep}`);
26925
+ const relative = path8.relative(root, candidate);
26926
+ return relative !== "" && !path8.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path8.sep}`);
26455
26927
  }
26456
26928
  async function pluginResourcePath(root, relative) {
26457
- if (path7.isAbsolute(relative) || !inside(root, path7.resolve(root, relative))) {
26929
+ if (path8.isAbsolute(relative) || !inside(root, path8.resolve(root, relative))) {
26458
26930
  throw new Error("Plugin resource escapes its root");
26459
26931
  }
26460
- const resolved = await realpath(path7.resolve(root, relative));
26932
+ const resolved = await realpath(path8.resolve(root, relative));
26461
26933
  if (!inside(root, resolved) || !(await stat2(resolved)).isFile()) {
26462
26934
  throw new Error("Plugin resource must be a regular file inside its root");
26463
26935
  }
@@ -26602,7 +27074,7 @@ async function loadHarnessPlugins(options2) {
26602
27074
  const enabledIds = /* @__PURE__ */ new Set();
26603
27075
  const roots = /* @__PURE__ */ new Set();
26604
27076
  for (const configuredRoot of options2.roots) {
26605
- if (!path8.isAbsolute(configuredRoot)) {
27077
+ if (!path9.isAbsolute(configuredRoot)) {
26606
27078
  diagnose({ code: "invalidRoot" });
26607
27079
  continue;
26608
27080
  }
@@ -26632,7 +27104,7 @@ async function loadHarnessPlugins(options2) {
26632
27104
  for (const directory of directories) {
26633
27105
  try {
26634
27106
  const file2 = await pluginResourcePath(root, `${directory.name}/manifest.json`);
26635
- const pluginRoot = await realpath2(path8.join(root, directory.name));
27107
+ const pluginRoot = await realpath2(path9.join(root, directory.name));
26636
27108
  await pluginResourcePath(pluginRoot, "manifest.json");
26637
27109
  const manifest = harnessPluginManifestSchema.parse(
26638
27110
  JSON.parse(
@@ -26779,8 +27251,8 @@ function spawnOfficialAppServerConnection(input) {
26779
27251
 
26780
27252
  // packages/host-runtime/src/account/account-repository.ts
26781
27253
  import { mkdir as mkdir4, readFile as readFile5, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
26782
- import path9 from "node:path";
26783
- import { randomUUID as randomUUID5 } from "node:crypto";
27254
+ import path10 from "node:path";
27255
+ import { randomUUID as randomUUID6 } from "node:crypto";
26784
27256
  function validateAccountId(accountId) {
26785
27257
  if (!/^[A-Za-z0-9._~-]+$/u.test(accountId)) {
26786
27258
  throw new Error("Codex Account ID must be non-empty and filename-safe");
@@ -26798,19 +27270,19 @@ var AccountRepository = class {
26798
27270
  #mutationTail = Promise.resolve();
26799
27271
  #writeTail = Promise.resolve();
26800
27272
  constructor(input) {
26801
- this.#file = path9.join(path9.resolve(input.directory), "accounts.json");
27273
+ this.#file = path10.join(path10.resolve(input.directory), "accounts.json");
26802
27274
  this.#defaultAccount = {
26803
27275
  ...input.defaultAccount,
26804
27276
  label: input.defaultAccount.label ?? input.defaultAccount.accountId
26805
27277
  };
26806
27278
  validateAccountId(this.#defaultAccount.accountId);
26807
- if (!path9.isAbsolute(this.#defaultAccount.codexHome)) {
27279
+ if (!path10.isAbsolute(this.#defaultAccount.codexHome)) {
26808
27280
  throw new Error("Codex Account CODEX_HOME must be absolute");
26809
27281
  }
26810
27282
  }
26811
27283
  async initialize() {
26812
27284
  if (this.#initialized) return;
26813
- await mkdir4(path9.dirname(this.#file), { recursive: true });
27285
+ await mkdir4(path10.dirname(this.#file), { recursive: true });
26814
27286
  let stored = null;
26815
27287
  try {
26816
27288
  stored = JSON.parse(await readFile5(this.#file, "utf8"));
@@ -26878,7 +27350,7 @@ var AccountRepository = class {
26878
27350
  async upsert(input) {
26879
27351
  this.#requireInitialized();
26880
27352
  validateAccountId(input.accountId);
26881
- if (!path9.isAbsolute(input.codexHome))
27353
+ if (!path10.isAbsolute(input.codexHome))
26882
27354
  throw new Error("Codex Account CODEX_HOME must be absolute");
26883
27355
  return this.#mutate(async () => {
26884
27356
  const previous = this.#accounts.get(input.accountId);
@@ -26887,7 +27359,7 @@ var AccountRepository = class {
26887
27359
  const planType = input.planType === null ? void 0 : input.planType ?? previous?.planType;
26888
27360
  const account = {
26889
27361
  accountId: input.accountId,
26890
- codexHome: path9.normalize(input.codexHome),
27362
+ codexHome: path10.normalize(input.codexHome),
26891
27363
  ...email3 ? { email: email3 } : {},
26892
27364
  ...planType ? { planType } : {},
26893
27365
  label: input.label ?? previous?.label ?? input.accountId,
@@ -26901,7 +27373,7 @@ var AccountRepository = class {
26901
27373
  }
26902
27374
  #validateAccount(account) {
26903
27375
  validateAccountId(account.accountId);
26904
- if (!path9.isAbsolute(account.codexHome)) throw new Error("Stored CODEX_HOME must be absolute");
27376
+ if (!path10.isAbsolute(account.codexHome)) throw new Error("Stored CODEX_HOME must be absolute");
26905
27377
  if (account.email && (!account.email.includes("@") || account.email.length > 320)) {
26906
27378
  throw new Error("Stored Codex Account email is invalid");
26907
27379
  }
@@ -26922,7 +27394,7 @@ var AccountRepository = class {
26922
27394
  accounts: [...this.#accounts.values()]
26923
27395
  };
26924
27396
  const operation = this.#writeTail.then(async () => {
26925
- const temporary = `${this.#file}.${process.pid}.${randomUUID5()}.tmp`;
27397
+ const temporary = `${this.#file}.${process.pid}.${randomUUID6()}.tmp`;
26926
27398
  await writeFile3(temporary, `${JSON.stringify(value2, null, 2)}
26927
27399
  `, { mode: 384 });
26928
27400
  await rename3(temporary, this.#file);
@@ -26945,19 +27417,19 @@ function isMissingFile(error51) {
26945
27417
 
26946
27418
  // packages/host-runtime/src/account/thread-account-store.ts
26947
27419
  import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
26948
- import { randomUUID as randomUUID6 } from "node:crypto";
26949
- import path10 from "node:path";
27420
+ import { randomUUID as randomUUID7 } from "node:crypto";
27421
+ import path11 from "node:path";
26950
27422
  var ThreadAccountStore = class {
26951
27423
  #file;
26952
27424
  #bindings = /* @__PURE__ */ new Map();
26953
27425
  #initialized = false;
26954
27426
  #writeTail = Promise.resolve();
26955
27427
  constructor(input) {
26956
- this.#file = path10.join(path10.resolve(input.directory), "thread-accounts.json");
27428
+ this.#file = path11.join(path11.resolve(input.directory), "thread-accounts.json");
26957
27429
  }
26958
27430
  async initialize() {
26959
27431
  if (this.#initialized) return;
26960
- await mkdir5(path10.dirname(this.#file), { recursive: true });
27432
+ await mkdir5(path11.dirname(this.#file), { recursive: true });
26961
27433
  try {
26962
27434
  const stored = JSON.parse(await readFile6(this.#file, "utf8"));
26963
27435
  if (stored.formatVersion !== 1 || !stored.bindings || typeof stored.bindings !== "object") {
@@ -27016,7 +27488,7 @@ var ThreadAccountStore = class {
27016
27488
  bindings: Object.fromEntries(this.#bindings)
27017
27489
  };
27018
27490
  const operation = this.#writeTail.then(async () => {
27019
- const temporary = `${this.#file}.${process.pid}.${randomUUID6()}.tmp`;
27491
+ const temporary = `${this.#file}.${process.pid}.${randomUUID7()}.tmp`;
27020
27492
  await writeFile4(temporary, `${JSON.stringify(value2, null, 2)}
27021
27493
  `, { mode: 384 });
27022
27494
  await rename4(temporary, this.#file);
@@ -27033,7 +27505,7 @@ function isMissingFile2(error51) {
27033
27505
  import { mkdir as mkdir6 } from "node:fs/promises";
27034
27506
 
27035
27507
  // packages/host-runtime/src/official-request-broker.ts
27036
- import { randomUUID as randomUUID7 } from "node:crypto";
27508
+ import { randomUUID as randomUUID8 } from "node:crypto";
27037
27509
  var INTERNAL_REQUEST_PREFIX = "codexhost:official:";
27038
27510
  var MAX_RETIRED_IDS = 1024;
27039
27511
  function isRecord9(value2) {
@@ -27049,7 +27521,7 @@ var OfficialRequestBroker = class {
27049
27521
  constructor(input) {
27050
27522
  this.#send = input.send;
27051
27523
  this.#timeoutMs = input.timeoutMs ?? 3e4;
27052
- this.#nextId = input.nextId ?? (() => `${INTERNAL_REQUEST_PREFIX}${randomUUID7()}`);
27524
+ this.#nextId = input.nextId ?? (() => `${INTERNAL_REQUEST_PREFIX}${randomUUID8()}`);
27053
27525
  }
27054
27526
  get pendingCount() {
27055
27527
  return this.#pending.size;
@@ -27413,21 +27885,45 @@ function externalAnchor(entry) {
27413
27885
  if (entry.source !== "external") throw new Error("Expected an External Thread list entry");
27414
27886
  return { timestamp: entry.timestamp, threadId: threadId(entry.thread) };
27415
27887
  }
27416
- function includesExternalRecord(record3, query) {
27888
+ function includesExternalRecord(record3, query, byId) {
27417
27889
  if (record3.state !== "ready" || !record3.nativeSessionRef) return false;
27418
- if (record3.subagent) return false;
27890
+ let current = record3;
27891
+ const owners = /* @__PURE__ */ new Set();
27892
+ while (current.subagent) {
27893
+ if (owners.has(current.hostThreadId)) return false;
27894
+ owners.add(current.hostThreadId);
27895
+ const parent = byId.get(current.subagent.parentHostThreadId);
27896
+ if (!parent || parent.state !== "ready" || parent.harnessId !== current.harnessId || parent.nativeSessionRef?.nativeSessionId !== current.nativeSessionRef?.nativeSessionId)
27897
+ return false;
27898
+ current = parent;
27899
+ }
27900
+ const sourceKind = record3.subagent ? "subAgentThreadSpawn" : "vscode";
27901
+ const scoped = query.parentThreadId !== null || query.ancestorThreadId !== null;
27902
+ if (record3.subagent && !scoped && !query.sourceKinds?.some((kind) => kind === "subAgent" || kind === "subAgentThreadSpawn"))
27903
+ return false;
27904
+ if (query.parentThreadId !== null && record3.subagent?.parentHostThreadId !== query.parentThreadId)
27905
+ return false;
27906
+ if (query.ancestorThreadId !== null) {
27907
+ let parentId = record3.subagent?.parentHostThreadId;
27908
+ const visited = /* @__PURE__ */ new Set([record3.hostThreadId]);
27909
+ while (parentId && parentId !== query.ancestorThreadId && !visited.has(parentId)) {
27910
+ visited.add(parentId);
27911
+ parentId = byId.get(parentId)?.subagent?.parentHostThreadId;
27912
+ }
27913
+ if (record3.hostThreadId === query.ancestorThreadId || parentId !== query.ancestorThreadId)
27914
+ return false;
27915
+ }
27419
27916
  if (record3.archived !== query.archived) return false;
27420
27917
  if (query.cwd !== null && !query.cwd.includes(record3.cwd)) return false;
27421
27918
  if (query.modelProviders !== null && query.modelProviders.length > 0 && !query.modelProviders.includes("codexhost")) {
27422
27919
  return false;
27423
27920
  }
27424
- if (query.sourceKinds !== null && query.sourceKinds.length > 0 && !query.sourceKinds.includes("vscode")) {
27921
+ if (query.sourceKinds !== null && query.sourceKinds.length > 0 && !query.sourceKinds.includes(sourceKind) && !(record3.subagent && query.sourceKinds.includes("subAgent"))) {
27425
27922
  return false;
27426
27923
  }
27427
27924
  if (query.searchTerm !== null && !record3.title.toLowerCase().includes(query.searchTerm.toLowerCase())) {
27428
27925
  return false;
27429
27926
  }
27430
- if (query.parentThreadId !== null || query.ancestorThreadId !== null) return false;
27431
27927
  if (query.isPinned === true) return false;
27432
27928
  return true;
27433
27929
  }
@@ -27437,25 +27933,25 @@ function resolveExternalSessionTreeIds(records) {
27437
27933
  const resolve = (start) => {
27438
27934
  const cached2 = resolved.get(start.hostThreadId);
27439
27935
  if (cached2) return cached2;
27440
- const path25 = [];
27936
+ const path26 = [];
27441
27937
  const visited = /* @__PURE__ */ new Set();
27442
27938
  let current = start;
27443
27939
  while (true) {
27444
27940
  const known = resolved.get(current.hostThreadId);
27445
27941
  if (known) {
27446
- for (const record3 of path25) resolved.set(record3.hostThreadId, known);
27942
+ for (const record3 of path26) resolved.set(record3.hostThreadId, known);
27447
27943
  return known;
27448
27944
  }
27449
27945
  if (visited.has(current.hostThreadId)) {
27450
27946
  throw new Error("External Thread Fork tree contains a cycle");
27451
27947
  }
27452
27948
  visited.add(current.hostThreadId);
27453
- path25.push(current);
27454
- const sourceId = current.forkSource?.hostThreadId;
27949
+ path26.push(current);
27950
+ const sourceId = current.subagent?.parentHostThreadId ?? current.forkSource?.hostThreadId;
27455
27951
  const source = sourceId ? byId.get(sourceId) : void 0;
27456
27952
  if (!source) {
27457
27953
  const root = current.hostThreadId;
27458
- for (const record3 of path25) resolved.set(record3.hostThreadId, root);
27954
+ for (const record3 of path26) resolved.set(record3.hostThreadId, root);
27459
27955
  return root;
27460
27956
  }
27461
27957
  current = source;
@@ -27467,7 +27963,8 @@ function resolveExternalSessionTreeIds(records) {
27467
27963
  function listExternalThreadMetadata(input) {
27468
27964
  if (!input.query.supportsExternal) return { data: [], hasMore: false };
27469
27965
  const sessionIds = resolveExternalSessionTreeIds(input.records);
27470
- const entries = input.records.filter((record3) => includesExternalRecord(record3, input.query)).map((record3) => {
27966
+ const byId = new Map(input.records.map((record3) => [record3.hostThreadId, record3]));
27967
+ const entries = input.records.filter((record3) => includesExternalRecord(record3, input.query, byId)).map((record3) => {
27471
27968
  const runtime = input.runtimeFor(record3.hostThreadId);
27472
27969
  const sessionId = sessionIds.get(record3.hostThreadId);
27473
27970
  if (!sessionId) throw new Error("External Thread Session tree could not be resolved");
@@ -27549,6 +28046,7 @@ function threadId2(entry) {
27549
28046
  return entry.thread.id;
27550
28047
  }
27551
28048
  async function aggregateOfficialAccountThreadListPage(input) {
28049
+ const pageLimit = typeof input.params.limit === "number" ? input.params.limit : input.query.limit;
27552
28050
  const cursorValue2 = typeof input.params.cursor === "string" ? input.params.cursor : null;
27553
28051
  const cursor = cursorValue2 ? decodeCursor2(cursorValue2) : { accounts: initialAccounts(input.accountIds) };
27554
28052
  const sources = Object.entries(cursor.accounts).map(([accountId, state]) => ({
@@ -27585,7 +28083,7 @@ async function aggregateOfficialAccountThreadListPage(input) {
27585
28083
  if (source.done) return null;
27586
28084
  }
27587
28085
  source.batchStart = source.cursor;
27588
- const page = await request(source, source.cursor, Math.max(1, input.query.limit));
28086
+ const page = await request(source, source.cursor, Math.max(1, pageLimit));
27589
28087
  if (!source.requestedThisPage) {
27590
28088
  source.backwardsCursor = page.backwardsCursor;
27591
28089
  source.requestedThisPage = true;
@@ -27607,7 +28105,7 @@ async function aggregateOfficialAccountThreadListPage(input) {
27607
28105
  };
27608
28106
  const output = [];
27609
28107
  const emitted = /* @__PURE__ */ new Set();
27610
- while (output.length < input.query.limit) {
28108
+ while (output.length < pageLimit) {
27611
28109
  const candidates = await Promise.all(
27612
28110
  sources.map(async (source) => ({ source, entry: await ensure(source) }))
27613
28111
  );
@@ -28141,18 +28639,18 @@ var AppServerHost = class {
28141
28639
  };
28142
28640
  this.#writer = new OrderedWriter(this.#options.desktopOutput);
28143
28641
  const environment = this.#options.environment ?? process.env;
28144
- const dataDirectory = path11.resolve(
28145
- environment.CODEXHOST_DATA_DIR ?? path11.join(os2.homedir(), ".codexhost")
28642
+ const dataDirectory = path12.resolve(
28643
+ environment.CODEXHOST_DATA_DIR ?? path12.join(os2.homedir(), ".codexhost")
28146
28644
  );
28147
28645
  const accountRepository = options2.accountRepository ?? new AccountRepository({
28148
- directory: path11.join(dataDirectory, "codex-accounts"),
28646
+ directory: path12.join(dataDirectory, "codex-accounts"),
28149
28647
  defaultAccount: {
28150
28648
  accountId: "default",
28151
- codexHome: path11.resolve(environment.CODEX_HOME ?? path11.join(os2.homedir(), ".codex")),
28649
+ codexHome: path12.resolve(environment.CODEX_HOME ?? path12.join(os2.homedir(), ".codex")),
28152
28650
  label: "Default Codex Account"
28153
28651
  }
28154
28652
  });
28155
- const threadAccountStore = options2.threadAccountStore ?? new ThreadAccountStore({ directory: path11.join(dataDirectory, "codex-accounts") });
28653
+ const threadAccountStore = options2.threadAccountStore ?? new ThreadAccountStore({ directory: path12.join(dataDirectory, "codex-accounts") });
28156
28654
  this.#accountRepository = accountRepository;
28157
28655
  this.#threadAccountStore = threadAccountStore;
28158
28656
  this.#accountDataDirectory = dataDirectory;
@@ -28199,9 +28697,11 @@ var AppServerHost = class {
28199
28697
  cancelOfficial: (input) => this.#cancelOfficialDelegationThread(input),
28200
28698
  startOfficial: (input) => this.#startOfficialDelegation(input),
28201
28699
  listOfficial: (input) => this.#listDelegationThreads(input),
28700
+ officialThreadCwd: (threadId3) => this.#readOfficialThreadCwd(threadId3),
28202
28701
  activeOfficialParents: () => [...this.#activeOfficialTurns.keys()]
28203
28702
  });
28204
28703
  const unregisterDelegationApi = options2.onDelegationApi?.({
28704
+ listHarnesses: () => this.#delegationCoordinator.listHarnesses(),
28205
28705
  inspect: (input) => this.#delegationCoordinator.inspect(input),
28206
28706
  start: (input) => this.#delegationCoordinator.start(input),
28207
28707
  send: (input) => this.#delegationCoordinator.send(input),
@@ -28965,7 +29465,7 @@ var AppServerHost = class {
28965
29465
  throw new Error("Unknown Codex Account");
28966
29466
  const runtime = await this.#codexRuntimePool.get(params2.accountId);
28967
29467
  const response2 = await runtime.request("account/rateLimitResetCredit/consume", {
28968
- idempotencyKey: params2.idempotencyKey ?? randomUUID8()
29468
+ idempotencyKey: params2.idempotencyKey ?? randomUUID9()
28969
29469
  });
28970
29470
  if (isRecord12(response2.error)) {
28971
29471
  await this.#writer.json(rpcEnvelope(request, { error: response2.error }));
@@ -29012,10 +29512,10 @@ var AppServerHost = class {
29012
29512
  }
29013
29513
  if (request.method === "codexhost/account/create") {
29014
29514
  const params2 = codexAccountCreateParamsSchema.parse(requestObject(request));
29015
- const accountId = randomUUID8();
29515
+ const accountId = randomUUID9();
29016
29516
  const account = await this.#accountRepository.upsert({
29017
29517
  accountId,
29018
- codexHome: path11.join(this.#accountDataDirectory, "codex-homes", accountId),
29518
+ codexHome: path12.join(this.#accountDataDirectory, "codex-homes", accountId),
29019
29519
  label: params2.label ?? `Codex Account ${accountId.slice(0, 8)}`
29020
29520
  });
29021
29521
  const activeAccountId = await this.#accountRepository.getActiveAccountId();
@@ -29071,12 +29571,12 @@ var AppServerHost = class {
29071
29571
  for (const [key, session2] of this.#officialLoginSessions) {
29072
29572
  if (session2.accountId === params2.accountId) this.#officialLoginSessions.delete(key);
29073
29573
  }
29074
- const managedCodexHome = path11.join(
29574
+ const managedCodexHome = path12.join(
29075
29575
  this.#accountDataDirectory,
29076
29576
  "codex-homes",
29077
29577
  params2.accountId
29078
29578
  );
29079
- if (path11.resolve(account.codexHome) === path11.resolve(managedCodexHome)) {
29579
+ if (path12.resolve(account.codexHome) === path12.resolve(managedCodexHome)) {
29080
29580
  try {
29081
29581
  await rm4(managedCodexHome, { recursive: true, force: true });
29082
29582
  } catch (error51) {
@@ -29225,6 +29725,13 @@ var AppServerHost = class {
29225
29725
  ]);
29226
29726
  return thread !== null || childDelegation !== null || delegation !== null;
29227
29727
  }
29728
+ async #readOfficialThreadCwd(threadId3) {
29729
+ const response = await this.#requestOfficial("thread/read", { threadId: threadId3 });
29730
+ if (isRecord12(response.error)) return void 0;
29731
+ const result = isRecord12(response.result) ? response.result : null;
29732
+ const thread = result && isRecord12(result.thread) ? result.thread : null;
29733
+ return thread && typeof thread.cwd === "string" && thread.cwd.trim() ? thread.cwd : void 0;
29734
+ }
29228
29735
  async #inspectOfficialDelegationTarget(input) {
29229
29736
  const response = await this.#requestOfficial("model/list", {});
29230
29737
  if (isRecord12(response.error)) {
@@ -29293,7 +29800,7 @@ var AppServerHost = class {
29293
29800
  throw new DelegationControlError("INVALID_ARGUMENT", "Official Model Ref is invalid");
29294
29801
  }
29295
29802
  const nativeModelId = requestedModel ? decodeOfficialCodexModelRef(requestedModel) : void 0;
29296
- const digest2 = createHash5("sha256").update(
29803
+ const digest2 = createHash6("sha256").update(
29297
29804
  JSON.stringify({
29298
29805
  task: input.task,
29299
29806
  cwd: input.cwd,
@@ -29396,7 +29903,7 @@ var AppServerHost = class {
29396
29903
  throw error51;
29397
29904
  }
29398
29905
  this.#activeOfficialTurns.set(threadId3, turnId);
29399
- const delegationId = hostThreadIdSchema.parse(randomUUID8());
29906
+ const delegationId = hostThreadIdSchema.parse(randomUUID9());
29400
29907
  try {
29401
29908
  const source = await this.#repository.find(input.parentThreadId);
29402
29909
  const pendingTerminal = this.#pendingOfficialTerminalStatuses.get(threadId3);
@@ -29417,6 +29924,7 @@ var AppServerHost = class {
29417
29924
  harnessId: "codex",
29418
29925
  deepLink: `codex://threads/${threadId3}`,
29419
29926
  status: pendingTerminal ?? "running",
29927
+ cwd: thread && typeof thread.cwd === "string" ? thread.cwd : input.cwd,
29420
29928
  ...requestedModel || input.thinkingOptionId ? {
29421
29929
  configuration: {
29422
29930
  requested: {
@@ -29559,7 +30067,7 @@ var AppServerHost = class {
29559
30067
  async #listDelegationThreads(input) {
29560
30068
  const [sortKey, sortDirection2] = input.sort.split("-");
29561
30069
  const request = {
29562
- id: `codexhost:delegation-list:${randomUUID8()}`,
30070
+ id: `codexhost:delegation-list:${randomUUID9()}`,
29563
30071
  method: "thread/list",
29564
30072
  params: {
29565
30073
  cwd: input.cwd ? [input.cwd] : null,
@@ -29622,6 +30130,8 @@ var AppServerHost = class {
29622
30130
  records,
29623
30131
  runtimeFor: (threadId3) => {
29624
30132
  const thread = this.#externalRuntime.get(threadId3);
30133
+ const subagentStatus = this.#subagentThreadStatuses.get(threadId3);
30134
+ if (subagentStatus) return { running: subagentStatus === "active" };
29625
30135
  return thread ? { running: thread.running } : null;
29626
30136
  },
29627
30137
  requestOfficialPage: (params) => aggregateOfficialAccountThreadListPage({
@@ -30035,7 +30545,7 @@ var AppServerHost = class {
30035
30545
  );
30036
30546
  return;
30037
30547
  }
30038
- const turnId = requestedTurnId ?? hostTurnIdSchema.parse(randomUUID8());
30548
+ const turnId = requestedTurnId ?? hostTurnIdSchema.parse(randomUUID9());
30039
30549
  const projection = {
30040
30550
  projector: new CodexTurnProjector({
30041
30551
  threadId: thread.id,
@@ -30894,7 +31404,7 @@ var AppServerHost = class {
30894
31404
  if (thread.running || thread.activeTurnId || this.#pendingExternalCommandRequests.has(thread.id)) {
30895
31405
  throw new ExternalSteerError(-32072, "External Thread already has an active Turn");
30896
31406
  }
30897
- const turnId = hostTurnIdSchema.parse(randomUUID8());
31407
+ const turnId = hostTurnIdSchema.parse(randomUUID9());
30898
31408
  const startedAtMs = Date.now();
30899
31409
  const projection = {
30900
31410
  projector: new CodexTurnProjector({
@@ -31070,7 +31580,7 @@ var AppServerHost = class {
31070
31580
  if (event.type === "subagent.transcript.changed") {
31071
31581
  const nativeSubagentId = event.nativeSubagentId;
31072
31582
  const record3 = (await this.#repository.list()).find(
31073
- (candidate) => candidate.subagent?.parentHostThreadId === thread.id && candidate.subagent.nativeSubagentId === nativeSubagentId
31583
+ (candidate) => candidate.subagent?.parentHostThreadId === thread.id && candidate.subagent.nativeSubagentId === nativeSubagentId && candidate.nativeSessionRef?.nativeSessionId === thread.record.nativeSessionRef?.nativeSessionId
31074
31584
  );
31075
31585
  if (record3) await this.#refreshOpenSubagentThread(record3.hostThreadId, false);
31076
31586
  return;
@@ -31078,7 +31588,7 @@ var AppServerHost = class {
31078
31588
  if (event.type === "subagent.state.changed") {
31079
31589
  const nativeSubagentId = event.nativeSubagentId;
31080
31590
  const record3 = (await this.#repository.list()).find(
31081
- (candidate) => candidate.subagent?.parentHostThreadId === thread.id && candidate.subagent.nativeSubagentId === nativeSubagentId
31591
+ (candidate) => candidate.subagent?.parentHostThreadId === thread.id && candidate.subagent.nativeSubagentId === nativeSubagentId && candidate.nativeSessionRef?.nativeSessionId === thread.record.nativeSessionRef?.nativeSessionId
31082
31592
  );
31083
31593
  if (!record3) return;
31084
31594
  const status = event.status === "pending" || event.status === "running" ? "active" : "idle";
@@ -31182,33 +31692,13 @@ var AppServerHost = class {
31182
31692
  async #materializeSubagent(parent, subagent) {
31183
31693
  if (!subagent.nativeSubagentId || !parent.record.nativeSessionRef) return subagent;
31184
31694
  const status = subagent.status === "pending" || subagent.status === "running" ? "active" : "idle";
31185
- const records = await this.#repository.list();
31186
- const existing = records.find(
31187
- (record4) => record4.subagent?.parentHostThreadId === parent.id && record4.subagent.nativeSubagentId === subagent.nativeSubagentId
31188
- );
31189
- if (existing) {
31190
- this.#trackRunningSubagent(parent.id, existing.hostThreadId, status);
31191
- await this.#setSubagentThreadStatus(existing.hostThreadId, status);
31192
- return { ...subagent, subagentId: existing.hostThreadId };
31695
+ const record3 = await this.#repository.materializeSubagent(parent.record, subagent);
31696
+ if (!record3) return subagent;
31697
+ if (this.#subagentThreadStatuses.has(record3.hostThreadId)) {
31698
+ this.#trackRunningSubagent(parent.id, record3.hostThreadId, status);
31699
+ await this.#setSubagentThreadStatus(record3.hostThreadId, status);
31700
+ return { ...subagent, subagentId: record3.hostThreadId };
31193
31701
  }
31194
- const recordInput = createExternalThreadRecordInput({
31195
- harnessId: parent.record.harnessId,
31196
- cwd: parent.cwd,
31197
- title: subagent.description,
31198
- transportModelId: parent.transportModelId,
31199
- ephemeral: false,
31200
- historyMode: "paginated",
31201
- subagent: {
31202
- parentHostThreadId: parent.id,
31203
- nativeSubagentId: subagent.nativeSubagentId,
31204
- ...subagent.role ? { role: subagent.role } : {}
31205
- }
31206
- });
31207
- let record3 = await this.#repository.createProvisional(recordInput);
31208
- record3 = await this.#repository.commitNative(
31209
- record3.hostThreadId,
31210
- parent.record.nativeSessionRef
31211
- );
31212
31702
  const thread = externalThreadValue({
31213
31703
  record: record3,
31214
31704
  turns: [],
@@ -31452,7 +31942,7 @@ var AppServerHost = class {
31452
31942
  try {
31453
31943
  result = projection.projector.projectQuestion(
31454
31944
  interaction,
31455
- hostItemIdSchema.parse(randomUUID8())
31945
+ hostItemIdSchema.parse(randomUUID9())
31456
31946
  );
31457
31947
  } catch (error51) {
31458
31948
  this.#diagnose(error51);
@@ -31608,7 +32098,7 @@ var AppServerHost = class {
31608
32098
  };
31609
32099
 
31610
32100
  // packages/host-runtime/src/codex-runtime/account-official-listeners.ts
31611
- import path12 from "node:path";
32101
+ import path13 from "node:path";
31612
32102
  var AccountOfficialListeners = class {
31613
32103
  constructor(createListener) {
31614
32104
  this.createListener = createListener;
@@ -31619,7 +32109,7 @@ var AccountOfficialListeners = class {
31619
32109
  #closed = false;
31620
32110
  async endpoint(account) {
31621
32111
  if (this.#closed) throw new Error("Account official listeners are closed");
31622
- const key = path12.resolve(account.codexHome);
32112
+ const key = path13.resolve(account.codexHome);
31623
32113
  const existing = this.#starting.get(key);
31624
32114
  if (existing) return existing;
31625
32115
  const listener = this.createListener(account);
@@ -31667,6 +32157,12 @@ var DelegationControlRegistry = class {
31667
32157
  "Harness inspection requires exactly one active Host Runtime session"
31668
32158
  ).inspect(input);
31669
32159
  }
32160
+ async listHarnesses() {
32161
+ return only(
32162
+ [...this.#registrations],
32163
+ "Harness discovery requires exactly one active Host Runtime session"
32164
+ ).listHarnesses();
32165
+ }
31670
32166
  async start(input) {
31671
32167
  return (await this.#registrationForStart(input)).start(input);
31672
32168
  }
@@ -31749,17 +32245,17 @@ var DelegationControlRegistry = class {
31749
32245
 
31750
32246
  // packages/host-runtime/src/installed-harness-plugins.ts
31751
32247
  import os3 from "node:os";
31752
- import path14 from "node:path";
32248
+ import path15 from "node:path";
31753
32249
  import { fileURLToPath } from "node:url";
31754
32250
 
31755
32251
  // packages/host-runtime/src/launcher-url-opener.ts
31756
32252
  import { spawn as spawn3 } from "node:child_process";
31757
- import path13 from "node:path";
32253
+ import path14 from "node:path";
31758
32254
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
31759
32255
  var OPEN_TIMEOUT_MS = 1e4;
31760
32256
  function createLauncherUrlOpener(environment, spawnLauncher = (command, arguments_2, options2) => spawn3(command, arguments_2, options2)) {
31761
32257
  const launcher = environment.CODEXHOST_LAUNCHER_EXECUTABLE;
31762
- if (!launcher || !path13.isAbsolute(launcher)) return void 0;
32258
+ if (!launcher || !path14.isAbsolute(launcher)) return void 0;
31763
32259
  const launcherEnvironment = Object.fromEntries(
31764
32260
  Object.entries(environment).filter(([name]) => {
31765
32261
  const normalized = name.toUpperCase();
@@ -31817,9 +32313,9 @@ function installedHarnessPluginOptions(environment, managedRemoteHost = false, h
31817
32313
  const opener = managedRemoteHost ? void 0 : createLauncherUrlOpener(environment);
31818
32314
  return {
31819
32315
  pluginRoots: [
31820
- path14.join(path14.dirname(fileURLToPath(hostRuntimeUrl)), "plugins"),
31821
- environment[HARNESS_PLUGIN_DIRECTORY_ENV] ?? path14.join(
31822
- environment.CODEXHOST_DATA_DIR ? path14.resolve(environment.CODEXHOST_DATA_DIR) : path14.join(os3.homedir(), ".codexhost"),
32316
+ path15.join(path15.dirname(fileURLToPath(hostRuntimeUrl)), "plugins"),
32317
+ environment[HARNESS_PLUGIN_DIRECTORY_ENV] ?? path15.join(
32318
+ environment.CODEXHOST_DATA_DIR ? path15.resolve(environment.CODEXHOST_DATA_DIR) : path15.join(os3.homedir(), ".codexhost"),
31823
32319
  "plugins"
31824
32320
  )
31825
32321
  ],
@@ -31898,6 +32394,9 @@ async function startDelegationControlServer(input) {
31898
32394
  }
31899
32395
  const body = await jsonBody(request);
31900
32396
  switch (request.url) {
32397
+ case "/v1/harness/list":
32398
+ writeJson2(response, 200, await input.api.listHarnesses());
32399
+ return;
31901
32400
  case "/v1/harness/inspect":
31902
32401
  writeJson2(response, 200, await input.api.inspect(body));
31903
32402
  return;
@@ -31941,13 +32440,15 @@ async function startDelegationControlServer(input) {
31941
32440
  }
31942
32441
 
31943
32442
  // packages/host-runtime/src/delegation-skill.ts
31944
- import { createHash as createHash6, randomUUID as randomUUID9 } from "node:crypto";
32443
+ import { createHash as createHash7, randomUUID as randomUUID10 } from "node:crypto";
31945
32444
  import { mkdir as mkdir7, open as open5, readFile as readFile7, rename as rename5, rm as rm5, stat as stat3 } from "node:fs/promises";
31946
32445
  import os4 from "node:os";
31947
- import path15 from "node:path";
31948
- var SKILL_VERSION = 5;
31949
- var SKILL_RELATIVE_PATH = path15.join("skills", "codexhost-delegation", "SKILL.md");
32446
+ import path16 from "node:path";
32447
+ var SKILL_VERSION = 7;
32448
+ var SKILL_RELATIVE_PATH = path16.join("skills", "codexhost-delegation", "SKILL.md");
31950
32449
  var PREVIOUS_MANAGED_DIGESTS = [
32450
+ "9d2f491850fb0b4084a31ba9b5e4a550b5e833747af322090d8ed0ff80b88c30",
32451
+ "2bb0aebb9b06febbc6c0c0bcdb0b32506c7cdbf8dc3b734cc6b2a86621270e4e",
31951
32452
  "aff258622dc8ff321f32b15620d081e578cb9c9ed1134d6a57f35ca8e7762c0a",
31952
32453
  "ba509f57e5448e796b3dfdd5031dcb08672eded50b61c0a54de84cfa02c49dd3",
31953
32454
  "d3ddf6db9bc5c5df825479c885bbbf0ca08da66f7057a12e02e1fdf57525149e",
@@ -31971,21 +32472,13 @@ Before acting, run:
31971
32472
 
31972
32473
  \`codexhost delegate --help\`
31973
32474
 
31974
- Treat its output as the sole authoritative source for:
31975
-
31976
- - available commands;
31977
- - command parameters;
31978
- - available target Harness IDs;
31979
- - Thread identifier formats;
31980
- - waiting and reading behavior;
31981
- - response fields;
31982
- - errors and recovery guidance.
31983
-
31984
- Do not construct commands, parameters, or Harness IDs from memory.
32475
+ Use CLI help as the authoritative source for commands and behavior. Consult
32476
+ command-specific help for options and the Harness listing command when the
32477
+ target is unknown. Prefer compact output when supported, and use its task links
32478
+ directly for subsequent commands.
31985
32479
 
31986
- When the user asks for a specific Model or Thinking level, inspect the target
31987
- Harness first and use the exact opaque IDs returned by the authoritative CLI.
31988
- When they do not specify either setting, omit it so the target keeps its default.
32480
+ Use the Harness native defaults. Inspect the target when a Model or Thinking
32481
+ selection is needed or the default is unavailable.
31989
32482
 
31990
32483
  For a new delegation, create an independent child session and submit the
31991
32484
  requested task. For an existing external session, resolve the target from the
@@ -32004,22 +32497,12 @@ user’s request and the task:
32004
32497
  - check it again later;
32005
32498
  - leave it running in the background.
32006
32499
 
32007
- When the result is needed, explicitly read the target Thread. Report only the
32008
- visible result returned by that Thread.
32009
-
32010
- Provide the user with the necessary tracking information available from the
32011
- CLI; omit unavailable fields rather than inventing them:
32012
-
32013
- - target agent;
32014
- - \`delegationId\`;
32015
- - \`threadId\`;
32016
- - \`turnId\`;
32017
- - \`deepLink\`;
32018
- - current or final status.
32500
+ Report the result returned by read or a completed wait, together with the target
32501
+ agent, status, and a labeled task link. Keep internal tracking IDs in tool calls.
32019
32502
  `;
32020
- var CURRENT_DIGEST = createHash6("sha256").update(CODEXHOST_DELEGATION_SKILL).digest("hex");
32503
+ var CURRENT_DIGEST = createHash7("sha256").update(CODEXHOST_DELEGATION_SKILL).digest("hex");
32021
32504
  function digest(value2) {
32022
- return createHash6("sha256").update(value2).digest("hex");
32505
+ return createHash7("sha256").update(value2).digest("hex");
32023
32506
  }
32024
32507
  function managedVersion(value2) {
32025
32508
  const match = /^version:\s*(\d+)\s*$/mu.exec(value2);
@@ -32034,8 +32517,8 @@ async function readOptional(filePath) {
32034
32517
  }
32035
32518
  }
32036
32519
  async function atomicWrite(filePath, content) {
32037
- await mkdir7(path15.dirname(filePath), { recursive: true, mode: 448 });
32038
- const temporaryPath = path15.join(path15.dirname(filePath), `.SKILL.md.${randomUUID9()}.tmp`);
32520
+ await mkdir7(path16.dirname(filePath), { recursive: true, mode: 448 });
32521
+ const temporaryPath = path16.join(path16.dirname(filePath), `.SKILL.md.${randomUUID10()}.tmp`);
32039
32522
  const handle = await open5(temporaryPath, "wx", 384);
32040
32523
  try {
32041
32524
  await handle.writeFile(content, "utf8");
@@ -32052,8 +32535,8 @@ async function atomicWrite(filePath, content) {
32052
32535
  async function installDelegationSkills(input = {}) {
32053
32536
  const home = input.homeDirectory ?? os4.homedir();
32054
32537
  const destinations = [
32055
- path15.join(home, ".agents", SKILL_RELATIVE_PATH),
32056
- path15.join(home, ".claude", SKILL_RELATIVE_PATH)
32538
+ path16.join(home, ".agents", SKILL_RELATIVE_PATH),
32539
+ path16.join(home, ".claude", SKILL_RELATIVE_PATH)
32057
32540
  ];
32058
32541
  const knownDigests = /* @__PURE__ */ new Set([
32059
32542
  CURRENT_DIGEST,
@@ -32118,9 +32601,9 @@ async function installDelegationSkills(input = {}) {
32118
32601
  }
32119
32602
 
32120
32603
  // packages/host-runtime/src/remote-control-app-server.ts
32121
- import { randomUUID as randomUUID10 } from "node:crypto";
32604
+ import { randomUUID as randomUUID11 } from "node:crypto";
32122
32605
  import { mkdir as mkdir8, open as open6, rename as rename6, rm as rm6 } from "node:fs/promises";
32123
- import path16 from "node:path";
32606
+ import path17 from "node:path";
32124
32607
 
32125
32608
  // packages/host-runtime/src/remote-official-connection.ts
32126
32609
  import net from "node:net";
@@ -32310,30 +32793,30 @@ var BRIDGE_READY_METHOD = "codexhost/remote-control-bridge/ready";
32310
32793
  function absoluteEnvironmentPath2(environment, name, fallback, platform = process.platform) {
32311
32794
  const value2 = environment[name] ?? fallback;
32312
32795
  if (!value2) return null;
32313
- if (path16.isAbsolute(value2)) return path16.normalize(value2);
32314
- return platform === "win32" && path16.win32.isAbsolute(value2) ? path16.win32.normalize(value2) : null;
32796
+ if (path17.isAbsolute(value2)) return path17.normalize(value2);
32797
+ return platform === "win32" && path17.win32.isAbsolute(value2) ? path17.win32.normalize(value2) : null;
32315
32798
  }
32316
32799
  function nodeCompatibleWindowsPath(value2) {
32317
32800
  if (value2.startsWith("\\\\?\\UNC\\")) return `\\\\${value2.slice(8)}`;
32318
32801
  if (value2.startsWith("\\\\?\\")) return value2.slice(4);
32319
32802
  return value2;
32320
32803
  }
32321
- function remoteControlBridgePipePath(processId = process.pid, instanceId = randomUUID10()) {
32804
+ function remoteControlBridgePipePath(processId = process.pid, instanceId = randomUUID11()) {
32322
32805
  const safeInstance = instanceId.replaceAll(/[^a-zA-Z0-9-]/gu, "");
32323
32806
  if (!safeInstance) throw new Error("Remote Control bridge instance ID is invalid");
32324
32807
  return `\\\\.\\pipe\\codexhost-remote-control-${processId}-${safeInstance}`;
32325
32808
  }
32326
32809
  function remoteControlBridgeDescriptorPath(environment) {
32327
32810
  const root = environment.LOCALAPPDATA;
32328
- if (!root || !path16.isAbsolute(root)) return null;
32329
- return path16.join(path16.normalize(root), "codexhost", REMOTE_CONTROL_BRIDGE_DESCRIPTOR_FILE);
32811
+ if (!root || !path17.isAbsolute(root)) return null;
32812
+ return path17.join(path17.normalize(root), "codexhost", REMOTE_CONTROL_BRIDGE_DESCRIPTOR_FILE);
32330
32813
  }
32331
32814
  async function publishRemoteControlAppServerDescriptor(plan) {
32332
- const directory = path16.dirname(plan.descriptorPath);
32815
+ const directory = path17.dirname(plan.descriptorPath);
32333
32816
  await mkdir8(directory, { recursive: true, mode: 448 });
32334
- const temporaryPath = path16.join(
32817
+ const temporaryPath = path17.join(
32335
32818
  directory,
32336
- `.${REMOTE_CONTROL_BRIDGE_DESCRIPTOR_FILE}.${plan.descriptor.ownerPid}.${randomUUID10()}.tmp`
32819
+ `.${REMOTE_CONTROL_BRIDGE_DESCRIPTOR_FILE}.${plan.descriptor.ownerPid}.${randomUUID11()}.tmp`
32337
32820
  );
32338
32821
  const handle = await open6(temporaryPath, "wx", 384);
32339
32822
  try {
@@ -32424,14 +32907,14 @@ async function runRemoteControlAppServerBridge(input = {}) {
32424
32907
  import { createServer as createServer2 } from "node:http";
32425
32908
  import net2 from "node:net";
32426
32909
  import { chmod as chmod3, lstat as lstat6, mkdir as mkdir10, rm as rm8 } from "node:fs/promises";
32427
- import path18 from "node:path";
32910
+ import path19 from "node:path";
32428
32911
  import { PassThrough as PassThrough2 } from "node:stream";
32429
32912
 
32430
32913
  // packages/host-runtime/src/remote-socket-lock.ts
32431
- import { randomUUID as randomUUID11 } from "node:crypto";
32914
+ import { randomUUID as randomUUID12 } from "node:crypto";
32432
32915
  import { chmod as chmod2, lstat as lstat5, mkdir as mkdir9, open as open7, readFile as readFile8, readdir as readdir4, rename as rename7, rm as rm7 } from "node:fs/promises";
32433
32916
  import { uptime } from "node:os";
32434
- import path17 from "node:path";
32917
+ import path18 from "node:path";
32435
32918
  import { setTimeout as delay4 } from "node:timers/promises";
32436
32919
  var LOCK_RETRY_COUNT = 200;
32437
32920
  var LOCK_RETRY_DELAY_MS = 25;
@@ -32505,7 +32988,7 @@ async function readLockEntrySnapshot(filePath) {
32505
32988
  source,
32506
32989
  identity: { dev: metadata.dev, ino: metadata.ino },
32507
32990
  mtimeMs: metadata.mtimeMs,
32508
- record: record3 && path17.basename(filePath) === lockEntryName(record3.ownerToken) ? record3 : null
32991
+ record: record3 && path18.basename(filePath) === lockEntryName(record3.ownerToken) ? record3 : null
32509
32992
  };
32510
32993
  }
32511
32994
  function lockEntryIsAbandoned(snapshot) {
@@ -32526,7 +33009,7 @@ async function readLockEntryCatalog(lockDirectory) {
32526
33009
  let unsettled = false;
32527
33010
  for (const name of names) {
32528
33011
  if (!name.startsWith(LOCK_ENTRY_PREFIX) || !name.endsWith(LOCK_ENTRY_SUFFIX)) continue;
32529
- const snapshot = await readLockEntrySnapshot(path17.join(lockDirectory, name));
33012
+ const snapshot = await readLockEntrySnapshot(path18.join(lockDirectory, name));
32530
33013
  if (snapshot === null) continue;
32531
33014
  if (lockEntryIsAbandoned(snapshot)) {
32532
33015
  if (!await removeAbandonedLockEntry(snapshot)) unsettled = true;
@@ -32554,7 +33037,7 @@ async function preparePrivateLockDirectory(lockDirectory) {
32554
33037
  await chmod2(lockDirectory, 448);
32555
33038
  }
32556
33039
  async function writeLockRecordAtomic(lockDirectory, entryPath, record3) {
32557
- const temporary = path17.join(lockDirectory, `.tmp-${record3.ownerToken}-${randomUUID11()}`);
33040
+ const temporary = path18.join(lockDirectory, `.tmp-${record3.ownerToken}-${randomUUID12()}`);
32558
33041
  const handle = await open7(temporary, "wx", 384);
32559
33042
  try {
32560
33043
  await handle.writeFile(`${JSON.stringify(record3)}
@@ -32629,8 +33112,8 @@ async function withRemoteAppServerSocketInitializationLock(socketPath, action) {
32629
33112
  const legacyPath = `${socketPath}.initializing`;
32630
33113
  const lockDirectory = `${socketPath}.initializers`;
32631
33114
  await preparePrivateLockDirectory(lockDirectory);
32632
- const ownerToken = randomUUID11();
32633
- const entryPath = path17.join(lockDirectory, lockEntryName(ownerToken));
33115
+ const ownerToken = randomUUID12();
33116
+ const entryPath = path18.join(lockDirectory, lockEntryName(ownerToken));
32634
33117
  const baseRecord = {
32635
33118
  version: 2,
32636
33119
  ownerToken,
@@ -32804,14 +33287,14 @@ function remoteAppServerSocketPath(environment, listenUrl = "unix://") {
32804
33287
  throw new Error("Remote app-server listener must use a Unix URL");
32805
33288
  }
32806
33289
  const explicit = listenUrl.slice("unix://".length);
32807
- if (explicit.length > 0) return path18.posix.resolve(decodeURIComponent(explicit));
32808
- const codexHome = environment.CODEX_HOME ?? (environment.HOME ? path18.posix.join(environment.HOME, ".codex") : void 0);
33290
+ if (explicit.length > 0) return path19.posix.resolve(decodeURIComponent(explicit));
33291
+ const codexHome = environment.CODEX_HOME ?? (environment.HOME ? path19.posix.join(environment.HOME, ".codex") : void 0);
32809
33292
  if (!codexHome)
32810
33293
  throw new Error("CODEX_HOME or HOME is required for the remote app-server socket");
32811
- return path18.posix.join(codexHome, "app-server-control", "app-server-control.sock");
33294
+ return path19.posix.join(codexHome, "app-server-control", "app-server-control.sock");
32812
33295
  }
32813
33296
  function officialListenerArgumentsForRemoteListener(arguments_2, socketPath) {
32814
- if (!path18.posix.isAbsolute(socketPath)) {
33297
+ if (!path19.posix.isAbsolute(socketPath)) {
32815
33298
  throw new Error("Shared official app-server socket path must be absolute");
32816
33299
  }
32817
33300
  if (remoteUnixListenerUrl(arguments_2) === null) {
@@ -32922,7 +33405,7 @@ async function removeStaleSocket(socketPath) {
32922
33405
  await rm8(socketPath, { force: true });
32923
33406
  }
32924
33407
  async function prepareRemoteAppServerSocketDirectory(socketPath) {
32925
- const socketDirectory = path18.dirname(socketPath);
33408
+ const socketDirectory = path19.dirname(socketPath);
32926
33409
  const existing = await lstat6(socketDirectory).catch((error51) => {
32927
33410
  if (error51.code === "ENOENT") return null;
32928
33411
  throw error51;
@@ -33127,20 +33610,20 @@ function createRemoteAppServerWebSocketListener(input) {
33127
33610
 
33128
33611
  // packages/host-runtime/src/remote-official-app-server.ts
33129
33612
  import { spawn as spawn4 } from "node:child_process";
33130
- import { randomUUID as randomUUID12 } from "node:crypto";
33613
+ import { randomUUID as randomUUID13 } from "node:crypto";
33131
33614
  import { lstat as lstat7, rm as rm9 } from "node:fs/promises";
33132
- import path19 from "node:path";
33615
+ import path20 from "node:path";
33133
33616
  var DEFAULT_CLOSE_TIMEOUT_MS = 2e3;
33134
33617
  var DEFAULT_LISTEN_TIMEOUT_MS = 1e4;
33135
- function remoteOfficialAppServerSocketPath(desktopControlSocketPath, token = randomUUID12()) {
33136
- if (!path19.posix.isAbsolute(desktopControlSocketPath)) {
33618
+ function remoteOfficialAppServerSocketPath(desktopControlSocketPath, token = randomUUID13()) {
33619
+ if (!path20.posix.isAbsolute(desktopControlSocketPath)) {
33137
33620
  throw new Error("Desktop control socket path must be absolute");
33138
33621
  }
33139
33622
  if (!/^[A-Za-z0-9-]+$/u.test(token) || !/[A-Za-z0-9]/u.test(token)) {
33140
33623
  throw new Error("Shared official app-server socket token is invalid");
33141
33624
  }
33142
33625
  const compactToken = token.replaceAll("-", "").slice(0, 15);
33143
- return path19.posix.join(path19.posix.dirname(desktopControlSocketPath), `.c-${compactToken}.sock`);
33626
+ return path20.posix.join(path20.posix.dirname(desktopControlSocketPath), `.c-${compactToken}.sock`);
33144
33627
  }
33145
33628
  function errorMessage5(error51) {
33146
33629
  return error51 instanceof Error ? error51.message : String(error51);
@@ -33431,7 +33914,16 @@ function createHostUpdateCoordinator(options2) {
33431
33914
  });
33432
33915
  return contextPromise;
33433
33916
  };
33434
- const fetchLatest = options2.fetchLatest ?? ((signal) => fetchLatestGitHubRelease({ signal: signal ?? AbortSignal.timeout(15e3) }));
33917
+ const fetchLatest = options2.fetchLatest ?? (async (signal) => {
33918
+ const timeoutSignal = AbortSignal.timeout(15e3);
33919
+ const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
33920
+ const authenticated = await fetchLatestGitHubReleaseWithGitHubCli({
33921
+ ...options2.environment ? { environment: options2.environment } : {},
33922
+ platform,
33923
+ signal: requestSignal
33924
+ });
33925
+ return authenticated ?? fetchLatestGitHubRelease({ signal: requestSignal });
33926
+ });
33435
33927
  let candidate = null;
33436
33928
  async function latestStatus(context) {
33437
33929
  const discovered = await discoverLatestUpdateStatus(context.common.stateDirectory);
@@ -33607,9 +34099,9 @@ function hasLauncherManagedUpdateRuntime(environment, hostRuntimePath) {
33607
34099
  if (!environment[UPDATE_RUNTIME_ENV.launcherPid]) return false;
33608
34100
  const npmPackageRoot = environment[UPDATE_RUNTIME_ENV.npmPackageRoot];
33609
34101
  if (!npmPackageRoot || !hostRuntimePath) return true;
33610
- if (!path20.isAbsolute(npmPackageRoot) || !path20.isAbsolute(hostRuntimePath)) return false;
33611
- const runtimePackageRoot = path20.dirname(path20.dirname(path20.normalize(hostRuntimePath)));
33612
- return path20.relative(path20.normalize(npmPackageRoot), runtimePackageRoot) === "";
34102
+ if (!path21.isAbsolute(npmPackageRoot) || !path21.isAbsolute(hostRuntimePath)) return false;
34103
+ const runtimePackageRoot = path21.dirname(path21.dirname(path21.normalize(hostRuntimePath)));
34104
+ return path21.relative(path21.normalize(npmPackageRoot), runtimePackageRoot) === "";
33613
34105
  }
33614
34106
  function requiredRuntimeConfiguration(environment) {
33615
34107
  const stockCodexPath = environment[STOCK_CODEX_PATH_ENV];
@@ -33736,8 +34228,8 @@ async function runHostRuntime(input) {
33736
34228
  });
33737
34229
  try {
33738
34230
  await officialListeners.endpoint({
33739
- codexHome: path20.resolve(
33740
- delegationEnvironment.CODEX_HOME ?? path20.join(homedir(), ".codex")
34231
+ codexHome: path21.resolve(
34232
+ delegationEnvironment.CODEX_HOME ?? path21.join(homedir(), ".codex")
33741
34233
  )
33742
34234
  });
33743
34235
  await listener.listen();
@@ -33838,7 +34330,7 @@ async function runHostRuntime(input) {
33838
34330
  }
33839
34331
 
33840
34332
  // packages/host-runtime/src/remote-host-install.ts
33841
- import { createHash as createHash7, randomUUID as randomUUID13 } from "node:crypto";
34333
+ import { createHash as createHash8, randomUUID as randomUUID14 } from "node:crypto";
33842
34334
  import { constants as fsConstants } from "node:fs";
33843
34335
  import {
33844
34336
  access,
@@ -33853,7 +34345,7 @@ import {
33853
34345
  stat as stat4,
33854
34346
  writeFile as writeFile5
33855
34347
  } from "node:fs/promises";
33856
- import path21 from "node:path";
34348
+ import path22 from "node:path";
33857
34349
  var MANIFEST_FORMAT = 1;
33858
34350
  var WRAPPER_MARKER = "# codexhost remote SSH wrapper v1";
33859
34351
  var PROFILE_START = "# >>> codexhost remote SSH >>>";
@@ -33864,25 +34356,25 @@ function resolvePaths(options2) {
33864
34356
  if (!configuredHome) {
33865
34357
  throw new Error("A non-root HOME is required for remote Host installation");
33866
34358
  }
33867
- const home = path21.resolve(configuredHome);
33868
- if (!home || home === path21.parse(home).root) {
34359
+ const home = path22.resolve(configuredHome);
34360
+ if (!home || home === path22.parse(home).root) {
33869
34361
  throw new Error("A non-root HOME is required for remote Host installation");
33870
34362
  }
33871
- const installRoot = path21.resolve(options2.installRoot ?? path21.join(home, ".codexhost", "remote"));
33872
- const profilePath = path21.resolve(
33873
- options2.profilePath ?? path21.join(
34363
+ const installRoot = path22.resolve(options2.installRoot ?? path22.join(home, ".codexhost", "remote"));
34364
+ const profilePath = path22.resolve(
34365
+ options2.profilePath ?? path22.join(
33874
34366
  home,
33875
- path21.basename(environment.SHELL ?? "") === "zsh" ? ".zshenv" : path21.basename(environment.SHELL ?? "") === "bash" ? ".bashrc" : ".profile"
34367
+ path22.basename(environment.SHELL ?? "") === "zsh" ? ".zshenv" : path22.basename(environment.SHELL ?? "") === "bash" ? ".bashrc" : ".profile"
33876
34368
  )
33877
34369
  );
33878
- const binDirectory = path21.join(installRoot, "bin");
34370
+ const binDirectory = path22.join(installRoot, "bin");
33879
34371
  return {
33880
34372
  home,
33881
34373
  installRoot,
33882
34374
  binDirectory,
33883
- wrapperPath: path21.join(binDirectory, "codex"),
33884
- manifestPath: path21.join(installRoot, "manifest.json"),
33885
- dataDirectory: path21.join(installRoot, "data"),
34375
+ wrapperPath: path22.join(binDirectory, "codex"),
34376
+ manifestPath: path22.join(installRoot, "manifest.json"),
34377
+ dataDirectory: path22.join(installRoot, "data"),
33886
34378
  profilePath
33887
34379
  };
33888
34380
  }
@@ -33898,10 +34390,10 @@ async function existingText(filePath) {
33898
34390
  }
33899
34391
  }
33900
34392
  async function fileSha256(filePath) {
33901
- return createHash7("sha256").update(await readFile9(filePath)).digest("hex");
34393
+ return createHash8("sha256").update(await readFile9(filePath)).digest("hex");
33902
34394
  }
33903
34395
  async function executable(filePath, label) {
33904
- const absolute = path21.resolve(filePath);
34396
+ const absolute = path22.resolve(filePath);
33905
34397
  try {
33906
34398
  if (!(await stat4(absolute)).isFile()) throw new Error("not a regular file");
33907
34399
  await access(absolute, fsConstants.X_OK);
@@ -33911,7 +34403,7 @@ async function executable(filePath, label) {
33911
34403
  return absolute;
33912
34404
  }
33913
34405
  async function existingFile(filePath, label) {
33914
- const absolute = path21.resolve(filePath);
34406
+ const absolute = path22.resolve(filePath);
33915
34407
  try {
33916
34408
  const metadata = await stat4(absolute);
33917
34409
  if (!metadata.isFile()) throw new Error("not a regular file");
@@ -33921,9 +34413,9 @@ async function existingFile(filePath, label) {
33921
34413
  return absolute;
33922
34414
  }
33923
34415
  async function discoverExecutable(name, environment) {
33924
- for (const directory of (environment.PATH ?? "").split(path21.delimiter)) {
34416
+ for (const directory of (environment.PATH ?? "").split(path22.delimiter)) {
33925
34417
  if (!directory) continue;
33926
- const candidate = path21.resolve(directory, name);
34418
+ const candidate = path22.resolve(directory, name);
33927
34419
  try {
33928
34420
  await access(candidate, fsConstants.X_OK);
33929
34421
  return candidate;
@@ -33934,10 +34426,10 @@ async function discoverExecutable(name, environment) {
33934
34426
  return null;
33935
34427
  }
33936
34428
  async function writeAtomic(filePath, contents, mode) {
33937
- await mkdir11(path21.dirname(filePath), { recursive: true, mode: 448 });
33938
- const temporary = path21.join(
33939
- path21.dirname(filePath),
33940
- `.${path21.basename(filePath)}.${process.pid}.${randomUUID13()}.tmp`
34429
+ await mkdir11(path22.dirname(filePath), { recursive: true, mode: 448 });
34430
+ const temporary = path22.join(
34431
+ path22.dirname(filePath),
34432
+ `.${path22.basename(filePath)}.${process.pid}.${randomUUID14()}.tmp`
33941
34433
  );
33942
34434
  try {
33943
34435
  if (typeof contents === "string") {
@@ -33958,10 +34450,10 @@ async function writeAtomic(filePath, contents, mode) {
33958
34450
  }
33959
34451
  }
33960
34452
  async function writeAtomicExecutable(filePath, sourcePath) {
33961
- await mkdir11(path21.dirname(filePath), { recursive: true, mode: 448 });
33962
- const temporary = path21.join(
33963
- path21.dirname(filePath),
33964
- `.${path21.basename(filePath)}.${process.pid}.${randomUUID13()}.tmp`
34453
+ await mkdir11(path22.dirname(filePath), { recursive: true, mode: 448 });
34454
+ const temporary = path22.join(
34455
+ path22.dirname(filePath),
34456
+ `.${path22.basename(filePath)}.${process.pid}.${randomUUID14()}.tmp`
33965
34457
  );
33966
34458
  try {
33967
34459
  await copyFile3(sourcePath, temporary);
@@ -34002,8 +34494,8 @@ function removeManagedProfileBlock(contents) {
34002
34494
  function installManagedProfileBlock(contents, manifest) {
34003
34495
  const base = removeManagedProfileBlock(contents);
34004
34496
  const environment = [
34005
- `export CODEX_INSTALL_DIR=${shellQuote(path21.dirname(manifest.wrapperPath))}`,
34006
- `export PATH=${shellQuote(path21.dirname(manifest.wrapperPath))}:${shellQuote(path21.dirname(manifest.nodePath))}:${shellQuote(path21.dirname(manifest.stockCodexPath))}:"\${PATH:-/usr/local/bin:/usr/bin:/bin}"`,
34497
+ `export CODEX_INSTALL_DIR=${shellQuote(path22.dirname(manifest.wrapperPath))}`,
34498
+ `export PATH=${shellQuote(path22.dirname(manifest.wrapperPath))}:${shellQuote(path22.dirname(manifest.nodePath))}:${shellQuote(path22.dirname(manifest.stockCodexPath))}:"\${PATH:-/usr/local/bin:/usr/bin:/bin}"`,
34007
34499
  `export CODEXHOST_STOCK_CODEX_PATH=${shellQuote(manifest.stockCodexPath)}`,
34008
34500
  `export CODEXHOST_HOST_NODE_PATH=${shellQuote(manifest.nodePath)}`,
34009
34501
  `export CODEXHOST_HOST_RUNTIME_PATH=${shellQuote(manifest.hostRuntimePath)}`,
@@ -34020,7 +34512,7 @@ function installManagedProfileBlock(contents, manifest) {
34020
34512
  PROFILE_END,
34021
34513
  ""
34022
34514
  ].join("\n");
34023
- if (path21.basename(manifest.profilePath) === ".bashrc") return `${block}${base}`;
34515
+ if (path22.basename(manifest.profilePath) === ".bashrc") return `${block}${base}`;
34024
34516
  const separator = base.length > 0 && !base.endsWith("\n") ? "\n" : "";
34025
34517
  return `${base}${separator}${block}`;
34026
34518
  }
@@ -34039,7 +34531,7 @@ ${WRAPPER_MARKER}
34039
34531
  return "legacy-wrapper";
34040
34532
  }
34041
34533
  if (manifest.entrypointSha256 !== void 0) {
34042
- return createHash7("sha256").update(entrypoint).digest("hex") === manifest.entrypointSha256 ? "native" : "modified";
34534
+ return createHash8("sha256").update(entrypoint).digest("hex") === manifest.entrypointSha256 ? "native" : "modified";
34043
34535
  }
34044
34536
  const shim = await readFile9(manifest.shimPath).catch((error51) => {
34045
34537
  if (error51.code === "ENOENT") return null;
@@ -34085,9 +34577,9 @@ async function readManifest(filePath) {
34085
34577
  ];
34086
34578
  const optionalPaths = ["claudeCommand", "profileBackupPath"];
34087
34579
  if (manifest.format !== MANIFEST_FORMAT || Object.keys(manifest).some((key) => !allowed.has(key)) || requiredPaths.some(
34088
- (key) => typeof manifest[key] !== "string" || !path21.isAbsolute(manifest[key])
34580
+ (key) => typeof manifest[key] !== "string" || !path22.isAbsolute(manifest[key])
34089
34581
  ) || optionalPaths.some(
34090
- (key) => manifest[key] !== void 0 && (typeof manifest[key] !== "string" || !path21.isAbsolute(manifest[key]))
34582
+ (key) => manifest[key] !== void 0 && (typeof manifest[key] !== "string" || !path22.isAbsolute(manifest[key]))
34091
34583
  ) || manifest.entrypointSha256 !== void 0 && (typeof manifest.entrypointSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(manifest.entrypointSha256))) {
34092
34584
  throw new Error("Remote Host manifest has an unsupported format");
34093
34585
  }
@@ -34316,7 +34808,7 @@ async function uninstallRemoteHost(options2) {
34316
34808
  import { spawn as spawn5 } from "node:child_process";
34317
34809
  import { stat as stat5 } from "node:fs/promises";
34318
34810
  import net3 from "node:net";
34319
- import path22 from "node:path";
34811
+ import path23 from "node:path";
34320
34812
  import { Duplex } from "node:stream";
34321
34813
  var CODEXHOST_STATUS_METHOD = "codexhost/update/status";
34322
34814
  var DIRECT_PROBE_TIMEOUT_MS = 5e3;
@@ -34335,9 +34827,9 @@ function classifyRemoteHostProbeResponse(response, socketPath) {
34335
34827
  }
34336
34828
  var lifecycleDependencyOverrides = {};
34337
34829
  function socketPathFor(environment) {
34338
- const codexHome = environment.CODEX_HOME ?? (environment.HOME ? path22.join(environment.HOME, ".codex") : void 0);
34830
+ const codexHome = environment.CODEX_HOME ?? (environment.HOME ? path23.join(environment.HOME, ".codex") : void 0);
34339
34831
  if (!codexHome) throw new Error("CODEX_HOME or HOME is required for remote Host lifecycle");
34340
- return path22.join(codexHome, "app-server-control", "app-server-control.sock");
34832
+ return path23.join(codexHome, "app-server-control", "app-server-control.sock");
34341
34833
  }
34342
34834
  async function defaultSocketExists(socketPath) {
34343
34835
  const metadata = await stat5(socketPath).catch((error51) => {
@@ -34464,7 +34956,7 @@ function installedManifest(status) {
34464
34956
  function managedEnvironment(manifest, environment) {
34465
34957
  return {
34466
34958
  ...environment,
34467
- CODEX_INSTALL_DIR: path22.dirname(manifest.wrapperPath),
34959
+ CODEX_INSTALL_DIR: path23.dirname(manifest.wrapperPath),
34468
34960
  CODEXHOST_STOCK_CODEX_PATH: manifest.stockCodexPath,
34469
34961
  CODEXHOST_HOST_NODE_PATH: manifest.nodePath,
34470
34962
  CODEXHOST_HOST_RUNTIME_PATH: manifest.hostRuntimePath,
@@ -34472,7 +34964,7 @@ function managedEnvironment(manifest, environment) {
34472
34964
  CODEXHOST_DEFAULT_AGENT: "codex",
34473
34965
  CODEXHOST_REMOTE_SSH_MANAGED: "1",
34474
34966
  ...manifest.claudeCommand ? { CODEXHOST_CLAUDE_COMMAND: manifest.claudeCommand } : {},
34475
- PATH: `${path22.dirname(manifest.wrapperPath)}${path22.delimiter}${path22.dirname(manifest.stockCodexPath)}${path22.delimiter}${environment.PATH ?? "/usr/bin:/bin"}`
34967
+ PATH: `${path23.dirname(manifest.wrapperPath)}${path23.delimiter}${path23.dirname(manifest.stockCodexPath)}${path23.delimiter}${environment.PATH ?? "/usr/bin:/bin"}`
34476
34968
  };
34477
34969
  }
34478
34970
  async function defaultRunTerminator(manifest, socketPath, role, environment) {
@@ -34760,7 +35252,7 @@ var harnessBrokerServerFrameSchema = external_exports.union([
34760
35252
  var harnessBrokerDescriptorSchema = external_exports.object({
34761
35253
  schemaVersion: external_exports.literal(1),
34762
35254
  protocolVersion: external_exports.literal(HARNESS_BROKER_PROTOCOL_VERSION),
34763
- harnessId: external_exports.literal("claude-code"),
35255
+ harnessId: harnessPluginIdSchema,
34764
35256
  generation: external_exports.string().uuid(),
34765
35257
  ownerPid: external_exports.number().int().positive(),
34766
35258
  socketPath: external_exports.string().min(1).max(512),
@@ -34811,33 +35303,48 @@ function consumeBrokerFrames(socket, onFrame, onError) {
34811
35303
 
34812
35304
  // packages/harness-broker/dist/paths.js
34813
35305
  import os5 from "node:os";
34814
- import path23 from "node:path";
35306
+ import path24 from "node:path";
34815
35307
  var HARNESS_BROKER_DESCRIPTOR_ENV = "CODEXHOST_CLAUDE_BROKER_DESCRIPTOR";
34816
35308
  var HARNESS_BROKER_DESCRIPTOR_FILE = "claude-code-broker-v1.json";
34817
35309
  var HARNESS_BROKER_SOCKET_FILE = "claude-code-broker-v1.sock";
34818
35310
  function defaultHarnessBrokerDirectory(environment = process.env) {
34819
35311
  const home = environment.HOME || os5.homedir();
34820
- return path23.join(home, ".codexhost", "harness-broker");
35312
+ return path24.join(home, ".codexhost", "harness-broker");
34821
35313
  }
34822
- function defaultHarnessBrokerDescriptorPath(environment = process.env) {
34823
- return environment[HARNESS_BROKER_DESCRIPTOR_ENV] ?? path23.join(defaultHarnessBrokerDirectory(environment), HARNESS_BROKER_DESCRIPTOR_FILE);
35314
+ function defaultHarnessBrokerDescriptorPath(environment = process.env, harnessId = "claude-code") {
35315
+ harnessPluginIdSchema.parse(harnessId);
35316
+ if (harnessId !== "claude-code")
35317
+ return path24.join(defaultHarnessBrokerDirectory(environment), `${harnessId}-broker-v1.json`);
35318
+ return environment[HARNESS_BROKER_DESCRIPTOR_ENV] ?? path24.join(defaultHarnessBrokerDirectory(environment), HARNESS_BROKER_DESCRIPTOR_FILE);
34824
35319
  }
34825
- function defaultHarnessBrokerSocketPath(environment = process.env) {
34826
- return path23.join(defaultHarnessBrokerDirectory(environment), HARNESS_BROKER_SOCKET_FILE);
35320
+ function defaultHarnessBrokerSocketPath(environment = process.env, harnessId = "claude-code") {
35321
+ harnessPluginIdSchema.parse(harnessId);
35322
+ if (harnessId !== "claude-code")
35323
+ return path24.join(defaultHarnessBrokerDirectory(environment), `${harnessId}-broker-v1.sock`);
35324
+ return path24.join(defaultHarnessBrokerDirectory(environment), HARNESS_BROKER_SOCKET_FILE);
34827
35325
  }
34828
35326
 
34829
35327
  // packages/harness-broker/dist/validation.js
34830
35328
  var cwdSchema = external_exports.string().min(1).max(16384);
35329
+ var brokerEnvironmentSchema = external_exports.object({
35330
+ CODEXHOST_CLI_PATH: external_exports.string().max(16384).optional(),
35331
+ CODEXHOST_RUNTIME_ENDPOINT: external_exports.string().max(16384).optional(),
35332
+ CODEXHOST_RUNTIME_TOKEN: external_exports.string().max(16384).optional(),
35333
+ CODEXHOST_THREAD_ID: external_exports.string().max(256).optional()
35334
+ }).strict();
34831
35335
  var createSchema = external_exports.object({
34832
35336
  kind: external_exports.literal("create"),
34833
35337
  cwd: cwdSchema,
34834
35338
  executionPolicy: external_exports.enum(["default", "unattended-full-access"]).optional(),
35339
+ environment: brokerEnvironmentSchema.optional(),
34835
35340
  model: harnessModelRefSchema.optional(),
34836
35341
  thinkingOptionId: harnessThinkingOptionIdSchema.optional(),
34837
35342
  permissionModeId: harnessPermissionModeIdSchema.optional()
34838
35343
  }).strict();
34839
35344
  var resumeSchema = external_exports.object({
34840
35345
  kind: external_exports.literal("resume"),
35346
+ environment: brokerEnvironmentSchema.optional(),
35347
+ permissionModeId: harnessPermissionModeIdSchema.optional(),
34841
35348
  model: harnessModelRefSchema.optional(),
34842
35349
  thinkingOptionId: harnessThinkingOptionIdSchema.optional(),
34843
35350
  nativeRef: nativeSessionRefSchema,
@@ -34846,12 +35353,14 @@ var resumeSchema = external_exports.object({
34846
35353
  }).strict();
34847
35354
  var forkSchema = external_exports.object({
34848
35355
  kind: external_exports.literal("fork"),
35356
+ environment: brokerEnvironmentSchema.optional(),
34849
35357
  sourceRef: nativeSessionRefSchema,
34850
35358
  checkpoint: nativeCheckpointRefSchema,
34851
35359
  cwd: cwdSchema
34852
35360
  }).strict();
34853
35361
  var rollbackSchema = external_exports.object({
34854
35362
  kind: external_exports.literal("rollbackLastTurn"),
35363
+ environment: brokerEnvironmentSchema.optional(),
34855
35364
  model: harnessModelRefSchema.optional(),
34856
35365
  thinkingOptionId: harnessThinkingOptionIdSchema.optional(),
34857
35366
  permissionModeId: harnessPermissionModeIdSchema.optional(),
@@ -35025,10 +35534,10 @@ var harnessOutputSchema = external_exports.custom((value2) => {
35025
35534
  });
35026
35535
 
35027
35536
  // packages/harness-broker/dist/server.js
35028
- import { randomBytes as randomBytes2, randomUUID as randomUUID14 } from "node:crypto";
35537
+ import { randomBytes as randomBytes2, randomUUID as randomUUID15 } from "node:crypto";
35029
35538
  import { chmod as chmod5, lstat as lstat9, mkdir as mkdir12, open as open8, readFile as readFile10, rename as rename9, rm as rm11 } from "node:fs/promises";
35030
35539
  import net4, {} from "node:net";
35031
- import path24 from "node:path";
35540
+ import path25 from "node:path";
35032
35541
  function harnessError(message, retryable = true) {
35033
35542
  return { code: "unavailable", message, retryable, stage: "harnessBroker" };
35034
35543
  }
@@ -35084,7 +35593,7 @@ async function assertNoLiveDescriptor(descriptorPath) {
35084
35593
  }
35085
35594
  }
35086
35595
  async function publishDescriptor(descriptorPath, descriptor) {
35087
- const temporary = `${descriptorPath}.${process.pid}.${randomUUID14()}.tmp`;
35596
+ const temporary = `${descriptorPath}.${process.pid}.${randomUUID15()}.tmp`;
35088
35597
  const handle = await open8(temporary, "wx", 384);
35089
35598
  try {
35090
35599
  await handle.writeFile(`${JSON.stringify(descriptor)}
@@ -35150,25 +35659,23 @@ async function prepareUnixSocketPath(socketPath) {
35150
35659
  }
35151
35660
  }
35152
35661
  async function startHarnessBrokerServer(input) {
35153
- if (input.adapter.harnessId !== "claude-code") {
35154
- throw new Error("Harness broker accepts only the claude-code adapter");
35155
- }
35156
- if (process.platform !== "win32" && path24.dirname(input.descriptorPath) !== path24.dirname(input.socketPath)) {
35662
+ harnessPluginIdSchema.parse(input.adapter.harnessId);
35663
+ if (process.platform !== "win32" && path25.dirname(input.descriptorPath) !== path25.dirname(input.socketPath)) {
35157
35664
  throw new Error("Harness broker descriptor and socket must share one private directory");
35158
35665
  }
35159
35666
  if (process.platform === "darwin" && Buffer.byteLength(input.socketPath) > 103) {
35160
35667
  throw new Error("Harness broker Unix socket path is too long for macOS");
35161
35668
  }
35162
- await privateDirectory(path24.dirname(input.descriptorPath));
35669
+ await privateDirectory(path25.dirname(input.descriptorPath));
35163
35670
  await assertNoLiveDescriptor(input.descriptorPath);
35164
35671
  if (process.platform !== "win32")
35165
35672
  await prepareUnixSocketPath(input.socketPath);
35166
- const generation = input.generation ?? randomUUID14();
35673
+ const generation = input.generation ?? randomUUID15();
35167
35674
  const token = input.token ?? randomBytes2(32).toString("hex");
35168
35675
  const descriptor = {
35169
35676
  schemaVersion: 1,
35170
35677
  protocolVersion: HARNESS_BROKER_PROTOCOL_VERSION,
35171
- harnessId: "claude-code",
35678
+ harnessId: input.adapter.harnessId,
35172
35679
  generation,
35173
35680
  ownerPid: process.pid,
35174
35681
  socketPath: input.socketPath,
@@ -35181,7 +35688,7 @@ async function startHarnessBrokerServer(input) {
35181
35688
  let closed = false;
35182
35689
  const server = net4.createServer((socket) => {
35183
35690
  const state = {
35184
- id: randomUUID14(),
35691
+ id: randomUUID15(),
35185
35692
  socket,
35186
35693
  authenticated: false,
35187
35694
  inputSequence: 0,
@@ -35224,8 +35731,9 @@ async function startHarnessBrokerServer(input) {
35224
35731
  if (output.kind === "event" && output.event.type === "session.state.changed") {
35225
35732
  const state2 = output.event.state;
35226
35733
  const observedNativeId = state2.nativeRef?.nativeSessionId;
35227
- if (state2.nativeRef && record3.nativeRef && (record3.nativeRef.harnessId !== state2.nativeRef.harnessId || record3.nativeRef.nativeSessionId !== state2.nativeRef.nativeSessionId || record3.nativeRef.formatVersion !== state2.nativeRef.formatVersion)) {
35734
+ if (state2.nativeRef && (state2.nativeRef.harnessId !== input.adapter.harnessId || record3.nativeRef && (record3.nativeRef.harnessId !== state2.nativeRef.harnessId || record3.nativeRef.nativeSessionId !== state2.nativeRef.nativeSessionId || record3.nativeRef.formatVersion !== state2.nativeRef.formatVersion))) {
35228
35735
  record3.faulted = true;
35736
+ releaseProvisionalWriter(record3);
35229
35737
  await send({
35230
35738
  kind: "output",
35231
35739
  sessionId: record3.id,
@@ -35236,7 +35744,7 @@ async function startHarnessBrokerServer(input) {
35236
35744
  type: "session.faulted",
35237
35745
  error: {
35238
35746
  code: "protocolError",
35239
- message: "Native Claude Session identity changed after open",
35747
+ message: "Native Harness Session identity changed after open",
35240
35748
  retryable: false,
35241
35749
  stage: "harnessBroker.identity"
35242
35750
  }
@@ -35262,7 +35770,7 @@ async function startHarnessBrokerServer(input) {
35262
35770
  type: "session.faulted",
35263
35771
  error: {
35264
35772
  code: "sessionBusy",
35265
- message: "Native Claude Session already has an active writer",
35773
+ message: "Native Harness Session already has an active writer",
35266
35774
  retryable: true,
35267
35775
  stage: "harnessBroker.identity"
35268
35776
  }
@@ -35360,20 +35868,25 @@ async function startHarnessBrokerServer(input) {
35360
35868
  if (request.method === "adapter.subagent.readSnapshot") {
35361
35869
  const subagents = input.adapter.subagents;
35362
35870
  if (!subagents)
35363
- return { ok: false, error: harnessError("Claude subagents are unavailable", false) };
35364
- return subagents.readSnapshot(subagentReadSnapshotSchema.parse(request.params));
35871
+ return { ok: false, error: harnessError("Harness subagents are unavailable", false) };
35872
+ const params = subagentReadSnapshotSchema.parse(request.params);
35873
+ if (params.parent.harnessId !== input.adapter.harnessId)
35874
+ return { ok: false, error: protocolError("Subagent parent belongs to another Harness") };
35875
+ return subagents.readSnapshot(params);
35365
35876
  }
35366
35877
  if (request.method === "adapter.open") {
35367
35878
  const openInput = brokerOpenInputSchema.parse(request.params);
35368
35879
  const sourceRef = openInput.kind === "create" ? void 0 : openInput.kind === "resume" ? openInput.nativeRef : openInput.sourceRef;
35369
35880
  const sourceNativeId = sourceRef?.nativeSessionId;
35881
+ if (sourceRef && sourceRef.harnessId !== input.adapter.harnessId)
35882
+ return { ok: false, error: protocolError("Native Session belongs to another Harness") };
35370
35883
  const sourceKey = sourceNativeId ? nativeWriterKey(sourceNativeId) : void 0;
35371
35884
  if (sourceKey && nativeWriters.has(sourceKey)) {
35372
35885
  return {
35373
35886
  ok: false,
35374
35887
  error: {
35375
35888
  code: "sessionBusy",
35376
- message: "Native Claude Session already has an active writer",
35889
+ message: "Native Harness Session already has an active writer",
35377
35890
  retryable: true,
35378
35891
  stage: "harnessBroker.open"
35379
35892
  }
@@ -35384,13 +35897,13 @@ async function startHarnessBrokerServer(input) {
35384
35897
  ok: false,
35385
35898
  error: {
35386
35899
  code: "sessionBusy",
35387
- message: "Native Claude Session identity claim is already pending",
35900
+ message: "Native Harness Session identity claim is already pending",
35388
35901
  retryable: true,
35389
35902
  stage: "harnessBroker.open"
35390
35903
  }
35391
35904
  };
35392
35905
  }
35393
- const openReservation = randomUUID14();
35906
+ const openReservation = randomUUID15();
35394
35907
  if (sourceKey)
35395
35908
  nativeWriters.set(sourceKey, openReservation);
35396
35909
  const provisionalReservation = openInput.kind === "create" ? openReservation : void 0;
@@ -35418,6 +35931,14 @@ async function startHarnessBrokerServer(input) {
35418
35931
  return { ok: false, error: harnessError("Harness broker connection closed") };
35419
35932
  }
35420
35933
  const openedRef = opened.value.initialState.nativeRef;
35934
+ if (openedRef && openedRef.harnessId !== input.adapter.harnessId) {
35935
+ releaseOpenReservations();
35936
+ await opened.value.close().catch(() => void 0);
35937
+ return {
35938
+ ok: false,
35939
+ error: protocolError("Adapter opened a Session for another Harness")
35940
+ };
35941
+ }
35421
35942
  if (sourceRef && (!openedRef || openedRef.harnessId !== sourceRef.harnessId || openedRef.formatVersion !== sourceRef.formatVersion || openInput.kind === "resume" && openedRef.nativeSessionId !== sourceRef.nativeSessionId)) {
35422
35943
  releaseOpenReservations();
35423
35944
  await opened.value.close().catch(() => void 0);
@@ -35425,7 +35946,7 @@ async function startHarnessBrokerServer(input) {
35425
35946
  ok: false,
35426
35947
  error: {
35427
35948
  code: "protocolError",
35428
- message: "Native Claude Session identity did not match the requested open",
35949
+ message: "Native Harness Session identity did not match the requested open",
35429
35950
  retryable: false,
35430
35951
  stage: "harnessBroker.open"
35431
35952
  }
@@ -35441,7 +35962,7 @@ async function startHarnessBrokerServer(input) {
35441
35962
  ok: false,
35442
35963
  error: {
35443
35964
  code: "sessionBusy",
35444
- message: "Native Claude Session already has an active writer",
35965
+ message: "Native Harness Session already has an active writer",
35445
35966
  retryable: true,
35446
35967
  stage: "harnessBroker.open"
35447
35968
  }
@@ -35453,6 +35974,7 @@ async function startHarnessBrokerServer(input) {
35453
35974
  generation: 1,
35454
35975
  owner: state.id,
35455
35976
  cwd: openInput.cwd,
35977
+ ...openInput.environment ? { environment: openInput.environment } : {},
35456
35978
  ...nativeId ? { nativeId } : {},
35457
35979
  ...opened.value.initialState.nativeRef ? { nativeRef: opened.value.initialState.nativeRef } : {},
35458
35980
  ...nativeKey ? { writerKey: nativeKey } : {},
@@ -35493,7 +36015,7 @@ async function startHarnessBrokerServer(input) {
35493
36015
  ok: false,
35494
36016
  error: {
35495
36017
  code: "sessionBusy",
35496
- message: "Native Claude Session identity has not been claimed yet",
36018
+ message: "Native Harness Session identity has not been claimed yet",
35497
36019
  retryable: true,
35498
36020
  stage: "harnessBroker.identity"
35499
36021
  }
@@ -35570,7 +36092,7 @@ async function startHarnessBrokerServer(input) {
35570
36092
  ok: false,
35571
36093
  error: {
35572
36094
  code: "sessionBusy",
35573
- message: "Faulted Claude Session has no authoritative native identity",
36095
+ message: "Faulted Harness Session has no authoritative native identity",
35574
36096
  retryable: false,
35575
36097
  stage: "harnessBroker.reopen"
35576
36098
  }
@@ -35582,7 +36104,12 @@ async function startHarnessBrokerServer(input) {
35582
36104
  record4.forwarderEpoch += 1;
35583
36105
  await oldSession.close().catch(() => void 0);
35584
36106
  await oldOutputTask.catch(() => void 0);
35585
- const reopened = await input.adapter.open({ kind: "resume", cwd: record4.cwd, nativeRef });
36107
+ const reopened = await input.adapter.open({
36108
+ kind: "resume",
36109
+ cwd: record4.cwd,
36110
+ nativeRef,
36111
+ ...record4.environment ? { environment: record4.environment } : {}
36112
+ });
35586
36113
  if (!reopened.ok)
35587
36114
  return reopened;
35588
36115
  const reopenedRef = reopened.value.initialState.nativeRef;
@@ -35592,7 +36119,7 @@ async function startHarnessBrokerServer(input) {
35592
36119
  ok: false,
35593
36120
  error: {
35594
36121
  code: "protocolError",
35595
- message: "Claude Aqua broker reopen changed the native Session identity",
36122
+ message: "Aqua Harness broker reopen changed the native Session identity",
35596
36123
  retryable: false,
35597
36124
  stage: "harnessBroker.reopen"
35598
36125
  }
@@ -35656,7 +36183,7 @@ async function startHarnessBrokerServer(input) {
35656
36183
  }
35657
36184
  state.authenticated = true;
35658
36185
  state.inputSequence = 1;
35659
- await send({ kind: "response", id: randomUUID14(), ok: true, value: { ready: true } });
36186
+ await send({ kind: "response", id: randomUUID15(), ok: true, value: { ready: true } });
35660
36187
  return;
35661
36188
  }
35662
36189
  const parsed = harnessBrokerRequestSchema.safeParse(raw);
@@ -35729,15 +36256,16 @@ async function startHarnessBrokerServer(input) {
35729
36256
  }
35730
36257
 
35731
36258
  // packages/host-runtime/src/aqua-harness-broker.ts
35732
- async function runClaudeAquaHarnessBroker(environment = process.env) {
36259
+ async function runClaudeAquaHarnessBroker(environment = process.env, requestedHarnessId = "claude-code") {
36260
+ const harnessId = harnessPluginIdSchema.parse(requestedHarnessId);
35733
36261
  if (process.platform !== "darwin") {
35734
- throw new Error("Claude Aqua Harness broker is available only on macOS");
36262
+ throw new Error("Aqua Harness broker is available only on macOS");
35735
36263
  }
35736
36264
  const { pluginRoots, pluginContext } = installedHarnessPluginOptions(environment);
35737
36265
  const plugins = await loadHarnessPlugins({
35738
36266
  roots: pluginRoots,
35739
36267
  context: pluginContext,
35740
- onlyIds: /* @__PURE__ */ new Set(["claude-code"]),
36268
+ onlyIds: /* @__PURE__ */ new Set([harnessId]),
35741
36269
  warmup: false,
35742
36270
  diagnose: (diagnostic) => process.stderr.write(`Harness plugin: ${JSON.stringify(diagnostic)}
35743
36271
  `)
@@ -35750,19 +36278,19 @@ async function runClaudeAquaHarnessBroker(environment = process.env) {
35750
36278
  let server;
35751
36279
  try {
35752
36280
  server = await startHarnessBrokerServer({
35753
- descriptorPath: defaultHarnessBrokerDescriptorPath(environment),
35754
- socketPath: defaultHarnessBrokerSocketPath(environment),
36281
+ descriptorPath: defaultHarnessBrokerDescriptorPath(environment, harnessId),
36282
+ socketPath: defaultHarnessBrokerSocketPath(environment, harnessId),
35755
36283
  adapter
35756
36284
  });
35757
36285
  } catch (error51) {
35758
36286
  await adapter.close().catch(() => void 0);
35759
36287
  throw error51;
35760
36288
  }
35761
- process.title = "codexhost claude-code Aqua harness broker";
36289
+ process.title = `codexhost ${harnessId} Aqua harness broker`;
35762
36290
  process.stdout.write(
35763
36291
  `${JSON.stringify({
35764
36292
  method: "codexhost/harness-broker/ready",
35765
- params: { protocolVersion: 1, harnessId: "claude-code" }
36293
+ params: { protocolVersion: 1, harnessId }
35766
36294
  })}
35767
36295
  `
35768
36296
  );
@@ -35785,7 +36313,7 @@ async function runClaudeAquaHarnessBroker(environment = process.env) {
35785
36313
 
35786
36314
  // packages/host-runtime/src/release-main.ts
35787
36315
  var arguments_ = process.argv.slice(2);
35788
- process.exitCode = arguments_[0] === "--codexhost-delegation-cli" ? await runDelegationCli({ arguments: arguments_.slice(1), environment: process.env }) : arguments_[0] === "--codexhost-harness-broker" ? await runClaudeAquaHarnessBroker(process.env) : arguments_[0] === "--codexhost-remote" ? await runRemoteHostCli({ arguments: arguments_.slice(1), environment: process.env }) : arguments_[0] === "--codexhost-remote-control-bridge" ? await runRemoteControlAppServerBridge() : await runHostRuntime({
36316
+ process.exitCode = arguments_[0] === "--codexhost-delegation-cli" ? await runDelegationCli({ arguments: arguments_.slice(1), environment: process.env }) : arguments_[0] === "--codexhost-harness-broker" ? await runClaudeAquaHarnessBroker(process.env, arguments_[1] ?? "claude-code") : arguments_[0] === "--codexhost-remote" ? await runRemoteHostCli({ arguments: arguments_.slice(1), environment: process.env }) : arguments_[0] === "--codexhost-remote-control-bridge" ? await runRemoteControlAppServerBridge() : await runHostRuntime({
35789
36317
  arguments: arguments_,
35790
36318
  environment: process.env,
35791
36319
  hostRuntimeUrl: import.meta.url