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.
Files changed (80) hide show
  1. package/LICENSE +661 -0
  2. package/README.en.md +215 -0
  3. package/README.md +259 -0
  4. package/bin/codeksei.js +10 -0
  5. package/bin/cyberboss.js +11 -0
  6. package/package.json +86 -0
  7. package/scripts/install-background-tasks.ps1 +135 -0
  8. package/scripts/open_shared_wechat_thread.sh +94 -0
  9. package/scripts/open_wechat_thread.sh +117 -0
  10. package/scripts/shared-common.js +791 -0
  11. package/scripts/shared-open.js +46 -0
  12. package/scripts/shared-start.js +41 -0
  13. package/scripts/shared-status.js +74 -0
  14. package/scripts/shared-supervisor.js +141 -0
  15. package/scripts/shared-task-runner.ps1 +87 -0
  16. package/scripts/shared-watchdog.js +290 -0
  17. package/scripts/show_shared_status.sh +53 -0
  18. package/scripts/start_shared_app_server.sh +65 -0
  19. package/scripts/start_shared_wechat.sh +108 -0
  20. package/scripts/timeline-screenshot.sh +15 -0
  21. package/scripts/uninstall-background-tasks.ps1 +23 -0
  22. package/src/adapters/channel/weixin/account-store.js +135 -0
  23. package/src/adapters/channel/weixin/api-v2.js +258 -0
  24. package/src/adapters/channel/weixin/api.js +180 -0
  25. package/src/adapters/channel/weixin/context-token-store.js +84 -0
  26. package/src/adapters/channel/weixin/index.js +605 -0
  27. package/src/adapters/channel/weixin/legacy.js +567 -0
  28. package/src/adapters/channel/weixin/login-common.js +63 -0
  29. package/src/adapters/channel/weixin/login-legacy.js +124 -0
  30. package/src/adapters/channel/weixin/login-v2.js +186 -0
  31. package/src/adapters/channel/weixin/media-mime.js +22 -0
  32. package/src/adapters/channel/weixin/media-receive.js +370 -0
  33. package/src/adapters/channel/weixin/media-send.js +331 -0
  34. package/src/adapters/channel/weixin/message-utils-v2.js +282 -0
  35. package/src/adapters/channel/weixin/message-utils.js +199 -0
  36. package/src/adapters/channel/weixin/protocol.js +77 -0
  37. package/src/adapters/channel/weixin/redact.js +41 -0
  38. package/src/adapters/channel/weixin/reminder-queue-store.js +101 -0
  39. package/src/adapters/channel/weixin/sync-buffer-store.js +35 -0
  40. package/src/adapters/runtime/codex/events.js +252 -0
  41. package/src/adapters/runtime/codex/index.js +502 -0
  42. package/src/adapters/runtime/codex/message-utils.js +141 -0
  43. package/src/adapters/runtime/codex/model-catalog.js +106 -0
  44. package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -0
  45. package/src/adapters/runtime/codex/rpc-client.js +443 -0
  46. package/src/adapters/runtime/codex/session-store.js +376 -0
  47. package/src/app/channel-send-file-cli.js +57 -0
  48. package/src/app/diary-write-cli.js +620 -0
  49. package/src/app/note-auto-cli.js +201 -0
  50. package/src/app/note-sync-cli.js +130 -0
  51. package/src/app/project-radar-cli.js +165 -0
  52. package/src/app/reminder-write-cli.js +210 -0
  53. package/src/app/review-cli.js +134 -0
  54. package/src/app/system-checkin-poller.js +100 -0
  55. package/src/app/system-send-cli.js +129 -0
  56. package/src/app/timeline-event-cli.js +273 -0
  57. package/src/app/timeline-screenshot-cli.js +109 -0
  58. package/src/core/app.js +1810 -0
  59. package/src/core/branding.js +167 -0
  60. package/src/core/command-registry.js +609 -0
  61. package/src/core/config.js +84 -0
  62. package/src/core/default-targets.js +163 -0
  63. package/src/core/durable-note-schema.js +325 -0
  64. package/src/core/instructions-template.js +31 -0
  65. package/src/core/note-sync.js +433 -0
  66. package/src/core/project-radar.js +402 -0
  67. package/src/core/review-semantic.js +524 -0
  68. package/src/core/review.js +1081 -0
  69. package/src/core/shared-bridge-heartbeat.js +140 -0
  70. package/src/core/stream-delivery.js +990 -0
  71. package/src/core/system-message-dispatcher.js +68 -0
  72. package/src/core/system-message-queue-store.js +128 -0
  73. package/src/core/thread-state-store.js +135 -0
  74. package/src/core/timeline-screenshot-queue-store.js +134 -0
  75. package/src/core/workspace-alias.js +163 -0
  76. package/src/core/workspace-bootstrap.js +338 -0
  77. package/src/index.js +270 -0
  78. package/src/integrations/timeline/index.js +191 -0
  79. package/templates/weixin-instructions.md +53 -0
  80. 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 };