codeksei 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.en.md +215 -0
- package/README.md +259 -0
- package/bin/codeksei.js +10 -0
- package/bin/cyberboss.js +11 -0
- package/package.json +86 -0
- package/scripts/install-background-tasks.ps1 +135 -0
- package/scripts/open_shared_wechat_thread.sh +94 -0
- package/scripts/open_wechat_thread.sh +117 -0
- package/scripts/shared-common.js +791 -0
- package/scripts/shared-open.js +46 -0
- package/scripts/shared-start.js +41 -0
- package/scripts/shared-status.js +74 -0
- package/scripts/shared-supervisor.js +141 -0
- package/scripts/shared-task-runner.ps1 +87 -0
- package/scripts/shared-watchdog.js +290 -0
- package/scripts/show_shared_status.sh +53 -0
- package/scripts/start_shared_app_server.sh +65 -0
- package/scripts/start_shared_wechat.sh +108 -0
- package/scripts/timeline-screenshot.sh +15 -0
- package/scripts/uninstall-background-tasks.ps1 +23 -0
- package/src/adapters/channel/weixin/account-store.js +135 -0
- package/src/adapters/channel/weixin/api-v2.js +258 -0
- package/src/adapters/channel/weixin/api.js +180 -0
- package/src/adapters/channel/weixin/context-token-store.js +84 -0
- package/src/adapters/channel/weixin/index.js +605 -0
- package/src/adapters/channel/weixin/legacy.js +567 -0
- package/src/adapters/channel/weixin/login-common.js +63 -0
- package/src/adapters/channel/weixin/login-legacy.js +124 -0
- package/src/adapters/channel/weixin/login-v2.js +186 -0
- package/src/adapters/channel/weixin/media-mime.js +22 -0
- package/src/adapters/channel/weixin/media-receive.js +370 -0
- package/src/adapters/channel/weixin/media-send.js +331 -0
- package/src/adapters/channel/weixin/message-utils-v2.js +282 -0
- package/src/adapters/channel/weixin/message-utils.js +199 -0
- package/src/adapters/channel/weixin/protocol.js +77 -0
- package/src/adapters/channel/weixin/redact.js +41 -0
- package/src/adapters/channel/weixin/reminder-queue-store.js +101 -0
- package/src/adapters/channel/weixin/sync-buffer-store.js +35 -0
- package/src/adapters/runtime/codex/events.js +252 -0
- package/src/adapters/runtime/codex/index.js +502 -0
- package/src/adapters/runtime/codex/message-utils.js +141 -0
- package/src/adapters/runtime/codex/model-catalog.js +106 -0
- package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -0
- package/src/adapters/runtime/codex/rpc-client.js +443 -0
- package/src/adapters/runtime/codex/session-store.js +376 -0
- package/src/app/channel-send-file-cli.js +57 -0
- package/src/app/diary-write-cli.js +620 -0
- package/src/app/note-auto-cli.js +201 -0
- package/src/app/note-sync-cli.js +130 -0
- package/src/app/project-radar-cli.js +165 -0
- package/src/app/reminder-write-cli.js +210 -0
- package/src/app/review-cli.js +134 -0
- package/src/app/system-checkin-poller.js +100 -0
- package/src/app/system-send-cli.js +129 -0
- package/src/app/timeline-event-cli.js +273 -0
- package/src/app/timeline-screenshot-cli.js +109 -0
- package/src/core/app.js +1810 -0
- package/src/core/branding.js +167 -0
- package/src/core/command-registry.js +609 -0
- package/src/core/config.js +84 -0
- package/src/core/default-targets.js +163 -0
- package/src/core/durable-note-schema.js +325 -0
- package/src/core/instructions-template.js +31 -0
- package/src/core/note-sync.js +433 -0
- package/src/core/project-radar.js +402 -0
- package/src/core/review-semantic.js +524 -0
- package/src/core/review.js +1081 -0
- package/src/core/shared-bridge-heartbeat.js +140 -0
- package/src/core/stream-delivery.js +990 -0
- package/src/core/system-message-dispatcher.js +68 -0
- package/src/core/system-message-queue-store.js +128 -0
- package/src/core/thread-state-store.js +135 -0
- package/src/core/timeline-screenshot-queue-store.js +134 -0
- package/src/core/workspace-alias.js +163 -0
- package/src/core/workspace-bootstrap.js +338 -0
- package/src/index.js +270 -0
- package/src/integrations/timeline/index.js +191 -0
- package/templates/weixin-instructions.md +53 -0
- package/templates/weixin-operations.md +69 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
function extractModelCatalogFromListResponse(response) {
|
|
2
|
+
const candidates = Array.isArray(response?.result?.data)
|
|
3
|
+
? response.result.data
|
|
4
|
+
: Array.isArray(response?.data)
|
|
5
|
+
? response.data
|
|
6
|
+
: [];
|
|
7
|
+
return normalizeModelCatalog(candidates);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function resolveEffectiveModelForEffort(models, currentModel) {
|
|
11
|
+
if (!Array.isArray(models) || !models.length) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const normalizedCurrent = normalizeText(currentModel).toLowerCase();
|
|
15
|
+
if (normalizedCurrent) {
|
|
16
|
+
const matched = findModelByQuery(models, normalizedCurrent);
|
|
17
|
+
if (matched) {
|
|
18
|
+
return matched;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return models.find((item) => item.isDefault) || models[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findModelByQuery(models, query) {
|
|
25
|
+
const normalizedQuery = normalizeText(query).toLowerCase();
|
|
26
|
+
if (!normalizedQuery || !Array.isArray(models)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return models.find((item) => (
|
|
30
|
+
normalizeText(item?.model).toLowerCase() === normalizedQuery
|
|
31
|
+
|| normalizeText(item?.id).toLowerCase() === normalizedQuery
|
|
32
|
+
)) || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeModelCatalog(models) {
|
|
36
|
+
if (!Array.isArray(models)) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const normalized = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const model of models) {
|
|
42
|
+
if (!model || typeof model !== "object") {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const modelId = normalizeText(model.model);
|
|
46
|
+
const id = normalizeText(model.id);
|
|
47
|
+
const normalizedModel = modelId || id;
|
|
48
|
+
if (!normalizedModel) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const dedupeKey = normalizedModel.toLowerCase();
|
|
52
|
+
if (seen.has(dedupeKey)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
seen.add(dedupeKey);
|
|
56
|
+
normalized.push({
|
|
57
|
+
id,
|
|
58
|
+
model: normalizedModel,
|
|
59
|
+
displayName: normalizeText(model.displayName || model.display_name),
|
|
60
|
+
supportedReasoningEfforts: normalizeReasoningEfforts(
|
|
61
|
+
model.supportedReasoningEfforts || model.supported_reasoning_efforts
|
|
62
|
+
),
|
|
63
|
+
defaultReasoningEffort: normalizeText(model.defaultReasoningEffort || model.default_reasoning_effort),
|
|
64
|
+
isDefault: !!(model.isDefault || model.is_default),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeReasoningEfforts(efforts) {
|
|
71
|
+
if (!Array.isArray(efforts)) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const result = [];
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const effort of efforts) {
|
|
77
|
+
const normalized = normalizeText(
|
|
78
|
+
typeof effort === "string"
|
|
79
|
+
? effort
|
|
80
|
+
: effort?.reasoningEffort || effort?.reasoning_effort
|
|
81
|
+
);
|
|
82
|
+
if (!normalized) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const key = normalized.toLowerCase();
|
|
86
|
+
if (seen.has(key)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
seen.add(key);
|
|
90
|
+
result.push(normalized);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeText(value) {
|
|
96
|
+
return typeof value === "string" ? value.trim() : "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
extractModelCatalogFromListResponse,
|
|
101
|
+
findModelByQuery,
|
|
102
|
+
normalizeModelCatalog,
|
|
103
|
+
normalizeText,
|
|
104
|
+
resolveEffectiveModelForEffort,
|
|
105
|
+
};
|
|
106
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const SUSPICIOUS_PATTERNS = [
|
|
2
|
+
/\b(?:analysis|commentary|final|summary)\s+to=[a-z0-9_.-]+/i,
|
|
3
|
+
/\bto=functions\.[a-z0-9_]+/i,
|
|
4
|
+
/\bfunctions\.[a-z0-9_]+\b/i,
|
|
5
|
+
/\bas_string\s*=\s*(?:true|false)\b/i,
|
|
6
|
+
/\brecipient_name\b/i,
|
|
7
|
+
/\btool_uses\b/i,
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function sanitizeProtocolLeakText(text) {
|
|
11
|
+
const normalizedText = normalizeLineEndings(text);
|
|
12
|
+
if (!normalizedText) {
|
|
13
|
+
return {
|
|
14
|
+
text: "",
|
|
15
|
+
changed: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let cutIndex = -1;
|
|
20
|
+
for (const pattern of SUSPICIOUS_PATTERNS) {
|
|
21
|
+
const match = pattern.exec(normalizedText);
|
|
22
|
+
if (!match || typeof match.index !== "number" || match.index < 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (cutIndex === -1 || match.index < cutIndex) {
|
|
26
|
+
cutIndex = match.index;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (cutIndex === -1) {
|
|
31
|
+
return {
|
|
32
|
+
text: normalizedText,
|
|
33
|
+
changed: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const safeCutIndex = findSafeProtocolCutIndex(normalizedText, cutIndex);
|
|
38
|
+
const truncated = normalizedText
|
|
39
|
+
.slice(0, safeCutIndex)
|
|
40
|
+
.replace(/\s+$/g, "")
|
|
41
|
+
.replace(/[|·::,,;;、\-–—\s]+$/g, "")
|
|
42
|
+
.trim();
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
text: truncated,
|
|
46
|
+
changed: truncated !== normalizedText,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeLineEndings(value) {
|
|
51
|
+
return String(value || "").replace(/\r\n/g, "\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function findSafeProtocolCutIndex(text, leakStartIndex) {
|
|
55
|
+
if (!text || leakStartIndex <= 0) {
|
|
56
|
+
return Math.max(0, leakStartIndex);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const prefix = text.slice(0, leakStartIndex);
|
|
60
|
+
let lastBoundary = -1;
|
|
61
|
+
for (let index = prefix.length - 1; index >= 0; index -= 1) {
|
|
62
|
+
const char = prefix[index];
|
|
63
|
+
if (char === "。" || char === "!" || char === "?" || char === "!" || char === "?") {
|
|
64
|
+
lastBoundary = index + 1;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (lastBoundary !== -1) {
|
|
70
|
+
return lastBoundary;
|
|
71
|
+
}
|
|
72
|
+
return leakStartIndex;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { sanitizeProtocolLeakText };
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const WebSocket = require("ws");
|
|
4
|
+
const { PRIMARY_RPC_CLIENT_INFO } = require("../../../core/branding");
|
|
5
|
+
const { readPrefixedEnv } = require("../../../core/branding");
|
|
6
|
+
|
|
7
|
+
const IS_WINDOWS = os.platform() === "win32";
|
|
8
|
+
const DEFAULT_CODEX_COMMAND = "codex";
|
|
9
|
+
const WINDOWS_EXECUTABLE_SUFFIX_RE = /\.(cmd|exe|bat)$/i;
|
|
10
|
+
const CODEX_CLIENT_INFO = PRIMARY_RPC_CLIENT_INFO;
|
|
11
|
+
|
|
12
|
+
class CodexRpcClient {
|
|
13
|
+
constructor({ endpoint = "", env = process.env, codexCommand = "", extraWritableRoots = [] }) {
|
|
14
|
+
this.endpoint = endpoint;
|
|
15
|
+
this.env = env;
|
|
16
|
+
this.codexCommand = codexCommand || resolveDefaultCodexCommand(env);
|
|
17
|
+
this.extraWritableRoots = normalizeWritableRoots(extraWritableRoots);
|
|
18
|
+
this.mode = endpoint ? "websocket" : "spawn";
|
|
19
|
+
this.socket = null;
|
|
20
|
+
this.child = null;
|
|
21
|
+
this.stdoutBuffer = "";
|
|
22
|
+
this.pending = new Map();
|
|
23
|
+
this.isReady = false;
|
|
24
|
+
this.messageListeners = new Set();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async connect() {
|
|
28
|
+
if (this.isConnected()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (this.mode === "websocket") {
|
|
32
|
+
await this.connectWebSocket();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await this.connectSpawn();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async connectSpawn() {
|
|
39
|
+
const commandCandidates = buildCodexCommandCandidates(this.codexCommand);
|
|
40
|
+
let child = null;
|
|
41
|
+
let lastError = null;
|
|
42
|
+
|
|
43
|
+
for (const command of commandCandidates) {
|
|
44
|
+
try {
|
|
45
|
+
const spawnSpec = buildSpawnSpec(command);
|
|
46
|
+
child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
47
|
+
env: { ...this.env },
|
|
48
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
49
|
+
shell: false,
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
if (error?.code !== "ENOENT" && error?.code !== "EINVAL") {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!child) {
|
|
62
|
+
const attempted = commandCandidates.join(", ");
|
|
63
|
+
const detail = lastError?.message ? `: ${lastError.message}` : "";
|
|
64
|
+
throw new Error(`Unable to spawn Codex app-server. Tried ${attempted}${detail}.`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.child = child;
|
|
68
|
+
child.on("error", () => {
|
|
69
|
+
this.isReady = false;
|
|
70
|
+
});
|
|
71
|
+
child.stdout.on("data", (chunk) => {
|
|
72
|
+
this.stdoutBuffer += chunk.toString("utf8");
|
|
73
|
+
const lines = this.stdoutBuffer.split("\n");
|
|
74
|
+
this.stdoutBuffer = lines.pop() || "";
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (trimmed) {
|
|
78
|
+
this.handleIncoming(trimmed);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
child.on("close", () => {
|
|
83
|
+
this.isReady = false;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async connectWebSocket() {
|
|
88
|
+
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (this.socket) {
|
|
92
|
+
try {
|
|
93
|
+
this.socket.terminate();
|
|
94
|
+
} catch {
|
|
95
|
+
// best effort
|
|
96
|
+
}
|
|
97
|
+
this.socket = null;
|
|
98
|
+
}
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
let settled = false;
|
|
101
|
+
const socket = new WebSocket(this.endpoint);
|
|
102
|
+
this.socket = socket;
|
|
103
|
+
socket.on("open", () => {
|
|
104
|
+
settled = true;
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
socket.on("error", (error) => {
|
|
108
|
+
if (!settled) {
|
|
109
|
+
this.socket = null;
|
|
110
|
+
reject(error);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.handleTransportClosed("Codex websocket errored");
|
|
114
|
+
});
|
|
115
|
+
socket.on("message", (chunk) => {
|
|
116
|
+
const message = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
117
|
+
if (message.trim()) {
|
|
118
|
+
this.handleIncoming(message);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
socket.on("close", () => {
|
|
122
|
+
if (!settled) {
|
|
123
|
+
this.socket = null;
|
|
124
|
+
reject(new Error("Codex websocket closed before connection completed"));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.handleTransportClosed("Codex websocket closed");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
isConnected() {
|
|
133
|
+
if (this.mode === "websocket") {
|
|
134
|
+
return Boolean(this.socket && this.socket.readyState === WebSocket.OPEN);
|
|
135
|
+
}
|
|
136
|
+
return Boolean(this.child && this.child.stdin && this.child.stdin.writable);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onMessage(listener) {
|
|
140
|
+
this.messageListeners.add(listener);
|
|
141
|
+
return () => this.messageListeners.delete(listener);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async initialize() {
|
|
145
|
+
if (this.isReady) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await this.sendRequest("initialize", {
|
|
149
|
+
clientInfo: CODEX_CLIENT_INFO,
|
|
150
|
+
capabilities: {
|
|
151
|
+
experimentalApi: true,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
await this.sendNotification("initialized", null);
|
|
155
|
+
this.isReady = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async sendUserMessage({ threadId, text, model = null, effort = null, accessMode = null, workspaceRoot = "" }) {
|
|
159
|
+
const input = buildTurnInputPayload(text);
|
|
160
|
+
return threadId
|
|
161
|
+
? this.sendRequest("turn/start", buildTurnStartParams({
|
|
162
|
+
threadId,
|
|
163
|
+
input,
|
|
164
|
+
model,
|
|
165
|
+
effort,
|
|
166
|
+
accessMode,
|
|
167
|
+
workspaceRoot,
|
|
168
|
+
extraWritableRoots: this.extraWritableRoots,
|
|
169
|
+
}))
|
|
170
|
+
: this.sendRequest("thread/start", { input });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async startThread({ cwd }) {
|
|
174
|
+
return this.sendRequest("thread/start", buildStartThreadParams(cwd));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async resumeThread({ threadId }) {
|
|
178
|
+
const normalizedThreadId = normalizeNonEmptyString(threadId);
|
|
179
|
+
if (!normalizedThreadId) {
|
|
180
|
+
throw new Error("thread/resume requires a non-empty threadId");
|
|
181
|
+
}
|
|
182
|
+
return this.sendRequest("thread/resume", { threadId: normalizedThreadId });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async listThreads({ cursor = null, limit = 100, sortKey = "updated_at" } = {}) {
|
|
186
|
+
return this.sendRequest("thread/list", buildListThreadsParams({
|
|
187
|
+
cursor,
|
|
188
|
+
limit,
|
|
189
|
+
sortKey,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async listModels() {
|
|
194
|
+
return this.sendRequest("model/list", {});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async cancelTurn({ threadId, turnId }) {
|
|
198
|
+
const normalizedThreadId = normalizeNonEmptyString(threadId);
|
|
199
|
+
const normalizedTurnId = normalizeNonEmptyString(turnId);
|
|
200
|
+
if (!normalizedThreadId || !normalizedTurnId) {
|
|
201
|
+
throw new Error("turn/cancel requires threadId and turnId");
|
|
202
|
+
}
|
|
203
|
+
return this.sendRequest("turn/cancel", {
|
|
204
|
+
threadId: normalizedThreadId,
|
|
205
|
+
turnId: normalizedTurnId,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async cancelTurn({ threadId, turnId }) {
|
|
210
|
+
const normalizedThreadId = normalizeNonEmptyString(threadId);
|
|
211
|
+
const normalizedTurnId = normalizeNonEmptyString(turnId);
|
|
212
|
+
if (!normalizedThreadId || !normalizedTurnId) {
|
|
213
|
+
throw new Error("turn/cancel requires threadId and turnId");
|
|
214
|
+
}
|
|
215
|
+
return this.sendRequest("turn/cancel", {
|
|
216
|
+
threadId: normalizedThreadId,
|
|
217
|
+
turnId: normalizedTurnId,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async close() {
|
|
222
|
+
this.rejectPending(new Error("Codex RPC client closed"));
|
|
223
|
+
if (this.socket) {
|
|
224
|
+
try {
|
|
225
|
+
this.socket.close();
|
|
226
|
+
} catch {
|
|
227
|
+
// best effort
|
|
228
|
+
}
|
|
229
|
+
this.socket = null;
|
|
230
|
+
}
|
|
231
|
+
if (this.child) {
|
|
232
|
+
try {
|
|
233
|
+
this.child.kill();
|
|
234
|
+
} catch {
|
|
235
|
+
// best effort
|
|
236
|
+
}
|
|
237
|
+
this.child = null;
|
|
238
|
+
}
|
|
239
|
+
this.isReady = false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async sendRequest(method, params) {
|
|
243
|
+
const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
244
|
+
const payload = JSON.stringify({ id, method, params });
|
|
245
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
246
|
+
this.pending.set(id, { resolve, reject });
|
|
247
|
+
});
|
|
248
|
+
this.sendRaw(payload);
|
|
249
|
+
return responsePromise;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async sendNotification(method, params) {
|
|
253
|
+
this.sendRaw(JSON.stringify({ method, params }));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async sendResponse(id, result) {
|
|
257
|
+
if (id == null || id === "") {
|
|
258
|
+
throw new Error("Codex RPC response requires a non-empty id");
|
|
259
|
+
}
|
|
260
|
+
this.sendRaw(JSON.stringify({ id, result }));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
sendRaw(payload) {
|
|
264
|
+
if (this.mode === "websocket") {
|
|
265
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
266
|
+
throw new Error("Codex websocket is not connected");
|
|
267
|
+
}
|
|
268
|
+
this.socket.send(payload);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!this.child || !this.child.stdin.writable) {
|
|
272
|
+
throw new Error("Codex process stdin is not writable");
|
|
273
|
+
}
|
|
274
|
+
this.child.stdin.write(`${payload}\n`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
handleTransportClosed(message) {
|
|
278
|
+
this.socket = null;
|
|
279
|
+
this.child = null;
|
|
280
|
+
this.isReady = false;
|
|
281
|
+
this.rejectPending(new Error(message));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
rejectPending(error) {
|
|
285
|
+
for (const { reject } of this.pending.values()) {
|
|
286
|
+
reject(error);
|
|
287
|
+
}
|
|
288
|
+
this.pending.clear();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
handleIncoming(rawMessage) {
|
|
292
|
+
let parsed = null;
|
|
293
|
+
try {
|
|
294
|
+
parsed = JSON.parse(rawMessage);
|
|
295
|
+
} catch {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (parsed && parsed.id != null && this.pending.has(String(parsed.id))) {
|
|
300
|
+
const { resolve, reject } = this.pending.get(String(parsed.id));
|
|
301
|
+
this.pending.delete(String(parsed.id));
|
|
302
|
+
if (parsed.error) {
|
|
303
|
+
reject(new Error(parsed.error.message || "Codex RPC request failed"));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
resolve(parsed);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const listener of this.messageListeners) {
|
|
311
|
+
listener(parsed);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resolveDefaultCodexCommand(env = process.env) {
|
|
317
|
+
return normalizeNonEmptyString(readPrefixedEnv(env, "CODEX_COMMAND")) || DEFAULT_CODEX_COMMAND;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function buildCodexCommandCandidates(configuredCommand) {
|
|
321
|
+
const explicit = normalizeNonEmptyString(configuredCommand);
|
|
322
|
+
if (explicit) {
|
|
323
|
+
if (!IS_WINDOWS) {
|
|
324
|
+
return [explicit];
|
|
325
|
+
}
|
|
326
|
+
const candidates = [explicit];
|
|
327
|
+
if (!WINDOWS_EXECUTABLE_SUFFIX_RE.test(explicit)) {
|
|
328
|
+
candidates.push(`${explicit}.cmd`, `${explicit}.exe`, `${explicit}.bat`);
|
|
329
|
+
}
|
|
330
|
+
return [...new Set(candidates)];
|
|
331
|
+
}
|
|
332
|
+
if (IS_WINDOWS) {
|
|
333
|
+
return [DEFAULT_CODEX_COMMAND, `${DEFAULT_CODEX_COMMAND}.cmd`, `${DEFAULT_CODEX_COMMAND}.exe`, `${DEFAULT_CODEX_COMMAND}.bat`];
|
|
334
|
+
}
|
|
335
|
+
return [DEFAULT_CODEX_COMMAND];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function buildSpawnSpec(command) {
|
|
339
|
+
if (IS_WINDOWS) {
|
|
340
|
+
return {
|
|
341
|
+
command: "cmd.exe",
|
|
342
|
+
args: ["/c", command, "app-server"],
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
command,
|
|
347
|
+
args: ["app-server"],
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function normalizeNonEmptyString(value) {
|
|
352
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function buildStartThreadParams(cwd) {
|
|
356
|
+
const normalizedCwd = normalizeNonEmptyString(cwd);
|
|
357
|
+
return normalizedCwd ? { cwd: normalizedCwd } : {};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function buildListThreadsParams({ cursor, limit, sortKey }) {
|
|
361
|
+
const params = { limit, sortKey };
|
|
362
|
+
const normalizedCursor = normalizeNonEmptyString(cursor);
|
|
363
|
+
if (normalizedCursor) {
|
|
364
|
+
params.cursor = normalizedCursor;
|
|
365
|
+
} else if (cursor != null) {
|
|
366
|
+
params.cursor = cursor;
|
|
367
|
+
}
|
|
368
|
+
return params;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function buildTurnInputPayload(text) {
|
|
372
|
+
const normalizedText = normalizeNonEmptyString(text);
|
|
373
|
+
return normalizedText ? [{ type: "text", text: normalizedText }] : [];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function buildTurnStartParams({ threadId, input, model, effort, accessMode, workspaceRoot, extraWritableRoots = [] }) {
|
|
377
|
+
const params = { threadId, input };
|
|
378
|
+
const normalizedWorkspaceRoot = normalizeNonEmptyString(workspaceRoot);
|
|
379
|
+
const normalizedModel = normalizeNonEmptyString(model);
|
|
380
|
+
const normalizedEffort = normalizeNonEmptyString(effort);
|
|
381
|
+
const normalizedAccessMode = normalizeAccessMode(accessMode);
|
|
382
|
+
const executionPolicies = buildExecutionPolicies(normalizedAccessMode, workspaceRoot, extraWritableRoots);
|
|
383
|
+
if (normalizedWorkspaceRoot) {
|
|
384
|
+
params.cwd = normalizedWorkspaceRoot;
|
|
385
|
+
}
|
|
386
|
+
if (normalizedModel) {
|
|
387
|
+
params.model = normalizedModel;
|
|
388
|
+
}
|
|
389
|
+
if (normalizedEffort) {
|
|
390
|
+
params.effort = normalizedEffort;
|
|
391
|
+
}
|
|
392
|
+
if (normalizedAccessMode) {
|
|
393
|
+
params.accessMode = normalizedAccessMode;
|
|
394
|
+
}
|
|
395
|
+
params.approvalPolicy = executionPolicies.approvalPolicy;
|
|
396
|
+
params.sandboxPolicy = executionPolicies.sandboxPolicy;
|
|
397
|
+
return params;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function normalizeAccessMode(value) {
|
|
401
|
+
const normalized = normalizeNonEmptyString(value).toLowerCase();
|
|
402
|
+
if (normalized === "default") {
|
|
403
|
+
return "current";
|
|
404
|
+
}
|
|
405
|
+
return normalized === "full-access" ? normalized : "";
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function buildExecutionPolicies(accessMode, workspaceRoot, extraWritableRoots = []) {
|
|
409
|
+
if (accessMode === "full-access") {
|
|
410
|
+
return {
|
|
411
|
+
approvalPolicy: "never",
|
|
412
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const normalizedWorkspaceRoot = normalizeNonEmptyString(workspaceRoot);
|
|
416
|
+
const writableRoots = normalizeWritableRoots([
|
|
417
|
+
normalizedWorkspaceRoot,
|
|
418
|
+
...extraWritableRoots,
|
|
419
|
+
]);
|
|
420
|
+
const sandboxPolicy = writableRoots.length
|
|
421
|
+
? { type: "workspaceWrite", writableRoots, networkAccess: true }
|
|
422
|
+
: { type: "workspaceWrite", networkAccess: true };
|
|
423
|
+
return {
|
|
424
|
+
approvalPolicy: "on-request",
|
|
425
|
+
sandboxPolicy,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function normalizeWritableRoots(values) {
|
|
430
|
+
const roots = [];
|
|
431
|
+
const seen = new Set();
|
|
432
|
+
for (const value of values) {
|
|
433
|
+
const normalized = normalizeNonEmptyString(value);
|
|
434
|
+
if (!normalized || seen.has(normalized)) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
seen.add(normalized);
|
|
438
|
+
roots.push(normalized);
|
|
439
|
+
}
|
|
440
|
+
return roots;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
module.exports = { CodexRpcClient };
|