remote-codex 0.1.7 → 0.1.8
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 +1052 -878
- package/apps/supervisor-web/dist/assets/{highlighted-body-OFNGDK62-0cYcfOfd.js → highlighted-body-OFNGDK62-owvlMiML.js} +1 -1
- package/apps/supervisor-web/dist/assets/{index-nH6a8Wwn.js → index-CrcX157r.js} +70 -70
- package/apps/supervisor-web/dist/assets/{xterm-DisVWgDR.js → xterm-BQ_J5An_.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 +72 -1
- package/packages/codex/src/runtimeAdapter.ts +60 -9
- package/packages/db/src/repositories.ts +6 -1
- package/packages/shared/src/index.ts +4 -2
|
@@ -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";
|
|
@@ -10333,7 +10746,52 @@ 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) {
|
|
@@ -10457,25 +10915,25 @@ function applyRecordedTurnItemOrders(turns, turnItemOrder) {
|
|
|
10457
10915
|
}
|
|
10458
10916
|
|
|
10459
10917
|
// src/codex/modelPricing.ts
|
|
10460
|
-
import
|
|
10461
|
-
import
|
|
10918
|
+
import fs8 from "fs";
|
|
10919
|
+
import path8 from "path";
|
|
10462
10920
|
var TOKEN_PRICE_DENOMINATOR = 1e6;
|
|
10463
10921
|
var cachedPricingConfig = null;
|
|
10464
10922
|
function resolvePackageRoot2(start = process.cwd()) {
|
|
10465
10923
|
if (process.env.REMOTE_CODEX_PACKAGE_ROOT) {
|
|
10466
|
-
return
|
|
10924
|
+
return path8.resolve(process.env.REMOTE_CODEX_PACKAGE_ROOT);
|
|
10467
10925
|
}
|
|
10468
|
-
let current =
|
|
10469
|
-
while (current !==
|
|
10470
|
-
if (
|
|
10926
|
+
let current = path8.resolve(start);
|
|
10927
|
+
while (current !== path8.dirname(current)) {
|
|
10928
|
+
if (fs8.existsSync(path8.join(current, "pnpm-workspace.yaml"))) {
|
|
10471
10929
|
return current;
|
|
10472
10930
|
}
|
|
10473
|
-
current =
|
|
10931
|
+
current = path8.dirname(current);
|
|
10474
10932
|
}
|
|
10475
10933
|
throw new Error("Unable to locate package root for Codex pricing config.");
|
|
10476
10934
|
}
|
|
10477
10935
|
function getPricingConfigPath() {
|
|
10478
|
-
return
|
|
10936
|
+
return path8.join(resolvePackageRoot2(), "config", "codex-model-pricing.json");
|
|
10479
10937
|
}
|
|
10480
10938
|
function isPositiveNumber(value) {
|
|
10481
10939
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
@@ -10552,7 +11010,7 @@ function getPricingConfig() {
|
|
|
10552
11010
|
return cachedPricingConfig;
|
|
10553
11011
|
}
|
|
10554
11012
|
const configPath = getPricingConfigPath();
|
|
10555
|
-
const content =
|
|
11013
|
+
const content = fs8.readFileSync(configPath, "utf8");
|
|
10556
11014
|
cachedPricingConfig = parsePricingConfig(JSON.parse(content));
|
|
10557
11015
|
return cachedPricingConfig;
|
|
10558
11016
|
}
|
|
@@ -10624,29 +11082,15 @@ function estimateTurnPrice(usage, snapshot) {
|
|
|
10624
11082
|
};
|
|
10625
11083
|
}
|
|
10626
11084
|
|
|
10627
|
-
// src/thread-service.ts
|
|
10628
|
-
var DEFAULT_THREAD_TITLE = "Untitled thread";
|
|
10629
|
-
var GENERIC_REMOTE_THREAD_TITLE = "Thread";
|
|
10630
|
-
var LOCAL_PLAN_DECISION_PREFIX = "plan-decision:";
|
|
10631
|
-
var IMPLEMENT_APPROVED_PLAN_PROMPT = "Implement the approved plan.";
|
|
10632
|
-
var THREAD_DETAIL_CACHE_TTL_MS = 5e3;
|
|
10633
|
-
var CONTEXT_BASELINE_TOKENS = 12e3;
|
|
10634
|
-
var FAST_MODE_NOTE_ON = "Fast mode on";
|
|
10635
|
-
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.";
|
|
11085
|
+
// src/thread-service.ts
|
|
11086
|
+
var DEFAULT_THREAD_TITLE = "Untitled thread";
|
|
11087
|
+
var GENERIC_REMOTE_THREAD_TITLE = "Thread";
|
|
11088
|
+
var LOCAL_PLAN_DECISION_PREFIX = "plan-decision:";
|
|
11089
|
+
var IMPLEMENT_APPROVED_PLAN_PROMPT = "Implement the approved plan.";
|
|
11090
|
+
var THREAD_DETAIL_CACHE_TTL_MS = 5e3;
|
|
11091
|
+
var CONTEXT_BASELINE_TOKENS = 12e3;
|
|
11092
|
+
var FAST_MODE_NOTE_ON = "Fast mode on";
|
|
11093
|
+
var FAST_MODE_NOTE_OFF = "Fast mode off";
|
|
10650
11094
|
function toIsoFromEpoch2(value) {
|
|
10651
11095
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
10652
11096
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10718,22 +11162,36 @@ function mergeGoalHistoryEntry(existing, incoming) {
|
|
|
10718
11162
|
localGoalId: latest.localGoalId ?? fallback.localGoalId ?? null
|
|
10719
11163
|
};
|
|
10720
11164
|
}
|
|
10721
|
-
function
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
11165
|
+
function goalHistoryStatusRank(status) {
|
|
11166
|
+
return ["active", "paused", "budgetLimited"].includes(status) ? 0 : 1;
|
|
11167
|
+
}
|
|
11168
|
+
function resetGoalProgress(goal) {
|
|
11169
|
+
return {
|
|
11170
|
+
...goal,
|
|
11171
|
+
tokensUsed: 0,
|
|
11172
|
+
timeUsedSeconds: 0
|
|
11173
|
+
};
|
|
11174
|
+
}
|
|
11175
|
+
function goalObjectiveChanged(existing, nextObjective) {
|
|
11176
|
+
return existing !== null && typeof nextObjective === "string" && nextObjective.trim().length > 0 && nextObjective !== existing.objective;
|
|
11177
|
+
}
|
|
11178
|
+
function isLocalGoalStatus(status) {
|
|
11179
|
+
return ["active", "paused", "budgetLimited"].includes(status);
|
|
11180
|
+
}
|
|
11181
|
+
function localGoalSnapshotForFallback(goalHistory) {
|
|
11182
|
+
const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
|
|
11183
|
+
if (activeGoal) {
|
|
11184
|
+
return activeGoal;
|
|
10735
11185
|
}
|
|
10736
|
-
|
|
11186
|
+
return goalHistory.find((entry) => entry.status === "terminated") ?? null;
|
|
11187
|
+
}
|
|
11188
|
+
function localGoalSnapshotToPreserve(goalHistory, remoteGoal) {
|
|
11189
|
+
const activeGoal = goalHistory.find((entry) => isLocalGoalStatus(entry.status)) ?? null;
|
|
11190
|
+
const hasTerminatedHistory = goalHistory.some((entry) => entry.status === "terminated");
|
|
11191
|
+
if (activeGoal && hasTerminatedHistory && isLocalGoalStatus(remoteGoal.status) && activeGoal.objective === remoteGoal.objective && activeGoal.tokensUsed === 0 && activeGoal.timeUsedSeconds === 0 && (remoteGoal.tokensUsed > 0 || remoteGoal.timeUsedSeconds > 0)) {
|
|
11192
|
+
return activeGoal;
|
|
11193
|
+
}
|
|
11194
|
+
return activeGoal ? null : goalHistory.find((entry) => entry.status === "terminated") ?? null;
|
|
10737
11195
|
}
|
|
10738
11196
|
function defaultSandboxModeForApprovalMode(approvalMode) {
|
|
10739
11197
|
return approvalMode === "guarded" ? "workspace-write" : "danger-full-access";
|
|
@@ -10748,34 +11206,6 @@ function normalizeSandboxMode(value) {
|
|
|
10748
11206
|
return null;
|
|
10749
11207
|
}
|
|
10750
11208
|
}
|
|
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
11209
|
function normalizeReasoningEffort(value) {
|
|
10780
11210
|
switch (value) {
|
|
10781
11211
|
case "none":
|
|
@@ -10795,8 +11225,8 @@ function normalizeCollaborationMode(value) {
|
|
|
10795
11225
|
function normalizeFastMode(value) {
|
|
10796
11226
|
return value === true || value === 1;
|
|
10797
11227
|
}
|
|
10798
|
-
function
|
|
10799
|
-
return fastMode ? "fast" :
|
|
11228
|
+
function performanceModeForFastMode(fastMode) {
|
|
11229
|
+
return fastMode ? "fast" : "standard";
|
|
10800
11230
|
}
|
|
10801
11231
|
function normalizePricingTier(value) {
|
|
10802
11232
|
return value === "fast" || value === "standard" ? value : null;
|
|
@@ -10841,26 +11271,26 @@ function buildAnsweredRequestNote(request, input) {
|
|
|
10841
11271
|
}
|
|
10842
11272
|
async function pathExists(absPath) {
|
|
10843
11273
|
try {
|
|
10844
|
-
await
|
|
11274
|
+
await fs9.access(absPath);
|
|
10845
11275
|
return true;
|
|
10846
11276
|
} catch {
|
|
10847
11277
|
return false;
|
|
10848
11278
|
}
|
|
10849
11279
|
}
|
|
10850
11280
|
async function resolveComparablePath2(absPath) {
|
|
10851
|
-
const resolved =
|
|
11281
|
+
const resolved = path9.resolve(absPath);
|
|
10852
11282
|
if (await pathExists(resolved)) {
|
|
10853
|
-
return
|
|
11283
|
+
return fs9.realpath(resolved);
|
|
10854
11284
|
}
|
|
10855
|
-
const parentPath =
|
|
11285
|
+
const parentPath = path9.dirname(resolved);
|
|
10856
11286
|
if (parentPath === resolved) {
|
|
10857
11287
|
return resolved;
|
|
10858
11288
|
}
|
|
10859
11289
|
const resolvedParent = await resolveComparablePath2(parentPath);
|
|
10860
|
-
return
|
|
11290
|
+
return path9.join(resolvedParent, path9.basename(resolved));
|
|
10861
11291
|
}
|
|
10862
11292
|
async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
10863
|
-
if (!
|
|
11293
|
+
if (!path9.isAbsolute(candidatePath)) {
|
|
10864
11294
|
throw new HttpError(400, {
|
|
10865
11295
|
code: "bad_request",
|
|
10866
11296
|
message: "Imported session path must be absolute."
|
|
@@ -10868,7 +11298,7 @@ async function resolveImportedWorkspacePath(workspaceRoot, candidatePath) {
|
|
|
10868
11298
|
}
|
|
10869
11299
|
const resolvedRoot = await resolveComparablePath2(workspaceRoot);
|
|
10870
11300
|
const resolvedCandidate = await resolveComparablePath2(candidatePath);
|
|
10871
|
-
const normalizedRoot = resolvedRoot.endsWith(
|
|
11301
|
+
const normalizedRoot = resolvedRoot.endsWith(path9.sep) ? resolvedRoot : `${resolvedRoot}${path9.sep}`;
|
|
10872
11302
|
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(normalizedRoot)) {
|
|
10873
11303
|
throw new HttpError(403, {
|
|
10874
11304
|
code: "forbidden",
|
|
@@ -10888,7 +11318,7 @@ function toWorkspaceDto(record) {
|
|
|
10888
11318
|
lastOpenedAt: record.lastOpenedAt
|
|
10889
11319
|
};
|
|
10890
11320
|
}
|
|
10891
|
-
function
|
|
11321
|
+
function isRecord5(value) {
|
|
10892
11322
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10893
11323
|
}
|
|
10894
11324
|
function numberOrNull2(value) {
|
|
@@ -10927,11 +11357,11 @@ function computeContextRemainingPercent(tokensInContextWindow, contextWindow) {
|
|
|
10927
11357
|
return clampPercentage(Math.round(remaining / effectiveWindow * 100));
|
|
10928
11358
|
}
|
|
10929
11359
|
function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
10930
|
-
const tokenUsage =
|
|
11360
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
10931
11361
|
const modelContextWindow = contextWindowForModel(model) ?? numberOrNull2(
|
|
10932
11362
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
10933
11363
|
);
|
|
10934
|
-
const lastUsage =
|
|
11364
|
+
const lastUsage = isRecord5(tokenUsage?.last) ? tokenUsage.last : null;
|
|
10935
11365
|
const tokensInContextWindow = numberOrNull2(
|
|
10936
11366
|
lastUsage?.totalTokens ?? lastUsage?.total_tokens
|
|
10937
11367
|
);
|
|
@@ -10950,7 +11380,7 @@ function buildThreadContextUsageFromPayload(payload, model = null, timestamp = (
|
|
|
10950
11380
|
};
|
|
10951
11381
|
}
|
|
10952
11382
|
function buildTurnTokenBreakdown(payload) {
|
|
10953
|
-
const usage =
|
|
11383
|
+
const usage = isRecord5(payload) ? payload : null;
|
|
10954
11384
|
const totalTokens2 = numberOrNull2(usage?.totalTokens ?? usage?.total_tokens);
|
|
10955
11385
|
const inputTokens = numberOrNull2(usage?.inputTokens ?? usage?.input_tokens);
|
|
10956
11386
|
const cachedInputTokens = numberOrNull2(
|
|
@@ -10996,12 +11426,12 @@ function subtractTurnTokenBreakdowns(current, previous) {
|
|
|
10996
11426
|
};
|
|
10997
11427
|
}
|
|
10998
11428
|
function parseThreadTurnTokenUsage(payload) {
|
|
10999
|
-
const tokenUsage =
|
|
11429
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
11000
11430
|
const total = buildTurnTokenBreakdown(
|
|
11001
|
-
|
|
11431
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
11002
11432
|
);
|
|
11003
11433
|
const last = buildTurnTokenBreakdown(
|
|
11004
|
-
|
|
11434
|
+
isRecord5(tokenUsage?.last) ? tokenUsage.last : null
|
|
11005
11435
|
);
|
|
11006
11436
|
const modelContextWindow = numberOrNull2(
|
|
11007
11437
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
@@ -11025,7 +11455,7 @@ function parseStoredThreadTurnTokenUsageState(value) {
|
|
|
11025
11455
|
try {
|
|
11026
11456
|
const parsed = JSON.parse(value);
|
|
11027
11457
|
const baselineTotal = buildTurnTokenBreakdown(
|
|
11028
|
-
|
|
11458
|
+
isRecord5(parsed?.baselineTotal) ? parsed.baselineTotal : null
|
|
11029
11459
|
);
|
|
11030
11460
|
return {
|
|
11031
11461
|
baselineTotal,
|
|
@@ -11062,12 +11492,12 @@ function stringifyStoredThreadTurnTokenUsageState(state) {
|
|
|
11062
11492
|
});
|
|
11063
11493
|
}
|
|
11064
11494
|
function buildThreadTurnTokenUsage(payload, baselineTotal, previous = null) {
|
|
11065
|
-
const tokenUsage =
|
|
11495
|
+
const tokenUsage = isRecord5(payload) ? payload : null;
|
|
11066
11496
|
const cumulativeTotal = buildTurnTokenBreakdown(
|
|
11067
|
-
|
|
11497
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
11068
11498
|
);
|
|
11069
11499
|
const last = buildTurnTokenBreakdown(
|
|
11070
|
-
|
|
11500
|
+
isRecord5(tokenUsage?.last) ? tokenUsage.last : null
|
|
11071
11501
|
);
|
|
11072
11502
|
const modelContextWindow = numberOrNull2(
|
|
11073
11503
|
tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window
|
|
@@ -11085,8 +11515,8 @@ function parseThreadTurnTokenUsageJson(value) {
|
|
|
11085
11515
|
return parseStoredThreadTurnTokenUsageState(value).usage;
|
|
11086
11516
|
}
|
|
11087
11517
|
function sanitizeAttachmentFileName(originalName) {
|
|
11088
|
-
const basename =
|
|
11089
|
-
const extension =
|
|
11518
|
+
const basename = path9.basename(originalName).trim() || "attachment";
|
|
11519
|
+
const extension = path9.extname(basename).replace(/[^a-zA-Z0-9.]/g, "");
|
|
11090
11520
|
const rawStem = extension ? basename.slice(0, -extension.length) : basename;
|
|
11091
11521
|
const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
11092
11522
|
const stem = sanitizedStem || "attachment";
|
|
@@ -11094,269 +11524,7 @@ function sanitizeAttachmentFileName(originalName) {
|
|
|
11094
11524
|
return `${stem}-${randomUUID2().slice(0, 8)}${normalizedExtension}`;
|
|
11095
11525
|
}
|
|
11096
11526
|
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
|
-
});
|
|
11527
|
+
return path9.join(workspacePath, ".temp", "threads", localThreadId);
|
|
11360
11528
|
}
|
|
11361
11529
|
function buildTurnDto(turn, metadata) {
|
|
11362
11530
|
const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
|
|
@@ -11416,13 +11584,13 @@ function safeTranscriptExportFileName(title, extension) {
|
|
|
11416
11584
|
return `remote-codex-${stem || "thread"}-${timestamp}.${extension}`;
|
|
11417
11585
|
}
|
|
11418
11586
|
var ThreadService = class {
|
|
11419
|
-
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot,
|
|
11587
|
+
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, codexManagement) {
|
|
11420
11588
|
this.db = db;
|
|
11421
11589
|
this.agentRuntimes = agentRuntimes;
|
|
11422
11590
|
this.eventBus = eventBus;
|
|
11423
11591
|
this.localSessionStore = localSessionStore;
|
|
11424
11592
|
this.workspaceRoot = workspaceRoot;
|
|
11425
|
-
this.
|
|
11593
|
+
this.codexManagement = codexManagement;
|
|
11426
11594
|
for (const runtime of this.agentRuntimes.all()) {
|
|
11427
11595
|
runtime.on("event", (event) => {
|
|
11428
11596
|
void this.handleRuntimeEvent(event);
|
|
@@ -11437,7 +11605,7 @@ var ThreadService = class {
|
|
|
11437
11605
|
eventBus;
|
|
11438
11606
|
localSessionStore;
|
|
11439
11607
|
workspaceRoot;
|
|
11440
|
-
|
|
11608
|
+
codexManagement;
|
|
11441
11609
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
11442
11610
|
dismissedPlanDecisionTurns = /* @__PURE__ */ new Map();
|
|
11443
11611
|
threadDetailCache = /* @__PURE__ */ new Map();
|
|
@@ -11497,13 +11665,13 @@ var ThreadService = class {
|
|
|
11497
11665
|
return this.providerForRecord({ provider }) === "codex";
|
|
11498
11666
|
}
|
|
11499
11667
|
runtimeSupportsFastMode(provider) {
|
|
11500
|
-
return this.runtimeForProvider(provider).capabilities.controls.
|
|
11668
|
+
return this.runtimeForProvider(provider).capabilities.controls.performanceMode;
|
|
11501
11669
|
}
|
|
11502
11670
|
fastModeForProvider(provider, fastMode) {
|
|
11503
11671
|
return this.runtimeSupportsFastMode(provider) ? normalizeFastMode(fastMode) : false;
|
|
11504
11672
|
}
|
|
11505
|
-
|
|
11506
|
-
return
|
|
11673
|
+
performanceModeForRecord(record) {
|
|
11674
|
+
return performanceModeForFastMode(this.fastModeForProvider(record.provider, record.fastMode));
|
|
11507
11675
|
}
|
|
11508
11676
|
assertCodexHooksFileManagement(provider) {
|
|
11509
11677
|
if (!this.isCodexProvider(provider)) {
|
|
@@ -11740,7 +11908,7 @@ var ThreadService = class {
|
|
|
11740
11908
|
const matchedModel = modelRecords.find((entry) => entry.model === input.model);
|
|
11741
11909
|
const reasoningEffort = normalizeReasoningEffort(matchedModel?.defaultReasoningEffort) ?? "medium";
|
|
11742
11910
|
const sandboxMode = defaultSandboxModeForApprovalMode(input.approvalMode);
|
|
11743
|
-
const fastMode = this.runtimeSupportsFastMode(provider) ?
|
|
11911
|
+
const fastMode = this.runtimeSupportsFastMode(provider) ? this.codexManagement.readFastMode() : false;
|
|
11744
11912
|
if (this.runtimeSupportsFastMode(provider)) {
|
|
11745
11913
|
ensureFastModeSupported(input.model, fastMode);
|
|
11746
11914
|
}
|
|
@@ -11749,7 +11917,7 @@ var ThreadService = class {
|
|
|
11749
11917
|
model: input.model,
|
|
11750
11918
|
approvalMode: input.approvalMode,
|
|
11751
11919
|
sandboxMode,
|
|
11752
|
-
|
|
11920
|
+
performanceMode: performanceModeForFastMode(fastMode)
|
|
11753
11921
|
});
|
|
11754
11922
|
const created = createThreadRecord(this.db, {
|
|
11755
11923
|
workspaceId: workspace.id,
|
|
@@ -11804,7 +11972,7 @@ var ThreadService = class {
|
|
|
11804
11972
|
if (!workspace) {
|
|
11805
11973
|
workspace = createWorkspaceRecord(this.db, {
|
|
11806
11974
|
absPath: importedPath,
|
|
11807
|
-
label:
|
|
11975
|
+
label: path9.basename(importedPath) || "workspace"
|
|
11808
11976
|
});
|
|
11809
11977
|
}
|
|
11810
11978
|
const created = createThreadRecord(this.db, {
|
|
@@ -11820,7 +11988,7 @@ var ThreadService = class {
|
|
|
11820
11988
|
approvalMode: "yolo",
|
|
11821
11989
|
sandboxMode: defaultSandboxModeForApprovalMode("yolo"),
|
|
11822
11990
|
summaryText: localSession.turns.flatMap((turn) => turn.items).find((item) => item.kind === "userMessage")?.text ?? null,
|
|
11823
|
-
fastMode:
|
|
11991
|
+
fastMode: this.codexManagement.readFastMode(),
|
|
11824
11992
|
source: "local_codex_import",
|
|
11825
11993
|
isConnected: false
|
|
11826
11994
|
});
|
|
@@ -11869,8 +12037,8 @@ var ThreadService = class {
|
|
|
11869
12037
|
cachedDetail.turns,
|
|
11870
12038
|
pagedTurns.turns
|
|
11871
12039
|
);
|
|
11872
|
-
const goal = await this.getThreadGoalForRecord(updated).catch(() => null);
|
|
11873
12040
|
const goalHistory = this.listThreadGoalHistory(updated.id);
|
|
12041
|
+
const goal = await this.getThreadGoalForRecord(updated).catch(() => null) ?? localGoalSnapshotForFallback(goalHistory);
|
|
11874
12042
|
return {
|
|
11875
12043
|
thread: this.toThreadDto(updated, loadedIds),
|
|
11876
12044
|
workspace: toWorkspaceDto(workspace),
|
|
@@ -12010,7 +12178,7 @@ var ThreadService = class {
|
|
|
12010
12178
|
});
|
|
12011
12179
|
}
|
|
12012
12180
|
this.requireProviderSessionId(record);
|
|
12013
|
-
return this.getThreadGoalForRecord(record, { allowEnableFeature: true });
|
|
12181
|
+
return await this.getThreadGoalForRecord(record, { allowEnableFeature: true }) ?? localGoalSnapshotForFallback(this.listThreadGoalHistory(localThreadId));
|
|
12014
12182
|
}
|
|
12015
12183
|
async updateThreadGoal(localThreadId, input) {
|
|
12016
12184
|
const record = getThreadRecordById(this.db, localThreadId);
|
|
@@ -12037,14 +12205,32 @@ var ThreadService = class {
|
|
|
12037
12205
|
try {
|
|
12038
12206
|
await this.ensureGoalsFeatureEnabled(record.provider);
|
|
12039
12207
|
await this.ensureThreadLoadedForCodexOperation(record);
|
|
12040
|
-
const
|
|
12208
|
+
const activeGoal = this.listThreadGoalHistory(localThreadId).find(
|
|
12209
|
+
(goal2) => ["active", "paused", "budgetLimited"].includes(goal2.status)
|
|
12210
|
+
) ?? null;
|
|
12211
|
+
const creatingNewGoal = goalObjectiveChanged(activeGoal, input.objective);
|
|
12212
|
+
if (creatingNewGoal) {
|
|
12213
|
+
markActiveThreadGoalRecordTerminated(this.db, localThreadId);
|
|
12214
|
+
}
|
|
12215
|
+
if (input.status === "terminated") {
|
|
12216
|
+
const terminatedGoal = markActiveThreadGoalRecordTerminated(this.db, localThreadId);
|
|
12217
|
+
const goalHistory = this.listThreadGoalHistory(localThreadId);
|
|
12218
|
+
const goal2 = terminatedGoal ? toThreadGoalDtoFromRecord(terminatedGoal) : goalHistory[0] ?? null;
|
|
12219
|
+
this.emitThreadEvent("thread.goal.updated", localThreadId, {
|
|
12220
|
+
goal: goal2,
|
|
12221
|
+
goalHistory
|
|
12222
|
+
});
|
|
12223
|
+
return goal2;
|
|
12224
|
+
}
|
|
12225
|
+
const upstreamStatus = input.status;
|
|
12041
12226
|
const goal = await runtime.setGoal({
|
|
12042
12227
|
providerSessionId,
|
|
12043
12228
|
...input.objective !== void 0 ? { objective: input.objective } : {},
|
|
12044
12229
|
...upstreamStatus !== void 0 ? { status: upstreamStatus } : {},
|
|
12045
12230
|
...input.tokenBudget !== void 0 ? { tokenBudget: input.tokenBudget } : {}
|
|
12046
12231
|
});
|
|
12047
|
-
const
|
|
12232
|
+
const upstreamDto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
|
|
12233
|
+
const dto = creatingNewGoal ? resetGoalProgress(upstreamDto) : upstreamDto;
|
|
12048
12234
|
const persistedGoal = toThreadGoalDtoFromRecord(
|
|
12049
12235
|
this.persistThreadGoalSnapshot(localThreadId, dto)
|
|
12050
12236
|
);
|
|
@@ -12054,7 +12240,7 @@ var ThreadService = class {
|
|
|
12054
12240
|
});
|
|
12055
12241
|
return persistedGoal;
|
|
12056
12242
|
} catch (error) {
|
|
12057
|
-
|
|
12243
|
+
this.codexManagement.mapGoalError(error);
|
|
12058
12244
|
}
|
|
12059
12245
|
}
|
|
12060
12246
|
async clearThreadGoal(localThreadId) {
|
|
@@ -12088,7 +12274,7 @@ var ThreadService = class {
|
|
|
12088
12274
|
this.emitThreadEvent("thread.goal.cleared", localThreadId, { goalHistory });
|
|
12089
12275
|
return { cleared, goalHistory };
|
|
12090
12276
|
} catch (error) {
|
|
12091
|
-
|
|
12277
|
+
this.codexManagement.mapGoalError(error);
|
|
12092
12278
|
}
|
|
12093
12279
|
}
|
|
12094
12280
|
async getThreadGoalForRecord(record, options = {}) {
|
|
@@ -12109,9 +12295,16 @@ var ThreadService = class {
|
|
|
12109
12295
|
return null;
|
|
12110
12296
|
}
|
|
12111
12297
|
const dto = normalizeThreadGoalStatusForThread(toThreadGoalDtoFromAgentGoal(goal), record);
|
|
12298
|
+
const localGoal = localGoalSnapshotToPreserve(
|
|
12299
|
+
this.listThreadGoalHistory(record.id),
|
|
12300
|
+
dto
|
|
12301
|
+
);
|
|
12302
|
+
if (localGoal) {
|
|
12303
|
+
return localGoal;
|
|
12304
|
+
}
|
|
12112
12305
|
return toThreadGoalDtoFromRecord(this.persistThreadGoalSnapshot(record.id, dto));
|
|
12113
12306
|
} catch (error) {
|
|
12114
|
-
if (
|
|
12307
|
+
if (this.codexManagement.isRuntimeRequestError(error)) {
|
|
12115
12308
|
return null;
|
|
12116
12309
|
}
|
|
12117
12310
|
throw error;
|
|
@@ -12144,29 +12337,20 @@ var ThreadService = class {
|
|
|
12144
12337
|
deduped.set(key, existing ? mergeGoalHistoryEntry(existing, goal) : goal);
|
|
12145
12338
|
}
|
|
12146
12339
|
return [...deduped.values()].sort(
|
|
12147
|
-
(left, right) =>
|
|
12340
|
+
(left, right) => {
|
|
12341
|
+
const updatedDelta = Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
|
|
12342
|
+
if (updatedDelta !== 0) {
|
|
12343
|
+
return updatedDelta;
|
|
12344
|
+
}
|
|
12345
|
+
return goalHistoryStatusRank(left.status) - goalHistoryStatusRank(right.status);
|
|
12346
|
+
}
|
|
12148
12347
|
);
|
|
12149
12348
|
}
|
|
12150
12349
|
async ensureGoalsFeatureEnabled(provider) {
|
|
12151
12350
|
if (!this.isCodexProvider(provider)) {
|
|
12152
12351
|
return;
|
|
12153
12352
|
}
|
|
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
|
-
}
|
|
12353
|
+
await this.codexManagement.ensureGoalsFeatureEnabled(this.codexRuntime());
|
|
12170
12354
|
}
|
|
12171
12355
|
async ensureThreadLoadedForCodexOperation(record) {
|
|
12172
12356
|
if (!record.providerSessionId) {
|
|
@@ -12244,7 +12428,7 @@ var ThreadService = class {
|
|
|
12244
12428
|
});
|
|
12245
12429
|
}
|
|
12246
12430
|
const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
|
|
12247
|
-
await
|
|
12431
|
+
await fs9.mkdir(tempDirectory, { recursive: true });
|
|
12248
12432
|
let rewrittenPrompt = input.prompt;
|
|
12249
12433
|
for (const attachment of attachments) {
|
|
12250
12434
|
if (!rewrittenPrompt.includes(attachment.manifest.placeholder)) {
|
|
@@ -12254,7 +12438,7 @@ var ThreadService = class {
|
|
|
12254
12438
|
});
|
|
12255
12439
|
}
|
|
12256
12440
|
const savedFileName = sanitizeAttachmentFileName(attachment.manifest.originalName);
|
|
12257
|
-
await
|
|
12441
|
+
await fs9.writeFile(path9.join(tempDirectory, savedFileName), attachment.buffer);
|
|
12258
12442
|
const relativePath = `./.temp/threads/${localThreadId}/${savedFileName}`;
|
|
12259
12443
|
const replacementToken = attachment.manifest.kind === "photo" ? `[PHOTO ${relativePath}]` : `[FILE ${relativePath}]`;
|
|
12260
12444
|
rewrittenPrompt = rewrittenPrompt.split(attachment.manifest.placeholder).join(replacementToken);
|
|
@@ -12286,7 +12470,7 @@ var ThreadService = class {
|
|
|
12286
12470
|
providerSessionId,
|
|
12287
12471
|
model: input.model ?? record.model ?? null,
|
|
12288
12472
|
sandboxMode,
|
|
12289
|
-
|
|
12473
|
+
performanceMode: performanceModeForFastMode(fastMode)
|
|
12290
12474
|
});
|
|
12291
12475
|
} catch (error) {
|
|
12292
12476
|
if (!isRemoteThreadBootstrapError(error)) {
|
|
@@ -12391,7 +12575,7 @@ var ThreadService = class {
|
|
|
12391
12575
|
const sandboxMode = (input.sandboxMode !== void 0 ? normalizeSandboxMode(input.sandboxMode) : normalizeSandboxMode(record.sandboxMode)) ?? defaultSandboxModeForApprovalMode(record.approvalMode ?? "yolo");
|
|
12392
12576
|
const fastMode = this.fastModeForProvider(record.provider, record.fastMode);
|
|
12393
12577
|
ensureFastModeSupported(effectiveModel, fastMode);
|
|
12394
|
-
const
|
|
12578
|
+
const performanceMode = performanceModeForFastMode(fastMode);
|
|
12395
12579
|
const connectedRecord = {
|
|
12396
12580
|
...record,
|
|
12397
12581
|
providerSessionId
|
|
@@ -12408,7 +12592,7 @@ var ThreadService = class {
|
|
|
12408
12592
|
normalizedReasoning,
|
|
12409
12593
|
collaborationMode,
|
|
12410
12594
|
sandboxMode,
|
|
12411
|
-
|
|
12595
|
+
performanceMode,
|
|
12412
12596
|
workspacePath: workspace.absPath
|
|
12413
12597
|
});
|
|
12414
12598
|
}
|
|
@@ -12418,26 +12602,27 @@ var ThreadService = class {
|
|
|
12418
12602
|
normalizedReasoning,
|
|
12419
12603
|
collaborationMode,
|
|
12420
12604
|
sandboxMode,
|
|
12421
|
-
|
|
12605
|
+
performanceMode,
|
|
12422
12606
|
workspacePath: workspace.absPath
|
|
12423
12607
|
});
|
|
12424
12608
|
}
|
|
12425
12609
|
async startPromptTurn(localThreadId, record, input) {
|
|
12426
12610
|
const runtime = this.runtimeForProvider(record.provider);
|
|
12427
12611
|
const modelRecords = await runtime.listModels().catch(() => []);
|
|
12428
|
-
ensureFastModeSupported(input.effectiveModel, input.
|
|
12612
|
+
ensureFastModeSupported(input.effectiveModel, input.performanceMode === "fast");
|
|
12429
12613
|
const pricingSnapshot = buildTurnPricingSnapshot(
|
|
12430
12614
|
input.effectiveModel,
|
|
12431
|
-
input.
|
|
12615
|
+
input.performanceMode === "fast"
|
|
12432
12616
|
);
|
|
12433
12617
|
const turn = await runtime.startTurn({
|
|
12434
12618
|
providerSessionId: record.providerSessionId,
|
|
12435
12619
|
prompt: input.prompt,
|
|
12436
12620
|
model: input.effectiveModel,
|
|
12437
|
-
|
|
12621
|
+
performanceMode: input.performanceMode,
|
|
12438
12622
|
reasoningEffort: input.normalizedReasoning,
|
|
12439
12623
|
collaborationMode: input.collaborationMode,
|
|
12440
|
-
|
|
12624
|
+
sandboxMode: input.sandboxMode,
|
|
12625
|
+
workspacePath: input.workspacePath
|
|
12441
12626
|
});
|
|
12442
12627
|
upsertThreadTurnMetadata(this.db, {
|
|
12443
12628
|
threadId: localThreadId,
|
|
@@ -12525,7 +12710,7 @@ var ThreadService = class {
|
|
|
12525
12710
|
normalizedReasoning: input.normalizedReasoning,
|
|
12526
12711
|
collaborationMode: input.collaborationMode,
|
|
12527
12712
|
sandboxMode: input.sandboxMode,
|
|
12528
|
-
|
|
12713
|
+
performanceMode: input.performanceMode,
|
|
12529
12714
|
workspacePath: input.workspacePath
|
|
12530
12715
|
});
|
|
12531
12716
|
}
|
|
@@ -12559,7 +12744,7 @@ var ThreadService = class {
|
|
|
12559
12744
|
this.clearPendingPlanDecisionRequests(localThreadId, true);
|
|
12560
12745
|
}
|
|
12561
12746
|
if (supportsFastMode2 && currentFastMode !== nextFastMode) {
|
|
12562
|
-
await
|
|
12747
|
+
await this.codexManagement.writeFastMode(nextFastMode);
|
|
12563
12748
|
this.appendActivityNote(localThreadId, {
|
|
12564
12749
|
kind: "fastMode",
|
|
12565
12750
|
text: nextFastMode ? FAST_MODE_NOTE_ON : FAST_MODE_NOTE_OFF
|
|
@@ -12912,12 +13097,7 @@ var ThreadService = class {
|
|
|
12912
13097
|
});
|
|
12913
13098
|
}
|
|
12914
13099
|
this.assertCodexHooksFileManagement(record.provider);
|
|
12915
|
-
await
|
|
12916
|
-
codexHome: this.codexHome,
|
|
12917
|
-
workspacePath: workspace.absPath,
|
|
12918
|
-
input
|
|
12919
|
-
});
|
|
12920
|
-
await trustHookForInput(runtime, workspace.absPath, input);
|
|
13100
|
+
await this.codexManagement.writeHookEntry(runtime, workspace.absPath, input);
|
|
12921
13101
|
return this.listThreadHooks(localThreadId);
|
|
12922
13102
|
}
|
|
12923
13103
|
async updateThreadHook(localThreadId, input) {
|
|
@@ -12943,12 +13123,7 @@ var ThreadService = class {
|
|
|
12943
13123
|
});
|
|
12944
13124
|
}
|
|
12945
13125
|
this.assertCodexHooksFileManagement(record.provider);
|
|
12946
|
-
await
|
|
12947
|
-
codexHome: this.codexHome,
|
|
12948
|
-
workspacePath: workspace.absPath,
|
|
12949
|
-
input
|
|
12950
|
-
});
|
|
12951
|
-
await trustHookForInput(runtime, workspace.absPath, input);
|
|
13126
|
+
await this.codexManagement.updateHookEntry(runtime, workspace.absPath, input);
|
|
12952
13127
|
return this.listThreadHooks(localThreadId);
|
|
12953
13128
|
}
|
|
12954
13129
|
async trustThreadHook(localThreadId, input) {
|
|
@@ -13051,7 +13226,7 @@ var ThreadService = class {
|
|
|
13051
13226
|
const workspace = getWorkspaceRecordById(this.db, record.workspaceId);
|
|
13052
13227
|
if (workspace) {
|
|
13053
13228
|
const tempDirectory = threadTempDirectoryPath(workspace.absPath, localThreadId);
|
|
13054
|
-
await
|
|
13229
|
+
await fs9.rm(tempDirectory, { recursive: true, force: true }).catch(() => {
|
|
13055
13230
|
});
|
|
13056
13231
|
}
|
|
13057
13232
|
this.pendingRequests.delete(localThreadId);
|
|
@@ -13226,7 +13401,7 @@ var ThreadService = class {
|
|
|
13226
13401
|
if (!record) {
|
|
13227
13402
|
return;
|
|
13228
13403
|
}
|
|
13229
|
-
const tokenUsage =
|
|
13404
|
+
const tokenUsage = isRecord5(event.usage) ? event.usage : null;
|
|
13230
13405
|
const usage = buildThreadContextUsageFromPayload(
|
|
13231
13406
|
tokenUsage,
|
|
13232
13407
|
record.model
|
|
@@ -13242,7 +13417,7 @@ var ThreadService = class {
|
|
|
13242
13417
|
);
|
|
13243
13418
|
const previousCumulativeTotal = this.threadCumulativeTokenUsage.get(record.id);
|
|
13244
13419
|
const currentCumulativeTotal = buildTurnTokenBreakdown(
|
|
13245
|
-
|
|
13420
|
+
isRecord5(tokenUsage?.total) ? tokenUsage.total : null
|
|
13246
13421
|
);
|
|
13247
13422
|
if (currentCumulativeTotal) {
|
|
13248
13423
|
this.threadCumulativeTokenUsage.set(record.id, currentCumulativeTotal);
|
|
@@ -13489,7 +13664,7 @@ var ThreadService = class {
|
|
|
13489
13664
|
const defaultMappedRequest = runtime.mapProviderRequest?.(request, {
|
|
13490
13665
|
approvalMode: "guarded"
|
|
13491
13666
|
});
|
|
13492
|
-
const providerSessionIdFromParams =
|
|
13667
|
+
const providerSessionIdFromParams = isRecord5(request.params) ? request.params.providerSessionId ?? request.params.threadId ?? request.params.conversationId ?? request.params.sessionId : null;
|
|
13493
13668
|
const providerSessionId = defaultMappedRequest?.providerSessionId ?? (typeof providerSessionIdFromParams === "string" ? providerSessionIdFromParams : null);
|
|
13494
13669
|
const record = providerSessionId ? this.findRecordByProviderSessionId(request.provider, providerSessionId) : null;
|
|
13495
13670
|
if (!record) {
|
|
@@ -13659,8 +13834,7 @@ var ThreadService = class {
|
|
|
13659
13834
|
});
|
|
13660
13835
|
}
|
|
13661
13836
|
async toThreadHooksDto(provider, workspacePath, entry, fallbackWarnings = []) {
|
|
13662
|
-
const globalHooksPath =
|
|
13663
|
-
const projectHooksPath = path8.join(workspacePath, ".codex", "hooks.json");
|
|
13837
|
+
const { globalHooksPath, projectHooksPath } = this.codexManagement.hooksPaths(workspacePath);
|
|
13664
13838
|
const officialHooks = (entry?.hooks ?? []).map((hook) => ({
|
|
13665
13839
|
key: hook.key,
|
|
13666
13840
|
eventName: hook.eventName,
|
|
@@ -13679,12 +13853,12 @@ var ThreadService = class {
|
|
|
13679
13853
|
trustStatus: hook.trustStatus
|
|
13680
13854
|
}));
|
|
13681
13855
|
const [globalHooks, projectHooks] = this.isCodexProvider(provider) ? await Promise.all([
|
|
13682
|
-
readLocalHookDtos({
|
|
13856
|
+
this.codexManagement.readLocalHookDtos({
|
|
13683
13857
|
hooksPath: globalHooksPath,
|
|
13684
13858
|
source: "user",
|
|
13685
13859
|
displayOffset: officialHooks.length
|
|
13686
13860
|
}),
|
|
13687
|
-
readLocalHookDtos({
|
|
13861
|
+
this.codexManagement.readLocalHookDtos({
|
|
13688
13862
|
hooksPath: projectHooksPath,
|
|
13689
13863
|
source: "project",
|
|
13690
13864
|
displayOffset: officialHooks.length + 1e4
|
|
@@ -14266,8 +14440,8 @@ async function registerSystemRoutes(app2) {
|
|
|
14266
14440
|
}
|
|
14267
14441
|
|
|
14268
14442
|
// src/routes/threads.ts
|
|
14269
|
-
import
|
|
14270
|
-
import
|
|
14443
|
+
import fs10 from "fs/promises";
|
|
14444
|
+
import path10 from "path";
|
|
14271
14445
|
import { z as z5 } from "zod";
|
|
14272
14446
|
var createThreadSchema = z5.object({
|
|
14273
14447
|
workspaceId: z5.string().uuid(),
|
|
@@ -14304,7 +14478,7 @@ var updateThreadSettingsSchema = z5.object({
|
|
|
14304
14478
|
});
|
|
14305
14479
|
var updateThreadGoalSchema = z5.object({
|
|
14306
14480
|
objective: z5.string().min(1).nullable().optional(),
|
|
14307
|
-
status: z5.enum(["active", "paused", "budgetLimited", "complete"]).nullable().optional(),
|
|
14481
|
+
status: z5.enum(["active", "paused", "budgetLimited", "complete", "terminated"]).nullable().optional(),
|
|
14308
14482
|
tokenBudget: z5.number().int().positive().nullable().optional()
|
|
14309
14483
|
}).refine((body) => Object.keys(body).length > 0, {
|
|
14310
14484
|
message: "At least one goal field must be provided."
|
|
@@ -14626,23 +14800,23 @@ async function registerThreadRoutes(app2) {
|
|
|
14626
14800
|
message: "Workspace was not found for this thread."
|
|
14627
14801
|
});
|
|
14628
14802
|
}
|
|
14629
|
-
const candidatePath =
|
|
14630
|
-
const requestedPath = await
|
|
14803
|
+
const candidatePath = path10.isAbsolute(query.path) ? query.path : path10.resolve(workspace.absPath, query.path);
|
|
14804
|
+
const requestedPath = await fs10.realpath(candidatePath).catch(() => null);
|
|
14631
14805
|
if (!requestedPath) {
|
|
14632
14806
|
throw new HttpError(404, {
|
|
14633
14807
|
code: "not_found",
|
|
14634
14808
|
message: "Image file was not found."
|
|
14635
14809
|
});
|
|
14636
14810
|
}
|
|
14637
|
-
const resolvedWorkspaceRoot = await
|
|
14638
|
-
const workspacePrefix = resolvedWorkspaceRoot.endsWith(
|
|
14811
|
+
const resolvedWorkspaceRoot = await fs10.realpath(app2.services.config.workspaceRoot).catch(() => path10.resolve(app2.services.config.workspaceRoot));
|
|
14812
|
+
const workspacePrefix = resolvedWorkspaceRoot.endsWith(path10.sep) ? resolvedWorkspaceRoot : `${resolvedWorkspaceRoot}${path10.sep}`;
|
|
14639
14813
|
if (requestedPath !== resolvedWorkspaceRoot && !requestedPath.startsWith(workspacePrefix)) {
|
|
14640
14814
|
throw new HttpError(403, {
|
|
14641
14815
|
code: "forbidden",
|
|
14642
14816
|
message: "Image path must stay within the configured workspace root."
|
|
14643
14817
|
});
|
|
14644
14818
|
}
|
|
14645
|
-
const stats = await
|
|
14819
|
+
const stats = await fs10.stat(requestedPath).catch(() => null);
|
|
14646
14820
|
if (!stats?.isFile()) {
|
|
14647
14821
|
throw new HttpError(404, {
|
|
14648
14822
|
code: "not_found",
|
|
@@ -14653,7 +14827,7 @@ async function registerThreadRoutes(app2) {
|
|
|
14653
14827
|
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
14828
|
reply.header("Content-Type", contentType);
|
|
14655
14829
|
reply.header("Cache-Control", "private, max-age=60");
|
|
14656
|
-
return reply.send(await
|
|
14830
|
+
return reply.send(await fs10.readFile(requestedPath));
|
|
14657
14831
|
});
|
|
14658
14832
|
app2.patch("/api/threads/:id", async (request) => {
|
|
14659
14833
|
const params = z5.object({ id: z5.string().uuid() }).parse(request.params);
|
|
@@ -14827,8 +15001,8 @@ async function registerThreadRoutes(app2) {
|
|
|
14827
15001
|
}
|
|
14828
15002
|
|
|
14829
15003
|
// src/routes/workspaces.ts
|
|
14830
|
-
import
|
|
14831
|
-
import
|
|
15004
|
+
import fs11 from "fs/promises";
|
|
15005
|
+
import path11 from "path";
|
|
14832
15006
|
import { spawn as spawn2 } from "child_process";
|
|
14833
15007
|
import { z as z6 } from "zod";
|
|
14834
15008
|
var createWorkspaceSchema = z6.union([
|
|
@@ -14868,7 +15042,7 @@ function inferGitRepoName(gitUrl) {
|
|
|
14868
15042
|
const normalized = withoutQuery.replace(/[\\/]+$/, "");
|
|
14869
15043
|
const rawName = normalized.split(/[/:]/).filter(Boolean).at(-1) ?? "";
|
|
14870
15044
|
const repoName = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
|
|
14871
|
-
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(
|
|
15045
|
+
if (!repoName || repoName === "." || repoName === ".." || repoName.includes(path11.sep)) {
|
|
14872
15046
|
throw new HttpError(400, {
|
|
14873
15047
|
code: "bad_request",
|
|
14874
15048
|
message: "Unable to infer a target directory from the Git URL."
|
|
@@ -14878,7 +15052,7 @@ function inferGitRepoName(gitUrl) {
|
|
|
14878
15052
|
}
|
|
14879
15053
|
async function pathExists2(absPath) {
|
|
14880
15054
|
try {
|
|
14881
|
-
await
|
|
15055
|
+
await fs11.stat(absPath);
|
|
14882
15056
|
return true;
|
|
14883
15057
|
} catch (error) {
|
|
14884
15058
|
if (error.code === "ENOENT") {
|
|
@@ -14933,7 +15107,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
14933
15107
|
});
|
|
14934
15108
|
app2.get("/api/workspaces/tree", async (request) => {
|
|
14935
15109
|
const query = treeQuerySchema.parse(request.query);
|
|
14936
|
-
const requestedPath = query.path ?
|
|
15110
|
+
const requestedPath = query.path ? path11.resolve(query.path) : app2.services.config.workspaceRoot;
|
|
14937
15111
|
const tree = await readWorkspaceTree({
|
|
14938
15112
|
rootPath: app2.services.config.workspaceRoot,
|
|
14939
15113
|
targetPath: requestedPath,
|
|
@@ -14965,7 +15139,7 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
14965
15139
|
let validated;
|
|
14966
15140
|
if ("gitUrl" in body) {
|
|
14967
15141
|
const repoName = inferGitRepoName(body.gitUrl);
|
|
14968
|
-
const targetPath =
|
|
15142
|
+
const targetPath = path11.join(settings.devHome, repoName);
|
|
14969
15143
|
if (await pathExists2(targetPath)) {
|
|
14970
15144
|
throw new HttpError(409, {
|
|
14971
15145
|
code: "conflict",
|
|
@@ -15074,8 +15248,8 @@ async function registerWorkspaceRoutes(app2) {
|
|
|
15074
15248
|
}
|
|
15075
15249
|
|
|
15076
15250
|
// src/provider-host-config-service.ts
|
|
15077
|
-
import
|
|
15078
|
-
import
|
|
15251
|
+
import fs12 from "fs/promises";
|
|
15252
|
+
import path12 from "path";
|
|
15079
15253
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
15080
15254
|
function providerError(message, statusCode = 404) {
|
|
15081
15255
|
const error = new Error(message);
|
|
@@ -15083,23 +15257,23 @@ function providerError(message, statusCode = 404) {
|
|
|
15083
15257
|
return error;
|
|
15084
15258
|
}
|
|
15085
15259
|
function resolveProviderHostFilePath(providerHome, name) {
|
|
15086
|
-
return
|
|
15260
|
+
return path12.join(providerHome, name);
|
|
15087
15261
|
}
|
|
15088
15262
|
function resolveArchiveRoot(providerHome) {
|
|
15089
|
-
return
|
|
15263
|
+
return path12.join(providerHome, "supervisor-config-archives");
|
|
15090
15264
|
}
|
|
15091
15265
|
function resolveArchiveIndexPath(providerHome) {
|
|
15092
|
-
return
|
|
15266
|
+
return path12.join(resolveArchiveRoot(providerHome), "index.json");
|
|
15093
15267
|
}
|
|
15094
15268
|
function resolveArchivePath(providerHome, archiveId) {
|
|
15095
|
-
return
|
|
15269
|
+
return path12.join(resolveArchiveRoot(providerHome), archiveId);
|
|
15096
15270
|
}
|
|
15097
15271
|
function defaultArchiveLabel(createdAt) {
|
|
15098
15272
|
return `Backup ${createdAt.replace("T", " ").replace(/\.\d{3}Z$/, " UTC")}`;
|
|
15099
15273
|
}
|
|
15100
15274
|
async function readArchiveIndex(providerHome) {
|
|
15101
15275
|
try {
|
|
15102
|
-
const raw = await
|
|
15276
|
+
const raw = await fs12.readFile(resolveArchiveIndexPath(providerHome), "utf8");
|
|
15103
15277
|
const parsed = JSON.parse(raw);
|
|
15104
15278
|
return {
|
|
15105
15279
|
archives: Array.isArray(parsed.archives) ? parsed.archives : []
|
|
@@ -15113,8 +15287,8 @@ async function readArchiveIndex(providerHome) {
|
|
|
15113
15287
|
}
|
|
15114
15288
|
async function writeArchiveIndex(providerHome, index) {
|
|
15115
15289
|
const root = resolveArchiveRoot(providerHome);
|
|
15116
|
-
await
|
|
15117
|
-
await
|
|
15290
|
+
await fs12.mkdir(root, { recursive: true });
|
|
15291
|
+
await fs12.writeFile(
|
|
15118
15292
|
resolveArchiveIndexPath(providerHome),
|
|
15119
15293
|
`${JSON.stringify(index, null, 2)}
|
|
15120
15294
|
`,
|
|
@@ -15185,7 +15359,7 @@ var ProviderHostConfigService = class {
|
|
|
15185
15359
|
const fileName = this.assertHostFile(provider, name);
|
|
15186
15360
|
const filePath = resolveProviderHostFilePath(providerHome, fileName);
|
|
15187
15361
|
try {
|
|
15188
|
-
const content = await
|
|
15362
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
15189
15363
|
return {
|
|
15190
15364
|
name: fileName,
|
|
15191
15365
|
path: filePath,
|
|
@@ -15208,8 +15382,8 @@ var ProviderHostConfigService = class {
|
|
|
15208
15382
|
const providerHome = this.providerHome(provider);
|
|
15209
15383
|
const fileName = this.assertHostFile(provider, name);
|
|
15210
15384
|
const filePath = resolveProviderHostFilePath(providerHome, fileName);
|
|
15211
|
-
await
|
|
15212
|
-
await
|
|
15385
|
+
await fs12.mkdir(path12.dirname(filePath), { recursive: true });
|
|
15386
|
+
await fs12.writeFile(filePath, input.content, "utf8");
|
|
15213
15387
|
return this.readFile(provider, fileName);
|
|
15214
15388
|
}
|
|
15215
15389
|
async listArchives(provider) {
|
|
@@ -15235,7 +15409,7 @@ var ProviderHostConfigService = class {
|
|
|
15235
15409
|
}
|
|
15236
15410
|
])
|
|
15237
15411
|
);
|
|
15238
|
-
await
|
|
15412
|
+
await fs12.mkdir(archivePath, { recursive: true });
|
|
15239
15413
|
for (const name of fileNames) {
|
|
15240
15414
|
const hostFile = await this.readFile(provider, name);
|
|
15241
15415
|
files[name] = {
|
|
@@ -15243,7 +15417,7 @@ var ProviderHostConfigService = class {
|
|
|
15243
15417
|
exists: hostFile.exists
|
|
15244
15418
|
};
|
|
15245
15419
|
if (hostFile.exists) {
|
|
15246
|
-
await
|
|
15420
|
+
await fs12.writeFile(path12.join(archivePath, name), hostFile.content, "utf8");
|
|
15247
15421
|
}
|
|
15248
15422
|
}
|
|
15249
15423
|
const archive = {
|
|
@@ -15279,14 +15453,14 @@ var ProviderHostConfigService = class {
|
|
|
15279
15453
|
const fileNames = this.archiveFileNames(provider);
|
|
15280
15454
|
const { archive } = await findArchiveOrThrow(providerHome, id);
|
|
15281
15455
|
const archivePath = resolveArchivePath(providerHome, archive.id);
|
|
15282
|
-
await
|
|
15456
|
+
await fs12.mkdir(providerHome, { recursive: true });
|
|
15283
15457
|
for (const name of fileNames) {
|
|
15284
15458
|
const hostPath = resolveProviderHostFilePath(providerHome, name);
|
|
15285
15459
|
if (archive.files[name]?.exists) {
|
|
15286
|
-
const content = await
|
|
15287
|
-
await
|
|
15460
|
+
const content = await fs12.readFile(path12.join(archivePath, name), "utf8");
|
|
15461
|
+
await fs12.writeFile(hostPath, content, "utf8");
|
|
15288
15462
|
} else {
|
|
15289
|
-
await
|
|
15463
|
+
await fs12.rm(hostPath, { force: true });
|
|
15290
15464
|
}
|
|
15291
15465
|
}
|
|
15292
15466
|
await runtime.stop();
|
|
@@ -15299,12 +15473,12 @@ var ProviderHostConfigService = class {
|
|
|
15299
15473
|
};
|
|
15300
15474
|
|
|
15301
15475
|
// src/shell/shell-session-service.ts
|
|
15302
|
-
import
|
|
15476
|
+
import fs13 from "fs/promises";
|
|
15303
15477
|
import os3 from "os";
|
|
15304
|
-
import
|
|
15478
|
+
import path13 from "path";
|
|
15305
15479
|
async function pathExists3(filePath) {
|
|
15306
15480
|
try {
|
|
15307
|
-
await
|
|
15481
|
+
await fs13.access(filePath);
|
|
15308
15482
|
return true;
|
|
15309
15483
|
} catch {
|
|
15310
15484
|
return false;
|
|
@@ -15329,7 +15503,7 @@ function basenameFromPath2(filePath) {
|
|
|
15329
15503
|
if (!normalized) {
|
|
15330
15504
|
return "";
|
|
15331
15505
|
}
|
|
15332
|
-
return
|
|
15506
|
+
return path13.basename(normalized) || normalized;
|
|
15333
15507
|
}
|
|
15334
15508
|
function isInteractiveShellCommand(command) {
|
|
15335
15509
|
const normalized = (command ?? "").trim().toLowerCase();
|
|
@@ -15490,11 +15664,11 @@ function buildShellPromptInitScriptContents(command) {
|
|
|
15490
15664
|
async function ensureShellPromptInitScript(command) {
|
|
15491
15665
|
const normalized = command.trim().toLowerCase();
|
|
15492
15666
|
const extension = normalized === "zsh" ? "zsh" : "sh";
|
|
15493
|
-
const filePath =
|
|
15667
|
+
const filePath = path13.join(
|
|
15494
15668
|
os3.tmpdir(),
|
|
15495
15669
|
`remote-codex-shell-prompt.${extension}`
|
|
15496
15670
|
);
|
|
15497
|
-
await
|
|
15671
|
+
await fs13.writeFile(filePath, buildShellPromptInitScriptContents(command), "utf8");
|
|
15498
15672
|
return filePath;
|
|
15499
15673
|
}
|
|
15500
15674
|
async function buildShellPromptInitCommand(command, options = {}) {
|
|
@@ -16080,8 +16254,8 @@ var ShellSessionService = class {
|
|
|
16080
16254
|
};
|
|
16081
16255
|
|
|
16082
16256
|
// src/shell/tmux-manager.ts
|
|
16083
|
-
import
|
|
16084
|
-
import
|
|
16257
|
+
import fs14 from "fs";
|
|
16258
|
+
import path14 from "path";
|
|
16085
16259
|
import { spawn as spawnChild } from "child_process";
|
|
16086
16260
|
async function defaultExecCommand(command, args) {
|
|
16087
16261
|
return await new Promise((resolve, reject) => {
|
|
@@ -16108,17 +16282,17 @@ async function defaultExecCommand(command, args) {
|
|
|
16108
16282
|
});
|
|
16109
16283
|
}
|
|
16110
16284
|
function resolveExecutablePath(command) {
|
|
16111
|
-
if (command.includes(
|
|
16285
|
+
if (command.includes(path14.sep)) {
|
|
16112
16286
|
return command;
|
|
16113
16287
|
}
|
|
16114
16288
|
const searchPath = process.env.PATH ?? "";
|
|
16115
|
-
for (const entry of searchPath.split(
|
|
16289
|
+
for (const entry of searchPath.split(path14.delimiter)) {
|
|
16116
16290
|
const trimmed = entry.trim();
|
|
16117
16291
|
if (!trimmed) {
|
|
16118
16292
|
continue;
|
|
16119
16293
|
}
|
|
16120
|
-
const candidate =
|
|
16121
|
-
if (
|
|
16294
|
+
const candidate = path14.join(trimmed, command);
|
|
16295
|
+
if (fs14.existsSync(candidate)) {
|
|
16122
16296
|
return candidate;
|
|
16123
16297
|
}
|
|
16124
16298
|
}
|
|
@@ -16447,16 +16621,16 @@ var HttpError = class extends Error {
|
|
|
16447
16621
|
};
|
|
16448
16622
|
function findRepoRoot(start = process.cwd()) {
|
|
16449
16623
|
if (process.env.REMOTE_CODEX_REPO_ROOT) {
|
|
16450
|
-
return
|
|
16624
|
+
return path15.resolve(process.env.REMOTE_CODEX_REPO_ROOT);
|
|
16451
16625
|
}
|
|
16452
|
-
let current =
|
|
16453
|
-
while (current !==
|
|
16454
|
-
if (
|
|
16626
|
+
let current = path15.resolve(start);
|
|
16627
|
+
while (current !== path15.dirname(current)) {
|
|
16628
|
+
if (fs15.existsSync(path15.join(current, "pnpm-workspace.yaml")) && fs15.existsSync(path15.join(current, "scripts", "service-restart.mjs"))) {
|
|
16455
16629
|
return current;
|
|
16456
16630
|
}
|
|
16457
|
-
current =
|
|
16631
|
+
current = path15.dirname(current);
|
|
16458
16632
|
}
|
|
16459
|
-
return
|
|
16633
|
+
return path15.resolve(process.cwd());
|
|
16460
16634
|
}
|
|
16461
16635
|
function createServiceLifecycle() {
|
|
16462
16636
|
return {
|
|
@@ -16468,8 +16642,8 @@ function createServiceLifecycle() {
|
|
|
16468
16642
|
});
|
|
16469
16643
|
}
|
|
16470
16644
|
const repoRoot = findRepoRoot();
|
|
16471
|
-
const restartScript =
|
|
16472
|
-
if (!
|
|
16645
|
+
const restartScript = path15.join(repoRoot, "scripts", "service-restart.mjs");
|
|
16646
|
+
if (!fs15.existsSync(restartScript) || !fs15.existsSync(path15.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
16473
16647
|
throw new HttpError(503, {
|
|
16474
16648
|
code: "service_unavailable",
|
|
16475
16649
|
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 +16674,7 @@ function buildApp(options = {}) {
|
|
|
16500
16674
|
eventBus,
|
|
16501
16675
|
runtimeBootstrap.localCodexSessionStore,
|
|
16502
16676
|
config.workspaceRoot,
|
|
16503
|
-
|
|
16677
|
+
runtimeBootstrap.codexManagement
|
|
16504
16678
|
);
|
|
16505
16679
|
const shellService = options.shellService ?? new ShellSessionService(database.db, eventBus, new TmuxManager());
|
|
16506
16680
|
const providerHostConfigService = new ProviderHostConfigService(
|
|
@@ -16810,7 +16984,7 @@ function makeShellErrorEnvelope(shellId, error) {
|
|
|
16810
16984
|
}
|
|
16811
16985
|
|
|
16812
16986
|
// src/index.ts
|
|
16813
|
-
if (
|
|
16987
|
+
if (fs16.existsSync(".env")) {
|
|
16814
16988
|
process.loadEnvFile?.(".env");
|
|
16815
16989
|
}
|
|
16816
16990
|
var app = buildApp();
|