@codexhost/cli-darwin-x64 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/app/codexhost-distribution.json +1 -1
- package/app/host-runtime.mjs +470 -80
- package/app/plugins/claude-code/plugin.mjs +26 -15
- package/app/plugins/codex-usage/plugin.mjs +18 -9
- package/app/plugins/hermes/plugin.mjs +184 -40
- package/app/renderer-extension.js +64 -35
- package/bin/codexhost +0 -0
- package/libexec/codexhost-shim +0 -0
- package/libexec/codexhost-updater +0 -0
- package/package.json +1 -1
package/app/host-runtime.mjs
CHANGED
|
@@ -4295,9 +4295,11 @@ async function runDelegationCli(input) {
|
|
|
4295
4295
|
"INVALID_ARGUMENT",
|
|
4296
4296
|
"--host conflicts with the Thread reference"
|
|
4297
4297
|
);
|
|
4298
|
+
const caller = command === "read" ? callerThreadId(environment) : void 0;
|
|
4298
4299
|
const body = {
|
|
4299
4300
|
...reference,
|
|
4300
4301
|
...hostId2 ? { hostId: hostId2 } : {},
|
|
4302
|
+
...caller ? { callerThreadId: normalizeThreadId(caller) } : {},
|
|
4301
4303
|
view,
|
|
4302
4304
|
...value(parsed2, "--cursor") ? { cursor: value(parsed2, "--cursor") } : {},
|
|
4303
4305
|
...value(parsed2, "--limit") ? { limit: positiveInteger(value(parsed2, "--limit"), "--limit", MAX_LIMIT) } : {},
|
|
@@ -4434,6 +4436,7 @@ import {
|
|
|
4434
4436
|
realpath,
|
|
4435
4437
|
stat,
|
|
4436
4438
|
readdir as readdir2,
|
|
4439
|
+
unlink,
|
|
4437
4440
|
writeFile as writeFile3
|
|
4438
4441
|
} from "node:fs/promises";
|
|
4439
4442
|
import { homedir } from "node:os";
|
|
@@ -19943,6 +19946,7 @@ config(en_default());
|
|
|
19943
19946
|
var REMOTE_THREAD_REPLY_MAX_BYTES = 16 * 1024 * 1024;
|
|
19944
19947
|
var REMOTE_THREAD_CONTROL_TIMEOUT_MS = 4e4;
|
|
19945
19948
|
var DELEGATION_READ_METHOD = "codexhost/thread/delegation-read";
|
|
19949
|
+
var THREAD_REFERENCE_SOURCE_HOST_PARAM = "codexhostSourceHostId";
|
|
19946
19950
|
var delegationReadParamsSchema = external_exports.strictObject({
|
|
19947
19951
|
threadId: external_exports.string().min(1).max(1024),
|
|
19948
19952
|
view: external_exports.enum(["result", "messages"]),
|
|
@@ -21467,14 +21471,37 @@ var RuntimeMaintenance = class {
|
|
|
21467
21471
|
options2.environment.CODEXHOST_DATA_DIR ?? path7.join(home, ".codexhost", "remote", "data"),
|
|
21468
21472
|
"remote-update.json"
|
|
21469
21473
|
);
|
|
21474
|
+
this.#cancelPath = path7.join(path7.dirname(this.#statusPath), "remote-update.cancel");
|
|
21470
21475
|
}
|
|
21471
21476
|
options;
|
|
21472
21477
|
#running;
|
|
21473
21478
|
#statusPath;
|
|
21479
|
+
#cancelPath;
|
|
21474
21480
|
#update = { phase: "idle", targetVersion: null, error: null };
|
|
21475
21481
|
#blocked = false;
|
|
21482
|
+
#watch = null;
|
|
21476
21483
|
#starting = null;
|
|
21477
21484
|
#startingVersion = null;
|
|
21485
|
+
#ensureWatching() {
|
|
21486
|
+
if (this.#watch) return;
|
|
21487
|
+
this.#watch = setInterval(() => {
|
|
21488
|
+
void this.status().catch(() => void 0).then(() => {
|
|
21489
|
+
if (!this.#blocked && this.#watch) {
|
|
21490
|
+
clearInterval(this.#watch);
|
|
21491
|
+
this.#watch = null;
|
|
21492
|
+
}
|
|
21493
|
+
});
|
|
21494
|
+
}, UPDATER_WATCH_INTERVAL_MS);
|
|
21495
|
+
this.#watch.unref();
|
|
21496
|
+
}
|
|
21497
|
+
async #recordStartFailure(version2, failure) {
|
|
21498
|
+
await writeFile3(this.#cancelPath, JSON.stringify({ targetVersion: version2 }), { mode: 384 });
|
|
21499
|
+
await writeFile3(
|
|
21500
|
+
this.#statusPath,
|
|
21501
|
+
JSON.stringify({ phase: "failed", targetVersion: version2, error: failure }),
|
|
21502
|
+
{ mode: 384 }
|
|
21503
|
+
);
|
|
21504
|
+
}
|
|
21478
21505
|
#sshPending = /* @__PURE__ */ new Set();
|
|
21479
21506
|
async setupSsh(input) {
|
|
21480
21507
|
if (this.options.remote) throw new Error("SSH installation must run from the local computer");
|
|
@@ -21535,7 +21562,7 @@ var RuntimeMaintenance = class {
|
|
|
21535
21562
|
installation(this.options.runtimePath, this.options.environment),
|
|
21536
21563
|
this.#resources()
|
|
21537
21564
|
]);
|
|
21538
|
-
if (this.#update.phase === "idle" || this.#blocked) {
|
|
21565
|
+
if (this.#update.phase === "idle" || this.#blocked || this.#update.phase === "failed") {
|
|
21539
21566
|
try {
|
|
21540
21567
|
const value2 = JSON.parse(await readFile5(this.#statusPath, "utf8"));
|
|
21541
21568
|
const parsed2 = runtimeStatusSchema.shape.update.parse(value2);
|
|
@@ -21558,8 +21585,15 @@ var RuntimeMaintenance = class {
|
|
|
21558
21585
|
parsed2.error = "The previous remote update was interrupted; retry the update";
|
|
21559
21586
|
}
|
|
21560
21587
|
}
|
|
21561
|
-
if (this.#update === observedUpdate)
|
|
21562
|
-
|
|
21588
|
+
if (this.#update === observedUpdate) {
|
|
21589
|
+
this.#update = parsed2;
|
|
21590
|
+
if (["installing", "restarting"].includes(this.#update.phase)) {
|
|
21591
|
+
this.#blocked = true;
|
|
21592
|
+
this.#ensureWatching();
|
|
21593
|
+
} else if (["failed", "succeeded"].includes(this.#update.phase)) {
|
|
21594
|
+
this.#blocked = false;
|
|
21595
|
+
}
|
|
21596
|
+
}
|
|
21563
21597
|
} catch {
|
|
21564
21598
|
}
|
|
21565
21599
|
}
|
|
@@ -21631,6 +21665,7 @@ var RuntimeMaintenance = class {
|
|
|
21631
21665
|
await chmod2(helper, 448);
|
|
21632
21666
|
const request = path7.join(work, "request.json");
|
|
21633
21667
|
this.#update = { phase: "installing", targetVersion: version2, error: null };
|
|
21668
|
+
await unlink(this.#cancelPath).catch(() => void 0);
|
|
21634
21669
|
await writeFile3(this.#statusPath, JSON.stringify(this.#update), { mode: 384 });
|
|
21635
21670
|
await writeFile3(
|
|
21636
21671
|
request,
|
|
@@ -21649,35 +21684,38 @@ var RuntimeMaintenance = class {
|
|
|
21649
21684
|
);
|
|
21650
21685
|
const starter = spawn3(
|
|
21651
21686
|
"/bin/sh",
|
|
21652
|
-
["-c", '"$0" remote --request "$1" </dev/null >/dev/null 2>&1 &', helper, request],
|
|
21653
|
-
{ detached: true, stdio: "ignore", env: this.options.environment }
|
|
21687
|
+
["-c", '"$0" remote --request "$1" </dev/null >/dev/null 2>&1 & echo $!', helper, request],
|
|
21688
|
+
{ detached: true, stdio: ["ignore", "pipe", "ignore"], env: this.options.environment }
|
|
21654
21689
|
);
|
|
21690
|
+
let pidOutput = "";
|
|
21691
|
+
starter.stdout?.on("data", (chunk) => {
|
|
21692
|
+
pidOutput += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
21693
|
+
});
|
|
21655
21694
|
await new Promise((resolve, reject) => {
|
|
21656
21695
|
starter.once("error", reject);
|
|
21657
21696
|
starter.once(
|
|
21658
|
-
"
|
|
21697
|
+
"close",
|
|
21659
21698
|
(code) => code === 0 ? resolve() : reject(new Error("Remote updater could not be started"))
|
|
21660
21699
|
);
|
|
21661
21700
|
});
|
|
21701
|
+
const helperPid = Number.parseInt(pidOutput.trim(), 10);
|
|
21702
|
+
if (!Number.isSafeInteger(helperPid) || helperPid <= 0) {
|
|
21703
|
+
throw new Error("Remote updater could not be started");
|
|
21704
|
+
}
|
|
21662
21705
|
const deadline = Date.now() + UPDATER_START_TIMEOUT_MS;
|
|
21663
21706
|
while (!await this.#helperReported()) {
|
|
21664
21707
|
if (Date.now() >= deadline) {
|
|
21708
|
+
try {
|
|
21709
|
+
process.kill(helperPid);
|
|
21710
|
+
} catch {
|
|
21711
|
+
}
|
|
21665
21712
|
const failure = "Remote updater did not start";
|
|
21666
|
-
await
|
|
21667
|
-
this.#statusPath,
|
|
21668
|
-
JSON.stringify({ phase: "failed", targetVersion: version2, error: failure }),
|
|
21669
|
-
{ mode: 384 }
|
|
21670
|
-
);
|
|
21713
|
+
await this.#recordStartFailure(version2, failure);
|
|
21671
21714
|
throw new Error(failure);
|
|
21672
21715
|
}
|
|
21673
21716
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
21674
21717
|
}
|
|
21675
|
-
|
|
21676
|
-
void this.status().catch(() => void 0).then(() => {
|
|
21677
|
-
if (!this.#blocked) clearInterval(watch);
|
|
21678
|
-
});
|
|
21679
|
-
}, UPDATER_WATCH_INTERVAL_MS);
|
|
21680
|
-
watch.unref();
|
|
21718
|
+
this.#ensureWatching();
|
|
21681
21719
|
}
|
|
21682
21720
|
async #helperReported() {
|
|
21683
21721
|
try {
|
|
@@ -21691,6 +21729,7 @@ var RuntimeMaintenance = class {
|
|
|
21691
21729
|
|
|
21692
21730
|
// packages/host-runtime/src/run-host-runtime.ts
|
|
21693
21731
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
21732
|
+
import { statSync } from "node:fs";
|
|
21694
21733
|
import path37 from "node:path";
|
|
21695
21734
|
import { homedir as homedir4 } from "node:os";
|
|
21696
21735
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -31745,6 +31784,16 @@ var ExternalThreadIdleRelease = class {
|
|
|
31745
31784
|
const state = this.#activity.get(thread);
|
|
31746
31785
|
if (state) state.outputFailed = true;
|
|
31747
31786
|
}
|
|
31787
|
+
/** After a successful Session revival, allow idle release again. */
|
|
31788
|
+
clearOutputFailed(thread) {
|
|
31789
|
+
const state = this.#activity.get(thread);
|
|
31790
|
+
if (state) {
|
|
31791
|
+
state.outputFailed = false;
|
|
31792
|
+
state.lastActivity = Date.now();
|
|
31793
|
+
} else {
|
|
31794
|
+
this.touch(thread);
|
|
31795
|
+
}
|
|
31796
|
+
}
|
|
31748
31797
|
failure(thread) {
|
|
31749
31798
|
return this.#activity.get(thread)?.closeFailed ? CLOSE_FAILURE : void 0;
|
|
31750
31799
|
}
|
|
@@ -31893,6 +31942,80 @@ var ExternalThreadIdleRelease = class {
|
|
|
31893
31942
|
}
|
|
31894
31943
|
};
|
|
31895
31944
|
|
|
31945
|
+
// packages/host-runtime/src/external-thread-lifecycle.ts
|
|
31946
|
+
var RELEASE_TIMEOUT_MS = 6e4;
|
|
31947
|
+
var ExternalSessionReleaseError = class extends Error {
|
|
31948
|
+
constructor(cause) {
|
|
31949
|
+
super(
|
|
31950
|
+
"External Session shutdown could not be confirmed. Retry to confirm release; recovery is blocked until the old Session closes and its output drains. If the Harness cannot retry shutdown, stop residual processes and restart Codex Desktop.",
|
|
31951
|
+
{ cause }
|
|
31952
|
+
);
|
|
31953
|
+
this.name = "ExternalSessionReleaseError";
|
|
31954
|
+
}
|
|
31955
|
+
};
|
|
31956
|
+
var ExternalThreadLifecycle = class {
|
|
31957
|
+
#pending = /* @__PURE__ */ new Map();
|
|
31958
|
+
#closing = /* @__PURE__ */ new WeakMap();
|
|
31959
|
+
#releasing = /* @__PURE__ */ new Map();
|
|
31960
|
+
busy(id2) {
|
|
31961
|
+
return this.#pending.has(id2) || this.#releasing.has(id2);
|
|
31962
|
+
}
|
|
31963
|
+
pending() {
|
|
31964
|
+
return [...this.#pending];
|
|
31965
|
+
}
|
|
31966
|
+
run(id2, operation) {
|
|
31967
|
+
const previous = this.#pending.get(id2) ?? Promise.resolve();
|
|
31968
|
+
const task = previous.then(async () => {
|
|
31969
|
+
const releasing = this.#releasing.get(id2);
|
|
31970
|
+
if (releasing) await this.release(id2, releasing.session, releasing.output);
|
|
31971
|
+
return operation();
|
|
31972
|
+
});
|
|
31973
|
+
const settled = task.then(
|
|
31974
|
+
() => void 0,
|
|
31975
|
+
() => void 0
|
|
31976
|
+
);
|
|
31977
|
+
this.#pending.set(id2, settled);
|
|
31978
|
+
void settled.then(() => {
|
|
31979
|
+
if (this.#pending.get(id2) === settled) this.#pending.delete(id2);
|
|
31980
|
+
});
|
|
31981
|
+
return task;
|
|
31982
|
+
}
|
|
31983
|
+
/** Call inside the Thread's lifecycle operation. A timeout never cancels the owned close. */
|
|
31984
|
+
async release(id2, session, output) {
|
|
31985
|
+
this.#releasing.set(id2, { session, output });
|
|
31986
|
+
let closing = this.#closing.get(session);
|
|
31987
|
+
if (!closing) {
|
|
31988
|
+
closing = Promise.resolve().then(() => session.close());
|
|
31989
|
+
this.#closing.set(session, closing);
|
|
31990
|
+
const attempt = closing;
|
|
31991
|
+
void closing.catch(() => {
|
|
31992
|
+
if (this.#closing.get(session) === attempt) this.#closing.delete(session);
|
|
31993
|
+
});
|
|
31994
|
+
}
|
|
31995
|
+
let timeout;
|
|
31996
|
+
try {
|
|
31997
|
+
await Promise.race([
|
|
31998
|
+
closing.then(() => output),
|
|
31999
|
+
new Promise((_, reject) => {
|
|
32000
|
+
timeout = setTimeout(
|
|
32001
|
+
() => reject(new Error("Session shutdown timed out")),
|
|
32002
|
+
RELEASE_TIMEOUT_MS
|
|
32003
|
+
);
|
|
32004
|
+
timeout.unref();
|
|
32005
|
+
})
|
|
32006
|
+
]);
|
|
32007
|
+
this.#releasing.delete(id2);
|
|
32008
|
+
} catch (error51) {
|
|
32009
|
+
throw new ExternalSessionReleaseError(error51);
|
|
32010
|
+
} finally {
|
|
32011
|
+
if (timeout) clearTimeout(timeout);
|
|
32012
|
+
}
|
|
32013
|
+
}
|
|
32014
|
+
async drain() {
|
|
32015
|
+
while (this.#pending.size) await Promise.all(this.#pending.values());
|
|
32016
|
+
}
|
|
32017
|
+
};
|
|
32018
|
+
|
|
31896
32019
|
// packages/host-runtime/src/usage-metering.ts
|
|
31897
32020
|
var TOKENS_PER_PRICE_UNIT = 1e6;
|
|
31898
32021
|
var UsageMeter = class {
|
|
@@ -32175,6 +32298,11 @@ var ExternalThreadOpenError = class extends Error {
|
|
|
32175
32298
|
function errorMessage3(error51) {
|
|
32176
32299
|
return error51 instanceof Error ? error51.message : String(error51);
|
|
32177
32300
|
}
|
|
32301
|
+
function recoveryError(error51) {
|
|
32302
|
+
if (error51 instanceof ExternalThreadOpenError) return error51.rpcError;
|
|
32303
|
+
if (error51 instanceof ExternalSessionReleaseError) return { code: -32075, message: error51.message };
|
|
32304
|
+
return { code: -32076, message: "External Thread recovery failed; retry the request" };
|
|
32305
|
+
}
|
|
32178
32306
|
var ExternalThreadRuntime = class {
|
|
32179
32307
|
idleRelease;
|
|
32180
32308
|
#adapters;
|
|
@@ -32182,7 +32310,8 @@ var ExternalThreadRuntime = class {
|
|
|
32182
32310
|
#diagnose;
|
|
32183
32311
|
#environment;
|
|
32184
32312
|
#repository;
|
|
32185
|
-
#
|
|
32313
|
+
#lifecycle = new ExternalThreadLifecycle();
|
|
32314
|
+
#hasRunningDescendants;
|
|
32186
32315
|
#subagentRunning;
|
|
32187
32316
|
#threads = /* @__PURE__ */ new Map();
|
|
32188
32317
|
constructor(input) {
|
|
@@ -32192,12 +32321,13 @@ var ExternalThreadRuntime = class {
|
|
|
32192
32321
|
this.#consumeOutputs = input.consumeOutputs;
|
|
32193
32322
|
this.#diagnose = input.diagnose;
|
|
32194
32323
|
this.#subagentRunning = input.subagentRunning ?? (() => false);
|
|
32324
|
+
this.#hasRunningDescendants = input.hasRunningDescendants ?? (() => false);
|
|
32195
32325
|
this.idleRelease = new ExternalThreadIdleRelease({
|
|
32196
32326
|
threads: () => this.values(),
|
|
32197
32327
|
get: (id2) => this.get(id2),
|
|
32198
32328
|
remove: (id2) => this.remove(id2),
|
|
32199
32329
|
queue: input.idleRelease?.queue ?? new DesktopRequestQueue(),
|
|
32200
|
-
canRelease: input.idleRelease?.canRelease
|
|
32330
|
+
canRelease: (thread) => !thread.sessionUnavailable && !this.#lifecycle.busy(thread.id) && (input.idleRelease?.canRelease(thread) ?? false),
|
|
32201
32331
|
...input.idleRelease?.onClosed ? { onClosed: input.idleRelease.onClosed } : {},
|
|
32202
32332
|
diagnose: input.diagnose
|
|
32203
32333
|
});
|
|
@@ -32213,7 +32343,45 @@ var ExternalThreadRuntime = class {
|
|
|
32213
32343
|
}
|
|
32214
32344
|
clear() {
|
|
32215
32345
|
this.#threads.clear();
|
|
32216
|
-
|
|
32346
|
+
}
|
|
32347
|
+
drain() {
|
|
32348
|
+
return this.#lifecycle.drain();
|
|
32349
|
+
}
|
|
32350
|
+
#withLifecycle(threadId2, operation) {
|
|
32351
|
+
return this.#lifecycle.run(threadId2, async () => {
|
|
32352
|
+
await this.idleRelease.waitForClose(threadId2);
|
|
32353
|
+
return operation();
|
|
32354
|
+
});
|
|
32355
|
+
}
|
|
32356
|
+
markSessionUnavailable(thread) {
|
|
32357
|
+
thread.sessionUnavailable = true;
|
|
32358
|
+
}
|
|
32359
|
+
#idleForRestore(thread) {
|
|
32360
|
+
if (thread.running || thread.activeTurnId !== null) return false;
|
|
32361
|
+
if (thread.session.hasBackgroundWork?.()) return false;
|
|
32362
|
+
if (this.#hasRunningDescendants(thread.id)) return false;
|
|
32363
|
+
return true;
|
|
32364
|
+
}
|
|
32365
|
+
async #dropIdleSession(thread) {
|
|
32366
|
+
if (this.#threads.get(thread.id) !== thread) return;
|
|
32367
|
+
thread.sessionUnavailable = true;
|
|
32368
|
+
await this.#lifecycle.release(thread.id, thread.session, thread.outputTask);
|
|
32369
|
+
if (this.#threads.get(thread.id) !== thread) return;
|
|
32370
|
+
await this.#retireSubagents(thread);
|
|
32371
|
+
if (this.#threads.get(thread.id) === thread) this.#threads.delete(thread.id);
|
|
32372
|
+
}
|
|
32373
|
+
/** Called without awaiting from the output consumer: release itself drains that consumer. */
|
|
32374
|
+
async discardUnavailableIdle(thread) {
|
|
32375
|
+
const session = thread.session;
|
|
32376
|
+
try {
|
|
32377
|
+
await this.#withLifecycle(thread.id, async () => {
|
|
32378
|
+
if (this.#threads.get(thread.id) !== thread || thread.session !== session || !thread.sessionUnavailable || !this.#idleForRestore(thread))
|
|
32379
|
+
return;
|
|
32380
|
+
await this.#dropIdleSession(thread);
|
|
32381
|
+
});
|
|
32382
|
+
} catch (error51) {
|
|
32383
|
+
this.#diagnose(error51);
|
|
32384
|
+
}
|
|
32217
32385
|
}
|
|
32218
32386
|
register(input) {
|
|
32219
32387
|
const harnessId = input.record.harnessId;
|
|
@@ -32258,7 +32426,8 @@ var ExternalThreadRuntime = class {
|
|
|
32258
32426
|
ephemeralTurnIds: /* @__PURE__ */ new Set(),
|
|
32259
32427
|
persistenceError: null,
|
|
32260
32428
|
projectedTerminalTurnId: null,
|
|
32261
|
-
ignoredInteractionIds: /* @__PURE__ */ new Set()
|
|
32429
|
+
ignoredInteractionIds: /* @__PURE__ */ new Set(),
|
|
32430
|
+
sessionUnavailable: false
|
|
32262
32431
|
};
|
|
32263
32432
|
this.idleRelease.touch(externalThread);
|
|
32264
32433
|
externalThread.outputTask = this.#consumeOutputs(externalThread);
|
|
@@ -32285,11 +32454,12 @@ var ExternalThreadRuntime = class {
|
|
|
32285
32454
|
} catch (error51) {
|
|
32286
32455
|
this.#diagnose(error51);
|
|
32287
32456
|
}
|
|
32288
|
-
await this.#retireSubagents(current
|
|
32457
|
+
await this.#retireSubagents(current, true);
|
|
32289
32458
|
this.#threads.delete(current.id);
|
|
32290
32459
|
return this.register(input);
|
|
32291
32460
|
}
|
|
32292
|
-
async #retireSubagents(
|
|
32461
|
+
async #retireSubagents(parent, bestEffort = false) {
|
|
32462
|
+
const parentId = parent.id;
|
|
32293
32463
|
const ancestors = /* @__PURE__ */ new Map();
|
|
32294
32464
|
const descendant = async (record4) => {
|
|
32295
32465
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -32306,24 +32476,35 @@ var ExternalThreadRuntime = class {
|
|
|
32306
32476
|
}
|
|
32307
32477
|
return false;
|
|
32308
32478
|
};
|
|
32309
|
-
for (const [id2,
|
|
32310
|
-
if (await descendant(await this.#repository.find(id2)))
|
|
32311
|
-
await restoring.catch(this.#diagnose);
|
|
32312
|
-
}
|
|
32479
|
+
for (const [id2, pending] of this.#lifecycle.pending()) {
|
|
32480
|
+
if (id2 !== parentId && await descendant(await this.#repository.find(id2))) await pending;
|
|
32313
32481
|
}
|
|
32314
|
-
for (const child of this.#threads.values()) {
|
|
32482
|
+
for (const child of [...this.#threads.values()]) {
|
|
32315
32483
|
if (!await descendant(child.record)) continue;
|
|
32316
|
-
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32484
|
+
const retire = async () => {
|
|
32485
|
+
if (this.#threads.get(parentId) !== parent || this.#threads.get(child.id) !== child) return;
|
|
32486
|
+
if (bestEffort) {
|
|
32487
|
+
this.#threads.delete(child.id);
|
|
32488
|
+
await child.session.close();
|
|
32489
|
+
await child.outputTask;
|
|
32490
|
+
} else {
|
|
32491
|
+
child.sessionUnavailable = true;
|
|
32492
|
+
await this.#lifecycle.release(child.id, child.session, child.outputTask);
|
|
32493
|
+
if (this.#threads.get(child.id) === child) this.#threads.delete(child.id);
|
|
32494
|
+
}
|
|
32495
|
+
};
|
|
32496
|
+
if (bestEffort) await retire().catch(this.#diagnose);
|
|
32497
|
+
else await this.#withLifecycle(child.id, retire);
|
|
32323
32498
|
}
|
|
32324
32499
|
}
|
|
32325
32500
|
async locate(threadId2) {
|
|
32326
|
-
|
|
32501
|
+
try {
|
|
32502
|
+
return await this.#withLifecycle(threadId2, () => this.#locate(threadId2));
|
|
32503
|
+
} catch (error51) {
|
|
32504
|
+
return { kind: "error", error: recoveryError(error51) };
|
|
32505
|
+
}
|
|
32506
|
+
}
|
|
32507
|
+
async #locate(threadId2) {
|
|
32327
32508
|
const loaded = this.#threads.get(threadId2);
|
|
32328
32509
|
if (loaded) {
|
|
32329
32510
|
const failure = this.idleRelease.failure(loaded);
|
|
@@ -32349,37 +32530,61 @@ var ExternalThreadRuntime = class {
|
|
|
32349
32530
|
return { kind: "external", record: record4, thread: null };
|
|
32350
32531
|
}
|
|
32351
32532
|
async resolve(threadId2) {
|
|
32352
|
-
|
|
32353
|
-
|
|
32354
|
-
|
|
32355
|
-
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
|
|
32362
|
-
|
|
32363
|
-
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32367
|
-
|
|
32533
|
+
try {
|
|
32534
|
+
return await this.#withLifecycle(threadId2, async () => {
|
|
32535
|
+
const location = await this.#locate(threadId2);
|
|
32536
|
+
if (location.kind !== "external") return location;
|
|
32537
|
+
if (location.thread) {
|
|
32538
|
+
if (location.thread.sessionUnavailable && this.#idleForRestore(location.thread)) {
|
|
32539
|
+
await this.#dropIdleSession(location.thread);
|
|
32540
|
+
const current = this.#threads.get(threadId2);
|
|
32541
|
+
if (current) return { kind: "external", thread: current, historyFresh: false };
|
|
32542
|
+
} else {
|
|
32543
|
+
this.idleRelease.touch(location.thread);
|
|
32544
|
+
return { kind: "external", thread: location.thread, historyFresh: false };
|
|
32545
|
+
}
|
|
32546
|
+
}
|
|
32547
|
+
return {
|
|
32548
|
+
kind: "external",
|
|
32549
|
+
thread: await this.#restore(location.record),
|
|
32550
|
+
historyFresh: true
|
|
32551
|
+
};
|
|
32368
32552
|
});
|
|
32369
|
-
|
|
32553
|
+
} catch (error51) {
|
|
32554
|
+
return { kind: "error", error: recoveryError(error51) };
|
|
32370
32555
|
}
|
|
32556
|
+
}
|
|
32557
|
+
async refresh(thread) {
|
|
32371
32558
|
try {
|
|
32372
|
-
return
|
|
32559
|
+
return await this.#withLifecycle(thread.id, () => this.#refresh(thread));
|
|
32373
32560
|
} catch (error51) {
|
|
32561
|
+
return recoveryError(error51);
|
|
32562
|
+
}
|
|
32563
|
+
}
|
|
32564
|
+
async #refresh(thread) {
|
|
32565
|
+
if (this.#threads.get(thread.id) !== thread) {
|
|
32374
32566
|
return {
|
|
32375
|
-
|
|
32376
|
-
|
|
32567
|
+
code: -32076,
|
|
32568
|
+
message: "External Session changed during recovery; retry the request"
|
|
32377
32569
|
};
|
|
32378
32570
|
}
|
|
32379
|
-
|
|
32380
|
-
|
|
32381
|
-
|
|
32382
|
-
|
|
32571
|
+
const failure = this.idleRelease.failure(thread);
|
|
32572
|
+
if (failure) return { code: -32075, message: failure };
|
|
32573
|
+
if (thread.sessionUnavailable && this.#idleForRestore(thread)) {
|
|
32574
|
+
const revived = await this.#reviveIdleSession(thread);
|
|
32575
|
+
if (revived) return revived;
|
|
32576
|
+
}
|
|
32577
|
+
let snapshot2 = await thread.session.readSnapshot();
|
|
32578
|
+
if (!snapshot2.ok) {
|
|
32579
|
+
if (snapshot2.error.code === "invalidState" && this.#idleForRestore(thread) && this.#threads.get(thread.id) === thread) {
|
|
32580
|
+
const revived = await this.#reviveIdleSession(thread);
|
|
32581
|
+
if (revived) return revived;
|
|
32582
|
+
snapshot2 = await thread.session.readSnapshot();
|
|
32583
|
+
if (!snapshot2.ok) return mapExternalThreadHarnessError(snapshot2.error, "read");
|
|
32584
|
+
} else {
|
|
32585
|
+
return mapExternalThreadHarnessError(snapshot2.error, "read");
|
|
32586
|
+
}
|
|
32587
|
+
}
|
|
32383
32588
|
try {
|
|
32384
32589
|
const aligned = await this.#repository.alignSnapshot(thread.record, snapshot2.value);
|
|
32385
32590
|
thread.record = aligned.record;
|
|
@@ -32396,6 +32601,122 @@ var ExternalThreadRuntime = class {
|
|
|
32396
32601
|
return { code: -32081, message: "External Thread history could not be persisted" };
|
|
32397
32602
|
}
|
|
32398
32603
|
}
|
|
32604
|
+
async #reviveIdleSession(thread) {
|
|
32605
|
+
thread.sessionUnavailable = true;
|
|
32606
|
+
const harnessId = thread.record.harnessId;
|
|
32607
|
+
const adapter = this.#adapters.get(harnessId);
|
|
32608
|
+
if (!adapter || !thread.record.nativeSessionRef) {
|
|
32609
|
+
await this.#dropIdleSession(thread);
|
|
32610
|
+
return { code: -32077, message: "External Harness is unavailable" };
|
|
32611
|
+
}
|
|
32612
|
+
await this.#lifecycle.release(thread.id, thread.session, thread.outputTask);
|
|
32613
|
+
await this.#retireSubagents(thread);
|
|
32614
|
+
if (this.#threads.get(thread.id) !== thread) {
|
|
32615
|
+
return { code: -32076, message: "External Thread recovery failed" };
|
|
32616
|
+
}
|
|
32617
|
+
const restoredSelection = decodeExternalTransportSelection(
|
|
32618
|
+
harnessId,
|
|
32619
|
+
thread.record.transportModelId
|
|
32620
|
+
);
|
|
32621
|
+
const opened = await adapter.open({
|
|
32622
|
+
kind: "resume",
|
|
32623
|
+
cwd: thread.record.cwd,
|
|
32624
|
+
environment: { ...this.#environment, [DELEGATION_THREAD_ID_ENV]: thread.record.hostThreadId },
|
|
32625
|
+
nativeRef: thread.record.nativeSessionRef,
|
|
32626
|
+
knownTurnRefs: thread.record.turnMappings.map(({ nativeTurnRef }) => nativeTurnRef),
|
|
32627
|
+
...restoredSelection?.model ? { model: restoredSelection.model } : {},
|
|
32628
|
+
...restoredSelection?.thinkingOptionId ? { thinkingOptionId: restoredSelection.thinkingOptionId } : {},
|
|
32629
|
+
...restoredSelection?.permissionModeId ? { permissionModeId: restoredSelection.permissionModeId } : {}
|
|
32630
|
+
});
|
|
32631
|
+
if (!opened.ok) {
|
|
32632
|
+
await this.#dropIdleSession(thread);
|
|
32633
|
+
return mapExternalThreadHarnessError(opened.error, "resume");
|
|
32634
|
+
}
|
|
32635
|
+
const session = opened.value;
|
|
32636
|
+
if (this.#threads.get(thread.id) !== thread) {
|
|
32637
|
+
await this.#lifecycle.release(thread.id, session, Promise.resolve());
|
|
32638
|
+
return { code: -32076, message: "External Thread recovery failed" };
|
|
32639
|
+
}
|
|
32640
|
+
try {
|
|
32641
|
+
const snapshot2 = await session.readSnapshot();
|
|
32642
|
+
if (!snapshot2.ok) {
|
|
32643
|
+
await this.#lifecycle.release(thread.id, session, Promise.resolve());
|
|
32644
|
+
await this.#dropIdleSession(thread);
|
|
32645
|
+
return mapExternalThreadHarnessError(snapshot2.error, "read");
|
|
32646
|
+
}
|
|
32647
|
+
let aligned = await this.#repository.alignSnapshot(thread.record, snapshot2.value);
|
|
32648
|
+
if (this.#threads.get(thread.id) !== thread) {
|
|
32649
|
+
await this.#lifecycle.release(thread.id, session, Promise.resolve());
|
|
32650
|
+
return { code: -32076, message: "External Thread recovery failed" };
|
|
32651
|
+
}
|
|
32652
|
+
const restoredState = snapshot2.value.state;
|
|
32653
|
+
const effectiveModel = restoredState ? restoredState.effectiveModel : restoredSelection?.model;
|
|
32654
|
+
const effectiveThinkingOptionId = restoredState ? restoredState.effectiveThinkingOptionId : restoredSelection?.thinkingOptionId;
|
|
32655
|
+
const effectivePermissionModeId = restoredState ? restoredState.effectivePermissionModeId : restoredSelection?.permissionModeId;
|
|
32656
|
+
let transportModelId = aligned.record.transportModelId;
|
|
32657
|
+
if (restoredState && effectiveModel) {
|
|
32658
|
+
const liveSelection = {
|
|
32659
|
+
model: effectiveModel,
|
|
32660
|
+
...effectiveThinkingOptionId ? { thinkingOptionId: effectiveThinkingOptionId } : {},
|
|
32661
|
+
...effectivePermissionModeId ? { permissionModeId: effectivePermissionModeId } : {}
|
|
32662
|
+
};
|
|
32663
|
+
transportModelId = encodeExternalTransportSelection(harnessId, liveSelection);
|
|
32664
|
+
if (transportModelId !== aligned.record.transportModelId) {
|
|
32665
|
+
try {
|
|
32666
|
+
aligned = {
|
|
32667
|
+
...aligned,
|
|
32668
|
+
record: await this.#repository.setTransportModelId(
|
|
32669
|
+
aligned.record.hostThreadId,
|
|
32670
|
+
transportModelId
|
|
32671
|
+
)
|
|
32672
|
+
};
|
|
32673
|
+
} catch (error51) {
|
|
32674
|
+
this.#diagnose(error51);
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32677
|
+
}
|
|
32678
|
+
if (this.#threads.get(thread.id) !== thread) {
|
|
32679
|
+
await this.#lifecycle.release(thread.id, session, Promise.resolve());
|
|
32680
|
+
return { code: -32076, message: "External Thread recovery failed" };
|
|
32681
|
+
}
|
|
32682
|
+
const initialState = restoredState ?? session.initialState;
|
|
32683
|
+
const observerState = {
|
|
32684
|
+
...initialState,
|
|
32685
|
+
...effectiveModel ? { effectiveModel } : {},
|
|
32686
|
+
...effectiveThinkingOptionId ? { effectiveThinkingOptionId } : {},
|
|
32687
|
+
...effectivePermissionModeId ? { effectivePermissionModeId } : {}
|
|
32688
|
+
};
|
|
32689
|
+
thread.session = session;
|
|
32690
|
+
thread.record = aligned.record;
|
|
32691
|
+
thread.turns = aligned.turns;
|
|
32692
|
+
thread.historyHydrated = true;
|
|
32693
|
+
thread.sessionUnavailable = false;
|
|
32694
|
+
thread.persistenceError = null;
|
|
32695
|
+
thread.transportModelId = transportModelId;
|
|
32696
|
+
if (effectiveModel) thread.requestedModel = effectiveModel;
|
|
32697
|
+
if (effectiveThinkingOptionId) thread.requestedThinkingOptionId = effectiveThinkingOptionId;
|
|
32698
|
+
if (effectivePermissionModeId) thread.requestedPermissionModeId = effectivePermissionModeId;
|
|
32699
|
+
thread.stateObserver = new SessionStateObserver(observerState);
|
|
32700
|
+
thread.latestUsage = session.initialUsage;
|
|
32701
|
+
thread.usageMeter = new UsageMeter();
|
|
32702
|
+
thread.usageTurnId = null;
|
|
32703
|
+
thread.thread = externalThreadValue({
|
|
32704
|
+
record: aligned.record,
|
|
32705
|
+
turns: aligned.turns,
|
|
32706
|
+
sessionId: thread.sessionId,
|
|
32707
|
+
running: thread.running
|
|
32708
|
+
});
|
|
32709
|
+
thread.outputTask = this.#consumeOutputs(thread);
|
|
32710
|
+
this.idleRelease.clearOutputFailed(thread);
|
|
32711
|
+
return null;
|
|
32712
|
+
} catch (error51) {
|
|
32713
|
+
if (error51 instanceof ExternalSessionReleaseError) throw error51;
|
|
32714
|
+
await this.#lifecycle.release(thread.id, session, Promise.resolve());
|
|
32715
|
+
this.#diagnose(error51);
|
|
32716
|
+
await this.#dropIdleSession(thread);
|
|
32717
|
+
return { code: -32081, message: "External Thread history could not be persisted" };
|
|
32718
|
+
}
|
|
32719
|
+
}
|
|
32399
32720
|
async persistTerminalIdentity(thread, event) {
|
|
32400
32721
|
if (thread.persistenceError) return thread.persistenceError;
|
|
32401
32722
|
if (!event.nativeTurnRef) {
|
|
@@ -32534,7 +32855,8 @@ var ExternalThreadRuntime = class {
|
|
|
32534
32855
|
transportModelId
|
|
32535
32856
|
});
|
|
32536
32857
|
} catch (error51) {
|
|
32537
|
-
|
|
32858
|
+
if (error51 instanceof ExternalSessionReleaseError) throw error51;
|
|
32859
|
+
await this.#lifecycle.release(record4.hostThreadId, session, Promise.resolve());
|
|
32538
32860
|
throw error51;
|
|
32539
32861
|
}
|
|
32540
32862
|
}
|
|
@@ -37684,6 +38006,7 @@ var AppServerHost = class {
|
|
|
37684
38006
|
#launchSettings;
|
|
37685
38007
|
#accountInspections = new HarnessAccountInspectionCache();
|
|
37686
38008
|
#externalRuntime;
|
|
38009
|
+
#executionHosts = /* @__PURE__ */ new WeakMap();
|
|
37687
38010
|
#externalPrewarms = new ExternalThreadPrewarms();
|
|
37688
38011
|
#externalSteering = new ExternalTurnSteering();
|
|
37689
38012
|
#liveCommandCache = new LiveCommandCatalogCache();
|
|
@@ -37818,6 +38141,7 @@ var AppServerHost = class {
|
|
|
37818
38141
|
consumeOutputs: (thread) => this.#consumeHarnessOutputs(thread),
|
|
37819
38142
|
diagnose: (error51) => this.#diagnose(error51),
|
|
37820
38143
|
subagentRunning: (threadId2) => this.#subagentThreadStatuses.get(threadId2) === "active",
|
|
38144
|
+
hasRunningDescendants: (threadId2) => this.#hasRunningSubagents(threadId2),
|
|
37821
38145
|
idleRelease: {
|
|
37822
38146
|
queue: this.#desktopRequests,
|
|
37823
38147
|
onClosed: async (thread) => {
|
|
@@ -37876,7 +38200,11 @@ var AppServerHost = class {
|
|
|
37876
38200
|
wait: (input) => this.#waitForPlugins().then(() => this.#delegationCoordinator.wait(input)),
|
|
37877
38201
|
list: (input) => this.#waitForPlugins().then(() => this.#delegationCoordinator.list(input)),
|
|
37878
38202
|
canHandleStart: (input) => this.#canHandleDelegationStart(input),
|
|
37879
|
-
ownsThread: (threadId2) => this.#ownsDelegationThread(threadId2)
|
|
38203
|
+
ownsThread: (threadId2) => this.#ownsDelegationThread(threadId2),
|
|
38204
|
+
executionHostId: (threadId2) => {
|
|
38205
|
+
const thread = this.#externalRuntime.get(threadId2);
|
|
38206
|
+
return thread ? this.#executionHosts.get(thread) : void 0;
|
|
38207
|
+
}
|
|
37880
38208
|
});
|
|
37881
38209
|
this.#unregisterDelegationApi = typeof unregisterDelegationApi === "function" ? unregisterDelegationApi : void 0;
|
|
37882
38210
|
}
|
|
@@ -38013,6 +38341,7 @@ var AppServerHost = class {
|
|
|
38013
38341
|
await Promise.allSettled(threads.map(({ session }) => session.close()));
|
|
38014
38342
|
await Promise.allSettled(threads.map(({ outputTask }) => outputTask));
|
|
38015
38343
|
await this.#closeAdapters();
|
|
38344
|
+
await this.#externalRuntime.drain();
|
|
38016
38345
|
for (const pending of [...this.#pendingDesktopApprovals.values()]) {
|
|
38017
38346
|
await this.#resolveDesktopApproval(pending.interaction.interactionId).catch(
|
|
38018
38347
|
() => void 0
|
|
@@ -40206,7 +40535,7 @@ var AppServerHost = class {
|
|
|
40206
40535
|
this.#pendingExternalCommandRequests.delete(thread.id);
|
|
40207
40536
|
}
|
|
40208
40537
|
}
|
|
40209
|
-
async #beginExternalCommand(thread, descriptor, arguments_2, requestedTurnId) {
|
|
40538
|
+
async #beginExternalCommand(thread, descriptor, arguments_2, requestedTurnId, sourceHostId) {
|
|
40210
40539
|
const commands = thread.session.commands;
|
|
40211
40540
|
if (!commands) {
|
|
40212
40541
|
throw new ExternalCommandError(-32078, "External Harness does not expose commands");
|
|
@@ -40235,6 +40564,7 @@ var AppServerHost = class {
|
|
|
40235
40564
|
thread.ephemeralTurnIds.add(turnId);
|
|
40236
40565
|
if (descriptor.invocation === "/compact") this.#manualCompactionTurns.set(thread, turnId);
|
|
40237
40566
|
else this.#manualCompactionTurns.delete(thread);
|
|
40567
|
+
const restoreExecutionHost = this.#setExecutionHost(thread, sourceHostId);
|
|
40238
40568
|
try {
|
|
40239
40569
|
if (thread.unsubmittedPrewarm && await this.#externalRuntime.submitPrewarm(thread)) {
|
|
40240
40570
|
await this.#notifyExternalThreadStarted(thread.thread);
|
|
@@ -40247,6 +40577,7 @@ var AppServerHost = class {
|
|
|
40247
40577
|
if (!result.ok) throw new ExternalCommandError(-32073, result.error.message);
|
|
40248
40578
|
return { turnId: result.value.turnId, turn: projection.projector.pendingTurn(), gate };
|
|
40249
40579
|
} catch (error51) {
|
|
40580
|
+
restoreExecutionHost();
|
|
40250
40581
|
thread.running = false;
|
|
40251
40582
|
thread.activeTurnId = null;
|
|
40252
40583
|
thread.projectedTurns.delete(turnId);
|
|
@@ -41026,7 +41357,8 @@ var AppServerHost = class {
|
|
|
41026
41357
|
thread,
|
|
41027
41358
|
text,
|
|
41028
41359
|
void 0,
|
|
41029
|
-
typeof params.clientUserMessageId === "string" ? params.clientUserMessageId : void 0
|
|
41360
|
+
typeof params.clientUserMessageId === "string" ? params.clientUserMessageId : void 0,
|
|
41361
|
+
params[THREAD_REFERENCE_SOURCE_HOST_PARAM]
|
|
41030
41362
|
);
|
|
41031
41363
|
try {
|
|
41032
41364
|
await this.#writer.json(rpcEnvelope(request, { result: { turn: started.turn } }));
|
|
@@ -41052,7 +41384,8 @@ var AppServerHost = class {
|
|
|
41052
41384
|
thread,
|
|
41053
41385
|
text,
|
|
41054
41386
|
assertActive,
|
|
41055
|
-
typeof requestObject(request).clientUserMessageId === "string" ? requestObject(request).clientUserMessageId : void 0
|
|
41387
|
+
typeof requestObject(request).clientUserMessageId === "string" ? requestObject(request).clientUserMessageId : void 0,
|
|
41388
|
+
requestObject(request)[THREAD_REFERENCE_SOURCE_HOST_PARAM]
|
|
41056
41389
|
)
|
|
41057
41390
|
);
|
|
41058
41391
|
try {
|
|
@@ -41073,12 +41406,12 @@ var AppServerHost = class {
|
|
|
41073
41406
|
}
|
|
41074
41407
|
}
|
|
41075
41408
|
/** Keep command restoration and dispatch identical for start and steer submissions. */
|
|
41076
|
-
async #beginExternalInputTurn(thread, inputText, assertActive, clientUserMessageId) {
|
|
41409
|
+
async #beginExternalInputTurn(thread, inputText, assertActive, clientUserMessageId, sourceHostId) {
|
|
41077
41410
|
const text = restoreHarnessCommandMentions(inputText);
|
|
41078
41411
|
const commands = thread.session.commands;
|
|
41079
41412
|
if (!commands || !isExternalCommandCandidate(text)) {
|
|
41080
41413
|
assertActive?.();
|
|
41081
|
-
return this.#beginExternalTurn(thread, text, clientUserMessageId);
|
|
41414
|
+
return this.#beginExternalTurn(thread, text, clientUserMessageId, sourceHostId);
|
|
41082
41415
|
}
|
|
41083
41416
|
this.#pendingExternalCommandRequests.add(thread.id);
|
|
41084
41417
|
try {
|
|
@@ -41089,9 +41422,15 @@ var AppServerHost = class {
|
|
|
41089
41422
|
assertActive?.();
|
|
41090
41423
|
if (!command) {
|
|
41091
41424
|
this.#pendingExternalCommandRequests.delete(thread.id);
|
|
41092
|
-
return await this.#beginExternalTurn(thread, text, clientUserMessageId);
|
|
41425
|
+
return await this.#beginExternalTurn(thread, text, clientUserMessageId, sourceHostId);
|
|
41093
41426
|
}
|
|
41094
|
-
return await this.#beginExternalCommand(
|
|
41427
|
+
return await this.#beginExternalCommand(
|
|
41428
|
+
thread,
|
|
41429
|
+
command.descriptor,
|
|
41430
|
+
command.arguments,
|
|
41431
|
+
void 0,
|
|
41432
|
+
sourceHostId
|
|
41433
|
+
);
|
|
41095
41434
|
} catch (error51) {
|
|
41096
41435
|
if (error51 instanceof ExternalCommandError || error51 instanceof ExternalSteerError) throw error51;
|
|
41097
41436
|
this.#diagnose(error51);
|
|
@@ -41103,7 +41442,20 @@ var AppServerHost = class {
|
|
|
41103
41442
|
this.#pendingExternalCommandRequests.delete(thread.id);
|
|
41104
41443
|
}
|
|
41105
41444
|
}
|
|
41106
|
-
|
|
41445
|
+
/** Apply context before native execution; a rejected start restores the prior identity. */
|
|
41446
|
+
#setExecutionHost(thread, sourceHostId) {
|
|
41447
|
+
const previousHostId = this.#executionHosts.get(thread);
|
|
41448
|
+
if (typeof sourceHostId === "string" && sourceHostId.length > 0 && sourceHostId.length <= 1024 && !/[\u0000-\u0020\u007f]/u.test(sourceHostId)) {
|
|
41449
|
+
this.#executionHosts.set(thread, sourceHostId);
|
|
41450
|
+
} else {
|
|
41451
|
+
this.#executionHosts.delete(thread);
|
|
41452
|
+
}
|
|
41453
|
+
return () => {
|
|
41454
|
+
if (previousHostId === void 0) this.#executionHosts.delete(thread);
|
|
41455
|
+
else this.#executionHosts.set(thread, previousHostId);
|
|
41456
|
+
};
|
|
41457
|
+
}
|
|
41458
|
+
async #beginExternalTurn(thread, text, clientUserMessageId, sourceHostId) {
|
|
41107
41459
|
if (this.#closeRequested || this.#externalRuntime.get(thread.id) !== thread) {
|
|
41108
41460
|
throw new ExternalSteerError(-32073, "External Thread is no longer available");
|
|
41109
41461
|
}
|
|
@@ -41130,6 +41482,7 @@ var AppServerHost = class {
|
|
|
41130
41482
|
thread.projectedTerminalTurnId = null;
|
|
41131
41483
|
thread.projectedTurns.set(turnId, projection);
|
|
41132
41484
|
thread.responseGates.set(turnId, gate);
|
|
41485
|
+
const restoreExecutionHost = this.#setExecutionHost(thread, sourceHostId);
|
|
41133
41486
|
try {
|
|
41134
41487
|
if (thread.unsubmittedPrewarm && await this.#externalRuntime.submitPrewarm(thread)) {
|
|
41135
41488
|
await this.#notifyExternalThreadStarted(thread.thread);
|
|
@@ -41142,6 +41495,7 @@ var AppServerHost = class {
|
|
|
41142
41495
|
if (!result.ok) throw new ExternalSteerError(-32073, result.error.message);
|
|
41143
41496
|
return { turnId, turn: projection.projector.pendingTurn(), gate };
|
|
41144
41497
|
} catch (error51) {
|
|
41498
|
+
restoreExecutionHost();
|
|
41145
41499
|
thread.running = false;
|
|
41146
41500
|
thread.activeTurnId = null;
|
|
41147
41501
|
thread.projectedTurns.delete(turnId);
|
|
@@ -41370,7 +41724,11 @@ var AppServerHost = class {
|
|
|
41370
41724
|
if (event.type === "session.faulted") {
|
|
41371
41725
|
this.#externalSteering.fault(thread.id, new Error(event.error.message));
|
|
41372
41726
|
thread.stateObserver.fault(new Error(event.error.message));
|
|
41727
|
+
this.#externalRuntime.markSessionUnavailable(thread);
|
|
41373
41728
|
this.#diagnose(`${thread.harnessId} Harness Session faulted: ${event.error.message}`);
|
|
41729
|
+
if (!thread.running && !thread.activeTurnId && !this.#hasRunningSubagents(thread.id) && !thread.session.hasBackgroundWork?.()) {
|
|
41730
|
+
void this.#externalRuntime.discardUnavailableIdle(thread);
|
|
41731
|
+
}
|
|
41374
41732
|
return;
|
|
41375
41733
|
}
|
|
41376
41734
|
if (event.type === "turn.autonomous.started") {
|
|
@@ -41499,6 +41857,11 @@ var AppServerHost = class {
|
|
|
41499
41857
|
return { ...subagent, subagentId: record4.hostThreadId };
|
|
41500
41858
|
}
|
|
41501
41859
|
async #refreshOpenSubagentThread(threadId2, terminal3 = true) {
|
|
41860
|
+
await this.#desktopRequests.run(threadId2, async () => {
|
|
41861
|
+
await this.#refreshOpenSubagentThreadBody(threadId2, terminal3);
|
|
41862
|
+
});
|
|
41863
|
+
}
|
|
41864
|
+
async #refreshOpenSubagentThreadBody(threadId2, terminal3) {
|
|
41502
41865
|
const child = this.#externalRuntime.get(threadId2);
|
|
41503
41866
|
if (!child) return;
|
|
41504
41867
|
const previousItems = new Map(
|
|
@@ -43523,15 +43886,28 @@ var DelegationControlRegistry = class {
|
|
|
43523
43886
|
return (await this.#registrationForThread(input.threadId)).cancel(input);
|
|
43524
43887
|
}
|
|
43525
43888
|
async read(input) {
|
|
43526
|
-
|
|
43527
|
-
|
|
43889
|
+
const { callerThreadId: callerThreadId2, ...read } = input;
|
|
43890
|
+
if (read.hostId !== void 0 && read.hostId !== "local") {
|
|
43891
|
+
if (typeof callerThreadId2 === "string" && callerThreadId2) {
|
|
43892
|
+
const callers = await this.#matching(
|
|
43893
|
+
(registration) => registration.executionHostId?.(callerThreadId2) === read.hostId && registration.ownsThread(callerThreadId2)
|
|
43894
|
+
);
|
|
43895
|
+
if (callers.length > 1)
|
|
43896
|
+
only(callers, "Calling Thread has ambiguous execution Host context");
|
|
43897
|
+
if (callers.length === 1) {
|
|
43898
|
+
const { hostId: hostId2, ...local } = read;
|
|
43899
|
+
const snapshot2 = await (await this.#registrationForThread(local.threadId)).read(local);
|
|
43900
|
+
return { ...snapshot2, hostId: hostId2 };
|
|
43901
|
+
}
|
|
43902
|
+
}
|
|
43903
|
+
if (!read.hostId || !this.#remoteRead)
|
|
43528
43904
|
throw new DelegationControlError(
|
|
43529
43905
|
"RUNTIME_UNREACHABLE",
|
|
43530
43906
|
"Remote Thread reading is unavailable"
|
|
43531
43907
|
);
|
|
43532
|
-
return this.#remoteRead(
|
|
43908
|
+
return this.#remoteRead(read);
|
|
43533
43909
|
}
|
|
43534
|
-
return (await this.#registrationForThread(
|
|
43910
|
+
return (await this.#registrationForThread(read.threadId)).read(read);
|
|
43535
43911
|
}
|
|
43536
43912
|
async wait(input) {
|
|
43537
43913
|
if (input.hostId !== void 0 && input.hostId !== "local")
|
|
@@ -45555,9 +45931,21 @@ function requiredRuntimeConfiguration(environment) {
|
|
|
45555
45931
|
if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
|
|
45556
45932
|
return { stockCodexPath };
|
|
45557
45933
|
}
|
|
45558
|
-
function delegationCliEnvironment(environment) {
|
|
45559
|
-
|
|
45560
|
-
|
|
45934
|
+
function delegationCliEnvironment(environment, hostRuntimePath) {
|
|
45935
|
+
let packagedCli;
|
|
45936
|
+
if (hostRuntimePath && path37.isAbsolute(hostRuntimePath)) {
|
|
45937
|
+
const candidate = path37.resolve(
|
|
45938
|
+
path37.dirname(hostRuntimePath),
|
|
45939
|
+
"../bin",
|
|
45940
|
+
process.platform === "win32" ? "codexhost.exe" : "codexhost"
|
|
45941
|
+
);
|
|
45942
|
+
try {
|
|
45943
|
+
if (statSync(candidate).isFile()) packagedCli = candidate;
|
|
45944
|
+
} catch {
|
|
45945
|
+
}
|
|
45946
|
+
}
|
|
45947
|
+
const cliPath = environment[DELEGATION_CLI_PATH_ENV] ?? environment[UPDATE_RUNTIME_ENV.launcherExecutable] ?? packagedCli;
|
|
45948
|
+
const nodePath2 = environment[DELEGATION_CLI_NODE_PATH_ENV] ?? environment[UPDATE_RUNTIME_ENV.npmNodePath] ?? (packagedCli && cliPath === packagedCli ? process.execPath : void 0);
|
|
45561
45949
|
return {
|
|
45562
45950
|
...cliPath ? { [DELEGATION_CLI_PATH_ENV]: cliPath } : {},
|
|
45563
45951
|
...nodePath2 && path37.isAbsolute(nodePath2) ? { [DELEGATION_CLI_NODE_PATH_ENV]: nodePath2 } : {}
|
|
@@ -45575,7 +45963,7 @@ async function prepareDelegationRuntime(input) {
|
|
|
45575
45963
|
const server = await startDelegationControlServer({ token, api: registry2, watchApi: registry2 });
|
|
45576
45964
|
const environment = {
|
|
45577
45965
|
...input.environment,
|
|
45578
|
-
...delegationCliEnvironment(input.environment),
|
|
45966
|
+
...delegationCliEnvironment(input.environment, input.hostRuntimePath),
|
|
45579
45967
|
[DELEGATION_RUNTIME_ENDPOINT_ENV]: server.endpoint,
|
|
45580
45968
|
[DELEGATION_RUNTIME_TOKEN_ENV]: token
|
|
45581
45969
|
};
|
|
@@ -45650,6 +46038,7 @@ async function runHostRuntime(input) {
|
|
|
45650
46038
|
const environment = remoteControlPlan?.environment ?? input.environment;
|
|
45651
46039
|
return prepareDelegationRuntime({
|
|
45652
46040
|
environment,
|
|
46041
|
+
...hostRuntimePath ? { hostRuntimePath } : {},
|
|
45653
46042
|
createHost: async (delegationEnvironment, onDelegationApi, registry2) => {
|
|
45654
46043
|
const official = await prepareLocalCodex({
|
|
45655
46044
|
stockCodexPath,
|
|
@@ -45758,6 +46147,7 @@ async function runHostRuntime(input) {
|
|
|
45758
46147
|
if (!listenUrl) throw new Error("Remote app-server listener URL is unavailable");
|
|
45759
46148
|
return prepareDelegationRuntime({
|
|
45760
46149
|
environment: input.environment,
|
|
46150
|
+
...hostRuntimePath ? { hostRuntimePath } : {},
|
|
45761
46151
|
createHost: async (delegationEnvironment, _onDelegationApi, registry2) => {
|
|
45762
46152
|
const socketPath = remoteAppServerSocketPath(delegationEnvironment, listenUrl);
|
|
45763
46153
|
const officialPlan = createRemoteOfficialAppServerPlan(input.arguments, socketPath);
|
|
@@ -47991,7 +48381,7 @@ import {
|
|
|
47991
48381
|
readdirSync,
|
|
47992
48382
|
renameSync,
|
|
47993
48383
|
rmSync,
|
|
47994
|
-
statSync,
|
|
48384
|
+
statSync as statSync2,
|
|
47995
48385
|
writeFileSync
|
|
47996
48386
|
} from "node:fs";
|
|
47997
48387
|
import os12 from "node:os";
|
|
@@ -48021,7 +48411,7 @@ function pruneRuntimeLogs(input) {
|
|
|
48021
48411
|
if (!Number.isSafeInteger(pid)) continue;
|
|
48022
48412
|
try {
|
|
48023
48413
|
const filePath = path42.join(input.directory, entry.name);
|
|
48024
|
-
const stats =
|
|
48414
|
+
const stats = statSync2(filePath);
|
|
48025
48415
|
files.push({
|
|
48026
48416
|
filePath,
|
|
48027
48417
|
pid,
|