deepcodex 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 (43) hide show
  1. package/.codex-plugin/plugin.json +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +178 -0
  4. package/bin/opencodex.js +49 -0
  5. package/config/desktop.json +9 -0
  6. package/config/pilot.json +16 -0
  7. package/config/worker.json +78 -0
  8. package/node_modules/smol-toml/LICENSE +24 -0
  9. package/node_modules/smol-toml/README.md +418 -0
  10. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  11. package/node_modules/smol-toml/dist/date.js +127 -0
  12. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  13. package/node_modules/smol-toml/dist/error.js +63 -0
  14. package/node_modules/smol-toml/dist/extract.js +69 -0
  15. package/node_modules/smol-toml/dist/index.cjs +734 -0
  16. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  17. package/node_modules/smol-toml/dist/index.js +33 -0
  18. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  19. package/node_modules/smol-toml/dist/parse.js +149 -0
  20. package/node_modules/smol-toml/dist/primitive.js +238 -0
  21. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  22. package/node_modules/smol-toml/dist/stringify.js +181 -0
  23. package/node_modules/smol-toml/dist/struct.js +179 -0
  24. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  25. package/node_modules/smol-toml/dist/util.js +89 -0
  26. package/node_modules/smol-toml/package.json +68 -0
  27. package/package.json +47 -0
  28. package/prompts/worker.md +20 -0
  29. package/scripts/credentials.js +102 -0
  30. package/scripts/desktop.js +199 -0
  31. package/scripts/pilot-router.js +241 -0
  32. package/scripts/pilot.js +242 -0
  33. package/scripts/toml.js +6 -0
  34. package/scripts/worker.js +637 -0
  35. package/skills/delegate-flash/SKILL.md +63 -0
  36. package/vendor/codex-router/LICENSE +21 -0
  37. package/vendor/codex-router/deepseek-responses.js +55 -0
  38. package/vendor/codex-router/json-number-rewrite.js +58 -0
  39. package/vendor/codex-router/namespace-relay.js +4294 -0
  40. package/vendor/codex-router/sse-prefix.js +115 -0
  41. package/vendor/codex-router/subagent-completion.js +261 -0
  42. package/vendor/codex-router/tool-arguments.js +111 -0
  43. package/vendor/codex-router/tool-schema-root.js +1008 -0
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env node
2
+ // Run a bounded Codex worker against DeepSeek without changing user config.
3
+
4
+ import { spawn, spawnSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { parseToml } from './toml.js';
10
+
11
+ export const ROOT = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
12
+
13
+ const SQLITE_BUSY = 5;
14
+ const ALLOWED_ENVIRONMENT = new Set(['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TMPDIR', 'LANG', 'CODEX_HOME']);
15
+ const REQUIRED_CLI_FLAGS = ['--ignore-user-config', '--ephemeral', '--json', '--strict-config'];
16
+ const USAGE = 'Usage: worker.js <doctor|run> [options]';
17
+
18
+ export const HELP = `${USAGE}
19
+
20
+ Run a bounded Codex worker against DeepSeek without changing user config.
21
+
22
+ Commands:
23
+ doctor Check local prerequisites without inference
24
+ run Execute one ticket; consumes DeepSeek API usage
25
+
26
+ Run options:
27
+ --cwd PATH Workspace directory for the ticket (required)
28
+ --task-file PATH File holding the ticket text (required)
29
+ --write Allow workspace writes for an authorized editing task
30
+
31
+ Options:
32
+ -h, --help Show this help message
33
+ `;
34
+
35
+ export function loadConfig() {
36
+ return JSON.parse(fs.readFileSync(path.join(ROOT, 'config/worker.json'), 'utf8'));
37
+ }
38
+
39
+ export function configArgs(values, prefix = '') {
40
+ const args = [];
41
+ for (const [key, value] of Object.entries(values)) {
42
+ const name = prefix ? `${prefix}.${key}` : key;
43
+ if (isTable(value)) args.push(...configArgs(value, name));
44
+ else args.push('-c', `${name}=${JSON.stringify(value)}`);
45
+ }
46
+ return args;
47
+ }
48
+
49
+ export function workerEnvironment(source) {
50
+ const env = {};
51
+ for (const [key, value] of Object.entries(source)) {
52
+ if (value === undefined) continue;
53
+ if (ALLOWED_ENVIRONMENT.has(key) || key.startsWith('LC_')) env[key] = value;
54
+ }
55
+ if (source.DEEPSEEK_API_KEY) env.DEEPSEEK_API_KEY = source.DEEPSEEK_API_KEY;
56
+ return env;
57
+ }
58
+
59
+ export function readEnvKey(file, name) {
60
+ let value = null;
61
+ for (const line of fs.readFileSync(file, 'utf8').split(/\r\n|\r|\n/)) {
62
+ let text = line.trim();
63
+ if (text.startsWith('export ')) text = text.slice(7).replace(/^\s+/, '');
64
+ const separator = text.indexOf('=');
65
+ if (separator === -1 || text.slice(0, separator).trim() !== name) continue;
66
+ if (value !== null) throw new Error(`Duplicate ${name} in credentials file`);
67
+ let parts;
68
+ try {
69
+ parts = shlexSplit(text.slice(separator + 1));
70
+ } catch {
71
+ throw new Error(`Invalid ${name} quoting in credentials file`);
72
+ }
73
+ if (parts.length !== 1 || !parts[0]) throw new Error(`Invalid or empty ${name} in credentials file`);
74
+ value = parts[0];
75
+ }
76
+ if (value === null) throw new Error(`Missing ${name} in credentials file`);
77
+ return value;
78
+ }
79
+
80
+ export function loadCredentials(env, config) {
81
+ const provider = config.codex.model_provider;
82
+ const name = config.codex.model_providers[provider].env_key;
83
+ if (!env[name]) {
84
+ const file = expandUser(config.credentials.env_file);
85
+ try {
86
+ env[name] = readEnvKey(file, name);
87
+ } catch (error) {
88
+ if (error.code !== 'ENOENT') throw error;
89
+ }
90
+ }
91
+ }
92
+
93
+ export function redact(value, secret) {
94
+ if (typeof value === 'string') return secret ? value.split(secret).join('[REDACTED]') : value;
95
+ if (Array.isArray(value)) return value.map(item => redact(item, secret));
96
+ if (isTable(value)) {
97
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redact(item, secret)]));
98
+ }
99
+ return value;
100
+ }
101
+
102
+ export function doctor(config, env) {
103
+ const binary = which('codex', env.PATH);
104
+ const report = { status: 'ready', codex: binary ?? null, api_key_present: Boolean(env.DEEPSEEK_API_KEY) };
105
+ if (binary) {
106
+ const timeout = config.limits.version_timeout_seconds * 1000;
107
+ const version = probe([binary, '--version'], env, timeout);
108
+ const help = probe([binary, 'exec', '--help'], env, timeout);
109
+ report.version = version.stdout.trim();
110
+ report.compatible_cli = version.status === 0 && help.status === 0
111
+ && REQUIRED_CLI_FLAGS.every(flag => help.stdout.includes(flag));
112
+ }
113
+ if (!binary || !report.compatible_cli || !report.api_key_present) report.status = 'not_ready';
114
+ return report;
115
+ }
116
+
117
+ export function checkProjectConfig(cwd) {
118
+ const userConfig = path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'), 'config.toml');
119
+ for (const directory of ancestors(cwd)) {
120
+ const candidate = path.join(directory, '.codex', 'config.toml');
121
+ if (candidate === userConfig || !isFile(candidate)) continue;
122
+ const data = parseToml(fs.readFileSync(candidate, 'utf8'));
123
+ if (pythonTruthy(data?.mcp_servers)) {
124
+ throw new Error(`Project MCP servers are not supported by this worker: ${candidate}`);
125
+ }
126
+ }
127
+ }
128
+
129
+ // Exclusive lock released by the kernel when the process dies, so a crashed worker cannot block the
130
+ // next one. The lock file lives in the per-user temporary directory, is created without following
131
+ // symlinks, is owned by this user and stays 0600. node:sqlite is loaded here so help and doctor stay
132
+ // free of its experimental warning.
133
+ export async function workerLock() {
134
+ const file = path.join(os.tmpdir(), `opencodex-worker-${process.getuid()}.lock`);
135
+ const fd = fs.openSync(file, fs.constants.O_CREAT | fs.constants.O_RDWR | fs.constants.O_NOFOLLOW, 0o600);
136
+ try {
137
+ const stat = fs.fstatSync(fd);
138
+ if (stat.uid !== process.getuid()) throw new Error('Worker lock belongs to another user');
139
+ if (!stat.isFile()) throw new Error('Worker lock is not a regular file');
140
+ if ((stat.mode & 0o777) !== 0o600) fs.fchmodSync(fd, 0o600);
141
+ } finally {
142
+ fs.closeSync(fd);
143
+ }
144
+ const { DatabaseSync } = await import('node:sqlite');
145
+ const database = new DatabaseSync(file, { timeout: 0 });
146
+ try {
147
+ database.exec('BEGIN EXCLUSIVE');
148
+ } catch (error) {
149
+ database.close();
150
+ if (error.errcode === SQLITE_BUSY) throw new Error('Another OpenCodex worker is running; wait for it or cancel that run');
151
+ throw error;
152
+ }
153
+ let closed = false;
154
+ return {
155
+ close() {
156
+ if (closed) return;
157
+ closed = true;
158
+ try {
159
+ if (database.isTransaction) database.exec('ROLLBACK');
160
+ } catch {
161
+ // Closing the handle releases the lock even when the transaction cannot be rolled back.
162
+ }
163
+ database.close();
164
+ },
165
+ [Symbol.dispose]() {
166
+ this.close();
167
+ },
168
+ };
169
+ }
170
+
171
+ export function buildCommand(binary, config, cwd, write, runDir) {
172
+ const metadata = { ...config.model_metadata };
173
+ metadata.slug = config.codex.model;
174
+ metadata.base_instructions = fs.readFileSync(path.join(ROOT, 'prompts/worker.md'), 'utf8');
175
+ const catalog = path.join(runDir, 'models.json');
176
+ fs.writeFileSync(catalog, JSON.stringify({ models: [metadata] }));
177
+ const finalPath = path.join(runDir, 'final.txt');
178
+ const args = [binary, 'exec', '--ignore-user-config', '--ephemeral', '--json', '--strict-config',
179
+ '--skip-git-repo-check', '--color', 'never', '--cd', cwd,
180
+ '--sandbox', write ? 'workspace-write' : 'read-only',
181
+ '--output-last-message', finalPath];
182
+ args.push(...configArgs(config.codex));
183
+ args.push('-c', `model_catalog_json=${JSON.stringify(catalog)}`, '-');
184
+ return { args, finalPath };
185
+ }
186
+
187
+ export function parseResult(stdout, stderr, finalText, returncode, stopReason, config) {
188
+ let completed = false;
189
+ const errors = [];
190
+ let usage = null;
191
+ let threadId = null;
192
+ let malformed = false;
193
+ for (const line of stdout.split(/\r\n|\r|\n/)) {
194
+ if (!line.trim()) continue;
195
+ let event;
196
+ try {
197
+ event = JSON.parse(line);
198
+ if (!isTable(event)) throw new Error('event must be an object');
199
+ } catch {
200
+ malformed = true;
201
+ continue;
202
+ }
203
+ if (event.type === 'thread.started') {
204
+ threadId = event.thread_id ?? null;
205
+ } else if (event.type === 'turn.completed') {
206
+ completed = true;
207
+ usage = event.usage ?? null;
208
+ } else if (event.type === 'turn.failed' || event.type === 'error') {
209
+ errors.push(pick(event, 'error', pick(event, 'message', 'Worker error')));
210
+ }
211
+ }
212
+ const status = stopReason
213
+ || (returncode === 0 && completed && finalText.trim() && errors.length === 0 && !malformed ? 'completed' : 'failed');
214
+ const result = {
215
+ status,
216
+ exit_code: returncode,
217
+ thread_id: threadId,
218
+ configured_model: config.codex.model,
219
+ configured_provider: config.codex.model_provider,
220
+ usage,
221
+ };
222
+ if (finalText) {
223
+ const cap = config.limits.max_result_chars;
224
+ result.result = head(finalText, cap);
225
+ result.result_truncated = finalText.length > cap;
226
+ }
227
+ if (malformed) errors.push('Invalid JSONL from Codex');
228
+ if (status !== 'completed') {
229
+ if (errors.length === 0) errors.push('Worker did not produce a complete turn and a non-empty final response');
230
+ result.errors = JSON.stringify(errors).slice(0, config.limits.max_error_chars);
231
+ result.diagnostic = tail(stderr, config.limits.max_error_chars);
232
+ result.partial_changes_possible = true;
233
+ }
234
+ return result;
235
+ }
236
+
237
+ // Codex runs in its own process group, so signalling the group also reaches descendants it left behind.
238
+ export async function killGroup(child) {
239
+ if (!child || child.pid === undefined) return;
240
+ try {
241
+ process.kill(-child.pid, 'SIGKILL');
242
+ } catch (error) {
243
+ if (error.code !== 'ESRCH') throw error;
244
+ }
245
+ if (child.exitCode === null && child.signalCode === null) {
246
+ await new Promise(resolve => child.once('close', resolve));
247
+ }
248
+ }
249
+
250
+ export async function runWorker(binary, config, cwd, task, write, env) {
251
+ checkProjectConfig(cwd);
252
+ const started = monotonic();
253
+ const { limits } = config;
254
+ const lock = await workerLock();
255
+ let runDir = null;
256
+ try {
257
+ runDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencodex-run-'));
258
+ const { args, finalPath } = buildCommand(binary, config, cwd, write, runDir);
259
+ const taskPath = path.join(runDir, 'task.txt');
260
+ const outPath = path.join(runDir, 'events.jsonl');
261
+ const errPath = path.join(runDir, 'stderr.txt');
262
+ const artifacts = [outPath, errPath, finalPath];
263
+ fs.writeFileSync(taskPath, task);
264
+ let stopReason = null;
265
+ let child = null;
266
+ const stdin = fs.openSync(taskPath, 'r');
267
+ const stdout = fs.openSync(outPath, 'w');
268
+ const stderr = fs.openSync(errPath, 'w');
269
+ try {
270
+ // spawn() supplies argv[0] itself, so args[0] (the binary) must not be repeated in the list.
271
+ child = spawn(binary, args.slice(1), { cwd, env, detached: true, stdio: [stdin, stdout, stderr] });
272
+ await waitForSpawn(child);
273
+ process.stderr.write(`${JSON.stringify({ event: 'worker.started', pid: child.pid, cwd, model: config.codex.model })}\n`);
274
+ while (child.exitCode === null && child.signalCode === null) {
275
+ if (cancellationRequested) {
276
+ stopReason = 'cancelled';
277
+ break;
278
+ }
279
+ if (monotonic() - started >= limits.timeout_seconds) {
280
+ stopReason = 'timeout';
281
+ break;
282
+ }
283
+ if (artifactBytes(artifacts) > limits.max_output_bytes) {
284
+ stopReason = 'output_limit';
285
+ break;
286
+ }
287
+ await pause(limits.poll_interval_seconds * 1000);
288
+ }
289
+ } finally {
290
+ // Also terminate descendants left behind after Codex exits.
291
+ await killGroup(child);
292
+ fs.closeSync(stdin);
293
+ fs.closeSync(stdout);
294
+ fs.closeSync(stderr);
295
+ }
296
+ if (artifactBytes(artifacts) > limits.max_output_bytes) stopReason ??= 'output_limit';
297
+ const returncode = child ? exitCodeOf(child) : null;
298
+ const result = stopReason === 'output_limit'
299
+ ? parseResult('', 'Worker output exceeded the configured limit', '', returncode, stopReason, config)
300
+ : parseResult(readText(outPath), readText(errPath), readTextIfExists(finalPath), returncode, stopReason, config);
301
+ result.duration_seconds = Math.round((monotonic() - started) * 100) / 100;
302
+ result.worker_pid = child?.pid ?? null;
303
+ result.cwd = cwd;
304
+ result.sandbox = write ? 'workspace-write' : 'read-only';
305
+ return result;
306
+ } finally {
307
+ if (runDir) fs.rmSync(runDir, { recursive: true, force: true });
308
+ lock.close();
309
+ }
310
+ }
311
+
312
+ export async function main(argv = process.argv.slice(2)) {
313
+ const parsed = parseArgs(argv);
314
+ if (parsed.help) {
315
+ process.stdout.write(parsed.text);
316
+ return 0;
317
+ }
318
+ if (parsed.error) {
319
+ process.stderr.write(`${USAGE}\n${parsed.error}\n`);
320
+ return 2;
321
+ }
322
+ const env = workerEnvironment(process.env);
323
+ const config = loadConfig();
324
+ resetCancellation();
325
+ process.on('SIGTERM', requestCancellation);
326
+ process.on('SIGINT', requestCancellation);
327
+ let report;
328
+ try {
329
+ loadCredentials(env, config);
330
+ report = doctor(config, env);
331
+ if (parsed.command === 'run' && report.status === 'ready') {
332
+ const cwd = fs.realpathSync(expandUser(parsed.cwd));
333
+ if (!isDirectory(cwd)) throw new Error('cwd must be a directory');
334
+ const task = readTicket(parsed.taskFile, config.limits.max_task_bytes);
335
+ if (!task.trim()) throw new Error('Ticket must not be empty');
336
+ report = await runWorker(report.codex, config, cwd, task, parsed.write, env);
337
+ }
338
+ if (parsed.command === 'run' && report.status === 'not_ready') {
339
+ report.error = 'Run doctor and resolve missing prerequisites before delegation';
340
+ }
341
+ } catch (error) {
342
+ report = { status: 'failed', error: errorMessage(error) };
343
+ } finally {
344
+ process.off('SIGTERM', requestCancellation);
345
+ process.off('SIGINT', requestCancellation);
346
+ }
347
+ process.stdout.write(`${JSON.stringify(redact(report, env.DEEPSEEK_API_KEY))}\n`);
348
+ return report.status === 'ready' || report.status === 'completed' ? 0 : 1;
349
+ }
350
+
351
+ let cancellationRequested = false;
352
+ let wakeCancellable = null;
353
+
354
+ // main() registers these while it runs, so SIGINT/SIGTERM also cancel a worker started through the
355
+ // CLI, which imports main() instead of executing this file as the entrypoint.
356
+ export function requestCancellation() {
357
+ cancellationRequested = true;
358
+ const wake = wakeCancellable;
359
+ wakeCancellable = null;
360
+ if (wake) wake();
361
+ }
362
+
363
+ function resetCancellation() {
364
+ cancellationRequested = false;
365
+ }
366
+
367
+ function parseArgs(argv) {
368
+ const command = argv[0];
369
+ const rest = argv.slice(1);
370
+ if (command === undefined || command === '--help' || command === '-h') return { help: true, text: HELP };
371
+ if (command !== 'doctor' && command !== 'run') return { error: `Unknown command: ${command}` };
372
+ const options = { command, write: false };
373
+ for (let index = 0; index < rest.length; index += 1) {
374
+ const arg = rest[index];
375
+ if (arg === '--help' || arg === '-h') return { help: true, text: HELP };
376
+ if (command !== 'run') return { error: `Unexpected argument for doctor: ${arg}` };
377
+ if (arg === '--write') {
378
+ options.write = true;
379
+ continue;
380
+ }
381
+ const separator = arg.indexOf('=');
382
+ const flag = separator === -1 ? arg : arg.slice(0, separator);
383
+ if (flag !== '--cwd' && flag !== '--task-file') return { error: `Unknown option: ${arg}` };
384
+ let value;
385
+ if (separator === -1) {
386
+ index += 1;
387
+ value = rest[index];
388
+ } else {
389
+ value = arg.slice(separator + 1);
390
+ }
391
+ if (value === undefined) return { error: `Option ${flag} requires a value` };
392
+ if (flag === '--cwd') options.cwd = value;
393
+ else options.taskFile = value;
394
+ }
395
+ if (command === 'run' && options.cwd === undefined) return { error: 'Option --cwd is required' };
396
+ if (command === 'run' && options.taskFile === undefined) return { error: 'Option --task-file is required' };
397
+ return options;
398
+ }
399
+
400
+ function readTicket(file, maxTaskBytes) {
401
+ const fd = fs.openSync(file, 'r');
402
+ try {
403
+ const buffer = Buffer.alloc(maxTaskBytes + 1);
404
+ const read = fs.readSync(fd, buffer, 0, buffer.length, 0);
405
+ if (read > maxTaskBytes) throw new Error('Ticket exceeds max_task_bytes');
406
+ // Strict UTF-8 like Python's bytes.decode, keeping a BOM instead of stripping it.
407
+ return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(buffer.subarray(0, read));
408
+ } finally {
409
+ fs.closeSync(fd);
410
+ }
411
+ }
412
+
413
+ function probe(args, env, timeout) {
414
+ const result = spawnSync(args[0], args.slice(1), { env, timeout, encoding: 'utf8' });
415
+ if (result.error) throw result.error;
416
+ return { status: result.status, stdout: result.stdout ?? '' };
417
+ }
418
+
419
+ function waitForSpawn(child) {
420
+ return new Promise((resolve, reject) => {
421
+ child.once('spawn', resolve);
422
+ child.once('error', reject);
423
+ });
424
+ }
425
+
426
+ function pause(milliseconds) {
427
+ return new Promise(resolve => {
428
+ const timer = setTimeout(() => {
429
+ wakeCancellable = null;
430
+ resolve();
431
+ }, Math.max(milliseconds, 0));
432
+ wakeCancellable = () => {
433
+ clearTimeout(timer);
434
+ resolve();
435
+ };
436
+ });
437
+ }
438
+
439
+ function exitCodeOf(child) {
440
+ if (typeof child.exitCode === 'number') return child.exitCode;
441
+ const signal = child.signalCode ? os.constants.signals[child.signalCode] : undefined;
442
+ return signal === undefined ? null : -signal;
443
+ }
444
+
445
+ function artifactBytes(files) {
446
+ let total = 0;
447
+ for (const file of files) {
448
+ try {
449
+ total += fs.statSync(file).size;
450
+ } catch (error) {
451
+ if (error.code !== 'ENOENT') throw error;
452
+ }
453
+ }
454
+ return total;
455
+ }
456
+
457
+ function which(command, searchPath) {
458
+ const directories = String(searchPath ?? process.env.PATH ?? '').split(path.delimiter);
459
+ for (const directory of directories) {
460
+ const candidate = path.join(directory || '.', command);
461
+ try {
462
+ const stat = fs.statSync(candidate);
463
+ if (stat.isFile()) {
464
+ fs.accessSync(candidate, fs.constants.X_OK);
465
+ return candidate;
466
+ }
467
+ } catch {
468
+ // Not a usable candidate; keep searching.
469
+ }
470
+ }
471
+ return undefined;
472
+ }
473
+
474
+ function ancestors(cwd) {
475
+ const directories = [];
476
+ let directory = cwd;
477
+ while (directory !== path.dirname(directory)) {
478
+ directories.push(directory);
479
+ directory = path.dirname(directory);
480
+ }
481
+ directories.push(directory);
482
+ return directories;
483
+ }
484
+
485
+ function expandUser(file) {
486
+ if (file === '~') return process.env.HOME ?? os.homedir();
487
+ if (file.startsWith('~/')) return path.join(process.env.HOME ?? os.homedir(), file.slice(2));
488
+ return file;
489
+ }
490
+
491
+ function isTable(value) {
492
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && !Buffer.isBuffer(value);
493
+ }
494
+
495
+ function isFile(target) {
496
+ try {
497
+ return fs.statSync(target).isFile();
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
502
+
503
+ function isDirectory(target) {
504
+ try {
505
+ return fs.statSync(target).isDirectory();
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+
511
+ function readText(file) {
512
+ return fs.readFileSync(file, 'utf8');
513
+ }
514
+
515
+ function readTextIfExists(file) {
516
+ return fs.existsSync(file) ? readText(file) : '';
517
+ }
518
+
519
+ function monotonic() {
520
+ return Number(process.hrtime.bigint()) / 1e9;
521
+ }
522
+
523
+ function pick(object, key, fallback) {
524
+ return Object.hasOwn(object, key) ? object[key] : fallback;
525
+ }
526
+
527
+ function head(text, cap) {
528
+ return cap > 0 ? text.slice(0, cap) : '';
529
+ }
530
+
531
+ function tail(text, cap) {
532
+ return cap > 0 ? text.slice(-cap) : '';
533
+ }
534
+
535
+ function pythonTruthy(value) {
536
+ if (Array.isArray(value)) return value.length > 0;
537
+ if (isTable(value)) return Object.keys(value).length > 0;
538
+ return Boolean(value);
539
+ }
540
+
541
+ function errorMessage(error) {
542
+ return error && typeof error.message === 'string' ? error.message : String(error);
543
+ }
544
+
545
+ // POSIX shlex.split(text, comments=True): quotes and escapes only, never shell expansion.
546
+ function shlexSplit(text) {
547
+ const tokens = [];
548
+ let current = '';
549
+ let word = false;
550
+ let index = 0;
551
+ while (index < text.length) {
552
+ const char = text[index];
553
+ if (char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === '\f' || char === '\v') {
554
+ if (word) {
555
+ tokens.push(current);
556
+ current = '';
557
+ word = false;
558
+ }
559
+ index += 1;
560
+ continue;
561
+ }
562
+ // shlex also comments out '#' inside an unquoted word, keeping the word read so far.
563
+ if (char === '#') {
564
+ if (word) {
565
+ tokens.push(current);
566
+ current = '';
567
+ word = false;
568
+ }
569
+ break;
570
+ }
571
+ if (char === '\\') {
572
+ if (index + 1 >= text.length) throw new Error('No escaped character');
573
+ current += text[index + 1];
574
+ word = true;
575
+ index += 2;
576
+ continue;
577
+ }
578
+ if (char === "'") {
579
+ const end = text.indexOf("'", index + 1);
580
+ if (end === -1) throw new Error('No closing quotation');
581
+ current += text.slice(index + 1, end);
582
+ word = true;
583
+ index = end + 1;
584
+ continue;
585
+ }
586
+ if (char === '"') {
587
+ let cursor = index + 1;
588
+ let closed = false;
589
+ while (cursor < text.length) {
590
+ const quoted = text[cursor];
591
+ if (quoted === '"') {
592
+ closed = true;
593
+ cursor += 1;
594
+ break;
595
+ }
596
+ if (quoted === '\\') {
597
+ const next = text[cursor + 1];
598
+ if (next === undefined) throw new Error('No escaped character');
599
+ // shlex escapes only the quote and the backslash here; every other backslash stays literal.
600
+ current += next === '"' || next === '\\' ? next : `\\${next}`;
601
+ cursor += 2;
602
+ continue;
603
+ }
604
+ current += quoted;
605
+ cursor += 1;
606
+ }
607
+ if (!closed) throw new Error('No closing quotation');
608
+ word = true;
609
+ index = cursor;
610
+ continue;
611
+ }
612
+ current += char;
613
+ word = true;
614
+ index += 1;
615
+ }
616
+ if (word) tokens.push(current);
617
+ return tokens;
618
+ }
619
+
620
+ function isEntrypoint() {
621
+ const invoked = process.argv[1];
622
+ if (!invoked) return false;
623
+ try {
624
+ return pathToFileURL(fs.realpathSync(invoked)).href === import.meta.url;
625
+ } catch {
626
+ return false;
627
+ }
628
+ }
629
+
630
+ if (isEntrypoint()) {
631
+ main().then(code => {
632
+ process.exitCode = code;
633
+ }, error => {
634
+ process.stderr.write(`${error?.stack ?? error}\n`);
635
+ process.exitCode = 1;
636
+ });
637
+ }
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: delegate-flash
3
+ description: Delegate bounded coding tasks to native DeepSeek Flash subagents in Codex through OpenCodex. Use when OpenCodex or Flash delegation is requested, or when diagnosing the Desktop integration.
4
+ ---
5
+
6
+ # OpenCodex
7
+
8
+ Keep the coordinator on the user's selected native model. DeepSeek Flash runs as
9
+ an actual Codex subagent through the local OpenCodex router. Use the native
10
+ collaboration tools for spawning, follow-ups, waiting, and interruption; the
11
+ normal delegation path does not launch a separate `codex exec` worker.
12
+
13
+ ## Delegate
14
+
15
+ Choose an independently verifiable task and give the child the objective,
16
+ necessary context, allowed files, exclusions, acceptance checks, and the user's
17
+ communication language. Preserve the language of existing code. Review the
18
+ actual changes and verification before declaring the user's task complete.
19
+
20
+ When the current tool schema offers `deepseek-flash`, call `spawn_agent` with
21
+ `model="deepseek-flash"`, `reasoning_effort="high"`, and `fork_turns="none"`.
22
+ A fresh child receives the bounded ticket without copying the full conversation.
23
+ Keep its returned identity and use `followup_task` for related work in the same
24
+ agent. Use `wait_agent` and `interrupt_agent` normally. Never invent unavailable
25
+ model overrides or disguise DeepSeek under an OpenAI model name.
26
+
27
+ If the schema does not offer Flash, check installation status and ask the user to
28
+ fully quit/reopen Desktop and start a new task after a configuration update.
29
+ Do not silently switch to a separate process or another provider. Existing tasks
30
+ can retain the previous provider and model catalog.
31
+
32
+ Subagents inherit Codex's permissions. A ticket's allowed files are instructions,
33
+ not a filesystem sandbox. Use isolation when the task needs it. Do not claim the
34
+ worker cannot read secrets accessible to the same user.
35
+
36
+ ## Local integration
37
+
38
+ Resolve plugin paths relative to this installed skill (the root is two levels
39
+ above). The source launcher is `scripts/desktop.js`; the installed LaunchAgent
40
+ runs the stable copy under `~/.local/share/opencodex/runtime`. Operational policy
41
+ is in `config/desktop.json`, shared transport defaults in `config/pilot.json`,
42
+ and the DeepSeek model and credential path in `config/worker.json`.
43
+
44
+ Run `node <plugin>/scripts/desktop.js status` to check the local service without
45
+ inference. The service is `com.opencodex.router`, bound only to loopback. Private
46
+ state and bounded metadata receipts live in `~/.config/opencodex/desktop`.
47
+ Receipts contain routing and tool names, not prompts, tool arguments or tokens.
48
+ Do not print `state.json`, provider headers, credential files or full user config.
49
+
50
+ DeepSeek credentials stay in `~/.config/opencodex/.env` outside the plugin and
51
+ are read by the service. Never ask for a key in chat or package `.env` files.
52
+ The router forwards native Codex authentication only to OpenAI and the DeepSeek
53
+ key only to DeepSeek. Native encrypted task handoffs use an additional OpenAI
54
+ relay call; delegation consumes both Codex quota and DeepSeek API usage.
55
+
56
+ `node <plugin>/scripts/desktop.js install` writes the service and user-level
57
+ provider/catalog settings. Run it only when the user authorizes activation or
58
+ repair. It preserves unrelated settings and keeps a private pre-install config
59
+ backup. Do not restore that full backup over later user changes without review.
60
+
61
+ The opt-in `scripts/pilot.js` runs an isolated live test and spends API usage.
62
+ A healthy service or visible model is not proof that a live delegated task ran.
63
+ Report separately configuration, CLI/API execution, and Desktop UI evidence.