remote-codex 0.1.7 → 0.1.9
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/apps/supervisor-api/dist/index.js +1122 -893
- package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-0cYcfOfd.js → highlighted-body-OFNGDK62-BFD4Ytvg.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-Rd2EBQac.js +377 -0
- package/apps/supervisor-web/dist/assets/{xterm-DisVWgDR.js → xterm-CukFWbxr.js} +1 -1
- package/apps/supervisor-web/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/agent-runtime/src/types.ts +8 -5
- package/packages/codex/src/historyItems.ts +115 -27
- package/packages/codex/src/runtimeAdapter.ts +60 -9
- package/packages/db/src/repositories.ts +6 -1
- package/packages/shared/src/index.ts +4 -2
- package/apps/supervisor-web/dist/assets/index-nH6a8Wwn.js +0 -377
|
@@ -5,15 +5,15 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import
|
|
8
|
+
import fs16 from "fs";
|
|
9
9
|
|
|
10
10
|
// src/app.ts
|
|
11
11
|
import Fastify from "fastify";
|
|
12
12
|
import multipart from "@fastify/multipart";
|
|
13
13
|
import websocket from "@fastify/websocket";
|
|
14
14
|
import { spawn as spawn3 } from "child_process";
|
|
15
|
-
import
|
|
16
|
-
import
|
|
15
|
+
import fs15 from "fs";
|
|
16
|
+
import path15 from "path";
|
|
17
17
|
import { ZodError } from "zod";
|
|
18
18
|
|
|
19
19
|
// ../../packages/config/src/index.ts
|
|
@@ -32,7 +32,10 @@ var envSchema = z.object({
|
|
|
32
32
|
DATABASE_URL: z.string().optional(),
|
|
33
33
|
CODEX_HOME: z.string().optional(),
|
|
34
34
|
CODEX_COMMAND: z.string().min(1).optional(),
|
|
35
|
-
CODEX_APP_SERVER_START_TIMEOUT_MS: z.coerce.number().int().positive().optional()
|
|
35
|
+
CODEX_APP_SERVER_START_TIMEOUT_MS: z.coerce.number().int().positive().optional(),
|
|
36
|
+
CLAUDE_HOME: z.string().optional(),
|
|
37
|
+
CLAUDE_COMMAND: z.string().min(1).optional(),
|
|
38
|
+
REMOTE_CODEX_ENABLED_AGENT_PROVIDERS: z.string().optional()
|
|
36
39
|
});
|
|
37
40
|
function resolveDatabaseUrl(nodeEnv, value) {
|
|
38
41
|
if (value && value.trim()) {
|
|
@@ -48,6 +51,11 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
48
51
|
const nodeEnv = parsed.NODE_ENV ?? "development";
|
|
49
52
|
const workspaceRoot = parsed.WORKSPACE_ROOT?.trim() ? path.resolve(parsed.WORKSPACE_ROOT) : os.homedir();
|
|
50
53
|
const disableRequestLogging = parsed.DISABLE_REQUEST_LOGGING === void 0 ? nodeEnv === "production" : ["1", "true", "yes", "on"].includes(parsed.DISABLE_REQUEST_LOGGING.toLowerCase());
|
|
54
|
+
const enabledProviders = new Set(
|
|
55
|
+
(parsed.REMOTE_CODEX_ENABLED_AGENT_PROVIDERS ?? "codex").split(",").map((provider) => provider.trim().toLowerCase()).filter(Boolean)
|
|
56
|
+
);
|
|
57
|
+
const codexHome = parsed.CODEX_HOME?.trim() ? path.resolve(parsed.CODEX_HOME) : path.join(os.homedir(), ".codex");
|
|
58
|
+
const claudeHome = parsed.CLAUDE_HOME?.trim() ? path.resolve(parsed.CLAUDE_HOME) : path.join(os.homedir(), ".claude");
|
|
51
59
|
return {
|
|
52
60
|
nodeEnv,
|
|
53
61
|
host: parsed.HOST ?? "127.0.0.1",
|
|
@@ -58,9 +66,21 @@ function loadRuntimeConfig(env = process.env) {
|
|
|
58
66
|
appVersion: parsed.APP_VERSION ?? "0.1.0",
|
|
59
67
|
workspaceRoot,
|
|
60
68
|
databaseUrl: resolveDatabaseUrl(nodeEnv, parsed.DATABASE_URL),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
agentProviders: {
|
|
70
|
+
codex: {
|
|
71
|
+
provider: "codex",
|
|
72
|
+
enabled: enabledProviders.has("codex"),
|
|
73
|
+
home: codexHome,
|
|
74
|
+
command: parsed.CODEX_COMMAND ?? "codex",
|
|
75
|
+
appServerStartTimeoutMs: parsed.CODEX_APP_SERVER_START_TIMEOUT_MS ?? 1e4
|
|
76
|
+
},
|
|
77
|
+
claude: {
|
|
78
|
+
provider: "claude",
|
|
79
|
+
enabled: enabledProviders.has("claude"),
|
|
80
|
+
home: claudeHome,
|
|
81
|
+
command: parsed.CLAUDE_COMMAND ?? "claude"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
64
84
|
};
|
|
65
85
|
}
|
|
66
86
|
|
|
@@ -1281,7 +1301,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
1281
1301
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
1282
1302
|
const nullifyMap = {};
|
|
1283
1303
|
const result = columns.reduce(
|
|
1284
|
-
(result2, { path:
|
|
1304
|
+
(result2, { path: path16, field }, columnIndex) => {
|
|
1285
1305
|
let decoder;
|
|
1286
1306
|
if (is(field, Column)) {
|
|
1287
1307
|
decoder = field;
|
|
@@ -1291,8 +1311,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
1291
1311
|
decoder = field.sql.decoder;
|
|
1292
1312
|
}
|
|
1293
1313
|
let node = result2;
|
|
1294
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
1295
|
-
if (pathChunkIndex <
|
|
1314
|
+
for (const [pathChunkIndex, pathChunk] of path16.entries()) {
|
|
1315
|
+
if (pathChunkIndex < path16.length - 1) {
|
|
1296
1316
|
if (!(pathChunk in node)) {
|
|
1297
1317
|
node[pathChunk] = {};
|
|
1298
1318
|
}
|
|
@@ -1300,8 +1320,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
1300
1320
|
} else {
|
|
1301
1321
|
const rawValue = row[columnIndex];
|
|
1302
1322
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
1303
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
1304
|
-
const objectName =
|
|
1323
|
+
if (joinsNotNullableMap && is(field, Column) && path16.length === 2) {
|
|
1324
|
+
const objectName = path16[0];
|
|
1305
1325
|
if (!(objectName in nullifyMap)) {
|
|
1306
1326
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
1307
1327
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -5879,6 +5899,9 @@ function getActiveThreadGoalRecord(db, threadId) {
|
|
|
5879
5899
|
) ?? null;
|
|
5880
5900
|
}
|
|
5881
5901
|
function getThreadGoalRecordForUpsert(db, input) {
|
|
5902
|
+
if (input.createNew) {
|
|
5903
|
+
return null;
|
|
5904
|
+
}
|
|
5882
5905
|
if (input.localGoalId) {
|
|
5883
5906
|
const byId = db.select().from(threadGoals).where(eq(threadGoals.id, input.localGoalId)).get();
|
|
5884
5907
|
if (byId?.threadId === input.threadId) {
|
|
@@ -5886,7 +5909,7 @@ function getThreadGoalRecordForUpsert(db, input) {
|
|
|
5886
5909
|
}
|
|
5887
5910
|
}
|
|
5888
5911
|
const active = getActiveThreadGoalRecord(db, input.threadId);
|
|
5889
|
-
if (active) {
|
|
5912
|
+
if (active && active.objective === input.objective) {
|
|
5890
5913
|
return active;
|
|
5891
5914
|
}
|
|
5892
5915
|
const matchingObjective = db.select().from(threadGoals).where(
|
|
@@ -7527,7 +7550,7 @@ function extractFileChangeEntries(item) {
|
|
|
7527
7550
|
isRecord2(entry.summary) ? entry.summary : null,
|
|
7528
7551
|
isRecord2(entry.diff) ? entry.diff : null
|
|
7529
7552
|
].filter((candidate) => Boolean(candidate));
|
|
7530
|
-
const
|
|
7553
|
+
const path16 = uniqueStrings([
|
|
7531
7554
|
stringOrNull(valueFromRecords(nestedRecords, ["path", "filePath", "targetPath"])),
|
|
7532
7555
|
stringOrNull(
|
|
7533
7556
|
valueFromRecords(nestedRecords, [
|
|
@@ -7574,7 +7597,7 @@ function extractFileChangeEntries(item) {
|
|
|
7574
7597
|
const diffStats = explicitAdditions === 0 && explicitDeletions === 0 && diffText ? countUnifiedDiffStats(diffText) : null;
|
|
7575
7598
|
const additions = explicitAdditions || diffStats?.additions || 0;
|
|
7576
7599
|
const deletions = explicitDeletions || diffStats?.deletions || 0;
|
|
7577
|
-
const normalizedPath =
|
|
7600
|
+
const normalizedPath = path16 ?? (diffText ? extractPathFromDiffText(diffText) : null);
|
|
7578
7601
|
if (!normalizedPath && additions === 0 && deletions === 0) {
|
|
7579
7602
|
return null;
|
|
7580
7603
|
}
|
|
@@ -8121,7 +8144,7 @@ var codexCapabilities = {
|
|
|
8121
8144
|
planMode: true,
|
|
8122
8145
|
permissionRequests: true,
|
|
8123
8146
|
sandboxMode: true,
|
|
8124
|
-
|
|
8147
|
+
performanceMode: true,
|
|
8125
8148
|
goals: true
|
|
8126
8149
|
},
|
|
8127
8150
|
management: {
|
|
@@ -8200,6 +8223,43 @@ function mapGoal(goal) {
|
|
|
8200
8223
|
rawGoal: goal
|
|
8201
8224
|
};
|
|
8202
8225
|
}
|
|
8226
|
+
function serviceTierForPerformanceMode(performanceMode) {
|
|
8227
|
+
if (performanceMode === void 0) {
|
|
8228
|
+
return void 0;
|
|
8229
|
+
}
|
|
8230
|
+
return performanceMode === "fast" ? "fast" : null;
|
|
8231
|
+
}
|
|
8232
|
+
function buildSandboxPolicy(sandboxMode, workspacePath) {
|
|
8233
|
+
if (sandboxMode === void 0) {
|
|
8234
|
+
return void 0;
|
|
8235
|
+
}
|
|
8236
|
+
switch (sandboxMode) {
|
|
8237
|
+
case "danger-full-access":
|
|
8238
|
+
return {
|
|
8239
|
+
type: "dangerFullAccess"
|
|
8240
|
+
};
|
|
8241
|
+
case "read-only":
|
|
8242
|
+
return {
|
|
8243
|
+
type: "readOnly",
|
|
8244
|
+
access: {
|
|
8245
|
+
type: "fullAccess"
|
|
8246
|
+
},
|
|
8247
|
+
networkAccess: false
|
|
8248
|
+
};
|
|
8249
|
+
case "workspace-write":
|
|
8250
|
+
default:
|
|
8251
|
+
return {
|
|
8252
|
+
type: "workspaceWrite",
|
|
8253
|
+
writableRoots: workspacePath ? [workspacePath] : [],
|
|
8254
|
+
readOnlyAccess: {
|
|
8255
|
+
type: "fullAccess"
|
|
8256
|
+
},
|
|
8257
|
+
networkAccess: false,
|
|
8258
|
+
excludeTmpdirEnvVar: false,
|
|
8259
|
+
excludeSlashTmp: false
|
|
8260
|
+
};
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
8203
8263
|
function mapSession(thread) {
|
|
8204
8264
|
return {
|
|
8205
8265
|
provider: "codex",
|
|
@@ -8465,6 +8525,8 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
|
|
|
8465
8525
|
command: `node -e 'process.stdin.resume(); process.stdin.on("end", () => console.log(JSON.stringify({ systemMessage: "remote-codex hook ran" })))'`
|
|
8466
8526
|
}
|
|
8467
8527
|
],
|
|
8528
|
+
providerConfigFormat: "toml",
|
|
8529
|
+
mcpConfigFormat: "codex-toml",
|
|
8468
8530
|
configArchives: true,
|
|
8469
8531
|
buildRestart: true
|
|
8470
8532
|
};
|
|
@@ -8511,8 +8573,9 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
|
|
|
8511
8573
|
if (input.sandboxMode !== void 0) {
|
|
8512
8574
|
startInput.sandbox = input.sandboxMode;
|
|
8513
8575
|
}
|
|
8514
|
-
|
|
8515
|
-
|
|
8576
|
+
const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
|
|
8577
|
+
if (serviceTier !== void 0) {
|
|
8578
|
+
startInput.serviceTier = serviceTier;
|
|
8516
8579
|
}
|
|
8517
8580
|
const response = await codexRuntimeCall(() => this.manager.startThread(startInput));
|
|
8518
8581
|
return {
|
|
@@ -8535,8 +8598,9 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
|
|
|
8535
8598
|
if (input.sandboxMode !== void 0) {
|
|
8536
8599
|
resumeInput.sandbox = input.sandboxMode;
|
|
8537
8600
|
}
|
|
8538
|
-
|
|
8539
|
-
|
|
8601
|
+
const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
|
|
8602
|
+
if (serviceTier !== void 0) {
|
|
8603
|
+
resumeInput.serviceTier = serviceTier;
|
|
8540
8604
|
}
|
|
8541
8605
|
const response = await codexRuntimeCall(() => this.manager.resumeThread(resumeInput));
|
|
8542
8606
|
return {
|
|
@@ -8563,11 +8627,13 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
|
|
|
8563
8627
|
if (input.collaborationMode !== void 0) {
|
|
8564
8628
|
turnInput.collaborationMode = input.collaborationMode;
|
|
8565
8629
|
}
|
|
8566
|
-
|
|
8567
|
-
|
|
8630
|
+
const sandboxPolicy = buildSandboxPolicy(input.sandboxMode, input.workspacePath);
|
|
8631
|
+
if (sandboxPolicy !== void 0) {
|
|
8632
|
+
turnInput.sandboxPolicy = sandboxPolicy;
|
|
8568
8633
|
}
|
|
8569
|
-
|
|
8570
|
-
|
|
8634
|
+
const serviceTier = serviceTierForPerformanceMode(input.performanceMode);
|
|
8635
|
+
if (serviceTier !== void 0) {
|
|
8636
|
+
turnInput.serviceTier = serviceTier;
|
|
8571
8637
|
}
|
|
8572
8638
|
return mapTurn2(await codexRuntimeCall(() => this.manager.startTurn(turnInput)));
|
|
8573
8639
|
}
|
|
@@ -8640,174 +8706,713 @@ var CodexRuntimeAdapter = class extends EventEmitter3 {
|
|
|
8640
8706
|
}
|
|
8641
8707
|
};
|
|
8642
8708
|
|
|
8643
|
-
// src/codex/
|
|
8644
|
-
import
|
|
8645
|
-
import
|
|
8646
|
-
import Database2 from "better-sqlite3";
|
|
8709
|
+
// src/codex/codex-management-service.ts
|
|
8710
|
+
import fs5 from "fs/promises";
|
|
8711
|
+
import path6 from "path";
|
|
8647
8712
|
|
|
8648
|
-
// src/codex/
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8713
|
+
// src/codex/codexHostConfig.ts
|
|
8714
|
+
import fs4 from "fs";
|
|
8715
|
+
import fsp from "fs/promises";
|
|
8716
|
+
import path5 from "path";
|
|
8717
|
+
var SERVICE_TIER_LINE_GLOBAL_PATTERN = /^\s*service_tier\s*=\s*("fast"|"flex"|'fast'|'flex').*\n?/gm;
|
|
8718
|
+
function resolveCodexConfigPath(codexHome) {
|
|
8719
|
+
return path5.join(codexHome, "config.toml");
|
|
8652
8720
|
}
|
|
8653
|
-
function
|
|
8654
|
-
const
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
if (characters.length <= AUTO_THREAD_TITLE_MAX_CHARS) {
|
|
8660
|
-
return normalized;
|
|
8661
|
-
}
|
|
8662
|
-
return `${characters.slice(0, AUTO_THREAD_TITLE_MAX_CHARS).join("")}...`;
|
|
8721
|
+
function parseCodexServiceTier(content) {
|
|
8722
|
+
const match = content.match(
|
|
8723
|
+
/^\s*service_tier\s*=\s*["'](?<tier>fast)["']\s*$/m
|
|
8724
|
+
);
|
|
8725
|
+
const tier = match?.groups?.tier;
|
|
8726
|
+
return tier === "fast" ? tier : null;
|
|
8663
8727
|
}
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
function basenameFromPath(absPath) {
|
|
8667
|
-
const normalized = absPath.replace(/[\\/]+$/, "");
|
|
8668
|
-
return normalized.split(/[\\/]/).at(-1) ?? normalized;
|
|
8728
|
+
function isFastModeEnabledFromConfig(content) {
|
|
8729
|
+
return parseCodexServiceTier(content) === "fast";
|
|
8669
8730
|
}
|
|
8670
|
-
function
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
return
|
|
8731
|
+
function readCodexFastModeSync(codexHome) {
|
|
8732
|
+
try {
|
|
8733
|
+
const content = fs4.readFileSync(resolveCodexConfigPath(codexHome), "utf8");
|
|
8734
|
+
return isFastModeEnabledFromConfig(content);
|
|
8735
|
+
} catch (error) {
|
|
8736
|
+
if (error.code === "ENOENT") {
|
|
8737
|
+
return false;
|
|
8738
|
+
}
|
|
8739
|
+
throw error;
|
|
8674
8740
|
}
|
|
8675
|
-
return truncateAutoThreadTitle(firstUserMessage.text);
|
|
8676
|
-
}
|
|
8677
|
-
function createHistoryItemId(turnId, prefix, index) {
|
|
8678
|
-
return `${turnId}-${prefix}-${index}`;
|
|
8679
8741
|
}
|
|
8680
|
-
function
|
|
8681
|
-
|
|
8682
|
-
|
|
8742
|
+
function upsertCodexServiceTier(content, enabled) {
|
|
8743
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
8744
|
+
const withoutServiceTier = normalized.replace(
|
|
8745
|
+
SERVICE_TIER_LINE_GLOBAL_PATTERN,
|
|
8746
|
+
""
|
|
8747
|
+
);
|
|
8748
|
+
if (!enabled) {
|
|
8749
|
+
return withoutServiceTier.replace(/\n{3,}/g, "\n\n").trimEnd() + (withoutServiceTier.trim() ? "\n" : "");
|
|
8683
8750
|
}
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8751
|
+
const nextLine = 'service_tier = "fast"';
|
|
8752
|
+
if (!withoutServiceTier.trim()) {
|
|
8753
|
+
return `${nextLine}
|
|
8754
|
+
`;
|
|
8755
|
+
}
|
|
8756
|
+
const firstSectionMatch = withoutServiceTier.match(/^\s*\[[^\]]+\]/m);
|
|
8757
|
+
if (!firstSectionMatch || firstSectionMatch.index === void 0) {
|
|
8758
|
+
return withoutServiceTier.endsWith("\n") ? `${withoutServiceTier}${nextLine}
|
|
8759
|
+
` : `${withoutServiceTier}
|
|
8760
|
+
${nextLine}
|
|
8761
|
+
`;
|
|
8762
|
+
}
|
|
8763
|
+
const beforeFirstSection = withoutServiceTier.slice(0, firstSectionMatch.index).trimEnd();
|
|
8764
|
+
const afterFirstSection = withoutServiceTier.slice(firstSectionMatch.index).replace(/^\n+/, "");
|
|
8765
|
+
return beforeFirstSection ? `${beforeFirstSection}
|
|
8766
|
+
${nextLine}
|
|
8767
|
+
${afterFirstSection}` : `${nextLine}
|
|
8768
|
+
${afterFirstSection}`;
|
|
8691
8769
|
}
|
|
8692
|
-
function
|
|
8693
|
-
const
|
|
8694
|
-
let
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
if (currentTurn) {
|
|
8701
|
-
return currentTurn;
|
|
8702
|
-
}
|
|
8703
|
-
fallbackTurnCount += 1;
|
|
8704
|
-
currentTurn = {
|
|
8705
|
-
id: `local-turn-${fallbackTurnCount}`,
|
|
8706
|
-
startedAt: timestamp ?? null,
|
|
8707
|
-
status: "inProgress",
|
|
8708
|
-
error: null,
|
|
8709
|
-
items: []
|
|
8710
|
-
};
|
|
8711
|
-
agentItemCount = 0;
|
|
8712
|
-
userItemCount = 0;
|
|
8713
|
-
return currentTurn;
|
|
8714
|
-
};
|
|
8715
|
-
for (const line of contents.split("\n")) {
|
|
8716
|
-
if (!line.trim()) {
|
|
8717
|
-
continue;
|
|
8718
|
-
}
|
|
8719
|
-
let entry;
|
|
8720
|
-
try {
|
|
8721
|
-
entry = JSON.parse(line);
|
|
8722
|
-
} catch {
|
|
8723
|
-
continue;
|
|
8724
|
-
}
|
|
8725
|
-
if (entry.type === "session_meta") {
|
|
8726
|
-
const payload2 = entry.payload ?? {};
|
|
8727
|
-
if (typeof payload2.cwd === "string" && payload2.cwd.trim()) {
|
|
8728
|
-
cwd = payload2.cwd;
|
|
8729
|
-
}
|
|
8730
|
-
continue;
|
|
8731
|
-
}
|
|
8732
|
-
if (entry.type !== "event_msg") {
|
|
8733
|
-
continue;
|
|
8734
|
-
}
|
|
8735
|
-
const payload = entry.payload ?? {};
|
|
8736
|
-
const payloadType = payload.type;
|
|
8737
|
-
if (payloadType === "task_started") {
|
|
8738
|
-
finalizeTurn(currentTurn, turns);
|
|
8739
|
-
currentTurn = {
|
|
8740
|
-
id: typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id : `local-turn-${fallbackTurnCount + 1}`,
|
|
8741
|
-
startedAt: entry.timestamp ?? null,
|
|
8742
|
-
status: "inProgress",
|
|
8743
|
-
error: null,
|
|
8744
|
-
items: []
|
|
8745
|
-
};
|
|
8746
|
-
agentItemCount = 0;
|
|
8747
|
-
userItemCount = 0;
|
|
8748
|
-
continue;
|
|
8749
|
-
}
|
|
8750
|
-
if (payloadType === "user_message" && typeof payload.message === "string") {
|
|
8751
|
-
const turn = ensureCurrentTurn(entry.timestamp);
|
|
8752
|
-
userItemCount += 1;
|
|
8753
|
-
turn.items.push({
|
|
8754
|
-
id: createHistoryItemId(turn.id, "user", userItemCount),
|
|
8755
|
-
kind: "userMessage",
|
|
8756
|
-
text: payload.message
|
|
8757
|
-
});
|
|
8758
|
-
continue;
|
|
8759
|
-
}
|
|
8760
|
-
if (payloadType === "agent_message" && typeof payload.message === "string") {
|
|
8761
|
-
const turn = ensureCurrentTurn(entry.timestamp);
|
|
8762
|
-
agentItemCount += 1;
|
|
8763
|
-
turn.items.push({
|
|
8764
|
-
id: createHistoryItemId(turn.id, "agent", agentItemCount),
|
|
8765
|
-
kind: "agentMessage",
|
|
8766
|
-
text: payload.message,
|
|
8767
|
-
status: typeof payload.phase === "string" ? payload.phase : null
|
|
8768
|
-
});
|
|
8769
|
-
continue;
|
|
8770
|
-
}
|
|
8771
|
-
if (payloadType === "task_complete") {
|
|
8772
|
-
const turn = ensureCurrentTurn(entry.timestamp);
|
|
8773
|
-
turn.status = turn.error ? "failed" : "completed";
|
|
8774
|
-
finalizeTurn(turn, turns);
|
|
8775
|
-
currentTurn = null;
|
|
8776
|
-
continue;
|
|
8777
|
-
}
|
|
8778
|
-
if (payloadType === "error") {
|
|
8779
|
-
const turn = ensureCurrentTurn(entry.timestamp);
|
|
8780
|
-
turn.status = "failed";
|
|
8781
|
-
turn.error = typeof payload.message === "string" ? payload.message : "Local Codex session failed.";
|
|
8770
|
+
async function writeCodexFastMode(codexHome, enabled) {
|
|
8771
|
+
const configPath = resolveCodexConfigPath(codexHome);
|
|
8772
|
+
let current = "";
|
|
8773
|
+
try {
|
|
8774
|
+
current = await fsp.readFile(configPath, "utf8");
|
|
8775
|
+
} catch (error) {
|
|
8776
|
+
if (error.code !== "ENOENT") {
|
|
8777
|
+
throw error;
|
|
8782
8778
|
}
|
|
8783
8779
|
}
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
turns
|
|
8789
|
-
};
|
|
8780
|
+
const next = upsertCodexServiceTier(current, enabled);
|
|
8781
|
+
await fsp.mkdir(path5.dirname(configPath), { recursive: true });
|
|
8782
|
+
await fsp.writeFile(configPath, next, "utf8");
|
|
8783
|
+
return next;
|
|
8790
8784
|
}
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
} catch {
|
|
8785
|
+
function isCodexFeatureEnabledFromConfig(content, featureName) {
|
|
8786
|
+
const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8787
|
+
const featuresMatch = content.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
|
|
8788
|
+
if (!featuresMatch || featuresMatch.index === void 0) {
|
|
8796
8789
|
return false;
|
|
8797
8790
|
}
|
|
8791
|
+
const sectionStart = featuresMatch.index + featuresMatch[0].length;
|
|
8792
|
+
const nextSectionMatch = content.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
|
|
8793
|
+
const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : content.length;
|
|
8794
|
+
const featuresBody = content.slice(sectionStart, sectionEnd);
|
|
8795
|
+
return new RegExp(`^\\s*${escaped}\\s*=\\s*true\\s*(?:#.*)?$`, "m").test(featuresBody);
|
|
8798
8796
|
}
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8797
|
+
async function readCodexFeatureFlag(codexHome, featureName) {
|
|
8798
|
+
try {
|
|
8799
|
+
const content = await fsp.readFile(resolveCodexConfigPath(codexHome), "utf8");
|
|
8800
|
+
return isCodexFeatureEnabledFromConfig(content, featureName);
|
|
8801
|
+
} catch (error) {
|
|
8802
|
+
if (error.code === "ENOENT") {
|
|
8803
|
+
return false;
|
|
8804
|
+
}
|
|
8805
|
+
throw error;
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
function upsertCodexFeatureFlag(content, featureName, enabled) {
|
|
8809
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
8810
|
+
const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8811
|
+
const featuresMatch = normalized.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
|
|
8812
|
+
const nextLine = `${featureName} = ${enabled ? "true" : "false"}`;
|
|
8813
|
+
if (!featuresMatch || featuresMatch.index === void 0) {
|
|
8814
|
+
const prefix = normalized.trimEnd();
|
|
8815
|
+
return prefix ? `${prefix}
|
|
8816
|
+
|
|
8817
|
+
[features]
|
|
8818
|
+
${nextLine}
|
|
8819
|
+
` : `[features]
|
|
8820
|
+
${nextLine}
|
|
8821
|
+
`;
|
|
8822
|
+
}
|
|
8823
|
+
const sectionStart = featuresMatch.index + featuresMatch[0].length;
|
|
8824
|
+
const nextSectionMatch = normalized.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
|
|
8825
|
+
const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : normalized.length;
|
|
8826
|
+
const beforeSectionBody = normalized.slice(0, sectionStart);
|
|
8827
|
+
const sectionBody = normalized.slice(sectionStart, sectionEnd);
|
|
8828
|
+
const afterSection = normalized.slice(sectionEnd);
|
|
8829
|
+
const flagPattern = new RegExp(
|
|
8830
|
+
`(^[^\\S\\r\\n]*)${escaped}[^\\S\\r\\n]*=[^\\S\\r\\n]*(true|false)[^\\S\\r\\n]*(?:#.*)?$`,
|
|
8831
|
+
"m"
|
|
8832
|
+
);
|
|
8833
|
+
if (flagPattern.test(sectionBody)) {
|
|
8834
|
+
return beforeSectionBody + sectionBody.replace(flagPattern, `$1${nextLine}`) + afterSection;
|
|
8835
|
+
}
|
|
8836
|
+
const bodyWithFlag = `${sectionBody.replace(/\n*$/, "")}
|
|
8837
|
+
${nextLine}
|
|
8838
|
+
`;
|
|
8839
|
+
const normalizedAfterSection = afterSection.startsWith("\n") ? afterSection : `
|
|
8840
|
+
${afterSection}`;
|
|
8841
|
+
return beforeSectionBody + bodyWithFlag + normalizedAfterSection;
|
|
8842
|
+
}
|
|
8843
|
+
async function writeCodexFeatureFlag(codexHome, featureName, enabled) {
|
|
8844
|
+
const configPath = resolveCodexConfigPath(codexHome);
|
|
8845
|
+
let current = "";
|
|
8846
|
+
try {
|
|
8847
|
+
current = await fsp.readFile(configPath, "utf8");
|
|
8848
|
+
} catch (error) {
|
|
8849
|
+
if (error.code !== "ENOENT") {
|
|
8850
|
+
throw error;
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8853
|
+
const next = upsertCodexFeatureFlag(current, featureName, enabled);
|
|
8854
|
+
await fsp.mkdir(path5.dirname(configPath), { recursive: true });
|
|
8855
|
+
await fsp.writeFile(configPath, next, "utf8");
|
|
8856
|
+
return next;
|
|
8857
|
+
}
|
|
8858
|
+
|
|
8859
|
+
// src/codex/runtime-errors.ts
|
|
8860
|
+
function unwrapCodexJsonRpcError(error) {
|
|
8861
|
+
if (error instanceof JsonRpcClientError) {
|
|
8862
|
+
return error;
|
|
8863
|
+
}
|
|
8864
|
+
if (error instanceof AgentRuntimeError && error.provider === "codex") {
|
|
8865
|
+
return error.cause instanceof JsonRpcClientError ? error.cause : null;
|
|
8866
|
+
}
|
|
8867
|
+
return null;
|
|
8868
|
+
}
|
|
8869
|
+
function isCodexRemoteError(error) {
|
|
8870
|
+
const codexError = unwrapCodexJsonRpcError(error);
|
|
8871
|
+
return codexError?.code === "remote_error" ? codexError : null;
|
|
8872
|
+
}
|
|
8873
|
+
function isCodexRuntimeRequestError(error) {
|
|
8874
|
+
return Boolean(unwrapCodexJsonRpcError(error));
|
|
8875
|
+
}
|
|
8876
|
+
function isRemoteThreadBootstrapError(error) {
|
|
8877
|
+
const codexError = isCodexRemoteError(error);
|
|
8878
|
+
if (!codexError) {
|
|
8879
|
+
return false;
|
|
8880
|
+
}
|
|
8881
|
+
return codexError.message.includes("includeTurns is unavailable before first user message") || codexError.message.includes("is not materialized yet") || codexError.message.includes("no rollout found for thread id") || codexError.message.includes("failed to load rollout") || codexError.message.includes("rollout at") && codexError.message.includes("is empty");
|
|
8882
|
+
}
|
|
8883
|
+
function isUnsupportedHooksListError(error) {
|
|
8884
|
+
const codexError = isCodexRemoteError(error);
|
|
8885
|
+
if (!codexError) {
|
|
8886
|
+
return false;
|
|
8887
|
+
}
|
|
8888
|
+
const remoteCode = codexError.details?.code;
|
|
8889
|
+
const message = codexError.message.toLowerCase();
|
|
8890
|
+
return remoteCode === -32601 || message.includes("endpoint not found") || message.includes("method not found") || message.includes("hooks/list") && message.includes("not found");
|
|
8891
|
+
}
|
|
8892
|
+
function parseTurnSteerRace(error) {
|
|
8893
|
+
const codexError = isCodexRemoteError(error);
|
|
8894
|
+
if (!codexError) {
|
|
8895
|
+
return null;
|
|
8896
|
+
}
|
|
8897
|
+
if (codexError.message === "no active turn to steer") {
|
|
8898
|
+
return { type: "missing" };
|
|
8899
|
+
}
|
|
8900
|
+
const mismatchPrefix = "expected active turn id `";
|
|
8901
|
+
const mismatchSeparator = "` but found `";
|
|
8902
|
+
if (!codexError.message.startsWith(mismatchPrefix)) {
|
|
8903
|
+
return null;
|
|
8904
|
+
}
|
|
8905
|
+
const actualTurnId = codexError.message.slice(mismatchPrefix.length).split(mismatchSeparator)[1]?.replace(/`$/, "");
|
|
8906
|
+
if (!actualTurnId) {
|
|
8907
|
+
return null;
|
|
8908
|
+
}
|
|
8909
|
+
return {
|
|
8910
|
+
type: "turnIdMismatch",
|
|
8911
|
+
actualTurnId
|
|
8912
|
+
};
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8915
|
+
// src/codex/codex-management-service.ts
|
|
8916
|
+
var GOAL_FEATURE_DISABLED_MESSAGE = "Codex /goal is experimental. Enable it by adding `goals = true` under `[features]` in ~/.codex/config.toml, then restart the Codex app-server.";
|
|
8917
|
+
var HOOK_EVENT_JSON_KEYS = {
|
|
8918
|
+
preToolUse: "PreToolUse",
|
|
8919
|
+
permissionRequest: "PermissionRequest",
|
|
8920
|
+
postToolUse: "PostToolUse",
|
|
8921
|
+
preCompact: "PreCompact",
|
|
8922
|
+
postCompact: "PostCompact",
|
|
8923
|
+
sessionStart: "SessionStart",
|
|
8924
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
8925
|
+
stop: "Stop"
|
|
8926
|
+
};
|
|
8927
|
+
var HOOK_EVENT_DTO_KEYS = Object.fromEntries(
|
|
8928
|
+
Object.entries(HOOK_EVENT_JSON_KEYS).map(([dtoKey, jsonKey]) => [jsonKey, dtoKey])
|
|
8929
|
+
);
|
|
8930
|
+
function isRecord4(value) {
|
|
8931
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8932
|
+
}
|
|
8933
|
+
function normalizeHooksJson(value) {
|
|
8934
|
+
if (!isRecord4(value) || !isRecord4(value.hooks)) {
|
|
8935
|
+
return { hooks: {} };
|
|
8936
|
+
}
|
|
8937
|
+
const hooks = {};
|
|
8938
|
+
for (const [eventName, groups] of Object.entries(value.hooks)) {
|
|
8939
|
+
hooks[eventName] = Array.isArray(groups) ? groups : [];
|
|
8940
|
+
}
|
|
8941
|
+
return { ...value, hooks };
|
|
8942
|
+
}
|
|
8943
|
+
function readJsonFileOrDefault(filePath) {
|
|
8944
|
+
return fs5.readFile(filePath, "utf8").then((raw) => {
|
|
8945
|
+
if (!raw.trim()) {
|
|
8946
|
+
return { hooks: {} };
|
|
8947
|
+
}
|
|
8948
|
+
return normalizeHooksJson(JSON.parse(raw));
|
|
8949
|
+
}).catch((error) => {
|
|
8950
|
+
if (error.code === "ENOENT") {
|
|
8951
|
+
return { hooks: {} };
|
|
8952
|
+
}
|
|
8953
|
+
throw error;
|
|
8954
|
+
});
|
|
8955
|
+
}
|
|
8956
|
+
function validateHookInput(input) {
|
|
8957
|
+
if (!HOOK_EVENT_JSON_KEYS[input.eventName]) {
|
|
8958
|
+
throw new HttpError(400, {
|
|
8959
|
+
code: "bad_request",
|
|
8960
|
+
message: "Unsupported hook event."
|
|
8961
|
+
});
|
|
8962
|
+
}
|
|
8963
|
+
if (input.scope !== "global" && input.scope !== "project") {
|
|
8964
|
+
throw new HttpError(400, {
|
|
8965
|
+
code: "bad_request",
|
|
8966
|
+
message: "Hook scope must be global or project."
|
|
8967
|
+
});
|
|
8968
|
+
}
|
|
8969
|
+
if (!input.command.trim()) {
|
|
8970
|
+
throw new HttpError(400, {
|
|
8971
|
+
code: "bad_request",
|
|
8972
|
+
message: "Hook command cannot be empty."
|
|
8973
|
+
});
|
|
8974
|
+
}
|
|
8975
|
+
if (input.timeoutSec !== void 0 && input.timeoutSec !== null && (!Number.isInteger(input.timeoutSec) || input.timeoutSec <= 0 || input.timeoutSec > 86400)) {
|
|
8976
|
+
throw new HttpError(400, {
|
|
8977
|
+
code: "bad_request",
|
|
8978
|
+
message: "Hook timeout must be a positive number of seconds."
|
|
8979
|
+
});
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
function hooksPathForInput(codexHome, workspacePath, input) {
|
|
8983
|
+
return input.scope === "global" ? path6.join(codexHome, "hooks.json") : path6.join(workspacePath, ".codex", "hooks.json");
|
|
8984
|
+
}
|
|
8985
|
+
function hookInputMatches(group, handler, input) {
|
|
8986
|
+
if (!isRecord4(group) || !isRecord4(handler)) {
|
|
8987
|
+
return false;
|
|
8988
|
+
}
|
|
8989
|
+
const matcher = typeof group.matcher === "string" ? group.matcher : null;
|
|
8990
|
+
const handlerCommand = typeof handler.command === "string" ? handler.command : "";
|
|
8991
|
+
const handlerTimeout = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : null;
|
|
8992
|
+
const handlerStatusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
|
|
8993
|
+
return handler.type === "command" && (input.matcher?.trim() || null) === matcher && input.command.trim() === handlerCommand && (input.timeoutSec ?? null) === handlerTimeout && (input.statusMessage?.trim() || null) === handlerStatusMessage;
|
|
8994
|
+
}
|
|
8995
|
+
function hookMatchesInput(hook, input) {
|
|
8996
|
+
return hook.source === input.scope && hook.eventName === input.eventName && (hook.matcher ?? null) === (input.matcher ?? null) && hook.command === input.command && (input.timeoutSec == null || hook.timeoutSec === input.timeoutSec) && (hook.statusMessage ?? null) === (input.statusMessage ?? null);
|
|
8997
|
+
}
|
|
8998
|
+
async function findOfficialHookForInput(runtime, workspacePath, input) {
|
|
8999
|
+
if (!runtime.listHooks) {
|
|
9000
|
+
return null;
|
|
9001
|
+
}
|
|
9002
|
+
const [entry] = await runtime.listHooks({
|
|
9003
|
+
cwds: [workspacePath]
|
|
9004
|
+
});
|
|
9005
|
+
const officialHooks = (entry?.hooks ?? []).map((hook) => ({
|
|
9006
|
+
key: hook.key,
|
|
9007
|
+
eventName: hook.eventName,
|
|
9008
|
+
handlerType: hook.handlerType,
|
|
9009
|
+
matcher: hook.matcher,
|
|
9010
|
+
command: hook.command,
|
|
9011
|
+
timeoutSec: hook.timeoutSec,
|
|
9012
|
+
statusMessage: hook.statusMessage,
|
|
9013
|
+
sourcePath: hook.sourcePath,
|
|
9014
|
+
source: hook.source,
|
|
9015
|
+
pluginId: hook.pluginId,
|
|
9016
|
+
displayOrder: hook.displayOrder,
|
|
9017
|
+
enabled: hook.enabled,
|
|
9018
|
+
isManaged: hook.isManaged,
|
|
9019
|
+
currentHash: hook.currentHash,
|
|
9020
|
+
trustStatus: hook.trustStatus
|
|
9021
|
+
}));
|
|
9022
|
+
return officialHooks.find((hook) => hookMatchesInput(hook, input)) ?? null;
|
|
9023
|
+
}
|
|
9024
|
+
async function trustHookForInput(runtime, workspacePath, input) {
|
|
9025
|
+
const hook = await findOfficialHookForInput(runtime, workspacePath, input);
|
|
9026
|
+
if (!runtime.setHookTrust || !hook || !hook.key || !hook.currentHash || hook.isManaged) {
|
|
9027
|
+
return;
|
|
9028
|
+
}
|
|
9029
|
+
await runtime.setHookTrust({
|
|
9030
|
+
key: hook.key,
|
|
9031
|
+
trustedHash: hook.currentHash
|
|
9032
|
+
});
|
|
9033
|
+
}
|
|
9034
|
+
var CodexManagementService = class {
|
|
9035
|
+
constructor(codexHome) {
|
|
9036
|
+
this.codexHome = codexHome;
|
|
9037
|
+
}
|
|
9038
|
+
codexHome;
|
|
9039
|
+
readFastMode() {
|
|
9040
|
+
return readCodexFastModeSync(this.codexHome);
|
|
9041
|
+
}
|
|
9042
|
+
writeFastMode(enabled) {
|
|
9043
|
+
return writeCodexFastMode(this.codexHome, enabled);
|
|
9044
|
+
}
|
|
9045
|
+
mapGoalError(error) {
|
|
9046
|
+
const codexError = unwrapCodexJsonRpcError(error);
|
|
9047
|
+
if (codexError) {
|
|
9048
|
+
const remoteMessage = codexError.message || "";
|
|
9049
|
+
if (remoteMessage.toLowerCase().includes("goals feature is disabled")) {
|
|
9050
|
+
throw new HttpError(409, {
|
|
9051
|
+
code: "goal_feature_disabled",
|
|
9052
|
+
message: GOAL_FEATURE_DISABLED_MESSAGE
|
|
9053
|
+
});
|
|
9054
|
+
}
|
|
9055
|
+
throw new HttpError(502, {
|
|
9056
|
+
code: "provider_goal_error",
|
|
9057
|
+
message: remoteMessage || "Provider goal operation failed.",
|
|
9058
|
+
details: {
|
|
9059
|
+
provider: "codex"
|
|
9060
|
+
}
|
|
9061
|
+
});
|
|
9062
|
+
}
|
|
9063
|
+
throw error;
|
|
9064
|
+
}
|
|
9065
|
+
async ensureGoalsFeatureEnabled(runtime) {
|
|
9066
|
+
try {
|
|
9067
|
+
if (await readCodexFeatureFlag(this.codexHome, "goals")) {
|
|
9068
|
+
return;
|
|
9069
|
+
}
|
|
9070
|
+
await writeCodexFeatureFlag(this.codexHome, "goals", true);
|
|
9071
|
+
await runtime.stop();
|
|
9072
|
+
await runtime.start();
|
|
9073
|
+
} catch (error) {
|
|
9074
|
+
if (isCodexRuntimeRequestError(error)) {
|
|
9075
|
+
throw new HttpError(409, {
|
|
9076
|
+
code: "goal_feature_disabled",
|
|
9077
|
+
message: GOAL_FEATURE_DISABLED_MESSAGE
|
|
9078
|
+
});
|
|
9079
|
+
}
|
|
9080
|
+
throw error;
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
isRuntimeRequestError(error) {
|
|
9084
|
+
return isCodexRuntimeRequestError(error);
|
|
9085
|
+
}
|
|
9086
|
+
async writeHookEntry(runtime, workspacePath, input) {
|
|
9087
|
+
validateHookInput(input);
|
|
9088
|
+
const hooksPath = hooksPathForInput(this.codexHome, workspacePath, input);
|
|
9089
|
+
const config = await readJsonFileOrDefault(hooksPath);
|
|
9090
|
+
const eventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
|
|
9091
|
+
const matcher = input.matcher?.trim() || null;
|
|
9092
|
+
const handler = {
|
|
9093
|
+
type: "command",
|
|
9094
|
+
command: input.command.trim()
|
|
9095
|
+
};
|
|
9096
|
+
if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
|
|
9097
|
+
handler.timeout = input.timeoutSec;
|
|
9098
|
+
}
|
|
9099
|
+
if (input.statusMessage?.trim()) {
|
|
9100
|
+
handler.statusMessage = input.statusMessage.trim();
|
|
9101
|
+
}
|
|
9102
|
+
const group = {
|
|
9103
|
+
hooks: [handler]
|
|
9104
|
+
};
|
|
9105
|
+
if (matcher) {
|
|
9106
|
+
group.matcher = matcher;
|
|
9107
|
+
}
|
|
9108
|
+
const currentGroups = Array.isArray(config.hooks[eventKey]) ? config.hooks[eventKey] : [];
|
|
9109
|
+
config.hooks[eventKey] = [...currentGroups, group];
|
|
9110
|
+
await fs5.mkdir(path6.dirname(hooksPath), { recursive: true });
|
|
9111
|
+
await fs5.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
|
|
9112
|
+
`, "utf8");
|
|
9113
|
+
await trustHookForInput(runtime, workspacePath, input);
|
|
9114
|
+
}
|
|
9115
|
+
async updateHookEntry(runtime, workspacePath, input) {
|
|
9116
|
+
validateHookInput(input);
|
|
9117
|
+
validateHookInput(input.target);
|
|
9118
|
+
if (input.scope !== input.target.scope) {
|
|
9119
|
+
throw new HttpError(400, {
|
|
9120
|
+
code: "bad_request",
|
|
9121
|
+
message: "Hook scope cannot be changed while editing."
|
|
9122
|
+
});
|
|
9123
|
+
}
|
|
9124
|
+
const hooksPath = hooksPathForInput(this.codexHome, workspacePath, input);
|
|
9125
|
+
const config = await readJsonFileOrDefault(hooksPath);
|
|
9126
|
+
const targetEventKey = HOOK_EVENT_JSON_KEYS[input.target.eventName];
|
|
9127
|
+
const nextEventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
|
|
9128
|
+
const currentGroups = Array.isArray(config.hooks[targetEventKey]) ? config.hooks[targetEventKey] : [];
|
|
9129
|
+
let replacementGroup = null;
|
|
9130
|
+
config.hooks[targetEventKey] = currentGroups.map((group) => {
|
|
9131
|
+
if (replacementGroup || !isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
9132
|
+
return group;
|
|
9133
|
+
}
|
|
9134
|
+
const hookIndex = group.hooks.findIndex(
|
|
9135
|
+
(handler2) => hookInputMatches(group, handler2, input.target)
|
|
9136
|
+
);
|
|
9137
|
+
if (hookIndex < 0) {
|
|
9138
|
+
return group;
|
|
9139
|
+
}
|
|
9140
|
+
const handler = {
|
|
9141
|
+
type: "command",
|
|
9142
|
+
command: input.command.trim()
|
|
9143
|
+
};
|
|
9144
|
+
if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
|
|
9145
|
+
handler.timeout = input.timeoutSec;
|
|
9146
|
+
}
|
|
9147
|
+
if (input.statusMessage?.trim()) {
|
|
9148
|
+
handler.statusMessage = input.statusMessage.trim();
|
|
9149
|
+
}
|
|
9150
|
+
replacementGroup = {
|
|
9151
|
+
hooks: [handler]
|
|
9152
|
+
};
|
|
9153
|
+
const matcher = input.matcher?.trim() || null;
|
|
9154
|
+
if (matcher) {
|
|
9155
|
+
replacementGroup.matcher = matcher;
|
|
9156
|
+
}
|
|
9157
|
+
if (targetEventKey !== nextEventKey) {
|
|
9158
|
+
const remainingHooks = group.hooks.filter((_, index) => index !== hookIndex);
|
|
9159
|
+
return {
|
|
9160
|
+
...group,
|
|
9161
|
+
hooks: remainingHooks
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
return {
|
|
9165
|
+
...replacementGroup,
|
|
9166
|
+
hooks: group.hooks.map(
|
|
9167
|
+
(existing, index) => index === hookIndex ? replacementGroup.hooks[0] : existing
|
|
9168
|
+
)
|
|
9169
|
+
};
|
|
9170
|
+
}).filter((group) => {
|
|
9171
|
+
if (!isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
9172
|
+
return true;
|
|
9173
|
+
}
|
|
9174
|
+
return group.hooks.length > 0;
|
|
9175
|
+
});
|
|
9176
|
+
if (!replacementGroup) {
|
|
9177
|
+
throw new HttpError(404, {
|
|
9178
|
+
code: "not_found",
|
|
9179
|
+
message: "Hook was not found in hooks.json."
|
|
9180
|
+
});
|
|
9181
|
+
}
|
|
9182
|
+
if (targetEventKey !== nextEventKey) {
|
|
9183
|
+
if (config.hooks[targetEventKey]?.length === 0) {
|
|
9184
|
+
delete config.hooks[targetEventKey];
|
|
9185
|
+
}
|
|
9186
|
+
const nextGroups = Array.isArray(config.hooks[nextEventKey]) ? config.hooks[nextEventKey] : [];
|
|
9187
|
+
config.hooks[nextEventKey] = [...nextGroups, replacementGroup];
|
|
9188
|
+
}
|
|
9189
|
+
await fs5.mkdir(path6.dirname(hooksPath), { recursive: true });
|
|
9190
|
+
await fs5.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
|
|
9191
|
+
`, "utf8");
|
|
9192
|
+
await trustHookForInput(runtime, workspacePath, input);
|
|
9193
|
+
}
|
|
9194
|
+
async readLocalHookDtos(input) {
|
|
9195
|
+
const config = await readJsonFileOrDefault(input.hooksPath);
|
|
9196
|
+
const hooks = [];
|
|
9197
|
+
for (const [eventKey, groups] of Object.entries(config.hooks)) {
|
|
9198
|
+
const eventName = HOOK_EVENT_DTO_KEYS[eventKey];
|
|
9199
|
+
if (!eventName || !Array.isArray(groups)) {
|
|
9200
|
+
continue;
|
|
9201
|
+
}
|
|
9202
|
+
groups.forEach((group, groupIndex) => {
|
|
9203
|
+
if (!isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9206
|
+
const matcher = typeof group.matcher === "string" ? group.matcher : null;
|
|
9207
|
+
group.hooks.forEach((handler, handlerIndex) => {
|
|
9208
|
+
if (!isRecord4(handler) || handler.type !== "command") {
|
|
9209
|
+
return;
|
|
9210
|
+
}
|
|
9211
|
+
const command = typeof handler.command === "string" ? handler.command : null;
|
|
9212
|
+
if (!command) {
|
|
9213
|
+
return;
|
|
9214
|
+
}
|
|
9215
|
+
const timeoutSec = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : 600;
|
|
9216
|
+
const statusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
|
|
9217
|
+
const key = `${input.source}:${input.hooksPath}:${eventKey}:${groupIndex}:${handlerIndex}`;
|
|
9218
|
+
hooks.push({
|
|
9219
|
+
key,
|
|
9220
|
+
eventName,
|
|
9221
|
+
handlerType: "command",
|
|
9222
|
+
matcher,
|
|
9223
|
+
command,
|
|
9224
|
+
timeoutSec,
|
|
9225
|
+
statusMessage,
|
|
9226
|
+
sourcePath: input.hooksPath,
|
|
9227
|
+
source: input.source,
|
|
9228
|
+
pluginId: null,
|
|
9229
|
+
displayOrder: input.displayOffset + hooks.length,
|
|
9230
|
+
enabled: true,
|
|
9231
|
+
isManaged: false,
|
|
9232
|
+
currentHash: "",
|
|
9233
|
+
trustStatus: "untrusted"
|
|
9234
|
+
});
|
|
9235
|
+
});
|
|
9236
|
+
});
|
|
9237
|
+
}
|
|
9238
|
+
return hooks;
|
|
9239
|
+
}
|
|
9240
|
+
hooksPaths(workspacePath) {
|
|
9241
|
+
return {
|
|
9242
|
+
globalHooksPath: path6.join(this.codexHome, "hooks.json"),
|
|
9243
|
+
projectHooksPath: path6.join(workspacePath, ".codex", "hooks.json")
|
|
9244
|
+
};
|
|
9245
|
+
}
|
|
9246
|
+
};
|
|
9247
|
+
|
|
9248
|
+
// src/codex/local-session-store.ts
|
|
9249
|
+
import fs6 from "fs/promises";
|
|
9250
|
+
import path7 from "path";
|
|
9251
|
+
import Database2 from "better-sqlite3";
|
|
9252
|
+
|
|
9253
|
+
// src/codex/thread-title.ts
|
|
9254
|
+
var AUTO_THREAD_TITLE_MAX_CHARS = 15;
|
|
9255
|
+
function normalizeWhitespace(value) {
|
|
9256
|
+
return value.replace(/\s+/g, " ").trim();
|
|
9257
|
+
}
|
|
9258
|
+
function truncateAutoThreadTitle(value) {
|
|
9259
|
+
const normalized = normalizeWhitespace(value);
|
|
9260
|
+
if (!normalized) {
|
|
9261
|
+
return "";
|
|
9262
|
+
}
|
|
9263
|
+
const characters = Array.from(normalized);
|
|
9264
|
+
if (characters.length <= AUTO_THREAD_TITLE_MAX_CHARS) {
|
|
9265
|
+
return normalized;
|
|
9266
|
+
}
|
|
9267
|
+
return `${characters.slice(0, AUTO_THREAD_TITLE_MAX_CHARS).join("")}...`;
|
|
9268
|
+
}
|
|
9269
|
+
|
|
9270
|
+
// src/codex/local-session-store.ts
|
|
9271
|
+
function basenameFromPath(absPath) {
|
|
9272
|
+
const normalized = absPath.replace(/[\\/]+$/, "");
|
|
9273
|
+
return normalized.split(/[\\/]/).at(-1) ?? normalized;
|
|
9274
|
+
}
|
|
9275
|
+
function summarizeTitleFromTurns(turns) {
|
|
9276
|
+
const firstUserMessage = turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage" && item.text.trim());
|
|
9277
|
+
if (!firstUserMessage) {
|
|
9278
|
+
return null;
|
|
9279
|
+
}
|
|
9280
|
+
return truncateAutoThreadTitle(firstUserMessage.text);
|
|
9281
|
+
}
|
|
9282
|
+
function createHistoryItemId(turnId, prefix, index) {
|
|
9283
|
+
return `${turnId}-${prefix}-${index}`;
|
|
9284
|
+
}
|
|
9285
|
+
function finalizeTurn(turn, turns) {
|
|
9286
|
+
if (!turn || turn.items.length === 0) {
|
|
9287
|
+
return;
|
|
9288
|
+
}
|
|
9289
|
+
turns.push({
|
|
9290
|
+
id: turn.id,
|
|
9291
|
+
startedAt: turn.startedAt,
|
|
9292
|
+
status: turn.status,
|
|
9293
|
+
error: turn.error,
|
|
9294
|
+
items: turn.items
|
|
9295
|
+
});
|
|
9296
|
+
}
|
|
9297
|
+
function parseTranscript(contents) {
|
|
9298
|
+
const turns = [];
|
|
9299
|
+
let cwd = null;
|
|
9300
|
+
let currentTurn = null;
|
|
9301
|
+
let fallbackTurnCount = 0;
|
|
9302
|
+
let agentItemCount = 0;
|
|
9303
|
+
let userItemCount = 0;
|
|
9304
|
+
const ensureCurrentTurn = (timestamp) => {
|
|
9305
|
+
if (currentTurn) {
|
|
9306
|
+
return currentTurn;
|
|
9307
|
+
}
|
|
9308
|
+
fallbackTurnCount += 1;
|
|
9309
|
+
currentTurn = {
|
|
9310
|
+
id: `local-turn-${fallbackTurnCount}`,
|
|
9311
|
+
startedAt: timestamp ?? null,
|
|
9312
|
+
status: "inProgress",
|
|
9313
|
+
error: null,
|
|
9314
|
+
items: []
|
|
9315
|
+
};
|
|
9316
|
+
agentItemCount = 0;
|
|
9317
|
+
userItemCount = 0;
|
|
9318
|
+
return currentTurn;
|
|
9319
|
+
};
|
|
9320
|
+
for (const line of contents.split("\n")) {
|
|
9321
|
+
if (!line.trim()) {
|
|
9322
|
+
continue;
|
|
9323
|
+
}
|
|
9324
|
+
let entry;
|
|
9325
|
+
try {
|
|
9326
|
+
entry = JSON.parse(line);
|
|
9327
|
+
} catch {
|
|
9328
|
+
continue;
|
|
9329
|
+
}
|
|
9330
|
+
if (entry.type === "session_meta") {
|
|
9331
|
+
const payload2 = entry.payload ?? {};
|
|
9332
|
+
if (typeof payload2.cwd === "string" && payload2.cwd.trim()) {
|
|
9333
|
+
cwd = payload2.cwd;
|
|
9334
|
+
}
|
|
9335
|
+
continue;
|
|
9336
|
+
}
|
|
9337
|
+
if (entry.type !== "event_msg") {
|
|
9338
|
+
continue;
|
|
9339
|
+
}
|
|
9340
|
+
const payload = entry.payload ?? {};
|
|
9341
|
+
const payloadType = payload.type;
|
|
9342
|
+
if (payloadType === "task_started") {
|
|
9343
|
+
finalizeTurn(currentTurn, turns);
|
|
9344
|
+
currentTurn = {
|
|
9345
|
+
id: typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id : `local-turn-${fallbackTurnCount + 1}`,
|
|
9346
|
+
startedAt: entry.timestamp ?? null,
|
|
9347
|
+
status: "inProgress",
|
|
9348
|
+
error: null,
|
|
9349
|
+
items: []
|
|
9350
|
+
};
|
|
9351
|
+
agentItemCount = 0;
|
|
9352
|
+
userItemCount = 0;
|
|
9353
|
+
continue;
|
|
9354
|
+
}
|
|
9355
|
+
if (payloadType === "user_message" && typeof payload.message === "string") {
|
|
9356
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
9357
|
+
userItemCount += 1;
|
|
9358
|
+
turn.items.push({
|
|
9359
|
+
id: createHistoryItemId(turn.id, "user", userItemCount),
|
|
9360
|
+
kind: "userMessage",
|
|
9361
|
+
text: payload.message
|
|
9362
|
+
});
|
|
9363
|
+
continue;
|
|
9364
|
+
}
|
|
9365
|
+
if (payloadType === "agent_message" && typeof payload.message === "string") {
|
|
9366
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
9367
|
+
agentItemCount += 1;
|
|
9368
|
+
turn.items.push({
|
|
9369
|
+
id: createHistoryItemId(turn.id, "agent", agentItemCount),
|
|
9370
|
+
kind: "agentMessage",
|
|
9371
|
+
text: payload.message,
|
|
9372
|
+
status: typeof payload.phase === "string" ? payload.phase : null
|
|
9373
|
+
});
|
|
9374
|
+
continue;
|
|
9375
|
+
}
|
|
9376
|
+
if (payloadType === "task_complete") {
|
|
9377
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
9378
|
+
turn.status = turn.error ? "failed" : "completed";
|
|
9379
|
+
finalizeTurn(turn, turns);
|
|
9380
|
+
currentTurn = null;
|
|
9381
|
+
continue;
|
|
9382
|
+
}
|
|
9383
|
+
if (payloadType === "error") {
|
|
9384
|
+
const turn = ensureCurrentTurn(entry.timestamp);
|
|
9385
|
+
turn.status = "failed";
|
|
9386
|
+
turn.error = typeof payload.message === "string" ? payload.message : "Local Codex session failed.";
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
finalizeTurn(currentTurn, turns);
|
|
9390
|
+
return {
|
|
9391
|
+
cwd,
|
|
9392
|
+
title: summarizeTitleFromTurns(turns),
|
|
9393
|
+
turns
|
|
9394
|
+
};
|
|
9395
|
+
}
|
|
9396
|
+
async function fileExists(filePath) {
|
|
9397
|
+
try {
|
|
9398
|
+
await fs6.access(filePath);
|
|
9399
|
+
return true;
|
|
9400
|
+
} catch {
|
|
9401
|
+
return false;
|
|
9402
|
+
}
|
|
9403
|
+
}
|
|
9404
|
+
var LocalCodexSessionStore = class {
|
|
9405
|
+
constructor(codexHome) {
|
|
9406
|
+
this.codexHome = codexHome;
|
|
9407
|
+
}
|
|
9408
|
+
codexHome;
|
|
9409
|
+
async findSession(sessionId) {
|
|
8805
9410
|
const stateRecord = await this.findSessionInStateDatabases(sessionId);
|
|
8806
9411
|
const transcriptPath = await this.resolveTranscriptPath(
|
|
8807
9412
|
stateRecord?.rolloutPath ?? null,
|
|
8808
9413
|
sessionId
|
|
8809
9414
|
);
|
|
8810
|
-
const transcript = transcriptPath ? parseTranscript(await
|
|
9415
|
+
const transcript = transcriptPath ? parseTranscript(await fs6.readFile(transcriptPath, "utf8")) : null;
|
|
8811
9416
|
const cwd = stateRecord?.cwd ?? transcript?.cwd ?? null;
|
|
8812
9417
|
if (!cwd) {
|
|
8813
9418
|
return null;
|
|
@@ -8824,14 +9429,14 @@ var LocalCodexSessionStore = class {
|
|
|
8824
9429
|
async findSessionInStateDatabases(sessionId) {
|
|
8825
9430
|
let entries;
|
|
8826
9431
|
try {
|
|
8827
|
-
entries = await
|
|
9432
|
+
entries = await fs6.readdir(this.codexHome);
|
|
8828
9433
|
} catch {
|
|
8829
9434
|
return null;
|
|
8830
9435
|
}
|
|
8831
9436
|
const stateFiles = await Promise.all(
|
|
8832
9437
|
entries.filter((entry) => /^state_\d+\.sqlite$/i.test(entry)).map(async (entry) => {
|
|
8833
|
-
const absPath =
|
|
8834
|
-
const stats = await
|
|
9438
|
+
const absPath = path7.join(this.codexHome, entry);
|
|
9439
|
+
const stats = await fs6.stat(absPath);
|
|
8835
9440
|
return {
|
|
8836
9441
|
absPath,
|
|
8837
9442
|
mtimeMs: stats.mtimeMs
|
|
@@ -8870,22 +9475,22 @@ var LocalCodexSessionStore = class {
|
|
|
8870
9475
|
}
|
|
8871
9476
|
async resolveTranscriptPath(rolloutPath, sessionId) {
|
|
8872
9477
|
if (rolloutPath?.trim()) {
|
|
8873
|
-
const absolutePath =
|
|
9478
|
+
const absolutePath = path7.isAbsolute(rolloutPath) ? rolloutPath : path7.resolve(this.codexHome, rolloutPath);
|
|
8874
9479
|
if (await fileExists(absolutePath)) {
|
|
8875
9480
|
return absolutePath;
|
|
8876
9481
|
}
|
|
8877
9482
|
}
|
|
8878
|
-
return this.findTranscriptFile(
|
|
9483
|
+
return this.findTranscriptFile(path7.join(this.codexHome, "sessions"), sessionId);
|
|
8879
9484
|
}
|
|
8880
9485
|
async findTranscriptFile(directory, sessionId) {
|
|
8881
9486
|
let entries;
|
|
8882
9487
|
try {
|
|
8883
|
-
entries = await
|
|
9488
|
+
entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
8884
9489
|
} catch {
|
|
8885
9490
|
return null;
|
|
8886
9491
|
}
|
|
8887
9492
|
for (const entry of entries) {
|
|
8888
|
-
const absPath =
|
|
9493
|
+
const absPath = path7.join(directory, entry.name);
|
|
8889
9494
|
if (entry.isDirectory()) {
|
|
8890
9495
|
const nested = await this.findTranscriptFile(absPath, sessionId);
|
|
8891
9496
|
if (nested) {
|
|
@@ -8903,23 +9508,33 @@ var LocalCodexSessionStore = class {
|
|
|
8903
9508
|
|
|
8904
9509
|
// src/agent-runtime-bootstrap.ts
|
|
8905
9510
|
function createAgentRuntimeBootstrap(config) {
|
|
8906
|
-
const
|
|
9511
|
+
const runtimes = [];
|
|
9512
|
+
const providerHostHomes = {};
|
|
9513
|
+
const codexConfig = config.agentProviders.codex;
|
|
9514
|
+
const codexRuntime = codexConfig.enabled ? new CodexRuntimeAdapter(
|
|
8907
9515
|
new CodexAppServerManager({
|
|
8908
|
-
command:
|
|
8909
|
-
startupTimeoutMs:
|
|
9516
|
+
command: codexConfig.command,
|
|
9517
|
+
startupTimeoutMs: codexConfig.appServerStartTimeoutMs,
|
|
8910
9518
|
clientInfo: {
|
|
8911
9519
|
name: "remote-codex-supervisor",
|
|
8912
9520
|
title: config.appName,
|
|
8913
9521
|
version: config.appVersion
|
|
8914
9522
|
}
|
|
8915
9523
|
})
|
|
8916
|
-
);
|
|
9524
|
+
) : null;
|
|
9525
|
+
if (codexRuntime) {
|
|
9526
|
+
runtimes.push(codexRuntime);
|
|
9527
|
+
providerHostHomes.codex = codexConfig.home;
|
|
9528
|
+
}
|
|
9529
|
+
const claudeConfig = config.agentProviders.claude;
|
|
9530
|
+
if (claudeConfig.enabled) {
|
|
9531
|
+
providerHostHomes.claude = claudeConfig.home;
|
|
9532
|
+
}
|
|
8917
9533
|
return {
|
|
8918
|
-
agentRuntimes: new AgentRuntimeRegistry(
|
|
8919
|
-
localCodexSessionStore: new LocalCodexSessionStore(
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
}
|
|
9534
|
+
agentRuntimes: new AgentRuntimeRegistry(runtimes),
|
|
9535
|
+
localCodexSessionStore: new LocalCodexSessionStore(codexConfig.home),
|
|
9536
|
+
codexManagement: new CodexManagementService(codexConfig.home),
|
|
9537
|
+
providerHostHomes
|
|
8923
9538
|
};
|
|
8924
9539
|
}
|
|
8925
9540
|
|
|
@@ -8948,11 +9563,11 @@ var SupervisorEventBus = class extends EventEmitter4 {
|
|
|
8948
9563
|
|
|
8949
9564
|
// src/thread-service.ts
|
|
8950
9565
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8951
|
-
import
|
|
8952
|
-
import
|
|
9566
|
+
import fs9 from "fs/promises";
|
|
9567
|
+
import path9 from "path";
|
|
8953
9568
|
|
|
8954
9569
|
// src/exports/thread-pdf-export.ts
|
|
8955
|
-
import
|
|
9570
|
+
import fs7 from "fs";
|
|
8956
9571
|
import puppeteer from "puppeteer-core";
|
|
8957
9572
|
import { marked } from "marked";
|
|
8958
9573
|
var MAX_TEXT_CHARS = 12e3;
|
|
@@ -8985,10 +9600,10 @@ function renderEmbeddedCjkFontCss() {
|
|
|
8985
9600
|
embeddedCjkFontCss = "";
|
|
8986
9601
|
for (const candidate of EMBEDDED_CJK_FONT_CANDIDATES) {
|
|
8987
9602
|
try {
|
|
8988
|
-
if (!
|
|
9603
|
+
if (!fs7.existsSync(candidate.path)) {
|
|
8989
9604
|
continue;
|
|
8990
9605
|
}
|
|
8991
|
-
const font =
|
|
9606
|
+
const font = fs7.readFileSync(candidate.path);
|
|
8992
9607
|
embeddedCjkFontCss = `
|
|
8993
9608
|
@font-face {
|
|
8994
9609
|
font-family: "RemoteCodexCJK";
|
|
@@ -10030,208 +10645,6 @@ async function launchPdfBrowser() {
|
|
|
10030
10645
|
}
|
|
10031
10646
|
}
|
|
10032
10647
|
|
|
10033
|
-
// src/codex/codexHostConfig.ts
|
|
10034
|
-
import fs6 from "fs";
|
|
10035
|
-
import fsp from "fs/promises";
|
|
10036
|
-
import path6 from "path";
|
|
10037
|
-
var SERVICE_TIER_LINE_GLOBAL_PATTERN = /^\s*service_tier\s*=\s*("fast"|"flex"|'fast'|'flex').*\n?/gm;
|
|
10038
|
-
function resolveCodexConfigPath(codexHome) {
|
|
10039
|
-
return path6.join(codexHome, "config.toml");
|
|
10040
|
-
}
|
|
10041
|
-
function parseCodexServiceTier(content) {
|
|
10042
|
-
const match = content.match(
|
|
10043
|
-
/^\s*service_tier\s*=\s*["'](?<tier>fast)["']\s*$/m
|
|
10044
|
-
);
|
|
10045
|
-
const tier = match?.groups?.tier;
|
|
10046
|
-
return tier === "fast" ? tier : null;
|
|
10047
|
-
}
|
|
10048
|
-
function isFastModeEnabledFromConfig(content) {
|
|
10049
|
-
return parseCodexServiceTier(content) === "fast";
|
|
10050
|
-
}
|
|
10051
|
-
function readCodexFastModeSync(codexHome) {
|
|
10052
|
-
try {
|
|
10053
|
-
const content = fs6.readFileSync(resolveCodexConfigPath(codexHome), "utf8");
|
|
10054
|
-
return isFastModeEnabledFromConfig(content);
|
|
10055
|
-
} catch (error) {
|
|
10056
|
-
if (error.code === "ENOENT") {
|
|
10057
|
-
return false;
|
|
10058
|
-
}
|
|
10059
|
-
throw error;
|
|
10060
|
-
}
|
|
10061
|
-
}
|
|
10062
|
-
function upsertCodexServiceTier(content, enabled) {
|
|
10063
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
10064
|
-
const withoutServiceTier = normalized.replace(
|
|
10065
|
-
SERVICE_TIER_LINE_GLOBAL_PATTERN,
|
|
10066
|
-
""
|
|
10067
|
-
);
|
|
10068
|
-
if (!enabled) {
|
|
10069
|
-
return withoutServiceTier.replace(/\n{3,}/g, "\n\n").trimEnd() + (withoutServiceTier.trim() ? "\n" : "");
|
|
10070
|
-
}
|
|
10071
|
-
const nextLine = 'service_tier = "fast"';
|
|
10072
|
-
if (!withoutServiceTier.trim()) {
|
|
10073
|
-
return `${nextLine}
|
|
10074
|
-
`;
|
|
10075
|
-
}
|
|
10076
|
-
const firstSectionMatch = withoutServiceTier.match(/^\s*\[[^\]]+\]/m);
|
|
10077
|
-
if (!firstSectionMatch || firstSectionMatch.index === void 0) {
|
|
10078
|
-
return withoutServiceTier.endsWith("\n") ? `${withoutServiceTier}${nextLine}
|
|
10079
|
-
` : `${withoutServiceTier}
|
|
10080
|
-
${nextLine}
|
|
10081
|
-
`;
|
|
10082
|
-
}
|
|
10083
|
-
const beforeFirstSection = withoutServiceTier.slice(0, firstSectionMatch.index).trimEnd();
|
|
10084
|
-
const afterFirstSection = withoutServiceTier.slice(firstSectionMatch.index).replace(/^\n+/, "");
|
|
10085
|
-
return beforeFirstSection ? `${beforeFirstSection}
|
|
10086
|
-
${nextLine}
|
|
10087
|
-
${afterFirstSection}` : `${nextLine}
|
|
10088
|
-
${afterFirstSection}`;
|
|
10089
|
-
}
|
|
10090
|
-
async function writeCodexFastMode(codexHome, enabled) {
|
|
10091
|
-
const configPath = resolveCodexConfigPath(codexHome);
|
|
10092
|
-
let current = "";
|
|
10093
|
-
try {
|
|
10094
|
-
current = await fsp.readFile(configPath, "utf8");
|
|
10095
|
-
} catch (error) {
|
|
10096
|
-
if (error.code !== "ENOENT") {
|
|
10097
|
-
throw error;
|
|
10098
|
-
}
|
|
10099
|
-
}
|
|
10100
|
-
const next = upsertCodexServiceTier(current, enabled);
|
|
10101
|
-
await fsp.mkdir(path6.dirname(configPath), { recursive: true });
|
|
10102
|
-
await fsp.writeFile(configPath, next, "utf8");
|
|
10103
|
-
return next;
|
|
10104
|
-
}
|
|
10105
|
-
function isCodexFeatureEnabledFromConfig(content, featureName) {
|
|
10106
|
-
const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10107
|
-
const featuresMatch = content.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
|
|
10108
|
-
if (!featuresMatch || featuresMatch.index === void 0) {
|
|
10109
|
-
return false;
|
|
10110
|
-
}
|
|
10111
|
-
const sectionStart = featuresMatch.index + featuresMatch[0].length;
|
|
10112
|
-
const nextSectionMatch = content.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
|
|
10113
|
-
const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : content.length;
|
|
10114
|
-
const featuresBody = content.slice(sectionStart, sectionEnd);
|
|
10115
|
-
return new RegExp(`^\\s*${escaped}\\s*=\\s*true\\s*(?:#.*)?$`, "m").test(featuresBody);
|
|
10116
|
-
}
|
|
10117
|
-
async function readCodexFeatureFlag(codexHome, featureName) {
|
|
10118
|
-
try {
|
|
10119
|
-
const content = await fsp.readFile(resolveCodexConfigPath(codexHome), "utf8");
|
|
10120
|
-
return isCodexFeatureEnabledFromConfig(content, featureName);
|
|
10121
|
-
} catch (error) {
|
|
10122
|
-
if (error.code === "ENOENT") {
|
|
10123
|
-
return false;
|
|
10124
|
-
}
|
|
10125
|
-
throw error;
|
|
10126
|
-
}
|
|
10127
|
-
}
|
|
10128
|
-
function upsertCodexFeatureFlag(content, featureName, enabled) {
|
|
10129
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
10130
|
-
const escaped = featureName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10131
|
-
const featuresMatch = normalized.match(/^[^\S\r\n]*\[features\][^\S\r\n]*$/m);
|
|
10132
|
-
const nextLine = `${featureName} = ${enabled ? "true" : "false"}`;
|
|
10133
|
-
if (!featuresMatch || featuresMatch.index === void 0) {
|
|
10134
|
-
const prefix = normalized.trimEnd();
|
|
10135
|
-
return prefix ? `${prefix}
|
|
10136
|
-
|
|
10137
|
-
[features]
|
|
10138
|
-
${nextLine}
|
|
10139
|
-
` : `[features]
|
|
10140
|
-
${nextLine}
|
|
10141
|
-
`;
|
|
10142
|
-
}
|
|
10143
|
-
const sectionStart = featuresMatch.index + featuresMatch[0].length;
|
|
10144
|
-
const nextSectionMatch = normalized.slice(sectionStart).match(/^[^\S\r\n]*\[[^\]\r\n]+\][^\S\r\n]*$/m);
|
|
10145
|
-
const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? sectionStart + nextSectionMatch.index : normalized.length;
|
|
10146
|
-
const beforeSectionBody = normalized.slice(0, sectionStart);
|
|
10147
|
-
const sectionBody = normalized.slice(sectionStart, sectionEnd);
|
|
10148
|
-
const afterSection = normalized.slice(sectionEnd);
|
|
10149
|
-
const flagPattern = new RegExp(
|
|
10150
|
-
`(^[^\\S\\r\\n]*)${escaped}[^\\S\\r\\n]*=[^\\S\\r\\n]*(true|false)[^\\S\\r\\n]*(?:#.*)?$`,
|
|
10151
|
-
"m"
|
|
10152
|
-
);
|
|
10153
|
-
if (flagPattern.test(sectionBody)) {
|
|
10154
|
-
return beforeSectionBody + sectionBody.replace(flagPattern, `$1${nextLine}`) + afterSection;
|
|
10155
|
-
}
|
|
10156
|
-
const bodyWithFlag = `${sectionBody.replace(/\n*$/, "")}
|
|
10157
|
-
${nextLine}
|
|
10158
|
-
`;
|
|
10159
|
-
const normalizedAfterSection = afterSection.startsWith("\n") ? afterSection : `
|
|
10160
|
-
${afterSection}`;
|
|
10161
|
-
return beforeSectionBody + bodyWithFlag + normalizedAfterSection;
|
|
10162
|
-
}
|
|
10163
|
-
async function writeCodexFeatureFlag(codexHome, featureName, enabled) {
|
|
10164
|
-
const configPath = resolveCodexConfigPath(codexHome);
|
|
10165
|
-
let current = "";
|
|
10166
|
-
try {
|
|
10167
|
-
current = await fsp.readFile(configPath, "utf8");
|
|
10168
|
-
} catch (error) {
|
|
10169
|
-
if (error.code !== "ENOENT") {
|
|
10170
|
-
throw error;
|
|
10171
|
-
}
|
|
10172
|
-
}
|
|
10173
|
-
const next = upsertCodexFeatureFlag(current, featureName, enabled);
|
|
10174
|
-
await fsp.mkdir(path6.dirname(configPath), { recursive: true });
|
|
10175
|
-
await fsp.writeFile(configPath, next, "utf8");
|
|
10176
|
-
return next;
|
|
10177
|
-
}
|
|
10178
|
-
|
|
10179
|
-
// src/codex/runtime-errors.ts
|
|
10180
|
-
function unwrapCodexJsonRpcError(error) {
|
|
10181
|
-
if (error instanceof JsonRpcClientError) {
|
|
10182
|
-
return error;
|
|
10183
|
-
}
|
|
10184
|
-
if (error instanceof AgentRuntimeError && error.provider === "codex") {
|
|
10185
|
-
return error.cause instanceof JsonRpcClientError ? error.cause : null;
|
|
10186
|
-
}
|
|
10187
|
-
return null;
|
|
10188
|
-
}
|
|
10189
|
-
function isCodexRemoteError(error) {
|
|
10190
|
-
const codexError = unwrapCodexJsonRpcError(error);
|
|
10191
|
-
return codexError?.code === "remote_error" ? codexError : null;
|
|
10192
|
-
}
|
|
10193
|
-
function isCodexRuntimeRequestError(error) {
|
|
10194
|
-
return Boolean(unwrapCodexJsonRpcError(error));
|
|
10195
|
-
}
|
|
10196
|
-
function isRemoteThreadBootstrapError(error) {
|
|
10197
|
-
const codexError = isCodexRemoteError(error);
|
|
10198
|
-
if (!codexError) {
|
|
10199
|
-
return false;
|
|
10200
|
-
}
|
|
10201
|
-
return codexError.message.includes("includeTurns is unavailable before first user message") || codexError.message.includes("is not materialized yet") || codexError.message.includes("no rollout found for thread id") || codexError.message.includes("failed to load rollout") || codexError.message.includes("rollout at") && codexError.message.includes("is empty");
|
|
10202
|
-
}
|
|
10203
|
-
function isUnsupportedHooksListError(error) {
|
|
10204
|
-
const codexError = isCodexRemoteError(error);
|
|
10205
|
-
if (!codexError) {
|
|
10206
|
-
return false;
|
|
10207
|
-
}
|
|
10208
|
-
const remoteCode = codexError.details?.code;
|
|
10209
|
-
const message = codexError.message.toLowerCase();
|
|
10210
|
-
return remoteCode === -32601 || message.includes("endpoint not found") || message.includes("method not found") || message.includes("hooks/list") && message.includes("not found");
|
|
10211
|
-
}
|
|
10212
|
-
function parseTurnSteerRace(error) {
|
|
10213
|
-
const codexError = isCodexRemoteError(error);
|
|
10214
|
-
if (!codexError) {
|
|
10215
|
-
return null;
|
|
10216
|
-
}
|
|
10217
|
-
if (codexError.message === "no active turn to steer") {
|
|
10218
|
-
return { type: "missing" };
|
|
10219
|
-
}
|
|
10220
|
-
const mismatchPrefix = "expected active turn id `";
|
|
10221
|
-
const mismatchSeparator = "` but found `";
|
|
10222
|
-
if (!codexError.message.startsWith(mismatchPrefix)) {
|
|
10223
|
-
return null;
|
|
10224
|
-
}
|
|
10225
|
-
const actualTurnId = codexError.message.slice(mismatchPrefix.length).split(mismatchSeparator)[1]?.replace(/`$/, "");
|
|
10226
|
-
if (!actualTurnId) {
|
|
10227
|
-
return null;
|
|
10228
|
-
}
|
|
10229
|
-
return {
|
|
10230
|
-
type: "turnIdMismatch",
|
|
10231
|
-
actualTurnId
|
|
10232
|
-
};
|
|
10233
|
-
}
|
|
10234
|
-
|
|
10235
10648
|
// src/thread-history-items.ts
|
|
10236
10649
|
var DEFERRED_COMMAND_DETAIL_TITLE2 = "Command Output";
|
|
10237
10650
|
var DEFERRED_TOOL_DETAIL_TITLE2 = "Tool Call Details";
|
|
@@ -10298,7 +10711,7 @@ function deferLargeHistoryItemDetails(turn, deferredDetails) {
|
|
|
10298
10711
|
};
|
|
10299
10712
|
}
|
|
10300
10713
|
function shouldPersistLiveHistoryItem(item) {
|
|
10301
|
-
return item.kind === "commandExecution" || item.kind === "fileChange" || item.kind === "hook" || item.kind === "toolCall" || item.kind === "webSearch";
|
|
10714
|
+
return item.kind === "agentMessage" || item.kind === "commandExecution" || item.kind === "fileChange" || item.kind === "hook" || item.kind === "toolCall" || item.kind === "webSearch";
|
|
10302
10715
|
}
|
|
10303
10716
|
function parseStoredHistoryItem(value) {
|
|
10304
10717
|
try {
|
|
@@ -10333,14 +10746,59 @@ function sortTurnItemsByRecordedSequence(items) {
|
|
|
10333
10746
|
leadingItems.push(items[index]);
|
|
10334
10747
|
index += 1;
|
|
10335
10748
|
}
|
|
10336
|
-
|
|
10749
|
+
const trailingItems = items.slice(index);
|
|
10750
|
+
if (!trailingItems.some(hasHistoryItemSequence)) {
|
|
10751
|
+
return items;
|
|
10752
|
+
}
|
|
10753
|
+
const sequenceValues = trailingItems.map((item) => historyItemSequence(item)).filter(Number.isFinite);
|
|
10754
|
+
const maxSequence = sequenceValues.length > 0 ? Math.max(...sequenceValues) : 0;
|
|
10755
|
+
const orderedItems = [];
|
|
10756
|
+
let cursor = 0;
|
|
10757
|
+
while (cursor < trailingItems.length) {
|
|
10758
|
+
const item = trailingItems[cursor];
|
|
10759
|
+
if (hasHistoryItemSequence(item)) {
|
|
10760
|
+
orderedItems.push({ item, index: cursor, order: historyItemSequence(item) });
|
|
10761
|
+
cursor += 1;
|
|
10762
|
+
continue;
|
|
10763
|
+
}
|
|
10764
|
+
const blockStart = cursor;
|
|
10765
|
+
while (cursor < trailingItems.length && !hasHistoryItemSequence(trailingItems[cursor])) {
|
|
10766
|
+
cursor += 1;
|
|
10767
|
+
}
|
|
10768
|
+
const block = trailingItems.slice(blockStart, cursor);
|
|
10769
|
+
const previousSequenced = [...trailingItems.slice(0, blockStart)].reverse().find(hasHistoryItemSequence);
|
|
10770
|
+
const nextSequenced = trailingItems.slice(cursor).find(hasHistoryItemSequence);
|
|
10771
|
+
const previousSequence = previousSequenced ? historyItemSequence(previousSequenced) : null;
|
|
10772
|
+
const nextSequence = nextSequenced ? historyItemSequence(nextSequenced) : null;
|
|
10773
|
+
block.forEach((blockItem, blockIndex) => {
|
|
10774
|
+
let order;
|
|
10775
|
+
if (previousSequence === null && nextSequence !== null) {
|
|
10776
|
+
order = nextSequence - (block.length - blockIndex) / (block.length + 1);
|
|
10777
|
+
} else if (previousSequence !== null && nextSequence !== null && nextSequence > previousSequence) {
|
|
10778
|
+
const span = nextSequence - previousSequence;
|
|
10779
|
+
order = previousSequence + (blockIndex + 1) / (block.length + 1) * span;
|
|
10780
|
+
} else {
|
|
10781
|
+
order = maxSequence + 1 + blockIndex / (block.length + 1);
|
|
10782
|
+
}
|
|
10783
|
+
orderedItems.push({
|
|
10784
|
+
item: blockItem,
|
|
10785
|
+
index: blockStart + blockIndex,
|
|
10786
|
+
order
|
|
10787
|
+
});
|
|
10788
|
+
});
|
|
10789
|
+
}
|
|
10790
|
+
const sortedTrailingItems = orderedItems.sort((left, right) => {
|
|
10791
|
+
const orderDelta = left.order - right.order;
|
|
10792
|
+
return orderDelta === 0 ? left.index - right.index : orderDelta;
|
|
10793
|
+
}).map((entry) => entry.item);
|
|
10794
|
+
return [...leadingItems, ...sortedTrailingItems];
|
|
10337
10795
|
}
|
|
10338
10796
|
function mergeHistoryItemsBySequence(items, missingItems) {
|
|
10339
10797
|
if (missingItems.length === 0) {
|
|
10340
|
-
return items;
|
|
10798
|
+
return sortTurnItemsByRecordedSequence(items);
|
|
10341
10799
|
}
|
|
10342
10800
|
if (!missingItems.some(hasHistoryItemSequence)) {
|
|
10343
|
-
return [...items, ...missingItems];
|
|
10801
|
+
return sortTurnItemsByRecordedSequence([...items, ...missingItems]);
|
|
10344
10802
|
}
|
|
10345
10803
|
const mergedItems = [...items];
|
|
10346
10804
|
const orderedMissingItems = sortHistoryItemsBySequence(missingItems);
|
|
@@ -10365,16 +10823,20 @@ function mergeHistoryItemsBySequence(items, missingItems) {
|
|
|
10365
10823
|
}
|
|
10366
10824
|
mergedItems.push(missingItem);
|
|
10367
10825
|
}
|
|
10368
|
-
return mergedItems;
|
|
10826
|
+
return sortTurnItemsByRecordedSequence(mergedItems);
|
|
10827
|
+
}
|
|
10828
|
+
function copyPersistedSequence(item, persistedItem) {
|
|
10829
|
+
if (!hasHistoryItemSequence(persistedItem)) {
|
|
10830
|
+
return item;
|
|
10831
|
+
}
|
|
10832
|
+
const sequence = historyItemSequence(persistedItem);
|
|
10833
|
+
return item.sequence === sequence ? item : { ...item, sequence };
|
|
10369
10834
|
}
|
|
10370
10835
|
function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, deferredDetails) {
|
|
10371
10836
|
if (persistedItemsByTurnId.size === 0) {
|
|
10372
10837
|
return turns;
|
|
10373
10838
|
}
|
|
10374
10839
|
return turns.map((turn) => {
|
|
10375
|
-
if (turn.status === "inProgress") {
|
|
10376
|
-
return turn;
|
|
10377
|
-
}
|
|
10378
10840
|
const persistedItems = persistedItemsByTurnId.get(turn.id);
|
|
10379
10841
|
if (!persistedItems || persistedItems.length === 0) {
|
|
10380
10842
|
return turn;
|
|
@@ -10383,23 +10845,32 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
|
|
|
10383
10845
|
const persistedItemsById = new Map(persistedItems.map((item) => [item.id, item]));
|
|
10384
10846
|
const nextItems = turn.items.map((item) => {
|
|
10385
10847
|
const persistedItem = persistedItemsById.get(item.id);
|
|
10386
|
-
if (!persistedItem
|
|
10848
|
+
if (!persistedItem) {
|
|
10387
10849
|
return item;
|
|
10388
10850
|
}
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
if (persistedText.length <= existingText.length) {
|
|
10851
|
+
persistedItemsById.delete(item.id);
|
|
10852
|
+
if (item.kind !== persistedItem.kind) {
|
|
10392
10853
|
return item;
|
|
10393
10854
|
}
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
persistedItem
|
|
10401
|
-
|
|
10402
|
-
|
|
10855
|
+
const sequencedItem = copyPersistedSequence(item, persistedItem);
|
|
10856
|
+
if (sequencedItem !== item) {
|
|
10857
|
+
changed = true;
|
|
10858
|
+
}
|
|
10859
|
+
if (persistedItem.kind === "commandExecution" || persistedItem.kind === "toolCall") {
|
|
10860
|
+
const existingText = item.detailText?.trim() || item.text.trim();
|
|
10861
|
+
const persistedText = persistedItem.detailText?.trim() || persistedItem.text.trim();
|
|
10862
|
+
if (persistedText.length > existingText.length) {
|
|
10863
|
+
changed = true;
|
|
10864
|
+
return persistedItem.kind === "commandExecution" ? deferCommandHistoryItem2(
|
|
10865
|
+
persistedItem,
|
|
10866
|
+
deferredDetails
|
|
10867
|
+
) : deferToolCallHistoryItem2(
|
|
10868
|
+
persistedItem,
|
|
10869
|
+
deferredDetails
|
|
10870
|
+
);
|
|
10871
|
+
}
|
|
10872
|
+
}
|
|
10873
|
+
return sequencedItem;
|
|
10403
10874
|
});
|
|
10404
10875
|
const existingItemIds = new Set(nextItems.map((item) => item.id));
|
|
10405
10876
|
const missingItems = [...persistedItemsById.values()].filter((item) => !existingItemIds.has(item.id)).map(
|
|
@@ -10457,25 +10928,25 @@ function applyRecordedTurnItemOrders(turns, turnItemOrder) {
|
|
|
10457
10928
|
}
|
|
10458
10929
|
|
|
10459
10930
|
// src/codex/modelPricing.ts
|
|
10460
|
-
import
|
|
10461
|
-
import
|
|
10931
|
+
import fs8 from "fs";
|
|
10932
|
+
import path8 from "path";
|
|
10462
10933
|
var TOKEN_PRICE_DENOMINATOR = 1e6;
|
|
10463
10934
|
var cachedPricingConfig = null;
|
|
10464
10935
|
function resolvePackageRoot2(start = process.cwd()) {
|
|
10465
10936
|
if (process.env.REMOTE_CODEX_PACKAGE_ROOT) {
|
|
10466
|
-
return
|
|
10937
|
+
return path8.resolve(process.env.REMOTE_CODEX_PACKAGE_ROOT);
|
|
10467
10938
|
}
|
|
10468
|
-
let current =
|
|
10469
|
-
while (current !==
|
|
10470
|
-
if (
|
|
10939
|
+
let current = path8.resolve(start);
|
|
10940
|
+
while (current !== path8.dirname(current)) {
|
|
10941
|
+
if (fs8.existsSync(path8.join(current, "pnpm-workspace.yaml"))) {
|
|
10471
10942
|
return current;
|
|
10472
10943
|
}
|
|
10473
|
-
current =
|
|
10944
|
+
current = path8.dirname(current);
|
|
10474
10945
|
}
|
|
10475
10946
|
throw new Error("Unable to locate package root for Codex pricing config.");
|
|
10476
10947
|
}
|
|
10477
10948
|
function getPricingConfigPath() {
|
|
10478
|
-
return
|
|
10949
|
+
return path8.join(resolvePackageRoot2(), "config", "codex-model-pricing.json");
|
|
10479
10950
|
}
|
|
10480
10951
|
function isPositiveNumber(value) {
|
|
10481
10952
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
@@ -10552,7 +11023,7 @@ function getPricingConfig() {
|
|
|
10552
11023
|
return cachedPricingConfig;
|
|
10553
11024
|
}
|
|
10554
11025
|
const configPath = getPricingConfigPath();
|
|
10555
|
-
const content =
|
|
11026
|
+
const content = fs8.readFileSync(configPath, "utf8");
|
|
10556
11027
|
cachedPricingConfig = parsePricingConfig(JSON.parse(content));
|
|
10557
11028
|
return cachedPricingConfig;
|
|
10558
11029
|
}
|
|
@@ -10633,20 +11104,6 @@ var THREAD_DETAIL_CACHE_TTL_MS = 5e3;
|
|
|
10633
11104
|
var CONTEXT_BASELINE_TOKENS = 12e3;
|
|
10634
11105
|
var FAST_MODE_NOTE_ON = "Fast mode on";
|
|
10635
11106
|
var FAST_MODE_NOTE_OFF = "Fast mode off";
|
|
10636
|
-
var HOOK_EVENT_JSON_KEYS = {
|
|
10637
|
-
preToolUse: "PreToolUse",
|
|
10638
|
-
permissionRequest: "PermissionRequest",
|
|
10639
|
-
postToolUse: "PostToolUse",
|
|
10640
|
-
preCompact: "PreCompact",
|
|
10641
|
-
postCompact: "PostCompact",
|
|
10642
|
-
sessionStart: "SessionStart",
|
|
10643
|
-
userPromptSubmit: "UserPromptSubmit",
|
|
10644
|
-
stop: "Stop"
|
|
10645
|
-
};
|
|
10646
|
-
var HOOK_EVENT_DTO_KEYS = Object.fromEntries(
|
|
10647
|
-
Object.entries(HOOK_EVENT_JSON_KEYS).map(([dtoKey, jsonKey]) => [jsonKey, dtoKey])
|
|
10648
|
-
);
|
|
10649
|
-
var GOAL_FEATURE_DISABLED_MESSAGE = "Codex /goal is experimental. Enable it by adding `goals = true` under `[features]` in ~/.codex/config.toml, then restart the Codex app-server.";
|
|
10650
11107
|
function toIsoFromEpoch2(value) {
|
|
10651
11108
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
10652
11109
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10718,22 +11175,36 @@ function mergeGoalHistoryEntry(existing, incoming) {
|
|
|
10718
11175
|
localGoalId: latest.localGoalId ?? fallback.localGoalId ?? null
|
|
10719
11176
|
};
|
|
10720
11177
|
}
|
|
10721
|
-
function
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
11178
|
+
function goalHistoryStatusRank(status) {
|
|
11179
|
+
return ["active", "paused", "budgetLimited"].includes(status) ? 0 : 1;
|
|
11180
|
+
}
|
|
11181
|
+
function resetGoalProgress(goal) {
|
|
11182
|
+
return {
|
|
11183
|
+
...goal,
|
|
11184
|
+
tokensUsed: 0,
|
|
11185
|
+
timeUsedSeconds: 0
|
|
11186
|
+
};
|
|
11187
|
+
}
|
|
11188
|
+
function goalObjectiveChanged(existing, nextObjective) {
|
|
11189
|
+
return existing !== null && typeof nextObjective === "string" && nextObjective.trim().length > 0 && nextObjective !== existing.objective;
|
|
11190
|
+
}
|
|
11191
|
+
function isLocalGoalStatus(status) {
|
|
11192
|
+
return ["active", "paused", "budgetLimited"].includes(status);
|
|
11193
|
+
}
|
|
11194
|
+
function localGoalSnapshotForFallback(goalHistory) {
|
|
11195
|
+
const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
|
|
11196
|
+
if (activeGoal) {
|
|
11197
|
+
return activeGoal;
|
|
11198
|
+
}
|
|
11199
|
+
return goalHistory.find((entry) => entry.status === "terminated") ?? null;
|
|
11200
|
+
}
|
|
11201
|
+
function localGoalSnapshotToPreserve(goalHistory, remoteGoal) {
|
|
11202
|
+
const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
|
|
11203
|
+
const hasTerminatedHistory = goalHistory.some((entry) => entry.status === "terminated");
|
|
11204
|
+
if (activeGoal && hasTerminatedHistory && isLocalGoalStatus(remoteGoal.status) && activeGoal.objective === remoteGoal.objective && activeGoal.tokensUsed === 0 && activeGoal.timeUsedSeconds === 0 && (remoteGoal.tokensUsed > 0 || remoteGoal.timeUsedSeconds > 0)) {
|
|
11205
|
+
return activeGoal;
|
|
10735
11206
|
}
|
|
10736
|
-
|
|
11207
|
+
return activeGoal ? null : goalHistory.find((entry) => entry.status === "terminated") ?? null;
|
|
10737
11208
|
}
|
|
10738
11209
|
function defaultSandboxModeForApprovalMode(approvalMode) {
|
|
10739
11210
|
return approvalMode === "guarded" ? "workspace-write" : "danger-full-access";
|
|
@@ -10748,34 +11219,6 @@ function normalizeSandboxMode(value) {
|
|
|
10748
11219
|
return null;
|
|
10749
11220
|
}
|
|
10750
11221
|
}
|
|
10751
|
-
function buildTurnSandboxPolicy(sandboxMode, writableRoot) {
|
|
10752
|
-
switch (sandboxMode) {
|
|
10753
|
-
case "danger-full-access":
|
|
10754
|
-
return {
|
|
10755
|
-
type: "dangerFullAccess"
|
|
10756
|
-
};
|
|
10757
|
-
case "read-only":
|
|
10758
|
-
return {
|
|
10759
|
-
type: "readOnly",
|
|
10760
|
-
access: {
|
|
10761
|
-
type: "fullAccess"
|
|
10762
|
-
},
|
|
10763
|
-
networkAccess: false
|
|
10764
|
-
};
|
|
10765
|
-
case "workspace-write":
|
|
10766
|
-
default:
|
|
10767
|
-
return {
|
|
10768
|
-
type: "workspaceWrite",
|
|
10769
|
-
writableRoots: [writableRoot],
|
|
10770
|
-
readOnlyAccess: {
|
|
10771
|
-
type: "fullAccess"
|
|
10772
|
-
},
|
|
10773
|
-
networkAccess: false,
|
|
10774
|
-
excludeTmpdirEnvVar: false,
|
|
10775
|
-
excludeSlashTmp: false
|
|
10776
|
-
};
|
|
10777
|
-
}
|
|
10778
|
-
}
|
|
10779
11222
|
function normalizeReasoningEffort(value) {
|
|
10780
11223
|
switch (value) {
|
|
10781
11224
|
case "none":
|
|
@@ -10795,8 +11238,8 @@ function normalizeCollaborationMode(value) {
|
|
|
10795
11238
|
function normalizeFastMode(value) {
|
|
10796
11239
|
return value === true || value === 1;
|
|
10797
11240
|
}
|
|
10798
|
-
function
|
|
10799
|
-
return fastMode ? "fast" :
|
|
11241
|
+
function performanceModeForFastMode(fastMode) {
|
|
11242
|
+
return fastMode ? "fast" : "standard";
|
|
10800
11243
|
}
|
|
10801
11244
|
function normalizePricingTier(value) {
|
|
10802
11245
|
return value === "fast" || value === "standard" ? value : null;
|
|
@@ -10841,26 +11284,26 @@ function buildAnsweredRequestNote(request, input) {
|
|
|
10841
11284
|
}
|
|
10842
11285
|
async function pathExists(absPath) {
|
|
10843
11286
|
try {
|
|
10844
|
-
await
|
|
11287
|
+
await fs9.access(absPath);
|
|
10845
11288
|
return true;
|
|
10846
11289
|
} catch {
|
|
10847
11290
|
return false;
|
|
10848
11291
|
}
|
|
10849
11292
|
}
|
|
10850
11293
|
async function resolveComparablePath2(absPath) {
|
|
10851
|
-
const resolved =
|
|
11294
|
+
const resolved = path9.resolve(absPath);
|
|
10852
11295
|
if (await pathExists(resolved)) {
|
|
10853
|
-
return
|
|
11296
|
+
return fs9.realpath(resolved);
|
|
10854
11297
|
}
|
|
10855
|
-
const parentPath =
|
|
11298
|
+
const parentPath = path9.dirname(resolved);
|
|
10856
11299
|
if (parentPath === resolved) {
|
|
10857
11300
|
return resolved;
|
|
10858
11301
|
}
|
|
10859
11302
|
const resolvedParent = await resolveComparablePath2(parentPath);
|
|
10860
|
-
return
|
|
11303
|
+
return path9.join(resolvedParent, path9.basename(resolved));
|
|
10861
11304
|
}
|
|
10862
11305
|
async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
10863
|
-
if (!
|
|
11306
|
+
if (!path9.isAbsolute(candidatePath)) {
|
|
10864
11307
|
throw new HttpError(400, {
|
|
10865
11308
|
code: "bad_request",
|
|
10866
11309
|
message: "Imported session path must be absolute."
|
|
@@ -10868,7 +11311,7 @@ async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
|
10868
11311
|
}
|
|
10869
11312
|
const resolvedRoot = await resolveComparablePath2(workspaceRoot);
|
|
10870
11313
|
const resolvedCandidate = await resolveComparablePath2(candidatePath);
|
|
10871
|
-
const normalizedRoot = resolvedRoot.endsWith(
|
|
11314
|
+
const normalizedRoot = resolvedRoot.endsWith(path9.sep) ? resolvedRoot : `${resolvedRoot}${path9.sep}`;
|
|
10872
11315
|
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRoot)) {
|
|
10873
11316
|
throw new HttpError(403, {
|
|
10874
11317
|
code: "forbidden",
|
|
@@ -10888,7 +11331,7 @@ function toWorkspaceDto(record) {
|
|
|
10888
11331
|
lastOpenedAt: record.lastOpenedAt
|
|
10889
11332
|
};
|
|
10890
11333
|
}
|
|
10891
|
-
function
|
|
11334
|
+
function isRecord5(value) {
|
|
10892
11335
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10893
11336
|
}
|
|
10894
11337
|
function numberOrNull2(value) {
|
|
@@ -10927,11 +11370,11 @@ function computeContextRemainingPercent(tokensInContextWindow, contextWindow) {
|
|
|
10927
11370
|
return clampPercentage(Math.round(remaining / effectiveWindow * 100));
|
|
10928
11371
|
}
|
|
10929
11372
|
function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10930
|
-
const tokenUsage =
|
|
11373
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
10931
11374
|
const modelContextWindow = contextWindowForModel(model) ?? numberOrNull2(
|
|
10932
11375
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
10933
11376
|
);
|
|
10934
|
-
const lastUsage =
|
|
11377
|
+
const lastUsage = isRecord5(tokenUsage?.last) ? tokenUsage.last : null;
|
|
10935
11378
|
const tokensInContextWindow = numberOrNull2(
|
|
10936
11379
|
lastUsage?.totalTokens ?? lastUsage?.total_tokens
|
|
10937
11380
|
);
|
|
@@ -10950,7 +11393,7 @@ function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (
|
|
|
10950
11393
|
};
|
|
10951
11394
|
}
|
|
10952
11395
|
function buildTurnTokenBreakdown(payload) {
|
|
10953
|
-
const usage =
|
|
11396
|
+
const usage = isRecord5(payload) ? payload : null;
|
|
10954
11397
|
const totalTokens2 = numberOrNull2(usage?.totalTokens ?? usage?.total_tokens);
|
|
10955
11398
|
const inputTokens = numberOrNull2(usage?.inputTokens ?? usage?.input_tokens);
|
|
10956
11399
|
const cachedInputTokens = numberOrNull2(
|
|
@@ -10996,12 +11439,12 @@ function subtractTurnTokenBreakdowns(current, previous) {
|
|
|
10996
11439
|
};
|
|
10997
11440
|
}
|
|
10998
11441
|
function parseThreadTurnTokenUsage(payload) {
|
|
10999
|
-
const tokenUsage =
|
|
11442
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
11000
11443
|
const total = buildTurnTokenBreakdown(
|
|
11001
|
-
|
|
11444
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
11002
11445
|
);
|
|
11003
11446
|
const last = buildTurnTokenBreakdown(
|
|
11004
|
-
|
|
11447
|
+
isRecord5(tokenUsage?.last) ? tokenUsage.last : null
|
|
11005
11448
|
);
|
|
11006
11449
|
const modelContextWindow = numberOrNull2(
|
|
11007
11450
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
@@ -11025,7 +11468,7 @@ function parseStoredThreadTurnTokenUsageState(value) {
|
|
|
11025
11468
|
try {
|
|
11026
11469
|
const parsed = JSON.parse(value);
|
|
11027
11470
|
const baselineTotal = buildTurnTokenBreakdown(
|
|
11028
|
-
|
|
11471
|
+
isRecord5(parsed?.baselineTotal) ? parsed.baselineTotal : null
|
|
11029
11472
|
);
|
|
11030
11473
|
return {
|
|
11031
11474
|
baselineTotal,
|
|
@@ -11062,12 +11505,12 @@ function stringifyStoredThreadTurnTokenUsageState(state) {
|
|
|
11062
11505
|
});
|
|
11063
11506
|
}
|
|
11064
11507
|
function buildThreadTurnTokenUsage(payload, baselineTotal, previous = null) {
|
|
11065
|
-
const tokenUsage =
|
|
11508
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
11066
11509
|
const cumulativeTotal = buildTurnTokenBreakdown(
|
|
11067
|
-
|
|
11510
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
11068
11511
|
);
|
|
11069
11512
|
const last = buildTurnTokenBreakdown(
|
|
11070
|
-
|
|
11513
|
+
isRecord5(tokenUsage?.last) ? tokenUsage.last : null
|
|
11071
11514
|
);
|
|
11072
11515
|
const modelContextWindow = numberOrNull2(
|
|
11073
11516
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
@@ -11085,8 +11528,8 @@ function parseThreadTurnTokenUsageJson(value) {
|
|
|
11085
11528
|
return parseStoredThreadTurnTokenUsageState(value).usage;
|
|
11086
11529
|
}
|
|
11087
11530
|
function sanitizeAttachmentFileName(originalName) {
|
|
11088
|
-
const basename =
|
|
11089
|
-
const extension =
|
|
11531
|
+
const basename = path9.basename(originalName).trim() || "attachment";
|
|
11532
|
+
const extension = path9.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
|
|
11090
11533
|
const rawStem = extension ? basename.slice(0, -extension.length) : basename;
|
|
11091
11534
|
const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
11092
11535
|
const stem = sanitizedStem || "attachment";
|
|
@@ -11094,269 +11537,7 @@ function sanitizeAttachmentFileName(originalName) {
|
|
|
11094
11537
|
return `${stem}-${randomUUID2().slice(0, 8)}${normalizedExtension}`;
|
|
11095
11538
|
}
|
|
11096
11539
|
function threadTempDirectoryPath(workspacePath, localThreadId) {
|
|
11097
|
-
return
|
|
11098
|
-
}
|
|
11099
|
-
function normalizeHooksJson(value) {
|
|
11100
|
-
if (!isRecord4(value) || !isRecord4(value.hooks)) {
|
|
11101
|
-
return { hooks: {} };
|
|
11102
|
-
}
|
|
11103
|
-
const hooks = {};
|
|
11104
|
-
for (const [eventName, groups] of Object.entries(value.hooks)) {
|
|
11105
|
-
hooks[eventName] = Array.isArray(groups) ? groups : [];
|
|
11106
|
-
}
|
|
11107
|
-
return { ...value, hooks };
|
|
11108
|
-
}
|
|
11109
|
-
function readJsonFileOrDefault(filePath) {
|
|
11110
|
-
return fs8.readFile(filePath, "utf8").then((raw) => {
|
|
11111
|
-
if (!raw.trim()) {
|
|
11112
|
-
return { hooks: {} };
|
|
11113
|
-
}
|
|
11114
|
-
return normalizeHooksJson(JSON.parse(raw));
|
|
11115
|
-
}).catch((error) => {
|
|
11116
|
-
if (error.code === "ENOENT") {
|
|
11117
|
-
return { hooks: {} };
|
|
11118
|
-
}
|
|
11119
|
-
throw error;
|
|
11120
|
-
});
|
|
11121
|
-
}
|
|
11122
|
-
function validateHookInput(input) {
|
|
11123
|
-
if (!HOOK_EVENT_JSON_KEYS[input.eventName]) {
|
|
11124
|
-
throw new HttpError(400, {
|
|
11125
|
-
code: "bad_request",
|
|
11126
|
-
message: "Unsupported hook event."
|
|
11127
|
-
});
|
|
11128
|
-
}
|
|
11129
|
-
if (input.scope !== "global" && input.scope !== "project") {
|
|
11130
|
-
throw new HttpError(400, {
|
|
11131
|
-
code: "bad_request",
|
|
11132
|
-
message: "Hook scope must be global or project."
|
|
11133
|
-
});
|
|
11134
|
-
}
|
|
11135
|
-
if (!input.command.trim()) {
|
|
11136
|
-
throw new HttpError(400, {
|
|
11137
|
-
code: "bad_request",
|
|
11138
|
-
message: "Hook command cannot be empty."
|
|
11139
|
-
});
|
|
11140
|
-
}
|
|
11141
|
-
if (input.timeoutSec !== void 0 && input.timeoutSec !== null && (!Number.isInteger(input.timeoutSec) || input.timeoutSec <= 0 || input.timeoutSec > 86400)) {
|
|
11142
|
-
throw new HttpError(400, {
|
|
11143
|
-
code: "bad_request",
|
|
11144
|
-
message: "Hook timeout must be a positive number of seconds."
|
|
11145
|
-
});
|
|
11146
|
-
}
|
|
11147
|
-
}
|
|
11148
|
-
async function writeHookJsonEntry({
|
|
11149
|
-
codexHome,
|
|
11150
|
-
workspacePath,
|
|
11151
|
-
input
|
|
11152
|
-
}) {
|
|
11153
|
-
validateHookInput(input);
|
|
11154
|
-
const hooksPath = input.scope === "global" ? path8.join(codexHome, "hooks.json") : path8.join(workspacePath, ".codex", "hooks.json");
|
|
11155
|
-
const config = await readJsonFileOrDefault(hooksPath);
|
|
11156
|
-
const eventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
|
|
11157
|
-
const matcher = input.matcher?.trim() || null;
|
|
11158
|
-
const handler = {
|
|
11159
|
-
type: "command",
|
|
11160
|
-
command: input.command.trim()
|
|
11161
|
-
};
|
|
11162
|
-
if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
|
|
11163
|
-
handler.timeout = input.timeoutSec;
|
|
11164
|
-
}
|
|
11165
|
-
if (input.statusMessage?.trim()) {
|
|
11166
|
-
handler.statusMessage = input.statusMessage.trim();
|
|
11167
|
-
}
|
|
11168
|
-
const group = {
|
|
11169
|
-
hooks: [handler]
|
|
11170
|
-
};
|
|
11171
|
-
if (matcher) {
|
|
11172
|
-
group.matcher = matcher;
|
|
11173
|
-
}
|
|
11174
|
-
const currentGroups = Array.isArray(config.hooks[eventKey]) ? config.hooks[eventKey] : [];
|
|
11175
|
-
config.hooks[eventKey] = [...currentGroups, group];
|
|
11176
|
-
await fs8.mkdir(path8.dirname(hooksPath), { recursive: true });
|
|
11177
|
-
await fs8.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
|
|
11178
|
-
`, "utf8");
|
|
11179
|
-
}
|
|
11180
|
-
function hookInputMatches(group, handler, input) {
|
|
11181
|
-
if (!isRecord4(group) || !isRecord4(handler)) {
|
|
11182
|
-
return false;
|
|
11183
|
-
}
|
|
11184
|
-
const matcher = typeof group.matcher === "string" ? group.matcher : null;
|
|
11185
|
-
const handlerCommand = typeof handler.command === "string" ? handler.command : "";
|
|
11186
|
-
const handlerTimeout = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : null;
|
|
11187
|
-
const handlerStatusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
|
|
11188
|
-
return handler.type === "command" && (input.matcher?.trim() || null) === matcher && input.command.trim() === handlerCommand && (input.timeoutSec ?? null) === handlerTimeout && (input.statusMessage?.trim() || null) === handlerStatusMessage;
|
|
11189
|
-
}
|
|
11190
|
-
async function updateHookJsonEntry({
|
|
11191
|
-
codexHome,
|
|
11192
|
-
workspacePath,
|
|
11193
|
-
input
|
|
11194
|
-
}) {
|
|
11195
|
-
validateHookInput(input);
|
|
11196
|
-
validateHookInput(input.target);
|
|
11197
|
-
if (input.scope !== input.target.scope) {
|
|
11198
|
-
throw new HttpError(400, {
|
|
11199
|
-
code: "bad_request",
|
|
11200
|
-
message: "Hook scope cannot be changed while editing."
|
|
11201
|
-
});
|
|
11202
|
-
}
|
|
11203
|
-
const hooksPath = input.scope === "global" ? path8.join(codexHome, "hooks.json") : path8.join(workspacePath, ".codex", "hooks.json");
|
|
11204
|
-
const config = await readJsonFileOrDefault(hooksPath);
|
|
11205
|
-
const targetEventKey = HOOK_EVENT_JSON_KEYS[input.target.eventName];
|
|
11206
|
-
const nextEventKey = HOOK_EVENT_JSON_KEYS[input.eventName];
|
|
11207
|
-
const currentGroups = Array.isArray(config.hooks[targetEventKey]) ? config.hooks[targetEventKey] : [];
|
|
11208
|
-
let replacementGroup = null;
|
|
11209
|
-
config.hooks[targetEventKey] = currentGroups.map((group) => {
|
|
11210
|
-
if (replacementGroup || !isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
11211
|
-
return group;
|
|
11212
|
-
}
|
|
11213
|
-
const hookIndex = group.hooks.findIndex(
|
|
11214
|
-
(handler2) => hookInputMatches(group, handler2, input.target)
|
|
11215
|
-
);
|
|
11216
|
-
if (hookIndex < 0) {
|
|
11217
|
-
return group;
|
|
11218
|
-
}
|
|
11219
|
-
const handler = {
|
|
11220
|
-
type: "command",
|
|
11221
|
-
command: input.command.trim()
|
|
11222
|
-
};
|
|
11223
|
-
if (input.timeoutSec !== void 0 && input.timeoutSec !== null) {
|
|
11224
|
-
handler.timeout = input.timeoutSec;
|
|
11225
|
-
}
|
|
11226
|
-
if (input.statusMessage?.trim()) {
|
|
11227
|
-
handler.statusMessage = input.statusMessage.trim();
|
|
11228
|
-
}
|
|
11229
|
-
replacementGroup = {
|
|
11230
|
-
hooks: [handler]
|
|
11231
|
-
};
|
|
11232
|
-
const matcher = input.matcher?.trim() || null;
|
|
11233
|
-
if (matcher) {
|
|
11234
|
-
replacementGroup.matcher = matcher;
|
|
11235
|
-
}
|
|
11236
|
-
if (targetEventKey !== nextEventKey) {
|
|
11237
|
-
const remainingHooks = group.hooks.filter((_, index) => index !== hookIndex);
|
|
11238
|
-
return {
|
|
11239
|
-
...group,
|
|
11240
|
-
hooks: remainingHooks
|
|
11241
|
-
};
|
|
11242
|
-
}
|
|
11243
|
-
return {
|
|
11244
|
-
...replacementGroup,
|
|
11245
|
-
hooks: group.hooks.map(
|
|
11246
|
-
(existing, index) => index === hookIndex ? replacementGroup.hooks[0] : existing
|
|
11247
|
-
)
|
|
11248
|
-
};
|
|
11249
|
-
}).filter((group) => {
|
|
11250
|
-
if (!isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
11251
|
-
return true;
|
|
11252
|
-
}
|
|
11253
|
-
return group.hooks.length > 0;
|
|
11254
|
-
});
|
|
11255
|
-
if (!replacementGroup) {
|
|
11256
|
-
throw new HttpError(404, {
|
|
11257
|
-
code: "not_found",
|
|
11258
|
-
message: "Hook was not found in hooks.json."
|
|
11259
|
-
});
|
|
11260
|
-
}
|
|
11261
|
-
if (targetEventKey !== nextEventKey) {
|
|
11262
|
-
if (config.hooks[targetEventKey]?.length === 0) {
|
|
11263
|
-
delete config.hooks[targetEventKey];
|
|
11264
|
-
}
|
|
11265
|
-
const nextGroups = Array.isArray(config.hooks[nextEventKey]) ? config.hooks[nextEventKey] : [];
|
|
11266
|
-
config.hooks[nextEventKey] = [...nextGroups, replacementGroup];
|
|
11267
|
-
}
|
|
11268
|
-
await fs8.mkdir(path8.dirname(hooksPath), { recursive: true });
|
|
11269
|
-
await fs8.writeFile(hooksPath, `${JSON.stringify(config, null, 2)}
|
|
11270
|
-
`, "utf8");
|
|
11271
|
-
}
|
|
11272
|
-
async function readLocalHookDtos({
|
|
11273
|
-
hooksPath,
|
|
11274
|
-
source,
|
|
11275
|
-
displayOffset
|
|
11276
|
-
}) {
|
|
11277
|
-
const config = await readJsonFileOrDefault(hooksPath);
|
|
11278
|
-
const hooks = [];
|
|
11279
|
-
for (const [eventKey, groups] of Object.entries(config.hooks)) {
|
|
11280
|
-
const eventName = HOOK_EVENT_DTO_KEYS[eventKey];
|
|
11281
|
-
if (!eventName || !Array.isArray(groups)) {
|
|
11282
|
-
continue;
|
|
11283
|
-
}
|
|
11284
|
-
groups.forEach((group, groupIndex) => {
|
|
11285
|
-
if (!isRecord4(group) || !Array.isArray(group.hooks)) {
|
|
11286
|
-
return;
|
|
11287
|
-
}
|
|
11288
|
-
const matcher = typeof group.matcher === "string" ? group.matcher : null;
|
|
11289
|
-
group.hooks.forEach((handler, handlerIndex) => {
|
|
11290
|
-
if (!isRecord4(handler) || handler.type !== "command") {
|
|
11291
|
-
return;
|
|
11292
|
-
}
|
|
11293
|
-
const command = typeof handler.command === "string" ? handler.command : null;
|
|
11294
|
-
if (!command) {
|
|
11295
|
-
return;
|
|
11296
|
-
}
|
|
11297
|
-
const timeoutSec = typeof handler.timeout === "number" && Number.isFinite(handler.timeout) ? handler.timeout : 600;
|
|
11298
|
-
const statusMessage = typeof handler.statusMessage === "string" ? handler.statusMessage : null;
|
|
11299
|
-
const key = `${source}:${hooksPath}:${eventKey}:${groupIndex}:${handlerIndex}`;
|
|
11300
|
-
hooks.push({
|
|
11301
|
-
key,
|
|
11302
|
-
eventName,
|
|
11303
|
-
handlerType: "command",
|
|
11304
|
-
matcher,
|
|
11305
|
-
command,
|
|
11306
|
-
timeoutSec,
|
|
11307
|
-
statusMessage,
|
|
11308
|
-
sourcePath: hooksPath,
|
|
11309
|
-
source,
|
|
11310
|
-
pluginId: null,
|
|
11311
|
-
displayOrder: displayOffset + hooks.length,
|
|
11312
|
-
enabled: true,
|
|
11313
|
-
isManaged: false,
|
|
11314
|
-
currentHash: "",
|
|
11315
|
-
trustStatus: "untrusted"
|
|
11316
|
-
});
|
|
11317
|
-
});
|
|
11318
|
-
});
|
|
11319
|
-
}
|
|
11320
|
-
return hooks;
|
|
11321
|
-
}
|
|
11322
|
-
function hookMatchesInput(hook, input) {
|
|
11323
|
-
return hook.source === input.scope && hook.eventName === input.eventName && (hook.matcher ?? null) === (input.matcher ?? null) && hook.command === input.command && (input.timeoutSec == null || hook.timeoutSec === input.timeoutSec) && (hook.statusMessage ?? null) === (input.statusMessage ?? null);
|
|
11324
|
-
}
|
|
11325
|
-
async function findOfficialHookForInput(runtime, workspacePath, input) {
|
|
11326
|
-
if (!runtime.listHooks) {
|
|
11327
|
-
return null;
|
|
11328
|
-
}
|
|
11329
|
-
const [entry] = await runtime.listHooks({
|
|
11330
|
-
cwds: [workspacePath]
|
|
11331
|
-
});
|
|
11332
|
-
const officialHooks = (entry?.hooks ?? []).map((hook) => ({
|
|
11333
|
-
key: hook.key,
|
|
11334
|
-
eventName: hook.eventName,
|
|
11335
|
-
handlerType: hook.handlerType,
|
|
11336
|
-
matcher: hook.matcher,
|
|
11337
|
-
command: hook.command,
|
|
11338
|
-
timeoutSec: hook.timeoutSec,
|
|
11339
|
-
statusMessage: hook.statusMessage,
|
|
11340
|
-
sourcePath: hook.sourcePath,
|
|
11341
|
-
source: hook.source,
|
|
11342
|
-
pluginId: hook.pluginId,
|
|
11343
|
-
displayOrder: hook.displayOrder,
|
|
11344
|
-
enabled: hook.enabled,
|
|
11345
|
-
isManaged: hook.isManaged,
|
|
11346
|
-
currentHash: hook.currentHash,
|
|
11347
|
-
trustStatus: hook.trustStatus
|
|
11348
|
-
}));
|
|
11349
|
-
return officialHooks.find((hook) => hookMatchesInput(hook, input)) ?? null;
|
|
11350
|
-
}
|
|
11351
|
-
async function trustHookForInput(runtime, workspacePath, input) {
|
|
11352
|
-
const hook = await findOfficialHookForInput(runtime, workspacePath, input);
|
|
11353
|
-
if (!runtime.setHookTrust || !hook || !hook.key || !hook.currentHash || hook.isManaged) {
|
|
11354
|
-
return;
|
|
11355
|
-
}
|
|
11356
|
-
await runtime.setHookTrust({
|
|
11357
|
-
key: hook.key,
|
|
11358
|
-
trustedHash: hook.currentHash
|
|
11359
|
-
});
|
|
11540
|
+
return path9.join(workspacePath, ".temp", "threads", localThreadId);
|
|
11360
11541
|
}
|
|
11361
11542
|
function buildTurnDto(turn, metadata) {
|
|
11362
11543
|
const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
|
|
@@ -11416,13 +11597,13 @@ function safeTranscriptExportFileName(title, extension) {
|
|
|
11416
11597
|
return `remote-codex-${stem || "thread"}-${timestamp}.${extension}`;
|
|
11417
11598
|
}
|
|
11418
11599
|
var ThreadService = class {
|
|
11419
|
-
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot,
|
|
11600
|
+
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, codexManagement) {
|
|
11420
11601
|
this.db = db;
|
|
11421
11602
|
this.agentRuntimes = agentRuntimes;
|
|
11422
11603
|
this.eventBus = eventBus;
|
|
11423
11604
|
this.localSessionStore = localSessionStore;
|
|
11424
11605
|
this.workspaceRoot = workspaceRoot;
|
|
11425
|
-
this.
|
|
11606
|
+
this.codexManagement = codexManagement;
|
|
11426
11607
|
for (const runtime of this.agentRuntimes.all()) {
|
|
11427
11608
|
runtime.on("event", (event) => {
|
|
11428
11609
|
void this.handleRuntimeEvent(event);
|
|
@@ -11437,7 +11618,7 @@ var ThreadService = class {
|
|
|
11437
11618
|
eventBus;
|
|
11438
11619
|
localSessionStore;
|
|
11439
11620
|
workspaceRoot;
|
|
11440
|
-
|
|
11621
|
+
codexManagement;
|
|
11441
11622
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
11442
11623
|
dismissedPlanDecisionTurns = /* @__PURE__ */ new Map();
|
|
11443
11624
|
threadDetailCache = /* @__PURE__ */ new Map();
|
|
@@ -11497,13 +11678,13 @@ var ThreadService = class {
|
|
|
11497
11678
|
return this.providerForRecord({ provider }) === "codex";
|
|
11498
11679
|
}
|
|
11499
11680
|
runtimeSupportsFastMode(provider) {
|
|
11500
|
-
return this.runtimeForProvider(provider).capabilities.controls.
|
|
11681
|
+
return this.runtimeForProvider(provider).capabilities.controls.performanceMode;
|
|
11501
11682
|
}
|
|
11502
11683
|
fastModeForProvider(provider, fastMode) {
|
|
11503
11684
|
return this.runtimeSupportsFastMode(provider) ? normalizeFastMode(fastMode) : false;
|
|
11504
11685
|
}
|
|
11505
|
-
|
|
11506
|
-
return
|
|
11686
|
+
performanceModeForRecord(record) {
|
|
11687
|
+
return performanceModeForFastMode(this.fastModeForProvider(record.provider, record.fastMode));
|
|
11507
11688
|
}
|
|
11508
11689
|
assertCodexHooksFileManagement(provider) {
|
|
11509
11690
|
if (!this.isCodexProvider(provider)) {
|
|
@@ -11740,7 +11921,7 @@ var ThreadService = class {
|
|
|
11740
11921
|
const matchedModel = modelRecords.find((entry) => entry.model === input.model);
|
|
11741
11922
|
const reasoningEffort = normalizeReasoningEffort(matchedModel?.defaultReasoningEffort) ?? "medium";
|
|
11742
11923
|
const sandboxMode = defaultSandboxModeForApprovalMode(input.approvalMode);
|
|
11743
|
-
const fastMode = this.runtimeSupportsFastMode(provider) ?
|
|
11924
|
+
const fastMode = this.runtimeSupportsFastMode(provider) ? this.codexManagement.readFastMode() : false;
|
|
11744
11925
|
if (this.runtimeSupportsFastMode(provider)) {
|
|
11745
11926
|
ensureFastModeSupported(input.model, fastMode);
|
|
11746
11927
|
}
|
|
@@ -11749,7 +11930,7 @@ var ThreadService = class {
|
|
|
11749
11930
|
model: input.model,
|
|
11750
11931
|
approvalMode: input.approvalMode,
|
|
11751
11932
|
sandboxMode,
|
|
11752
|
-
|
|
11933
|
+
performanceMode: performanceModeForFastMode(fastMode)
|
|
11753
11934
|
});
|
|
11754
11935
|
const created = createThreadRecord(this.db, {
|
|
11755
11936
|
workspaceId: workspace.id,
|
|
@@ -11804,7 +11985,7 @@ var ThreadService = class {
|
|
|
11804
11985
|
if (!workspace) {
|
|
11805
11986
|
workspace = createWorkspaceRecord(this.db, {
|
|
11806
11987
|
absPath: importedPath,
|
|
11807
|
-
label:
|
|
11988
|
+
label: path9.basename(importedPath) || "workspace"
|
|
11808
11989
|
});
|
|
11809
11990
|
}
|
|
11810
11991
|
const created = createThreadRecord(this.db, {
|
|
@@ -11820,7 +12001,7 @@ var ThreadService = class {
|
|
|
11820
12001
|
approvalMode: "yolo",
|
|
11821
12002
|
sandboxMode: defaultSandboxModeForApprovalMode("yolo"),
|
|
11822
12003
|
summaryText: localSession.turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage")?.text ?? null,
|
|
11823
|
-
fastMode:
|
|
12004
|
+
fastMode: this.codexManagement.readFastMode(),
|
|
11824
12005
|
source: "local_codex_import",
|
|
11825
12006
|
isConnected: false
|
|
11826
12007
|
});
|
|
@@ -11869,8 +12050,8 @@ var ThreadService = class {
|
|
|
11869
12050
|
cachedDetail.turns,
|
|
11870
12051
|
pagedTurns.turns
|
|
11871
12052
|
);
|
|
11872
|
-
const goal = await this.getThreadGoalForRecord(updated).catch(() => null);
|
|
11873
12053
|
const goalHistory = this.listThreadGoalHistory(updated.id);
|
|
12054
|
+
const goal = await this.getThreadGoalForRecord(updated).catch(() => null) ?? localGoalSnapshotForFallback(goalHistory);
|
|
11874
12055
|
return {
|
|
11875
12056
|
thread: this.toThreadDto(updated, loadedIds),
|
|
11876
12057
|
workspace: toWorkspaceDto(workspace),
|
|
@@ -12010,7 +12191,7 @@ var ThreadService = class {
|
|
|
12010
12191
|
});
|
|
12011
12192
|
}
|
|
12012
12193
|
this.requireProviderSessionId(record);
|
|
12013
|
-
return this.getThreadGoalForRecord(record, { allowEnableFeature: true });
|
|
12194
|
+
return await this.getThreadGoalForRecord(record, { allowEnableFeature: true }) ?? localGoalSnapshotForFallback(this.listThreadGoalHistory(localThreadId));
|
|
12014
12195
|
}
|
|
12015
12196
|
async updateThreadGoal(localThreadId, input) {
|
|
12016
12197
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
@@ -12037,14 +12218,32 @@ var ThreadService = class {
|
|
|
12037
12218
|
try {
|
|
12038
12219
|
await this.ensureGoalsFeatureEnabled(record.provider);
|
|
12039
12220
|
await this.ensureThreadLoadedForCodexOperation(record);
|
|
12040
|
-
const
|
|
12221
|
+
const activeGoal = this.listThreadGoalHistory(localThreadId).find(
|
|
12222
|
+
(goal2) => ["active", "paused", "budgetLimited"].includes(goal2.status)
|
|
12223
|
+
) ?? null;
|
|
12224
|
+
const creatingNewGoal = goalObjectiveChanged(activeGoal, input.objective);
|
|
12225
|
+
if (creatingNewGoal) {
|
|
12226
|
+
markActiveThreadGoalRecordTerminated(this.db, localThreadId);
|
|
12227
|
+
}
|
|
12228
|
+
if (input.status === "terminated") {
|
|
12229
|
+
const terminatedGoal = markActiveThreadGoalRecordTerminated(this.db, localThreadId);
|
|
12230
|
+
const goalHistory = this.listThreadGoalHistory(localThreadId);
|
|
12231
|
+
const goal2 = terminatedGoal ? toThreadGoalDtoFromRecord(terminatedGoal) : goalHistory[0] ?? null;
|
|
12232
|
+
this.emitThreadEvent("thread.goal.updated", localThreadId, {
|
|
12233
|
+
goal: goal2,
|
|
12234
|
+
goalHistory
|
|
12235
|
+
});
|
|
12236
|
+
return goal2;
|
|
12237
|
+
}
|
|
12238
|
+
const upstreamStatus = input.status;
|
|
12041
12239
|
const goal = await runtime.setGoal({
|
|
12042
12240
|
providerSessionId,
|
|
12043
12241
|
...input.objective !== void 0 ? { objective: input.objective } : {},
|
|
12044
12242
|
...upstreamStatus !== void 0 ? { status: upstreamStatus } : {},
|
|
12045
12243
|
...input.tokenBudget !== void 0 ? { tokenBudget: input.tokenBudget } : {}
|
|
12046
12244
|
});
|
|
12047
|
-
const
|
|
12245
|
+
const upstreamDto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
|
|
12246
|
+
const dto = creatingNewGoal ? resetGoalProgress(upstreamDto) : upstreamDto;
|
|
12048
12247
|
const persistedGoal = toThreadGoalDtoFromRecord(
|
|
12049
12248
|
this.persistThreadGoalSnapshot(localThreadId, dto)
|
|
12050
12249
|
);
|
|
@@ -12054,7 +12253,7 @@ var ThreadService = class {
|
|
|
12054
12253
|
});
|
|
12055
12254
|
return persistedGoal;
|
|
12056
12255
|
} catch (error) {
|
|
12057
|
-
|
|
12256
|
+
this.codexManagement.mapGoalError(error);
|
|
12058
12257
|
}
|
|
12059
12258
|
}
|
|
12060
12259
|
async clearThreadGoal(localThreadId) {
|
|
@@ -12088,7 +12287,7 @@ var ThreadService = class {
|
|
|
12088
12287
|
this.emitThreadEvent("thread.goal.cleared", localThreadId, { goalHistory });
|
|
12089
12288
|
return { cleared, goalHistory };
|
|
12090
12289
|
} catch (error) {
|
|
12091
|
-
|
|
12290
|
+
this.codexManagement.mapGoalError(error);
|
|
12092
12291
|
}
|
|
12093
12292
|
}
|
|
12094
12293
|
async getThreadGoalForRecord(record, options = {}) {
|
|
@@ -12109,9 +12308,16 @@ var ThreadService = class {
|
|
|
12109
12308
|
return null;
|
|
12110
12309
|
}
|
|
12111
12310
|
const dto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
|
|
12311
|
+
const localGoal = localGoalSnapshotToPreserve(
|
|
12312
|
+
this.listThreadGoalHistory(record.id),
|
|
12313
|
+
dto
|
|
12314
|
+
);
|
|
12315
|
+
if (localGoal) {
|
|
12316
|
+
return localGoal;
|
|
12317
|
+
}
|
|
12112
12318
|
return toThreadGoalDtoFromRecord(this.persistThreadGoalSnapshot(record.id, dto));
|
|
12113
12319
|
} catch (error) {
|
|
12114
|
-
if (
|
|
12320
|
+
if (this.codexManagement.isRuntimeRequestError(error)) {
|
|
12115
12321
|
return null;
|
|
12116
12322
|
}
|
|
12117
12323
|
throw error;
|
|
@@ -12144,29 +12350,20 @@ var ThreadService = class {
|
|
|
12144
12350
|
deduped.set(key, existing ? mergeGoalHistoryEntry(existing, goal) : goal);
|
|
12145
12351
|
}
|
|
12146
12352
|
return [...deduped.values()].sort(
|
|
12147
|
-
(left, right) =>
|
|
12353
|
+
(left, right) => {
|
|
12354
|
+
const updatedDelta = Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
|
|
12355
|
+
if (updatedDelta !== 0) {
|
|
12356
|
+
return updatedDelta;
|
|
12357
|
+
}
|
|
12358
|
+
return goalHistoryStatusRank(left.status) - goalHistoryStatusRank(right.status);
|
|
12359
|
+
}
|
|
12148
12360
|
);
|
|
12149
12361
|
}
|
|
12150
12362
|
async ensureGoalsFeatureEnabled(provider) {
|
|
12151
12363
|
if (!this.isCodexProvider(provider)) {
|
|
12152
12364
|
return;
|
|
12153
12365
|
}
|
|
12154
|
-
|
|
12155
|
-
if (await readCodexFeatureFlag(this.codexHome, "goals")) {
|
|
12156
|
-
return;
|
|
12157
|
-
}
|
|
12158
|
-
await writeCodexFeatureFlag(this.codexHome, "goals", true);
|
|
12159
|
-
await this.codexRuntime().stop();
|
|
12160
|
-
await this.codexRuntime().start();
|
|
12161
|
-
} catch (error) {
|
|
12162
|
-
if (isCodexRuntimeRequestError(error)) {
|
|
12163
|
-
throw new HttpError(409, {
|
|
12164
|
-
code: "goal_feature_disabled",
|
|
12165
|
-
message: GOAL_FEATURE_DISABLED_MESSAGE
|
|
12166
|
-
});
|
|
12167
|
-
}
|
|
12168
|
-
throw error;
|
|
12169
|
-
}
|
|
12366
|
+
await this.codexManagement.ensureGoalsFeatureEnabled(this.codexRuntime());
|
|
12170
12367
|
}
|
|
12171
12368
|
async ensureThreadLoadedForCodexOperation(record) {
|
|
12172
12369
|
if (!record.providerSessionId) {
|
|
@@ -12244,7 +12441,7 @@ var ThreadService = class {
|
|
|
12244
12441
|
});
|
|
12245
12442
|
}
|
|
12246
12443
|
const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
|
|
12247
|
-
await
|
|
12444
|
+
await fs9.mkdir(tempDirectory, { recursive: true });
|
|
12248
12445
|
let rewrittenPrompt = input.prompt;
|
|
12249
12446
|
for (const attachment of attachments) {
|
|
12250
12447
|
if (!rewrittenPrompt.includes(attachment.manifest.placeholder)) {
|
|
@@ -12254,7 +12451,7 @@ var ThreadService = class {
|
|
|
12254
12451
|
});
|
|
12255
12452
|
}
|
|
12256
12453
|
const savedFileName = sanitizeAttachmentFileName(attachment.manifest.originalName);
|
|
12257
|
-
await
|
|
12454
|
+
await fs9.writeFile(path9.join(tempDirectory, savedFileName), attachment.buffer);
|
|
12258
12455
|
const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
|
|
12259
12456
|
const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
|
|
12260
12457
|
rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
|
|
@@ -12286,7 +12483,7 @@ var ThreadService = class {
|
|
|
12286
12483
|
providerSessionId,
|
|
12287
12484
|
model: input.model ?? record.model ?? null,
|
|
12288
12485
|
sandboxMode,
|
|
12289
|
-
|
|
12486
|
+
performanceMode: performanceModeForFastMode(fastMode)
|
|
12290
12487
|
});
|
|
12291
12488
|
} catch (error) {
|
|
12292
12489
|
if (!isRemoteThreadBootstrapError(error)) {
|
|
@@ -12391,7 +12588,7 @@ var ThreadService = class {
|
|
|
12391
12588
|
const sandboxMode = (input.sandboxMode !== void 0 ? normalizeSandboxMode(input.sandboxMode) : normalizeSandboxMode(record.sandboxMode)) ?? defaultSandboxModeForApprovalMode(record.approvalMode ?? "yolo");
|
|
12392
12589
|
const fastMode = this.fastModeForProvider(record.provider, record.fastMode);
|
|
12393
12590
|
ensureFastModeSupported(effectiveModel, fastMode);
|
|
12394
|
-
const
|
|
12591
|
+
const performanceMode = performanceModeForFastMode(fastMode);
|
|
12395
12592
|
const connectedRecord = {
|
|
12396
12593
|
...record,
|
|
12397
12594
|
providerSessionId
|
|
@@ -12408,7 +12605,7 @@ var ThreadService = class {
|
|
|
12408
12605
|
normalizedReasoning,
|
|
12409
12606
|
collaborationMode,
|
|
12410
12607
|
sandboxMode,
|
|
12411
|
-
|
|
12608
|
+
performanceMode,
|
|
12412
12609
|
workspacePath: workspace.absPath
|
|
12413
12610
|
});
|
|
12414
12611
|
}
|
|
@@ -12418,26 +12615,27 @@ var ThreadService = class {
|
|
|
12418
12615
|
normalizedReasoning,
|
|
12419
12616
|
collaborationMode,
|
|
12420
12617
|
sandboxMode,
|
|
12421
|
-
|
|
12618
|
+
performanceMode,
|
|
12422
12619
|
workspacePath: workspace.absPath
|
|
12423
12620
|
});
|
|
12424
12621
|
}
|
|
12425
12622
|
async startPromptTurn(localThreadId, record, input) {
|
|
12426
12623
|
const runtime = this.runtimeForProvider(record.provider);
|
|
12427
12624
|
const modelRecords = await runtime.listModels().catch(() => []);
|
|
12428
|
-
ensureFastModeSupported(input.effectiveModel, input.
|
|
12625
|
+
ensureFastModeSupported(input.effectiveModel, input.performanceMode === "fast");
|
|
12429
12626
|
const pricingSnapshot = buildTurnPricingSnapshot(
|
|
12430
12627
|
input.effectiveModel,
|
|
12431
|
-
input.
|
|
12628
|
+
input.performanceMode === "fast"
|
|
12432
12629
|
);
|
|
12433
12630
|
const turn = await runtime.startTurn({
|
|
12434
12631
|
providerSessionId: record.providerSessionId,
|
|
12435
12632
|
prompt: input.prompt,
|
|
12436
12633
|
model: input.effectiveModel,
|
|
12437
|
-
|
|
12634
|
+
performanceMode: input.performanceMode,
|
|
12438
12635
|
reasoningEffort: input.normalizedReasoning,
|
|
12439
12636
|
collaborationMode: input.collaborationMode,
|
|
12440
|
-
|
|
12637
|
+
sandboxMode: input.sandboxMode,
|
|
12638
|
+
workspacePath: input.workspacePath
|
|
12441
12639
|
});
|
|
12442
12640
|
upsertThreadTurnMetadata(this.db, {
|
|
12443
12641
|
threadId: localThreadId,
|
|
@@ -12525,7 +12723,7 @@ var ThreadService = class {
|
|
|
12525
12723
|
normalizedReasoning: input.normalizedReasoning,
|
|
12526
12724
|
collaborationMode: input.collaborationMode,
|
|
12527
12725
|
sandboxMode: input.sandboxMode,
|
|
12528
|
-
|
|
12726
|
+
performanceMode: input.performanceMode,
|
|
12529
12727
|
workspacePath: input.workspacePath
|
|
12530
12728
|
});
|
|
12531
12729
|
}
|
|
@@ -12559,7 +12757,7 @@ var ThreadService = class {
|
|
|
12559
12757
|
this.clearPendingPlanDecisionRequests(localThreadId, true);
|
|
12560
12758
|
}
|
|
12561
12759
|
if (supportsFastMode2 && currentFastMode !== nextFastMode) {
|
|
12562
|
-
await
|
|
12760
|
+
await this.codexManagement.writeFastMode(nextFastMode);
|
|
12563
12761
|
this.appendActivityNote(localThreadId, {
|
|
12564
12762
|
kind: "fastMode",
|
|
12565
12763
|
text: nextFastMode ? FAST_MODE_NOTE_ON : FAST_MODE_NOTE_OFF
|
|
@@ -12912,12 +13110,7 @@ var ThreadService = class {
|
|
|
12912
13110
|
});
|
|
12913
13111
|
}
|
|
12914
13112
|
this.assertCodexHooksFileManagement(record.provider);
|
|
12915
|
-
await
|
|
12916
|
-
codexHome: this.codexHome,
|
|
12917
|
-
workspacePath: workspace.absPath,
|
|
12918
|
-
input
|
|
12919
|
-
});
|
|
12920
|
-
await trustHookForInput(runtime, workspace.absPath, input);
|
|
13113
|
+
await this.codexManagement.writeHookEntry(runtime, workspace.absPath, input);
|
|
12921
13114
|
return this.listThreadHooks(localThreadId);
|
|
12922
13115
|
}
|
|
12923
13116
|
async updateThreadHook(localThreadId, input) {
|
|
@@ -12943,12 +13136,7 @@ var ThreadService = class {
|
|
|
12943
13136
|
});
|
|
12944
13137
|
}
|
|
12945
13138
|
this.assertCodexHooksFileManagement(record.provider);
|
|
12946
|
-
await
|
|
12947
|
-
codexHome: this.codexHome,
|
|
12948
|
-
workspacePath: workspace.absPath,
|
|
12949
|
-
input
|
|
12950
|
-
});
|
|
12951
|
-
await trustHookForInput(runtime, workspace.absPath, input);
|
|
13139
|
+
await this.codexManagement.updateHookEntry(runtime, workspace.absPath, input);
|
|
12952
13140
|
return this.listThreadHooks(localThreadId);
|
|
12953
13141
|
}
|
|
12954
13142
|
async trustThreadHook(localThreadId, input) {
|
|
@@ -13051,7 +13239,7 @@ var ThreadService = class {
|
|
|
13051
13239
|
const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
|
|
13052
13240
|
if (workspace) {
|
|
13053
13241
|
const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
|
|
13054
|
-
await
|
|
13242
|
+
await fs9.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
|
|
13055
13243
|
});
|
|
13056
13244
|
}
|
|
13057
13245
|
this.pendingRequests.delete(localThreadId);
|
|
@@ -13226,7 +13414,7 @@ var ThreadService = class {
|
|
|
13226
13414
|
if (!record) {
|
|
13227
13415
|
return;
|
|
13228
13416
|
}
|
|
13229
|
-
const tokenUsage =
|
|
13417
|
+
const tokenUsage = isRecord5(event.usage) ? event.usage : null;
|
|
13230
13418
|
const usage = buildThreadContextUsageFromPayload(
|
|
13231
13419
|
tokenUsage,
|
|
13232
13420
|
record.model
|
|
@@ -13242,7 +13430,7 @@ var ThreadService = class {
|
|
|
13242
13430
|
);
|
|
13243
13431
|
const previousCumulativeTotal = this.threadCumulativeTokenUsage.get(record.id);
|
|
13244
13432
|
const currentCumulativeTotal = buildTurnTokenBreakdown(
|
|
13245
|
-
|
|
13433
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
13246
13434
|
);
|
|
13247
13435
|
if (currentCumulativeTotal) {
|
|
13248
13436
|
this.threadCumulativeTokenUsage.set(record.id, currentCumulativeTotal);
|
|
@@ -13412,10 +13600,22 @@ var ThreadService = class {
|
|
|
13412
13600
|
if (!record) {
|
|
13413
13601
|
return;
|
|
13414
13602
|
}
|
|
13415
|
-
this.recordTurnItemOrder(
|
|
13603
|
+
const sequence = this.recordTurnItemOrder(
|
|
13604
|
+
record.id,
|
|
13605
|
+
event.providerTurnId,
|
|
13606
|
+
event.itemId
|
|
13607
|
+
);
|
|
13608
|
+
this.appendLiveAgentMessageDelta(
|
|
13609
|
+
record.id,
|
|
13610
|
+
event.providerTurnId,
|
|
13611
|
+
event.itemId,
|
|
13612
|
+
event.delta,
|
|
13613
|
+
sequence
|
|
13614
|
+
);
|
|
13416
13615
|
this.emitThreadEvent("thread.output.delta", record.id, {
|
|
13417
13616
|
turnId: event.providerTurnId,
|
|
13418
13617
|
itemId: event.itemId,
|
|
13618
|
+
sequence,
|
|
13419
13619
|
delta: event.delta
|
|
13420
13620
|
});
|
|
13421
13621
|
return;
|
|
@@ -13489,7 +13689,7 @@ var ThreadService = class {
|
|
|
13489
13689
|
const defaultMappedRequest = runtime.mapProviderRequest?.(request, {
|
|
13490
13690
|
approvalMode: "guarded"
|
|
13491
13691
|
});
|
|
13492
|
-
const providerSessionIdFromParams =
|
|
13692
|
+
const providerSessionIdFromParams = isRecord5(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
|
|
13493
13693
|
const providerSessionId = defaultMappedRequest?.providerSessionId ?? (typeof providerSessionIdFromParams === "string" ? providerSessionIdFromParams : null);
|
|
13494
13694
|
const record = providerSessionId ? this.findRecordByProviderSessionId(request.provider, providerSessionId) : null;
|
|
13495
13695
|
if (!record) {
|
|
@@ -13658,9 +13858,32 @@ var ThreadService = class {
|
|
|
13658
13858
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13659
13859
|
});
|
|
13660
13860
|
}
|
|
13861
|
+
appendLiveAgentMessageDelta(localThreadId, turnId, itemId, delta, sequence) {
|
|
13862
|
+
const current = this.threadLiveItems.get(localThreadId);
|
|
13863
|
+
const currentItems = current?.turnId === turnId ? current.items : [];
|
|
13864
|
+
const existing = currentItems.find((entry) => entry.id === itemId);
|
|
13865
|
+
const nextItem = existing?.kind === "agentMessage" ? {
|
|
13866
|
+
...existing,
|
|
13867
|
+
text: `${existing.text}${delta}`,
|
|
13868
|
+
sequence
|
|
13869
|
+
} : {
|
|
13870
|
+
id: itemId,
|
|
13871
|
+
kind: "agentMessage",
|
|
13872
|
+
text: delta,
|
|
13873
|
+
sequence
|
|
13874
|
+
};
|
|
13875
|
+
this.persistLiveHistoryItem(localThreadId, turnId, nextItem);
|
|
13876
|
+
this.setLiveItems(localThreadId, {
|
|
13877
|
+
turnId,
|
|
13878
|
+
items: sortHistoryItemsBySequence([
|
|
13879
|
+
...currentItems.filter((entry) => entry.id !== itemId),
|
|
13880
|
+
nextItem
|
|
13881
|
+
]),
|
|
13882
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13883
|
+
});
|
|
13884
|
+
}
|
|
13661
13885
|
async toThreadHooksDto(provider, workspacePath, entry, fallbackWarnings = []) {
|
|
13662
|
-
const globalHooksPath =
|
|
13663
|
-
const projectHooksPath = path8.join(workspacePath, ".codex", "hooks.json");
|
|
13886
|
+
const { globalHooksPath, projectHooksPath } = this.codexManagement.hooksPaths(workspacePath);
|
|
13664
13887
|
const officialHooks = (entry?.hooks ?? []).map((hook) => ({
|
|
13665
13888
|
key: hook.key,
|
|
13666
13889
|
eventName: hook.eventName,
|
|
@@ -13679,12 +13902,12 @@ var ThreadService = class {
|
|
|
13679
13902
|
trustStatus: hook.trustStatus
|
|
13680
13903
|
}));
|
|
13681
13904
|
const [globalHooks, projectHooks] = this.isCodexProvider(provider) ? await Promise.all([
|
|
13682
|
-
readLocalHookDtos({
|
|
13905
|
+
this.codexManagement.readLocalHookDtos({
|
|
13683
13906
|
hooksPath: globalHooksPath,
|
|
13684
13907
|
source: "user",
|
|
13685
13908
|
displayOffset: officialHooks.length
|
|
13686
13909
|
}),
|
|
13687
|
-
readLocalHookDtos({
|
|
13910
|
+
this.codexManagement.readLocalHookDtos({
|
|
13688
13911
|
hooksPath: projectHooksPath,
|
|
13689
13912
|
source: "project",
|
|
13690
13913
|
displayOffset: officialHooks.length + 1e4
|
|
@@ -13719,11 +13942,17 @@ var ThreadService = class {
|
|
|
13719
13942
|
return null;
|
|
13720
13943
|
}
|
|
13721
13944
|
const matchingTurn = turns.find((turn) => turn.id === current.turnId);
|
|
13722
|
-
const
|
|
13723
|
-
matchingTurn?.items.map((item) => item.id) ?? []
|
|
13945
|
+
const materializedItemsById = new Map(
|
|
13946
|
+
matchingTurn?.items.map((item) => [item.id, item]) ?? []
|
|
13724
13947
|
);
|
|
13725
13948
|
const nextItems = current.items.filter(
|
|
13726
|
-
(item) =>
|
|
13949
|
+
(item) => {
|
|
13950
|
+
const materializedItem = materializedItemsById.get(item.id);
|
|
13951
|
+
if (!materializedItem) {
|
|
13952
|
+
return true;
|
|
13953
|
+
}
|
|
13954
|
+
return typeof item.sequence === "number" && Number.isFinite(item.sequence) && materializedItem.sequence !== item.sequence;
|
|
13955
|
+
}
|
|
13727
13956
|
);
|
|
13728
13957
|
if (nextItems.length === current.items.length) {
|
|
13729
13958
|
return current;
|
|
@@ -14266,8 +14495,8 @@ async function registerSystemRoutes(app2) {
|
|
|
14266
14495
|
}
|
|
14267
14496
|
|
|
14268
14497
|
// src/routes/threads.ts
|
|
14269
|
-
import
|
|
14270
|
-
import
|
|
14498
|
+
import fs10 from "fs/promises";
|
|
14499
|
+
import path10 from "path";
|
|
14271
14500
|
import { z as z5 } from "zod";
|
|
14272
14501
|
var createThreadSchema = z5.object({
|
|
14273
14502
|
workspaceId: z5.string().uuid(),
|
|
@@ -14304,7 +14533,7 @@ var updateThreadSettingsSchema = z5.object({
|
|
|
14304
14533
|
});
|
|
14305
14534
|
var updateThreadGoalSchema = z5.object({
|
|
14306
14535
|
objective: z5.string().min(1).nullable().optional(),
|
|
14307
|
-
status: z5.enum(["active", "paused", "budgetLimited", "complete"]).nullable().optional(),
|
|
14536
|
+
status: z5.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
|
|
14308
14537
|
tokenBudget: z5.number().int().positive().nullable().optional()
|
|
14309
14538
|
}).refine((body) => Object.keys(body).length > 0, {
|
|
14310
14539
|
message: "At least one goal field must be provided."
|
|
@@ -14626,23 +14855,23 @@ async function registerThreadRoutes(app2) {
|
|
|
14626
14855
|
message: "Workspace was not found for this thread."
|
|
14627
14856
|
});
|
|
14628
14857
|
}
|
|
14629
|
-
const candidatePath =
|
|
14630
|
-
const requestedPath = await
|
|
14858
|
+
const candidatePath = path10.isAbsolute(query.path) ? query.path : path10.resolve(workspace.absPath, query.path);
|
|
14859
|
+
const requestedPath = await fs10.realpath(candidatePath).catch(() => null);
|
|
14631
14860
|
if (!requestedPath) {
|
|
14632
14861
|
throw new HttpError(404, {
|
|
14633
14862
|
code: "not_found",
|
|
14634
14863
|
message: "Image file was not found."
|
|
14635
14864
|
});
|
|
14636
14865
|
}
|
|
14637
|
-
const resolvedWorkspaceRoot = await
|
|
14638
|
-
const workspacePrefix = resolvedWorkspaceRoot.endsWith(
|
|
14866
|
+
const resolvedWorkspaceRoot = await fs10.realpath(app2.services.config.workspaceRoot).catch(() => path10.resolve(app2.services.config.workspaceRoot));
|
|
14867
|
+
const workspacePrefix = resolvedWorkspaceRoot.endsWith(path10.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path10.sep}`;
|
|
14639
14868
|
if (requestedPath !== resolvedWorkspaceRoot && !requestedPath.startsWith(workspacePrefix)) {
|
|
14640
14869
|
throw new HttpError(403, {
|
|
14641
14870
|
code: "forbidden",
|
|
14642
14871
|
message: "Image path must stay within the configured workspace root."
|
|
14643
14872
|
});
|
|
14644
14873
|
}
|
|
14645
|
-
const stats = await
|
|
14874
|
+
const stats = await fs10.stat(requestedPath).catch(() => null);
|
|
14646
14875
|
if (!stats?.isFile()) {
|
|
14647
14876
|
throw new HttpError(404, {
|
|
14648
14877
|
code: "not_found",
|
|
@@ -14653,7 +14882,7 @@ async function registerThreadRoutes(app2) {
|
|
|
14653
14882
|
const contentType = lowerPath.endsWith(".png") ? "image/png" : lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg") ? "image/jpeg" : lowerPath.endsWith(".gif") ? "image/gif" : lowerPath.endsWith(".webp") ? "image/webp" : lowerPath.endsWith(".svg") ? "image/svg+xml" : lowerPath.endsWith(".heic") ? "image/heic" : lowerPath.endsWith(".heif") ? "image/heif" : "application/octet-stream";
|
|
14654
14883
|
reply.header("Content-Type", contentType);
|
|
14655
14884
|
reply.header("Cache-Control", "private, max-age=60");
|
|
14656
|
-
return reply.send(await
|
|
14885
|
+
return reply.send(await fs10.readFile(requestedPath));
|
|
14657
14886
|
});
|
|
14658
14887
|
app2.patch("/api/threads/:id", async (request) => {
|
|
14659
14888
|
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
@@ -14827,8 +15056,8 @@ async function registerThreadRoutes(app2) {
|
|
|
14827
15056
|
}
|
|
14828
15057
|
|
|
14829
15058
|
// src/routes/workspaces.ts
|
|
14830
|
-
import
|
|
14831
|
-
import
|
|
15059
|
+
import fs11 from "fs/promises";
|
|
15060
|
+
import path11 from "path";
|
|
14832
15061
|
import { spawn as spawn2 } from "child_process";
|
|
14833
15062
|
import { z as z6 } from "zod";
|
|
14834
15063
|
var createWorkspaceSchema = z6.union([
|
|
@@ -14868,7 +15097,7 @@ function inferGitRepoName(gitUrl) {
|
|
|
14868
15097
|
const normalized = withoutQuery.replace(/[\\/]+$/, "");
|
|
14869
15098
|
const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
|
|
14870
15099
|
const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
|
|
14871
|
-
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(
|
|
15100
|
+
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path11.sep)) {
|
|
14872
15101
|
throw new HttpError(400, {
|
|
14873
15102
|
code: "bad_request",
|
|
14874
15103
|
message: "Unable to infer a target directory from the Git URL."
|
|
@@ -14878,7 +15107,7 @@ function inferGitRepoName(gitUrl) {
|
|
|
14878
15107
|
}
|
|
14879
15108
|
async function pathExists2(absPath) {
|
|
14880
15109
|
try {
|
|
14881
|
-
await
|
|
15110
|
+
await fs11.stat(absPath);
|
|
14882
15111
|
return true;
|
|
14883
15112
|
} catch (error) {
|
|
14884
15113
|
if (error.code === "ENOENT") {
|
|
@@ -14933,7 +15162,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
14933
15162
|
});
|
|
14934
15163
|
app2.get("/api/workspaces/tree", async (request) => {
|
|
14935
15164
|
const query = treeQuerySchema.parse(request.query);
|
|
14936
|
-
const requestedPath = query.path ?
|
|
15165
|
+
const requestedPath = query.path ? path11.resolve(query.path) : app2.services.config.workspaceRoot;
|
|
14937
15166
|
const tree = await readWorkspaceTree({
|
|
14938
15167
|
rootPath: app2.services.config.workspaceRoot,
|
|
14939
15168
|
targetPath: requestedPath,
|
|
@@ -14965,7 +15194,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
14965
15194
|
let validated;
|
|
14966
15195
|
if ("gitUrl" in body) {
|
|
14967
15196
|
const repoName = inferGitRepoName(body.gitUrl);
|
|
14968
|
-
const targetPath =
|
|
15197
|
+
const targetPath = path11.join(settings.devHome, repoName);
|
|
14969
15198
|
if (await pathExists2(targetPath)) {
|
|
14970
15199
|
throw new HttpError(409, {
|
|
14971
15200
|
code: "conflict",
|
|
@@ -15074,8 +15303,8 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
15074
15303
|
}
|
|
15075
15304
|
|
|
15076
15305
|
// src/provider-host-config-service.ts
|
|
15077
|
-
import
|
|
15078
|
-
import
|
|
15306
|
+
import fs12 from "fs/promises";
|
|
15307
|
+
import path12 from "path";
|
|
15079
15308
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
15080
15309
|
function providerError(message, statusCode = 404) {
|
|
15081
15310
|
const error = new Error(message);
|
|
@@ -15083,23 +15312,23 @@ function providerError(message, statusCode = 404) {
|
|
|
15083
15312
|
return error;
|
|
15084
15313
|
}
|
|
15085
15314
|
function resolveProviderHostFilePath(providerHome, name) {
|
|
15086
|
-
return
|
|
15315
|
+
return path12.join(providerHome, name);
|
|
15087
15316
|
}
|
|
15088
15317
|
function resolveArchiveRoot(providerHome) {
|
|
15089
|
-
return
|
|
15318
|
+
return path12.join(providerHome, "supervisor-config-archives");
|
|
15090
15319
|
}
|
|
15091
15320
|
function resolveArchiveIndexPath(providerHome) {
|
|
15092
|
-
return
|
|
15321
|
+
return path12.join(resolveArchiveRoot(providerHome), "index.json");
|
|
15093
15322
|
}
|
|
15094
15323
|
function resolveArchivePath(providerHome, archiveId) {
|
|
15095
|
-
return
|
|
15324
|
+
return path12.join(resolveArchiveRoot(providerHome), archiveId);
|
|
15096
15325
|
}
|
|
15097
15326
|
function defaultArchiveLabel(createdAt) {
|
|
15098
15327
|
return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
|
|
15099
15328
|
}
|
|
15100
15329
|
async function readArchiveIndex(providerHome) {
|
|
15101
15330
|
try {
|
|
15102
|
-
const raw = await
|
|
15331
|
+
const raw = await fs12.readFile(resolveArchiveIndexPath(providerHome), "utf8");
|
|
15103
15332
|
const parsed = JSON.parse(raw);
|
|
15104
15333
|
return {
|
|
15105
15334
|
archives: Array.isArray(parsed.archives) ? parsed.archives : []
|
|
@@ -15113,8 +15342,8 @@ async function readArchiveIndex(providerHome) {
|
|
|
15113
15342
|
}
|
|
15114
15343
|
async function writeArchiveIndex(providerHome, index) {
|
|
15115
15344
|
const root = resolveArchiveRoot(providerHome);
|
|
15116
|
-
await
|
|
15117
|
-
await
|
|
15345
|
+
await fs12.mkdir(root, { recursive: true });
|
|
15346
|
+
await fs12.writeFile(
|
|
15118
15347
|
resolveArchiveIndexPath(providerHome),
|
|
15119
15348
|
`${JSON.stringify(index, null, 2)}
|
|
15120
15349
|
`,
|
|
@@ -15185,7 +15414,7 @@ var ProviderHostConfigService = class {
|
|
|
15185
15414
|
const fileName = this.assertHostFile(provider, name);
|
|
15186
15415
|
const filePath = resolveProviderHostFilePath(providerHome, fileName);
|
|
15187
15416
|
try {
|
|
15188
|
-
const content = await
|
|
15417
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
15189
15418
|
return {
|
|
15190
15419
|
name: fileName,
|
|
15191
15420
|
path: filePath,
|
|
@@ -15208,8 +15437,8 @@ var ProviderHostConfigService = class {
|
|
|
15208
15437
|
const providerHome = this.providerHome(provider);
|
|
15209
15438
|
const fileName = this.assertHostFile(provider, name);
|
|
15210
15439
|
const filePath = resolveProviderHostFilePath(providerHome, fileName);
|
|
15211
|
-
await
|
|
15212
|
-
await
|
|
15440
|
+
await fs12.mkdir(path12.dirname(filePath), { recursive: true });
|
|
15441
|
+
await fs12.writeFile(filePath, input.content, "utf8");
|
|
15213
15442
|
return this.readFile(provider, fileName);
|
|
15214
15443
|
}
|
|
15215
15444
|
async listArchives(provider) {
|
|
@@ -15235,7 +15464,7 @@ var ProviderHostConfigService = class {
|
|
|
15235
15464
|
}
|
|
15236
15465
|
])
|
|
15237
15466
|
);
|
|
15238
|
-
await
|
|
15467
|
+
await fs12.mkdir(archivePath, { recursive: true });
|
|
15239
15468
|
for (const name of fileNames) {
|
|
15240
15469
|
const hostFile = await this.readFile(provider, name);
|
|
15241
15470
|
files[name] = {
|
|
@@ -15243,7 +15472,7 @@ var ProviderHostConfigService = class {
|
|
|
15243
15472
|
exists: hostFile.exists
|
|
15244
15473
|
};
|
|
15245
15474
|
if (hostFile.exists) {
|
|
15246
|
-
await
|
|
15475
|
+
await fs12.writeFile(path12.join(archivePath, name), hostFile.content, "utf8");
|
|
15247
15476
|
}
|
|
15248
15477
|
}
|
|
15249
15478
|
const archive = {
|
|
@@ -15279,14 +15508,14 @@ var ProviderHostConfigService = class {
|
|
|
15279
15508
|
const fileNames = this.archiveFileNames(provider);
|
|
15280
15509
|
const { archive } = await findArchiveOrThrow(providerHome, id);
|
|
15281
15510
|
const archivePath = resolveArchivePath(providerHome, archive.id);
|
|
15282
|
-
await
|
|
15511
|
+
await fs12.mkdir(providerHome, { recursive: true });
|
|
15283
15512
|
for (const name of fileNames) {
|
|
15284
15513
|
const hostPath = resolveProviderHostFilePath(providerHome, name);
|
|
15285
15514
|
if (archive.files[name]?.exists) {
|
|
15286
|
-
const content = await
|
|
15287
|
-
await
|
|
15515
|
+
const content = await fs12.readFile(path12.join(archivePath, name), "utf8");
|
|
15516
|
+
await fs12.writeFile(hostPath, content, "utf8");
|
|
15288
15517
|
} else {
|
|
15289
|
-
await
|
|
15518
|
+
await fs12.rm(hostPath, { force: true });
|
|
15290
15519
|
}
|
|
15291
15520
|
}
|
|
15292
15521
|
await runtime.stop();
|
|
@@ -15299,12 +15528,12 @@ var ProviderHostConfigService = class {
|
|
|
15299
15528
|
};
|
|
15300
15529
|
|
|
15301
15530
|
// src/shell/shell-session-service.ts
|
|
15302
|
-
import
|
|
15531
|
+
import fs13 from "fs/promises";
|
|
15303
15532
|
import os3 from "os";
|
|
15304
|
-
import
|
|
15533
|
+
import path13 from "path";
|
|
15305
15534
|
async function pathExists3(filePath) {
|
|
15306
15535
|
try {
|
|
15307
|
-
await
|
|
15536
|
+
await fs13.access(filePath);
|
|
15308
15537
|
return true;
|
|
15309
15538
|
} catch {
|
|
15310
15539
|
return false;
|
|
@@ -15329,7 +15558,7 @@ function basenameFromPath2(filePath) {
|
|
|
15329
15558
|
if (!normalized) {
|
|
15330
15559
|
return "";
|
|
15331
15560
|
}
|
|
15332
|
-
return
|
|
15561
|
+
return path13.basename(normalized) || normalized;
|
|
15333
15562
|
}
|
|
15334
15563
|
function isInteractiveShellCommand(command) {
|
|
15335
15564
|
const normalized = (command ?? "").trim().toLowerCase();
|
|
@@ -15490,11 +15719,11 @@ function buildShellPromptInitScriptContents(command) {
|
|
|
15490
15719
|
async function ensureShellPromptInitScript(command) {
|
|
15491
15720
|
const normalized = command.trim().toLowerCase();
|
|
15492
15721
|
const extension = normalized === "zsh" ? "zsh" : "sh";
|
|
15493
|
-
const filePath =
|
|
15722
|
+
const filePath = path13.join(
|
|
15494
15723
|
os3.tmpdir(),
|
|
15495
15724
|
`remote-codex-shell-prompt.${extension}`
|
|
15496
15725
|
);
|
|
15497
|
-
await
|
|
15726
|
+
await fs13.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
|
|
15498
15727
|
return filePath;
|
|
15499
15728
|
}
|
|
15500
15729
|
async function buildShellPromptInitCommand(command, options = {}) {
|
|
@@ -16080,8 +16309,8 @@ var ShellSessionService = class {
|
|
|
16080
16309
|
};
|
|
16081
16310
|
|
|
16082
16311
|
// src/shell/tmux-manager.ts
|
|
16083
|
-
import
|
|
16084
|
-
import
|
|
16312
|
+
import fs14 from "fs";
|
|
16313
|
+
import path14 from "path";
|
|
16085
16314
|
import { spawn as spawnChild } from "child_process";
|
|
16086
16315
|
async function defaultExecCommand(command, args) {
|
|
16087
16316
|
return await new Promise((resolve, reject) => {
|
|
@@ -16108,17 +16337,17 @@ async function defaultExecCommand(command, args) {
|
|
|
16108
16337
|
});
|
|
16109
16338
|
}
|
|
16110
16339
|
function resolveExecutablePath(command) {
|
|
16111
|
-
if (command.includes(
|
|
16340
|
+
if (command.includes(path14.sep)) {
|
|
16112
16341
|
return command;
|
|
16113
16342
|
}
|
|
16114
16343
|
const searchPath = process.env.PATH ?? "";
|
|
16115
|
-
for (const entry of searchPath.split(
|
|
16344
|
+
for (const entry of searchPath.split(path14.delimiter)) {
|
|
16116
16345
|
const trimmed = entry.trim();
|
|
16117
16346
|
if (!trimmed) {
|
|
16118
16347
|
continue;
|
|
16119
16348
|
}
|
|
16120
|
-
const candidate =
|
|
16121
|
-
if (
|
|
16349
|
+
const candidate = path14.join(trimmed, command);
|
|
16350
|
+
if (fs14.existsSync(candidate)) {
|
|
16122
16351
|
return candidate;
|
|
16123
16352
|
}
|
|
16124
16353
|
}
|
|
@@ -16447,16 +16676,16 @@ var HttpError = class extends Error {
|
|
|
16447
16676
|
};
|
|
16448
16677
|
function findRepoRoot(start = process.cwd()) {
|
|
16449
16678
|
if (process.env.REMOTE_CODEX_REPO_ROOT) {
|
|
16450
|
-
return
|
|
16679
|
+
return path15.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
|
|
16451
16680
|
}
|
|
16452
|
-
let current =
|
|
16453
|
-
while (current !==
|
|
16454
|
-
if (
|
|
16681
|
+
let current = path15.resolve(start);
|
|
16682
|
+
while (current !== path15.dirname(current)) {
|
|
16683
|
+
if (fs15.existsSync(path15.join(current, "pnpm-workspace.yaml")) && fs15.existsSync(path15.join(current, "scripts", "service-restart.mjs"))) {
|
|
16455
16684
|
return current;
|
|
16456
16685
|
}
|
|
16457
|
-
current =
|
|
16686
|
+
current = path15.dirname(current);
|
|
16458
16687
|
}
|
|
16459
|
-
return
|
|
16688
|
+
return path15.resolve(process.cwd());
|
|
16460
16689
|
}
|
|
16461
16690
|
function createServiceLifecycle() {
|
|
16462
16691
|
return {
|
|
@@ -16468,8 +16697,8 @@ function createServiceLifecycle() {
|
|
|
16468
16697
|
});
|
|
16469
16698
|
}
|
|
16470
16699
|
const repoRoot = findRepoRoot();
|
|
16471
|
-
const restartScript =
|
|
16472
|
-
if (!
|
|
16700
|
+
const restartScript = path15.join(repoRoot, "scripts", "service-restart.mjs");
|
|
16701
|
+
if (!fs15.existsSync(restartScript) || !fs15.existsSync(path15.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
16473
16702
|
throw new HttpError(503, {
|
|
16474
16703
|
code: "service_unavailable",
|
|
16475
16704
|
message: "Build and restart requires a Remote Codex source checkout. Set REMOTE_CODEX_REPO_ROOT to the checkout path, or update the npm package with npm install -g remote-codex@latest."
|
|
@@ -16500,7 +16729,7 @@ function buildApp(options = {}) {
|
|
|
16500
16729
|
eventBus,
|
|
16501
16730
|
runtimeBootstrap.localCodexSessionStore,
|
|
16502
16731
|
config.workspaceRoot,
|
|
16503
|
-
|
|
16732
|
+
runtimeBootstrap.codexManagement
|
|
16504
16733
|
);
|
|
16505
16734
|
const shellService = options.shellService ?? new ShellSessionService(database.db, eventBus, new TmuxManager());
|
|
16506
16735
|
const providerHostConfigService = new ProviderHostConfigService(
|
|
@@ -16810,7 +17039,7 @@ function makeShellErrorEnvelope(shellId, error) {
|
|
|
16810
17039
|
}
|
|
16811
17040
|
|
|
16812
17041
|
// src/index.ts
|
|
16813
|
-
if (
|
|
17042
|
+
if (fs16.existsSync(".env")) {
|
|
16814
17043
|
process.loadEnvFile?.(".env");
|
|
16815
17044
|
}
|
|
16816
17045
|
var app = buildApp();
|