@codexhost/cli-darwin-arm64 0.1.0-test.1 → 0.1.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.
- package/README.md +1 -1
- package/app/codexhost-distribution.json +1 -0
- package/app/desktop-controller.mjs +322 -173
- package/app/host-runtime.mjs +1628 -326
- package/app/renderer-extension.js +1404 -483
- 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
|
@@ -4,6 +4,9 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// packages/host-runtime/src/release-main.ts
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
7
10
|
// node_modules/zod/v4/classic/external.js
|
|
8
11
|
var external_exports = {};
|
|
9
12
|
__export(external_exports, {
|
|
@@ -726,9 +729,9 @@ function floatSafeRemainder(val, step) {
|
|
|
726
729
|
return ratio - roundedRatio;
|
|
727
730
|
}
|
|
728
731
|
var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
|
|
729
|
-
function defineLazy(
|
|
732
|
+
function defineLazy(object3, key, getter) {
|
|
730
733
|
let value = void 0;
|
|
731
|
-
Object.defineProperty(
|
|
734
|
+
Object.defineProperty(object3, key, {
|
|
732
735
|
get() {
|
|
733
736
|
if (value === EVALUATING) {
|
|
734
737
|
return void 0;
|
|
@@ -740,7 +743,7 @@ function defineLazy(object2, key, getter) {
|
|
|
740
743
|
return value;
|
|
741
744
|
},
|
|
742
745
|
set(v2) {
|
|
743
|
-
Object.defineProperty(
|
|
746
|
+
Object.defineProperty(object3, key, {
|
|
744
747
|
value: v2
|
|
745
748
|
// configurable: true,
|
|
746
749
|
});
|
|
@@ -770,10 +773,10 @@ function mergeDefs(...defs) {
|
|
|
770
773
|
function cloneDef(schema) {
|
|
771
774
|
return mergeDefs(schema._zod.def);
|
|
772
775
|
}
|
|
773
|
-
function getElementAtPath(obj,
|
|
774
|
-
if (!
|
|
776
|
+
function getElementAtPath(obj, path11) {
|
|
777
|
+
if (!path11)
|
|
775
778
|
return obj;
|
|
776
|
-
return
|
|
779
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
777
780
|
}
|
|
778
781
|
function promiseAllObject(promisesObj) {
|
|
779
782
|
const keys = Object.keys(promisesObj);
|
|
@@ -1182,11 +1185,11 @@ function explicitlyAborted(x2, startIndex = 0) {
|
|
|
1182
1185
|
}
|
|
1183
1186
|
return false;
|
|
1184
1187
|
}
|
|
1185
|
-
function prefixIssues(
|
|
1188
|
+
function prefixIssues(path11, issues) {
|
|
1186
1189
|
return issues.map((iss) => {
|
|
1187
1190
|
var _a4;
|
|
1188
1191
|
(_a4 = iss).path ?? (_a4.path = []);
|
|
1189
|
-
iss.path.unshift(
|
|
1192
|
+
iss.path.unshift(path11);
|
|
1190
1193
|
return iss;
|
|
1191
1194
|
});
|
|
1192
1195
|
}
|
|
@@ -1333,16 +1336,16 @@ function flattenError(error52, mapper = (issue2) => issue2.message) {
|
|
|
1333
1336
|
}
|
|
1334
1337
|
function formatError(error52, mapper = (issue2) => issue2.message) {
|
|
1335
1338
|
const fieldErrors = { _errors: [] };
|
|
1336
|
-
const processError = (error53,
|
|
1339
|
+
const processError = (error53, path11 = []) => {
|
|
1337
1340
|
for (const issue2 of error53.issues) {
|
|
1338
1341
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1339
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1342
|
+
issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
|
|
1340
1343
|
} else if (issue2.code === "invalid_key") {
|
|
1341
|
-
processError({ issues: issue2.issues }, [...
|
|
1344
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1342
1345
|
} else if (issue2.code === "invalid_element") {
|
|
1343
|
-
processError({ issues: issue2.issues }, [...
|
|
1346
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1344
1347
|
} else {
|
|
1345
|
-
const fullpath = [...
|
|
1348
|
+
const fullpath = [...path11, ...issue2.path];
|
|
1346
1349
|
if (fullpath.length === 0) {
|
|
1347
1350
|
fieldErrors._errors.push(mapper(issue2));
|
|
1348
1351
|
} else {
|
|
@@ -1369,17 +1372,17 @@ function formatError(error52, mapper = (issue2) => issue2.message) {
|
|
|
1369
1372
|
}
|
|
1370
1373
|
function treeifyError(error52, mapper = (issue2) => issue2.message) {
|
|
1371
1374
|
const result = { errors: [] };
|
|
1372
|
-
const processError = (error53,
|
|
1375
|
+
const processError = (error53, path11 = []) => {
|
|
1373
1376
|
var _a4, _b2;
|
|
1374
1377
|
for (const issue2 of error53.issues) {
|
|
1375
1378
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1376
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1379
|
+
issue2.errors.map((issues) => processError({ issues }, [...path11, ...issue2.path]));
|
|
1377
1380
|
} else if (issue2.code === "invalid_key") {
|
|
1378
|
-
processError({ issues: issue2.issues }, [...
|
|
1381
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1379
1382
|
} else if (issue2.code === "invalid_element") {
|
|
1380
|
-
processError({ issues: issue2.issues }, [...
|
|
1383
|
+
processError({ issues: issue2.issues }, [...path11, ...issue2.path]);
|
|
1381
1384
|
} else {
|
|
1382
|
-
const fullpath = [...
|
|
1385
|
+
const fullpath = [...path11, ...issue2.path];
|
|
1383
1386
|
if (fullpath.length === 0) {
|
|
1384
1387
|
result.errors.push(mapper(issue2));
|
|
1385
1388
|
continue;
|
|
@@ -1411,8 +1414,8 @@ function treeifyError(error52, mapper = (issue2) => issue2.message) {
|
|
|
1411
1414
|
}
|
|
1412
1415
|
function toDotPath(_path) {
|
|
1413
1416
|
const segs = [];
|
|
1414
|
-
const
|
|
1415
|
-
for (const seg of
|
|
1417
|
+
const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1418
|
+
for (const seg of path11) {
|
|
1416
1419
|
if (typeof seg === "number")
|
|
1417
1420
|
segs.push(`[${seg}]`);
|
|
1418
1421
|
else if (typeof seg === "symbol")
|
|
@@ -14104,13 +14107,13 @@ function resolveRef(ref, ctx) {
|
|
|
14104
14107
|
if (!ref.startsWith("#")) {
|
|
14105
14108
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14106
14109
|
}
|
|
14107
|
-
const
|
|
14108
|
-
if (
|
|
14110
|
+
const path11 = ref.slice(1).split("/").filter(Boolean);
|
|
14111
|
+
if (path11.length === 0) {
|
|
14109
14112
|
return ctx.rootSchema;
|
|
14110
14113
|
}
|
|
14111
14114
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14112
|
-
if (
|
|
14113
|
-
const key =
|
|
14115
|
+
if (path11[0] === defsKey) {
|
|
14116
|
+
const key = path11[1];
|
|
14114
14117
|
if (!key || !ctx.defs[key]) {
|
|
14115
14118
|
throw new Error(`Reference not found: ${ref}`);
|
|
14116
14119
|
}
|
|
@@ -14650,6 +14653,50 @@ var threadPermissionModeSelectParamsSchema = external_exports.object({
|
|
|
14650
14653
|
permissionModeId: harnessPermissionModeIdSchema
|
|
14651
14654
|
}).strict();
|
|
14652
14655
|
|
|
14656
|
+
// packages/shared-contracts/dist/thread-usage.js
|
|
14657
|
+
var nonNegativeSafeIntegerSchema = external_exports.number().int().safe().nonnegative();
|
|
14658
|
+
var finiteNonNegativeNumberSchema = external_exports.number().finite().nonnegative();
|
|
14659
|
+
var cacheHitRatePercentSchema = external_exports.number().finite().min(0).max(100);
|
|
14660
|
+
var threadUsageSnapshotSchema = external_exports.object({
|
|
14661
|
+
inputTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14662
|
+
cachedInputTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14663
|
+
cacheWriteInputTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14664
|
+
outputTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14665
|
+
reasoningOutputTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14666
|
+
totalTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14667
|
+
totalCostUsd: finiteNonNegativeNumberSchema.optional(),
|
|
14668
|
+
cacheHitRatePercent: cacheHitRatePercentSchema.optional(),
|
|
14669
|
+
contextWindowTokens: nonNegativeSafeIntegerSchema.optional(),
|
|
14670
|
+
contextUsedTokens: nonNegativeSafeIntegerSchema.optional()
|
|
14671
|
+
}).strict().superRefine((usage, context) => {
|
|
14672
|
+
if (Object.keys(usage).length === 0) {
|
|
14673
|
+
context.addIssue({ code: "custom", message: "Thread Usage must contain a reliable field" });
|
|
14674
|
+
}
|
|
14675
|
+
const hasContextUsed = usage.contextUsedTokens !== void 0;
|
|
14676
|
+
const hasContextWindow = usage.contextWindowTokens !== void 0;
|
|
14677
|
+
if (hasContextUsed !== hasContextWindow) {
|
|
14678
|
+
context.addIssue({
|
|
14679
|
+
code: "custom",
|
|
14680
|
+
message: "Thread Usage context fields must be provided together",
|
|
14681
|
+
path: [hasContextUsed ? "contextWindowTokens" : "contextUsedTokens"]
|
|
14682
|
+
});
|
|
14683
|
+
}
|
|
14684
|
+
if (usage.contextWindowTokens === 0) {
|
|
14685
|
+
context.addIssue({
|
|
14686
|
+
code: "custom",
|
|
14687
|
+
message: "Thread Usage contextWindowTokens must be greater than zero",
|
|
14688
|
+
path: ["contextWindowTokens"]
|
|
14689
|
+
});
|
|
14690
|
+
}
|
|
14691
|
+
});
|
|
14692
|
+
var threadUsageInspectionParamsSchema = external_exports.object({
|
|
14693
|
+
threadId: hostThreadIdSchema
|
|
14694
|
+
}).strict();
|
|
14695
|
+
var threadUsageInspectionSchema = external_exports.object({
|
|
14696
|
+
threadId: hostThreadIdSchema,
|
|
14697
|
+
usage: threadUsageSnapshotSchema.nullable()
|
|
14698
|
+
}).strict();
|
|
14699
|
+
|
|
14653
14700
|
// packages/shared-contracts/dist/harness-models.js
|
|
14654
14701
|
var HARNESS_MODEL_REF_MAX_LENGTH = 512;
|
|
14655
14702
|
var HARNESS_MODEL_LABEL_MAX_LENGTH = 256;
|
|
@@ -14740,7 +14787,8 @@ var harnessModelCatalogSchema = external_exports.object({
|
|
|
14740
14787
|
});
|
|
14741
14788
|
var harnessHistoryCapabilitiesSchema = external_exports.object({
|
|
14742
14789
|
fork: external_exports.boolean(),
|
|
14743
|
-
forkAcrossCwd: external_exports.boolean()
|
|
14790
|
+
forkAcrossCwd: external_exports.boolean(),
|
|
14791
|
+
rollbackLastTurn: external_exports.boolean()
|
|
14744
14792
|
}).strict().refine((history) => history.fork || !history.forkAcrossCwd, {
|
|
14745
14793
|
path: ["forkAcrossCwd"],
|
|
14746
14794
|
message: "Cross-cwd Fork requires exact history Fork support"
|
|
@@ -14822,6 +14870,7 @@ var externalThreadInspectionSchema = external_exports.object({
|
|
|
14822
14870
|
availableThinkingOptions: harnessThinkingOptionsSchema.optional(),
|
|
14823
14871
|
effectivePermissionModeId: harnessPermissionModeIdSchema.optional(),
|
|
14824
14872
|
history: harnessHistoryCapabilitiesSchema,
|
|
14873
|
+
usage: threadUsageSnapshotSchema.optional(),
|
|
14825
14874
|
locked: external_exports.literal(true)
|
|
14826
14875
|
}).strict();
|
|
14827
14876
|
var threadInspectionSchema = external_exports.discriminatedUnion("owner", [
|
|
@@ -14950,6 +14999,45 @@ var nativeCheckpointRefV1RuntimeSchema = external_exports.strictObject({
|
|
|
14950
14999
|
var nativeCheckpointRefV1Schema = nativeCheckpointRefV1RuntimeSchema;
|
|
14951
15000
|
var nativeCheckpointRefSchema = nativeCheckpointRefV1Schema;
|
|
14952
15001
|
|
|
15002
|
+
// packages/shared-contracts/dist/updates.js
|
|
15003
|
+
var UPDATE_ERROR_MAX_LENGTH = 500;
|
|
15004
|
+
var UPDATE_SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
|
|
15005
|
+
var updateSemanticVersionSchema = external_exports.string().regex(UPDATE_SEMVER_PATTERN);
|
|
15006
|
+
var updateInstallationSchema = external_exports.enum(["npm", "windows-installer", "macos-dmg"]);
|
|
15007
|
+
var updatePhaseSchema = external_exports.enum([
|
|
15008
|
+
"prepared",
|
|
15009
|
+
"waiting-for-exit",
|
|
15010
|
+
"installing",
|
|
15011
|
+
"restarting",
|
|
15012
|
+
"succeeded",
|
|
15013
|
+
"failed"
|
|
15014
|
+
]);
|
|
15015
|
+
var updateStatusSchema = external_exports.strictObject({
|
|
15016
|
+
version: updateSemanticVersionSchema,
|
|
15017
|
+
installation: updateInstallationSchema,
|
|
15018
|
+
phase: updatePhaseSchema,
|
|
15019
|
+
updatedAt: external_exports.number().int().nonnegative(),
|
|
15020
|
+
error: external_exports.string().min(1).max(UPDATE_ERROR_MAX_LENGTH).nullable()
|
|
15021
|
+
});
|
|
15022
|
+
var updateEmptyParamsSchema = external_exports.strictObject({});
|
|
15023
|
+
var githubReleaseNotesUrlSchema = external_exports.string().max(300).regex(/^https:\/\/github\.com\/BytePioneer-AI\/codex-host\/releases\/tag\/v[0-9A-Za-z.+-]+$/u, "release notes URL must identify a codexhost GitHub Release");
|
|
15024
|
+
var updateCheckResultSchema = external_exports.strictObject({
|
|
15025
|
+
currentVersion: updateSemanticVersionSchema,
|
|
15026
|
+
latestVersion: updateSemanticVersionSchema.nullable(),
|
|
15027
|
+
updateAvailable: external_exports.boolean(),
|
|
15028
|
+
installationAvailable: external_exports.boolean(),
|
|
15029
|
+
releaseNotes: external_exports.string().min(1).max(2e4).nullable(),
|
|
15030
|
+
releaseNotesUrl: githubReleaseNotesUrlSchema.nullable(),
|
|
15031
|
+
status: updateStatusSchema.nullable(),
|
|
15032
|
+
error: external_exports.string().min(1).max(UPDATE_ERROR_MAX_LENGTH).nullable()
|
|
15033
|
+
});
|
|
15034
|
+
var updateStartResultSchema = external_exports.strictObject({
|
|
15035
|
+
status: updateStatusSchema
|
|
15036
|
+
});
|
|
15037
|
+
var updateStatusResultSchema = external_exports.strictObject({
|
|
15038
|
+
status: updateStatusSchema.nullable()
|
|
15039
|
+
});
|
|
15040
|
+
|
|
14953
15041
|
// packages/shared-contracts/dist/index.js
|
|
14954
15042
|
var workspaceContractVersionSchema = external_exports.literal(WORKSPACE_CONTRACT_VERSION);
|
|
14955
15043
|
|
|
@@ -15057,7 +15145,11 @@ var tokenFields = [
|
|
|
15057
15145
|
"contextWindowTokens",
|
|
15058
15146
|
"contextUsedTokens"
|
|
15059
15147
|
];
|
|
15060
|
-
var usageFields = /* @__PURE__ */ new Set([
|
|
15148
|
+
var usageFields = /* @__PURE__ */ new Set([
|
|
15149
|
+
...tokenFields,
|
|
15150
|
+
"totalCostUsd",
|
|
15151
|
+
"cacheHitRatePercent"
|
|
15152
|
+
]);
|
|
15061
15153
|
function isRecord(value) {
|
|
15062
15154
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15063
15155
|
}
|
|
@@ -15081,6 +15173,9 @@ function parseHostUsage(value) {
|
|
|
15081
15173
|
if (value.totalCostUsd !== void 0 && (typeof value.totalCostUsd !== "number" || !Number.isFinite(value.totalCostUsd) || value.totalCostUsd < 0)) {
|
|
15082
15174
|
throw new Error("Harness Usage 'totalCostUsd' must be a finite non-negative number");
|
|
15083
15175
|
}
|
|
15176
|
+
if (value.cacheHitRatePercent !== void 0 && (typeof value.cacheHitRatePercent !== "number" || !Number.isFinite(value.cacheHitRatePercent) || value.cacheHitRatePercent < 0 || value.cacheHitRatePercent > 100)) {
|
|
15177
|
+
throw new Error("Harness Usage 'cacheHitRatePercent' must be between 0 and 100");
|
|
15178
|
+
}
|
|
15084
15179
|
const hasContextUsed = value.contextUsedTokens !== void 0;
|
|
15085
15180
|
const hasContextWindow = value.contextWindowTokens !== void 0;
|
|
15086
15181
|
if (hasContextUsed !== hasContextWindow) {
|
|
@@ -42652,7 +42747,7 @@ var ClaudeHarnessSession = class {
|
|
|
42652
42747
|
selectThinkingOption: true,
|
|
42653
42748
|
selectPermissionMode: true
|
|
42654
42749
|
},
|
|
42655
|
-
history: { fork: true, forkAcrossCwd: false }
|
|
42750
|
+
history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
|
|
42656
42751
|
};
|
|
42657
42752
|
initialState;
|
|
42658
42753
|
initialUsage = null;
|
|
@@ -42719,7 +42814,7 @@ var ClaudeHarnessSession = class {
|
|
|
42719
42814
|
};
|
|
42720
42815
|
}
|
|
42721
42816
|
if (!this.#statePublished)
|
|
42722
|
-
return { ok: true, value: { turns: [] } };
|
|
42817
|
+
return { ok: true, value: { turns: [], state: this.#state } };
|
|
42723
42818
|
this.#readingHistory = true;
|
|
42724
42819
|
try {
|
|
42725
42820
|
let messages;
|
|
@@ -42746,7 +42841,10 @@ var ClaudeHarnessSession = class {
|
|
|
42746
42841
|
};
|
|
42747
42842
|
}
|
|
42748
42843
|
try {
|
|
42749
|
-
return {
|
|
42844
|
+
return {
|
|
42845
|
+
ok: true,
|
|
42846
|
+
value: { ...mapClaudeSnapshot(messages, this.#sessionId), state: this.#state }
|
|
42847
|
+
};
|
|
42750
42848
|
} catch {
|
|
42751
42849
|
return {
|
|
42752
42850
|
ok: false,
|
|
@@ -43649,7 +43747,7 @@ var ClaudeCodeAdapter = class {
|
|
|
43649
43747
|
selectThinkingOption: false,
|
|
43650
43748
|
selectPermissionMode: snapshot.canSelectPermissionMode
|
|
43651
43749
|
},
|
|
43652
|
-
history: { fork: true, forkAcrossCwd: false }
|
|
43750
|
+
history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
|
|
43653
43751
|
}
|
|
43654
43752
|
};
|
|
43655
43753
|
}
|
|
@@ -43664,7 +43762,7 @@ var ClaudeCodeAdapter = class {
|
|
|
43664
43762
|
selectThinkingOption: true,
|
|
43665
43763
|
selectPermissionMode: snapshot.canSelectPermissionMode
|
|
43666
43764
|
},
|
|
43667
|
-
history: { fork: true, forkAcrossCwd: false }
|
|
43765
|
+
history: { fork: true, forkAcrossCwd: false, rollbackLastTurn: false }
|
|
43668
43766
|
}
|
|
43669
43767
|
};
|
|
43670
43768
|
} catch (error52) {
|
|
@@ -43702,6 +43800,16 @@ var ClaudeCodeAdapter = class {
|
|
|
43702
43800
|
}
|
|
43703
43801
|
};
|
|
43704
43802
|
}
|
|
43803
|
+
if (input.kind === "rollbackLastTurn") {
|
|
43804
|
+
return {
|
|
43805
|
+
ok: false,
|
|
43806
|
+
error: {
|
|
43807
|
+
code: "unsupported",
|
|
43808
|
+
message: "Claude Code does not support exact last-Turn rollback",
|
|
43809
|
+
retryable: false
|
|
43810
|
+
}
|
|
43811
|
+
};
|
|
43812
|
+
}
|
|
43705
43813
|
let requestedThinkingOptionId = CLAUDE_DEFAULT_THINKING_OPTION_ID;
|
|
43706
43814
|
if (input.kind === "create" && input.thinkingOptionId) {
|
|
43707
43815
|
try {
|
|
@@ -44246,6 +44354,11 @@ function mapPiSnapshot(history, state) {
|
|
|
44246
44354
|
}
|
|
44247
44355
|
return { turns };
|
|
44248
44356
|
}
|
|
44357
|
+
function resolvePiLastTurnBoundary(history) {
|
|
44358
|
+
const users = activePiEntries(history).filter((entry) => messageRole(entry) === "user");
|
|
44359
|
+
const last = users.at(-1);
|
|
44360
|
+
return last ? { lastUserEntryId: last.id, sourceTurnCount: users.length } : null;
|
|
44361
|
+
}
|
|
44249
44362
|
function resolvePiForkBoundary(history, checkpointId) {
|
|
44250
44363
|
const active = activePiEntries(history);
|
|
44251
44364
|
const users = active.filter((entry) => messageRole(entry) === "user");
|
|
@@ -44258,6 +44371,58 @@ function resolvePiForkBoundary(history, checkpointId) {
|
|
|
44258
44371
|
};
|
|
44259
44372
|
}
|
|
44260
44373
|
|
|
44374
|
+
// packages/adapters/pi/dist/pi-last-turn-rollback.js
|
|
44375
|
+
function modelFromState(state) {
|
|
44376
|
+
if (state.provider === null && state.modelId === null)
|
|
44377
|
+
return null;
|
|
44378
|
+
if (state.provider === null || state.modelId === null) {
|
|
44379
|
+
throw new Error("Pi state contains a partial Model identity");
|
|
44380
|
+
}
|
|
44381
|
+
return { provider: state.provider, id: state.modelId };
|
|
44382
|
+
}
|
|
44383
|
+
async function rollbackPiLastTurn(transport, sourceSessionId, cwd) {
|
|
44384
|
+
const startupState = transport.state;
|
|
44385
|
+
const startupSessionId = startupState.sessionId;
|
|
44386
|
+
const currentModel = modelFromState(startupState);
|
|
44387
|
+
const currentThinking = startupState.thinkingLevel;
|
|
44388
|
+
const copiedHistory = await transport.getEntries();
|
|
44389
|
+
const boundary = resolvePiLastTurnBoundary(copiedHistory);
|
|
44390
|
+
if (!boundary)
|
|
44391
|
+
return { ok: false, reason: "empty" };
|
|
44392
|
+
const sourceSnapshot = mapPiSnapshot(copiedHistory, {
|
|
44393
|
+
sessionId: startupSessionId,
|
|
44394
|
+
model: currentModel
|
|
44395
|
+
});
|
|
44396
|
+
let state = await transport.fork(boundary.lastUserEntryId);
|
|
44397
|
+
if (state.sessionId === sourceSessionId || state.sessionId === startupSessionId) {
|
|
44398
|
+
throw new Error("Pi last-Turn rollback did not create a distinct Native Session");
|
|
44399
|
+
}
|
|
44400
|
+
if (!samePiModel(modelFromState(state), currentModel)) {
|
|
44401
|
+
if (!currentModel)
|
|
44402
|
+
throw new Error("Pi last-Turn rollback changed the current Model");
|
|
44403
|
+
state = await transport.selectModel(currentModel);
|
|
44404
|
+
}
|
|
44405
|
+
if (state.thinkingLevel !== currentThinking) {
|
|
44406
|
+
if (!currentThinking)
|
|
44407
|
+
throw new Error("Pi last-Turn rollback changed current Thinking");
|
|
44408
|
+
state = await transport.selectThinkingOption(currentThinking);
|
|
44409
|
+
}
|
|
44410
|
+
if (!samePiModel(modelFromState(state), currentModel) || state.thinkingLevel !== currentThinking) {
|
|
44411
|
+
throw new Error("Pi last-Turn rollback could not restore current Model and Thinking");
|
|
44412
|
+
}
|
|
44413
|
+
const snapshot = mapPiSnapshot(await transport.getEntries(), {
|
|
44414
|
+
sessionId: state.sessionId,
|
|
44415
|
+
model: modelFromState(state)
|
|
44416
|
+
});
|
|
44417
|
+
const expectedTurnKeys = sourceSnapshot.turns.slice(0, -1).map((turn) => turn.nativeTurnRef.nativeTurnKey);
|
|
44418
|
+
const actualTurnKeys = snapshot.turns.map((turn) => turn.nativeTurnRef.nativeTurnKey);
|
|
44419
|
+
if (snapshot.turns.length !== boundary.sourceTurnCount - 1 || actualTurnKeys.some((key, index) => key !== expectedTurnKeys[index])) {
|
|
44420
|
+
throw new Error("Pi last-Turn rollback did not produce the exact retained history prefix");
|
|
44421
|
+
}
|
|
44422
|
+
await transport.verifySessionCwd(cwd);
|
|
44423
|
+
return { ok: true };
|
|
44424
|
+
}
|
|
44425
|
+
|
|
44261
44426
|
// packages/adapters/pi/dist/pi-rpc-session.js
|
|
44262
44427
|
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
44263
44428
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -44268,6 +44433,29 @@ import path5 from "node:path";
|
|
|
44268
44433
|
function isRecord8(value) {
|
|
44269
44434
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44270
44435
|
}
|
|
44436
|
+
function nonNegativeSafeInteger(value) {
|
|
44437
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
44438
|
+
}
|
|
44439
|
+
function optionalPiCacheHitRatePercent(value) {
|
|
44440
|
+
if (!isRecord8(value) || value.role !== "assistant" || !isRecord8(value.usage))
|
|
44441
|
+
return null;
|
|
44442
|
+
const input = nonNegativeSafeInteger(value.usage.input);
|
|
44443
|
+
const cacheRead = nonNegativeSafeInteger(value.usage.cacheRead);
|
|
44444
|
+
const cacheWrite = nonNegativeSafeInteger(value.usage.cacheWrite);
|
|
44445
|
+
if (input === null || cacheRead === null || cacheWrite === null)
|
|
44446
|
+
return null;
|
|
44447
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
44448
|
+
return promptTokens > 0 ? cacheRead / promptTokens * 100 : null;
|
|
44449
|
+
}
|
|
44450
|
+
function latestPiCacheHitRatePercent(history) {
|
|
44451
|
+
let latest = null;
|
|
44452
|
+
for (const entry of activePiEntries(history)) {
|
|
44453
|
+
if (entry.type === "message" && isRecord8(entry.message) && entry.message.role === "assistant") {
|
|
44454
|
+
latest = optionalPiCacheHitRatePercent(entry.message);
|
|
44455
|
+
}
|
|
44456
|
+
}
|
|
44457
|
+
return latest;
|
|
44458
|
+
}
|
|
44271
44459
|
function responseData(response, operation) {
|
|
44272
44460
|
if (!isRecord8(response.data)) {
|
|
44273
44461
|
throw new Error(`Pi RPC ${operation} response has no data`);
|
|
@@ -44564,13 +44752,13 @@ function piRpcProcessCommand(options, dependencies = {}) {
|
|
|
44564
44752
|
const command = platform === "win32" ? resolveWindowsCommand(selectedCommand, options.environment, dependencies.isFile ?? isRegularFile) : selectedCommand;
|
|
44565
44753
|
const sessionArguments = options.forkSessionFile ? ["--fork", options.forkSessionFile] : options.sessionFile ? ["--session", options.sessionFile] : [];
|
|
44566
44754
|
const modelArguments = options.model ? ["--provider", options.model.provider, "--model", options.model.id] : [];
|
|
44567
|
-
const
|
|
44755
|
+
const arguments_2 = ["--mode", "rpc", ...modelArguments, ...sessionArguments];
|
|
44568
44756
|
const extension = path5.win32.extname(command).toLowerCase();
|
|
44569
44757
|
if (platform !== "win32" || ![".cmd", ".bat"].includes(extension)) {
|
|
44570
|
-
return { command, arguments:
|
|
44758
|
+
return { command, arguments: arguments_2, windowsVerbatimArguments: false };
|
|
44571
44759
|
}
|
|
44572
44760
|
const quote = (value) => `"${value.replaceAll("%", "%%").replaceAll('"', '""')}"`;
|
|
44573
|
-
const commandLine = [command, ...
|
|
44761
|
+
const commandLine = [command, ...arguments_2].map(quote).join(" ");
|
|
44574
44762
|
return {
|
|
44575
44763
|
command: options.environment?.ComSpec ?? options.environment?.COMSPEC ?? "cmd.exe",
|
|
44576
44764
|
arguments: ["/d", "/v:off", "/s", "/c", `"${commandLine}"`],
|
|
@@ -44602,6 +44790,7 @@ var PiRpcSession = class {
|
|
|
44602
44790
|
#failed = false;
|
|
44603
44791
|
#pending = /* @__PURE__ */ new Map();
|
|
44604
44792
|
#state = null;
|
|
44793
|
+
#latestCacheHitRatePercent;
|
|
44605
44794
|
constructor(options, processAdapter = nodeProcessAdapter) {
|
|
44606
44795
|
if (options.sessionFile && options.forkSessionFile) {
|
|
44607
44796
|
throw new Error("Pi RPC cannot combine Session resume and Fork startup");
|
|
@@ -44682,7 +44871,9 @@ var PiRpcSession = class {
|
|
|
44682
44871
|
}
|
|
44683
44872
|
async getSessionUsage() {
|
|
44684
44873
|
try {
|
|
44685
|
-
|
|
44874
|
+
const usage = parsePiSessionUsage(await this.#send("get_session_stats", {}));
|
|
44875
|
+
await this.#ensureLatestCacheHitRate();
|
|
44876
|
+
return this.#withLatestCacheHitRate(usage);
|
|
44686
44877
|
} catch (error52) {
|
|
44687
44878
|
if (!(error52 instanceof PiRpcUnsupportedCommandError))
|
|
44688
44879
|
throw error52;
|
|
@@ -44691,9 +44882,23 @@ var PiRpcSession = class {
|
|
|
44691
44882
|
if (!this.#state || observedState.sessionId !== this.#state.sessionId || observedState.provider !== this.#state.provider || observedState.modelId !== this.#state.modelId) {
|
|
44692
44883
|
throw new Error("Pi RPC Usage fallback does not match the confirmed Session state");
|
|
44693
44884
|
}
|
|
44694
|
-
|
|
44885
|
+
await this.#ensureLatestCacheHitRate();
|
|
44886
|
+
const usage = parsePiStateContextUsage(response);
|
|
44887
|
+
return usage ? this.#withLatestCacheHitRate(usage) : null;
|
|
44695
44888
|
}
|
|
44696
44889
|
}
|
|
44890
|
+
async #ensureLatestCacheHitRate() {
|
|
44891
|
+
if (this.#latestCacheHitRatePercent !== void 0)
|
|
44892
|
+
return;
|
|
44893
|
+
try {
|
|
44894
|
+
this.#latestCacheHitRatePercent = latestPiCacheHitRatePercent(parseSessionHistory(await this.#send("get_entries", {})));
|
|
44895
|
+
} catch {
|
|
44896
|
+
this.#latestCacheHitRatePercent = null;
|
|
44897
|
+
}
|
|
44898
|
+
}
|
|
44899
|
+
#withLatestCacheHitRate(usage) {
|
|
44900
|
+
return this.#latestCacheHitRatePercent === null || this.#latestCacheHitRatePercent === void 0 ? usage : parseHostUsage({ ...usage, cacheHitRatePercent: this.#latestCacheHitRatePercent });
|
|
44901
|
+
}
|
|
44697
44902
|
async fork(entryId) {
|
|
44698
44903
|
if (entryId.length === 0)
|
|
44699
44904
|
throw new Error("Pi Fork Entry ID must not be empty");
|
|
@@ -45190,12 +45395,14 @@ var PiRpcSession = class {
|
|
|
45190
45395
|
const finalText = assistantText2(value);
|
|
45191
45396
|
const finalReasoning = assistantReasoning2(value);
|
|
45192
45397
|
const failure2 = assistantFailure(value);
|
|
45398
|
+
const cacheHitRatePercent = optionalPiCacheHitRatePercent(value);
|
|
45193
45399
|
if (finalText === null || finalReasoning === null || failure2 === void 0)
|
|
45194
45400
|
return;
|
|
45195
45401
|
if (active.assistantMessageId === null && finalText === active.lastFinalizedMessageText && finalReasoning === active.lastFinalizedMessageReasoning && !active.sawStreamedMessageText && !active.sawStreamedMessageReasoning) {
|
|
45196
45402
|
return;
|
|
45197
45403
|
}
|
|
45198
45404
|
active.failure = failure2;
|
|
45405
|
+
this.#latestCacheHitRatePercent = cacheHitRatePercent;
|
|
45199
45406
|
const messageId = this.#ensureAssistantMessage(active, value);
|
|
45200
45407
|
if (!active.sawStreamedMessageReasoning && finalReasoning.length > 0) {
|
|
45201
45408
|
active.reasoningMessageOpen = true;
|
|
@@ -45449,8 +45656,8 @@ function numberField(value, key) {
|
|
|
45449
45656
|
const field = value[key];
|
|
45450
45657
|
return typeof field === "number" || field === null ? field : void 0;
|
|
45451
45658
|
}
|
|
45452
|
-
function stripDiffPrefix(
|
|
45453
|
-
return
|
|
45659
|
+
function stripDiffPrefix(path11) {
|
|
45660
|
+
return path11.startsWith("a/") || path11.startsWith("b/") ? path11.slice(2) : path11;
|
|
45454
45661
|
}
|
|
45455
45662
|
function reliableFileChange(result) {
|
|
45456
45663
|
if (!isRecord11(result) || !isRecord11(result.details) || typeof result.details.patch !== "string") {
|
|
@@ -45469,10 +45676,10 @@ function reliableFileChange(result) {
|
|
|
45469
45676
|
const oldFile = file2.oldFileName;
|
|
45470
45677
|
const newFile = file2.newFileName;
|
|
45471
45678
|
const kind = oldFile === "/dev/null" ? "add" : newFile === "/dev/null" ? "delete" : "update";
|
|
45472
|
-
const
|
|
45473
|
-
if (!
|
|
45679
|
+
const path11 = stripDiffPrefix(kind === "delete" ? oldFile : newFile);
|
|
45680
|
+
if (!path11 || path11 === "/dev/null")
|
|
45474
45681
|
return null;
|
|
45475
|
-
return [{ path:
|
|
45682
|
+
return [{ path: path11, kind, unifiedDiff: patch }];
|
|
45476
45683
|
}
|
|
45477
45684
|
function delay3(milliseconds) {
|
|
45478
45685
|
return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
|
|
@@ -45516,7 +45723,7 @@ var PiHarnessSession = class {
|
|
|
45516
45723
|
selectThinkingOption: options.supportsThinkingSelection,
|
|
45517
45724
|
selectPermissionMode: false
|
|
45518
45725
|
},
|
|
45519
|
-
history: { fork: true, forkAcrossCwd: true }
|
|
45726
|
+
history: { fork: true, forkAcrossCwd: true, rollbackLastTurn: true }
|
|
45520
45727
|
};
|
|
45521
45728
|
this.#transport = options.startedTransport ?? null;
|
|
45522
45729
|
this.initialState = options.startedTransport ? harnessStateFromPi(options.startedTransport.state, options.startedThinkingLevels ?? null) : {};
|
|
@@ -45547,10 +45754,13 @@ var PiHarnessSession = class {
|
|
|
45547
45754
|
const history = await transport.getEntries();
|
|
45548
45755
|
return {
|
|
45549
45756
|
ok: true,
|
|
45550
|
-
value:
|
|
45551
|
-
|
|
45552
|
-
|
|
45553
|
-
|
|
45757
|
+
value: {
|
|
45758
|
+
...mapPiSnapshot(history, {
|
|
45759
|
+
sessionId: transport.state.sessionId,
|
|
45760
|
+
model: nativeModelForHistory(transport.state)
|
|
45761
|
+
}),
|
|
45762
|
+
state: this.#state
|
|
45763
|
+
}
|
|
45554
45764
|
};
|
|
45555
45765
|
} catch (error52) {
|
|
45556
45766
|
return { ok: false, error: normalizedError(error52, "nativeFailure") };
|
|
@@ -46461,7 +46671,7 @@ var PiAdapter = class {
|
|
|
46461
46671
|
selectThinkingOption: thinkingLevels !== null,
|
|
46462
46672
|
selectPermissionMode: false
|
|
46463
46673
|
},
|
|
46464
|
-
history: { fork: true, forkAcrossCwd: true }
|
|
46674
|
+
history: { fork: true, forkAcrossCwd: true, rollbackLastTurn: true }
|
|
46465
46675
|
}
|
|
46466
46676
|
};
|
|
46467
46677
|
} catch (error52) {
|
|
@@ -46566,7 +46776,7 @@ var PiAdapter = class {
|
|
|
46566
46776
|
retryable: false
|
|
46567
46777
|
});
|
|
46568
46778
|
}
|
|
46569
|
-
} else {
|
|
46779
|
+
} else if (input.kind === "fork") {
|
|
46570
46780
|
const checkpoint = nativeCheckpointRefSchema.parse(input.checkpoint);
|
|
46571
46781
|
const startupSessionId = transport.state.sessionId;
|
|
46572
46782
|
if (startupSessionId === sourceRef.nativeSessionId) {
|
|
@@ -46596,6 +46806,15 @@ var PiAdapter = class {
|
|
|
46596
46806
|
throw new Error("Pi Fork derived history does not match the requested Checkpoint");
|
|
46597
46807
|
}
|
|
46598
46808
|
await transport.verifySessionCwd(input.cwd);
|
|
46809
|
+
} else {
|
|
46810
|
+
const rolledBack = await rollbackPiLastTurn(transport, sourceRef.nativeSessionId, input.cwd);
|
|
46811
|
+
if (!rolledBack.ok) {
|
|
46812
|
+
throw new PiAdapterFaultError({
|
|
46813
|
+
code: "invalidState",
|
|
46814
|
+
message: "Pi Native Session has no Turn to roll back",
|
|
46815
|
+
retryable: false
|
|
46816
|
+
});
|
|
46817
|
+
}
|
|
46599
46818
|
}
|
|
46600
46819
|
const startedThinkingLevels = await transport.getAvailableThinkingLevels();
|
|
46601
46820
|
this.#thinkingSelectionSupported = startedThinkingLevels !== null;
|
|
@@ -46682,6 +46901,7 @@ import { randomUUID as randomUUID7 } from "node:crypto";
|
|
|
46682
46901
|
import nodePath from "node:path";
|
|
46683
46902
|
|
|
46684
46903
|
// packages/mapping-store/dist/mapping-store.js
|
|
46904
|
+
import { execFileSync } from "node:child_process";
|
|
46685
46905
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
46686
46906
|
import { constants } from "node:fs";
|
|
46687
46907
|
import { copyFile, mkdir, open as open2, readFile, readdir, rename, rm as rm2, stat } from "node:fs/promises";
|
|
@@ -46721,15 +46941,15 @@ var storedThreadRecordV1Schema = external_exports.object({
|
|
|
46721
46941
|
turnMappings: external_exports.array(storedTurnMappingV1Schema),
|
|
46722
46942
|
createdAt: isoDateSchema,
|
|
46723
46943
|
updatedAt: isoDateSchema
|
|
46724
|
-
}).strict().superRefine((
|
|
46725
|
-
if (
|
|
46944
|
+
}).strict().superRefine((record3, context) => {
|
|
46945
|
+
if (record3.state === "ready" && !record3.nativeSessionRef) {
|
|
46726
46946
|
context.addIssue({
|
|
46727
46947
|
code: "custom",
|
|
46728
46948
|
path: ["nativeSessionRef"],
|
|
46729
46949
|
message: "Ready Thread must have a Native Session Ref"
|
|
46730
46950
|
});
|
|
46731
46951
|
}
|
|
46732
|
-
if (
|
|
46952
|
+
if (record3.nativeSessionRef && record3.nativeSessionRef.harnessId !== record3.harnessId) {
|
|
46733
46953
|
context.addIssue({
|
|
46734
46954
|
code: "custom",
|
|
46735
46955
|
path: ["nativeSessionRef", "harnessId"],
|
|
@@ -46738,7 +46958,7 @@ var storedThreadRecordV1Schema = external_exports.object({
|
|
|
46738
46958
|
}
|
|
46739
46959
|
const hostTurnIds = /* @__PURE__ */ new Set();
|
|
46740
46960
|
const nativeTurnKeys = /* @__PURE__ */ new Set();
|
|
46741
|
-
for (const [index, mapping] of
|
|
46961
|
+
for (const [index, mapping] of record3.turnMappings.entries()) {
|
|
46742
46962
|
if (hostTurnIds.has(mapping.hostTurnId)) {
|
|
46743
46963
|
context.addIssue({
|
|
46744
46964
|
code: "custom",
|
|
@@ -46762,14 +46982,14 @@ var storedThreadRecordV1Schema = external_exports.object({
|
|
|
46762
46982
|
]) {
|
|
46763
46983
|
if (!ref)
|
|
46764
46984
|
continue;
|
|
46765
|
-
if (ref.harnessId !==
|
|
46985
|
+
if (ref.harnessId !== record3.harnessId) {
|
|
46766
46986
|
context.addIssue({
|
|
46767
46987
|
code: "custom",
|
|
46768
46988
|
path: ["turnMappings", index, name, "harnessId"],
|
|
46769
46989
|
message: "Turn Ref Harness must match the Thread Harness"
|
|
46770
46990
|
});
|
|
46771
46991
|
}
|
|
46772
|
-
if (
|
|
46992
|
+
if (record3.nativeSessionRef && ref.nativeSessionId !== record3.nativeSessionRef.nativeSessionId) {
|
|
46773
46993
|
context.addIssue({
|
|
46774
46994
|
code: "custom",
|
|
46775
46995
|
path: ["turnMappings", index, name, "nativeSessionId"],
|
|
@@ -46789,8 +47009,8 @@ var MappingStoreError = class extends Error {
|
|
|
46789
47009
|
this.name = "MappingStoreError";
|
|
46790
47010
|
}
|
|
46791
47011
|
};
|
|
46792
|
-
function cloneRecord(
|
|
46793
|
-
return JSON.parse(JSON.stringify(
|
|
47012
|
+
function cloneRecord(record3) {
|
|
47013
|
+
return JSON.parse(JSON.stringify(record3));
|
|
46794
47014
|
}
|
|
46795
47015
|
function nativeSessionKey(ref) {
|
|
46796
47016
|
return `${ref.harnessId}\0${ref.nativeSessionId}`;
|
|
@@ -46815,13 +47035,62 @@ async function exists(file2) {
|
|
|
46815
47035
|
throw error52;
|
|
46816
47036
|
}
|
|
46817
47037
|
}
|
|
46818
|
-
function
|
|
47038
|
+
function processIdentity(pid) {
|
|
46819
47039
|
try {
|
|
46820
47040
|
process.kill(pid, 0);
|
|
46821
|
-
return true;
|
|
46822
47041
|
} catch (error52) {
|
|
46823
|
-
|
|
47042
|
+
if (systemErrorCode(error52) !== "EPERM")
|
|
47043
|
+
return null;
|
|
47044
|
+
}
|
|
47045
|
+
if (process.platform !== "win32") {
|
|
47046
|
+
return { executablePath: null, startedAt: null };
|
|
47047
|
+
}
|
|
47048
|
+
try {
|
|
47049
|
+
const query = [
|
|
47050
|
+
"$ErrorActionPreference = 'Stop'",
|
|
47051
|
+
`$process = Get-Process -Id ${pid}`,
|
|
47052
|
+
"$process | Select-Object Path, StartTime | ConvertTo-Json -Compress"
|
|
47053
|
+
].join("; ");
|
|
47054
|
+
const result = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", query], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
|
|
47055
|
+
if (!result)
|
|
47056
|
+
return { executablePath: null, startedAt: null };
|
|
47057
|
+
const parsed = JSON.parse(result);
|
|
47058
|
+
const executablePath = typeof parsed.Path === "string" ? parsed.Path : null;
|
|
47059
|
+
const startedAt = typeof parsed.StartTime === "string" ? Date.parse(parsed.StartTime) : NaN;
|
|
47060
|
+
return { executablePath, startedAt: Number.isFinite(startedAt) ? startedAt : null };
|
|
47061
|
+
} catch {
|
|
47062
|
+
return { executablePath: null, startedAt: null };
|
|
47063
|
+
}
|
|
47064
|
+
}
|
|
47065
|
+
function normalizeExecutablePath(value) {
|
|
47066
|
+
return process.platform === "win32" ? value.replaceAll("/", "\\").toLowerCase() : value;
|
|
47067
|
+
}
|
|
47068
|
+
function isNodeExecutable(value) {
|
|
47069
|
+
const normalized = normalizeExecutablePath(value);
|
|
47070
|
+
return normalized.endsWith("\\node.exe") || normalized.endsWith("/node");
|
|
47071
|
+
}
|
|
47072
|
+
function lockOwnerIsLive(lock) {
|
|
47073
|
+
if (typeof lock.pid !== "number")
|
|
47074
|
+
return false;
|
|
47075
|
+
const identity = processIdentity(lock.pid);
|
|
47076
|
+
if (!identity)
|
|
47077
|
+
return false;
|
|
47078
|
+
if (process.platform !== "win32")
|
|
47079
|
+
return true;
|
|
47080
|
+
if (identity.executablePath && lock.executablePath) {
|
|
47081
|
+
if (normalizeExecutablePath(identity.executablePath) !== normalizeExecutablePath(lock.executablePath)) {
|
|
47082
|
+
return false;
|
|
47083
|
+
}
|
|
47084
|
+
} else if (identity.executablePath && !lock.executablePath && !isNodeExecutable(identity.executablePath)) {
|
|
47085
|
+
return false;
|
|
47086
|
+
}
|
|
47087
|
+
if (lock.processStartedAt && identity.startedAt !== null) {
|
|
47088
|
+
const expected = Date.parse(lock.processStartedAt);
|
|
47089
|
+
if (Number.isFinite(expected) && Math.abs(identity.startedAt - expected) > 5e3) {
|
|
47090
|
+
return false;
|
|
47091
|
+
}
|
|
46824
47092
|
}
|
|
47093
|
+
return true;
|
|
46825
47094
|
}
|
|
46826
47095
|
var MappingStore = class {
|
|
46827
47096
|
#backupsDirectory;
|
|
@@ -46865,13 +47134,13 @@ var MappingStore = class {
|
|
|
46865
47134
|
for (const name of names) {
|
|
46866
47135
|
const primary = path6.join(this.#threadsDirectory, name);
|
|
46867
47136
|
const backup = path6.join(this.#backupsDirectory, name);
|
|
46868
|
-
let
|
|
47137
|
+
let record3 = null;
|
|
46869
47138
|
try {
|
|
46870
|
-
|
|
47139
|
+
record3 = await this.#readRecord(primary, name);
|
|
46871
47140
|
} catch (primaryError) {
|
|
46872
47141
|
try {
|
|
46873
|
-
|
|
46874
|
-
await this.#replaceFile(primary,
|
|
47142
|
+
record3 = await this.#readRecord(backup, name);
|
|
47143
|
+
await this.#replaceFile(primary, record3, false);
|
|
46875
47144
|
} catch (backupError) {
|
|
46876
47145
|
const quarantine = path6.join(this.#quarantineDirectory, `${name}.${this.#now().getTime()}.invalid`);
|
|
46877
47146
|
await rename(primary, quarantine).catch(() => void 0);
|
|
@@ -46880,12 +47149,12 @@ var MappingStore = class {
|
|
|
46880
47149
|
continue;
|
|
46881
47150
|
}
|
|
46882
47151
|
}
|
|
46883
|
-
if (
|
|
47152
|
+
if (record3.state === "creating" && !record3.nativeSessionRef) {
|
|
46884
47153
|
await rm2(primary, { force: true });
|
|
46885
47154
|
await rm2(backup, { force: true });
|
|
46886
47155
|
continue;
|
|
46887
47156
|
}
|
|
46888
|
-
this.#records.set(
|
|
47157
|
+
this.#records.set(record3.hostThreadId, record3);
|
|
46889
47158
|
}
|
|
46890
47159
|
this.#rebuildIndexes();
|
|
46891
47160
|
this.#initialized = true;
|
|
@@ -46896,8 +47165,8 @@ var MappingStore = class {
|
|
|
46896
47165
|
}
|
|
46897
47166
|
async getThread(hostThreadId) {
|
|
46898
47167
|
this.#requireInitialized();
|
|
46899
|
-
const
|
|
46900
|
-
return
|
|
47168
|
+
const record3 = this.#records.get(hostThreadId);
|
|
47169
|
+
return record3 ? cloneRecord(record3) : null;
|
|
46901
47170
|
}
|
|
46902
47171
|
async listThreads() {
|
|
46903
47172
|
this.#requireInitialized();
|
|
@@ -46917,7 +47186,7 @@ var MappingStore = class {
|
|
|
46917
47186
|
throw new MappingStoreError("DUPLICATE_CREATE_REQUEST", "Create request already identifies another Thread");
|
|
46918
47187
|
}
|
|
46919
47188
|
const timestamp = this.#now().toISOString();
|
|
46920
|
-
const
|
|
47189
|
+
const record3 = storedThreadRecordV1Schema.parse({
|
|
46921
47190
|
formatVersion: 1,
|
|
46922
47191
|
revision: 1,
|
|
46923
47192
|
hostThreadId: input.hostThreadId,
|
|
@@ -46935,8 +47204,8 @@ var MappingStore = class {
|
|
|
46935
47204
|
createdAt: timestamp,
|
|
46936
47205
|
updatedAt: timestamp
|
|
46937
47206
|
});
|
|
46938
|
-
await this.#writeNew(
|
|
46939
|
-
return cloneRecord(
|
|
47207
|
+
await this.#writeNew(record3);
|
|
47208
|
+
return cloneRecord(record3);
|
|
46940
47209
|
}
|
|
46941
47210
|
async commitReady(input) {
|
|
46942
47211
|
return this.#update(input.hostThreadId, (current) => ({
|
|
@@ -46959,6 +47228,18 @@ var MappingStore = class {
|
|
|
46959
47228
|
};
|
|
46960
47229
|
});
|
|
46961
47230
|
}
|
|
47231
|
+
async replaceReadySessionAfterLastTurn(input) {
|
|
47232
|
+
return this.#update(input.hostThreadId, (current) => {
|
|
47233
|
+
if (current.state !== "ready" || !current.nativeSessionRef || current.nativeSessionRef.nativeSessionId === input.nativeSessionRef.nativeSessionId || input.turnMappings.length !== current.turnMappings.length - 1 || input.turnMappings.some(({ hostTurnId }, index) => hostTurnId !== current.turnMappings[index]?.hostTurnId)) {
|
|
47234
|
+
throw new MappingStoreError("MAPPING_CONFLICT", "Last-Turn Session replacement must retain the exact shorter Host Turn prefix");
|
|
47235
|
+
}
|
|
47236
|
+
return {
|
|
47237
|
+
...current,
|
|
47238
|
+
nativeSessionRef: input.nativeSessionRef,
|
|
47239
|
+
turnMappings: input.turnMappings
|
|
47240
|
+
};
|
|
47241
|
+
});
|
|
47242
|
+
}
|
|
46962
47243
|
async upsertTurnMappings(hostThreadId, mappings) {
|
|
46963
47244
|
return this.#update(hostThreadId, (current) => ({
|
|
46964
47245
|
...current,
|
|
@@ -47005,8 +47286,8 @@ var MappingStore = class {
|
|
|
47005
47286
|
}
|
|
47006
47287
|
async removeProvisional(hostThreadId) {
|
|
47007
47288
|
this.#requireInitialized();
|
|
47008
|
-
const
|
|
47009
|
-
if (
|
|
47289
|
+
const record3 = this.#records.get(hostThreadId);
|
|
47290
|
+
if (record3?.state === "ready") {
|
|
47010
47291
|
throw new MappingStoreError("MAPPING_CONFLICT", "Ready Thread cannot be removed as provisional");
|
|
47011
47292
|
}
|
|
47012
47293
|
await this.#remove(hostThreadId);
|
|
@@ -47097,25 +47378,25 @@ var MappingStore = class {
|
|
|
47097
47378
|
}
|
|
47098
47379
|
return merged;
|
|
47099
47380
|
}
|
|
47100
|
-
async #writeNew(
|
|
47101
|
-
this.#validateGlobal(
|
|
47102
|
-
await this.#replaceFile(this.#recordPath(
|
|
47103
|
-
this.#records.set(
|
|
47381
|
+
async #writeNew(record3) {
|
|
47382
|
+
this.#validateGlobal(record3, null);
|
|
47383
|
+
await this.#replaceFile(this.#recordPath(record3.hostThreadId), record3, false);
|
|
47384
|
+
this.#records.set(record3.hostThreadId, record3);
|
|
47104
47385
|
this.#rebuildIndexes();
|
|
47105
47386
|
}
|
|
47106
|
-
async #replaceFile(target,
|
|
47387
|
+
async #replaceFile(target, record3, preserveBackup) {
|
|
47107
47388
|
const temp = `${target}.tmp-${randomUUID4()}`;
|
|
47108
47389
|
let handle = null;
|
|
47109
47390
|
try {
|
|
47110
|
-
await this.#beforeReplace?.(cloneRecord(
|
|
47391
|
+
await this.#beforeReplace?.(cloneRecord(record3));
|
|
47111
47392
|
handle = await open2(temp, "wx", constants.S_IRUSR | constants.S_IWUSR);
|
|
47112
|
-
await handle.writeFile(`${JSON.stringify(
|
|
47393
|
+
await handle.writeFile(`${JSON.stringify(record3, null, 2)}
|
|
47113
47394
|
`, "utf8");
|
|
47114
47395
|
await handle.sync();
|
|
47115
47396
|
await handle.close();
|
|
47116
47397
|
handle = null;
|
|
47117
47398
|
if (preserveBackup && await exists(target)) {
|
|
47118
|
-
await copyFile(target, this.#backupPath(
|
|
47399
|
+
await copyFile(target, this.#backupPath(record3.hostThreadId));
|
|
47119
47400
|
}
|
|
47120
47401
|
await rename(temp, target);
|
|
47121
47402
|
} catch (error52) {
|
|
@@ -47150,7 +47431,9 @@ var MappingStore = class {
|
|
|
47150
47431
|
const lock = {
|
|
47151
47432
|
pid: process.pid,
|
|
47152
47433
|
instanceId: this.#instanceId,
|
|
47153
|
-
startedAt: this.#now().toISOString()
|
|
47434
|
+
startedAt: this.#now().toISOString(),
|
|
47435
|
+
executablePath: process.execPath,
|
|
47436
|
+
processStartedAt: new Date(Date.now() - process.uptime() * 1e3).toISOString()
|
|
47154
47437
|
};
|
|
47155
47438
|
await handle.writeFile(`${JSON.stringify(lock)}
|
|
47156
47439
|
`, "utf8");
|
|
@@ -47169,7 +47452,7 @@ var MappingStore = class {
|
|
|
47169
47452
|
existing = JSON.parse(await readFile(this.#lockPath, "utf8"));
|
|
47170
47453
|
} catch {
|
|
47171
47454
|
}
|
|
47172
|
-
if (typeof existing.pid === "number" &&
|
|
47455
|
+
if (typeof existing.pid === "number" && lockOwnerIsLive(existing)) {
|
|
47173
47456
|
throw new MappingStoreError("STORE_LOCKED", "Another codexhost process owns Mapping Store");
|
|
47174
47457
|
}
|
|
47175
47458
|
await rename(this.#lockPath, `${this.#lockPath}.stale-${this.#now().getTime()}`).catch(() => void 0);
|
|
@@ -47181,18 +47464,18 @@ var MappingStore = class {
|
|
|
47181
47464
|
});
|
|
47182
47465
|
}
|
|
47183
47466
|
}
|
|
47184
|
-
#validateGlobal(
|
|
47185
|
-
const duplicateCreate = this.#createRequests.get(
|
|
47467
|
+
#validateGlobal(record3, replacing) {
|
|
47468
|
+
const duplicateCreate = this.#createRequests.get(record3.createRequestId);
|
|
47186
47469
|
if (duplicateCreate && duplicateCreate !== replacing) {
|
|
47187
47470
|
throw new MappingStoreError("DUPLICATE_CREATE_REQUEST", "Create request is already stored");
|
|
47188
47471
|
}
|
|
47189
|
-
if (
|
|
47190
|
-
const duplicateSession = this.#nativeSessions.get(nativeSessionKey(
|
|
47472
|
+
if (record3.nativeSessionRef) {
|
|
47473
|
+
const duplicateSession = this.#nativeSessions.get(nativeSessionKey(record3.nativeSessionRef));
|
|
47191
47474
|
if (duplicateSession && duplicateSession !== replacing) {
|
|
47192
47475
|
throw new MappingStoreError("DUPLICATE_NATIVE_SESSION", "Native Session is already mapped");
|
|
47193
47476
|
}
|
|
47194
47477
|
}
|
|
47195
|
-
for (const mapping of
|
|
47478
|
+
for (const mapping of record3.turnMappings) {
|
|
47196
47479
|
const duplicateHostTurn = this.#hostTurns.get(mapping.hostTurnId);
|
|
47197
47480
|
if (duplicateHostTurn && duplicateHostTurn !== replacing) {
|
|
47198
47481
|
throw new MappingStoreError("MAPPING_CONFLICT", "Host Turn is already mapped");
|
|
@@ -47208,14 +47491,14 @@ var MappingStore = class {
|
|
|
47208
47491
|
this.#nativeSessions.clear();
|
|
47209
47492
|
this.#hostTurns.clear();
|
|
47210
47493
|
this.#nativeTurns.clear();
|
|
47211
|
-
for (const
|
|
47212
|
-
this.#validateGlobal(
|
|
47213
|
-
this.#createRequests.set(
|
|
47214
|
-
if (
|
|
47215
|
-
this.#nativeSessions.set(nativeSessionKey(
|
|
47216
|
-
}
|
|
47217
|
-
for (const mapping of
|
|
47218
|
-
this.#hostTurns.set(mapping.hostTurnId,
|
|
47494
|
+
for (const record3 of this.#records.values()) {
|
|
47495
|
+
this.#validateGlobal(record3, record3.hostThreadId);
|
|
47496
|
+
this.#createRequests.set(record3.createRequestId, record3.hostThreadId);
|
|
47497
|
+
if (record3.nativeSessionRef) {
|
|
47498
|
+
this.#nativeSessions.set(nativeSessionKey(record3.nativeSessionRef), record3.hostThreadId);
|
|
47499
|
+
}
|
|
47500
|
+
for (const mapping of record3.turnMappings) {
|
|
47501
|
+
this.#hostTurns.set(mapping.hostTurnId, record3.hostThreadId);
|
|
47219
47502
|
this.#nativeTurns.set(nativeTurnKey(mapping), mapping.hostTurnId);
|
|
47220
47503
|
}
|
|
47221
47504
|
}
|
|
@@ -47604,8 +47887,8 @@ function projectItem(item, outcome, defaultCwd, includeCommandOutput = true) {
|
|
|
47604
47887
|
return {
|
|
47605
47888
|
id: item.itemId,
|
|
47606
47889
|
type: "fileChange",
|
|
47607
|
-
changes: item.changes.map(({ path:
|
|
47608
|
-
path:
|
|
47890
|
+
changes: item.changes.map(({ path: path11, kind, unifiedDiff }) => ({
|
|
47891
|
+
path: path11,
|
|
47609
47892
|
kind,
|
|
47610
47893
|
diff: unifiedDiff
|
|
47611
47894
|
})),
|
|
@@ -48022,8 +48305,8 @@ var CodexTurnProjector = class {
|
|
|
48022
48305
|
return messages;
|
|
48023
48306
|
}
|
|
48024
48307
|
#fileChangeUpdates(itemId2, changes) {
|
|
48025
|
-
const projectedChanges = changes.map(({ path:
|
|
48026
|
-
path:
|
|
48308
|
+
const projectedChanges = changes.map(({ path: path11, kind, unifiedDiff }) => ({
|
|
48309
|
+
path: path11,
|
|
48027
48310
|
kind,
|
|
48028
48311
|
diff: unifiedDiff
|
|
48029
48312
|
}));
|
|
@@ -48106,13 +48389,13 @@ function decodeThreadForkRequest(request) {
|
|
|
48106
48389
|
if (runtimeWorkspaceRoots !== void 0 && runtimeWorkspaceRoots !== null && (!Array.isArray(runtimeWorkspaceRoots) || runtimeWorkspaceRoots.some((root) => typeof root !== "string" || root.length === 0))) {
|
|
48107
48390
|
throw new Error("thread/fork params.runtimeWorkspaceRoots must be text paths or null");
|
|
48108
48391
|
}
|
|
48109
|
-
const
|
|
48392
|
+
const path11 = optionalText(params, "path", { allowEmpty: true });
|
|
48110
48393
|
const ephemeral = optionalBoolean(params, "ephemeral");
|
|
48111
48394
|
return {
|
|
48112
48395
|
threadId: threadId2,
|
|
48113
48396
|
...lastTurnText ? { lastTurnId: hostTurnIdSchema.parse(lastTurnText) } : {},
|
|
48114
48397
|
...beforeTurnText ? { beforeTurnId: hostTurnIdSchema.parse(beforeTurnText) } : {},
|
|
48115
|
-
...
|
|
48398
|
+
...path11 ? { path: path11 } : {},
|
|
48116
48399
|
...optionalField(params, "model"),
|
|
48117
48400
|
...optionalField(params, "modelProvider"),
|
|
48118
48401
|
...optionalField(params, "cwd"),
|
|
@@ -48744,11 +49027,11 @@ var ExternalThreadRepository = class {
|
|
|
48744
49027
|
removeThread(hostThreadId) {
|
|
48745
49028
|
return this.store.removeThread(hostThreadId);
|
|
48746
49029
|
}
|
|
48747
|
-
async persistTurn(
|
|
48748
|
-
if (
|
|
49030
|
+
async persistTurn(record3, hostTurnId, nativeTurnRef, nativeCheckpointRef) {
|
|
49031
|
+
if (record3.state !== "ready" || !record3.nativeSessionRef || nativeTurnRef.harnessId !== record3.harnessId || nativeTurnRef.nativeSessionId !== record3.nativeSessionRef.nativeSessionId || nativeCheckpointRef && (nativeCheckpointRef.harnessId !== record3.harnessId || nativeCheckpointRef.nativeSessionId !== record3.nativeSessionRef.nativeSessionId)) {
|
|
48749
49032
|
throw new Error("Live Turn identity does not belong to the stored Thread");
|
|
48750
49033
|
}
|
|
48751
|
-
return this.store.upsertTurnMappings(
|
|
49034
|
+
return this.store.upsertTurnMappings(record3.hostThreadId, [
|
|
48752
49035
|
{
|
|
48753
49036
|
hostTurnId,
|
|
48754
49037
|
nativeTurnRef,
|
|
@@ -48756,12 +49039,12 @@ var ExternalThreadRepository = class {
|
|
|
48756
49039
|
}
|
|
48757
49040
|
]);
|
|
48758
49041
|
}
|
|
48759
|
-
async commitDerivedSnapshot(
|
|
48760
|
-
if (
|
|
49042
|
+
async commitDerivedSnapshot(record3, nativeSessionRef, snapshot) {
|
|
49043
|
+
if (record3.state !== "creating" || record3.turnMappings.length !== 0) {
|
|
48761
49044
|
throw new Error("Derived Thread must be provisional before Snapshot commit");
|
|
48762
49045
|
}
|
|
48763
49046
|
const mappings = snapshot.turns.map((turn) => {
|
|
48764
|
-
if (turn.nativeTurnRef.harnessId !==
|
|
49047
|
+
if (turn.nativeTurnRef.harnessId !== record3.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record3.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
|
|
48765
49048
|
throw new Error("Derived Snapshot identity does not belong to its Native Session");
|
|
48766
49049
|
}
|
|
48767
49050
|
return {
|
|
@@ -48771,7 +49054,7 @@ var ExternalThreadRepository = class {
|
|
|
48771
49054
|
};
|
|
48772
49055
|
});
|
|
48773
49056
|
const nextRecord = await this.store.commitReady({
|
|
48774
|
-
hostThreadId:
|
|
49057
|
+
hostThreadId: record3.hostThreadId,
|
|
48775
49058
|
nativeSessionRef,
|
|
48776
49059
|
turnMappings: mappings
|
|
48777
49060
|
});
|
|
@@ -48782,7 +49065,7 @@ var ExternalThreadRepository = class {
|
|
|
48782
49065
|
if (!mapping) throw new Error("Derived Snapshot mapping is incomplete");
|
|
48783
49066
|
return projectHistoricalTurn({
|
|
48784
49067
|
turnId: mapping.hostTurnId,
|
|
48785
|
-
cwd:
|
|
49068
|
+
cwd: record3.cwd,
|
|
48786
49069
|
snapshot: turn
|
|
48787
49070
|
});
|
|
48788
49071
|
})
|
|
@@ -48839,8 +49122,41 @@ var ExternalThreadRepository = class {
|
|
|
48839
49122
|
})
|
|
48840
49123
|
};
|
|
48841
49124
|
}
|
|
48842
|
-
async
|
|
48843
|
-
|
|
49125
|
+
async commitLastTurnRollback(current, nativeSessionRef, snapshot) {
|
|
49126
|
+
if (current.state !== "ready" || !current.nativeSessionRef || nativeSessionRef.harnessId !== current.harnessId || nativeSessionRef.nativeSessionId === current.nativeSessionRef.nativeSessionId || snapshot.turns.length !== current.turnMappings.length - 1) {
|
|
49127
|
+
throw new Error("Last-Turn rollback is not an exact ready Session replacement");
|
|
49128
|
+
}
|
|
49129
|
+
const mappings = snapshot.turns.map((turn, index) => {
|
|
49130
|
+
const previous = current.turnMappings[index];
|
|
49131
|
+
if (!previous || turn.nativeTurnRef.harnessId !== current.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== current.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
|
|
49132
|
+
throw new Error("Last-Turn rollback Snapshot identity is invalid");
|
|
49133
|
+
}
|
|
49134
|
+
return {
|
|
49135
|
+
hostTurnId: previous.hostTurnId,
|
|
49136
|
+
nativeTurnRef: turn.nativeTurnRef,
|
|
49137
|
+
...turn.checkpoint ? { nativeCheckpointRef: turn.checkpoint } : {}
|
|
49138
|
+
};
|
|
49139
|
+
});
|
|
49140
|
+
const nextRecord = await this.store.replaceReadySessionAfterLastTurn({
|
|
49141
|
+
hostThreadId: current.hostThreadId,
|
|
49142
|
+
nativeSessionRef,
|
|
49143
|
+
turnMappings: mappings
|
|
49144
|
+
});
|
|
49145
|
+
return {
|
|
49146
|
+
record: nextRecord,
|
|
49147
|
+
turns: snapshot.turns.map((turn, index) => {
|
|
49148
|
+
const mapping = mappings[index];
|
|
49149
|
+
if (!mapping) throw new Error("Last-Turn rollback Snapshot mapping is incomplete");
|
|
49150
|
+
return projectHistoricalTurn({
|
|
49151
|
+
turnId: mapping.hostTurnId,
|
|
49152
|
+
cwd: current.cwd,
|
|
49153
|
+
snapshot: turn
|
|
49154
|
+
});
|
|
49155
|
+
})
|
|
49156
|
+
};
|
|
49157
|
+
}
|
|
49158
|
+
async sessionTreeId(record3) {
|
|
49159
|
+
let current = record3;
|
|
48844
49160
|
const visited = /* @__PURE__ */ new Set();
|
|
48845
49161
|
while (current.forkSource) {
|
|
48846
49162
|
if (visited.has(current.hostThreadId))
|
|
@@ -48852,18 +49168,18 @@ var ExternalThreadRepository = class {
|
|
|
48852
49168
|
}
|
|
48853
49169
|
return current.hostThreadId;
|
|
48854
49170
|
}
|
|
48855
|
-
async alignSnapshot(
|
|
48856
|
-
const nativeSessionRef =
|
|
48857
|
-
if (!nativeSessionRef ||
|
|
49171
|
+
async alignSnapshot(record3, snapshot) {
|
|
49172
|
+
const nativeSessionRef = record3.nativeSessionRef;
|
|
49173
|
+
if (!nativeSessionRef || record3.state !== "ready") {
|
|
48858
49174
|
throw new Error("External Thread has no committed Native Session identity");
|
|
48859
49175
|
}
|
|
48860
49176
|
for (const turn of snapshot.turns) {
|
|
48861
|
-
if (turn.nativeTurnRef.harnessId !==
|
|
49177
|
+
if (turn.nativeTurnRef.harnessId !== record3.harnessId || turn.nativeTurnRef.nativeSessionId !== nativeSessionRef.nativeSessionId || turn.checkpoint && (turn.checkpoint.harnessId !== record3.harnessId || turn.checkpoint.nativeSessionId !== nativeSessionRef.nativeSessionId)) {
|
|
48862
49178
|
throw new Error("External Snapshot identity does not belong to the stored Thread");
|
|
48863
49179
|
}
|
|
48864
49180
|
}
|
|
48865
49181
|
const mappingsByNative = new Map(
|
|
48866
|
-
|
|
49182
|
+
record3.turnMappings.map(
|
|
48867
49183
|
(mapping) => [nativeTurnKey2(mapping.nativeTurnRef), mapping]
|
|
48868
49184
|
)
|
|
48869
49185
|
);
|
|
@@ -48886,24 +49202,24 @@ var ExternalThreadRepository = class {
|
|
|
48886
49202
|
let existingIndex = 0;
|
|
48887
49203
|
for (const { existing } of aligned) {
|
|
48888
49204
|
if (!existing) continue;
|
|
48889
|
-
if (existing.hostTurnId !==
|
|
49205
|
+
if (existing.hostTurnId !== record3.turnMappings[existingIndex]?.hostTurnId) {
|
|
48890
49206
|
throw new Error("Persisted Turn mappings do not match the Native Snapshot order");
|
|
48891
49207
|
}
|
|
48892
49208
|
existingIndex += 1;
|
|
48893
49209
|
}
|
|
48894
|
-
if (existingIndex !==
|
|
49210
|
+
if (existingIndex !== record3.turnMappings.length) {
|
|
48895
49211
|
throw new Error("Persisted Turn mappings do not match the Native Snapshot order");
|
|
48896
49212
|
}
|
|
48897
49213
|
const orderedMappings = aligned.map(({ mapping }) => mapping);
|
|
48898
|
-
const mappingsChanged = orderedMappings.length !==
|
|
48899
|
-
const persisted =
|
|
49214
|
+
const mappingsChanged = orderedMappings.length !== record3.turnMappings.length || orderedMappings.some((mapping, index) => {
|
|
49215
|
+
const persisted = record3.turnMappings[index];
|
|
48900
49216
|
return !persisted || !sameMapping(mapping, persisted);
|
|
48901
49217
|
});
|
|
48902
|
-
const nextRecord = mappingsChanged ? await this.store.reconcileTurnMappings(
|
|
49218
|
+
const nextRecord = mappingsChanged ? await this.store.reconcileTurnMappings(record3.hostThreadId, orderedMappings) : record3;
|
|
48903
49219
|
return {
|
|
48904
49220
|
record: nextRecord,
|
|
48905
49221
|
turns: aligned.map(
|
|
48906
|
-
({ mapping, snapshot: turn }) => projectHistoricalTurn({ turnId: mapping.hostTurnId, cwd:
|
|
49222
|
+
({ mapping, snapshot: turn }) => projectHistoricalTurn({ turnId: mapping.hostTurnId, cwd: record3.cwd, snapshot: turn })
|
|
48907
49223
|
)
|
|
48908
49224
|
};
|
|
48909
49225
|
}
|
|
@@ -48922,9 +49238,9 @@ function createExternalThreadRecordInput(input) {
|
|
|
48922
49238
|
};
|
|
48923
49239
|
}
|
|
48924
49240
|
function externalThreadValue(input) {
|
|
48925
|
-
const { record:
|
|
48926
|
-
const createdAt = Math.floor(Date.parse(
|
|
48927
|
-
const updatedAt = Math.floor(Date.parse(
|
|
49241
|
+
const { record: record3 } = input;
|
|
49242
|
+
const createdAt = Math.floor(Date.parse(record3.createdAt) / 1e3);
|
|
49243
|
+
const updatedAt = Math.floor(Date.parse(record3.updatedAt) / 1e3);
|
|
48928
49244
|
const preview = input.turns.flatMap((turn) => Array.isArray(turn.items) ? turn.items : []).find(
|
|
48929
49245
|
(item) => typeof item === "object" && item !== null && !Array.isArray(item) && item.type === "userMessage"
|
|
48930
49246
|
);
|
|
@@ -48933,10 +49249,10 @@ function externalThreadValue(input) {
|
|
|
48933
49249
|
(part) => typeof part === "object" && part !== null && !Array.isArray(part) && part.type === "text"
|
|
48934
49250
|
).map((part) => typeof part.text === "string" ? part.text : "").join("\n") : "";
|
|
48935
49251
|
return {
|
|
48936
|
-
id:
|
|
49252
|
+
id: record3.hostThreadId,
|
|
48937
49253
|
sessionId: input.sessionId,
|
|
48938
49254
|
path: null,
|
|
48939
|
-
cwd:
|
|
49255
|
+
cwd: record3.cwd,
|
|
48940
49256
|
source: "vscode",
|
|
48941
49257
|
threadSource: null,
|
|
48942
49258
|
modelProvider: "codexhost",
|
|
@@ -48947,13 +49263,13 @@ function externalThreadValue(input) {
|
|
|
48947
49263
|
status: input.loaded === false ? { type: "notLoaded" } : input.running ? { type: "active", activeFlags: [] } : { type: "idle" },
|
|
48948
49264
|
turns: input.turns,
|
|
48949
49265
|
preview: previewText,
|
|
48950
|
-
name:
|
|
49266
|
+
name: record3.title || null,
|
|
48951
49267
|
gitInfo: null,
|
|
48952
|
-
forkedFromId:
|
|
49268
|
+
forkedFromId: record3.forkSource?.hostThreadId ?? null,
|
|
48953
49269
|
parentThreadId: null,
|
|
48954
|
-
ephemeral:
|
|
49270
|
+
ephemeral: record3.ephemeral,
|
|
48955
49271
|
canAcceptDirectInput: input.loaded === false ? null : true,
|
|
48956
|
-
historyMode:
|
|
49272
|
+
historyMode: record3.historyMode,
|
|
48957
49273
|
isPinned: false,
|
|
48958
49274
|
agentNickname: null,
|
|
48959
49275
|
agentRole: null,
|
|
@@ -49083,7 +49399,8 @@ async function executeExternalThreadFork(input) {
|
|
|
49083
49399
|
session,
|
|
49084
49400
|
sessionId: source.sessionId,
|
|
49085
49401
|
thread,
|
|
49086
|
-
turns: aligned.turns
|
|
49402
|
+
turns: aligned.turns,
|
|
49403
|
+
...snapshot.value.state ? { restoredState: snapshot.value.state } : {}
|
|
49087
49404
|
});
|
|
49088
49405
|
return {
|
|
49089
49406
|
ok: true,
|
|
@@ -49244,6 +49561,96 @@ function listExternalItems(turns, params) {
|
|
|
49244
49561
|
}
|
|
49245
49562
|
|
|
49246
49563
|
// packages/host-runtime/src/external-thread-rollback.ts
|
|
49564
|
+
function sameCurrentConfiguration(current, replacement) {
|
|
49565
|
+
return current.effectiveModel?.id === replacement.effectiveModel?.id && current.effectiveThinkingOptionId === replacement.effectiveThinkingOptionId;
|
|
49566
|
+
}
|
|
49567
|
+
async function executeCurrentLastTurnRollback(input) {
|
|
49568
|
+
const { current, adapters, repository, runtime } = input;
|
|
49569
|
+
if (current.record.turnMappings.length === 0) {
|
|
49570
|
+
return {
|
|
49571
|
+
ok: false,
|
|
49572
|
+
error: { code: -32076, message: "External Thread has no Turn to roll back" }
|
|
49573
|
+
};
|
|
49574
|
+
}
|
|
49575
|
+
const currentNativeRef = current.record.nativeSessionRef;
|
|
49576
|
+
const adapter = adapters.get(current.harnessId);
|
|
49577
|
+
if (!currentNativeRef || !adapter) {
|
|
49578
|
+
return {
|
|
49579
|
+
ok: false,
|
|
49580
|
+
error: { code: -32079, message: "External Native Session is unavailable" }
|
|
49581
|
+
};
|
|
49582
|
+
}
|
|
49583
|
+
let opened;
|
|
49584
|
+
try {
|
|
49585
|
+
opened = await adapter.open({
|
|
49586
|
+
kind: "rollbackLastTurn",
|
|
49587
|
+
cwd: current.cwd,
|
|
49588
|
+
sourceRef: currentNativeRef
|
|
49589
|
+
});
|
|
49590
|
+
} catch {
|
|
49591
|
+
return { ok: false, error: { code: -32076, message: "External Thread rollback failed" } };
|
|
49592
|
+
}
|
|
49593
|
+
if (!opened.ok) {
|
|
49594
|
+
return { ok: false, error: mapExternalThreadHarnessError(opened.error, "fork") };
|
|
49595
|
+
}
|
|
49596
|
+
const session = opened.value;
|
|
49597
|
+
const finalNativeRef = session.initialState.nativeRef;
|
|
49598
|
+
if (!finalNativeRef || finalNativeRef.harnessId !== current.harnessId || finalNativeRef.nativeSessionId === currentNativeRef.nativeSessionId) {
|
|
49599
|
+
await session.close().catch(() => void 0);
|
|
49600
|
+
return {
|
|
49601
|
+
ok: false,
|
|
49602
|
+
error: { code: -32076, message: "External rollback did not create a distinct Session" }
|
|
49603
|
+
};
|
|
49604
|
+
}
|
|
49605
|
+
const snapshot = await session.readSnapshot();
|
|
49606
|
+
if (!snapshot.ok) {
|
|
49607
|
+
await session.close().catch(() => void 0);
|
|
49608
|
+
return { ok: false, error: mapExternalThreadHarnessError(snapshot.error, "read") };
|
|
49609
|
+
}
|
|
49610
|
+
if (snapshot.value.turns.length !== current.record.turnMappings.length - 1) {
|
|
49611
|
+
await session.close().catch(() => void 0);
|
|
49612
|
+
return {
|
|
49613
|
+
ok: false,
|
|
49614
|
+
error: { code: -32080, message: "External rollback did not remove exactly one Turn" }
|
|
49615
|
+
};
|
|
49616
|
+
}
|
|
49617
|
+
const replacementState = snapshot.value.state ?? session.initialState;
|
|
49618
|
+
if (!sameCurrentConfiguration(current.stateObserver.state, replacementState)) {
|
|
49619
|
+
await session.close().catch(() => void 0);
|
|
49620
|
+
return {
|
|
49621
|
+
ok: false,
|
|
49622
|
+
error: { code: -32080, message: "External rollback changed Model or Thinking" }
|
|
49623
|
+
};
|
|
49624
|
+
}
|
|
49625
|
+
let aligned;
|
|
49626
|
+
try {
|
|
49627
|
+
aligned = await repository.commitLastTurnRollback(
|
|
49628
|
+
current.record,
|
|
49629
|
+
finalNativeRef,
|
|
49630
|
+
snapshot.value
|
|
49631
|
+
);
|
|
49632
|
+
} catch {
|
|
49633
|
+
await session.close().catch(() => void 0);
|
|
49634
|
+
return {
|
|
49635
|
+
ok: false,
|
|
49636
|
+
error: { code: -32081, message: "External rollback could not be persisted" }
|
|
49637
|
+
};
|
|
49638
|
+
}
|
|
49639
|
+
const thread = externalThreadValue({
|
|
49640
|
+
record: aligned.record,
|
|
49641
|
+
turns: aligned.turns,
|
|
49642
|
+
sessionId: current.sessionId
|
|
49643
|
+
});
|
|
49644
|
+
await runtime.replace(current, {
|
|
49645
|
+
record: aligned.record,
|
|
49646
|
+
session,
|
|
49647
|
+
sessionId: current.sessionId,
|
|
49648
|
+
thread,
|
|
49649
|
+
turns: aligned.turns,
|
|
49650
|
+
restoredState: replacementState
|
|
49651
|
+
});
|
|
49652
|
+
return { ok: true, thread };
|
|
49653
|
+
}
|
|
49247
49654
|
async function executeExternalThreadRollback(input) {
|
|
49248
49655
|
const { derived, rollback, adapters, repository, runtime } = input;
|
|
49249
49656
|
if (derived.running) {
|
|
@@ -49251,6 +49658,14 @@ async function executeExternalThreadRollback(input) {
|
|
|
49251
49658
|
}
|
|
49252
49659
|
const refreshError = await runtime.refresh(derived);
|
|
49253
49660
|
if (refreshError) return { ok: false, error: refreshError };
|
|
49661
|
+
if (rollback.numTurns === 1 && derived.session.capabilities.history.rollbackLastTurn) {
|
|
49662
|
+
return executeCurrentLastTurnRollback({
|
|
49663
|
+
current: derived,
|
|
49664
|
+
adapters,
|
|
49665
|
+
repository,
|
|
49666
|
+
runtime
|
|
49667
|
+
});
|
|
49668
|
+
}
|
|
49254
49669
|
const forkSource = derived.record.forkSource;
|
|
49255
49670
|
if (!forkSource) {
|
|
49256
49671
|
return {
|
|
@@ -49470,9 +49885,10 @@ var ExternalThreadRuntime = class {
|
|
|
49470
49885
|
if (!this.#adapters.has(harnessId)) {
|
|
49471
49886
|
throw new Error(`External Harness '${input.record.harnessId}' is not registered`);
|
|
49472
49887
|
}
|
|
49473
|
-
const
|
|
49474
|
-
const
|
|
49475
|
-
const
|
|
49888
|
+
const initialState = input.restoredState ?? input.session.initialState;
|
|
49889
|
+
const effectiveModel = input.requestedModel ?? initialState.effectiveModel;
|
|
49890
|
+
const effectiveThinkingOptionId = input.requestedThinkingOptionId ?? initialState.effectiveThinkingOptionId;
|
|
49891
|
+
const effectivePermissionModeId = input.requestedPermissionModeId ?? initialState.effectivePermissionModeId;
|
|
49476
49892
|
const externalThread = {
|
|
49477
49893
|
id: input.record.hostThreadId,
|
|
49478
49894
|
cwd: input.record.cwd,
|
|
@@ -49484,7 +49900,7 @@ var ExternalThreadRuntime = class {
|
|
|
49484
49900
|
...effectivePermissionModeId ? { requestedPermissionModeId: effectivePermissionModeId } : {},
|
|
49485
49901
|
record: input.record,
|
|
49486
49902
|
sessionId: input.sessionId,
|
|
49487
|
-
stateObserver: new SessionStateObserver(
|
|
49903
|
+
stateObserver: new SessionStateObserver(initialState),
|
|
49488
49904
|
thread: input.thread,
|
|
49489
49905
|
transportModelId: input.record.transportModelId,
|
|
49490
49906
|
turns: input.turns,
|
|
@@ -49518,23 +49934,23 @@ var ExternalThreadRuntime = class {
|
|
|
49518
49934
|
async locate(threadId2) {
|
|
49519
49935
|
const loaded = this.#threads.get(threadId2);
|
|
49520
49936
|
if (loaded) return { kind: "external", record: loaded.record, thread: loaded };
|
|
49521
|
-
let
|
|
49937
|
+
let record3;
|
|
49522
49938
|
try {
|
|
49523
|
-
|
|
49939
|
+
record3 = await this.#repository.find(threadId2);
|
|
49524
49940
|
} catch {
|
|
49525
49941
|
return {
|
|
49526
49942
|
kind: "error",
|
|
49527
49943
|
error: { code: -32081, message: "External Thread ownership could not be read" }
|
|
49528
49944
|
};
|
|
49529
49945
|
}
|
|
49530
|
-
if (!
|
|
49531
|
-
if (
|
|
49946
|
+
if (!record3) return { kind: "official" };
|
|
49947
|
+
if (record3.state !== "ready" || !record3.nativeSessionRef) {
|
|
49532
49948
|
return {
|
|
49533
49949
|
kind: "error",
|
|
49534
49950
|
error: { code: -32079, message: "External Native Session is unavailable" }
|
|
49535
49951
|
};
|
|
49536
49952
|
}
|
|
49537
|
-
return { kind: "external", record:
|
|
49953
|
+
return { kind: "external", record: record3, thread: null };
|
|
49538
49954
|
}
|
|
49539
49955
|
async resolve(threadId2) {
|
|
49540
49956
|
const location = await this.locate(threadId2);
|
|
@@ -49542,12 +49958,12 @@ var ExternalThreadRuntime = class {
|
|
|
49542
49958
|
if (location.thread) {
|
|
49543
49959
|
return { kind: "external", thread: location.thread, historyFresh: false };
|
|
49544
49960
|
}
|
|
49545
|
-
const { record:
|
|
49961
|
+
const { record: record3 } = location;
|
|
49546
49962
|
let restoring = this.#restores.get(threadId2);
|
|
49547
49963
|
if (!restoring) {
|
|
49548
49964
|
const restored = this.#threads.get(threadId2);
|
|
49549
49965
|
if (restored) return { kind: "external", thread: restored, historyFresh: false };
|
|
49550
|
-
restoring = this.#restore(
|
|
49966
|
+
restoring = this.#restore(record3).finally(() => {
|
|
49551
49967
|
this.#restores.delete(threadId2);
|
|
49552
49968
|
});
|
|
49553
49969
|
this.#restores.set(threadId2, restoring);
|
|
@@ -49601,10 +50017,10 @@ var ExternalThreadRuntime = class {
|
|
|
49601
50017
|
return failure2;
|
|
49602
50018
|
}
|
|
49603
50019
|
}
|
|
49604
|
-
async #restore(
|
|
49605
|
-
const harnessId =
|
|
50020
|
+
async #restore(record3) {
|
|
50021
|
+
const harnessId = record3.harnessId;
|
|
49606
50022
|
const adapter = this.#adapters.get(harnessId);
|
|
49607
|
-
if (!adapter || !
|
|
50023
|
+
if (!adapter || !record3.nativeSessionRef) {
|
|
49608
50024
|
throw new ExternalThreadOpenError({
|
|
49609
50025
|
code: -32077,
|
|
49610
50026
|
message: "External Harness is unavailable"
|
|
@@ -49612,8 +50028,8 @@ var ExternalThreadRuntime = class {
|
|
|
49612
50028
|
}
|
|
49613
50029
|
const opened = await adapter.open({
|
|
49614
50030
|
kind: "resume",
|
|
49615
|
-
cwd:
|
|
49616
|
-
nativeRef:
|
|
50031
|
+
cwd: record3.cwd,
|
|
50032
|
+
nativeRef: record3.nativeSessionRef
|
|
49617
50033
|
});
|
|
49618
50034
|
if (!opened.ok) {
|
|
49619
50035
|
throw new ExternalThreadOpenError(mapExternalThreadHarnessError(opened.error, "resume"));
|
|
@@ -49624,7 +50040,7 @@ var ExternalThreadRuntime = class {
|
|
|
49624
50040
|
if (!snapshot.ok) {
|
|
49625
50041
|
throw new ExternalThreadOpenError(mapExternalThreadHarnessError(snapshot.error, "read"));
|
|
49626
50042
|
}
|
|
49627
|
-
const aligned = await this.#repository.alignSnapshot(
|
|
50043
|
+
const aligned = await this.#repository.alignSnapshot(record3, snapshot.value);
|
|
49628
50044
|
const sessionId = await this.#repository.sessionTreeId(aligned.record);
|
|
49629
50045
|
return this.register({
|
|
49630
50046
|
record: aligned.record,
|
|
@@ -49635,7 +50051,8 @@ var ExternalThreadRuntime = class {
|
|
|
49635
50051
|
turns: aligned.turns,
|
|
49636
50052
|
sessionId
|
|
49637
50053
|
}),
|
|
49638
|
-
turns: aligned.turns
|
|
50054
|
+
turns: aligned.turns,
|
|
50055
|
+
...snapshot.value.state ? { restoredState: snapshot.value.state } : {}
|
|
49639
50056
|
});
|
|
49640
50057
|
} catch (error52) {
|
|
49641
50058
|
await session.close().catch(() => void 0);
|
|
@@ -49829,17 +50246,17 @@ function externalAnchor(entry) {
|
|
|
49829
50246
|
if (entry.source !== "external") throw new Error("Expected an External Thread list entry");
|
|
49830
50247
|
return { timestamp: entry.timestamp, threadId: threadId(entry.thread) };
|
|
49831
50248
|
}
|
|
49832
|
-
function includesExternalRecord(
|
|
49833
|
-
if (
|
|
49834
|
-
if (
|
|
49835
|
-
if (query.cwd !== null && !query.cwd.includes(
|
|
50249
|
+
function includesExternalRecord(record3, query) {
|
|
50250
|
+
if (record3.state !== "ready" || !record3.nativeSessionRef) return false;
|
|
50251
|
+
if (record3.archived !== query.archived) return false;
|
|
50252
|
+
if (query.cwd !== null && !query.cwd.includes(record3.cwd)) return false;
|
|
49836
50253
|
if (query.modelProviders !== null && query.modelProviders.length > 0 && !query.modelProviders.includes("codexhost")) {
|
|
49837
50254
|
return false;
|
|
49838
50255
|
}
|
|
49839
50256
|
if (query.sourceKinds !== null && query.sourceKinds.length > 0 && !query.sourceKinds.includes("vscode")) {
|
|
49840
50257
|
return false;
|
|
49841
50258
|
}
|
|
49842
|
-
if (query.searchTerm !== null && !
|
|
50259
|
+
if (query.searchTerm !== null && !record3.title.toLowerCase().includes(query.searchTerm.toLowerCase())) {
|
|
49843
50260
|
return false;
|
|
49844
50261
|
}
|
|
49845
50262
|
if (query.parentThreadId !== null || query.ancestorThreadId !== null) return false;
|
|
@@ -49847,47 +50264,47 @@ function includesExternalRecord(record2, query) {
|
|
|
49847
50264
|
return true;
|
|
49848
50265
|
}
|
|
49849
50266
|
function resolveExternalSessionTreeIds(records) {
|
|
49850
|
-
const byId = new Map(records.map((
|
|
50267
|
+
const byId = new Map(records.map((record3) => [record3.hostThreadId, record3]));
|
|
49851
50268
|
const resolved = /* @__PURE__ */ new Map();
|
|
49852
50269
|
const resolve2 = (start) => {
|
|
49853
50270
|
const cached2 = resolved.get(start.hostThreadId);
|
|
49854
50271
|
if (cached2) return cached2;
|
|
49855
|
-
const
|
|
50272
|
+
const path11 = [];
|
|
49856
50273
|
const visited = /* @__PURE__ */ new Set();
|
|
49857
50274
|
let current = start;
|
|
49858
50275
|
while (true) {
|
|
49859
50276
|
const known = resolved.get(current.hostThreadId);
|
|
49860
50277
|
if (known) {
|
|
49861
|
-
for (const
|
|
50278
|
+
for (const record3 of path11) resolved.set(record3.hostThreadId, known);
|
|
49862
50279
|
return known;
|
|
49863
50280
|
}
|
|
49864
50281
|
if (visited.has(current.hostThreadId)) {
|
|
49865
50282
|
throw new Error("External Thread Fork tree contains a cycle");
|
|
49866
50283
|
}
|
|
49867
50284
|
visited.add(current.hostThreadId);
|
|
49868
|
-
|
|
50285
|
+
path11.push(current);
|
|
49869
50286
|
const sourceId = current.forkSource?.hostThreadId;
|
|
49870
50287
|
const source = sourceId ? byId.get(sourceId) : void 0;
|
|
49871
50288
|
if (!source) {
|
|
49872
50289
|
const root = current.hostThreadId;
|
|
49873
|
-
for (const
|
|
50290
|
+
for (const record3 of path11) resolved.set(record3.hostThreadId, root);
|
|
49874
50291
|
return root;
|
|
49875
50292
|
}
|
|
49876
50293
|
current = source;
|
|
49877
50294
|
}
|
|
49878
50295
|
};
|
|
49879
|
-
for (const
|
|
50296
|
+
for (const record3 of records) resolve2(record3);
|
|
49880
50297
|
return resolved;
|
|
49881
50298
|
}
|
|
49882
50299
|
function listExternalThreadMetadata(input) {
|
|
49883
50300
|
if (!input.query.supportsExternal) return { data: [], hasMore: false };
|
|
49884
50301
|
const sessionIds = resolveExternalSessionTreeIds(input.records);
|
|
49885
|
-
const entries = input.records.filter((
|
|
49886
|
-
const runtime = input.runtimeFor(
|
|
49887
|
-
const sessionId = sessionIds.get(
|
|
50302
|
+
const entries = input.records.filter((record3) => includesExternalRecord(record3, input.query)).map((record3) => {
|
|
50303
|
+
const runtime = input.runtimeFor(record3.hostThreadId);
|
|
50304
|
+
const sessionId = sessionIds.get(record3.hostThreadId);
|
|
49888
50305
|
if (!sessionId) throw new Error("External Thread Session tree could not be resolved");
|
|
49889
50306
|
const thread = externalThreadValue({
|
|
49890
|
-
record:
|
|
50307
|
+
record: record3,
|
|
49891
50308
|
turns: [],
|
|
49892
50309
|
sessionId,
|
|
49893
50310
|
...runtime ? { running: runtime.running } : { loaded: false }
|
|
@@ -49895,7 +50312,7 @@ function listExternalThreadMetadata(input) {
|
|
|
49895
50312
|
return {
|
|
49896
50313
|
source: "external",
|
|
49897
50314
|
thread,
|
|
49898
|
-
timestamp: input.query.sortKey === "created_at" ? unixTimestamp(
|
|
50315
|
+
timestamp: input.query.sortKey === "created_at" ? unixTimestamp(record3.createdAt, "createdAt") : unixTimestamp(record3.updatedAt, "updatedAt")
|
|
49899
50316
|
};
|
|
49900
50317
|
}).sort((left, right) => compareThreadListEntries(left, right, input.query.sortDirection)).filter(
|
|
49901
50318
|
(entry) => !input.anchor || compareExternalAnchor(entry, input.anchor, input.query.sortDirection) > 0
|
|
@@ -49977,7 +50394,7 @@ async function aggregateThreadList(input) {
|
|
|
49977
50394
|
let officialIndex = 0;
|
|
49978
50395
|
let firstOfficialBackwardsCursor;
|
|
49979
50396
|
let officialRequestCount = 0;
|
|
49980
|
-
const externalIds = new Set(input.records.map((
|
|
50397
|
+
const externalIds = new Set(input.records.map((record3) => record3.hostThreadId));
|
|
49981
50398
|
const requestOfficial = async (cursor, limit) => {
|
|
49982
50399
|
officialRequestCount += 1;
|
|
49983
50400
|
if (officialRequestCount > MAX_OFFICIAL_PAGE_REQUESTS) {
|
|
@@ -50037,7 +50454,7 @@ async function aggregateThreadList(input) {
|
|
|
50037
50454
|
let nextOfficialCursor = officialCursor;
|
|
50038
50455
|
let nextOfficialDone = officialDone;
|
|
50039
50456
|
const finalOfficialBatch = officialBatch;
|
|
50040
|
-
if (finalOfficialBatch) {
|
|
50457
|
+
if (finalOfficialBatch && !officialDone) {
|
|
50041
50458
|
if (officialIndex === 0) {
|
|
50042
50459
|
nextOfficialCursor = officialBatchStart;
|
|
50043
50460
|
nextOfficialDone = false;
|
|
@@ -50101,7 +50518,15 @@ function officialEnvironment(source) {
|
|
|
50101
50518
|
"CODEXHOST_PI_COMMAND",
|
|
50102
50519
|
"CODEXHOST_ENABLE_CLAUDE_CODE",
|
|
50103
50520
|
"CODEXHOST_CLAUDE_COMMAND",
|
|
50104
|
-
"CODEXHOST_STOCK_CODEX_PATH"
|
|
50521
|
+
"CODEXHOST_STOCK_CODEX_PATH",
|
|
50522
|
+
"CODEXHOST_LAUNCHER_PID",
|
|
50523
|
+
"CODEXHOST_LAUNCHER_EXECUTABLE",
|
|
50524
|
+
"CODEXHOST_CONTROL_PORT",
|
|
50525
|
+
"CODEXHOST_CONTROL_NONCE",
|
|
50526
|
+
"CODEXHOST_NPM_NODE_PATH",
|
|
50527
|
+
"CODEXHOST_NPM_CLI_PATH",
|
|
50528
|
+
"CODEXHOST_NPM_LAUNCHER_PATH",
|
|
50529
|
+
"CODEXHOST_NPM_PACKAGE_ROOT"
|
|
50105
50530
|
]);
|
|
50106
50531
|
return Object.fromEntries(Object.entries(source).filter(([key]) => !internal.has(key)));
|
|
50107
50532
|
}
|
|
@@ -50145,7 +50570,7 @@ function isHostApprovalRequestId(value) {
|
|
|
50145
50570
|
function isHostQuestionRequestId(value) {
|
|
50146
50571
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= HOST_QUESTION_REQUEST_ID_MIN && value <= HOST_QUESTION_REQUEST_ID_MAX;
|
|
50147
50572
|
}
|
|
50148
|
-
function classifyCreateRequestRoute(request,
|
|
50573
|
+
function classifyCreateRequestRoute(request, defaultAgent) {
|
|
50149
50574
|
const route = decodeCreateRoute(request);
|
|
50150
50575
|
if (!route) return null;
|
|
50151
50576
|
if (route.harnessId !== "codex") {
|
|
@@ -50159,8 +50584,8 @@ function classifyCreateRequestRoute(request, defaultAgent2) {
|
|
|
50159
50584
|
return {
|
|
50160
50585
|
requestMethod: "thread/start",
|
|
50161
50586
|
modelCarrier: "official-model",
|
|
50162
|
-
selectedHarness:
|
|
50163
|
-
selectionSource:
|
|
50587
|
+
selectedHarness: defaultAgent,
|
|
50588
|
+
selectionSource: defaultAgent === "pi" ? "default-agent" : "official-model"
|
|
50164
50589
|
};
|
|
50165
50590
|
}
|
|
50166
50591
|
function requestObject(request) {
|
|
@@ -50322,6 +50747,10 @@ var AppServerHost = class {
|
|
|
50322
50747
|
continue;
|
|
50323
50748
|
}
|
|
50324
50749
|
const request = requestResult.data;
|
|
50750
|
+
if (request.method === "codexhost/update/check" || request.method === "codexhost/update/start" || request.method === "codexhost/update/status") {
|
|
50751
|
+
await this.#handleUpdateRequest(request);
|
|
50752
|
+
continue;
|
|
50753
|
+
}
|
|
50325
50754
|
if (request.method === "codexhost/harness/inspect") {
|
|
50326
50755
|
await this.#inspectHarness(request);
|
|
50327
50756
|
continue;
|
|
@@ -50334,6 +50763,10 @@ var AppServerHost = class {
|
|
|
50334
50763
|
await this.#inspectThread(request);
|
|
50335
50764
|
continue;
|
|
50336
50765
|
}
|
|
50766
|
+
if (request.method === "codexhost/thread/usage/inspect") {
|
|
50767
|
+
await this.#inspectThreadUsage(request);
|
|
50768
|
+
continue;
|
|
50769
|
+
}
|
|
50337
50770
|
if (request.method === "codexhost/thread/ownership/list") {
|
|
50338
50771
|
await this.#listThreadOwnership(request);
|
|
50339
50772
|
continue;
|
|
@@ -50662,9 +51095,9 @@ var AppServerHost = class {
|
|
|
50662
51095
|
);
|
|
50663
51096
|
return;
|
|
50664
51097
|
}
|
|
50665
|
-
let
|
|
51098
|
+
let record3;
|
|
50666
51099
|
try {
|
|
50667
|
-
|
|
51100
|
+
record3 = await this.#repository.setArchived(location.record.hostThreadId, archived);
|
|
50668
51101
|
} catch {
|
|
50669
51102
|
await this.#writer.json(
|
|
50670
51103
|
rpcError(request, -32081, "External Thread archive state could not be persisted")
|
|
@@ -50672,13 +51105,13 @@ var AppServerHost = class {
|
|
|
50672
51105
|
return;
|
|
50673
51106
|
}
|
|
50674
51107
|
const projected = externalThreadValue({
|
|
50675
|
-
record:
|
|
51108
|
+
record: record3,
|
|
50676
51109
|
turns: [],
|
|
50677
51110
|
sessionId,
|
|
50678
51111
|
...location.thread ? { running: location.thread.running } : { loaded: false }
|
|
50679
51112
|
});
|
|
50680
51113
|
if (location.thread) {
|
|
50681
|
-
location.thread.record =
|
|
51114
|
+
location.thread.record = record3;
|
|
50682
51115
|
location.thread.thread = {
|
|
50683
51116
|
...location.thread.thread,
|
|
50684
51117
|
...projected,
|
|
@@ -50690,9 +51123,40 @@ var AppServerHost = class {
|
|
|
50690
51123
|
);
|
|
50691
51124
|
await this.#writer.json({
|
|
50692
51125
|
method: archived ? "thread/archived" : "thread/unarchived",
|
|
50693
|
-
params: { threadId:
|
|
51126
|
+
params: { threadId: record3.hostThreadId }
|
|
50694
51127
|
});
|
|
50695
51128
|
}
|
|
51129
|
+
async #handleUpdateRequest(request) {
|
|
51130
|
+
const params = updateEmptyParamsSchema.safeParse(
|
|
51131
|
+
request.params === void 0 ? {} : request.params
|
|
51132
|
+
);
|
|
51133
|
+
if (!params.success) {
|
|
51134
|
+
await this.#writer.json(rpcError(request, -32602, "Update params must be empty"));
|
|
51135
|
+
return;
|
|
51136
|
+
}
|
|
51137
|
+
const coordinator = this.#options.updateCoordinator;
|
|
51138
|
+
if (!coordinator) {
|
|
51139
|
+
await this.#writer.json(rpcError(request, -32090, "Application updates are unavailable"));
|
|
51140
|
+
return;
|
|
51141
|
+
}
|
|
51142
|
+
try {
|
|
51143
|
+
if (request.method === "codexhost/update/check") {
|
|
51144
|
+
const result2 = updateCheckResultSchema.parse(await coordinator.check());
|
|
51145
|
+
await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
|
|
51146
|
+
return;
|
|
51147
|
+
}
|
|
51148
|
+
if (request.method === "codexhost/update/status") {
|
|
51149
|
+
const result2 = updateStatusResultSchema.parse(await coordinator.status());
|
|
51150
|
+
await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result2) }));
|
|
51151
|
+
return;
|
|
51152
|
+
}
|
|
51153
|
+
const result = updateStartResultSchema.parse(await coordinator.start());
|
|
51154
|
+
await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result) }));
|
|
51155
|
+
coordinator.requestShutdown();
|
|
51156
|
+
} catch (error52) {
|
|
51157
|
+
await this.#writer.json(rpcError(request, -32091, errorMessage3(error52).slice(0, 500)));
|
|
51158
|
+
}
|
|
51159
|
+
}
|
|
50696
51160
|
async #inspectHarness(request) {
|
|
50697
51161
|
const params = harnessInspectParamsSchema.safeParse(request.params);
|
|
50698
51162
|
if (!params.success) {
|
|
@@ -50760,11 +51224,35 @@ var AppServerHost = class {
|
|
|
50760
51224
|
effectivePermissionModeId: resolution.thread.stateObserver.state.effectivePermissionModeId
|
|
50761
51225
|
} : {},
|
|
50762
51226
|
history: resolution.thread.session.capabilities.history,
|
|
51227
|
+
...resolution.thread.latestUsage ? { usage: resolution.thread.latestUsage } : {},
|
|
50763
51228
|
locked: true
|
|
50764
51229
|
}
|
|
50765
51230
|
);
|
|
50766
51231
|
await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(inspection) }));
|
|
50767
51232
|
}
|
|
51233
|
+
async #inspectThreadUsage(request) {
|
|
51234
|
+
const params = threadUsageInspectionParamsSchema.safeParse(request.params);
|
|
51235
|
+
if (!params.success) {
|
|
51236
|
+
await this.#writer.json(rpcError(request, -32602, "Invalid Thread Usage inspection params"));
|
|
51237
|
+
return;
|
|
51238
|
+
}
|
|
51239
|
+
const resolution = await this.#resolveExternalThread(params.data.threadId);
|
|
51240
|
+
if (resolution.kind === "error") {
|
|
51241
|
+
await this.#writer.json(rpcError(request, resolution.error.code, resolution.error.message));
|
|
51242
|
+
return;
|
|
51243
|
+
}
|
|
51244
|
+
if (resolution.kind === "official") {
|
|
51245
|
+
await this.#writer.json(
|
|
51246
|
+
rpcError(request, -32079, "Thread Usage is unavailable for official Codex Threads")
|
|
51247
|
+
);
|
|
51248
|
+
return;
|
|
51249
|
+
}
|
|
51250
|
+
const result = threadUsageInspectionSchema.parse({
|
|
51251
|
+
threadId: params.data.threadId,
|
|
51252
|
+
usage: resolution.thread.latestUsage
|
|
51253
|
+
});
|
|
51254
|
+
await this.#writer.json(rpcEnvelope(request, { result: jsonValueSchema.parse(result) }));
|
|
51255
|
+
}
|
|
50768
51256
|
async #listThreadOwnership(request) {
|
|
50769
51257
|
const params = threadOwnershipListParamsSchema.safeParse(request.params);
|
|
50770
51258
|
if (!params.success) {
|
|
@@ -50774,8 +51262,8 @@ var AppServerHost = class {
|
|
|
50774
51262
|
try {
|
|
50775
51263
|
const threads = await Promise.all(
|
|
50776
51264
|
params.data.threadIds.map(async (threadId2) => {
|
|
50777
|
-
const
|
|
50778
|
-
return
|
|
51265
|
+
const record3 = await this.#repository.find(threadId2);
|
|
51266
|
+
return record3 ? { threadId: threadId2, owner: "external", harnessId: record3.harnessId } : { threadId: threadId2, owner: "codex" };
|
|
50779
51267
|
})
|
|
50780
51268
|
);
|
|
50781
51269
|
const result = threadOwnershipListResultSchema.parse({ threads });
|
|
@@ -51042,9 +51530,9 @@ var AppServerHost = class {
|
|
|
51042
51530
|
ephemeral: params.ephemeral === true,
|
|
51043
51531
|
historyMode: params.historyMode === "paginated" ? "paginated" : "legacy"
|
|
51044
51532
|
});
|
|
51045
|
-
let
|
|
51533
|
+
let record3;
|
|
51046
51534
|
try {
|
|
51047
|
-
|
|
51535
|
+
record3 = await this.#repository.createProvisional(recordInput);
|
|
51048
51536
|
} catch {
|
|
51049
51537
|
this.#routeObservationTracker.rejectCreate(request.id);
|
|
51050
51538
|
await this.#writer.json(rpcError(request, -32081, "External Thread could not be persisted"));
|
|
@@ -51059,7 +51547,7 @@ var AppServerHost = class {
|
|
|
51059
51547
|
});
|
|
51060
51548
|
if (!sessionResult.ok) {
|
|
51061
51549
|
this.#routeObservationTracker.rejectCreate(request.id);
|
|
51062
|
-
await this.#repository.removeProvisional(
|
|
51550
|
+
await this.#repository.removeProvisional(record3.hostThreadId).catch(() => void 0);
|
|
51063
51551
|
const mapped = mapExternalThreadHarnessError(sessionResult.error, "create");
|
|
51064
51552
|
await this.#writer.json(rpcError(request, mapped.code, mapped.message));
|
|
51065
51553
|
return;
|
|
@@ -51067,20 +51555,20 @@ var AppServerHost = class {
|
|
|
51067
51555
|
const session = sessionResult.value;
|
|
51068
51556
|
try {
|
|
51069
51557
|
if (session.initialState.nativeRef) {
|
|
51070
|
-
|
|
51071
|
-
|
|
51558
|
+
record3 = await this.#repository.commitNative(
|
|
51559
|
+
record3.hostThreadId,
|
|
51072
51560
|
session.initialState.nativeRef
|
|
51073
51561
|
);
|
|
51074
51562
|
}
|
|
51075
51563
|
const thread = externalThreadValue({
|
|
51076
|
-
record:
|
|
51564
|
+
record: record3,
|
|
51077
51565
|
turns: [],
|
|
51078
|
-
sessionId:
|
|
51566
|
+
sessionId: record3.hostThreadId
|
|
51079
51567
|
});
|
|
51080
51568
|
const externalThread = this.#registerExternalThread({
|
|
51081
|
-
record:
|
|
51569
|
+
record: record3,
|
|
51082
51570
|
session,
|
|
51083
|
-
sessionId:
|
|
51571
|
+
sessionId: record3.hostThreadId,
|
|
51084
51572
|
thread,
|
|
51085
51573
|
turns: [],
|
|
51086
51574
|
...requestedModel ? { requestedModel } : {},
|
|
@@ -51113,10 +51601,10 @@ var AppServerHost = class {
|
|
|
51113
51601
|
params: { thread }
|
|
51114
51602
|
});
|
|
51115
51603
|
} catch {
|
|
51116
|
-
this.#externalRuntime.remove(
|
|
51117
|
-
this.#routeObservationTracker.forgetThread(
|
|
51604
|
+
this.#externalRuntime.remove(record3.hostThreadId);
|
|
51605
|
+
this.#routeObservationTracker.forgetThread(record3.hostThreadId);
|
|
51118
51606
|
await session.close().catch(() => void 0);
|
|
51119
|
-
await this.#repository.removeProvisional(
|
|
51607
|
+
await this.#repository.removeProvisional(record3.hostThreadId).catch(() => void 0);
|
|
51120
51608
|
await this.#writer.json(rpcError(request, -32081, "External Thread could not be persisted"));
|
|
51121
51609
|
}
|
|
51122
51610
|
}
|
|
@@ -51231,9 +51719,9 @@ var AppServerHost = class {
|
|
|
51231
51719
|
);
|
|
51232
51720
|
return;
|
|
51233
51721
|
}
|
|
51234
|
-
let
|
|
51722
|
+
let record3;
|
|
51235
51723
|
try {
|
|
51236
|
-
|
|
51724
|
+
record3 = await this.#repository.setTitle(location.record.hostThreadId, name);
|
|
51237
51725
|
} catch {
|
|
51238
51726
|
await this.#writer.json(
|
|
51239
51727
|
rpcError(request, -32081, "External Thread title could not be persisted")
|
|
@@ -51241,7 +51729,7 @@ var AppServerHost = class {
|
|
|
51241
51729
|
return;
|
|
51242
51730
|
}
|
|
51243
51731
|
if (location.thread) {
|
|
51244
|
-
location.thread.record =
|
|
51732
|
+
location.thread.record = record3;
|
|
51245
51733
|
location.thread.thread.name = name;
|
|
51246
51734
|
location.thread.thread.updatedAt = unixSeconds();
|
|
51247
51735
|
}
|
|
@@ -51404,128 +51892,20 @@ var AppServerHost = class {
|
|
|
51404
51892
|
return;
|
|
51405
51893
|
}
|
|
51406
51894
|
const params = requestObject(request);
|
|
51407
|
-
|
|
51408
|
-
|
|
51409
|
-
|
|
51410
|
-
|
|
51411
|
-
|
|
51412
|
-
|
|
51413
|
-
}
|
|
51414
|
-
if (typeof params.model === "string" && requestedSelection === null) {
|
|
51415
|
-
await this.#writer.json(
|
|
51416
|
-
rpcError(request, -32602, "Turn Model carrier does not belong to the Thread Harness")
|
|
51417
|
-
);
|
|
51418
|
-
return;
|
|
51419
|
-
}
|
|
51420
|
-
const requestedModel = requestedSelection?.model;
|
|
51421
|
-
if (requestedModel) {
|
|
51422
|
-
if (!thread.session.capabilities.configuration.selectModel) {
|
|
51423
|
-
await this.#writer.json(
|
|
51424
|
-
rpcError(request, -32078, "External Harness does not support Model selection")
|
|
51425
|
-
);
|
|
51426
|
-
return;
|
|
51427
|
-
}
|
|
51428
|
-
const current = thread.stateObserver.state.effectiveModel;
|
|
51429
|
-
const pendingCreateSelection = current === void 0 && thread.requestedModel?.id === requestedModel.id;
|
|
51430
|
-
if (current?.id !== requestedModel.id && !pendingCreateSelection) {
|
|
51431
|
-
const beforeRevision = thread.stateObserver.revision;
|
|
51432
|
-
const selection = await thread.session.execute({
|
|
51433
|
-
type: "model.select",
|
|
51434
|
-
model: requestedModel
|
|
51435
|
-
});
|
|
51436
|
-
if (!selection.ok) {
|
|
51437
|
-
await this.#writer.json(rpcError(request, -32078, selection.error.message));
|
|
51438
|
-
return;
|
|
51439
|
-
}
|
|
51440
|
-
try {
|
|
51441
|
-
const state = await thread.stateObserver.waitForChange(beforeRevision);
|
|
51442
|
-
if (state.effectiveModel?.id !== requestedModel.id) {
|
|
51443
|
-
throw new Error("Harness Session activated a different Model");
|
|
51444
|
-
}
|
|
51445
|
-
} catch (error52) {
|
|
51446
|
-
await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
|
|
51447
|
-
return;
|
|
51448
|
-
}
|
|
51449
|
-
}
|
|
51450
|
-
thread.requestedModel = requestedModel;
|
|
51451
|
-
}
|
|
51452
|
-
const requestedThinkingOptionId = requestedSelection?.thinkingOptionId;
|
|
51453
|
-
if (requestedThinkingOptionId) {
|
|
51454
|
-
if (!thread.session.capabilities.configuration.selectThinkingOption) {
|
|
51455
|
-
await this.#writer.json(
|
|
51456
|
-
rpcError(request, -32078, "External Harness does not support Thinking selection")
|
|
51457
|
-
);
|
|
51895
|
+
if (typeof params.model === "string") {
|
|
51896
|
+
let route;
|
|
51897
|
+
try {
|
|
51898
|
+
route = decodeCreateRoute({ id: request.id, method: "thread/start", params });
|
|
51899
|
+
} catch (error52) {
|
|
51900
|
+
await this.#writer.json(rpcError(request, -32602, errorMessage3(error52)));
|
|
51458
51901
|
return;
|
|
51459
51902
|
}
|
|
51460
|
-
|
|
51461
|
-
const pendingCreateSelection = current === void 0 && thread.requestedThinkingOptionId === requestedThinkingOptionId;
|
|
51462
|
-
if (current !== requestedThinkingOptionId && !pendingCreateSelection) {
|
|
51463
|
-
const beforeRevision = thread.stateObserver.revision;
|
|
51464
|
-
const selection = await thread.session.execute({
|
|
51465
|
-
type: "thinking.select",
|
|
51466
|
-
thinkingOptionId: requestedThinkingOptionId
|
|
51467
|
-
});
|
|
51468
|
-
if (!selection.ok) {
|
|
51469
|
-
await this.#writer.json(rpcError(request, -32078, selection.error.message));
|
|
51470
|
-
return;
|
|
51471
|
-
}
|
|
51472
|
-
try {
|
|
51473
|
-
const state = await thread.stateObserver.waitForChange(beforeRevision);
|
|
51474
|
-
if (!state.effectiveThinkingOptionId) {
|
|
51475
|
-
throw new Error("Harness Session did not report effective Thinking");
|
|
51476
|
-
}
|
|
51477
|
-
thread.requestedThinkingOptionId = state.effectiveThinkingOptionId;
|
|
51478
|
-
} catch (error52) {
|
|
51479
|
-
await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
|
|
51480
|
-
return;
|
|
51481
|
-
}
|
|
51482
|
-
}
|
|
51483
|
-
}
|
|
51484
|
-
const requestedPermissionModeId = requestedSelection?.permissionModeId;
|
|
51485
|
-
if (requestedPermissionModeId) {
|
|
51486
|
-
if (!thread.session.capabilities.configuration.selectPermissionMode) {
|
|
51903
|
+
if (route?.harnessId !== "codex" && route?.harnessId !== thread.harnessId) {
|
|
51487
51904
|
await this.#writer.json(
|
|
51488
|
-
rpcError(request, -
|
|
51905
|
+
rpcError(request, -32602, "Turn Model carrier does not belong to the Thread Harness")
|
|
51489
51906
|
);
|
|
51490
51907
|
return;
|
|
51491
51908
|
}
|
|
51492
|
-
const current = thread.stateObserver.state.effectivePermissionModeId;
|
|
51493
|
-
const pendingCreateSelection = current === void 0 && thread.requestedPermissionModeId === requestedPermissionModeId;
|
|
51494
|
-
if (current !== requestedPermissionModeId && !pendingCreateSelection) {
|
|
51495
|
-
const beforeRevision = thread.stateObserver.revision;
|
|
51496
|
-
const selection = await thread.session.execute({
|
|
51497
|
-
type: "permissionMode.select",
|
|
51498
|
-
permissionModeId: requestedPermissionModeId
|
|
51499
|
-
});
|
|
51500
|
-
if (!selection.ok) {
|
|
51501
|
-
await this.#writer.json(rpcError(request, -32078, selection.error.message));
|
|
51502
|
-
return;
|
|
51503
|
-
}
|
|
51504
|
-
try {
|
|
51505
|
-
const state = await thread.stateObserver.waitForChange(beforeRevision);
|
|
51506
|
-
if (state.effectivePermissionModeId !== requestedPermissionModeId) {
|
|
51507
|
-
throw new Error("Harness Session activated a different Permission Mode");
|
|
51508
|
-
}
|
|
51509
|
-
} catch (error52) {
|
|
51510
|
-
await this.#writer.json(rpcError(request, -32078, errorMessage3(error52)));
|
|
51511
|
-
return;
|
|
51512
|
-
}
|
|
51513
|
-
}
|
|
51514
|
-
thread.requestedPermissionModeId = requestedPermissionModeId;
|
|
51515
|
-
}
|
|
51516
|
-
if (requestedModel || requestedThinkingOptionId || requestedPermissionModeId) {
|
|
51517
|
-
const transportModelId = params.model;
|
|
51518
|
-
thread.transportModelId = transportModelId;
|
|
51519
|
-
if (requestedPermissionModeId) {
|
|
51520
|
-
try {
|
|
51521
|
-
thread.record = await this.#repository.setTransportModelId(
|
|
51522
|
-
thread.record.hostThreadId,
|
|
51523
|
-
transportModelId
|
|
51524
|
-
);
|
|
51525
|
-
} catch (error52) {
|
|
51526
|
-
this.#diagnose(error52);
|
|
51527
|
-
}
|
|
51528
|
-
}
|
|
51529
51909
|
}
|
|
51530
51910
|
let text;
|
|
51531
51911
|
try {
|
|
@@ -51955,22 +52335,944 @@ var AppServerHost = class {
|
|
|
51955
52335
|
}
|
|
51956
52336
|
};
|
|
51957
52337
|
|
|
52338
|
+
// packages/host-runtime/src/update-coordinator.ts
|
|
52339
|
+
import { createConnection } from "node:net";
|
|
52340
|
+
|
|
52341
|
+
// packages/update-manager/dist/distribution.js
|
|
52342
|
+
import { lstat, readFile as readFile2 } from "node:fs/promises";
|
|
52343
|
+
import path8 from "node:path";
|
|
52344
|
+
|
|
52345
|
+
// packages/update-manager/dist/status.js
|
|
52346
|
+
var STATUS_SCHEMA_VERSION = 1;
|
|
52347
|
+
var SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
|
|
52348
|
+
function requireSemanticVersion(value) {
|
|
52349
|
+
if (!SEMVER_PATTERN.test(value))
|
|
52350
|
+
throw new Error("update version must be valid semantic version");
|
|
52351
|
+
return value;
|
|
52352
|
+
}
|
|
52353
|
+
function preparedStatus(version2, installation, now) {
|
|
52354
|
+
return {
|
|
52355
|
+
schemaVersion: STATUS_SCHEMA_VERSION,
|
|
52356
|
+
version: version2,
|
|
52357
|
+
installation,
|
|
52358
|
+
phase: "prepared",
|
|
52359
|
+
updatedAt: Math.floor(now / 1e3)
|
|
52360
|
+
};
|
|
52361
|
+
}
|
|
52362
|
+
function parseUpdateStatus(value) {
|
|
52363
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
52364
|
+
throw new Error("background update status must be an object");
|
|
52365
|
+
}
|
|
52366
|
+
const status = value;
|
|
52367
|
+
const allowed2 = ["error", "installation", "phase", "schemaVersion", "updatedAt", "version"];
|
|
52368
|
+
if (Object.keys(status).some((key) => !allowed2.includes(key))) {
|
|
52369
|
+
throw new Error("background update status contains unknown fields");
|
|
52370
|
+
}
|
|
52371
|
+
if (status.schemaVersion !== STATUS_SCHEMA_VERSION || typeof status.version !== "string" || !SEMVER_PATTERN.test(status.version) || !["npm", "windows-installer", "macos-dmg"].includes(String(status.installation)) || !["prepared", "waiting-for-exit", "installing", "restarting", "succeeded", "failed"].includes(String(status.phase)) || !Number.isSafeInteger(status.updatedAt) || status.error !== void 0 && typeof status.error !== "string") {
|
|
52372
|
+
throw new Error("background update status is invalid");
|
|
52373
|
+
}
|
|
52374
|
+
return status;
|
|
52375
|
+
}
|
|
52376
|
+
|
|
52377
|
+
// packages/update-manager/dist/distribution.js
|
|
52378
|
+
var UPDATE_RUNTIME_ENV = Object.freeze({
|
|
52379
|
+
launcherPid: "CODEXHOST_LAUNCHER_PID",
|
|
52380
|
+
launcherExecutable: "CODEXHOST_LAUNCHER_EXECUTABLE",
|
|
52381
|
+
controllerPort: "CODEXHOST_CONTROL_PORT",
|
|
52382
|
+
controllerNonce: "CODEXHOST_CONTROL_NONCE",
|
|
52383
|
+
npmNodePath: "CODEXHOST_NPM_NODE_PATH",
|
|
52384
|
+
npmCliPath: "CODEXHOST_NPM_CLI_PATH",
|
|
52385
|
+
npmLauncherPath: "CODEXHOST_NPM_LAUNCHER_PATH",
|
|
52386
|
+
npmPackageRoot: "CODEXHOST_NPM_PACKAGE_ROOT"
|
|
52387
|
+
});
|
|
52388
|
+
function object2(value, label) {
|
|
52389
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
52390
|
+
throw new Error(`${label} must be an object`);
|
|
52391
|
+
}
|
|
52392
|
+
return value;
|
|
52393
|
+
}
|
|
52394
|
+
function parseDistributionMetadata(value) {
|
|
52395
|
+
const metadata = object2(value, "distribution metadata");
|
|
52396
|
+
const allowed2 = ["distribution", "schemaVersion", "target", "version"];
|
|
52397
|
+
if (Object.keys(metadata).some((key) => !allowed2.includes(key))) {
|
|
52398
|
+
throw new Error("distribution metadata contains unknown fields");
|
|
52399
|
+
}
|
|
52400
|
+
if (metadata.schemaVersion !== 1 || metadata.distribution !== "npm" && metadata.distribution !== "installer" || !["macos-arm64", "macos-x64", "windows-x64", "windows-arm64"].includes(String(metadata.target)) || typeof metadata.version !== "string") {
|
|
52401
|
+
throw new Error("distribution metadata is invalid");
|
|
52402
|
+
}
|
|
52403
|
+
return {
|
|
52404
|
+
schemaVersion: 1,
|
|
52405
|
+
version: requireSemanticVersion(metadata.version),
|
|
52406
|
+
distribution: metadata.distribution,
|
|
52407
|
+
target: metadata.target
|
|
52408
|
+
};
|
|
52409
|
+
}
|
|
52410
|
+
function absoluteEnvironmentPath(environment, name) {
|
|
52411
|
+
const value = environment[name];
|
|
52412
|
+
if (!value || !path8.isAbsolute(value))
|
|
52413
|
+
throw new Error(`${name} must be an absolute path`);
|
|
52414
|
+
return path8.normalize(value);
|
|
52415
|
+
}
|
|
52416
|
+
function positiveEnvironmentInteger(environment, name) {
|
|
52417
|
+
const value = Number(environment[name]);
|
|
52418
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
52419
|
+
throw new Error(`${name} must be positive`);
|
|
52420
|
+
return value;
|
|
52421
|
+
}
|
|
52422
|
+
function expectedTarget(platform, architecture) {
|
|
52423
|
+
if (platform === "darwin" && architecture === "arm64")
|
|
52424
|
+
return "macos-arm64";
|
|
52425
|
+
if (platform === "darwin" && architecture === "x64")
|
|
52426
|
+
return "macos-x64";
|
|
52427
|
+
if (platform === "win32" && architecture === "arm64")
|
|
52428
|
+
return "windows-arm64";
|
|
52429
|
+
if (platform === "win32" && architecture === "x64")
|
|
52430
|
+
return "windows-x64";
|
|
52431
|
+
throw new Error(`unsupported update host ${platform}/${architecture}`);
|
|
52432
|
+
}
|
|
52433
|
+
function defaultUpdateStateDirectory(platform = process.platform, environment = process.env) {
|
|
52434
|
+
if (platform === "win32") {
|
|
52435
|
+
const root = environment.LOCALAPPDATA;
|
|
52436
|
+
if (!root || !path8.isAbsolute(root))
|
|
52437
|
+
throw new Error("LOCALAPPDATA is unavailable");
|
|
52438
|
+
return path8.join(root, "codexhost", "updates");
|
|
52439
|
+
}
|
|
52440
|
+
const home = environment.HOME;
|
|
52441
|
+
if (!home || !path8.isAbsolute(home))
|
|
52442
|
+
throw new Error("HOME is unavailable");
|
|
52443
|
+
return platform === "darwin" ? path8.join(home, "Library", "Application Support", "codexhost", "updates") : path8.join(home, ".codexhost", "updates");
|
|
52444
|
+
}
|
|
52445
|
+
async function resolveInstalledUpdateContext(options) {
|
|
52446
|
+
const environment = options.environment ?? process.env;
|
|
52447
|
+
const platform = options.platform ?? process.platform;
|
|
52448
|
+
const architecture = options.architecture ?? process.arch;
|
|
52449
|
+
if (!path8.isAbsolute(options.hostRuntimePath)) {
|
|
52450
|
+
throw new Error("Host Runtime path must be absolute");
|
|
52451
|
+
}
|
|
52452
|
+
const hostRuntimePath = path8.normalize(options.hostRuntimePath);
|
|
52453
|
+
const runtimeMetadata = await lstat(hostRuntimePath);
|
|
52454
|
+
if (!runtimeMetadata.isFile() || runtimeMetadata.isSymbolicLink()) {
|
|
52455
|
+
throw new Error("Host Runtime must be a regular file");
|
|
52456
|
+
}
|
|
52457
|
+
const appDirectory = path8.dirname(hostRuntimePath);
|
|
52458
|
+
const metadata = parseDistributionMetadata(JSON.parse(await readFile2(path8.join(appDirectory, "codexhost-distribution.json"), "utf8")));
|
|
52459
|
+
const target = expectedTarget(platform, architecture);
|
|
52460
|
+
if (metadata.target !== target) {
|
|
52461
|
+
throw new Error(`installed target ${metadata.target} does not match ${target}`);
|
|
52462
|
+
}
|
|
52463
|
+
const launcherPid = positiveEnvironmentInteger(environment, UPDATE_RUNTIME_ENV.launcherPid);
|
|
52464
|
+
const launcherExecutable = absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.launcherExecutable);
|
|
52465
|
+
const stateDirectory = path8.normalize(options.stateDirectory ?? defaultUpdateStateDirectory(platform, environment));
|
|
52466
|
+
const resourcesRoot = path8.dirname(appDirectory);
|
|
52467
|
+
const installationRoot = platform === "darwin" ? path8.dirname(path8.dirname(resourcesRoot)) : resourcesRoot;
|
|
52468
|
+
const updaterExecutable = path8.join(resourcesRoot, "libexec", platform === "win32" ? "codexhost-updater.exe" : "codexhost-updater");
|
|
52469
|
+
const common = {
|
|
52470
|
+
version: metadata.version,
|
|
52471
|
+
launcherPid,
|
|
52472
|
+
launcherExecutable,
|
|
52473
|
+
updaterExecutable,
|
|
52474
|
+
stateDirectory
|
|
52475
|
+
};
|
|
52476
|
+
const controller = {
|
|
52477
|
+
port: positiveEnvironmentInteger(environment, UPDATE_RUNTIME_ENV.controllerPort),
|
|
52478
|
+
nonce: environment[UPDATE_RUNTIME_ENV.controllerNonce] ?? ""
|
|
52479
|
+
};
|
|
52480
|
+
if (!/^[0-9a-f]{32}$/u.test(controller.nonce)) {
|
|
52481
|
+
throw new Error(`${UPDATE_RUNTIME_ENV.controllerNonce} must be a lowercase nonce`);
|
|
52482
|
+
}
|
|
52483
|
+
if (metadata.distribution === "npm") {
|
|
52484
|
+
const npmOptions = {
|
|
52485
|
+
...common,
|
|
52486
|
+
nodePath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmNodePath),
|
|
52487
|
+
npmCliPath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmCliPath),
|
|
52488
|
+
npmLauncherPath: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmLauncherPath),
|
|
52489
|
+
packageRoot: absoluteEnvironmentPath(environment, UPDATE_RUNTIME_ENV.npmPackageRoot)
|
|
52490
|
+
};
|
|
52491
|
+
if (path8.normalize(npmOptions.packageRoot) !== resourcesRoot) {
|
|
52492
|
+
throw new Error("npm platform package root does not own the Host Runtime");
|
|
52493
|
+
}
|
|
52494
|
+
return { metadata, common, controller, installation: { kind: "npm", options: npmOptions } };
|
|
52495
|
+
}
|
|
52496
|
+
if (platform === "win32") {
|
|
52497
|
+
return {
|
|
52498
|
+
metadata,
|
|
52499
|
+
common,
|
|
52500
|
+
controller,
|
|
52501
|
+
installation: {
|
|
52502
|
+
kind: "windows-installer",
|
|
52503
|
+
options: { ...common, installRoot: installationRoot }
|
|
52504
|
+
}
|
|
52505
|
+
};
|
|
52506
|
+
}
|
|
52507
|
+
if (platform === "darwin") {
|
|
52508
|
+
return {
|
|
52509
|
+
metadata,
|
|
52510
|
+
common,
|
|
52511
|
+
controller,
|
|
52512
|
+
installation: {
|
|
52513
|
+
kind: "macos-dmg",
|
|
52514
|
+
options: { ...common, appPath: installationRoot }
|
|
52515
|
+
}
|
|
52516
|
+
};
|
|
52517
|
+
}
|
|
52518
|
+
throw new Error("installer updates require Windows or macOS");
|
|
52519
|
+
}
|
|
52520
|
+
|
|
52521
|
+
// packages/update-manager/dist/github-release.js
|
|
52522
|
+
var CODEXHOST_LATEST_RELEASE_URL = "https://api.github.com/repos/BytePioneer-AI/codex-host/releases/latest";
|
|
52523
|
+
var SHA256_DIGEST_PATTERN = /^sha256:([0-9a-f]{64})$/u;
|
|
52524
|
+
var RELEASE_NOTES_URL_PATTERN = /^https:\/\/github\.com\/BytePioneer-AI\/codex-host\/releases\/tag\/(v[0-9A-Za-z.+-]+)$/u;
|
|
52525
|
+
var DOWNLOAD_URL_PREFIX = "https://github.com/BytePioneer-AI/codex-host/releases/download/";
|
|
52526
|
+
function record2(value, label) {
|
|
52527
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
52528
|
+
throw new Error(`${label} must be an object`);
|
|
52529
|
+
}
|
|
52530
|
+
return value;
|
|
52531
|
+
}
|
|
52532
|
+
function releaseAsset(value) {
|
|
52533
|
+
const asset = record2(value, "GitHub Release asset");
|
|
52534
|
+
if (typeof asset.name !== "string" || asset.name.length === 0 || asset.name.length > 160 || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 2 * 1024 * 1024 * 1024 || asset.digest != null && typeof asset.digest !== "string" || typeof asset.browser_download_url !== "string" || !asset.browser_download_url.startsWith(DOWNLOAD_URL_PREFIX)) {
|
|
52535
|
+
throw new Error("GitHub Release asset is invalid");
|
|
52536
|
+
}
|
|
52537
|
+
const url2 = new URL(asset.browser_download_url);
|
|
52538
|
+
if (url2.username || url2.password || url2.search || url2.hash) {
|
|
52539
|
+
throw new Error("GitHub Release asset URL is invalid");
|
|
52540
|
+
}
|
|
52541
|
+
return {
|
|
52542
|
+
name: asset.name,
|
|
52543
|
+
size: asset.size,
|
|
52544
|
+
digest: typeof asset.digest === "string" ? asset.digest : null,
|
|
52545
|
+
downloadUrl: asset.browser_download_url
|
|
52546
|
+
};
|
|
52547
|
+
}
|
|
52548
|
+
function parseLatestGitHubRelease(value) {
|
|
52549
|
+
const release = record2(value, "GitHub latest Release");
|
|
52550
|
+
if (release.draft !== false || release.prerelease !== false || typeof release.tag_name !== "string" || !release.tag_name.startsWith("v") || typeof release.html_url !== "string" || release.body != null && typeof release.body !== "string" || !Array.isArray(release.assets)) {
|
|
52551
|
+
throw new Error("GitHub latest Release is invalid");
|
|
52552
|
+
}
|
|
52553
|
+
const version2 = requireSemanticVersion(release.tag_name.slice(1));
|
|
52554
|
+
const notesMatch = RELEASE_NOTES_URL_PATTERN.exec(release.html_url);
|
|
52555
|
+
if (!notesMatch || notesMatch[1] !== release.tag_name) {
|
|
52556
|
+
throw new Error("GitHub Release notes URL does not match its tag");
|
|
52557
|
+
}
|
|
52558
|
+
const releaseNotes = typeof release.body === "string" && release.body.trim().length > 0 ? release.body.slice(0, 2e4) : null;
|
|
52559
|
+
return Object.freeze({
|
|
52560
|
+
version: version2,
|
|
52561
|
+
releaseNotes,
|
|
52562
|
+
releaseNotesUrl: release.html_url,
|
|
52563
|
+
assets: Object.freeze(release.assets.map(releaseAsset))
|
|
52564
|
+
});
|
|
52565
|
+
}
|
|
52566
|
+
async function fetchLatestGitHubRelease(options = {}) {
|
|
52567
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
52568
|
+
const response = await fetchImpl(CODEXHOST_LATEST_RELEASE_URL, {
|
|
52569
|
+
headers: {
|
|
52570
|
+
accept: "application/vnd.github+json",
|
|
52571
|
+
"user-agent": "codexhost-updater",
|
|
52572
|
+
"x-github-api-version": "2022-11-28"
|
|
52573
|
+
},
|
|
52574
|
+
redirect: "error",
|
|
52575
|
+
...options.signal ? { signal: options.signal } : {}
|
|
52576
|
+
});
|
|
52577
|
+
if (!response.ok)
|
|
52578
|
+
throw new Error(`GitHub latest Release request failed with HTTP ${response.status}`);
|
|
52579
|
+
return parseLatestGitHubRelease(await response.json());
|
|
52580
|
+
}
|
|
52581
|
+
function numericIdentifiers(value) {
|
|
52582
|
+
const core = value.split(/[+-]/u, 1)[0] ?? "";
|
|
52583
|
+
const parts = core.split(".").map(Number);
|
|
52584
|
+
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
52585
|
+
}
|
|
52586
|
+
function compareSemanticVersions(leftValue, rightValue) {
|
|
52587
|
+
const left = requireSemanticVersion(leftValue);
|
|
52588
|
+
const right = requireSemanticVersion(rightValue);
|
|
52589
|
+
const leftNumbers = numericIdentifiers(left);
|
|
52590
|
+
const rightNumbers = numericIdentifiers(right);
|
|
52591
|
+
for (let index = 0; index < 3; index += 1) {
|
|
52592
|
+
const difference = (leftNumbers[index] ?? 0) - (rightNumbers[index] ?? 0);
|
|
52593
|
+
if (difference !== 0)
|
|
52594
|
+
return difference < 0 ? -1 : 1;
|
|
52595
|
+
}
|
|
52596
|
+
const leftPrerelease = left.split("-", 2)[1]?.split("+")[0];
|
|
52597
|
+
const rightPrerelease = right.split("-", 2)[1]?.split("+")[0];
|
|
52598
|
+
if (leftPrerelease === rightPrerelease)
|
|
52599
|
+
return 0;
|
|
52600
|
+
if (leftPrerelease === void 0)
|
|
52601
|
+
return 1;
|
|
52602
|
+
if (rightPrerelease === void 0)
|
|
52603
|
+
return -1;
|
|
52604
|
+
const leftParts = leftPrerelease.split(".");
|
|
52605
|
+
const rightParts = rightPrerelease.split(".");
|
|
52606
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
52607
|
+
const leftPart = leftParts[index];
|
|
52608
|
+
const rightPart = rightParts[index];
|
|
52609
|
+
if (leftPart === rightPart)
|
|
52610
|
+
continue;
|
|
52611
|
+
if (leftPart === void 0)
|
|
52612
|
+
return -1;
|
|
52613
|
+
if (rightPart === void 0)
|
|
52614
|
+
return 1;
|
|
52615
|
+
const leftNumeric = /^\d+$/u.test(leftPart);
|
|
52616
|
+
const rightNumeric = /^\d+$/u.test(rightPart);
|
|
52617
|
+
if (leftNumeric && rightNumeric)
|
|
52618
|
+
return Number(leftPart) < Number(rightPart) ? -1 : 1;
|
|
52619
|
+
if (leftNumeric !== rightNumeric)
|
|
52620
|
+
return leftNumeric ? -1 : 1;
|
|
52621
|
+
return leftPart < rightPart ? -1 : 1;
|
|
52622
|
+
}
|
|
52623
|
+
return 0;
|
|
52624
|
+
}
|
|
52625
|
+
function expectedInstallerAssetName(version2, target) {
|
|
52626
|
+
requireSemanticVersion(version2);
|
|
52627
|
+
const extension = target.startsWith("macos-") ? "dmg" : "exe";
|
|
52628
|
+
return `codexhost-${version2}-${target}.${extension}`;
|
|
52629
|
+
}
|
|
52630
|
+
function selectInstallerReleaseArtifact(release, target) {
|
|
52631
|
+
const name = expectedInstallerAssetName(release.version, target);
|
|
52632
|
+
const matches = release.assets.filter((asset2) => asset2.name === name);
|
|
52633
|
+
if (matches.length !== 1)
|
|
52634
|
+
throw new Error(`GitHub Release must contain exactly one ${name}`);
|
|
52635
|
+
const asset = matches[0];
|
|
52636
|
+
if (!asset)
|
|
52637
|
+
throw new Error(`GitHub Release must contain exactly one ${name}`);
|
|
52638
|
+
const digest = asset.digest === null ? null : SHA256_DIGEST_PATTERN.exec(asset.digest);
|
|
52639
|
+
const sha256 = digest?.[1];
|
|
52640
|
+
if (!sha256)
|
|
52641
|
+
throw new Error(`GitHub Release asset ${name} has no valid SHA-256 digest`);
|
|
52642
|
+
const expectedPrefix = `${DOWNLOAD_URL_PREFIX}v${release.version}/`;
|
|
52643
|
+
if (!asset.downloadUrl.startsWith(expectedPrefix) || !asset.downloadUrl.endsWith(`/${name}`)) {
|
|
52644
|
+
throw new Error(`GitHub Release asset ${name} has an unexpected download URL`);
|
|
52645
|
+
}
|
|
52646
|
+
return Object.freeze({
|
|
52647
|
+
name,
|
|
52648
|
+
source: Object.freeze({ url: asset.downloadUrl, sha256, size: asset.size })
|
|
52649
|
+
});
|
|
52650
|
+
}
|
|
52651
|
+
|
|
52652
|
+
// packages/update-manager/dist/operation-state.js
|
|
52653
|
+
import { lstat as lstat2, mkdir as mkdir2, open as open3, readFile as readFile3, readdir as readdir2, rm as rm3, writeFile } from "node:fs/promises";
|
|
52654
|
+
import path9 from "node:path";
|
|
52655
|
+
var LOCK_FILE = "active-update-v1.lock";
|
|
52656
|
+
var STATUS_FILE = "status-v1.json";
|
|
52657
|
+
var TERMINAL_PHASES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
|
|
52658
|
+
async function regularFile(filePath) {
|
|
52659
|
+
try {
|
|
52660
|
+
const metadata = await lstat2(filePath);
|
|
52661
|
+
return metadata.isFile() && !metadata.isSymbolicLink();
|
|
52662
|
+
} catch (error52) {
|
|
52663
|
+
if (error52.code === "ENOENT")
|
|
52664
|
+
return false;
|
|
52665
|
+
throw error52;
|
|
52666
|
+
}
|
|
52667
|
+
}
|
|
52668
|
+
async function discoverLatestUpdateStatus(stateDirectory) {
|
|
52669
|
+
if (!path9.isAbsolute(stateDirectory))
|
|
52670
|
+
throw new Error("update state directory must be absolute");
|
|
52671
|
+
let entries;
|
|
52672
|
+
try {
|
|
52673
|
+
entries = await readdir2(stateDirectory, { withFileTypes: true });
|
|
52674
|
+
} catch (error52) {
|
|
52675
|
+
if (error52.code === "ENOENT")
|
|
52676
|
+
return null;
|
|
52677
|
+
throw error52;
|
|
52678
|
+
}
|
|
52679
|
+
const candidates2 = [];
|
|
52680
|
+
for (const entry of entries) {
|
|
52681
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
|
|
52682
|
+
continue;
|
|
52683
|
+
const statusPath = path9.join(stateDirectory, entry.name, STATUS_FILE);
|
|
52684
|
+
if (!await regularFile(statusPath))
|
|
52685
|
+
continue;
|
|
52686
|
+
try {
|
|
52687
|
+
candidates2.push({
|
|
52688
|
+
statusPath,
|
|
52689
|
+
status: parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")))
|
|
52690
|
+
});
|
|
52691
|
+
} catch {
|
|
52692
|
+
}
|
|
52693
|
+
}
|
|
52694
|
+
candidates2.sort((left, right) => right.status.updatedAt - left.status.updatedAt);
|
|
52695
|
+
return candidates2[0] ?? null;
|
|
52696
|
+
}
|
|
52697
|
+
async function cleanupTerminalUpdateState(stateDirectory, options = {}) {
|
|
52698
|
+
const now = Math.floor((options.now ?? Date.now()) / 1e3);
|
|
52699
|
+
const retentionSeconds = options.retentionSeconds ?? 7 * 24 * 60 * 60;
|
|
52700
|
+
let entries;
|
|
52701
|
+
try {
|
|
52702
|
+
entries = await readdir2(stateDirectory, { withFileTypes: true });
|
|
52703
|
+
} catch (error52) {
|
|
52704
|
+
if (error52.code === "ENOENT")
|
|
52705
|
+
return;
|
|
52706
|
+
throw error52;
|
|
52707
|
+
}
|
|
52708
|
+
for (const entry of entries) {
|
|
52709
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || !entry.name.startsWith("update-"))
|
|
52710
|
+
continue;
|
|
52711
|
+
const directory = path9.join(stateDirectory, entry.name);
|
|
52712
|
+
const statusPath = path9.join(directory, STATUS_FILE);
|
|
52713
|
+
try {
|
|
52714
|
+
const status = parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")));
|
|
52715
|
+
if (TERMINAL_PHASES.has(status.phase) && now - status.updatedAt > retentionSeconds) {
|
|
52716
|
+
await rm3(directory, { recursive: true, force: true });
|
|
52717
|
+
}
|
|
52718
|
+
} catch {
|
|
52719
|
+
}
|
|
52720
|
+
}
|
|
52721
|
+
}
|
|
52722
|
+
async function acquireUpdateOperationLock(stateDirectory) {
|
|
52723
|
+
if (!path9.isAbsolute(stateDirectory))
|
|
52724
|
+
throw new Error("update state directory must be absolute");
|
|
52725
|
+
await mkdir2(stateDirectory, { recursive: true, mode: 448 });
|
|
52726
|
+
const lockPath = path9.join(stateDirectory, LOCK_FILE);
|
|
52727
|
+
let handle;
|
|
52728
|
+
try {
|
|
52729
|
+
handle = await open3(lockPath, "wx", 384);
|
|
52730
|
+
} catch (error52) {
|
|
52731
|
+
if (error52.code === "EEXIST")
|
|
52732
|
+
return null;
|
|
52733
|
+
throw error52;
|
|
52734
|
+
}
|
|
52735
|
+
await handle.writeFile(`${JSON.stringify({ ownerPid: process.pid, statusPath: null })}
|
|
52736
|
+
`, "utf8");
|
|
52737
|
+
await handle.sync();
|
|
52738
|
+
await handle.close();
|
|
52739
|
+
let released = false;
|
|
52740
|
+
return {
|
|
52741
|
+
path: lockPath,
|
|
52742
|
+
async setStatusPath(statusPath) {
|
|
52743
|
+
if (released)
|
|
52744
|
+
throw new Error("update operation lock is released");
|
|
52745
|
+
if (!path9.isAbsolute(statusPath))
|
|
52746
|
+
throw new Error("update status path must be absolute");
|
|
52747
|
+
await writeFile(lockPath, `${JSON.stringify({ ownerPid: process.pid, statusPath: path9.normalize(statusPath) })}
|
|
52748
|
+
`, { encoding: "utf8", mode: 384 });
|
|
52749
|
+
},
|
|
52750
|
+
async release() {
|
|
52751
|
+
if (released)
|
|
52752
|
+
return;
|
|
52753
|
+
released = true;
|
|
52754
|
+
await rm3(lockPath, { force: true });
|
|
52755
|
+
}
|
|
52756
|
+
};
|
|
52757
|
+
}
|
|
52758
|
+
async function recoverUpdateOperationLock(stateDirectory) {
|
|
52759
|
+
const lockPath = path9.join(stateDirectory, LOCK_FILE);
|
|
52760
|
+
if (!await regularFile(lockPath))
|
|
52761
|
+
return;
|
|
52762
|
+
let statusPath;
|
|
52763
|
+
try {
|
|
52764
|
+
const value = JSON.parse(await readFile3(lockPath, "utf8"));
|
|
52765
|
+
statusPath = value.statusPath;
|
|
52766
|
+
} catch {
|
|
52767
|
+
return;
|
|
52768
|
+
}
|
|
52769
|
+
if (typeof statusPath !== "string" || !path9.isAbsolute(statusPath))
|
|
52770
|
+
return;
|
|
52771
|
+
try {
|
|
52772
|
+
const status = parseUpdateStatus(JSON.parse(await readFile3(statusPath, "utf8")));
|
|
52773
|
+
if (TERMINAL_PHASES.has(status.phase))
|
|
52774
|
+
await rm3(lockPath, { force: true });
|
|
52775
|
+
} catch {
|
|
52776
|
+
}
|
|
52777
|
+
}
|
|
52778
|
+
|
|
52779
|
+
// packages/update-manager/dist/update-manager.js
|
|
52780
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
52781
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
52782
|
+
import { chmod, copyFile as copyFile2, lstat as lstat4, mkdir as mkdir3, readFile as readFile4, rename as rename2, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
|
|
52783
|
+
import path10 from "node:path";
|
|
52784
|
+
|
|
52785
|
+
// packages/update-manager/dist/artifact.js
|
|
52786
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
52787
|
+
import { createReadStream } from "node:fs";
|
|
52788
|
+
import { lstat as lstat3, open as open4 } from "node:fs/promises";
|
|
52789
|
+
var MAX_ARTIFACT_BYTES = 2 * 1024 * 1024 * 1024;
|
|
52790
|
+
var SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
52791
|
+
function validateArtifact(source) {
|
|
52792
|
+
const url2 = new URL(source.url);
|
|
52793
|
+
if (url2.protocol !== "https:" || url2.username || url2.password) {
|
|
52794
|
+
throw new Error("update artifact URL must use HTTPS without credentials");
|
|
52795
|
+
}
|
|
52796
|
+
if (!SHA256_PATTERN.test(source.sha256)) {
|
|
52797
|
+
throw new Error("update artifact SHA-256 must be lowercase hexadecimal");
|
|
52798
|
+
}
|
|
52799
|
+
if (source.size !== void 0 && (!Number.isSafeInteger(source.size) || source.size <= 0 || source.size > MAX_ARTIFACT_BYTES)) {
|
|
52800
|
+
throw new Error(`update artifact size must be between 1 and ${MAX_ARTIFACT_BYTES} bytes`);
|
|
52801
|
+
}
|
|
52802
|
+
return source;
|
|
52803
|
+
}
|
|
52804
|
+
async function writeChunk(file2, chunk) {
|
|
52805
|
+
let offset = 0;
|
|
52806
|
+
while (offset < chunk.byteLength) {
|
|
52807
|
+
const result = await file2.write(chunk, offset, chunk.byteLength - offset, null);
|
|
52808
|
+
if (result.bytesWritten === 0)
|
|
52809
|
+
throw new Error("update artifact write made no progress");
|
|
52810
|
+
offset += result.bytesWritten;
|
|
52811
|
+
}
|
|
52812
|
+
}
|
|
52813
|
+
async function downloadArtifact(source, destination) {
|
|
52814
|
+
const response = await fetch(source.url, {
|
|
52815
|
+
redirect: "follow",
|
|
52816
|
+
headers: { "accept-encoding": "identity" }
|
|
52817
|
+
});
|
|
52818
|
+
if (!response.ok || response.body === null) {
|
|
52819
|
+
throw new Error(`update artifact download failed with HTTP ${response.status}`);
|
|
52820
|
+
}
|
|
52821
|
+
const finalUrl = new URL(response.url);
|
|
52822
|
+
if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
|
|
52823
|
+
throw new Error("update artifact redirected to a non-HTTPS URL");
|
|
52824
|
+
}
|
|
52825
|
+
const file2 = await open4(destination, "wx", 384);
|
|
52826
|
+
let bytes = 0;
|
|
52827
|
+
try {
|
|
52828
|
+
const reader = response.body.getReader();
|
|
52829
|
+
while (true) {
|
|
52830
|
+
const item = await reader.read();
|
|
52831
|
+
if (item.done)
|
|
52832
|
+
break;
|
|
52833
|
+
bytes += item.value.byteLength;
|
|
52834
|
+
if (bytes > MAX_ARTIFACT_BYTES) {
|
|
52835
|
+
await reader.cancel();
|
|
52836
|
+
throw new Error(`update artifact exceeds ${MAX_ARTIFACT_BYTES} bytes`);
|
|
52837
|
+
}
|
|
52838
|
+
await writeChunk(file2, item.value);
|
|
52839
|
+
}
|
|
52840
|
+
await file2.sync();
|
|
52841
|
+
} finally {
|
|
52842
|
+
await file2.close();
|
|
52843
|
+
}
|
|
52844
|
+
return { bytes, finalUrl: finalUrl.toString() };
|
|
52845
|
+
}
|
|
52846
|
+
async function sha256File(filePath) {
|
|
52847
|
+
const hash2 = createHash2("sha256");
|
|
52848
|
+
await new Promise((resolve2, reject) => {
|
|
52849
|
+
const stream = createReadStream(filePath);
|
|
52850
|
+
stream.on("data", (chunk) => hash2.update(chunk));
|
|
52851
|
+
stream.once("error", reject);
|
|
52852
|
+
stream.once("end", resolve2);
|
|
52853
|
+
});
|
|
52854
|
+
return hash2.digest("hex");
|
|
52855
|
+
}
|
|
52856
|
+
async function verifyDownloadedArtifact(source, filePath, result) {
|
|
52857
|
+
const metadata = await lstat3(filePath);
|
|
52858
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0) {
|
|
52859
|
+
throw new Error("downloaded update artifact is not a non-empty regular file");
|
|
52860
|
+
}
|
|
52861
|
+
if (metadata.size !== result.bytes) {
|
|
52862
|
+
throw new Error("downloaded update artifact size changed after download");
|
|
52863
|
+
}
|
|
52864
|
+
if (source.size !== void 0 && metadata.size !== source.size) {
|
|
52865
|
+
throw new Error(`update artifact size mismatch: expected ${source.size}, got ${metadata.size}`);
|
|
52866
|
+
}
|
|
52867
|
+
const actual = await sha256File(filePath);
|
|
52868
|
+
if (actual !== source.sha256) {
|
|
52869
|
+
throw new Error(`update artifact SHA-256 mismatch: expected ${source.sha256}, got ${actual}`);
|
|
52870
|
+
}
|
|
52871
|
+
}
|
|
52872
|
+
|
|
52873
|
+
// packages/update-manager/dist/update-manager.js
|
|
52874
|
+
var REQUEST_SCHEMA_VERSION = 1;
|
|
52875
|
+
function errorMessage4(error52) {
|
|
52876
|
+
return error52 instanceof Error ? error52.message : String(error52);
|
|
52877
|
+
}
|
|
52878
|
+
function requireAbsolutePath(value, label) {
|
|
52879
|
+
if (!path10.isAbsolute(value))
|
|
52880
|
+
throw new Error(`${label} must be an absolute path`);
|
|
52881
|
+
return path10.normalize(value);
|
|
52882
|
+
}
|
|
52883
|
+
async function requireRegularFile(value, label) {
|
|
52884
|
+
const filePath = requireAbsolutePath(value, label);
|
|
52885
|
+
const metadata = await lstat4(filePath).catch((error52) => {
|
|
52886
|
+
throw new Error(`${label} is unavailable: ${errorMessage4(error52)}`);
|
|
52887
|
+
});
|
|
52888
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
52889
|
+
throw new Error(`${label} must be a regular file`);
|
|
52890
|
+
}
|
|
52891
|
+
return filePath;
|
|
52892
|
+
}
|
|
52893
|
+
function requireLauncherPid(value) {
|
|
52894
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
52895
|
+
throw new Error("Launcher PID must be a positive integer");
|
|
52896
|
+
}
|
|
52897
|
+
return value;
|
|
52898
|
+
}
|
|
52899
|
+
async function writePrivateJson(filePath, value) {
|
|
52900
|
+
await writeFile2(filePath, `${JSON.stringify(value)}
|
|
52901
|
+
`, {
|
|
52902
|
+
encoding: "utf8",
|
|
52903
|
+
mode: 384,
|
|
52904
|
+
flag: "wx"
|
|
52905
|
+
});
|
|
52906
|
+
}
|
|
52907
|
+
function defaultSpawnUpdater(executable, requestPath) {
|
|
52908
|
+
const child = spawn4(executable, ["apply", "--request", requestPath], {
|
|
52909
|
+
detached: true,
|
|
52910
|
+
stdio: "ignore",
|
|
52911
|
+
windowsHide: true
|
|
52912
|
+
});
|
|
52913
|
+
child.unref();
|
|
52914
|
+
return child;
|
|
52915
|
+
}
|
|
52916
|
+
function createBackgroundUpdateManager(dependencies = {}) {
|
|
52917
|
+
const platform = dependencies.platform ?? process.platform;
|
|
52918
|
+
const randomId = dependencies.randomId ?? randomUUID8;
|
|
52919
|
+
const download = dependencies.download ?? downloadArtifact;
|
|
52920
|
+
const spawnUpdater = dependencies.spawnUpdater ?? defaultSpawnUpdater;
|
|
52921
|
+
const now = dependencies.now ?? Date.now;
|
|
52922
|
+
const preparedRequests = /* @__PURE__ */ new Set();
|
|
52923
|
+
async function prepareCommon(options, installation) {
|
|
52924
|
+
const version2 = requireSemanticVersion(options.version);
|
|
52925
|
+
const launcherPid = requireLauncherPid(options.launcherPid);
|
|
52926
|
+
const launcherExecutable = await requireRegularFile(options.launcherExecutable, "Launcher executable");
|
|
52927
|
+
const updaterExecutable = await requireRegularFile(options.updaterExecutable, "Updater executable");
|
|
52928
|
+
const stateDirectory = requireAbsolutePath(options.stateDirectory, "update state directory");
|
|
52929
|
+
await mkdir3(stateDirectory, { recursive: true, mode: 448 });
|
|
52930
|
+
const workDirectory = path10.join(stateDirectory, `update-${version2}-${randomId()}`);
|
|
52931
|
+
await mkdir3(workDirectory, { recursive: false, mode: 448 });
|
|
52932
|
+
const executableSuffix = platform === "win32" ? ".exe" : "";
|
|
52933
|
+
const helperPath = path10.join(workDirectory, `codexhost-updater${executableSuffix}`);
|
|
52934
|
+
await copyFile2(updaterExecutable, helperPath);
|
|
52935
|
+
if (platform !== "win32")
|
|
52936
|
+
await chmod(helperPath, 448);
|
|
52937
|
+
const requestPath = path10.join(workDirectory, "request-v1.json");
|
|
52938
|
+
const statusPath = path10.join(workDirectory, "status-v1.json");
|
|
52939
|
+
await writePrivateJson(statusPath, preparedStatus(version2, installation, now()));
|
|
52940
|
+
return {
|
|
52941
|
+
version: version2,
|
|
52942
|
+
launcherPid,
|
|
52943
|
+
launcherExecutable,
|
|
52944
|
+
workDirectory,
|
|
52945
|
+
helperPath,
|
|
52946
|
+
requestPath,
|
|
52947
|
+
statusPath
|
|
52948
|
+
};
|
|
52949
|
+
}
|
|
52950
|
+
async function prepareArtifact(sourceValue, workDirectory, fileName) {
|
|
52951
|
+
const source = validateArtifact(sourceValue);
|
|
52952
|
+
const temporaryPath = path10.join(workDirectory, `.${fileName}.download`);
|
|
52953
|
+
const artifactPath = path10.join(workDirectory, fileName);
|
|
52954
|
+
try {
|
|
52955
|
+
const result = await download(source, temporaryPath);
|
|
52956
|
+
const finalUrl = new URL(result.finalUrl);
|
|
52957
|
+
if (finalUrl.protocol !== "https:" || finalUrl.username || finalUrl.password) {
|
|
52958
|
+
throw new Error("update artifact downloader returned a non-HTTPS URL");
|
|
52959
|
+
}
|
|
52960
|
+
await verifyDownloadedArtifact(source, temporaryPath, result);
|
|
52961
|
+
await rename2(temporaryPath, artifactPath);
|
|
52962
|
+
return { source, artifactPath };
|
|
52963
|
+
} catch (error52) {
|
|
52964
|
+
await rm4(temporaryPath, { force: true });
|
|
52965
|
+
throw error52;
|
|
52966
|
+
}
|
|
52967
|
+
}
|
|
52968
|
+
async function finalize2(common, installation, artifactPath) {
|
|
52969
|
+
const request = {
|
|
52970
|
+
schema_version: REQUEST_SCHEMA_VERSION,
|
|
52971
|
+
version: common.version,
|
|
52972
|
+
wait_pid: common.launcherPid,
|
|
52973
|
+
wait_executable: common.launcherExecutable,
|
|
52974
|
+
status_path: common.statusPath,
|
|
52975
|
+
installation
|
|
52976
|
+
};
|
|
52977
|
+
await writePrivateJson(common.requestPath, request);
|
|
52978
|
+
preparedRequests.add(common.requestPath);
|
|
52979
|
+
return Object.freeze({
|
|
52980
|
+
version: common.version,
|
|
52981
|
+
installation: installation.kind,
|
|
52982
|
+
requestPath: common.requestPath,
|
|
52983
|
+
statusPath: common.statusPath,
|
|
52984
|
+
helperPath: common.helperPath,
|
|
52985
|
+
...artifactPath === void 0 ? {} : { artifactPath }
|
|
52986
|
+
});
|
|
52987
|
+
}
|
|
52988
|
+
return Object.freeze({
|
|
52989
|
+
async prepareNpm(options) {
|
|
52990
|
+
const common = await prepareCommon(options, "npm");
|
|
52991
|
+
return finalize2(common, {
|
|
52992
|
+
kind: "npm",
|
|
52993
|
+
node_path: await requireRegularFile(options.nodePath, "npm Node.js executable"),
|
|
52994
|
+
npm_cli_path: await requireRegularFile(options.npmCliPath, "npm CLI"),
|
|
52995
|
+
npm_launcher_path: await requireRegularFile(options.npmLauncherPath, "npm codexhost launcher"),
|
|
52996
|
+
package_root: requireAbsolutePath(options.packageRoot, "npm platform package root")
|
|
52997
|
+
});
|
|
52998
|
+
},
|
|
52999
|
+
async prepareWindowsInstaller(options) {
|
|
53000
|
+
if (platform !== "win32")
|
|
53001
|
+
throw new Error("Windows installer updates require Windows");
|
|
53002
|
+
const common = await prepareCommon(options, "windows-installer");
|
|
53003
|
+
const artifact = await prepareArtifact(options.artifact, common.workDirectory, "update.exe");
|
|
53004
|
+
return finalize2(common, {
|
|
53005
|
+
kind: "windows-installer",
|
|
53006
|
+
installer_path: artifact.artifactPath,
|
|
53007
|
+
artifact_sha256: artifact.source.sha256,
|
|
53008
|
+
install_root: requireAbsolutePath(options.installRoot, "Windows install root")
|
|
53009
|
+
}, artifact.artifactPath);
|
|
53010
|
+
},
|
|
53011
|
+
async prepareMacOsDmg(options) {
|
|
53012
|
+
if (platform !== "darwin")
|
|
53013
|
+
throw new Error("macOS DMG updates require macOS");
|
|
53014
|
+
const common = await prepareCommon(options, "macos-dmg");
|
|
53015
|
+
const appPath = requireAbsolutePath(options.appPath, "macOS application path");
|
|
53016
|
+
if (path10.extname(appPath) !== ".app") {
|
|
53017
|
+
throw new Error("macOS application path must end in .app");
|
|
53018
|
+
}
|
|
53019
|
+
const artifact = await prepareArtifact(options.artifact, common.workDirectory, "update.dmg");
|
|
53020
|
+
return finalize2(common, {
|
|
53021
|
+
kind: "macos-dmg",
|
|
53022
|
+
dmg_path: artifact.artifactPath,
|
|
53023
|
+
artifact_sha256: artifact.source.sha256,
|
|
53024
|
+
app_path: appPath
|
|
53025
|
+
}, artifact.artifactPath);
|
|
53026
|
+
},
|
|
53027
|
+
start(prepared) {
|
|
53028
|
+
if (!preparedRequests.delete(prepared.requestPath)) {
|
|
53029
|
+
throw new Error("background update was not prepared by this manager or was already started");
|
|
53030
|
+
}
|
|
53031
|
+
const child = spawnUpdater(prepared.helperPath, prepared.requestPath);
|
|
53032
|
+
if (child.pid === void 0)
|
|
53033
|
+
throw new Error("background Updater did not report a process ID");
|
|
53034
|
+
return Object.freeze({ ...prepared, updaterPid: child.pid });
|
|
53035
|
+
},
|
|
53036
|
+
async readStatus(statusPathValue) {
|
|
53037
|
+
const statusPath = requireAbsolutePath(statusPathValue, "update status path");
|
|
53038
|
+
try {
|
|
53039
|
+
return parseUpdateStatus(JSON.parse(await readFile4(statusPath, "utf8")));
|
|
53040
|
+
} catch (error52) {
|
|
53041
|
+
if (error52.code === "ENOENT")
|
|
53042
|
+
return null;
|
|
53043
|
+
throw error52;
|
|
53044
|
+
}
|
|
53045
|
+
}
|
|
53046
|
+
});
|
|
53047
|
+
}
|
|
53048
|
+
|
|
53049
|
+
// packages/host-runtime/src/update-coordinator.ts
|
|
53050
|
+
var ERROR_MAX_LENGTH = 500;
|
|
53051
|
+
var CONTROLLER_TIMEOUT_MS = 5e3;
|
|
53052
|
+
async function startCompatibilityUpdate(coordinator) {
|
|
53053
|
+
try {
|
|
53054
|
+
const check2 = await coordinator.check();
|
|
53055
|
+
if (check2.updateAvailable && check2.installationAvailable) {
|
|
53056
|
+
void coordinator.start().catch(() => void 0);
|
|
53057
|
+
return "update-started";
|
|
53058
|
+
}
|
|
53059
|
+
if (check2.error === null && check2.latestVersion !== null && check2.currentVersion === check2.latestVersion) {
|
|
53060
|
+
return "current";
|
|
53061
|
+
}
|
|
53062
|
+
} catch {
|
|
53063
|
+
}
|
|
53064
|
+
return "unavailable";
|
|
53065
|
+
}
|
|
53066
|
+
function boundedError(error52) {
|
|
53067
|
+
const message3 = error52 instanceof Error ? error52.message : String(error52);
|
|
53068
|
+
return message3.slice(0, ERROR_MAX_LENGTH) || "Update operation failed";
|
|
53069
|
+
}
|
|
53070
|
+
function publicStatus(status) {
|
|
53071
|
+
return {
|
|
53072
|
+
version: status.version,
|
|
53073
|
+
installation: status.installation,
|
|
53074
|
+
phase: status.phase,
|
|
53075
|
+
updatedAt: status.updatedAt,
|
|
53076
|
+
error: status.error?.slice(0, ERROR_MAX_LENGTH) ?? null
|
|
53077
|
+
};
|
|
53078
|
+
}
|
|
53079
|
+
function requestControllerShutdown(controller) {
|
|
53080
|
+
return new Promise((resolve2, reject) => {
|
|
53081
|
+
const socket = createConnection({ host: "127.0.0.1", port: controller.port });
|
|
53082
|
+
let response = "";
|
|
53083
|
+
const timeout = setTimeout(
|
|
53084
|
+
() => socket.destroy(new Error("Controller shutdown timed out")),
|
|
53085
|
+
CONTROLLER_TIMEOUT_MS
|
|
53086
|
+
);
|
|
53087
|
+
const settle = (operation) => {
|
|
53088
|
+
clearTimeout(timeout);
|
|
53089
|
+
operation();
|
|
53090
|
+
};
|
|
53091
|
+
socket.setEncoding("utf8");
|
|
53092
|
+
socket.once("error", (error52) => settle(() => reject(error52)));
|
|
53093
|
+
socket.on("data", (chunk) => {
|
|
53094
|
+
response += chunk;
|
|
53095
|
+
});
|
|
53096
|
+
socket.once(
|
|
53097
|
+
"end",
|
|
53098
|
+
() => settle(() => {
|
|
53099
|
+
if (response === "ready\n") resolve2();
|
|
53100
|
+
else reject(new Error("Desktop Controller rejected the shutdown request"));
|
|
53101
|
+
})
|
|
53102
|
+
);
|
|
53103
|
+
socket.once("connect", () => socket.write(`SHUTDOWN ${controller.nonce}
|
|
53104
|
+
`));
|
|
53105
|
+
});
|
|
53106
|
+
}
|
|
53107
|
+
function createHostUpdateCoordinator(options) {
|
|
53108
|
+
const manager = options.manager ?? createBackgroundUpdateManager(options.platform ? { platform: options.platform } : {});
|
|
53109
|
+
const contextPromise = resolveInstalledUpdateContext({
|
|
53110
|
+
hostRuntimePath: options.hostRuntimePath,
|
|
53111
|
+
...options.environment ? { environment: options.environment } : {},
|
|
53112
|
+
...options.platform ? { platform: options.platform } : {},
|
|
53113
|
+
...options.architecture ? { architecture: options.architecture } : {}
|
|
53114
|
+
});
|
|
53115
|
+
const fetchLatest = options.fetchLatest ?? ((signal) => fetchLatestGitHubRelease({ signal: signal ?? AbortSignal.timeout(15e3) }));
|
|
53116
|
+
const shutdown = options.shutdown ?? requestControllerShutdown;
|
|
53117
|
+
let candidate = null;
|
|
53118
|
+
let shutdownPending = null;
|
|
53119
|
+
async function latestStatus(context) {
|
|
53120
|
+
const discovered = await discoverLatestUpdateStatus(context.common.stateDirectory);
|
|
53121
|
+
return discovered ? publicStatus(discovered.status) : null;
|
|
53122
|
+
}
|
|
53123
|
+
async function installable(context, release) {
|
|
53124
|
+
if (context.metadata.distribution === "npm") return true;
|
|
53125
|
+
try {
|
|
53126
|
+
selectInstallerReleaseArtifact(release, context.metadata.target);
|
|
53127
|
+
return true;
|
|
53128
|
+
} catch {
|
|
53129
|
+
return false;
|
|
53130
|
+
}
|
|
53131
|
+
}
|
|
53132
|
+
return Object.freeze({
|
|
53133
|
+
async check(signal) {
|
|
53134
|
+
let context;
|
|
53135
|
+
try {
|
|
53136
|
+
context = await contextPromise;
|
|
53137
|
+
await recoverUpdateOperationLock(context.common.stateDirectory);
|
|
53138
|
+
await cleanupTerminalUpdateState(context.common.stateDirectory);
|
|
53139
|
+
} catch (error52) {
|
|
53140
|
+
return {
|
|
53141
|
+
currentVersion: "0.0.0",
|
|
53142
|
+
latestVersion: null,
|
|
53143
|
+
updateAvailable: false,
|
|
53144
|
+
installationAvailable: false,
|
|
53145
|
+
releaseNotes: null,
|
|
53146
|
+
releaseNotesUrl: null,
|
|
53147
|
+
status: null,
|
|
53148
|
+
error: boundedError(error52)
|
|
53149
|
+
};
|
|
53150
|
+
}
|
|
53151
|
+
const status = await latestStatus(context);
|
|
53152
|
+
try {
|
|
53153
|
+
const release = await fetchLatest(signal);
|
|
53154
|
+
candidate = release;
|
|
53155
|
+
const updateAvailable = compareSemanticVersions(context.metadata.version, release.version) < 0;
|
|
53156
|
+
let installationAvailable = false;
|
|
53157
|
+
let error52 = null;
|
|
53158
|
+
if (updateAvailable) {
|
|
53159
|
+
installationAvailable = await installable(context, release);
|
|
53160
|
+
if (!installationAvailable) {
|
|
53161
|
+
error52 = "The latest GitHub Release has no verified asset for this installation";
|
|
53162
|
+
}
|
|
53163
|
+
}
|
|
53164
|
+
return {
|
|
53165
|
+
currentVersion: context.metadata.version,
|
|
53166
|
+
latestVersion: release.version,
|
|
53167
|
+
updateAvailable,
|
|
53168
|
+
installationAvailable,
|
|
53169
|
+
releaseNotes: release.releaseNotes,
|
|
53170
|
+
releaseNotesUrl: release.releaseNotesUrl,
|
|
53171
|
+
status,
|
|
53172
|
+
error: error52
|
|
53173
|
+
};
|
|
53174
|
+
} catch (error52) {
|
|
53175
|
+
return {
|
|
53176
|
+
currentVersion: context.metadata.version,
|
|
53177
|
+
latestVersion: null,
|
|
53178
|
+
updateAvailable: false,
|
|
53179
|
+
installationAvailable: false,
|
|
53180
|
+
releaseNotes: null,
|
|
53181
|
+
releaseNotesUrl: null,
|
|
53182
|
+
status,
|
|
53183
|
+
error: boundedError(error52)
|
|
53184
|
+
};
|
|
53185
|
+
}
|
|
53186
|
+
},
|
|
53187
|
+
async start() {
|
|
53188
|
+
const context = await contextPromise;
|
|
53189
|
+
await recoverUpdateOperationLock(context.common.stateDirectory);
|
|
53190
|
+
const lock = await acquireUpdateOperationLock(context.common.stateDirectory);
|
|
53191
|
+
if (!lock) {
|
|
53192
|
+
const existing = await latestStatus(context);
|
|
53193
|
+
if (existing) return { status: existing };
|
|
53194
|
+
throw new Error("Another update operation is already active");
|
|
53195
|
+
}
|
|
53196
|
+
try {
|
|
53197
|
+
const release = await fetchLatest();
|
|
53198
|
+
if (compareSemanticVersions(context.metadata.version, release.version) >= 0 || candidate && candidate.version !== release.version) {
|
|
53199
|
+
throw new Error("The selected update is no longer the current GitHub Release");
|
|
53200
|
+
}
|
|
53201
|
+
let prepared;
|
|
53202
|
+
if (context.installation.kind === "npm") {
|
|
53203
|
+
prepared = await manager.prepareNpm({
|
|
53204
|
+
...context.installation.options,
|
|
53205
|
+
version: release.version
|
|
53206
|
+
});
|
|
53207
|
+
} else {
|
|
53208
|
+
const artifact = selectInstallerReleaseArtifact(release, context.metadata.target).source;
|
|
53209
|
+
prepared = context.installation.kind === "windows-installer" ? await manager.prepareWindowsInstaller({
|
|
53210
|
+
...context.installation.options,
|
|
53211
|
+
version: release.version,
|
|
53212
|
+
artifact
|
|
53213
|
+
}) : await manager.prepareMacOsDmg({
|
|
53214
|
+
...context.installation.options,
|
|
53215
|
+
version: release.version,
|
|
53216
|
+
artifact
|
|
53217
|
+
});
|
|
53218
|
+
}
|
|
53219
|
+
await lock.setStatusPath(prepared.statusPath);
|
|
53220
|
+
manager.start(prepared);
|
|
53221
|
+
const status = await manager.readStatus(prepared.statusPath);
|
|
53222
|
+
if (!status) throw new Error("Background Updater did not create status");
|
|
53223
|
+
shutdownPending = context.controller;
|
|
53224
|
+
return { status: publicStatus(status) };
|
|
53225
|
+
} catch (error52) {
|
|
53226
|
+
await lock.release();
|
|
53227
|
+
throw error52;
|
|
53228
|
+
}
|
|
53229
|
+
},
|
|
53230
|
+
async status() {
|
|
53231
|
+
try {
|
|
53232
|
+
const context = await contextPromise;
|
|
53233
|
+
await recoverUpdateOperationLock(context.common.stateDirectory);
|
|
53234
|
+
return { status: await latestStatus(context) };
|
|
53235
|
+
} catch {
|
|
53236
|
+
return { status: null };
|
|
53237
|
+
}
|
|
53238
|
+
},
|
|
53239
|
+
requestShutdown() {
|
|
53240
|
+
const controller = shutdownPending;
|
|
53241
|
+
shutdownPending = null;
|
|
53242
|
+
if (!controller) return;
|
|
53243
|
+
setTimeout(() => void shutdown(controller).catch(() => void 0), 50).unref();
|
|
53244
|
+
}
|
|
53245
|
+
});
|
|
53246
|
+
}
|
|
53247
|
+
|
|
51958
53248
|
// packages/host-runtime/src/release-main.ts
|
|
51959
53249
|
var STOCK_CODEX_PATH_ENV = "CODEXHOST_STOCK_CODEX_PATH";
|
|
51960
53250
|
var DEFAULT_AGENT_ENV = "CODEXHOST_DEFAULT_AGENT";
|
|
51961
|
-
var
|
|
51962
|
-
|
|
51963
|
-
var
|
|
51964
|
-
|
|
51965
|
-
|
|
51966
|
-
}
|
|
51967
|
-
|
|
51968
|
-
|
|
51969
|
-
|
|
51970
|
-
|
|
51971
|
-
|
|
51972
|
-
|
|
51973
|
-
|
|
51974
|
-
|
|
51975
|
-
|
|
51976
|
-
|
|
53251
|
+
var COMPATIBILITY_UPDATE_ARGUMENT = "--codexhost-compatibility-update";
|
|
53252
|
+
var arguments_ = process.argv.slice(2);
|
|
53253
|
+
var updateCoordinator = createHostUpdateCoordinator({
|
|
53254
|
+
hostRuntimePath: fileURLToPath(import.meta.url),
|
|
53255
|
+
environment: process.env
|
|
53256
|
+
});
|
|
53257
|
+
if (arguments_.length === 1 && arguments_[0] === COMPATIBILITY_UPDATE_ARGUMENT) {
|
|
53258
|
+
process.stdout.write(`${await startCompatibilityUpdate(updateCoordinator)}
|
|
53259
|
+
`);
|
|
53260
|
+
} else {
|
|
53261
|
+
const stockCodexPath = process.env[STOCK_CODEX_PATH_ENV];
|
|
53262
|
+
if (!stockCodexPath) throw new Error(`${STOCK_CODEX_PATH_ENV} is required`);
|
|
53263
|
+
const defaultAgent = process.env[DEFAULT_AGENT_ENV];
|
|
53264
|
+
if (defaultAgent !== "codex" && defaultAgent !== "pi") {
|
|
53265
|
+
throw new Error(`${DEFAULT_AGENT_ENV} must be 'codex' or 'pi'`);
|
|
53266
|
+
}
|
|
53267
|
+
const externalAdapters = createExternalHarnessAdapters(process.env);
|
|
53268
|
+
const host = new AppServerHost({
|
|
53269
|
+
stockCodexPath,
|
|
53270
|
+
arguments: arguments_,
|
|
53271
|
+
defaultAgent,
|
|
53272
|
+
environment: process.env,
|
|
53273
|
+
externalAdapters,
|
|
53274
|
+
updateCoordinator
|
|
53275
|
+
});
|
|
53276
|
+
void prefetchClaudeCodeModelCatalog(externalAdapters);
|
|
53277
|
+
process.exitCode = await host.run();
|
|
53278
|
+
}
|