replicas-engine 0.1.223 → 0.1.225
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/dist/src/index.js +1568 -1082
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -20,6 +20,11 @@ import { readFileSync } from "fs";
|
|
|
20
20
|
import { homedir } from "os";
|
|
21
21
|
import { join } from "path";
|
|
22
22
|
|
|
23
|
+
// ../shared/src/type-guards.ts
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
// ../shared/src/agent.ts
|
|
24
29
|
var CODEX_REASONING_EFFORT_BY_THINKING_LEVEL = {
|
|
25
30
|
low: "low",
|
|
@@ -35,9 +40,11 @@ function codexReasoningEffortForThinkingLevel(thinkingLevel) {
|
|
|
35
40
|
var ACCEPTED_USER_MESSAGE_SOURCE = "replicas-chat-turn-accepted";
|
|
36
41
|
var USER_MESSAGE_ID_PAYLOAD_KEY = "replicasMessageId";
|
|
37
42
|
var CODEX_ASP_ITEM_ID_PAYLOAD_KEY = "codexAspItemId";
|
|
43
|
+
var CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE = "codex-asp-transcript-updated";
|
|
38
44
|
var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
|
|
39
45
|
var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
|
|
40
46
|
var CHAT_GOAL_EVENT_TYPE = "chat-goal";
|
|
47
|
+
var AUTH_RETRY_STATUS_EVENT_TYPE = "auth-retry-status";
|
|
41
48
|
var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
|
|
42
49
|
|
|
43
50
|
// ../shared/src/languages.ts
|
|
@@ -1647,7 +1654,7 @@ var DEFAULT_WARM_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
1647
1654
|
var MAX_WARM_HOOK_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1648
1655
|
var DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS = 1e5;
|
|
1649
1656
|
var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
1650
|
-
function
|
|
1657
|
+
function isRecord2(value) {
|
|
1651
1658
|
return typeof value === "object" && value !== null;
|
|
1652
1659
|
}
|
|
1653
1660
|
function clampWarmHookTimeoutMs(timeoutMs) {
|
|
@@ -1671,7 +1678,7 @@ function parseWarmHookConfig(value, filename = "replicas.json") {
|
|
|
1671
1678
|
if (typeof value === "string") {
|
|
1672
1679
|
return value;
|
|
1673
1680
|
}
|
|
1674
|
-
if (!
|
|
1681
|
+
if (!isRecord2(value)) {
|
|
1675
1682
|
throw new Error(`Invalid ${filename}: "warmHook" must be a string or object`);
|
|
1676
1683
|
}
|
|
1677
1684
|
if (!Array.isArray(value.commands) || !value.commands.every((entry) => typeof entry === "string")) {
|
|
@@ -1707,11 +1714,11 @@ function resolveWarmHookConfig(value) {
|
|
|
1707
1714
|
|
|
1708
1715
|
// ../shared/src/replicas-config.ts
|
|
1709
1716
|
var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
|
|
1710
|
-
function
|
|
1717
|
+
function isRecord3(value) {
|
|
1711
1718
|
return typeof value === "object" && value !== null;
|
|
1712
1719
|
}
|
|
1713
1720
|
function parseReplicasConfig(value, filename = "replicas.json") {
|
|
1714
|
-
if (!
|
|
1721
|
+
if (!isRecord3(value)) {
|
|
1715
1722
|
throw new Error(`Invalid ${filename}: expected an object`);
|
|
1716
1723
|
}
|
|
1717
1724
|
const config = {};
|
|
@@ -1728,7 +1735,7 @@ function parseReplicasConfig(value, filename = "replicas.json") {
|
|
|
1728
1735
|
config.systemPrompt = value.systemPrompt;
|
|
1729
1736
|
}
|
|
1730
1737
|
if ("startHook" in value) {
|
|
1731
|
-
if (!
|
|
1738
|
+
if (!isRecord3(value.startHook)) {
|
|
1732
1739
|
throw new Error(`Invalid ${filename}: "startHook" must be an object with "commands" array`);
|
|
1733
1740
|
}
|
|
1734
1741
|
const { commands, timeout, separate } = value.startHook;
|
|
@@ -1759,8 +1766,14 @@ function parseReplicasConfigString(content, filename) {
|
|
|
1759
1766
|
return parseReplicasConfig(parsed, filename);
|
|
1760
1767
|
}
|
|
1761
1768
|
|
|
1769
|
+
// ../shared/src/claude-auth.ts
|
|
1770
|
+
function isClaudeAuthErrorText(text) {
|
|
1771
|
+
const lower = text.toLowerCase();
|
|
1772
|
+
return lower.includes("failed to authenticate") || lower.includes("authentication_error") || lower.includes("authentication_failed") || lower.includes("authentication failed") || lower.includes("invalid authentication credentials") || lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("401") && lower.includes("authentic");
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1762
1775
|
// ../shared/src/engine/environment.ts
|
|
1763
|
-
var DAYTONA_SNAPSHOT_ID = "
|
|
1776
|
+
var DAYTONA_SNAPSHOT_ID = "28-05-2026-royal-york-v2";
|
|
1764
1777
|
|
|
1765
1778
|
// ../shared/src/engine/types.ts
|
|
1766
1779
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -1799,6 +1812,15 @@ var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
|
1799
1812
|
|
|
1800
1813
|
// ../shared/src/engine/v1.ts
|
|
1801
1814
|
var MERGED_MESSAGE_SEPARATOR = "\n\n<!-- replicas:merged -->\n\n";
|
|
1815
|
+
function normalizeCodexAspTranscriptStatus(status, failed = false) {
|
|
1816
|
+
if (failed || status === "failed") return "failed";
|
|
1817
|
+
if (status === "completed") return "completed";
|
|
1818
|
+
return "in_progress";
|
|
1819
|
+
}
|
|
1820
|
+
function isCodexAspTranscript(value) {
|
|
1821
|
+
if (!isRecord(value)) return false;
|
|
1822
|
+
return typeof value.threadId === "string" && typeof value.updatedAt === "string" && Array.isArray(value.turns);
|
|
1823
|
+
}
|
|
1802
1824
|
|
|
1803
1825
|
// ../shared/src/routes/codex.ts
|
|
1804
1826
|
var CODEX_AUTH_ENV_KEYS = [
|
|
@@ -1872,14 +1894,6 @@ var AUDIT_LOG_ACTION = {
|
|
|
1872
1894
|
};
|
|
1873
1895
|
var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
|
|
1874
1896
|
|
|
1875
|
-
// ../shared/src/object-store/types.ts
|
|
1876
|
-
var MEDIA_KIND = {
|
|
1877
|
-
IMAGE: "image",
|
|
1878
|
-
VIDEO: "video",
|
|
1879
|
-
AUDIO: "audio"
|
|
1880
|
-
};
|
|
1881
|
-
var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
|
|
1882
|
-
|
|
1883
1897
|
// ../shared/src/agent-event-utils.ts
|
|
1884
1898
|
function getUserMessage(event) {
|
|
1885
1899
|
return event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string" ? event.payload.message : null;
|
|
@@ -1892,9 +1906,12 @@ function getUserMessageItemId(event) {
|
|
|
1892
1906
|
const itemId = event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY];
|
|
1893
1907
|
return typeof itemId === "string" ? itemId : null;
|
|
1894
1908
|
}
|
|
1909
|
+
function parseTimestampMs(timestamp) {
|
|
1910
|
+
const value = Date.parse(timestamp);
|
|
1911
|
+
return Number.isFinite(value) ? value : 0;
|
|
1912
|
+
}
|
|
1895
1913
|
function getEventTimestampMs(event) {
|
|
1896
|
-
|
|
1897
|
-
return Number.isNaN(value) ? 0 : value;
|
|
1914
|
+
return parseTimestampMs(event.timestamp);
|
|
1898
1915
|
}
|
|
1899
1916
|
function areSameUserMessageEvents(a, b) {
|
|
1900
1917
|
const aMessage = getUserMessage(a);
|
|
@@ -1908,21 +1925,17 @@ function areSameUserMessageEvents(a, b) {
|
|
|
1908
1925
|
if (aItemId || bItemId) return aItemId === bItemId;
|
|
1909
1926
|
return Math.abs(getEventTimestampMs(a) - getEventTimestampMs(b)) <= 3e4;
|
|
1910
1927
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
return merged;
|
|
1925
|
-
}
|
|
1928
|
+
|
|
1929
|
+
// ../shared/src/display-message/parsers/codex-asp-parser.ts
|
|
1930
|
+
var DUPLICATE_WINDOW_MS = 5 * 60 * 1e3;
|
|
1931
|
+
|
|
1932
|
+
// ../shared/src/object-store/types.ts
|
|
1933
|
+
var MEDIA_KIND = {
|
|
1934
|
+
IMAGE: "image",
|
|
1935
|
+
VIDEO: "video",
|
|
1936
|
+
AUDIO: "audio"
|
|
1937
|
+
};
|
|
1938
|
+
var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
|
|
1926
1939
|
|
|
1927
1940
|
// src/runtime-env-loader.ts
|
|
1928
1941
|
function loadRuntimeEnvFile() {
|
|
@@ -2006,8 +2019,7 @@ function loadEngineEnv() {
|
|
|
2006
2019
|
AWS_REGION: readEnv("AWS_REGION"),
|
|
2007
2020
|
REPLICAS_CLAUDE_AUTH_METHOD: parseClaudeAuthMethod(readEnv("REPLICAS_CLAUDE_AUTH_METHOD")),
|
|
2008
2021
|
REPLICAS_CODEX_AUTH_METHOD: parseCodexAuthMethod(readEnv("REPLICAS_CODEX_AUTH_METHOD")),
|
|
2009
|
-
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
|
|
2010
|
-
CODEX_ASP_ENABLED: readEnv("CODEX_ASP_ENABLED")?.toLowerCase() === "true"
|
|
2022
|
+
REPLICAS_ENV_SYSTEM_PROMPT: readEnv("REPLICAS_ENV_SYSTEM_PROMPT")
|
|
2011
2023
|
};
|
|
2012
2024
|
if (!IS_WARMING_MODE && !env.WORKSPACE_ID) {
|
|
2013
2025
|
console.error("WORKSPACE_ID is not set \u2014 this is required in normal (non-warming) mode");
|
|
@@ -2416,7 +2428,7 @@ import { join as join3 } from "path";
|
|
|
2416
2428
|
import { homedir as homedir3 } from "os";
|
|
2417
2429
|
|
|
2418
2430
|
// src/utils/type-guards.ts
|
|
2419
|
-
function
|
|
2431
|
+
function isRecord4(value) {
|
|
2420
2432
|
return typeof value === "object" && value !== null;
|
|
2421
2433
|
}
|
|
2422
2434
|
|
|
@@ -2448,10 +2460,10 @@ async function updateEngineState(updater) {
|
|
|
2448
2460
|
});
|
|
2449
2461
|
}
|
|
2450
2462
|
function isEngineRepoDiff(value) {
|
|
2451
|
-
return
|
|
2463
|
+
return isRecord4(value) && typeof value.added === "number" && typeof value.removed === "number";
|
|
2452
2464
|
}
|
|
2453
2465
|
function coerceRepoState(value) {
|
|
2454
|
-
if (!
|
|
2466
|
+
if (!isRecord4(value)) {
|
|
2455
2467
|
return null;
|
|
2456
2468
|
}
|
|
2457
2469
|
if (typeof value.name !== "string") return null;
|
|
@@ -2477,11 +2489,11 @@ function coerceRepoState(value) {
|
|
|
2477
2489
|
};
|
|
2478
2490
|
}
|
|
2479
2491
|
function coerceEngineState(value) {
|
|
2480
|
-
if (!
|
|
2492
|
+
if (!isRecord4(value)) {
|
|
2481
2493
|
return {};
|
|
2482
2494
|
}
|
|
2483
2495
|
const partial = {};
|
|
2484
|
-
if (
|
|
2496
|
+
if (isRecord4(value.repos)) {
|
|
2485
2497
|
const repos = {};
|
|
2486
2498
|
for (const [repoName, repoState] of Object.entries(value.repos)) {
|
|
2487
2499
|
const coerced = coerceRepoState(repoState);
|
|
@@ -3693,10 +3705,10 @@ import { homedir as homedir11 } from "os";
|
|
|
3693
3705
|
// src/utils/jsonl-reader.ts
|
|
3694
3706
|
import { readFile as readFile6 } from "fs/promises";
|
|
3695
3707
|
function isJsonlEvent(value) {
|
|
3696
|
-
if (!
|
|
3708
|
+
if (!isRecord4(value)) {
|
|
3697
3709
|
return false;
|
|
3698
3710
|
}
|
|
3699
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" &&
|
|
3711
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord4(value.payload);
|
|
3700
3712
|
}
|
|
3701
3713
|
function parseJsonlEvents(lines) {
|
|
3702
3714
|
const events = [];
|
|
@@ -4526,10 +4538,15 @@ var CodingAgentManager = class {
|
|
|
4526
4538
|
setCompacting(active) {
|
|
4527
4539
|
if (this.compacting === active) return;
|
|
4528
4540
|
this.compacting = active;
|
|
4541
|
+
this.emitSyntheticEvent(COMPACTION_STATUS_EVENT_TYPE, {
|
|
4542
|
+
state: active ? "in_progress" : "completed"
|
|
4543
|
+
});
|
|
4544
|
+
}
|
|
4545
|
+
emitSyntheticEvent(type, payload) {
|
|
4529
4546
|
this.onEvent({
|
|
4530
4547
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4531
|
-
type
|
|
4532
|
-
payload
|
|
4548
|
+
type,
|
|
4549
|
+
payload
|
|
4533
4550
|
});
|
|
4534
4551
|
}
|
|
4535
4552
|
initializeManager(processMessage) {
|
|
@@ -4892,6 +4909,12 @@ var TOOL_INPUT_HANDLERS = {
|
|
|
4892
4909
|
}
|
|
4893
4910
|
}
|
|
4894
4911
|
};
|
|
4912
|
+
var ClaudeAuthError = class extends Error {
|
|
4913
|
+
constructor(message) {
|
|
4914
|
+
super(message);
|
|
4915
|
+
this.name = "ClaudeAuthError";
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4895
4918
|
var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
4896
4919
|
historyFile;
|
|
4897
4920
|
sessionId = null;
|
|
@@ -4905,6 +4928,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
4905
4928
|
disallowedToolsOverride;
|
|
4906
4929
|
/** Active tool-input requests keyed by requestId; resolved when the user selects an option. */
|
|
4907
4930
|
pendingToolInputs = /* @__PURE__ */ new Map();
|
|
4931
|
+
authRetrying = false;
|
|
4908
4932
|
constructor(options) {
|
|
4909
4933
|
super(options);
|
|
4910
4934
|
this.historyFile = options.historyFilePath ?? join13(homedir11(), ".replicas", "claude", "history.jsonl");
|
|
@@ -4939,6 +4963,14 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
4939
4963
|
isAwaitingInput() {
|
|
4940
4964
|
return this.pendingToolInputs.size > 0;
|
|
4941
4965
|
}
|
|
4966
|
+
isAuthRetrying() {
|
|
4967
|
+
return this.authRetrying;
|
|
4968
|
+
}
|
|
4969
|
+
setAuthRetrying(value) {
|
|
4970
|
+
if (this.authRetrying === value) return;
|
|
4971
|
+
this.authRetrying = value;
|
|
4972
|
+
this.emitSyntheticEvent(AUTH_RETRY_STATUS_EVENT_TYPE, { isAuthRetrying: value });
|
|
4973
|
+
}
|
|
4942
4974
|
async respondToToolInput(requestId, selectionId) {
|
|
4943
4975
|
const pending = this.pendingToolInputs.get(requestId);
|
|
4944
4976
|
if (!pending) {
|
|
@@ -5015,29 +5047,73 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5015
5047
|
return result;
|
|
5016
5048
|
};
|
|
5017
5049
|
}
|
|
5018
|
-
/**
|
|
5019
|
-
* Internal method that actually processes the message.
|
|
5020
|
-
* On auth failure, refreshes credentials and retries once.
|
|
5021
|
-
*/
|
|
5022
5050
|
async processMessageInternal(request) {
|
|
5051
|
+
const MAX_AUTH_RETRIES = 2;
|
|
5052
|
+
let lastError;
|
|
5053
|
+
let authRetryExhausted = false;
|
|
5023
5054
|
try {
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
console.warn("[ClaudeManager] Auth failure detected, refreshing credentials and retrying...", error);
|
|
5028
|
-
const refreshed = await claudeTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
|
|
5029
|
-
if (refreshed) {
|
|
5030
|
-
await this.executeQuery(request);
|
|
5055
|
+
for (let attempt = 0; attempt <= MAX_AUTH_RETRIES; attempt++) {
|
|
5056
|
+
try {
|
|
5057
|
+
await this.executeQuery(request, { skipUserMessageRecord: attempt > 0 });
|
|
5031
5058
|
return;
|
|
5059
|
+
} catch (error) {
|
|
5060
|
+
lastError = error;
|
|
5061
|
+
if (!_ClaudeManager.isAuthError(error)) {
|
|
5062
|
+
throw error;
|
|
5063
|
+
}
|
|
5064
|
+
if (attempt === MAX_AUTH_RETRIES) {
|
|
5065
|
+
authRetryExhausted = true;
|
|
5066
|
+
throw error;
|
|
5067
|
+
}
|
|
5068
|
+
console.error(
|
|
5069
|
+
`[ClaudeManager] Auth failure detected (attempt ${attempt + 1}/${MAX_AUTH_RETRIES + 1}), refreshing credentials and retrying...`,
|
|
5070
|
+
error
|
|
5071
|
+
);
|
|
5072
|
+
this.setAuthRetrying(true);
|
|
5073
|
+
const refreshed = await claudeTokenManager.fetchFreshCredentials(
|
|
5074
|
+
error instanceof Error ? error.message : String(error)
|
|
5075
|
+
);
|
|
5076
|
+
if (!refreshed) {
|
|
5077
|
+
authRetryExhausted = true;
|
|
5078
|
+
throw error;
|
|
5079
|
+
}
|
|
5032
5080
|
}
|
|
5033
5081
|
}
|
|
5034
|
-
|
|
5082
|
+
} finally {
|
|
5083
|
+
this.setAuthRetrying(false);
|
|
5084
|
+
if (authRetryExhausted) {
|
|
5085
|
+
await this.emitAuthRetryExhaustedEvent(lastError);
|
|
5086
|
+
}
|
|
5087
|
+
try {
|
|
5088
|
+
await this.onTurnComplete();
|
|
5089
|
+
} catch (error) {
|
|
5090
|
+
console.error("[ClaudeManager] onTurnComplete failed:", error);
|
|
5091
|
+
}
|
|
5035
5092
|
}
|
|
5036
5093
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5094
|
+
async emitAuthRetryExhaustedEvent(error) {
|
|
5095
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
5096
|
+
const event = {
|
|
5097
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5098
|
+
type: "claude-result",
|
|
5099
|
+
payload: {
|
|
5100
|
+
type: "result",
|
|
5101
|
+
subtype: "error_during_execution",
|
|
5102
|
+
is_error: true,
|
|
5103
|
+
result: "Couldn't authenticate with Claude after multiple attempts. Check your credentials in Settings \u2192 Agents and try again.",
|
|
5104
|
+
errors: [detail],
|
|
5105
|
+
session_id: this.sessionId ?? "",
|
|
5106
|
+
parent_tool_use_id: null
|
|
5107
|
+
}
|
|
5108
|
+
};
|
|
5109
|
+
try {
|
|
5110
|
+
await appendFile4(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
|
|
5111
|
+
} catch (writeError) {
|
|
5112
|
+
console.error("[ClaudeManager] Failed to record auth-retry-exhausted event:", writeError);
|
|
5113
|
+
}
|
|
5114
|
+
this.onEvent(event);
|
|
5115
|
+
}
|
|
5116
|
+
async executeQuery(request, options = {}) {
|
|
5041
5117
|
const {
|
|
5042
5118
|
message,
|
|
5043
5119
|
model,
|
|
@@ -5081,7 +5157,9 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5081
5157
|
parent_tool_use_id: null,
|
|
5082
5158
|
session_id: this.sessionId ?? ""
|
|
5083
5159
|
};
|
|
5084
|
-
|
|
5160
|
+
if (!options.skipUserMessageRecord) {
|
|
5161
|
+
await this.recordEvent(userMessage);
|
|
5162
|
+
}
|
|
5085
5163
|
const promptStream = new PromptStream();
|
|
5086
5164
|
promptStream.push(userMessage);
|
|
5087
5165
|
this.activePromptStream = promptStream;
|
|
@@ -5133,6 +5211,11 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5133
5211
|
}
|
|
5134
5212
|
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
5135
5213
|
for await (const msg of response) {
|
|
5214
|
+
const authErrorMessage = _ClaudeManager.detectAuthErrorInMessage(msg);
|
|
5215
|
+
if (authErrorMessage) {
|
|
5216
|
+
this.activePromptStream?.close();
|
|
5217
|
+
throw new ClaudeAuthError(authErrorMessage);
|
|
5218
|
+
}
|
|
5136
5219
|
await this.handleMessage(msg);
|
|
5137
5220
|
if (linearSessionId) {
|
|
5138
5221
|
linearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
|
|
@@ -5150,11 +5233,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5150
5233
|
this.activePromptStream?.close();
|
|
5151
5234
|
this.activePromptStream = null;
|
|
5152
5235
|
this.pendingInterrupt = false;
|
|
5153
|
-
try {
|
|
5154
|
-
await this.onTurnComplete();
|
|
5155
|
-
} catch (error) {
|
|
5156
|
-
console.error("[ClaudeManager] onTurnComplete failed:", error);
|
|
5157
|
-
}
|
|
5158
5236
|
}
|
|
5159
5237
|
}
|
|
5160
5238
|
getContextUsageProvider() {
|
|
@@ -5200,17 +5278,33 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5200
5278
|
console.warn("[ClaudeManager] Failed to record context usage:", error instanceof Error ? error.message : error);
|
|
5201
5279
|
}
|
|
5202
5280
|
}
|
|
5203
|
-
/**
|
|
5204
|
-
* Determines if an error is an OAuth authentication failure from the Claude SDK.
|
|
5205
|
-
* Known patterns:
|
|
5206
|
-
* - "Not logged in · Please run /login"
|
|
5207
|
-
* - "authentication_failed"
|
|
5208
|
-
* - "unauthorized"
|
|
5209
|
-
*/
|
|
5210
5281
|
static isAuthError(error) {
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5282
|
+
if (error instanceof ClaudeAuthError) return true;
|
|
5283
|
+
return isClaudeAuthErrorText(error instanceof Error ? error.message : String(error));
|
|
5284
|
+
}
|
|
5285
|
+
// Done at the message-stream layer (rather than letting the failure flow
|
|
5286
|
+
// downstream as a normal result) so we can suppress emission before the
|
|
5287
|
+
// dashboard renders the raw 401 to the user.
|
|
5288
|
+
static detectAuthErrorInMessage(message) {
|
|
5289
|
+
if (message.type === "assistant" && "error" in message) {
|
|
5290
|
+
const error = message.error;
|
|
5291
|
+
if (typeof error === "string") {
|
|
5292
|
+
const errCode = error.toLowerCase();
|
|
5293
|
+
if (errCode === "authentication_failed" || errCode === "authentication_error") {
|
|
5294
|
+
return error;
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
if (message.type === "result" && message.is_error) {
|
|
5299
|
+
if (message.subtype === "success") {
|
|
5300
|
+
if (isClaudeAuthErrorText(message.result)) return message.result;
|
|
5301
|
+
} else {
|
|
5302
|
+
for (const err of message.errors) {
|
|
5303
|
+
if (isClaudeAuthErrorText(err)) return err;
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
return null;
|
|
5214
5308
|
}
|
|
5215
5309
|
async getHistory() {
|
|
5216
5310
|
await this.initialized;
|
|
@@ -5228,34 +5322,12 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5228
5322
|
console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
|
|
5229
5323
|
}
|
|
5230
5324
|
}
|
|
5231
|
-
/**
|
|
5232
|
-
* Checks if an SDK message indicates an OAuth authentication failure.
|
|
5233
|
-
* When detected, triggers a force-refresh of credentials from the monolith.
|
|
5234
|
-
*/
|
|
5235
|
-
async checkForAuthFailure(message) {
|
|
5236
|
-
if (message.type === "assistant" && "error" in message && message.error === "authentication_failed") {
|
|
5237
|
-
console.warn("[ClaudeManager] Detected authentication_failed in assistant message, force-refreshing credentials...");
|
|
5238
|
-
await claudeTokenManager.fetchFreshCredentials();
|
|
5239
|
-
return;
|
|
5240
|
-
}
|
|
5241
|
-
if (message.type === "result" && "is_error" in message && message.is_error && "errors" in message) {
|
|
5242
|
-
const errors = message.errors ?? [];
|
|
5243
|
-
const hasAuthError = errors.some(
|
|
5244
|
-
(err) => err.toLowerCase().includes("authentication") || err.toLowerCase().includes("unauthorized") || err.toLowerCase().includes("not logged in") || err.toLowerCase().includes("login")
|
|
5245
|
-
);
|
|
5246
|
-
if (hasAuthError) {
|
|
5247
|
-
console.warn("[ClaudeManager] Detected auth-related error in result, force-refreshing credentials...");
|
|
5248
|
-
await claudeTokenManager.fetchFreshCredentials();
|
|
5249
|
-
}
|
|
5250
|
-
}
|
|
5251
|
-
}
|
|
5252
5325
|
async handleMessage(message) {
|
|
5253
5326
|
if ("session_id" in message && message.session_id && !this.sessionId) {
|
|
5254
5327
|
this.sessionId = message.session_id;
|
|
5255
5328
|
await this.onSaveSessionId(this.sessionId);
|
|
5256
5329
|
console.log(`[ClaudeManager] Captured and persisted session ID: ${this.sessionId}`);
|
|
5257
5330
|
}
|
|
5258
|
-
await this.checkForAuthFailure(message);
|
|
5259
5331
|
this.trackNativeCompaction(message);
|
|
5260
5332
|
await this.recordEvent(message);
|
|
5261
5333
|
}
|
|
@@ -5282,702 +5354,216 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5282
5354
|
}
|
|
5283
5355
|
};
|
|
5284
5356
|
|
|
5285
|
-
// src/managers/codex-
|
|
5286
|
-
import {
|
|
5287
|
-
import {
|
|
5288
|
-
import { existsSync as existsSync6 } from "fs";
|
|
5289
|
-
import { join as join14 } from "path";
|
|
5290
|
-
import { homedir as homedir12 } from "os";
|
|
5291
|
-
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
5292
|
-
|
|
5293
|
-
// src/utils/codex-quota.ts
|
|
5294
|
-
function buildCodexRateLimitsSnapshot(fields) {
|
|
5295
|
-
if (fields.unlimited === true) return null;
|
|
5296
|
-
let state = "ok";
|
|
5297
|
-
if (fields.hasCredits === false) {
|
|
5298
|
-
state = "out_of_credits";
|
|
5299
|
-
} else if (fields.rateLimitResetType !== null) {
|
|
5300
|
-
state = "rate_limited";
|
|
5301
|
-
}
|
|
5302
|
-
return {
|
|
5303
|
-
state,
|
|
5304
|
-
balance: fields.balance,
|
|
5305
|
-
rateLimitResetType: fields.rateLimitResetType,
|
|
5306
|
-
planType: fields.planType
|
|
5307
|
-
};
|
|
5308
|
-
}
|
|
5309
|
-
function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
|
|
5310
|
-
if (!isRecord3(parsed)) return null;
|
|
5311
|
-
const payload = isRecord3(parsed.payload) ? parsed.payload : parsed;
|
|
5312
|
-
const rateLimits = isRecord3(payload.rate_limits) ? payload.rate_limits : null;
|
|
5313
|
-
if (!rateLimits) return null;
|
|
5314
|
-
const credits = isRecord3(rateLimits.credits) ? rateLimits.credits : null;
|
|
5315
|
-
const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
|
|
5316
|
-
const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
|
|
5317
|
-
const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
|
|
5318
|
-
const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
|
|
5319
|
-
const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
|
|
5320
|
-
return buildCodexRateLimitsSnapshot({
|
|
5321
|
-
unlimited,
|
|
5322
|
-
hasCredits,
|
|
5323
|
-
balance,
|
|
5324
|
-
rateLimitResetType,
|
|
5325
|
-
planType
|
|
5326
|
-
});
|
|
5327
|
-
}
|
|
5328
|
-
var CodexQuotaStatusTracker = class {
|
|
5329
|
-
lastEmittedQuotaState = "ok";
|
|
5330
|
-
latestQuotaSnapshot = null;
|
|
5331
|
-
quotaBlocked = false;
|
|
5332
|
-
get blocked() {
|
|
5333
|
-
return this.quotaBlocked;
|
|
5334
|
-
}
|
|
5335
|
-
get latestSnapshot() {
|
|
5336
|
-
return this.latestQuotaSnapshot;
|
|
5337
|
-
}
|
|
5338
|
-
prime(snapshot) {
|
|
5339
|
-
this.latestQuotaSnapshot = snapshot;
|
|
5340
|
-
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5341
|
-
}
|
|
5342
|
-
apply(snapshot, force = false) {
|
|
5343
|
-
const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
|
|
5344
|
-
if (!stateChanged && !force) {
|
|
5345
|
-
return null;
|
|
5346
|
-
}
|
|
5347
|
-
this.lastEmittedQuotaState = snapshot.state;
|
|
5348
|
-
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5349
|
-
this.latestQuotaSnapshot = snapshot;
|
|
5350
|
-
const payload = {
|
|
5351
|
-
state: snapshot.state,
|
|
5352
|
-
balance: snapshot.balance,
|
|
5353
|
-
rateLimitResetType: snapshot.rateLimitResetType,
|
|
5354
|
-
planType: snapshot.planType
|
|
5355
|
-
};
|
|
5356
|
-
const event = {
|
|
5357
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5358
|
-
type: CODEX_QUOTA_STATUS_EVENT_TYPE,
|
|
5359
|
-
payload
|
|
5360
|
-
};
|
|
5361
|
-
return event;
|
|
5362
|
-
}
|
|
5363
|
-
};
|
|
5357
|
+
// src/managers/codex-asp/app-server-process.ts
|
|
5358
|
+
import { spawn } from "child_process";
|
|
5359
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
5364
5360
|
|
|
5365
|
-
// src/
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
return
|
|
5361
|
+
// src/managers/codex-asp/asp-client.ts
|
|
5362
|
+
import { EventEmitter } from "events";
|
|
5363
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
5364
|
+
function hasOwn(record, key) {
|
|
5365
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
5370
5366
|
}
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5367
|
+
var AspClient = class {
|
|
5368
|
+
stdin;
|
|
5369
|
+
stdout;
|
|
5370
|
+
emitter = new EventEmitter();
|
|
5371
|
+
pending = /* @__PURE__ */ new Map();
|
|
5372
|
+
nextId = 1;
|
|
5373
|
+
lineBuffer = "";
|
|
5374
|
+
disposed = false;
|
|
5375
|
+
get isDisposed() {
|
|
5376
|
+
return this.disposed;
|
|
5378
5377
|
}
|
|
5379
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
|
|
5380
|
-
}
|
|
5381
|
-
function sleep(ms) {
|
|
5382
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5383
|
-
}
|
|
5384
|
-
var CodexManager = class extends CodingAgentManager {
|
|
5385
|
-
codex;
|
|
5386
|
-
currentThreadId = null;
|
|
5387
|
-
currentThread = null;
|
|
5388
|
-
activeAbortController = null;
|
|
5389
|
-
quotaStatus = new CodexQuotaStatusTracker();
|
|
5390
5378
|
constructor(options) {
|
|
5391
|
-
|
|
5392
|
-
this.
|
|
5393
|
-
this.
|
|
5379
|
+
this.stdin = options.stdin;
|
|
5380
|
+
this.stdout = options.stdout;
|
|
5381
|
+
this.stdout.setEncoding("utf8");
|
|
5382
|
+
this.stdout.on("data", this.handleStdoutData);
|
|
5383
|
+
this.stdin.on("error", this.handleStdinError);
|
|
5394
5384
|
}
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
return new Codex({
|
|
5398
|
-
env: buildCodexAgentEnv(),
|
|
5399
|
-
...codexApiKey ? { apiKey: codexApiKey } : {}
|
|
5400
|
-
});
|
|
5385
|
+
on(event, listener) {
|
|
5386
|
+
this.emitter.on(event, listener);
|
|
5401
5387
|
}
|
|
5402
|
-
|
|
5403
|
-
this.
|
|
5404
|
-
this.currentThread = null;
|
|
5388
|
+
off(event, listener) {
|
|
5389
|
+
this.emitter.off(event, listener);
|
|
5405
5390
|
}
|
|
5406
|
-
async
|
|
5407
|
-
if (this.
|
|
5408
|
-
|
|
5409
|
-
console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
|
|
5391
|
+
async request(method, params, opts) {
|
|
5392
|
+
if (this.disposed) {
|
|
5393
|
+
throw new Error(`Cannot send ${method}: ASP client disposed`);
|
|
5410
5394
|
}
|
|
5395
|
+
const id = this.nextId;
|
|
5396
|
+
this.nextId += 1;
|
|
5397
|
+
const promise = new Promise((resolve3, reject) => {
|
|
5398
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
5399
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
5400
|
+
this.pending.delete(id);
|
|
5401
|
+
reject(new Error(`ASP request timed out for ${method}`));
|
|
5402
|
+
}, timeoutMs) : null;
|
|
5403
|
+
this.pending.set(id, { resolve: resolve3, reject, method, timer });
|
|
5404
|
+
});
|
|
5405
|
+
this.write({ method, id, params });
|
|
5406
|
+
return promise;
|
|
5411
5407
|
}
|
|
5412
|
-
|
|
5413
|
-
if (
|
|
5414
|
-
|
|
5415
|
-
const sessionFile = await this.findSessionFile(this.currentThreadId);
|
|
5416
|
-
if (!sessionFile) return;
|
|
5417
|
-
const content = await readFile7(sessionFile, "utf-8");
|
|
5418
|
-
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
5419
|
-
let latest = null;
|
|
5420
|
-
for (const line of lines) {
|
|
5421
|
-
try {
|
|
5422
|
-
const parsed = JSON.parse(line);
|
|
5423
|
-
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
5424
|
-
if (snapshot) {
|
|
5425
|
-
latest = snapshot;
|
|
5426
|
-
}
|
|
5427
|
-
} catch {
|
|
5428
|
-
}
|
|
5429
|
-
}
|
|
5430
|
-
if (latest) {
|
|
5431
|
-
this.emitQuotaStatus(latest);
|
|
5432
|
-
}
|
|
5433
|
-
} catch (error) {
|
|
5434
|
-
console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
|
-
emitQuotaStatus(snapshot, force = false) {
|
|
5438
|
-
const event = this.quotaStatus.apply(snapshot, force);
|
|
5439
|
-
if (event) this.onEvent(event);
|
|
5440
|
-
}
|
|
5441
|
-
async interruptActiveTurn() {
|
|
5442
|
-
if (this.activeAbortController) {
|
|
5443
|
-
this.activeAbortController.abort();
|
|
5408
|
+
notify(method, params) {
|
|
5409
|
+
if (this.disposed) {
|
|
5410
|
+
return;
|
|
5444
5411
|
}
|
|
5445
|
-
}
|
|
5446
|
-
/**
|
|
5447
|
-
* Update the developer_instructions in ~/.codex/config.toml
|
|
5448
|
-
* This sets the system prompt that Codex will use for this turn
|
|
5449
|
-
*/
|
|
5450
|
-
async updateCodexConfig(developerInstructions) {
|
|
5451
5412
|
try {
|
|
5452
|
-
|
|
5453
|
-
await mkdir10(codexDir, { recursive: true });
|
|
5454
|
-
let config = {};
|
|
5455
|
-
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
5456
|
-
try {
|
|
5457
|
-
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
5458
|
-
const parsed = parseToml(existingContent);
|
|
5459
|
-
if (isRecord3(parsed)) {
|
|
5460
|
-
config = parsed;
|
|
5461
|
-
}
|
|
5462
|
-
} catch (parseError) {
|
|
5463
|
-
console.warn("[CodexManager] Failed to parse existing config.toml, starting fresh:", parseError);
|
|
5464
|
-
}
|
|
5465
|
-
}
|
|
5466
|
-
if (developerInstructions) {
|
|
5467
|
-
config.developer_instructions = developerInstructions;
|
|
5468
|
-
} else {
|
|
5469
|
-
delete config.developer_instructions;
|
|
5470
|
-
}
|
|
5471
|
-
const tomlContent = stringifyToml(config);
|
|
5472
|
-
await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
5473
|
-
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
5413
|
+
this.write(params === void 0 ? { method } : { method, params });
|
|
5474
5414
|
} catch (error) {
|
|
5475
|
-
console.
|
|
5415
|
+
console.warn(`[AspClient] Failed to send notification ${method}:`, error);
|
|
5476
5416
|
}
|
|
5477
5417
|
}
|
|
5478
|
-
|
|
5418
|
+
respond(id, result) {
|
|
5419
|
+
if (this.disposed) {
|
|
5420
|
+
return;
|
|
5421
|
+
}
|
|
5479
5422
|
try {
|
|
5480
|
-
|
|
5423
|
+
this.write({ id, result });
|
|
5481
5424
|
} catch (error) {
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5425
|
+
console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
dispose(reason = new Error("ASP client disposed")) {
|
|
5429
|
+
if (this.disposed) {
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5432
|
+
this.disposed = true;
|
|
5433
|
+
this.stdout.off("data", this.handleStdoutData);
|
|
5434
|
+
this.stdin.removeListener("error", this.handleStdinError);
|
|
5435
|
+
for (const [id, pending] of this.pending) {
|
|
5436
|
+
if (pending.timer) {
|
|
5437
|
+
clearTimeout(pending.timer);
|
|
5489
5438
|
}
|
|
5490
|
-
|
|
5439
|
+
pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
|
|
5440
|
+
this.pending.delete(id);
|
|
5491
5441
|
}
|
|
5442
|
+
this.lineBuffer = "";
|
|
5443
|
+
this.emitter.emit("dispose", reason);
|
|
5444
|
+
this.emitter.removeAllListeners();
|
|
5492
5445
|
}
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
console.error("[CodexManager] onTurnComplete failed during quota-blocked turn:", error);
|
|
5502
|
-
}
|
|
5503
|
-
return;
|
|
5446
|
+
handleStdoutData = (chunk) => {
|
|
5447
|
+
this.lineBuffer += chunk.toString();
|
|
5448
|
+
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5449
|
+
while (newlineIndex >= 0) {
|
|
5450
|
+
const line = this.lineBuffer.slice(0, newlineIndex).trim();
|
|
5451
|
+
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
5452
|
+
if (line.length > 0) {
|
|
5453
|
+
this.handleLine(line);
|
|
5504
5454
|
}
|
|
5455
|
+
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5505
5456
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
thinkingLevel
|
|
5513
|
-
} = request;
|
|
5514
|
-
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
5515
|
-
let tempImagePaths = [];
|
|
5516
|
-
let stopTail = null;
|
|
5517
|
-
let abortController = null;
|
|
5457
|
+
};
|
|
5458
|
+
handleStdinError = (error) => {
|
|
5459
|
+
this.dispose(new Error(`ASP stdin error: ${error.message}`));
|
|
5460
|
+
};
|
|
5461
|
+
handleLine(line) {
|
|
5462
|
+
let parsed;
|
|
5518
5463
|
try {
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
const developerInstructions = this.buildCombinedInstructions(customInstructions);
|
|
5524
|
-
await this.updateCodexConfig(developerInstructions);
|
|
5525
|
-
const sandboxMode = "danger-full-access";
|
|
5526
|
-
const webSearchMode = "live";
|
|
5527
|
-
const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
5528
|
-
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
5529
|
-
const threadOptions = {
|
|
5530
|
-
workingDirectory: this.workingDirectory,
|
|
5531
|
-
skipGitRepoCheck: true,
|
|
5532
|
-
sandboxMode,
|
|
5533
|
-
model: model || DEFAULT_MODEL,
|
|
5534
|
-
webSearchMode,
|
|
5535
|
-
additionalDirectories,
|
|
5536
|
-
...codexReasoningEffort ? { modelReasoningEffort: codexReasoningEffort } : {}
|
|
5537
|
-
};
|
|
5538
|
-
abortController = new AbortController();
|
|
5539
|
-
this.activeAbortController = abortController;
|
|
5540
|
-
if (this.currentThreadId) {
|
|
5541
|
-
this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
|
|
5542
|
-
} else {
|
|
5543
|
-
this.currentThread = this.codex.startThread(threadOptions);
|
|
5544
|
-
const { events } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
|
|
5545
|
-
for await (const event of events) {
|
|
5546
|
-
if (event.type === "thread.started") {
|
|
5547
|
-
this.currentThreadId = event.thread_id;
|
|
5548
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
5549
|
-
console.log(`[CodexManager] Captured and persisted thread ID: ${this.currentThreadId}`);
|
|
5550
|
-
}
|
|
5551
|
-
}
|
|
5552
|
-
if (!this.currentThreadId && this.currentThread.id) {
|
|
5553
|
-
this.currentThreadId = this.currentThread.id;
|
|
5554
|
-
await this.onSaveSessionId(this.currentThreadId);
|
|
5555
|
-
console.log(`[CodexManager] Captured and persisted thread ID from thread.id: ${this.currentThreadId}`);
|
|
5556
|
-
}
|
|
5557
|
-
}
|
|
5558
|
-
stopTail = this.currentThreadId ? await this.startSessionTail(this.currentThreadId) : null;
|
|
5559
|
-
let input;
|
|
5560
|
-
if (tempImagePaths.length > 0) {
|
|
5561
|
-
const inputItems = [
|
|
5562
|
-
{ type: "text", text: message },
|
|
5563
|
-
...tempImagePaths.map((path4) => ({ type: "local_image", path: path4 }))
|
|
5564
|
-
];
|
|
5565
|
-
input = inputItems;
|
|
5566
|
-
} else {
|
|
5567
|
-
input = message;
|
|
5568
|
-
}
|
|
5569
|
-
try {
|
|
5570
|
-
const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
|
|
5571
|
-
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
5572
|
-
for await (const event of events) {
|
|
5573
|
-
if (linearSessionId) {
|
|
5574
|
-
linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
|
|
5575
|
-
linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
|
|
5576
|
-
}
|
|
5577
|
-
}
|
|
5578
|
-
linearForwarder.flushThoughtAsResponse();
|
|
5579
|
-
} catch (error) {
|
|
5580
|
-
await this.flushQuotaSnapshotFromCurrentSession();
|
|
5581
|
-
if (this.quotaStatus.blocked) {
|
|
5582
|
-
return;
|
|
5583
|
-
}
|
|
5584
|
-
throw error;
|
|
5585
|
-
}
|
|
5586
|
-
} finally {
|
|
5587
|
-
if (stopTail) {
|
|
5588
|
-
await stopTail();
|
|
5589
|
-
}
|
|
5590
|
-
await removeTempImageFiles(tempImagePaths);
|
|
5591
|
-
try {
|
|
5592
|
-
await this.onTurnComplete();
|
|
5593
|
-
} catch (error) {
|
|
5594
|
-
console.error("[CodexManager] onTurnComplete failed:", error);
|
|
5595
|
-
}
|
|
5596
|
-
this.activeAbortController = null;
|
|
5464
|
+
parsed = JSON.parse(line);
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
console.warn("[AspClient] Failed to parse ASP JSON line:", error);
|
|
5467
|
+
return;
|
|
5597
5468
|
}
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
return {
|
|
5602
|
-
thread_id: null,
|
|
5603
|
-
events: []
|
|
5604
|
-
};
|
|
5469
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5470
|
+
console.warn("[AspClient] Ignoring non-object ASP message");
|
|
5471
|
+
return;
|
|
5605
5472
|
}
|
|
5606
|
-
const
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5473
|
+
const message = parsed;
|
|
5474
|
+
const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
|
|
5475
|
+
if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
|
|
5476
|
+
this.handleResponse(message);
|
|
5477
|
+
return;
|
|
5478
|
+
}
|
|
5479
|
+
if (hasRequestId && typeof message.method === "string") {
|
|
5480
|
+
this.emitter.emit("serverRequest", message);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
if (!hasOwn(message, "id") && typeof message.method === "string") {
|
|
5484
|
+
this.emitter.emit("notification", message);
|
|
5612
5485
|
}
|
|
5613
|
-
const events = await readJSONL(sessionFile);
|
|
5614
|
-
return {
|
|
5615
|
-
thread_id: this.currentThreadId,
|
|
5616
|
-
events
|
|
5617
|
-
};
|
|
5618
5486
|
}
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
const now = /* @__PURE__ */ new Date();
|
|
5624
|
-
const year = now.getFullYear();
|
|
5625
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
5626
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
5627
|
-
const todayDir = join14(sessionsDir, String(year), month, day);
|
|
5628
|
-
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
5629
|
-
if (file) return file;
|
|
5630
|
-
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
5631
|
-
const date = new Date(now);
|
|
5632
|
-
date.setDate(date.getDate() - daysAgo);
|
|
5633
|
-
const searchYear = date.getFullYear();
|
|
5634
|
-
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
5635
|
-
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
5636
|
-
const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
5637
|
-
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
5638
|
-
if (file2) return file2;
|
|
5639
|
-
}
|
|
5640
|
-
return null;
|
|
5641
|
-
} catch (error) {
|
|
5642
|
-
return null;
|
|
5487
|
+
handleResponse(message) {
|
|
5488
|
+
if (typeof message.id !== "number") {
|
|
5489
|
+
console.warn("[AspClient] Ignoring response with non-numeric request id");
|
|
5490
|
+
return;
|
|
5643
5491
|
}
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
for (const file of files) {
|
|
5649
|
-
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
5650
|
-
const fullPath = join14(directory, file);
|
|
5651
|
-
const stats = await stat2(fullPath);
|
|
5652
|
-
if (stats.isFile()) {
|
|
5653
|
-
return fullPath;
|
|
5654
|
-
}
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
|
-
return null;
|
|
5658
|
-
} catch (error) {
|
|
5659
|
-
return null;
|
|
5492
|
+
const pending = this.pending.get(message.id);
|
|
5493
|
+
if (!pending) {
|
|
5494
|
+
console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
|
|
5495
|
+
return;
|
|
5660
5496
|
}
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
while (Date.now() - start < timeoutMs) {
|
|
5665
|
-
const sessionFile = await this.findSessionFile(threadId);
|
|
5666
|
-
if (sessionFile) {
|
|
5667
|
-
return sessionFile;
|
|
5668
|
-
}
|
|
5669
|
-
await sleep(100);
|
|
5497
|
+
this.pending.delete(message.id);
|
|
5498
|
+
if (pending.timer) {
|
|
5499
|
+
clearTimeout(pending.timer);
|
|
5670
5500
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
// @openai/codex-sdk doesn't expose manual /compact (TUI-only); we only mirror the auto-compaction rollout entries to the UI.
|
|
5674
|
-
trackNativeCompaction(event) {
|
|
5675
|
-
if (event.type === "compacted") {
|
|
5676
|
-
this.setCompacting(false);
|
|
5501
|
+
if (hasOwn(message, "error")) {
|
|
5502
|
+
pending.reject(this.createRpcError(pending.method, message.error));
|
|
5677
5503
|
return;
|
|
5678
5504
|
}
|
|
5679
|
-
|
|
5680
|
-
const msg = event.payload.msg;
|
|
5681
|
-
if (!msg) return;
|
|
5682
|
-
const itemType = msg.payload?.item?.type;
|
|
5683
|
-
if (itemType !== "context_compaction") return;
|
|
5684
|
-
if (msg.type === "item_started") {
|
|
5685
|
-
this.setCompacting(true);
|
|
5686
|
-
} else if (msg.type === "item_completed") {
|
|
5687
|
-
this.setCompacting(false);
|
|
5688
|
-
}
|
|
5505
|
+
pending.resolve(message.result);
|
|
5689
5506
|
}
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
return async () => {
|
|
5694
|
-
};
|
|
5507
|
+
createRpcError(method, error) {
|
|
5508
|
+
if (typeof error !== "object" || error === null || Array.isArray(error)) {
|
|
5509
|
+
return new Error(`ASP request failed for ${method}`);
|
|
5695
5510
|
}
|
|
5696
|
-
|
|
5697
|
-
const
|
|
5698
|
-
const
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
if (snapshot) latest = snapshot;
|
|
5709
|
-
} catch {
|
|
5710
|
-
}
|
|
5711
|
-
}
|
|
5712
|
-
if (latest) {
|
|
5713
|
-
this.quotaStatus.prime(latest);
|
|
5714
|
-
}
|
|
5715
|
-
} catch {
|
|
5716
|
-
}
|
|
5717
|
-
};
|
|
5718
|
-
await seedSeenLines();
|
|
5719
|
-
const pump = async () => {
|
|
5720
|
-
let emitted = 0;
|
|
5721
|
-
try {
|
|
5722
|
-
const content = await readFile7(sessionFile, "utf-8");
|
|
5723
|
-
const lines = content.split("\n");
|
|
5724
|
-
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
5725
|
-
for (const line of completeLines) {
|
|
5726
|
-
const trimmed = line.trim();
|
|
5727
|
-
if (!trimmed || seenLines.has(trimmed)) {
|
|
5728
|
-
continue;
|
|
5729
|
-
}
|
|
5730
|
-
seenLines.add(trimmed);
|
|
5731
|
-
try {
|
|
5732
|
-
const parsed = JSON.parse(trimmed);
|
|
5733
|
-
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
5734
|
-
if (snapshot) {
|
|
5735
|
-
this.emitQuotaStatus(snapshot);
|
|
5736
|
-
}
|
|
5737
|
-
if (isJsonlEvent2(parsed)) {
|
|
5738
|
-
this.trackNativeCompaction(parsed);
|
|
5739
|
-
this.onEvent(parsed);
|
|
5740
|
-
emitted += 1;
|
|
5741
|
-
}
|
|
5742
|
-
} catch {
|
|
5743
|
-
}
|
|
5744
|
-
}
|
|
5745
|
-
} catch {
|
|
5746
|
-
}
|
|
5747
|
-
return emitted;
|
|
5748
|
-
};
|
|
5749
|
-
const loop = (async () => {
|
|
5750
|
-
while (active) {
|
|
5751
|
-
await pump();
|
|
5752
|
-
await sleep(100);
|
|
5753
|
-
}
|
|
5754
|
-
await pump();
|
|
5755
|
-
})();
|
|
5756
|
-
return async () => {
|
|
5757
|
-
active = false;
|
|
5758
|
-
await loop;
|
|
5759
|
-
const deadline = Date.now() + 1500;
|
|
5760
|
-
while (Date.now() < deadline) {
|
|
5761
|
-
const emitted = await pump();
|
|
5762
|
-
if (emitted > 0) {
|
|
5763
|
-
continue;
|
|
5511
|
+
const rpcError = error;
|
|
5512
|
+
const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
|
|
5513
|
+
const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
|
|
5514
|
+
const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
|
|
5515
|
+
return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
|
|
5516
|
+
}
|
|
5517
|
+
write(message) {
|
|
5518
|
+
try {
|
|
5519
|
+
this.stdin.write(`${JSON.stringify(message)}
|
|
5520
|
+
`, (error) => {
|
|
5521
|
+
if (error) {
|
|
5522
|
+
this.dispose(new Error(`ASP write failed: ${error.message}`));
|
|
5764
5523
|
}
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5524
|
+
});
|
|
5525
|
+
} catch (error) {
|
|
5526
|
+
const writeError = error instanceof Error ? error : new Error("ASP write failed");
|
|
5527
|
+
this.dispose(writeError);
|
|
5528
|
+
throw writeError;
|
|
5529
|
+
}
|
|
5768
5530
|
}
|
|
5769
5531
|
};
|
|
5770
5532
|
|
|
5771
5533
|
// src/managers/codex-asp/app-server-process.ts
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
var
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
constructor(options) {
|
|
5793
|
-
this.stdin = options.stdin;
|
|
5794
|
-
this.stdout = options.stdout;
|
|
5795
|
-
this.stdout.setEncoding("utf8");
|
|
5796
|
-
this.stdout.on("data", this.handleStdoutData);
|
|
5797
|
-
this.stdin.on("error", this.handleStdinError);
|
|
5534
|
+
var DEFAULT_CODEX_BINARY = "codex";
|
|
5535
|
+
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
5536
|
+
var ENGINE_PACKAGE_VERSION = "0.1.213";
|
|
5537
|
+
var INITIALIZE_METHOD = "initialize";
|
|
5538
|
+
var INITIALIZED_NOTIFICATION = "initialized";
|
|
5539
|
+
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
5540
|
+
var AppServerProcess = class {
|
|
5541
|
+
binary;
|
|
5542
|
+
args;
|
|
5543
|
+
env;
|
|
5544
|
+
cwd;
|
|
5545
|
+
emitter = new EventEmitter2();
|
|
5546
|
+
child = null;
|
|
5547
|
+
client = null;
|
|
5548
|
+
shuttingDown = false;
|
|
5549
|
+
constructor(options = {}) {
|
|
5550
|
+
this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
|
|
5551
|
+
this.args = options.args ?? DEFAULT_CODEX_ARGS;
|
|
5552
|
+
this.env = options.env ?? buildCodexAgentEnv();
|
|
5553
|
+
this.cwd = options.cwd ?? ENGINE_ENV.WORKSPACE_ROOT;
|
|
5798
5554
|
}
|
|
5799
5555
|
on(event, listener) {
|
|
5800
5556
|
this.emitter.on(event, listener);
|
|
5801
5557
|
}
|
|
5802
|
-
|
|
5803
|
-
this.
|
|
5804
|
-
|
|
5805
|
-
async request(method, params, opts) {
|
|
5806
|
-
if (this.disposed) {
|
|
5807
|
-
throw new Error(`Cannot send ${method}: ASP client disposed`);
|
|
5558
|
+
async start() {
|
|
5559
|
+
if (this.child && this.client) {
|
|
5560
|
+
return { client: this.client };
|
|
5808
5561
|
}
|
|
5809
|
-
|
|
5810
|
-
this.
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
this.pending.delete(id);
|
|
5815
|
-
reject(new Error(`ASP request timed out for ${method}`));
|
|
5816
|
-
}, timeoutMs) : null;
|
|
5817
|
-
this.pending.set(id, { resolve: resolve3, reject, method, timer });
|
|
5818
|
-
});
|
|
5819
|
-
this.write({ method, id, params });
|
|
5820
|
-
return promise;
|
|
5821
|
-
}
|
|
5822
|
-
notify(method, params) {
|
|
5823
|
-
if (this.disposed) {
|
|
5824
|
-
return;
|
|
5825
|
-
}
|
|
5826
|
-
try {
|
|
5827
|
-
this.write(params === void 0 ? { method } : { method, params });
|
|
5828
|
-
} catch (error) {
|
|
5829
|
-
console.warn(`[AspClient] Failed to send notification ${method}:`, error);
|
|
5830
|
-
}
|
|
5831
|
-
}
|
|
5832
|
-
respond(id, result) {
|
|
5833
|
-
if (this.disposed) {
|
|
5834
|
-
return;
|
|
5835
|
-
}
|
|
5836
|
-
try {
|
|
5837
|
-
this.write({ id, result });
|
|
5838
|
-
} catch (error) {
|
|
5839
|
-
console.warn(`[AspClient] Failed to send response ${String(id)}:`, error);
|
|
5840
|
-
}
|
|
5841
|
-
}
|
|
5842
|
-
dispose(reason = new Error("ASP client disposed")) {
|
|
5843
|
-
if (this.disposed) {
|
|
5844
|
-
return;
|
|
5845
|
-
}
|
|
5846
|
-
this.disposed = true;
|
|
5847
|
-
this.stdout.off("data", this.handleStdoutData);
|
|
5848
|
-
this.stdin.removeListener("error", this.handleStdinError);
|
|
5849
|
-
for (const [id, pending] of this.pending) {
|
|
5850
|
-
if (pending.timer) {
|
|
5851
|
-
clearTimeout(pending.timer);
|
|
5852
|
-
}
|
|
5853
|
-
pending.reject(new Error(`${reason.message} while waiting for ${pending.method}`));
|
|
5854
|
-
this.pending.delete(id);
|
|
5855
|
-
}
|
|
5856
|
-
this.lineBuffer = "";
|
|
5857
|
-
this.emitter.emit("dispose", reason);
|
|
5858
|
-
this.emitter.removeAllListeners();
|
|
5859
|
-
}
|
|
5860
|
-
handleStdoutData = (chunk) => {
|
|
5861
|
-
this.lineBuffer += chunk.toString();
|
|
5862
|
-
let newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5863
|
-
while (newlineIndex >= 0) {
|
|
5864
|
-
const line = this.lineBuffer.slice(0, newlineIndex).trim();
|
|
5865
|
-
this.lineBuffer = this.lineBuffer.slice(newlineIndex + 1);
|
|
5866
|
-
if (line.length > 0) {
|
|
5867
|
-
this.handleLine(line);
|
|
5868
|
-
}
|
|
5869
|
-
newlineIndex = this.lineBuffer.indexOf("\n");
|
|
5870
|
-
}
|
|
5871
|
-
};
|
|
5872
|
-
handleStdinError = (error) => {
|
|
5873
|
-
this.dispose(new Error(`ASP stdin error: ${error.message}`));
|
|
5874
|
-
};
|
|
5875
|
-
handleLine(line) {
|
|
5876
|
-
let parsed;
|
|
5877
|
-
try {
|
|
5878
|
-
parsed = JSON.parse(line);
|
|
5879
|
-
} catch (error) {
|
|
5880
|
-
console.warn("[AspClient] Failed to parse ASP JSON line:", error);
|
|
5881
|
-
return;
|
|
5882
|
-
}
|
|
5883
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5884
|
-
console.warn("[AspClient] Ignoring non-object ASP message");
|
|
5885
|
-
return;
|
|
5886
|
-
}
|
|
5887
|
-
const message = parsed;
|
|
5888
|
-
const hasRequestId = typeof message.id === "number" || typeof message.id === "string";
|
|
5889
|
-
if (hasRequestId && (hasOwn(message, "result") || hasOwn(message, "error"))) {
|
|
5890
|
-
this.handleResponse(message);
|
|
5891
|
-
return;
|
|
5892
|
-
}
|
|
5893
|
-
if (hasRequestId && typeof message.method === "string") {
|
|
5894
|
-
this.emitter.emit("serverRequest", message);
|
|
5895
|
-
return;
|
|
5896
|
-
}
|
|
5897
|
-
if (!hasOwn(message, "id") && typeof message.method === "string") {
|
|
5898
|
-
this.emitter.emit("notification", message);
|
|
5899
|
-
}
|
|
5900
|
-
}
|
|
5901
|
-
handleResponse(message) {
|
|
5902
|
-
if (typeof message.id !== "number") {
|
|
5903
|
-
console.warn("[AspClient] Ignoring response with non-numeric request id");
|
|
5904
|
-
return;
|
|
5905
|
-
}
|
|
5906
|
-
const pending = this.pending.get(message.id);
|
|
5907
|
-
if (!pending) {
|
|
5908
|
-
console.warn(`[AspClient] Ignoring response for unknown request id ${message.id}`);
|
|
5909
|
-
return;
|
|
5910
|
-
}
|
|
5911
|
-
this.pending.delete(message.id);
|
|
5912
|
-
if (pending.timer) {
|
|
5913
|
-
clearTimeout(pending.timer);
|
|
5914
|
-
}
|
|
5915
|
-
if (hasOwn(message, "error")) {
|
|
5916
|
-
pending.reject(this.createRpcError(pending.method, message.error));
|
|
5917
|
-
return;
|
|
5918
|
-
}
|
|
5919
|
-
pending.resolve(message.result);
|
|
5920
|
-
}
|
|
5921
|
-
createRpcError(method, error) {
|
|
5922
|
-
if (typeof error !== "object" || error === null || Array.isArray(error)) {
|
|
5923
|
-
return new Error(`ASP request failed for ${method}`);
|
|
5924
|
-
}
|
|
5925
|
-
const rpcError = error;
|
|
5926
|
-
const code = typeof rpcError.code === "number" ? ` ${rpcError.code}` : "";
|
|
5927
|
-
const message = typeof rpcError.message === "string" ? rpcError.message : "Unknown ASP error";
|
|
5928
|
-
const data = hasOwn(rpcError, "data") ? ` data=${JSON.stringify(rpcError.data)}` : "";
|
|
5929
|
-
return new Error(`ASP request failed for ${method}:${code} ${message}${data}`);
|
|
5930
|
-
}
|
|
5931
|
-
write(message) {
|
|
5932
|
-
try {
|
|
5933
|
-
this.stdin.write(`${JSON.stringify(message)}
|
|
5934
|
-
`, (error) => {
|
|
5935
|
-
if (error) {
|
|
5936
|
-
this.dispose(new Error(`ASP write failed: ${error.message}`));
|
|
5937
|
-
}
|
|
5938
|
-
});
|
|
5939
|
-
} catch (error) {
|
|
5940
|
-
const writeError = error instanceof Error ? error : new Error("ASP write failed");
|
|
5941
|
-
this.dispose(writeError);
|
|
5942
|
-
throw writeError;
|
|
5943
|
-
}
|
|
5944
|
-
}
|
|
5945
|
-
};
|
|
5946
|
-
|
|
5947
|
-
// src/managers/codex-asp/app-server-process.ts
|
|
5948
|
-
var DEFAULT_CODEX_BINARY = "codex";
|
|
5949
|
-
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
5950
|
-
var ENGINE_PACKAGE_VERSION = "0.1.213";
|
|
5951
|
-
var INITIALIZE_METHOD = "initialize";
|
|
5952
|
-
var INITIALIZED_NOTIFICATION = "initialized";
|
|
5953
|
-
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
5954
|
-
var AppServerProcess = class {
|
|
5955
|
-
binary;
|
|
5956
|
-
args;
|
|
5957
|
-
env;
|
|
5958
|
-
cwd;
|
|
5959
|
-
emitter = new EventEmitter2();
|
|
5960
|
-
child = null;
|
|
5961
|
-
client = null;
|
|
5962
|
-
shuttingDown = false;
|
|
5963
|
-
constructor(options = {}) {
|
|
5964
|
-
this.binary = options.binary ?? DEFAULT_CODEX_BINARY;
|
|
5965
|
-
this.args = options.args ?? DEFAULT_CODEX_ARGS;
|
|
5966
|
-
this.env = options.env ?? buildCodexAgentEnv();
|
|
5967
|
-
this.cwd = options.cwd ?? ENGINE_ENV.WORKSPACE_ROOT;
|
|
5968
|
-
}
|
|
5969
|
-
on(event, listener) {
|
|
5970
|
-
this.emitter.on(event, listener);
|
|
5971
|
-
}
|
|
5972
|
-
async start() {
|
|
5973
|
-
if (this.child && this.client) {
|
|
5974
|
-
return { client: this.client };
|
|
5975
|
-
}
|
|
5976
|
-
this.shuttingDown = false;
|
|
5977
|
-
const child = spawn(this.binary, this.args, {
|
|
5978
|
-
cwd: this.cwd,
|
|
5979
|
-
env: this.env,
|
|
5980
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5562
|
+
this.shuttingDown = false;
|
|
5563
|
+
const child = spawn(this.binary, this.args, {
|
|
5564
|
+
cwd: this.cwd,
|
|
5565
|
+
env: this.env,
|
|
5566
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5981
5567
|
});
|
|
5982
5568
|
this.child = child;
|
|
5983
5569
|
child.stderr.setEncoding("utf8");
|
|
@@ -6134,8 +5720,87 @@ async function restartCodexAspHost() {
|
|
|
6134
5720
|
}
|
|
6135
5721
|
}
|
|
6136
5722
|
|
|
5723
|
+
// src/utils/codex-quota.ts
|
|
5724
|
+
function buildCodexRateLimitsSnapshot(fields) {
|
|
5725
|
+
if (fields.unlimited === true) return null;
|
|
5726
|
+
let state = "ok";
|
|
5727
|
+
if (fields.hasCredits === false) {
|
|
5728
|
+
state = "out_of_credits";
|
|
5729
|
+
} else if (fields.rateLimitResetType !== null) {
|
|
5730
|
+
state = "rate_limited";
|
|
5731
|
+
}
|
|
5732
|
+
return {
|
|
5733
|
+
state,
|
|
5734
|
+
balance: fields.balance,
|
|
5735
|
+
rateLimitResetType: fields.rateLimitResetType,
|
|
5736
|
+
planType: fields.planType
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
function extractCodexRateLimitsSnapshotFromJsonl(parsed) {
|
|
5740
|
+
if (!isRecord4(parsed)) return null;
|
|
5741
|
+
const payload = isRecord4(parsed.payload) ? parsed.payload : parsed;
|
|
5742
|
+
const rateLimits = isRecord4(payload.rate_limits) ? payload.rate_limits : null;
|
|
5743
|
+
if (!rateLimits) return null;
|
|
5744
|
+
const credits = isRecord4(rateLimits.credits) ? rateLimits.credits : null;
|
|
5745
|
+
const hasCredits = credits && typeof credits.has_credits === "boolean" ? credits.has_credits : null;
|
|
5746
|
+
const unlimited = credits && typeof credits.unlimited === "boolean" ? credits.unlimited : null;
|
|
5747
|
+
const balance = credits && typeof credits.balance === "string" ? credits.balance : null;
|
|
5748
|
+
const rateLimitResetType = typeof rateLimits.rate_limit_reached_type === "string" && rateLimits.rate_limit_reached_type.length > 0 ? rateLimits.rate_limit_reached_type : null;
|
|
5749
|
+
const planType = typeof rateLimits.plan_type === "string" ? rateLimits.plan_type : null;
|
|
5750
|
+
return buildCodexRateLimitsSnapshot({
|
|
5751
|
+
unlimited,
|
|
5752
|
+
hasCredits,
|
|
5753
|
+
balance,
|
|
5754
|
+
rateLimitResetType,
|
|
5755
|
+
planType
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
var CodexQuotaStatusTracker = class {
|
|
5759
|
+
lastEmittedQuotaState = "ok";
|
|
5760
|
+
latestQuotaSnapshot = null;
|
|
5761
|
+
quotaBlocked = false;
|
|
5762
|
+
get blocked() {
|
|
5763
|
+
return this.quotaBlocked;
|
|
5764
|
+
}
|
|
5765
|
+
get latestSnapshot() {
|
|
5766
|
+
return this.latestQuotaSnapshot;
|
|
5767
|
+
}
|
|
5768
|
+
prime(snapshot) {
|
|
5769
|
+
this.latestQuotaSnapshot = snapshot;
|
|
5770
|
+
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5771
|
+
}
|
|
5772
|
+
apply(snapshot, force = false) {
|
|
5773
|
+
const stateChanged = snapshot.state !== this.lastEmittedQuotaState;
|
|
5774
|
+
if (!stateChanged && !force) {
|
|
5775
|
+
return null;
|
|
5776
|
+
}
|
|
5777
|
+
this.lastEmittedQuotaState = snapshot.state;
|
|
5778
|
+
this.quotaBlocked = snapshot.state === "out_of_credits";
|
|
5779
|
+
this.latestQuotaSnapshot = snapshot;
|
|
5780
|
+
const payload = {
|
|
5781
|
+
state: snapshot.state,
|
|
5782
|
+
balance: snapshot.balance,
|
|
5783
|
+
rateLimitResetType: snapshot.rateLimitResetType,
|
|
5784
|
+
planType: snapshot.planType
|
|
5785
|
+
};
|
|
5786
|
+
const event = {
|
|
5787
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5788
|
+
type: CODEX_QUOTA_STATUS_EVENT_TYPE,
|
|
5789
|
+
payload
|
|
5790
|
+
};
|
|
5791
|
+
return event;
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5794
|
+
|
|
5795
|
+
// src/utils/codex-auth.ts
|
|
5796
|
+
function isCodexAuthError(error) {
|
|
5797
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5798
|
+
const lower = msg.toLowerCase();
|
|
5799
|
+
return lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("invalid api key") || lower.includes("incorrect api key") || lower.includes("api key") && lower.includes("invalid") || lower.includes("401") || lower.includes('codexerrorinfo="unauthorized"') || lower.includes("codexerrorinfo=unauthorized");
|
|
5800
|
+
}
|
|
5801
|
+
|
|
6137
5802
|
// src/managers/codex-asp/mappers.ts
|
|
6138
|
-
var
|
|
5803
|
+
var DEFAULT_MODEL = "gpt-5.5";
|
|
6139
5804
|
var THREAD_START_METHOD = "thread/start";
|
|
6140
5805
|
var THREAD_RESUME_METHOD = "thread/resume";
|
|
6141
5806
|
var THREAD_READ_METHOD = "thread/read";
|
|
@@ -6161,198 +5826,354 @@ function extractRateLimitsSnapshot(rateLimits) {
|
|
|
6161
5826
|
function timestampFromSeconds(value) {
|
|
6162
5827
|
return new Date((value ?? Date.now() / 1e3) * 1e3).toISOString();
|
|
6163
5828
|
}
|
|
6164
|
-
function
|
|
6165
|
-
return
|
|
6166
|
-
threadId: goal.threadId,
|
|
6167
|
-
objective: goal.objective,
|
|
6168
|
-
status: goal.status,
|
|
6169
|
-
tokenBudget: goal.tokenBudget,
|
|
6170
|
-
tokensUsed: goal.tokensUsed,
|
|
6171
|
-
timeUsedSeconds: goal.timeUsedSeconds,
|
|
6172
|
-
createdAt: timestampFromSeconds(goal.createdAt),
|
|
6173
|
-
updatedAt: timestampFromSeconds(goal.updatedAt)
|
|
6174
|
-
};
|
|
5829
|
+
function timestampFromMilliseconds(value) {
|
|
5830
|
+
return new Date(value ?? Date.now()).toISOString();
|
|
6175
5831
|
}
|
|
6176
|
-
function
|
|
6177
|
-
|
|
6178
|
-
if (
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
5832
|
+
function stringifyToolOutput(value) {
|
|
5833
|
+
if (value === null || value === void 0) return void 0;
|
|
5834
|
+
if (typeof value === "string") return value;
|
|
5835
|
+
if (typeof value === "object" && "content" in value && Array.isArray(value.content)) {
|
|
5836
|
+
const text = value.content.map((item) => {
|
|
5837
|
+
if (typeof item === "string") return item;
|
|
5838
|
+
if (item && typeof item === "object" && "text" in item && typeof item.text === "string") {
|
|
5839
|
+
return item.text;
|
|
5840
|
+
}
|
|
5841
|
+
return "";
|
|
5842
|
+
}).filter(Boolean).join("\n");
|
|
5843
|
+
if (text) return text;
|
|
6184
5844
|
}
|
|
6185
|
-
|
|
6186
|
-
|
|
5845
|
+
try {
|
|
5846
|
+
return JSON.stringify(value);
|
|
5847
|
+
} catch {
|
|
5848
|
+
return String(value);
|
|
6187
5849
|
}
|
|
6188
|
-
return parts.join(" ");
|
|
6189
5850
|
}
|
|
6190
|
-
function
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
}
|
|
6196
|
-
|
|
6197
|
-
const lines = ["*** Begin Patch"];
|
|
6198
|
-
for (const change of item.changes) {
|
|
6199
|
-
if (change.kind.type === "add") {
|
|
6200
|
-
lines.push(`*** Add File: ${change.path}`);
|
|
6201
|
-
} else if (change.kind.type === "delete") {
|
|
6202
|
-
lines.push(`*** Delete File: ${change.path}`);
|
|
6203
|
-
} else {
|
|
6204
|
-
lines.push(`*** Update File: ${change.path}`);
|
|
6205
|
-
if (change.kind.move_path) {
|
|
6206
|
-
lines.push(`*** Move to: ${change.kind.move_path}`);
|
|
6207
|
-
}
|
|
6208
|
-
}
|
|
6209
|
-
if (change.diff.trim().length > 0) {
|
|
6210
|
-
lines.push(change.diff);
|
|
6211
|
-
}
|
|
6212
|
-
}
|
|
6213
|
-
lines.push("*** End Patch");
|
|
6214
|
-
return lines.join("\n");
|
|
5851
|
+
function transcriptPatchOperation(change) {
|
|
5852
|
+
return {
|
|
5853
|
+
action: change.kind.type,
|
|
5854
|
+
path: change.path,
|
|
5855
|
+
...change.kind.type === "update" && change.kind.move_path ? { moveTo: change.kind.move_path } : {},
|
|
5856
|
+
...change.diff.trim().length > 0 ? { diff: change.diff } : {}
|
|
5857
|
+
};
|
|
6215
5858
|
}
|
|
6216
|
-
function
|
|
5859
|
+
function transcriptItemsForTurn(turn) {
|
|
5860
|
+
const userItems = turn.items.filter((item) => item.type === "userMessage");
|
|
5861
|
+
const otherItems = turn.items.filter((item) => item.type !== "userMessage");
|
|
5862
|
+
return [...userItems, ...otherItems];
|
|
5863
|
+
}
|
|
5864
|
+
function itemToTranscriptItem(item, timestamp, status) {
|
|
5865
|
+
if (item.type === "userMessage") {
|
|
5866
|
+
const content = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
|
|
5867
|
+
return {
|
|
5868
|
+
type: "userMessage",
|
|
5869
|
+
id: item.id,
|
|
5870
|
+
content,
|
|
5871
|
+
timestamp
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
6217
5874
|
if (item.type === "agentMessage") {
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
content: [{
|
|
6225
|
-
type: "output_text",
|
|
6226
|
-
text: item.text
|
|
6227
|
-
}]
|
|
6228
|
-
}
|
|
6229
|
-
}];
|
|
5875
|
+
return {
|
|
5876
|
+
type: "agentMessage",
|
|
5877
|
+
id: item.id,
|
|
5878
|
+
text: item.text,
|
|
5879
|
+
timestamp
|
|
5880
|
+
};
|
|
6230
5881
|
}
|
|
6231
5882
|
if (item.type === "reasoning") {
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
text
|
|
6240
|
-
}
|
|
6241
|
-
}];
|
|
5883
|
+
return {
|
|
5884
|
+
type: "reasoning",
|
|
5885
|
+
id: item.id,
|
|
5886
|
+
text: [...item.summary, ...item.content].filter(Boolean).join("\n").trim(),
|
|
5887
|
+
timestamp,
|
|
5888
|
+
status
|
|
5889
|
+
};
|
|
6242
5890
|
}
|
|
6243
5891
|
if (item.type === "commandExecution") {
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
}
|
|
6255
|
-
return [{
|
|
6256
|
-
type: "response_item",
|
|
6257
|
-
payload: {
|
|
6258
|
-
type: "function_call_output",
|
|
6259
|
-
call_id: item.id,
|
|
6260
|
-
output: formatCommandOutput(item)
|
|
6261
|
-
}
|
|
6262
|
-
}];
|
|
5892
|
+
const exitCode = item.exitCode ?? null;
|
|
5893
|
+
return {
|
|
5894
|
+
type: "commandExecution",
|
|
5895
|
+
id: item.id,
|
|
5896
|
+
command: item.command,
|
|
5897
|
+
...item.aggregatedOutput ? { output: item.aggregatedOutput } : {},
|
|
5898
|
+
exitCode,
|
|
5899
|
+
timestamp,
|
|
5900
|
+
status: normalizeCodexAspTranscriptStatus(item.status, typeof exitCode === "number" && exitCode !== 0)
|
|
5901
|
+
};
|
|
6263
5902
|
}
|
|
6264
5903
|
if (item.type === "fileChange") {
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
5904
|
+
return {
|
|
5905
|
+
type: "fileChange",
|
|
5906
|
+
id: item.id,
|
|
5907
|
+
operations: item.changes.map(transcriptPatchOperation),
|
|
5908
|
+
output: item.status,
|
|
5909
|
+
exitCode: item.status === "completed" ? 0 : 1,
|
|
5910
|
+
timestamp,
|
|
5911
|
+
status: normalizeCodexAspTranscriptStatus(item.status)
|
|
5912
|
+
};
|
|
5913
|
+
}
|
|
5914
|
+
if (item.type === "mcpToolCall") {
|
|
5915
|
+
return {
|
|
5916
|
+
type: "toolCall",
|
|
5917
|
+
id: item.id,
|
|
5918
|
+
server: item.server,
|
|
5919
|
+
tool: item.tool,
|
|
5920
|
+
input: item.arguments,
|
|
5921
|
+
output: item.error?.message ?? stringifyToolOutput(item.result),
|
|
5922
|
+
timestamp,
|
|
5923
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.status === "failed")
|
|
5924
|
+
};
|
|
5925
|
+
}
|
|
5926
|
+
if (item.type === "dynamicToolCall") {
|
|
5927
|
+
return {
|
|
5928
|
+
type: "toolCall",
|
|
5929
|
+
id: item.id,
|
|
5930
|
+
server: item.namespace ?? "dynamic",
|
|
5931
|
+
tool: item.tool,
|
|
5932
|
+
input: item.arguments,
|
|
5933
|
+
output: stringifyToolOutput(item.contentItems),
|
|
5934
|
+
timestamp,
|
|
5935
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.success === false)
|
|
5936
|
+
};
|
|
5937
|
+
}
|
|
5938
|
+
if (item.type === "collabAgentToolCall") {
|
|
5939
|
+
return {
|
|
5940
|
+
type: "toolCall",
|
|
5941
|
+
id: item.id,
|
|
5942
|
+
server: "collab",
|
|
5943
|
+
tool: item.tool,
|
|
5944
|
+
input: item.prompt ?? void 0,
|
|
5945
|
+
output: stringifyToolOutput(item.agentsStates),
|
|
5946
|
+
timestamp,
|
|
5947
|
+
status: normalizeCodexAspTranscriptStatus(item.status, item.status === "failed")
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
if (item.type === "webSearch") {
|
|
5951
|
+
return {
|
|
5952
|
+
type: "webSearch",
|
|
5953
|
+
id: item.id,
|
|
5954
|
+
query: item.query,
|
|
5955
|
+
timestamp,
|
|
5956
|
+
status
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
if (item.type === "plan") {
|
|
5960
|
+
return {
|
|
5961
|
+
type: "plan",
|
|
5962
|
+
id: item.id,
|
|
5963
|
+
text: item.text,
|
|
5964
|
+
timestamp,
|
|
5965
|
+
status
|
|
5966
|
+
};
|
|
5967
|
+
}
|
|
5968
|
+
if (item.type === "contextCompaction") {
|
|
5969
|
+
return {
|
|
5970
|
+
type: "contextCompaction",
|
|
5971
|
+
id: item.id,
|
|
5972
|
+
timestamp,
|
|
5973
|
+
status
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
5976
|
+
if (item.type === "imageView" || item.type === "imageGeneration" || item.type === "hookPrompt") {
|
|
5977
|
+
return null;
|
|
5978
|
+
}
|
|
5979
|
+
if (item.type === "enteredReviewMode" || item.type === "exitedReviewMode") {
|
|
5980
|
+
return {
|
|
5981
|
+
type: "reasoning",
|
|
5982
|
+
id: item.id,
|
|
5983
|
+
text: item.review,
|
|
5984
|
+
timestamp,
|
|
5985
|
+
status
|
|
5986
|
+
};
|
|
5987
|
+
}
|
|
5988
|
+
return null;
|
|
5989
|
+
}
|
|
5990
|
+
function aspGoalToChatGoal(goal) {
|
|
5991
|
+
return {
|
|
5992
|
+
threadId: goal.threadId,
|
|
5993
|
+
objective: goal.objective,
|
|
5994
|
+
status: goal.status,
|
|
5995
|
+
tokenBudget: goal.tokenBudget,
|
|
5996
|
+
tokensUsed: goal.tokensUsed,
|
|
5997
|
+
timeUsedSeconds: goal.timeUsedSeconds,
|
|
5998
|
+
createdAt: timestampFromSeconds(goal.createdAt),
|
|
5999
|
+
updatedAt: timestampFromSeconds(goal.updatedAt)
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
6002
|
+
function formatTurnFailure(turn) {
|
|
6003
|
+
const turnError = turn.error;
|
|
6004
|
+
if (!turnError) {
|
|
6005
|
+
return "Codex ASP turn failed without error details";
|
|
6006
|
+
}
|
|
6007
|
+
const parts = [`Codex ASP turn failed: ${turnError.message}`];
|
|
6008
|
+
if (turnError.codexErrorInfo !== null) {
|
|
6009
|
+
parts.push(`codexErrorInfo=${JSON.stringify(turnError.codexErrorInfo)}`);
|
|
6010
|
+
}
|
|
6011
|
+
if (turnError.additionalDetails) {
|
|
6012
|
+
parts.push(`details=${turnError.additionalDetails}`);
|
|
6013
|
+
}
|
|
6014
|
+
return parts.join(" ");
|
|
6015
|
+
}
|
|
6016
|
+
function threadToAspTranscript(thread) {
|
|
6017
|
+
let sequence = 0;
|
|
6018
|
+
const turns = thread.turns.map((turn, index) => ({ turn, index })).sort((a, b) => (a.turn.startedAt ?? a.turn.completedAt ?? Number.MAX_SAFE_INTEGER) - (b.turn.startedAt ?? b.turn.completedAt ?? Number.MAX_SAFE_INTEGER) || a.index - b.index).map(({ turn }) => {
|
|
6019
|
+
const startedAt = timestampFromSeconds(turn.startedAt);
|
|
6020
|
+
const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
|
|
6021
|
+
const itemTimestamp = completedAt ?? startedAt;
|
|
6022
|
+
const status = normalizeCodexAspTranscriptStatus(turn.status);
|
|
6023
|
+
const items = [];
|
|
6024
|
+
for (const item of transcriptItemsForTurn(turn)) {
|
|
6025
|
+
const transcriptItem = itemToTranscriptItem(item, item.type === "userMessage" ? startedAt : itemTimestamp, status);
|
|
6026
|
+
if (transcriptItem) {
|
|
6027
|
+
items.push({ ...transcriptItem, sequence: sequence++ });
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
if (turn.error) {
|
|
6031
|
+
items.push({
|
|
6032
|
+
type: "error",
|
|
6033
|
+
id: `${turn.id}-error`,
|
|
6034
|
+
message: formatTurnFailure(turn),
|
|
6035
|
+
timestamp: itemTimestamp,
|
|
6036
|
+
sequence: sequence++
|
|
6037
|
+
});
|
|
6276
6038
|
}
|
|
6277
|
-
return
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6039
|
+
return {
|
|
6040
|
+
id: turn.id,
|
|
6041
|
+
status: turn.status,
|
|
6042
|
+
startedAt,
|
|
6043
|
+
completedAt,
|
|
6044
|
+
items
|
|
6045
|
+
};
|
|
6046
|
+
});
|
|
6047
|
+
return {
|
|
6048
|
+
threadId: thread.id,
|
|
6049
|
+
updatedAt: timestampFromSeconds(thread.updatedAt),
|
|
6050
|
+
turns
|
|
6051
|
+
};
|
|
6052
|
+
}
|
|
6053
|
+
function latestIsoTimestamp(a, b) {
|
|
6054
|
+
return Date.parse(a) >= Date.parse(b) ? a : b;
|
|
6055
|
+
}
|
|
6056
|
+
function nextTranscriptSequence(transcript) {
|
|
6057
|
+
let max = -1;
|
|
6058
|
+
for (const turn of transcript.turns) {
|
|
6059
|
+
for (const item of turn.items) {
|
|
6060
|
+
if (typeof item.sequence === "number" && item.sequence > max) {
|
|
6061
|
+
max = item.sequence;
|
|
6286
6062
|
}
|
|
6287
|
-
}
|
|
6063
|
+
}
|
|
6288
6064
|
}
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6065
|
+
return max + 1;
|
|
6066
|
+
}
|
|
6067
|
+
function transcriptItemRank(item) {
|
|
6068
|
+
return item.type === "userMessage" ? 0 : 1;
|
|
6069
|
+
}
|
|
6070
|
+
function transcriptItemSignature(item) {
|
|
6071
|
+
if (item.type === "userMessage") return `user:${item.content.trim()}`;
|
|
6072
|
+
if (item.type === "agentMessage" && item.text.trim()) return `agent:${item.text.trim()}`;
|
|
6073
|
+
if (item.type === "plan" && item.text.trim()) return `plan:${item.text.trim()}`;
|
|
6074
|
+
if (item.type === "error" && item.message.trim()) return `error:${item.message.trim()}`;
|
|
6075
|
+
return null;
|
|
6076
|
+
}
|
|
6077
|
+
function areEquivalentTranscriptItems(current, candidate) {
|
|
6078
|
+
if (current.id === candidate.id) return true;
|
|
6079
|
+
if (current.type !== candidate.type) return false;
|
|
6080
|
+
const signature = transcriptItemSignature(current);
|
|
6081
|
+
return signature !== null && signature === transcriptItemSignature(candidate);
|
|
6082
|
+
}
|
|
6083
|
+
function findEquivalentCodexAspTranscriptItemIndex(items, candidate) {
|
|
6084
|
+
return items.findIndex((current) => areEquivalentTranscriptItems(current, candidate));
|
|
6085
|
+
}
|
|
6086
|
+
function sortTranscriptItems(items) {
|
|
6087
|
+
return [...items].map((item, index) => ({ item, index })).sort((a, b) => {
|
|
6088
|
+
const aSequence = a.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
6089
|
+
const bSequence = b.item.sequence ?? Number.MAX_SAFE_INTEGER;
|
|
6090
|
+
return transcriptItemRank(a.item) - transcriptItemRank(b.item) || aSequence - bSequence || Date.parse(a.item.timestamp) - Date.parse(b.item.timestamp) || a.index - b.index;
|
|
6091
|
+
}).map(({ item }) => item);
|
|
6092
|
+
}
|
|
6093
|
+
function dedupeTranscriptItems(items) {
|
|
6094
|
+
const deduped = [];
|
|
6095
|
+
for (const item of items) {
|
|
6096
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(deduped, item);
|
|
6097
|
+
if (existingIndex === -1) {
|
|
6098
|
+
deduped.push(item);
|
|
6099
|
+
continue;
|
|
6303
6100
|
}
|
|
6304
|
-
|
|
6305
|
-
return [{
|
|
6306
|
-
type: "response_item",
|
|
6307
|
-
payload: {
|
|
6308
|
-
type: "custom_tool_call_output",
|
|
6309
|
-
call_id: item.id,
|
|
6310
|
-
output: JSON.stringify({
|
|
6311
|
-
output: failed ? "Failed" : "Done",
|
|
6312
|
-
metadata: { exit_code: failed ? 1 : 0 }
|
|
6313
|
-
})
|
|
6314
|
-
}
|
|
6315
|
-
}];
|
|
6101
|
+
deduped[existingIndex] = mergeTranscriptItem(deduped[existingIndex], item);
|
|
6316
6102
|
}
|
|
6317
|
-
return
|
|
6103
|
+
return deduped;
|
|
6318
6104
|
}
|
|
6319
|
-
function
|
|
6320
|
-
|
|
6321
|
-
const turns =
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6105
|
+
function normalizeCodexAspTranscriptSequences(transcript) {
|
|
6106
|
+
let sequence = 0;
|
|
6107
|
+
const turns = [...transcript.turns].map((turn, index) => ({ turn, index })).sort((a, b) => Date.parse(a.turn.startedAt) - Date.parse(b.turn.startedAt) || a.index - b.index).map(({ turn }) => ({
|
|
6108
|
+
...turn,
|
|
6109
|
+
items: sortTranscriptItems(dedupeTranscriptItems(turn.items)).map((item) => ({
|
|
6110
|
+
...item,
|
|
6111
|
+
sequence: sequence++
|
|
6112
|
+
}))
|
|
6113
|
+
}));
|
|
6114
|
+
return {
|
|
6115
|
+
...transcript,
|
|
6116
|
+
turns
|
|
6117
|
+
};
|
|
6118
|
+
}
|
|
6119
|
+
function mergeCodexAspTranscriptItem(current, candidate) {
|
|
6120
|
+
if (current.type === "agentMessage" && candidate.type === "agentMessage" && candidate.text.length === 0) {
|
|
6121
|
+
return {
|
|
6122
|
+
...current,
|
|
6123
|
+
sequence: current.sequence ?? candidate.sequence
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
return {
|
|
6127
|
+
...current,
|
|
6128
|
+
...candidate,
|
|
6129
|
+
id: current.id,
|
|
6130
|
+
timestamp: current.timestamp,
|
|
6131
|
+
sequence: current.sequence ?? candidate.sequence
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6134
|
+
function mergeCodexAspTranscripts(primary, supplemental) {
|
|
6135
|
+
if (!primary) return supplemental ? normalizeCodexAspTranscriptSequences(supplemental) : null;
|
|
6136
|
+
if (!supplemental) return normalizeCodexAspTranscriptSequences(primary);
|
|
6137
|
+
if (primary.threadId !== supplemental.threadId) return normalizeCodexAspTranscriptSequences(supplemental);
|
|
6138
|
+
let sequence = nextTranscriptSequence(primary);
|
|
6139
|
+
const turns = primary.turns.map((turn) => ({
|
|
6140
|
+
...turn,
|
|
6141
|
+
items: [...turn.items]
|
|
6142
|
+
}));
|
|
6143
|
+
for (const supplementalTurn of supplemental.turns) {
|
|
6144
|
+
const existingTurn = turns.find((turn) => turn.id === supplementalTurn.id);
|
|
6145
|
+
if (!existingTurn) {
|
|
6146
|
+
turns.push({
|
|
6147
|
+
...supplementalTurn,
|
|
6148
|
+
items: supplementalTurn.items.map((item) => typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ })
|
|
6149
|
+
});
|
|
6150
|
+
continue;
|
|
6340
6151
|
}
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6152
|
+
existingTurn.status = supplementalTurn.status;
|
|
6153
|
+
existingTurn.completedAt = supplementalTurn.completedAt ?? existingTurn.completedAt;
|
|
6154
|
+
existingTurn.startedAt = Date.parse(existingTurn.startedAt) <= Date.parse(supplementalTurn.startedAt) ? existingTurn.startedAt : supplementalTurn.startedAt;
|
|
6155
|
+
for (const item of supplementalTurn.items) {
|
|
6156
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(existingTurn.items, item);
|
|
6157
|
+
if (existingIndex === -1) {
|
|
6158
|
+
existingTurn.items.push(
|
|
6159
|
+
typeof item.sequence === "number" ? item : { ...item, sequence: sequence++ }
|
|
6160
|
+
);
|
|
6161
|
+
continue;
|
|
6347
6162
|
}
|
|
6163
|
+
existingTurn.items[existingIndex] = mergeCodexAspTranscriptItem(existingTurn.items[existingIndex], item);
|
|
6348
6164
|
}
|
|
6165
|
+
existingTurn.items = sortTranscriptItems(existingTurn.items);
|
|
6349
6166
|
}
|
|
6350
|
-
return
|
|
6167
|
+
return normalizeCodexAspTranscriptSequences({
|
|
6168
|
+
threadId: primary.threadId,
|
|
6169
|
+
updatedAt: latestIsoTimestamp(primary.updatedAt, supplemental.updatedAt),
|
|
6170
|
+
turns: turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt))
|
|
6171
|
+
});
|
|
6351
6172
|
}
|
|
6352
6173
|
async function buildThreadStartParams(workingDirectory, request, developerInstructions) {
|
|
6353
6174
|
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
6354
6175
|
return {
|
|
6355
|
-
model: request.model ??
|
|
6176
|
+
model: request.model ?? DEFAULT_MODEL,
|
|
6356
6177
|
cwd: workingDirectory,
|
|
6357
6178
|
runtimeWorkspaceRoots: additionalDirectories,
|
|
6358
6179
|
sandbox: "danger-full-access",
|
|
@@ -6366,7 +6187,7 @@ async function buildThreadResumeParams(workingDirectory, threadId, request, deve
|
|
|
6366
6187
|
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
6367
6188
|
return {
|
|
6368
6189
|
threadId,
|
|
6369
|
-
model: request.model ??
|
|
6190
|
+
model: request.model ?? DEFAULT_MODEL,
|
|
6370
6191
|
cwd: workingDirectory,
|
|
6371
6192
|
runtimeWorkspaceRoots: additionalDirectories,
|
|
6372
6193
|
sandbox: "danger-full-access",
|
|
@@ -6395,7 +6216,7 @@ async function buildTurnInput(request) {
|
|
|
6395
6216
|
}
|
|
6396
6217
|
async function buildTurnStartParams(threadId, request, developerInstructions) {
|
|
6397
6218
|
const effort = toReasoningEffort(request.thinkingLevel);
|
|
6398
|
-
const model = request.model ??
|
|
6219
|
+
const model = request.model ?? DEFAULT_MODEL;
|
|
6399
6220
|
const { input, tempImagePaths } = await buildTurnInput(request);
|
|
6400
6221
|
return {
|
|
6401
6222
|
params: {
|
|
@@ -6427,6 +6248,8 @@ var THREAD_GOAL_CLEARED_METHOD = "thread/goal/cleared";
|
|
|
6427
6248
|
var ITEM_STARTED_METHOD = "item/started";
|
|
6428
6249
|
var ITEM_COMPLETED_METHOD = "item/completed";
|
|
6429
6250
|
var AGENT_MESSAGE_DELTA_METHOD = "item/agentMessage/delta";
|
|
6251
|
+
var COMMAND_EXECUTION_OUTPUT_DELTA_METHOD = "item/commandExecution/outputDelta";
|
|
6252
|
+
var FILE_CHANGE_OUTPUT_DELTA_METHOD = "item/fileChange/outputDelta";
|
|
6430
6253
|
var ACCOUNT_RATE_LIMITS_UPDATED_METHOD = "account/rateLimits/updated";
|
|
6431
6254
|
var THREAD_TOKEN_USAGE_UPDATED_METHOD = "thread/tokenUsage/updated";
|
|
6432
6255
|
var THREAD_COMPACTED_METHOD = "thread/compacted";
|
|
@@ -6439,55 +6262,13 @@ function dispatchAspNotification(notification, handlers) {
|
|
|
6439
6262
|
}
|
|
6440
6263
|
|
|
6441
6264
|
// src/managers/codex-asp/codex-asp-manager.ts
|
|
6442
|
-
function historyEventKey(event) {
|
|
6443
|
-
const payloadType = typeof event.payload.type === "string" ? event.payload.type : null;
|
|
6444
|
-
const callId = typeof event.payload.call_id === "string" ? event.payload.call_id : null;
|
|
6445
|
-
const itemId = typeof event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY] === "string" ? event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY] : null;
|
|
6446
|
-
const messageId = typeof event.payload[USER_MESSAGE_ID_PAYLOAD_KEY] === "string" ? event.payload[USER_MESSAGE_ID_PAYLOAD_KEY] : null;
|
|
6447
|
-
if (event.type === "response_item" && payloadType && callId) {
|
|
6448
|
-
return `${event.type}:${payloadType}:${callId}`;
|
|
6449
|
-
}
|
|
6450
|
-
if (event.type === "event_msg" && event.payload.type === "user_message") {
|
|
6451
|
-
if (messageId) return `${event.type}:user_message:message:${messageId}`;
|
|
6452
|
-
if (itemId) return `${event.type}:user_message:item:${itemId}`;
|
|
6453
|
-
const command = typeof event.payload.command === "string" ? event.payload.command : "";
|
|
6454
|
-
const message = typeof event.payload.message === "string" ? event.payload.message : "";
|
|
6455
|
-
return `${event.type}:user_message:${event.timestamp}:${command}:${message}`;
|
|
6456
|
-
}
|
|
6457
|
-
if (itemId && payloadType) {
|
|
6458
|
-
return `${event.type}:${payloadType}:item:${itemId}`;
|
|
6459
|
-
}
|
|
6460
|
-
return `${event.type}:${event.timestamp}:${JSON.stringify(event.payload)}`;
|
|
6461
|
-
}
|
|
6462
|
-
function areDuplicateHistoryEvents(a, b) {
|
|
6463
|
-
if (historyEventKey(a) === historyEventKey(b)) return true;
|
|
6464
|
-
return areSameUserMessageEvents(a, b);
|
|
6465
|
-
}
|
|
6466
|
-
function mergeHistoryEvent(current, candidate) {
|
|
6467
|
-
if (getUserMessage(current) && getUserMessage(current) === getUserMessage(candidate)) {
|
|
6468
|
-
return {
|
|
6469
|
-
...current,
|
|
6470
|
-
timestamp: getEventTimestampMs(current) <= getEventTimestampMs(candidate) ? current.timestamp : candidate.timestamp,
|
|
6471
|
-
payload: {
|
|
6472
|
-
...current.payload,
|
|
6473
|
-
...candidate.payload
|
|
6474
|
-
}
|
|
6475
|
-
};
|
|
6476
|
-
}
|
|
6477
|
-
return candidate;
|
|
6478
|
-
}
|
|
6479
|
-
function mergeHistoryEvents(primary, supplemental) {
|
|
6480
|
-
return mergeAgentEvents(primary, supplemental, {
|
|
6481
|
-
areDuplicates: areDuplicateHistoryEvents,
|
|
6482
|
-
mergeEvent: mergeHistoryEvent
|
|
6483
|
-
});
|
|
6484
|
-
}
|
|
6485
6265
|
var CodexAspManager = class extends CodingAgentManager {
|
|
6486
6266
|
currentThreadId = null;
|
|
6487
6267
|
activeTurnId = null;
|
|
6488
6268
|
threadAttached = false;
|
|
6489
6269
|
historyEvents = [];
|
|
6490
|
-
|
|
6270
|
+
codexAspTranscript = null;
|
|
6271
|
+
codexAspSequence = 0;
|
|
6491
6272
|
quotaStatus = new CodexQuotaStatusTracker();
|
|
6492
6273
|
currentGoal = null;
|
|
6493
6274
|
constructor(options) {
|
|
@@ -6518,6 +6299,19 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6518
6299
|
if (!this.currentThreadId) {
|
|
6519
6300
|
return { thread_id: null, events: [], goal: null };
|
|
6520
6301
|
}
|
|
6302
|
+
if (this.activeTurnId && this.codexAspTranscript?.threadId === this.currentThreadId) {
|
|
6303
|
+
try {
|
|
6304
|
+
const host = await getCodexAspHost();
|
|
6305
|
+
await this.refreshThreadGoal(host, this.currentThreadId);
|
|
6306
|
+
} catch {
|
|
6307
|
+
}
|
|
6308
|
+
return {
|
|
6309
|
+
thread_id: this.currentThreadId,
|
|
6310
|
+
events: [...this.historyEvents],
|
|
6311
|
+
codexAspTranscript: this.codexAspTranscript,
|
|
6312
|
+
goal: this.currentGoal
|
|
6313
|
+
};
|
|
6314
|
+
}
|
|
6521
6315
|
try {
|
|
6522
6316
|
const host = await getCodexAspHost();
|
|
6523
6317
|
const [response] = await Promise.all([
|
|
@@ -6527,11 +6321,14 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6527
6321
|
),
|
|
6528
6322
|
this.refreshThreadGoal(host, this.currentThreadId)
|
|
6529
6323
|
]);
|
|
6530
|
-
const
|
|
6531
|
-
|
|
6324
|
+
const transcript = this.mergeTranscriptSnapshot(threadToAspTranscript(response.thread));
|
|
6325
|
+
if (transcript) {
|
|
6326
|
+
transcript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6327
|
+
}
|
|
6532
6328
|
return {
|
|
6533
6329
|
thread_id: this.currentThreadId,
|
|
6534
|
-
events,
|
|
6330
|
+
events: [...this.historyEvents],
|
|
6331
|
+
codexAspTranscript: transcript,
|
|
6535
6332
|
goal: this.currentGoal
|
|
6536
6333
|
};
|
|
6537
6334
|
} catch {
|
|
@@ -6539,12 +6336,27 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6539
6336
|
return {
|
|
6540
6337
|
thread_id: this.currentThreadId,
|
|
6541
6338
|
events: [...this.historyEvents],
|
|
6339
|
+
codexAspTranscript: this.codexAspTranscript,
|
|
6542
6340
|
goal: this.currentGoal
|
|
6543
6341
|
};
|
|
6544
6342
|
}
|
|
6545
6343
|
getGoal() {
|
|
6546
6344
|
return this.currentGoal;
|
|
6547
6345
|
}
|
|
6346
|
+
async clearGoal() {
|
|
6347
|
+
await this.initialized;
|
|
6348
|
+
if (!this.currentThreadId) {
|
|
6349
|
+
this.recordGoalChange(null, true);
|
|
6350
|
+
return null;
|
|
6351
|
+
}
|
|
6352
|
+
const host = await getCodexAspHost();
|
|
6353
|
+
await host.client.request(
|
|
6354
|
+
THREAD_GOAL_CLEAR_METHOD,
|
|
6355
|
+
{ threadId: this.currentThreadId }
|
|
6356
|
+
);
|
|
6357
|
+
this.recordGoalChange(null, true);
|
|
6358
|
+
return null;
|
|
6359
|
+
}
|
|
6548
6360
|
async processMessageInternal(request) {
|
|
6549
6361
|
let userMessageRecorded = false;
|
|
6550
6362
|
const recordUserMessage = (extraPayload = {}) => {
|
|
@@ -6631,32 +6443,31 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6631
6443
|
}
|
|
6632
6444
|
throw new Error(formatTurnFailure(completedTurn));
|
|
6633
6445
|
}
|
|
6634
|
-
if (completedTurn.status === "completed") {
|
|
6635
|
-
this.emitTurnCompletedItems(completedTurn);
|
|
6636
|
-
}
|
|
6637
6446
|
}
|
|
6638
6447
|
async ensureThread(host, request, developerInstructions) {
|
|
6639
|
-
|
|
6448
|
+
const existingThreadId = this.currentThreadId;
|
|
6449
|
+
if (existingThreadId) {
|
|
6640
6450
|
if (!this.threadAttached) {
|
|
6641
6451
|
const response = await host.client.request(
|
|
6642
6452
|
THREAD_RESUME_METHOD,
|
|
6643
|
-
await buildThreadResumeParams(this.workingDirectory,
|
|
6453
|
+
await buildThreadResumeParams(this.workingDirectory, existingThreadId, request, developerInstructions)
|
|
6644
6454
|
);
|
|
6645
6455
|
this.currentThreadId = response.thread.id;
|
|
6646
6456
|
this.threadAttached = true;
|
|
6647
6457
|
this.seedHistoryFromThread(response.thread);
|
|
6648
6458
|
await this.onSaveSessionId(this.currentThreadId);
|
|
6649
6459
|
}
|
|
6650
|
-
return this.currentThreadId;
|
|
6460
|
+
return this.currentThreadId ?? existingThreadId;
|
|
6651
6461
|
}
|
|
6652
6462
|
const threadStartResponse = await host.client.request(
|
|
6653
6463
|
THREAD_START_METHOD,
|
|
6654
6464
|
await buildThreadStartParams(this.workingDirectory, request, developerInstructions)
|
|
6655
6465
|
);
|
|
6656
|
-
|
|
6466
|
+
const threadId = threadStartResponse.thread.id;
|
|
6467
|
+
this.currentThreadId = threadId;
|
|
6657
6468
|
this.threadAttached = true;
|
|
6658
6469
|
await this.onSaveSessionId(this.currentThreadId);
|
|
6659
|
-
return
|
|
6470
|
+
return threadId;
|
|
6660
6471
|
}
|
|
6661
6472
|
async runTurn(host, threadId, request, developerInstructions) {
|
|
6662
6473
|
const { params, tempImagePaths } = await buildTurnStartParams(threadId, request, developerInstructions);
|
|
@@ -6694,7 +6505,7 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6694
6505
|
const completedItems = [];
|
|
6695
6506
|
const agentMessageDeltas = /* @__PURE__ */ new Map();
|
|
6696
6507
|
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
6697
|
-
const model = request.model ??
|
|
6508
|
+
const model = request.model ?? DEFAULT_MODEL;
|
|
6698
6509
|
let tempImagePaths = [];
|
|
6699
6510
|
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
6700
6511
|
const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId);
|
|
@@ -6722,11 +6533,13 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6722
6533
|
if (notification.params.threadId !== threadId) return;
|
|
6723
6534
|
observedTurnId = notification.params.turn.id;
|
|
6724
6535
|
this.activeTurnId = notification.params.turn.id;
|
|
6536
|
+
this.mergeTranscriptTurn(notification.params.threadId, notification.params.turn);
|
|
6537
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6725
6538
|
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6726
6539
|
},
|
|
6727
6540
|
[TURN_PLAN_UPDATED_METHOD]: (notification) => {
|
|
6728
6541
|
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6729
|
-
this.
|
|
6542
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6730
6543
|
linearForwarder.sendPlan(extractPlanFromCodexAspNotification(notification));
|
|
6731
6544
|
},
|
|
6732
6545
|
[ITEM_STARTED_METHOD]: (notification) => {
|
|
@@ -6734,7 +6547,15 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6734
6547
|
if (notification.params.item.type === "contextCompaction") {
|
|
6735
6548
|
this.setCompacting(true);
|
|
6736
6549
|
}
|
|
6737
|
-
this.
|
|
6550
|
+
this.upsertTranscriptItem(
|
|
6551
|
+
notification.params.threadId,
|
|
6552
|
+
notification.params.turnId,
|
|
6553
|
+
notification.params.item,
|
|
6554
|
+
timestampFromMilliseconds(notification.params.startedAtMs),
|
|
6555
|
+
"in_progress",
|
|
6556
|
+
"started"
|
|
6557
|
+
);
|
|
6558
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6738
6559
|
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6739
6560
|
},
|
|
6740
6561
|
[ITEM_COMPLETED_METHOD]: (notification) => {
|
|
@@ -6743,13 +6564,39 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6743
6564
|
if (notification.params.item.type === "contextCompaction") {
|
|
6744
6565
|
this.setCompacting(false);
|
|
6745
6566
|
}
|
|
6746
|
-
|
|
6567
|
+
const itemFailed = notification.params.item.type === "commandExecution" && typeof notification.params.item.exitCode === "number" && notification.params.item.exitCode !== 0;
|
|
6568
|
+
this.upsertTranscriptItem(
|
|
6569
|
+
notification.params.threadId,
|
|
6570
|
+
notification.params.turnId,
|
|
6571
|
+
notification.params.item,
|
|
6572
|
+
timestampFromMilliseconds(notification.params.completedAtMs),
|
|
6573
|
+
itemFailed ? "failed" : "completed",
|
|
6574
|
+
"completed"
|
|
6575
|
+
);
|
|
6576
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6747
6577
|
linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
|
|
6748
6578
|
},
|
|
6749
6579
|
[AGENT_MESSAGE_DELTA_METHOD]: (notification) => {
|
|
6750
6580
|
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6751
6581
|
const currentText = agentMessageDeltas.get(notification.params.itemId) ?? "";
|
|
6752
6582
|
agentMessageDeltas.set(notification.params.itemId, currentText + notification.params.delta);
|
|
6583
|
+
this.appendAgentMessageDelta(
|
|
6584
|
+
notification.params.threadId,
|
|
6585
|
+
notification.params.turnId,
|
|
6586
|
+
notification.params.itemId,
|
|
6587
|
+
notification.params.delta
|
|
6588
|
+
);
|
|
6589
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6590
|
+
},
|
|
6591
|
+
[COMMAND_EXECUTION_OUTPUT_DELTA_METHOD]: (notification) => {
|
|
6592
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6593
|
+
this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
6594
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6595
|
+
},
|
|
6596
|
+
[FILE_CHANGE_OUTPUT_DELTA_METHOD]: (notification) => {
|
|
6597
|
+
if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
|
|
6598
|
+
this.appendTranscriptOutput(notification.params.turnId, notification.params.itemId, notification.params.delta);
|
|
6599
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6753
6600
|
},
|
|
6754
6601
|
[TURN_COMPLETED_METHOD]: (notification) => {
|
|
6755
6602
|
if (notification.params.threadId !== threadId) return;
|
|
@@ -6774,7 +6621,10 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6774
6621
|
});
|
|
6775
6622
|
}
|
|
6776
6623
|
}
|
|
6777
|
-
|
|
6624
|
+
const completedTurn = items.length > 0 ? { ...turn, items, itemsView: "full" } : turn;
|
|
6625
|
+
this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
|
|
6626
|
+
this.emitTranscriptUpdated(notification.params.threadId);
|
|
6627
|
+
resolveCompleted(completedTurn);
|
|
6778
6628
|
}
|
|
6779
6629
|
};
|
|
6780
6630
|
const onNotification = (notification) => {
|
|
@@ -6816,117 +6666,655 @@ var CodexAspManager = class extends CodingAgentManager {
|
|
|
6816
6666
|
}
|
|
6817
6667
|
}
|
|
6818
6668
|
}
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6669
|
+
nextTranscriptSequence() {
|
|
6670
|
+
return this.codexAspSequence++;
|
|
6671
|
+
}
|
|
6672
|
+
syncTranscriptSequence(transcript) {
|
|
6673
|
+
if (!transcript) return;
|
|
6674
|
+
let next = 0;
|
|
6675
|
+
for (const turn of transcript.turns) {
|
|
6676
|
+
for (const item of turn.items) {
|
|
6677
|
+
if (typeof item.sequence === "number" && item.sequence >= next) {
|
|
6678
|
+
next = item.sequence + 1;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
this.codexAspSequence = next;
|
|
6683
|
+
}
|
|
6684
|
+
mergeTranscriptSnapshot(snapshot) {
|
|
6685
|
+
this.codexAspTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, snapshot);
|
|
6686
|
+
this.syncTranscriptSequence(this.codexAspTranscript);
|
|
6687
|
+
return this.codexAspTranscript;
|
|
6688
|
+
}
|
|
6689
|
+
ensureTranscript(threadId) {
|
|
6690
|
+
if (this.codexAspTranscript?.threadId === threadId) {
|
|
6691
|
+
return this.codexAspTranscript;
|
|
6692
|
+
}
|
|
6693
|
+
this.codexAspTranscript = {
|
|
6694
|
+
threadId,
|
|
6695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6696
|
+
turns: []
|
|
6697
|
+
};
|
|
6698
|
+
this.codexAspSequence = 0;
|
|
6699
|
+
return this.codexAspTranscript;
|
|
6700
|
+
}
|
|
6701
|
+
ensureTranscriptTurn(threadId, turnId, startedAt) {
|
|
6702
|
+
const transcript = this.ensureTranscript(threadId);
|
|
6703
|
+
let turn = transcript.turns.find((candidate) => candidate.id === turnId);
|
|
6704
|
+
if (!turn) {
|
|
6705
|
+
turn = {
|
|
6706
|
+
id: turnId,
|
|
6707
|
+
status: "inProgress",
|
|
6708
|
+
startedAt,
|
|
6709
|
+
completedAt: null,
|
|
6710
|
+
items: []
|
|
6711
|
+
};
|
|
6712
|
+
transcript.turns.push(turn);
|
|
6713
|
+
transcript.turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
|
|
6714
|
+
return turn;
|
|
6823
6715
|
}
|
|
6824
|
-
if (
|
|
6825
|
-
|
|
6826
|
-
this.recordAndEmitDrafts(itemToAgentEventDrafts(item, "started"));
|
|
6716
|
+
if (Date.parse(startedAt) < Date.parse(turn.startedAt)) {
|
|
6717
|
+
turn.startedAt = startedAt;
|
|
6827
6718
|
}
|
|
6828
|
-
|
|
6829
|
-
this.recordAndEmitDrafts(itemToAgentEventDrafts(item, lifecycle));
|
|
6719
|
+
return turn;
|
|
6830
6720
|
}
|
|
6831
|
-
|
|
6721
|
+
mergeTranscriptTurn(threadId, turn) {
|
|
6722
|
+
const startedAt = timestampFromSeconds(turn.startedAt);
|
|
6723
|
+
const completedAt = turn.completedAt === null ? null : timestampFromSeconds(turn.completedAt);
|
|
6724
|
+
const itemTimestamp = completedAt ?? startedAt;
|
|
6725
|
+
const transcriptTurn = this.ensureTranscriptTurn(threadId, turn.id, startedAt);
|
|
6726
|
+
transcriptTurn.status = turn.status;
|
|
6727
|
+
transcriptTurn.completedAt = completedAt;
|
|
6728
|
+
const itemStatus = turn.status === "failed" ? "failed" : turn.status === "completed" ? "completed" : "in_progress";
|
|
6832
6729
|
for (const item of turn.items) {
|
|
6833
|
-
this.
|
|
6730
|
+
this.upsertTranscriptItem(
|
|
6731
|
+
threadId,
|
|
6732
|
+
turn.id,
|
|
6733
|
+
item,
|
|
6734
|
+
item.type === "userMessage" ? startedAt : itemTimestamp,
|
|
6735
|
+
itemStatus,
|
|
6736
|
+
"completed"
|
|
6737
|
+
);
|
|
6738
|
+
}
|
|
6739
|
+
if (turn.error) {
|
|
6740
|
+
const existingIndex = transcriptTurn.items.findIndex((item) => item.id === `${turn.id}-error`);
|
|
6741
|
+
const errorItem = {
|
|
6742
|
+
type: "error",
|
|
6743
|
+
id: `${turn.id}-error`,
|
|
6744
|
+
message: formatTurnFailure(turn),
|
|
6745
|
+
timestamp: itemTimestamp,
|
|
6746
|
+
sequence: existingIndex === -1 ? this.nextTranscriptSequence() : transcriptTurn.items[existingIndex].sequence
|
|
6747
|
+
};
|
|
6748
|
+
if (existingIndex === -1) {
|
|
6749
|
+
transcriptTurn.items.push(errorItem);
|
|
6750
|
+
} else {
|
|
6751
|
+
transcriptTurn.items[existingIndex] = errorItem;
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
if (this.codexAspTranscript) {
|
|
6755
|
+
this.codexAspTranscript.updatedAt = itemTimestamp;
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
upsertTranscriptItem(threadId, turnId, item, timestamp, status, lifecycle) {
|
|
6759
|
+
const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
|
|
6760
|
+
const candidate = itemToTranscriptItem(item, timestamp, status);
|
|
6761
|
+
if (!candidate) return;
|
|
6762
|
+
const existingIndex = findEquivalentCodexAspTranscriptItemIndex(turn.items, candidate);
|
|
6763
|
+
const existing = existingIndex === -1 ? null : turn.items[existingIndex];
|
|
6764
|
+
const sequence = existing?.sequence ?? this.nextTranscriptSequence();
|
|
6765
|
+
const itemTimestamp = existing && lifecycle === "completed" ? existing.timestamp : timestamp;
|
|
6766
|
+
const transcriptItem = itemToTranscriptItem(item, itemTimestamp, status);
|
|
6767
|
+
if (!transcriptItem) return;
|
|
6768
|
+
const sequencedItem = { ...transcriptItem, sequence };
|
|
6769
|
+
if (existingIndex === -1) {
|
|
6770
|
+
turn.items.push(sequencedItem);
|
|
6771
|
+
} else {
|
|
6772
|
+
const existingItem = turn.items[existingIndex];
|
|
6773
|
+
const mergedItem = mergeCodexAspTranscriptItem(existingItem, {
|
|
6774
|
+
...sequencedItem,
|
|
6775
|
+
timestamp: itemTimestamp,
|
|
6776
|
+
sequence
|
|
6777
|
+
});
|
|
6778
|
+
turn.items[existingIndex] = existingItem.type === "agentMessage" && mergedItem.type === "agentMessage" && lifecycle === "started" && mergedItem.text.length === 0 ? { ...mergedItem, text: existingItem.text } : mergedItem;
|
|
6779
|
+
}
|
|
6780
|
+
if (this.codexAspTranscript) {
|
|
6781
|
+
this.codexAspTranscript.updatedAt = timestamp;
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
appendAgentMessageDelta(threadId, turnId, itemId, delta) {
|
|
6785
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
6786
|
+
const turn = this.ensureTranscriptTurn(threadId, turnId, timestamp);
|
|
6787
|
+
const existingIndex = turn.items.findIndex((item) => item.id === itemId);
|
|
6788
|
+
if (existingIndex === -1) {
|
|
6789
|
+
turn.items.push({
|
|
6790
|
+
type: "agentMessage",
|
|
6791
|
+
id: itemId,
|
|
6792
|
+
text: delta,
|
|
6793
|
+
timestamp,
|
|
6794
|
+
sequence: this.nextTranscriptSequence()
|
|
6795
|
+
});
|
|
6796
|
+
} else {
|
|
6797
|
+
const existing = turn.items[existingIndex];
|
|
6798
|
+
if (existing.type === "agentMessage") {
|
|
6799
|
+
turn.items[existingIndex] = {
|
|
6800
|
+
...existing,
|
|
6801
|
+
text: `${existing.text}${delta}`
|
|
6802
|
+
};
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
if (this.codexAspTranscript) {
|
|
6806
|
+
this.codexAspTranscript.updatedAt = timestamp;
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
appendTranscriptOutput(turnId, itemId, delta) {
|
|
6810
|
+
if (!this.codexAspTranscript) return;
|
|
6811
|
+
const turn = this.codexAspTranscript.turns.find((candidate) => candidate.id === turnId);
|
|
6812
|
+
if (!turn) return;
|
|
6813
|
+
const itemIndex = turn.items.findIndex((item2) => item2.id === itemId);
|
|
6814
|
+
if (itemIndex === -1) return;
|
|
6815
|
+
const item = turn.items[itemIndex];
|
|
6816
|
+
if (item.type !== "commandExecution" && item.type !== "fileChange") return;
|
|
6817
|
+
turn.items[itemIndex] = {
|
|
6818
|
+
...item,
|
|
6819
|
+
output: `${item.output ?? ""}${delta}`,
|
|
6820
|
+
status: "in_progress"
|
|
6821
|
+
};
|
|
6822
|
+
this.codexAspTranscript.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6823
|
+
}
|
|
6824
|
+
seedHistoryFromThread(thread) {
|
|
6825
|
+
this.mergeTranscriptSnapshot(threadToAspTranscript(thread));
|
|
6826
|
+
}
|
|
6827
|
+
async refreshThreadGoal(host, threadId) {
|
|
6828
|
+
try {
|
|
6829
|
+
const response = await host.client.request(
|
|
6830
|
+
THREAD_GOAL_GET_METHOD,
|
|
6831
|
+
{ threadId }
|
|
6832
|
+
);
|
|
6833
|
+
this.currentGoal = response.goal ? aspGoalToChatGoal(response.goal) : null;
|
|
6834
|
+
} catch (error) {
|
|
6835
|
+
console.warn("[CodexAspManager] Failed to read ASP thread goal:", error);
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
recordGoalChange(goal, force = false) {
|
|
6839
|
+
const nextGoal = goal ? aspGoalToChatGoal(goal) : null;
|
|
6840
|
+
if (!force && JSON.stringify(this.currentGoal) === JSON.stringify(nextGoal)) {
|
|
6841
|
+
return;
|
|
6842
|
+
}
|
|
6843
|
+
this.currentGoal = nextGoal;
|
|
6844
|
+
const event = this.recordHistoryEvent(
|
|
6845
|
+
CHAT_GOAL_EVENT_TYPE,
|
|
6846
|
+
{ goal: nextGoal }
|
|
6847
|
+
);
|
|
6848
|
+
this.onEvent(event);
|
|
6849
|
+
}
|
|
6850
|
+
recordHistoryEvent(type, payload) {
|
|
6851
|
+
const event = {
|
|
6852
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6853
|
+
type,
|
|
6854
|
+
payload
|
|
6855
|
+
};
|
|
6856
|
+
this.historyEvents.push(event);
|
|
6857
|
+
return event;
|
|
6858
|
+
}
|
|
6859
|
+
emitTranscriptUpdated(threadId) {
|
|
6860
|
+
const updatedAt = this.codexAspTranscript?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6861
|
+
const transcript = this.codexAspTranscript ? structuredClone(this.codexAspTranscript) : null;
|
|
6862
|
+
const updatePayload = {
|
|
6863
|
+
updatedAt,
|
|
6864
|
+
transcript,
|
|
6865
|
+
threadId
|
|
6866
|
+
};
|
|
6867
|
+
this.onEvent({
|
|
6868
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6869
|
+
type: CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
|
|
6870
|
+
payload: updatePayload
|
|
6871
|
+
});
|
|
6872
|
+
}
|
|
6873
|
+
handleRateLimits(rateLimits) {
|
|
6874
|
+
const snapshot = extractRateLimitsSnapshot(rateLimits);
|
|
6875
|
+
if (snapshot) {
|
|
6876
|
+
this.emitQuotaStatus(snapshot);
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
async refreshQuotaSnapshot(host) {
|
|
6880
|
+
try {
|
|
6881
|
+
const response = await host.client.request(
|
|
6882
|
+
ACCOUNT_RATE_LIMITS_READ_METHOD,
|
|
6883
|
+
void 0
|
|
6884
|
+
);
|
|
6885
|
+
this.handleRateLimits(response.rateLimits);
|
|
6886
|
+
} catch {
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
emitQuotaStatus(snapshot, force = false) {
|
|
6890
|
+
const event = this.quotaStatus.apply(snapshot, force);
|
|
6891
|
+
if (!event) return;
|
|
6892
|
+
this.historyEvents.push(event);
|
|
6893
|
+
this.onEvent(event);
|
|
6894
|
+
}
|
|
6895
|
+
emitCodexTokenUsage(tokenUsage, model) {
|
|
6896
|
+
const payload = buildCodexTokenUsageContextUsagePayload({
|
|
6897
|
+
model,
|
|
6898
|
+
modelContextWindow: tokenUsage.modelContextWindow,
|
|
6899
|
+
last: {
|
|
6900
|
+
inputTokens: tokenUsage.last.inputTokens,
|
|
6901
|
+
outputTokens: tokenUsage.last.outputTokens,
|
|
6902
|
+
totalTokens: tokenUsage.last.totalTokens,
|
|
6903
|
+
cachedInputTokens: tokenUsage.last.cachedInputTokens,
|
|
6904
|
+
reasoningOutputTokens: tokenUsage.last.reasoningOutputTokens
|
|
6905
|
+
},
|
|
6906
|
+
total: {
|
|
6907
|
+
totalTokens: tokenUsage.total.totalTokens
|
|
6908
|
+
},
|
|
6909
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6910
|
+
});
|
|
6911
|
+
const event = this.emitContextUsage(payload);
|
|
6912
|
+
this.historyEvents.push(event);
|
|
6913
|
+
}
|
|
6914
|
+
};
|
|
6915
|
+
|
|
6916
|
+
// src/managers/codex-manager.ts
|
|
6917
|
+
import { Codex } from "@openai/codex-sdk";
|
|
6918
|
+
import { readdir as readdir3, stat as stat2, writeFile as writeFile8, mkdir as mkdir10, readFile as readFile7 } from "fs/promises";
|
|
6919
|
+
import { existsSync as existsSync6 } from "fs";
|
|
6920
|
+
import { join as join14 } from "path";
|
|
6921
|
+
import { homedir as homedir12 } from "os";
|
|
6922
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
6923
|
+
var DEFAULT_MODEL2 = "gpt-5.5";
|
|
6924
|
+
var CODEX_CONFIG_PATH = join14(homedir12(), ".codex", "config.toml");
|
|
6925
|
+
function isJsonlEvent2(value) {
|
|
6926
|
+
if (!isRecord4(value)) {
|
|
6927
|
+
return false;
|
|
6928
|
+
}
|
|
6929
|
+
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord4(value.payload);
|
|
6930
|
+
}
|
|
6931
|
+
function sleep(ms) {
|
|
6932
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
6933
|
+
}
|
|
6934
|
+
var CodexManager = class extends CodingAgentManager {
|
|
6935
|
+
codex;
|
|
6936
|
+
currentThreadId = null;
|
|
6937
|
+
currentThread = null;
|
|
6938
|
+
activeAbortController = null;
|
|
6939
|
+
quotaStatus = new CodexQuotaStatusTracker();
|
|
6940
|
+
constructor(options) {
|
|
6941
|
+
super(options);
|
|
6942
|
+
this.codex = this.createCodexClient();
|
|
6943
|
+
this.initializeManager(this.processMessageInternal.bind(this));
|
|
6944
|
+
}
|
|
6945
|
+
createCodexClient() {
|
|
6946
|
+
const codexApiKey = resolveCodexApiKey();
|
|
6947
|
+
return new Codex({
|
|
6948
|
+
env: buildCodexAgentEnv(),
|
|
6949
|
+
...codexApiKey ? { apiKey: codexApiKey } : {}
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6952
|
+
resetCodexClient() {
|
|
6953
|
+
this.codex = this.createCodexClient();
|
|
6954
|
+
this.currentThread = null;
|
|
6955
|
+
}
|
|
6956
|
+
async initialize() {
|
|
6957
|
+
if (this.initialSessionId) {
|
|
6958
|
+
this.currentThreadId = this.initialSessionId;
|
|
6959
|
+
console.log(`[CodexManager] Restored thread ID from persisted state: ${this.currentThreadId}`);
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
async flushQuotaSnapshotFromCurrentSession() {
|
|
6963
|
+
if (!this.currentThreadId) return;
|
|
6964
|
+
try {
|
|
6965
|
+
const sessionFile = await this.findSessionFile(this.currentThreadId);
|
|
6966
|
+
if (!sessionFile) return;
|
|
6967
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
6968
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
6969
|
+
let latest = null;
|
|
6970
|
+
for (const line of lines) {
|
|
6971
|
+
try {
|
|
6972
|
+
const parsed = JSON.parse(line);
|
|
6973
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
6974
|
+
if (snapshot) {
|
|
6975
|
+
latest = snapshot;
|
|
6976
|
+
}
|
|
6977
|
+
} catch {
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
if (latest) {
|
|
6981
|
+
this.emitQuotaStatus(latest);
|
|
6982
|
+
}
|
|
6983
|
+
} catch (error) {
|
|
6984
|
+
console.warn("[CodexManager] Failed to flush quota snapshot from session:", error);
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
emitQuotaStatus(snapshot, force = false) {
|
|
6988
|
+
const event = this.quotaStatus.apply(snapshot, force);
|
|
6989
|
+
if (event) this.onEvent(event);
|
|
6990
|
+
}
|
|
6991
|
+
async interruptActiveTurn() {
|
|
6992
|
+
if (this.activeAbortController) {
|
|
6993
|
+
this.activeAbortController.abort();
|
|
6994
|
+
}
|
|
6995
|
+
}
|
|
6996
|
+
/**
|
|
6997
|
+
* Update the developer_instructions in ~/.codex/config.toml
|
|
6998
|
+
* This sets the system prompt that Codex will use for this turn
|
|
6999
|
+
*/
|
|
7000
|
+
async updateCodexConfig(developerInstructions) {
|
|
7001
|
+
try {
|
|
7002
|
+
const codexDir = join14(homedir12(), ".codex");
|
|
7003
|
+
await mkdir10(codexDir, { recursive: true });
|
|
7004
|
+
let config = {};
|
|
7005
|
+
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
7006
|
+
try {
|
|
7007
|
+
const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
|
|
7008
|
+
const parsed = parseToml(existingContent);
|
|
7009
|
+
if (isRecord4(parsed)) {
|
|
7010
|
+
config = parsed;
|
|
7011
|
+
}
|
|
7012
|
+
} catch (parseError) {
|
|
7013
|
+
console.warn("[CodexManager] Failed to parse existing config.toml, starting fresh:", parseError);
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
if (developerInstructions) {
|
|
7017
|
+
config.developer_instructions = developerInstructions;
|
|
7018
|
+
} else {
|
|
7019
|
+
delete config.developer_instructions;
|
|
7020
|
+
}
|
|
7021
|
+
const tomlContent = stringifyToml(config);
|
|
7022
|
+
await writeFile8(CODEX_CONFIG_PATH, tomlContent, "utf-8");
|
|
7023
|
+
console.log("[CodexManager] Updated config.toml with developer_instructions");
|
|
7024
|
+
} catch (error) {
|
|
7025
|
+
console.error("[CodexManager] Failed to update config.toml:", error);
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
async processMessageInternal(request) {
|
|
7029
|
+
try {
|
|
7030
|
+
await this.executeCodexTurn(request);
|
|
7031
|
+
} catch (error) {
|
|
7032
|
+
if (isCodexAuthError(error)) {
|
|
7033
|
+
const refreshed = await codexTokenManager.fetchFreshCredentials(error instanceof Error ? error.message : String(error));
|
|
7034
|
+
if (refreshed) {
|
|
7035
|
+
this.resetCodexClient();
|
|
7036
|
+
await this.executeCodexTurn(request);
|
|
7037
|
+
return;
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
throw error;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
async executeCodexTurn(request) {
|
|
7044
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
7045
|
+
await this.flushQuotaSnapshotFromCurrentSession();
|
|
7046
|
+
if (this.quotaStatus.blocked && this.quotaStatus.latestSnapshot) {
|
|
7047
|
+
this.emitQuotaStatus(this.quotaStatus.latestSnapshot, true);
|
|
7048
|
+
try {
|
|
7049
|
+
await this.onTurnComplete();
|
|
7050
|
+
} catch (error) {
|
|
7051
|
+
console.error("[CodexManager] onTurnComplete failed during quota-blocked turn:", error);
|
|
7052
|
+
}
|
|
7053
|
+
return;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
const {
|
|
7057
|
+
message,
|
|
7058
|
+
model,
|
|
7059
|
+
customInstructions,
|
|
7060
|
+
images,
|
|
7061
|
+
permissionMode,
|
|
7062
|
+
thinkingLevel
|
|
7063
|
+
} = request;
|
|
7064
|
+
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
7065
|
+
let tempImagePaths = [];
|
|
7066
|
+
let stopTail = null;
|
|
7067
|
+
let abortController = null;
|
|
7068
|
+
try {
|
|
7069
|
+
if (images && images.length > 0) {
|
|
7070
|
+
const normalizedImages = await normalizeImages(images);
|
|
7071
|
+
tempImagePaths = await saveNormalizedImagesToTempFiles(normalizedImages);
|
|
7072
|
+
}
|
|
7073
|
+
const developerInstructions = this.buildCombinedInstructions(customInstructions);
|
|
7074
|
+
await this.updateCodexConfig(developerInstructions);
|
|
7075
|
+
const sandboxMode = "danger-full-access";
|
|
7076
|
+
const webSearchMode = "live";
|
|
7077
|
+
const codexReasoningEffort = codexReasoningEffortForThinkingLevel(thinkingLevel);
|
|
7078
|
+
const additionalDirectories = await getAgentAdditionalDirectories();
|
|
7079
|
+
const threadOptions = {
|
|
7080
|
+
workingDirectory: this.workingDirectory,
|
|
7081
|
+
skipGitRepoCheck: true,
|
|
7082
|
+
sandboxMode,
|
|
7083
|
+
model: model || DEFAULT_MODEL2,
|
|
7084
|
+
webSearchMode,
|
|
7085
|
+
additionalDirectories,
|
|
7086
|
+
...codexReasoningEffort ? { modelReasoningEffort: codexReasoningEffort } : {}
|
|
7087
|
+
};
|
|
7088
|
+
abortController = new AbortController();
|
|
7089
|
+
this.activeAbortController = abortController;
|
|
7090
|
+
if (this.currentThreadId) {
|
|
7091
|
+
this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
|
|
7092
|
+
} else {
|
|
7093
|
+
this.currentThread = this.codex.startThread(threadOptions);
|
|
7094
|
+
const { events } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
|
|
7095
|
+
for await (const event of events) {
|
|
7096
|
+
if (event.type === "thread.started") {
|
|
7097
|
+
this.currentThreadId = event.thread_id;
|
|
7098
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
7099
|
+
console.log(`[CodexManager] Captured and persisted thread ID: ${this.currentThreadId}`);
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
if (!this.currentThreadId && this.currentThread.id) {
|
|
7103
|
+
this.currentThreadId = this.currentThread.id;
|
|
7104
|
+
await this.onSaveSessionId(this.currentThreadId);
|
|
7105
|
+
console.log(`[CodexManager] Captured and persisted thread ID from thread.id: ${this.currentThreadId}`);
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
stopTail = this.currentThreadId ? await this.startSessionTail(this.currentThreadId) : null;
|
|
7109
|
+
let input;
|
|
7110
|
+
if (tempImagePaths.length > 0) {
|
|
7111
|
+
const inputItems = [
|
|
7112
|
+
{ type: "text", text: message },
|
|
7113
|
+
...tempImagePaths.map((path4) => ({ type: "local_image", path: path4 }))
|
|
7114
|
+
];
|
|
7115
|
+
input = inputItems;
|
|
7116
|
+
} else {
|
|
7117
|
+
input = message;
|
|
7118
|
+
}
|
|
7119
|
+
try {
|
|
7120
|
+
const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
|
|
7121
|
+
const linearForwarder = new LinearEventForwarder(linearSessionId);
|
|
7122
|
+
for await (const event of events) {
|
|
7123
|
+
if (linearSessionId) {
|
|
7124
|
+
linearForwarder.sendPlan(extractPlanFromCodexEvent(event));
|
|
7125
|
+
linearForwarder.sendEvent(convertCodexEvent(event, linearSessionId));
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
linearForwarder.flushThoughtAsResponse();
|
|
7129
|
+
} catch (error) {
|
|
7130
|
+
await this.flushQuotaSnapshotFromCurrentSession();
|
|
7131
|
+
if (this.quotaStatus.blocked) {
|
|
7132
|
+
return;
|
|
7133
|
+
}
|
|
7134
|
+
throw error;
|
|
7135
|
+
}
|
|
7136
|
+
} finally {
|
|
7137
|
+
if (stopTail) {
|
|
7138
|
+
await stopTail();
|
|
7139
|
+
}
|
|
7140
|
+
await removeTempImageFiles(tempImagePaths);
|
|
7141
|
+
try {
|
|
7142
|
+
await this.onTurnComplete();
|
|
7143
|
+
} catch (error) {
|
|
7144
|
+
console.error("[CodexManager] onTurnComplete failed:", error);
|
|
7145
|
+
}
|
|
7146
|
+
this.activeAbortController = null;
|
|
7147
|
+
}
|
|
7148
|
+
}
|
|
7149
|
+
async getHistory() {
|
|
7150
|
+
if (!this.currentThreadId) {
|
|
7151
|
+
return {
|
|
7152
|
+
thread_id: null,
|
|
7153
|
+
events: []
|
|
7154
|
+
};
|
|
7155
|
+
}
|
|
7156
|
+
const sessionFile = await this.findSessionFile(this.currentThreadId);
|
|
7157
|
+
if (!sessionFile) {
|
|
7158
|
+
return {
|
|
7159
|
+
thread_id: this.currentThreadId,
|
|
7160
|
+
events: []
|
|
7161
|
+
};
|
|
6834
7162
|
}
|
|
7163
|
+
const events = await readJSONL(sessionFile);
|
|
7164
|
+
return {
|
|
7165
|
+
thread_id: this.currentThreadId,
|
|
7166
|
+
events
|
|
7167
|
+
};
|
|
6835
7168
|
}
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
7169
|
+
// Helper methods for finding session files
|
|
7170
|
+
async findSessionFile(threadId) {
|
|
7171
|
+
const sessionsDir = join14(homedir12(), ".codex", "sessions");
|
|
7172
|
+
try {
|
|
7173
|
+
const now = /* @__PURE__ */ new Date();
|
|
7174
|
+
const year = now.getFullYear();
|
|
7175
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
7176
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
7177
|
+
const todayDir = join14(sessionsDir, String(year), month, day);
|
|
7178
|
+
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
7179
|
+
if (file) return file;
|
|
7180
|
+
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
7181
|
+
const date = new Date(now);
|
|
7182
|
+
date.setDate(date.getDate() - daysAgo);
|
|
7183
|
+
const searchYear = date.getFullYear();
|
|
7184
|
+
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
7185
|
+
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
7186
|
+
const searchDir = join14(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
7187
|
+
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
7188
|
+
if (file2) return file2;
|
|
6845
7189
|
}
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
this.historyEvents.splice(0, this.historyEvents.length, ...mergeHistoryEvents(this.historyEvents, events));
|
|
7190
|
+
return null;
|
|
7191
|
+
} catch (error) {
|
|
7192
|
+
return null;
|
|
7193
|
+
}
|
|
6851
7194
|
}
|
|
6852
|
-
async
|
|
7195
|
+
async findFileInDirectory(directory, threadId) {
|
|
6853
7196
|
try {
|
|
6854
|
-
const
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
7197
|
+
const files = await readdir3(directory);
|
|
7198
|
+
for (const file of files) {
|
|
7199
|
+
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
7200
|
+
const fullPath = join14(directory, file);
|
|
7201
|
+
const stats = await stat2(fullPath);
|
|
7202
|
+
if (stats.isFile()) {
|
|
7203
|
+
return fullPath;
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
return null;
|
|
6859
7208
|
} catch (error) {
|
|
6860
|
-
|
|
7209
|
+
return null;
|
|
6861
7210
|
}
|
|
6862
7211
|
}
|
|
6863
|
-
|
|
6864
|
-
const
|
|
6865
|
-
|
|
6866
|
-
|
|
7212
|
+
async waitForSessionFile(threadId, timeoutMs = 5e3) {
|
|
7213
|
+
const start = Date.now();
|
|
7214
|
+
while (Date.now() - start < timeoutMs) {
|
|
7215
|
+
const sessionFile = await this.findSessionFile(threadId);
|
|
7216
|
+
if (sessionFile) {
|
|
7217
|
+
return sessionFile;
|
|
7218
|
+
}
|
|
7219
|
+
await sleep(100);
|
|
6867
7220
|
}
|
|
6868
|
-
|
|
6869
|
-
const event = this.recordHistoryEvent(
|
|
6870
|
-
CHAT_GOAL_EVENT_TYPE,
|
|
6871
|
-
{ goal: nextGoal }
|
|
6872
|
-
);
|
|
6873
|
-
this.onEvent(event);
|
|
7221
|
+
return null;
|
|
6874
7222
|
}
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
this.
|
|
7223
|
+
// @openai/codex-sdk doesn't expose manual /compact (TUI-only); we only mirror the auto-compaction rollout entries to the UI.
|
|
7224
|
+
trackNativeCompaction(event) {
|
|
7225
|
+
if (event.type === "compacted") {
|
|
7226
|
+
this.setCompacting(false);
|
|
7227
|
+
return;
|
|
6879
7228
|
}
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
}
|
|
6890
|
-
handleRateLimits(rateLimits) {
|
|
6891
|
-
const snapshot = extractRateLimitsSnapshot(rateLimits);
|
|
6892
|
-
if (snapshot) {
|
|
6893
|
-
this.emitQuotaStatus(snapshot);
|
|
7229
|
+
if (event.type !== "event_msg") return;
|
|
7230
|
+
const msg = event.payload.msg;
|
|
7231
|
+
if (!msg) return;
|
|
7232
|
+
const itemType = msg.payload?.item?.type;
|
|
7233
|
+
if (itemType !== "context_compaction") return;
|
|
7234
|
+
if (msg.type === "item_started") {
|
|
7235
|
+
this.setCompacting(true);
|
|
7236
|
+
} else if (msg.type === "item_completed") {
|
|
7237
|
+
this.setCompacting(false);
|
|
6894
7238
|
}
|
|
6895
7239
|
}
|
|
6896
|
-
async
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
);
|
|
6902
|
-
this.handleRateLimits(response.rateLimits);
|
|
6903
|
-
} catch {
|
|
7240
|
+
async startSessionTail(threadId) {
|
|
7241
|
+
const sessionFile = await this.waitForSessionFile(threadId);
|
|
7242
|
+
if (!sessionFile) {
|
|
7243
|
+
return async () => {
|
|
7244
|
+
};
|
|
6904
7245
|
}
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
const
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
}
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
const
|
|
6929
|
-
|
|
7246
|
+
let active = true;
|
|
7247
|
+
const seenLines = /* @__PURE__ */ new Set();
|
|
7248
|
+
const seedSeenLines = async () => {
|
|
7249
|
+
try {
|
|
7250
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
7251
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
7252
|
+
let latest = null;
|
|
7253
|
+
for (const line of lines) {
|
|
7254
|
+
seenLines.add(line);
|
|
7255
|
+
try {
|
|
7256
|
+
const parsed = JSON.parse(line);
|
|
7257
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
7258
|
+
if (snapshot) latest = snapshot;
|
|
7259
|
+
} catch {
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
if (latest) {
|
|
7263
|
+
this.quotaStatus.prime(latest);
|
|
7264
|
+
}
|
|
7265
|
+
} catch {
|
|
7266
|
+
}
|
|
7267
|
+
};
|
|
7268
|
+
await seedSeenLines();
|
|
7269
|
+
const pump = async () => {
|
|
7270
|
+
let emitted = 0;
|
|
7271
|
+
try {
|
|
7272
|
+
const content = await readFile7(sessionFile, "utf-8");
|
|
7273
|
+
const lines = content.split("\n");
|
|
7274
|
+
const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
|
|
7275
|
+
for (const line of completeLines) {
|
|
7276
|
+
const trimmed = line.trim();
|
|
7277
|
+
if (!trimmed || seenLines.has(trimmed)) {
|
|
7278
|
+
continue;
|
|
7279
|
+
}
|
|
7280
|
+
seenLines.add(trimmed);
|
|
7281
|
+
try {
|
|
7282
|
+
const parsed = JSON.parse(trimmed);
|
|
7283
|
+
const snapshot = extractCodexRateLimitsSnapshotFromJsonl(parsed);
|
|
7284
|
+
if (snapshot) {
|
|
7285
|
+
this.emitQuotaStatus(snapshot);
|
|
7286
|
+
}
|
|
7287
|
+
if (isJsonlEvent2(parsed)) {
|
|
7288
|
+
this.trackNativeCompaction(parsed);
|
|
7289
|
+
this.onEvent(parsed);
|
|
7290
|
+
emitted += 1;
|
|
7291
|
+
}
|
|
7292
|
+
} catch {
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
} catch {
|
|
7296
|
+
}
|
|
7297
|
+
return emitted;
|
|
7298
|
+
};
|
|
7299
|
+
const loop = (async () => {
|
|
7300
|
+
while (active) {
|
|
7301
|
+
await pump();
|
|
7302
|
+
await sleep(100);
|
|
7303
|
+
}
|
|
7304
|
+
await pump();
|
|
7305
|
+
})();
|
|
7306
|
+
return async () => {
|
|
7307
|
+
active = false;
|
|
7308
|
+
await loop;
|
|
7309
|
+
const deadline = Date.now() + 1500;
|
|
7310
|
+
while (Date.now() < deadline) {
|
|
7311
|
+
const emitted = await pump();
|
|
7312
|
+
if (emitted > 0) {
|
|
7313
|
+
continue;
|
|
7314
|
+
}
|
|
7315
|
+
await sleep(100);
|
|
7316
|
+
}
|
|
7317
|
+
};
|
|
6930
7318
|
}
|
|
6931
7319
|
};
|
|
6932
7320
|
|
|
@@ -7543,18 +7931,58 @@ var RELAY_HISTORY_DIR = join15(ENGINE_DIR2, "relay-histories");
|
|
|
7543
7931
|
var CHAT_SENDERS_DIR = join15(ENGINE_DIR2, "chat-senders");
|
|
7544
7932
|
var CODEX_AUTH_PATH2 = join15(homedir13(), ".codex", "auth.json");
|
|
7545
7933
|
function isChatMessageSender(value) {
|
|
7546
|
-
if (!
|
|
7934
|
+
if (!isRecord4(value)) return false;
|
|
7547
7935
|
return typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
|
|
7548
7936
|
}
|
|
7549
7937
|
function isCodexAvailable() {
|
|
7550
7938
|
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
7551
7939
|
}
|
|
7940
|
+
function isSameAcceptedUserEvent(event, acceptedEvent) {
|
|
7941
|
+
if (areSameUserMessageEvents(event, acceptedEvent)) return true;
|
|
7942
|
+
const eventMessage = getUserMessage(event);
|
|
7943
|
+
const acceptedMessage = getUserMessage(acceptedEvent);
|
|
7944
|
+
return Boolean(eventMessage) && eventMessage === acceptedMessage && Math.abs(getEventTimestampMs(event) - getEventTimestampMs(acceptedEvent)) <= 3e4;
|
|
7945
|
+
}
|
|
7946
|
+
function getCodexTranscriptUserMessages(transcript) {
|
|
7947
|
+
if (!transcript) return [];
|
|
7948
|
+
const messages = [];
|
|
7949
|
+
for (const turn of transcript.turns) {
|
|
7950
|
+
for (const item of turn.items) {
|
|
7951
|
+
if (item.type === "userMessage" && item.content.trim()) {
|
|
7952
|
+
messages.push(item.content);
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
}
|
|
7956
|
+
return messages;
|
|
7957
|
+
}
|
|
7958
|
+
function getCodexTranscriptFromEvent(event) {
|
|
7959
|
+
if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
|
|
7960
|
+
const transcript = event.payload.transcript;
|
|
7961
|
+
return isCodexAspTranscript(transcript) ? transcript : null;
|
|
7962
|
+
}
|
|
7963
|
+
function acceptedEventInCodexTranscript(acceptedEvent, transcript) {
|
|
7964
|
+
const message = getUserMessage(acceptedEvent);
|
|
7965
|
+
if (!message) return false;
|
|
7966
|
+
return getCodexTranscriptUserMessages(transcript).includes(message);
|
|
7967
|
+
}
|
|
7552
7968
|
function isPersistedChat(value) {
|
|
7553
|
-
if (!
|
|
7969
|
+
if (!isRecord4(value)) {
|
|
7554
7970
|
return false;
|
|
7555
7971
|
}
|
|
7556
7972
|
const candidate = value;
|
|
7557
|
-
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
|
|
7973
|
+
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string") && (candidate.codexBackend === void 0 || candidate.codexBackend === "sdk" || candidate.codexBackend === "asp");
|
|
7974
|
+
}
|
|
7975
|
+
function codexBackendForChat(chat) {
|
|
7976
|
+
if (chat.provider !== "codex") return "asp";
|
|
7977
|
+
if (chat.codexBackend) return chat.codexBackend;
|
|
7978
|
+
return chat.providerSessionId ? "sdk" : "asp";
|
|
7979
|
+
}
|
|
7980
|
+
function normalizePersistedChat(chat) {
|
|
7981
|
+
return {
|
|
7982
|
+
...chat,
|
|
7983
|
+
parentChatId: chat.parentChatId ?? null,
|
|
7984
|
+
...chat.provider === "codex" ? { codexBackend: codexBackendForChat(chat) } : {}
|
|
7985
|
+
};
|
|
7558
7986
|
}
|
|
7559
7987
|
function createUserMessageEvent(message, messageId) {
|
|
7560
7988
|
return {
|
|
@@ -7568,7 +7996,6 @@ function createUserMessageEvent(message, messageId) {
|
|
|
7568
7996
|
}
|
|
7569
7997
|
};
|
|
7570
7998
|
}
|
|
7571
|
-
var isSameUserMessageEvent = areSameUserMessageEvents;
|
|
7572
7999
|
var ChatService = class {
|
|
7573
8000
|
constructor(workingDirectory) {
|
|
7574
8001
|
this.workingDirectory = workingDirectory;
|
|
@@ -7632,7 +8059,8 @@ var ChatService = class {
|
|
|
7632
8059
|
createdAt: now,
|
|
7633
8060
|
updatedAt: now,
|
|
7634
8061
|
providerSessionId: null,
|
|
7635
|
-
parentChatId
|
|
8062
|
+
parentChatId,
|
|
8063
|
+
...request.provider === "codex" ? { codexBackend: "asp" } : {}
|
|
7636
8064
|
};
|
|
7637
8065
|
const runtime = this.createRuntimeChat(persisted);
|
|
7638
8066
|
this.chats.set(persisted.id, runtime);
|
|
@@ -7729,6 +8157,37 @@ var ChatService = class {
|
|
|
7729
8157
|
});
|
|
7730
8158
|
return result;
|
|
7731
8159
|
}
|
|
8160
|
+
async clearGoal(chatId) {
|
|
8161
|
+
const chat = this.requireChat(chatId);
|
|
8162
|
+
if (!chat.provider.clearGoal) {
|
|
8163
|
+
return { interrupted: false, queue: [], goal: null };
|
|
8164
|
+
}
|
|
8165
|
+
const interruptResult = await chat.provider.interrupt();
|
|
8166
|
+
chat.hasActiveTurn = false;
|
|
8167
|
+
chat.activeMessageId = null;
|
|
8168
|
+
keepAliveService.stop();
|
|
8169
|
+
chat.pendingMessageIds = [];
|
|
8170
|
+
chat.acceptedUserEvents.clear();
|
|
8171
|
+
const goal = await chat.provider.clearGoal();
|
|
8172
|
+
this.touch(chat);
|
|
8173
|
+
await this.publish({
|
|
8174
|
+
type: "chat.interrupted",
|
|
8175
|
+
payload: {
|
|
8176
|
+
chatId,
|
|
8177
|
+
interrupted: interruptResult.interrupted,
|
|
8178
|
+
queue: interruptResult.queue
|
|
8179
|
+
}
|
|
8180
|
+
});
|
|
8181
|
+
await this.publish({
|
|
8182
|
+
type: "chat.updated",
|
|
8183
|
+
payload: { chat: this.toSummary(chat) }
|
|
8184
|
+
});
|
|
8185
|
+
return {
|
|
8186
|
+
interrupted: interruptResult.interrupted,
|
|
8187
|
+
queue: interruptResult.queue,
|
|
8188
|
+
goal
|
|
8189
|
+
};
|
|
8190
|
+
}
|
|
7732
8191
|
getChatQueue(chatId) {
|
|
7733
8192
|
const chat = this.requireChat(chatId);
|
|
7734
8193
|
return {
|
|
@@ -7807,10 +8266,17 @@ var ChatService = class {
|
|
|
7807
8266
|
chat.provider.getHistory(),
|
|
7808
8267
|
this.readSenders(chatId)
|
|
7809
8268
|
]);
|
|
7810
|
-
const
|
|
8269
|
+
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
8270
|
+
if (acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript)) {
|
|
8271
|
+
chat.acceptedUserEvents.delete(messageId);
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
const acceptedEvents = [...chat.acceptedUserEvents.values()].filter((acceptedEvent) => !acceptedEventInCodexTranscript(acceptedEvent, history.codexAspTranscript) && !history.events.some((event) => isSameAcceptedUserEvent(event, acceptedEvent)));
|
|
8275
|
+
const events = [...history.events, ...acceptedEvents].sort((a, b) => getEventTimestampMs(a) - getEventTimestampMs(b));
|
|
7811
8276
|
return {
|
|
7812
8277
|
thread_id: history.thread_id,
|
|
7813
|
-
events
|
|
8278
|
+
events,
|
|
8279
|
+
codexAspTranscript: history.codexAspTranscript ?? null,
|
|
7814
8280
|
goal: history.goal ?? chat.provider.getGoal?.() ?? null,
|
|
7815
8281
|
senders
|
|
7816
8282
|
};
|
|
@@ -7865,7 +8331,7 @@ var ChatService = class {
|
|
|
7865
8331
|
codexAvailable: isCodexAvailable()
|
|
7866
8332
|
});
|
|
7867
8333
|
} else {
|
|
7868
|
-
const CodexProviderCtor =
|
|
8334
|
+
const CodexProviderCtor = codexBackendForChat(persisted) === "sdk" ? CodexManager : CodexAspManager;
|
|
7869
8335
|
provider = new CodexProviderCtor({
|
|
7870
8336
|
workingDirectory: this.workingDirectory,
|
|
7871
8337
|
initialSessionId: persisted.providerSessionId,
|
|
@@ -7899,6 +8365,7 @@ var ChatService = class {
|
|
|
7899
8365
|
processing: chat.provider.isProcessing(),
|
|
7900
8366
|
awaitingInput: chat.provider.isAwaitingInput?.() ?? false,
|
|
7901
8367
|
isCompacting: chat.provider.isCompacting?.() ?? false,
|
|
8368
|
+
isAuthRetrying: chat.provider.isAuthRetrying?.() ?? false,
|
|
7902
8369
|
goal: chat.provider.getGoal?.() ?? null,
|
|
7903
8370
|
parentChatId: chat.persisted.parentChatId
|
|
7904
8371
|
};
|
|
@@ -7945,12 +8412,20 @@ var ChatService = class {
|
|
|
7945
8412
|
};
|
|
7946
8413
|
}
|
|
7947
8414
|
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
7948
|
-
if (
|
|
8415
|
+
if (isSameAcceptedUserEvent(event, acceptedEvent)) {
|
|
7949
8416
|
chat.acceptedUserEvents.delete(messageId);
|
|
7950
8417
|
break;
|
|
7951
8418
|
}
|
|
7952
8419
|
}
|
|
7953
8420
|
}
|
|
8421
|
+
const codexTranscript = getCodexTranscriptFromEvent(event);
|
|
8422
|
+
if (codexTranscript) {
|
|
8423
|
+
for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
|
|
8424
|
+
if (acceptedEventInCodexTranscript(acceptedEvent, codexTranscript)) {
|
|
8425
|
+
chat.acceptedUserEvents.delete(messageId);
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
}
|
|
7954
8429
|
this.touch(chat);
|
|
7955
8430
|
this.observeCurrentBranches(chat).catch(() => {
|
|
7956
8431
|
});
|
|
@@ -7962,7 +8437,7 @@ var ChatService = class {
|
|
|
7962
8437
|
}
|
|
7963
8438
|
}).catch(() => {
|
|
7964
8439
|
});
|
|
7965
|
-
if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === CHAT_GOAL_EVENT_TYPE) {
|
|
8440
|
+
if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === AUTH_RETRY_STATUS_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE) {
|
|
7966
8441
|
this.publish({
|
|
7967
8442
|
type: "chat.updated",
|
|
7968
8443
|
payload: { chat: this.toSummary(chat) }
|
|
@@ -8004,7 +8479,7 @@ var ChatService = class {
|
|
|
8004
8479
|
if (!Array.isArray(parsed)) {
|
|
8005
8480
|
return [];
|
|
8006
8481
|
}
|
|
8007
|
-
return parsed.filter((entry) => isPersistedChat(entry));
|
|
8482
|
+
return parsed.filter((entry) => isPersistedChat(entry)).map((entry) => normalizePersistedChat(entry));
|
|
8008
8483
|
} catch (error) {
|
|
8009
8484
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
8010
8485
|
return [];
|
|
@@ -8999,6 +9474,17 @@ function createV1Routes(deps) {
|
|
|
8999
9474
|
return c.json(jsonError("Failed to interrupt chat", error instanceof Error ? error.message : "Unknown error"), 404);
|
|
9000
9475
|
}
|
|
9001
9476
|
});
|
|
9477
|
+
app2.post("/chats/:chatId/goal/clear", async (c) => {
|
|
9478
|
+
try {
|
|
9479
|
+
const result = await deps.chatService.clearGoal(c.req.param("chatId"));
|
|
9480
|
+
return c.json(result);
|
|
9481
|
+
} catch (error) {
|
|
9482
|
+
if (error instanceof ChatNotFoundError) {
|
|
9483
|
+
return c.json(jsonError("Failed to clear goal", error.message), 404);
|
|
9484
|
+
}
|
|
9485
|
+
return c.json(jsonError("Failed to clear goal", error instanceof Error ? error.message : "Unknown error"), 404);
|
|
9486
|
+
}
|
|
9487
|
+
});
|
|
9002
9488
|
app2.get("/chats/:chatId/queue", (c) => {
|
|
9003
9489
|
try {
|
|
9004
9490
|
const result = deps.chatService.getChatQueue(c.req.param("chatId"));
|