@supen-ai/cli 1.3.4 → 1.4.1
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/README.md +1 -1
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +0 -2
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -1
- package/daemon/dist/agent-sdk/drivers/registry.d.ts +2 -8
- package/daemon/dist/agent-sdk/drivers/registry.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/drivers/registry.js +7 -138
- package/daemon/dist/agent-sdk/drivers/registry.js.map +1 -1
- package/daemon/dist/agent-sdk/index.d.ts +0 -11
- package/daemon/dist/agent-sdk/index.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/index.js +0 -11
- package/daemon/dist/agent-sdk/index.js.map +1 -1
- package/daemon/dist/agent-sdk/session-manager.d.ts +1 -1
- package/daemon/dist/agent-sdk/session-manager.d.ts.map +1 -1
- package/daemon/dist/agent-sdk/session-manager.js +1 -2
- package/daemon/dist/agent-sdk/session-manager.js.map +1 -1
- package/daemon/dist/channels/acp.d.ts.map +1 -1
- package/daemon/dist/channels/acp.js +2 -3
- package/daemon/dist/channels/acp.js.map +1 -1
- package/daemon/dist/channels/http-routes.js +6 -6
- package/daemon/dist/channels/http-routes.js.map +1 -1
- package/daemon/dist/core/config.d.ts +1 -1
- package/daemon/dist/core/config.d.ts.map +1 -1
- package/daemon/dist/core/config.js +1 -1
- package/daemon/dist/core/config.js.map +1 -1
- package/daemon/dist/core/cortex.d.ts.map +1 -1
- package/daemon/dist/core/cortex.js +90 -205
- package/daemon/dist/core/cortex.js.map +1 -1
- package/daemon/dist/core/env.js +1 -1
- package/daemon/dist/core/gateway.d.ts.map +1 -1
- package/daemon/dist/core/gateway.js +1 -11
- package/daemon/dist/core/gateway.js.map +1 -1
- package/daemon/dist/core/loop-guard.d.ts +1 -1
- package/daemon/dist/core/loop-guard.js +1 -1
- package/daemon/dist/core/protocol-adapter.d.ts +1 -3
- package/daemon/dist/core/protocol-adapter.d.ts.map +1 -1
- package/daemon/dist/core/protocol-adapter.js +1 -3
- package/daemon/dist/core/protocol-adapter.js.map +1 -1
- package/daemon/dist/core/thread-context.d.ts +76 -0
- package/daemon/dist/core/thread-context.d.ts.map +1 -0
- package/daemon/dist/core/thread-context.js +308 -0
- package/daemon/dist/core/thread-context.js.map +1 -0
- package/daemon/dist/http/routes/rpc.d.ts.map +1 -1
- package/daemon/dist/http/routes/rpc.js +18 -1
- package/daemon/dist/http/routes/rpc.js.map +1 -1
- package/daemon/dist/http/routes/sessions.js +1 -1
- package/daemon/dist/http/routes/system.d.ts +3 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -1
- package/daemon/dist/http/routes/system.js +437 -141
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/dist/index.d.ts.map +1 -1
- package/daemon/dist/index.js +2 -1
- package/daemon/dist/index.js.map +1 -1
- package/daemon/dist/mcp/index.d.ts +2 -6
- package/daemon/dist/mcp/index.d.ts.map +1 -1
- package/daemon/dist/mcp/index.js +2 -6
- package/daemon/dist/mcp/index.js.map +1 -1
- package/daemon/dist/mcp/tools.d.ts +5 -7
- package/daemon/dist/mcp/tools.d.ts.map +1 -1
- package/daemon/dist/mcp/tools.js +4 -8
- package/daemon/dist/mcp/tools.js.map +1 -1
- package/daemon/dist/tools/built-ins.d.ts +9 -84
- package/daemon/dist/tools/built-ins.d.ts.map +1 -1
- package/daemon/dist/tools/built-ins.js +3 -3
- package/daemon/dist/tools/built-ins.js.map +1 -1
- package/daemon/dist/tools/local-tool.d.ts +7 -0
- package/daemon/dist/tools/local-tool.d.ts.map +1 -0
- package/daemon/dist/tools/local-tool.js +4 -0
- package/daemon/dist/tools/local-tool.js.map +1 -0
- package/daemon/dist/tools/skill-tools.d.ts +5 -8
- package/daemon/dist/tools/skill-tools.d.ts.map +1 -1
- package/daemon/dist/tools/skill-tools.js +6 -10
- package/daemon/dist/tools/skill-tools.js.map +1 -1
- package/daemon/package.json +6 -9
- package/dist/backend.js +5 -5
- package/dist/backend.js.map +1 -1
- package/dist/bootstrap.js +2 -6
- package/dist/bootstrap.js.map +1 -1
- package/dist/computer.js +1 -1
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/repl.js +6 -66
- package/dist/repl.js.map +1 -1
- package/dist/service.js +2 -3
- package/dist/service.js.map +1 -1
- package/package.json +3 -6
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts +0 -21
- package/daemon/dist/agent-sdk/drivers/acpx-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js +0 -488
- package/daemon/dist/agent-sdk/drivers/acpx-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts +0 -5
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js +0 -7
- package/daemon/dist/agent-sdk/drivers/claude-acpx-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts +0 -20
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js +0 -264
- package/daemon/dist/agent-sdk/drivers/claude-cli-direct-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts +0 -29
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js +0 -24
- package/daemon/dist/agent-sdk/drivers/claude-code-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts +0 -25
- package/daemon/dist/agent-sdk/drivers/claude-code-events.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js +0 -58
- package/daemon/dist/agent-sdk/drivers/claude-code-events.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts +0 -41
- package/daemon/dist/agent-sdk/drivers/claude-code-items.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js +0 -77
- package/daemon/dist/agent-sdk/drivers/claude-code-items.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts +0 -5
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js +0 -7
- package/daemon/dist/agent-sdk/drivers/codex-acpx-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts +0 -28
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js +0 -219
- package/daemon/dist/agent-sdk/drivers/codex-exec-driver.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts +0 -9
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js +0 -82
- package/daemon/dist/agent-sdk/drivers/codex-exec-events.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts +0 -9
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js +0 -86
- package/daemon/dist/agent-sdk/drivers/codex-exec-items.js.map +0 -1
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts +0 -17
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.d.ts.map +0 -1
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js +0 -255
- package/daemon/dist/agent-sdk/drivers/gemini-cli-driver.js.map +0 -1
- package/daemon/dist/core/sdk-wrapper.d.ts +0 -36
- package/daemon/dist/core/sdk-wrapper.d.ts.map +0 -1
- package/daemon/dist/core/sdk-wrapper.js +0 -533
- package/daemon/dist/core/sdk-wrapper.js.map +0 -1
- package/daemon/dist/skills/claude_code.d.ts +0 -25
- package/daemon/dist/skills/claude_code.d.ts.map +0 -1
- package/daemon/dist/skills/claude_code.js +0 -49
- package/daemon/dist/skills/claude_code.js.map +0 -1
|
@@ -6,7 +6,7 @@ import { createRequire } from 'module';
|
|
|
6
6
|
import { spawn, spawnSync } from 'child_process';
|
|
7
7
|
import { DAEMON_HOSTNAME, DEFAULT_MODEL, MODELS_REGISTRY, SUPEN_HOME, reloadModelsRegistry, } from '../../core/config.js';
|
|
8
8
|
import { readConfigSummary, readConfigYamlFile, writeConfigYamlFile, } from '../../core/env.js';
|
|
9
|
-
import { readSpaceEnvMap, updateSpaceEnvMap, withSupenLlmEnv } from '../../core/space-env.js';
|
|
9
|
+
import { readSpaceEnvMap, updateSpaceEnvMap, withRuntimeSpaceEnv, withSupenLlmEnv } from '../../core/space-env.js';
|
|
10
10
|
import { readCodexSubscription } from '../../core/codex-subscription.js';
|
|
11
11
|
import { buildHubSnapshotForSpace } from '../../core/hub-snapshot.js';
|
|
12
12
|
import { readEnrollmentState, createEnrollmentToken, verifyEnrollmentToken, setTrustState } from '../../core/enrollment.js';
|
|
@@ -22,20 +22,27 @@ import { logger } from '../../core/logger.js';
|
|
|
22
22
|
import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
|
|
23
23
|
import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
|
|
24
24
|
import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
25
|
+
import { projectThreadRuntimeState } from '../../core/thread-runtime-state.js';
|
|
26
|
+
import { attachThreadContextToChunk, readThreadContext, } from '../../core/thread-context.js';
|
|
25
27
|
import { addThreadStreamClient, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
26
28
|
const DEFAULT_TOKEN_TTL_MINUTES = 20;
|
|
27
29
|
const SPACE_LOG_EVENT_LIMIT = 200;
|
|
28
30
|
const SPACE_LOG_STREAM_POLL_MS = 1000;
|
|
29
31
|
const SPACE_LOG_STREAM_PING_MS = 15000;
|
|
30
32
|
const MIRRORED_THREAD_LIMIT = 80;
|
|
31
|
-
const MIRRORED_THREAD_HISTORY_LIMIT =
|
|
32
|
-
const MIRRORED_THREAD_HISTORY_MAX_BYTES =
|
|
33
|
+
const MIRRORED_THREAD_HISTORY_LIMIT = 200;
|
|
34
|
+
const MIRRORED_THREAD_HISTORY_MAX_BYTES = 256 * 1024 * 1024;
|
|
35
|
+
const MIRRORED_THREAD_PROJECTION_VERSION = 2;
|
|
33
36
|
const MIRRORED_THREAD_TEXT_LIMIT = 48_000;
|
|
34
37
|
const MIRRORED_THREAD_INLINE_DATA_URL_LIMIT = 120_000;
|
|
35
38
|
const MIRRORED_THREAD_STREAM_REPLAY_LIMIT = 500;
|
|
36
39
|
const MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES = 8 * 1024 * 1024;
|
|
40
|
+
const MIRRORED_THREAD_STREAM_JSONL_POLL_MS = 500;
|
|
41
|
+
const MIRRORED_THREAD_STREAM_JSONL_READ_MAX_BYTES = 2 * 1024 * 1024;
|
|
42
|
+
const MIRRORED_THREAD_STATUS_TAIL_MAX_BYTES = 256 * 1024;
|
|
37
43
|
const MIRRORED_THREAD_RUNNING_LEASE_MS = 30 * 60 * 1000;
|
|
38
44
|
const MIRRORED_THREAD_HISTORY_CHUNK_BYTES = 1024 * 1024;
|
|
45
|
+
const MIRRORED_THREAD_SESSION_FILE_INDEX_TTL_MS = 2_000;
|
|
39
46
|
const require = createRequire(import.meta.url);
|
|
40
47
|
const HOST_DAEMON_INSTALL_DIR = path.join(SUPEN_HOME, 'daemon');
|
|
41
48
|
const HOST_DAEMON_CLI_PACKAGE_ROOT = path.join(HOST_DAEMON_INSTALL_DIR, 'node_modules', '@supen-ai', 'cli');
|
|
@@ -433,6 +440,9 @@ export function readRecentJsonlLines(filePath, maxBytes = MIRRORED_THREAD_HISTOR
|
|
|
433
440
|
}
|
|
434
441
|
}
|
|
435
442
|
function isCodexHistoryMessageLine(line) {
|
|
443
|
+
if (!line.includes('"type":"message"') && !line.includes('"type":"user_message"')) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
436
446
|
const parsed = parseJsonLine(line);
|
|
437
447
|
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
438
448
|
? parsed.payload
|
|
@@ -440,8 +450,78 @@ function isCodexHistoryMessageLine(line) {
|
|
|
440
450
|
return ((parsed?.type === 'event_msg' && payload.type === 'user_message') ||
|
|
441
451
|
(parsed?.type === 'response_item' && payload.type === 'message'));
|
|
442
452
|
}
|
|
453
|
+
function isCodexVisibleUserHistoryLine(line) {
|
|
454
|
+
const parsed = parseJsonLine(line);
|
|
455
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
456
|
+
? parsed.payload
|
|
457
|
+
: {};
|
|
458
|
+
if (parsed?.type === 'event_msg' && payload.type === 'user_message') {
|
|
459
|
+
const text = typeof payload.message === 'string' ? payload.message.trim() : '';
|
|
460
|
+
return Boolean(text && !isInternalMirroredUserMessage(text));
|
|
461
|
+
}
|
|
462
|
+
if (parsed?.type !== 'response_item' || payload.type !== 'message' || payload.role !== 'user') {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
const text = mirroredMessageContentParts(payload.content).text;
|
|
466
|
+
return Boolean(text && !isInternalMirroredUserMessage(sanitizeMirroredUserText(text)));
|
|
467
|
+
}
|
|
468
|
+
function isCodexHistoryTranscriptEventLine(line) {
|
|
469
|
+
if (!line.includes('"type":"function_call"') &&
|
|
470
|
+
!line.includes('"type":"custom_tool_call"') &&
|
|
471
|
+
!line.includes('"type":"patch_apply_end"') &&
|
|
472
|
+
!line.includes('"type":"tool_search_call"') &&
|
|
473
|
+
!line.includes('"type":"web_search_call"') &&
|
|
474
|
+
!line.includes('"type":"mcp_tool_call"')) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const parsed = parseJsonLine(line);
|
|
478
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
479
|
+
? parsed.payload
|
|
480
|
+
: {};
|
|
481
|
+
if (parsed?.type === 'event_msg') {
|
|
482
|
+
return payload.type === 'patch_apply_end';
|
|
483
|
+
}
|
|
484
|
+
if (parsed?.type !== 'response_item')
|
|
485
|
+
return false;
|
|
486
|
+
return (payload.type === 'function_call' ||
|
|
487
|
+
payload.type === 'custom_tool_call' ||
|
|
488
|
+
payload.type === 'tool_search_call' ||
|
|
489
|
+
payload.type === 'web_search_call' ||
|
|
490
|
+
payload.type === 'mcp_tool_call');
|
|
491
|
+
}
|
|
492
|
+
function isCodexHistoryTranscriptLine(line) {
|
|
493
|
+
return isCodexHistoryMessageLine(line) || isCodexHistoryTranscriptEventLine(line);
|
|
494
|
+
}
|
|
495
|
+
function isCodexJsonlMessageRecord(parsed) {
|
|
496
|
+
const payload = parsed?.payload && typeof parsed.payload === 'object'
|
|
497
|
+
? parsed.payload
|
|
498
|
+
: {};
|
|
499
|
+
return parsed?.type === 'response_item' && payload.type === 'message';
|
|
500
|
+
}
|
|
501
|
+
function codexJsonlEventChunk(parsed) {
|
|
502
|
+
const payload = parsed.payload && typeof parsed.payload === 'object'
|
|
503
|
+
? parsed.payload
|
|
504
|
+
: {};
|
|
505
|
+
const payloadType = typeof payload.type === 'string' ? payload.type : '';
|
|
506
|
+
return {
|
|
507
|
+
type: 'data-codex-event',
|
|
508
|
+
data: {
|
|
509
|
+
eventType: `codex-jsonl/${parsed.type || 'record'}${payloadType ? `/${payloadType}` : ''}`,
|
|
510
|
+
raw: parsed,
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function codexAppServerEventChunk(method, params = {}) {
|
|
515
|
+
return {
|
|
516
|
+
type: 'data-codex-event',
|
|
517
|
+
data: {
|
|
518
|
+
eventType: method,
|
|
519
|
+
raw: { method, params },
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
}
|
|
443
523
|
function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRRORED_THREAD_HISTORY_MAX_BYTES) {
|
|
444
|
-
const safeTargetMessages = Math.max(1, Math.min(Math.floor(targetMessages),
|
|
524
|
+
const safeTargetMessages = Math.max(1, Math.min(Math.floor(targetMessages), 5_000));
|
|
445
525
|
const safeMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), MIRRORED_THREAD_HISTORY_MAX_BYTES));
|
|
446
526
|
const size = fs.statSync(filePath).size;
|
|
447
527
|
const fd = fs.openSync(filePath, 'r');
|
|
@@ -464,12 +544,9 @@ function readRecentCodexHistoryLines(filePath, targetMessages, maxBytes = MIRROR
|
|
|
464
544
|
lines.unshift(leadingPartial);
|
|
465
545
|
leadingPartial = '';
|
|
466
546
|
}
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
messageCount += 1;
|
|
471
|
-
}
|
|
472
|
-
collected = [...completeLines, ...collected];
|
|
547
|
+
const historyLines = lines.filter((line) => line && isCodexHistoryTranscriptLine(line));
|
|
548
|
+
messageCount += historyLines.filter(isCodexVisibleUserHistoryLine).length;
|
|
549
|
+
collected = [...historyLines, ...collected];
|
|
473
550
|
}
|
|
474
551
|
return collected.filter(Boolean);
|
|
475
552
|
}
|
|
@@ -574,7 +651,8 @@ function readThreadStateEntries(limit) {
|
|
|
574
651
|
return [];
|
|
575
652
|
}
|
|
576
653
|
}
|
|
577
|
-
|
|
654
|
+
let mirroredSessionFileIndexCache = null;
|
|
655
|
+
function walkSessionFilesUncached(dir, out = new Map()) {
|
|
578
656
|
if (!fs.existsSync(dir))
|
|
579
657
|
return out;
|
|
580
658
|
let entries;
|
|
@@ -587,7 +665,7 @@ function walkSessionFiles(dir, out = new Map()) {
|
|
|
587
665
|
for (const entry of entries) {
|
|
588
666
|
const fullPath = path.join(dir, entry.name);
|
|
589
667
|
if (entry.isDirectory()) {
|
|
590
|
-
|
|
668
|
+
walkSessionFilesUncached(fullPath, out);
|
|
591
669
|
continue;
|
|
592
670
|
}
|
|
593
671
|
if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
|
|
@@ -598,6 +676,21 @@ function walkSessionFiles(dir, out = new Map()) {
|
|
|
598
676
|
}
|
|
599
677
|
return out;
|
|
600
678
|
}
|
|
679
|
+
function walkSessionFiles(dir) {
|
|
680
|
+
const now = Date.now();
|
|
681
|
+
if (mirroredSessionFileIndexCache &&
|
|
682
|
+
mirroredSessionFileIndexCache.root === dir &&
|
|
683
|
+
now - mirroredSessionFileIndexCache.indexedAt <= MIRRORED_THREAD_SESSION_FILE_INDEX_TTL_MS) {
|
|
684
|
+
return new Map(mirroredSessionFileIndexCache.files);
|
|
685
|
+
}
|
|
686
|
+
const files = walkSessionFilesUncached(dir);
|
|
687
|
+
mirroredSessionFileIndexCache = {
|
|
688
|
+
root: dir,
|
|
689
|
+
indexedAt: now,
|
|
690
|
+
files: new Map(files),
|
|
691
|
+
};
|
|
692
|
+
return files;
|
|
693
|
+
}
|
|
601
694
|
function readFileHead(filePath, maxBytes = 64 * 1024) {
|
|
602
695
|
const fd = fs.openSync(filePath, 'r');
|
|
603
696
|
try {
|
|
@@ -622,6 +715,34 @@ function readFileTail(filePath, maxBytes = 256 * 1024) {
|
|
|
622
715
|
fs.closeSync(fd);
|
|
623
716
|
}
|
|
624
717
|
}
|
|
718
|
+
function readAppendedJsonlLines(filePath, offset) {
|
|
719
|
+
if (!fs.existsSync(filePath))
|
|
720
|
+
return { offset, lines: [], partial: '' };
|
|
721
|
+
const stat = fs.statSync(filePath);
|
|
722
|
+
if (stat.size <= offset)
|
|
723
|
+
return { offset: stat.size, lines: [], partial: '' };
|
|
724
|
+
const bytesToRead = Math.min(stat.size - offset, MIRRORED_THREAD_STREAM_JSONL_READ_MAX_BYTES);
|
|
725
|
+
const readStart = stat.size - offset > MIRRORED_THREAD_STREAM_JSONL_READ_MAX_BYTES
|
|
726
|
+
? stat.size - MIRRORED_THREAD_STREAM_JSONL_READ_MAX_BYTES
|
|
727
|
+
: offset;
|
|
728
|
+
const fd = fs.openSync(filePath, 'r');
|
|
729
|
+
try {
|
|
730
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
731
|
+
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, readStart);
|
|
732
|
+
const text = buffer.subarray(0, bytesRead).toString('utf-8');
|
|
733
|
+
const parts = text.split(/\r?\n/);
|
|
734
|
+
const partial = text.endsWith('\n') || text.endsWith('\r') ? '' : (parts.pop() || '');
|
|
735
|
+
const completeLines = parts.filter(Boolean);
|
|
736
|
+
return {
|
|
737
|
+
offset: readStart + bytesRead - Buffer.byteLength(partial),
|
|
738
|
+
lines: readStart === offset ? completeLines : completeLines.slice(1),
|
|
739
|
+
partial,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
fs.closeSync(fd);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
625
746
|
function readThreadWorkspacePath(filePath) {
|
|
626
747
|
if (!filePath)
|
|
627
748
|
return null;
|
|
@@ -645,6 +766,91 @@ function readThreadWorkspacePath(filePath) {
|
|
|
645
766
|
}
|
|
646
767
|
return null;
|
|
647
768
|
}
|
|
769
|
+
function startCodexThreadObserver(input) {
|
|
770
|
+
const child = spawn('codex', ['app-server', '--listen', 'stdio://'], {
|
|
771
|
+
cwd: input.cwd,
|
|
772
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
773
|
+
env: withRuntimeSpaceEnv(process.env),
|
|
774
|
+
});
|
|
775
|
+
let buffer = '';
|
|
776
|
+
let nextId = 1;
|
|
777
|
+
let observerEventIndex = 0;
|
|
778
|
+
let initialized = false;
|
|
779
|
+
const send = (message) => {
|
|
780
|
+
if (!child.stdin.writable)
|
|
781
|
+
return;
|
|
782
|
+
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
783
|
+
};
|
|
784
|
+
const request = (method, params) => {
|
|
785
|
+
send({ id: nextId++, method, ...(params ? { params } : {}) });
|
|
786
|
+
};
|
|
787
|
+
child.stdout.on('data', (chunk) => {
|
|
788
|
+
buffer += String(chunk);
|
|
789
|
+
while (true) {
|
|
790
|
+
const newlineIndex = buffer.indexOf('\n');
|
|
791
|
+
if (newlineIndex < 0)
|
|
792
|
+
break;
|
|
793
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
794
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
795
|
+
if (!line.startsWith('{'))
|
|
796
|
+
continue;
|
|
797
|
+
let parsed;
|
|
798
|
+
try {
|
|
799
|
+
parsed = JSON.parse(line);
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (parsed.id !== undefined && typeof parsed.method === 'string') {
|
|
805
|
+
send({ id: parsed.id, result: {} });
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (parsed.id !== undefined) {
|
|
809
|
+
if (!initialized && parsed.id === 1) {
|
|
810
|
+
initialized = true;
|
|
811
|
+
send({ method: 'initialized' });
|
|
812
|
+
request('thread/resume', {
|
|
813
|
+
threadId: input.threadId,
|
|
814
|
+
cwd: input.cwd,
|
|
815
|
+
experimentalRawEvents: true,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const method = typeof parsed.method === 'string' ? parsed.method : '';
|
|
821
|
+
if (!method)
|
|
822
|
+
continue;
|
|
823
|
+
const params = parsed.params && typeof parsed.params === 'object'
|
|
824
|
+
? parsed.params
|
|
825
|
+
: {};
|
|
826
|
+
const eventThreadId = typeof params.threadId === 'string' ? params.threadId : '';
|
|
827
|
+
if (eventThreadId !== input.threadId)
|
|
828
|
+
continue;
|
|
829
|
+
observerEventIndex += 1;
|
|
830
|
+
input.write(codexAppServerEventChunk(method, params), `observer-${Date.now()}-${observerEventIndex}`);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
child.stderr.on('data', () => { });
|
|
834
|
+
child.once('error', (error) => {
|
|
835
|
+
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer failed');
|
|
836
|
+
});
|
|
837
|
+
request('initialize', {
|
|
838
|
+
clientInfo: {
|
|
839
|
+
name: 'supen-daemon-thread-observer',
|
|
840
|
+
title: 'Supen',
|
|
841
|
+
version: '0.1.0',
|
|
842
|
+
},
|
|
843
|
+
capabilities: { experimentalApi: true },
|
|
844
|
+
});
|
|
845
|
+
return () => {
|
|
846
|
+
try {
|
|
847
|
+
child.kill('SIGTERM');
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
// Observer cleanup is best-effort; the HTTP stream is already closing.
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
648
854
|
function normalizeThreadStatusToken(value) {
|
|
649
855
|
return typeof value === 'string'
|
|
650
856
|
? value.trim().toLowerCase().replace(/[-_\s]/g, '')
|
|
@@ -697,7 +903,7 @@ function readThreadStatus(filePath) {
|
|
|
697
903
|
let sawUserTurn = false;
|
|
698
904
|
let completedAfterLastUser = false;
|
|
699
905
|
let lastRunningActivityMs = null;
|
|
700
|
-
const tail = readFileTail(filePath,
|
|
906
|
+
const tail = readFileTail(filePath, MIRRORED_THREAD_STATUS_TAIL_MAX_BYTES);
|
|
701
907
|
for (const line of tail.split(/\r?\n/)) {
|
|
702
908
|
if (!line.trim())
|
|
703
909
|
continue;
|
|
@@ -707,7 +913,9 @@ function readThreadStatus(filePath) {
|
|
|
707
913
|
? parsed.payload
|
|
708
914
|
: {};
|
|
709
915
|
if (parsed?.type === 'event_msg') {
|
|
710
|
-
if (payload.type === 'task_started' ||
|
|
916
|
+
if (payload.type === 'task_started' ||
|
|
917
|
+
payload.type === 'user_message' ||
|
|
918
|
+
payload.type === 'thread_goal_updated') {
|
|
711
919
|
sawUserTurn = true;
|
|
712
920
|
completedAfterLastUser = false;
|
|
713
921
|
lastRunningActivityMs = timestampMs;
|
|
@@ -730,6 +938,26 @@ function readThreadStatus(filePath) {
|
|
|
730
938
|
lastRunningActivityMs = timestampMs;
|
|
731
939
|
continue;
|
|
732
940
|
}
|
|
941
|
+
if (parsed?.type === 'response_item' &&
|
|
942
|
+
payload.type === 'message' &&
|
|
943
|
+
payload.role === 'assistant') {
|
|
944
|
+
if (payload.phase === 'final_answer') {
|
|
945
|
+
completedAfterLastUser = true;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (sawUserTurn) {
|
|
949
|
+
completedAfterLastUser = false;
|
|
950
|
+
lastRunningActivityMs = timestampMs;
|
|
951
|
+
}
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (parsed?.type === 'response_item' &&
|
|
955
|
+
sawUserTurn &&
|
|
956
|
+
!completedAfterLastUser &&
|
|
957
|
+
payload.type !== 'reasoning') {
|
|
958
|
+
lastRunningActivityMs = timestampMs;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
733
961
|
const raw = payload.raw && typeof payload.raw === 'object'
|
|
734
962
|
? payload.raw
|
|
735
963
|
: null;
|
|
@@ -774,6 +1002,17 @@ function readThreadStatus(filePath) {
|
|
|
774
1002
|
return 'idle';
|
|
775
1003
|
}
|
|
776
1004
|
}
|
|
1005
|
+
function readMirroredThreadStatus(threadId, filePath) {
|
|
1006
|
+
try {
|
|
1007
|
+
const runtimeState = projectThreadRuntimeState(threadId);
|
|
1008
|
+
if (isRunningThreadStatusToken(runtimeState.runtime.status))
|
|
1009
|
+
return 'running';
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// Fall back to the Codex session file status below.
|
|
1013
|
+
}
|
|
1014
|
+
return readThreadStatus(filePath);
|
|
1015
|
+
}
|
|
777
1016
|
function projectNameFromPath(projectPath) {
|
|
778
1017
|
if (!projectPath)
|
|
779
1018
|
return 'No workspace';
|
|
@@ -892,7 +1131,7 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
|
|
|
892
1131
|
id: entry.id,
|
|
893
1132
|
title,
|
|
894
1133
|
updated_at: entry.updated_at,
|
|
895
|
-
status:
|
|
1134
|
+
status: readMirroredThreadStatus(entry.id, sessionPath),
|
|
896
1135
|
session_path: sessionPath,
|
|
897
1136
|
});
|
|
898
1137
|
projects.set(projectId, project);
|
|
@@ -923,7 +1162,7 @@ function readMirroredTaskProjects(limit = MIRRORED_THREAD_LIMIT) {
|
|
|
923
1162
|
id: entry.id,
|
|
924
1163
|
title: entry.thread_name,
|
|
925
1164
|
updated_at: entry.updated_at,
|
|
926
|
-
status:
|
|
1165
|
+
status: readMirroredThreadStatus(entry.id, sessionPath),
|
|
927
1166
|
session_path: sessionPath,
|
|
928
1167
|
});
|
|
929
1168
|
projects.set(projectId, project);
|
|
@@ -1050,6 +1289,9 @@ function isInternalMirroredUserMessage(text) {
|
|
|
1050
1289
|
const trimmed = text.trimStart();
|
|
1051
1290
|
return (trimmed.startsWith('<environment_context>') ||
|
|
1052
1291
|
trimmed.startsWith('<developer_context>') ||
|
|
1292
|
+
trimmed.startsWith('<codex_internal_context') ||
|
|
1293
|
+
trimmed.startsWith('<goal_context>') ||
|
|
1294
|
+
trimmed.startsWith('<turn_aborted>') ||
|
|
1053
1295
|
trimmed.startsWith('[plugin:vite:'));
|
|
1054
1296
|
}
|
|
1055
1297
|
function isMirroredGoalContextMessage(text) {
|
|
@@ -1065,8 +1307,55 @@ function mirroredThreadWorkspacePath(sessionPath, stateEntry) {
|
|
|
1065
1307
|
}
|
|
1066
1308
|
return null;
|
|
1067
1309
|
}
|
|
1310
|
+
function latestRawThreadEventTimestampMs(events) {
|
|
1311
|
+
let latest = null;
|
|
1312
|
+
for (const event of events) {
|
|
1313
|
+
const timestampMs = Date.parse(event.received_at);
|
|
1314
|
+
if (!Number.isFinite(timestampMs))
|
|
1315
|
+
continue;
|
|
1316
|
+
latest = latest === null ? timestampMs : Math.max(latest, timestampMs);
|
|
1317
|
+
}
|
|
1318
|
+
return latest;
|
|
1319
|
+
}
|
|
1320
|
+
function rawThreadEventsAreStaleForHistory(events, latestHistoryTimestamp) {
|
|
1321
|
+
const latestEventTimestampMs = latestRawThreadEventTimestampMs(events);
|
|
1322
|
+
if (latestEventTimestampMs === null)
|
|
1323
|
+
return false;
|
|
1324
|
+
const latestHistoryTimestampMs = Date.parse(latestHistoryTimestamp);
|
|
1325
|
+
if (!Number.isFinite(latestHistoryTimestampMs))
|
|
1326
|
+
return false;
|
|
1327
|
+
return latestEventTimestampMs + 30_000 < latestHistoryTimestampMs;
|
|
1328
|
+
}
|
|
1329
|
+
function collapseCompletedAssistantProgressMessages(input) {
|
|
1330
|
+
const output = [];
|
|
1331
|
+
let assistantRun = [];
|
|
1332
|
+
const flushAssistantRun = () => {
|
|
1333
|
+
if (assistantRun.length === 0)
|
|
1334
|
+
return;
|
|
1335
|
+
const finalAnswers = assistantRun.filter((message) => message.phase === 'final_answer');
|
|
1336
|
+
if (finalAnswers.length > 0) {
|
|
1337
|
+
output.push(...finalAnswers);
|
|
1338
|
+
}
|
|
1339
|
+
else {
|
|
1340
|
+
output.push(...assistantRun);
|
|
1341
|
+
}
|
|
1342
|
+
assistantRun = [];
|
|
1343
|
+
};
|
|
1344
|
+
for (const message of input) {
|
|
1345
|
+
if (message.role === 'assistant') {
|
|
1346
|
+
assistantRun.push(message);
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
flushAssistantRun();
|
|
1350
|
+
output.push(message);
|
|
1351
|
+
}
|
|
1352
|
+
flushAssistantRun();
|
|
1353
|
+
return output
|
|
1354
|
+
.filter((message) => message.internal !== true)
|
|
1355
|
+
.map(({ phase: _phase, internal: _internal, ...message }) => message);
|
|
1356
|
+
}
|
|
1068
1357
|
export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY_LIMIT, options = {}) {
|
|
1069
|
-
const safeLimit = Math.max(1, Math.min(limit,
|
|
1358
|
+
const safeLimit = Math.max(1, Math.min(limit, 1000));
|
|
1070
1359
|
const sessionPath = walkSessionFiles(path.join(localAgentHome(), 'sessions')).get(threadId);
|
|
1071
1360
|
if (!sessionPath)
|
|
1072
1361
|
return null;
|
|
@@ -1075,10 +1364,12 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1075
1364
|
const workspacePath = mirroredThreadWorkspacePath(sessionPath, stateEntry);
|
|
1076
1365
|
const indexEntry = readThreadIndexEntry(threadId);
|
|
1077
1366
|
const eventLogHead = readThreadEventLogHead(eventLogThreadId);
|
|
1078
|
-
const
|
|
1367
|
+
const historyLineTarget = Math.min(250, Math.max(3, Math.ceil(safeLimit / 4)));
|
|
1368
|
+
const lines = readRecentCodexHistoryLines(sessionPath, historyLineTarget);
|
|
1079
1369
|
const messages = [];
|
|
1080
1370
|
const events = [];
|
|
1081
1371
|
let messageIndex = 0;
|
|
1372
|
+
let eventIndex = 0;
|
|
1082
1373
|
let latestTimestamp = indexEntry?.updated_at || '';
|
|
1083
1374
|
let recentGoalContext = null;
|
|
1084
1375
|
const mirroredUserMessageKey = (text) => sanitizeMirroredUserText(text)
|
|
@@ -1086,11 +1377,37 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1086
1377
|
.trim();
|
|
1087
1378
|
const pushUserMessage = (text, timestamp, messageId, attachments = []) => {
|
|
1088
1379
|
text = sanitizeMirroredUserText(text);
|
|
1380
|
+
const pushInternalBoundary = () => {
|
|
1381
|
+
let lastVisibleMessage;
|
|
1382
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1383
|
+
if (messages[index].internal === true)
|
|
1384
|
+
continue;
|
|
1385
|
+
lastVisibleMessage = messages[index];
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
if (lastVisibleMessage?.role !== 'assistant' || lastVisibleMessage.phase !== 'final_answer') {
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
messages.push({
|
|
1392
|
+
id: messageId || `${threadId}-internal-${messageIndex + 1}`,
|
|
1393
|
+
role: 'system',
|
|
1394
|
+
content: '',
|
|
1395
|
+
timestamp,
|
|
1396
|
+
task_id: threadId,
|
|
1397
|
+
internal: true,
|
|
1398
|
+
});
|
|
1399
|
+
};
|
|
1089
1400
|
if (isMirroredGoalContextMessage(text)) {
|
|
1090
1401
|
recentGoalContext = { text, timestamp };
|
|
1402
|
+
pushInternalBoundary();
|
|
1403
|
+
return;
|
|
1091
1404
|
}
|
|
1092
1405
|
text = truncateMirroredHistoryString(text, MIRRORED_THREAD_TEXT_LIMIT, 'Message');
|
|
1093
|
-
if (
|
|
1406
|
+
if (isInternalMirroredUserMessage(text)) {
|
|
1407
|
+
pushInternalBoundary();
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (!text && attachments.length === 0)
|
|
1094
1411
|
return;
|
|
1095
1412
|
const key = mirroredUserMessageKey(text);
|
|
1096
1413
|
const timestampMs = Date.parse(timestamp);
|
|
@@ -1170,6 +1487,15 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1170
1487
|
const messageId = typeof payload.id === 'string' ? payload.id : `${threadId}-web-user-${event.sequence}`;
|
|
1171
1488
|
pushUserMessage(text, timestamp, messageId, attachments);
|
|
1172
1489
|
};
|
|
1490
|
+
const pushCodexJsonlEvent = (parsed, timestamp) => {
|
|
1491
|
+
eventIndex += 1;
|
|
1492
|
+
events.push({
|
|
1493
|
+
id: `${threadId}-jsonl-event-${eventIndex}`,
|
|
1494
|
+
timestamp,
|
|
1495
|
+
task_id: threadId,
|
|
1496
|
+
chunk: codexJsonlEventChunk(parsed),
|
|
1497
|
+
});
|
|
1498
|
+
};
|
|
1173
1499
|
for (const line of lines) {
|
|
1174
1500
|
const parsed = parseJsonLine(line);
|
|
1175
1501
|
if (typeof parsed?.timestamp === 'string')
|
|
@@ -1185,12 +1511,19 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1185
1511
|
const text = typeof payload.message === 'string' ? payload.message.trim() : '';
|
|
1186
1512
|
pushUserMessage(text, timestamp, undefined, imageAttachmentsFromUnknown(payload.images));
|
|
1187
1513
|
}
|
|
1514
|
+
else if (payload.type === 'patch_apply_end') {
|
|
1515
|
+
pushCodexJsonlEvent(parsed, timestamp);
|
|
1516
|
+
}
|
|
1188
1517
|
continue;
|
|
1189
1518
|
}
|
|
1190
1519
|
if (parsed?.type !== 'response_item')
|
|
1191
1520
|
continue;
|
|
1192
|
-
if (payload.type !== 'message')
|
|
1521
|
+
if (payload.type !== 'message') {
|
|
1522
|
+
if (isCodexHistoryTranscriptEventLine(line)) {
|
|
1523
|
+
pushCodexJsonlEvent(parsed, timestamp);
|
|
1524
|
+
}
|
|
1193
1525
|
continue;
|
|
1526
|
+
}
|
|
1194
1527
|
messageIndex += 1;
|
|
1195
1528
|
const messageId = `${threadId}-message-${messageIndex}`;
|
|
1196
1529
|
const role = payload.role;
|
|
@@ -1210,16 +1543,22 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1210
1543
|
content: truncateMirroredHistoryString(text, MIRRORED_THREAD_TEXT_LIMIT, 'Message'),
|
|
1211
1544
|
timestamp,
|
|
1212
1545
|
task_id: threadId,
|
|
1546
|
+
...(typeof payload.phase === 'string' && payload.phase.trim()
|
|
1547
|
+
? { phase: payload.phase.trim() }
|
|
1548
|
+
: {}),
|
|
1213
1549
|
});
|
|
1214
1550
|
}
|
|
1215
1551
|
const eventLogEvents = listRecentThreadEventsAfter(eventLogThreadId, 0, {
|
|
1216
1552
|
limit: MIRRORED_THREAD_STREAM_REPLAY_LIMIT,
|
|
1217
1553
|
maxBytes: MIRRORED_THREAD_STREAM_REPLAY_MAX_BYTES,
|
|
1218
1554
|
});
|
|
1219
|
-
|
|
1555
|
+
const usableEventLogEvents = rawThreadEventsAreStaleForHistory(eventLogEvents, latestTimestamp)
|
|
1556
|
+
? []
|
|
1557
|
+
: eventLogEvents;
|
|
1558
|
+
for (const event of usableEventLogEvents) {
|
|
1220
1559
|
pushWebUserMessage(event);
|
|
1221
1560
|
}
|
|
1222
|
-
for (const event of
|
|
1561
|
+
for (const event of usableEventLogEvents) {
|
|
1223
1562
|
const chunk = threadStreamChunkForEvent(event);
|
|
1224
1563
|
if (!chunk)
|
|
1225
1564
|
continue;
|
|
@@ -1231,14 +1570,20 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1231
1570
|
chunk,
|
|
1232
1571
|
});
|
|
1233
1572
|
}
|
|
1234
|
-
const
|
|
1573
|
+
const collapsedMessages = collapseCompletedAssistantProgressMessages(messages);
|
|
1574
|
+
const slicedMessages = collapsedMessages.slice(-safeLimit);
|
|
1235
1575
|
const slicedEvents = events.slice(-safeLimit * 8);
|
|
1236
|
-
const status =
|
|
1576
|
+
const status = readMirroredThreadStatus(eventLogThreadId, sessionPath);
|
|
1237
1577
|
const latestGoalContext = recentGoalContext;
|
|
1578
|
+
const context = readThreadContext({
|
|
1579
|
+
threadId,
|
|
1580
|
+
eventLogThreadId,
|
|
1581
|
+
workspace: workspacePath,
|
|
1582
|
+
});
|
|
1238
1583
|
return {
|
|
1239
1584
|
session: {
|
|
1240
1585
|
task_id: threadId,
|
|
1241
|
-
agent_id: '
|
|
1586
|
+
agent_id: 'codex',
|
|
1242
1587
|
channel: 'local-agent',
|
|
1243
1588
|
source_ref: `local-agent:${threadId}`,
|
|
1244
1589
|
title: indexEntry?.thread_name || stateEntry?.title || slicedMessages.find((message) => message.role === 'user')?.content || 'New Task',
|
|
@@ -1254,14 +1599,15 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1254
1599
|
items: [],
|
|
1255
1600
|
runtimeState: {
|
|
1256
1601
|
event_log_head: eventLogHead,
|
|
1257
|
-
projection_version:
|
|
1602
|
+
projection_version: MIRRORED_THREAD_PROJECTION_VERSION,
|
|
1258
1603
|
event_log: { status: 'current' },
|
|
1259
1604
|
runtime: {
|
|
1260
1605
|
status: status === 'running' ? 'running' : 'idle',
|
|
1261
1606
|
updated_at: latestTimestamp || undefined,
|
|
1262
1607
|
},
|
|
1263
1608
|
},
|
|
1264
|
-
|
|
1609
|
+
context,
|
|
1610
|
+
has_more: collapsedMessages.length > safeLimit || lines.length >= historyLineTarget,
|
|
1265
1611
|
...(latestGoalContext ? { goal_context: latestGoalContext } : {}),
|
|
1266
1612
|
};
|
|
1267
1613
|
}
|
|
@@ -1309,14 +1655,14 @@ function coerceSingleQueryParam(value) {
|
|
|
1309
1655
|
return value || null;
|
|
1310
1656
|
}
|
|
1311
1657
|
function codexThreadsListRoute(pathname) {
|
|
1312
|
-
return /^\/api\/computers\/\{computer_id\}\/agents\/
|
|
1658
|
+
return /^\/api\/computers\/\{computer_id\}\/agents\/codex\/threads$/.test(pathname);
|
|
1313
1659
|
}
|
|
1314
1660
|
function codexThreadActionRoute(pathname, action) {
|
|
1315
1661
|
const suffix = action === 'messages' ? 'messages' : action;
|
|
1316
|
-
return pathname.match(new RegExp(`^/api/computers/\\{computer_id\\}/agents/
|
|
1662
|
+
return pathname.match(new RegExp(`^/api/computers/\\{computer_id\\}/agents/codex/threads/([^/]+)/${suffix}$`));
|
|
1317
1663
|
}
|
|
1318
1664
|
function codexProjectsOpenRoute(pathname) {
|
|
1319
|
-
return /^\/api\/computers\/\{computer_id\}\/agents\/
|
|
1665
|
+
return /^\/api\/computers\/\{computer_id\}\/agents\/codex\/projects\/open$/.test(pathname);
|
|
1320
1666
|
}
|
|
1321
1667
|
function collectSpaceLogEntries(filters = {}) {
|
|
1322
1668
|
const spaceId = currentSpaceId();
|
|
@@ -1439,11 +1785,9 @@ function buildMcpSettingsResponse() {
|
|
|
1439
1785
|
}
|
|
1440
1786
|
const CODING_CLI_INSTALL_COMMANDS = {
|
|
1441
1787
|
codex: { command: 'npm', args: ['install', '-g', '@openai/codex'] },
|
|
1442
|
-
gemini: { command: 'npm', args: ['install', '-g', '@google/gemini-cli'] },
|
|
1443
1788
|
};
|
|
1444
1789
|
const MANAGED_CODING_CLI_INSTALL_COMMANDS = {
|
|
1445
1790
|
codex: CODING_CLI_INSTALL_COMMANDS.codex,
|
|
1446
|
-
gemini: CODING_CLI_INSTALL_COMMANDS.gemini,
|
|
1447
1791
|
};
|
|
1448
1792
|
function resolveManagedCodingCliInstallCommand(cli, current) {
|
|
1449
1793
|
if (cli === 'codex' && current.install_source === 'homebrew') {
|
|
@@ -1454,7 +1798,7 @@ function resolveManagedCodingCliInstallCommand(cli, current) {
|
|
|
1454
1798
|
}
|
|
1455
1799
|
return MANAGED_CODING_CLI_INSTALL_COMMANDS[cli];
|
|
1456
1800
|
}
|
|
1457
|
-
const CODING_CLI_NAMES = ['codex'
|
|
1801
|
+
const CODING_CLI_NAMES = ['codex'];
|
|
1458
1802
|
const DETECTABLE_SPACE_APPS = [
|
|
1459
1803
|
{ id: 'libreoffice', command: 'libreoffice', managed: false },
|
|
1460
1804
|
{ id: 'dotnet', command: 'dotnet', managed: false },
|
|
@@ -1477,18 +1821,18 @@ function nowIso() {
|
|
|
1477
1821
|
}
|
|
1478
1822
|
function normalizeCliName(raw) {
|
|
1479
1823
|
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
1480
|
-
if (value === 'codex'
|
|
1824
|
+
if (value === 'codex')
|
|
1481
1825
|
return value;
|
|
1482
1826
|
return null;
|
|
1483
1827
|
}
|
|
1484
1828
|
function normalizeCodexTransport(raw) {
|
|
1485
1829
|
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
1486
|
-
if (value === 'app-server'
|
|
1830
|
+
if (value === 'app-server')
|
|
1487
1831
|
return value;
|
|
1488
1832
|
return null;
|
|
1489
1833
|
}
|
|
1490
1834
|
function isManagedCliName(name) {
|
|
1491
|
-
return name === 'codex'
|
|
1835
|
+
return name === 'codex';
|
|
1492
1836
|
}
|
|
1493
1837
|
function trimLogNoise(value) {
|
|
1494
1838
|
const line = value.trim();
|
|
@@ -1672,16 +2016,6 @@ function inferCliInstallSource(name, executablePath, resolvedPath) {
|
|
|
1672
2016
|
return { install_source: 'node-package', update_supported: false };
|
|
1673
2017
|
}
|
|
1674
2018
|
}
|
|
1675
|
-
if (name === 'gemini') {
|
|
1676
|
-
const npmRoot = detectGlobalNpmRoot();
|
|
1677
|
-
const geminiPackagePath = resolvedPath || executablePath || '';
|
|
1678
|
-
if (npmRoot && geminiPackagePath.startsWith(`${npmRoot}${path.sep}@google${path.sep}gemini-cli${path.sep}`)) {
|
|
1679
|
-
return { install_source: 'npm-global', update_supported: true };
|
|
1680
|
-
}
|
|
1681
|
-
if (normalized.includes('/node_modules/@google/gemini-cli/')) {
|
|
1682
|
-
return { install_source: 'node-package', update_supported: false };
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
2019
|
return { install_source: null, update_supported: false };
|
|
1686
2020
|
}
|
|
1687
2021
|
function detectCliInstalled(name) {
|
|
@@ -1904,13 +2238,6 @@ function readCodingCliStatusPayload() {
|
|
|
1904
2238
|
});
|
|
1905
2239
|
const codexInstalled = clis.find((cli) => cli.name === 'codex')?.installed ?? false;
|
|
1906
2240
|
const codexVersion = clis.find((cli) => cli.name === 'codex')?.version ?? null;
|
|
1907
|
-
const gemini = clis.find((cli) => cli.name === 'gemini');
|
|
1908
|
-
const claude = clis.find((cli) => cli.name === 'claude');
|
|
1909
|
-
const acpxDetected = detectCliInstalled('acpx');
|
|
1910
|
-
const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
|
|
1911
|
-
const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
|
|
1912
|
-
normalizeCodexTransport(readConfigSummary().codex_transport) ||
|
|
1913
|
-
'app-server';
|
|
1914
2241
|
const codexDefaultModel = readCodexDefaultModel();
|
|
1915
2242
|
const codexModelOptions = readCodexModelOptions(codexDefaultModel);
|
|
1916
2243
|
const installed_apps = DETECTABLE_SPACE_APPS.map((app) => {
|
|
@@ -1927,9 +2254,9 @@ function readCodingCliStatusPayload() {
|
|
|
1927
2254
|
};
|
|
1928
2255
|
});
|
|
1929
2256
|
return {
|
|
1930
|
-
selected_cli,
|
|
1931
|
-
default_model:
|
|
1932
|
-
model_options:
|
|
2257
|
+
selected_cli: 'codex',
|
|
2258
|
+
default_model: codexDefaultModel,
|
|
2259
|
+
model_options: codexModelOptions,
|
|
1933
2260
|
clis,
|
|
1934
2261
|
agent_runtimes: [
|
|
1935
2262
|
{
|
|
@@ -1941,53 +2268,9 @@ function readCodingCliStatusPayload() {
|
|
|
1941
2268
|
model_options: codexModelOptions,
|
|
1942
2269
|
transport: 'app-server',
|
|
1943
2270
|
recommended: true,
|
|
1944
|
-
default:
|
|
2271
|
+
default: true,
|
|
1945
2272
|
description: 'Daemon-managed codex app-server process with protocol-native streaming.',
|
|
1946
2273
|
},
|
|
1947
|
-
{
|
|
1948
|
-
id: 'codex-exec',
|
|
1949
|
-
label: 'Codex CLI Exec',
|
|
1950
|
-
installed: codexInstalled,
|
|
1951
|
-
version: codexVersion,
|
|
1952
|
-
default_model: codexDefaultModel,
|
|
1953
|
-
model_options: codexModelOptions,
|
|
1954
|
-
transport: 'exec',
|
|
1955
|
-
recommended: false,
|
|
1956
|
-
default: selected_cli === 'codex' && codexTransport === 'exec',
|
|
1957
|
-
description: 'Direct codex exec fallback, typically one process per turn.',
|
|
1958
|
-
},
|
|
1959
|
-
{
|
|
1960
|
-
id: 'codex-acpx',
|
|
1961
|
-
label: 'Codex via ACPX',
|
|
1962
|
-
installed: codexInstalled && acpxDetected.installed,
|
|
1963
|
-
version: acpxDetected.version || codexVersion,
|
|
1964
|
-
default_model: codexDefaultModel,
|
|
1965
|
-
model_options: codexModelOptions,
|
|
1966
|
-
transport: 'acpx',
|
|
1967
|
-
recommended: false,
|
|
1968
|
-
default: selected_cli === 'codex' && codexTransport === 'acpx',
|
|
1969
|
-
description: 'Compatibility bridge through ACPX for hosts that still need it.',
|
|
1970
|
-
},
|
|
1971
|
-
{
|
|
1972
|
-
id: 'claude-code',
|
|
1973
|
-
label: 'Claude Code',
|
|
1974
|
-
installed: Boolean(claude?.installed),
|
|
1975
|
-
version: claude?.version ?? null,
|
|
1976
|
-
transport: 'cli',
|
|
1977
|
-
recommended: false,
|
|
1978
|
-
default: selected_cli === 'claude',
|
|
1979
|
-
description: 'Claude Code CLI runtime.',
|
|
1980
|
-
},
|
|
1981
|
-
{
|
|
1982
|
-
id: 'gemini-cli',
|
|
1983
|
-
label: 'Gemini CLI',
|
|
1984
|
-
installed: Boolean(gemini?.installed),
|
|
1985
|
-
version: gemini?.version ?? null,
|
|
1986
|
-
transport: 'cli',
|
|
1987
|
-
recommended: false,
|
|
1988
|
-
default: selected_cli === 'gemini',
|
|
1989
|
-
description: 'Gemini CLI runtime.',
|
|
1990
|
-
},
|
|
1991
2274
|
],
|
|
1992
2275
|
installed_apps,
|
|
1993
2276
|
codex_auth: detectCodexAuthStatus(codexInstalled),
|
|
@@ -2173,52 +2456,20 @@ async function readCodexAgentStatusPayload() {
|
|
|
2173
2456
|
};
|
|
2174
2457
|
}
|
|
2175
2458
|
function readRuntimeModelStatusPayload() {
|
|
2176
|
-
const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
|
|
2177
|
-
const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
|
|
2178
|
-
normalizeCodexTransport(readConfigSummary().codex_transport) ||
|
|
2179
|
-
'app-server';
|
|
2180
2459
|
const codexDefaultModel = readCodexDefaultModel();
|
|
2181
2460
|
const codexModelOptions = readCodexModelOptions(codexDefaultModel);
|
|
2182
2461
|
return {
|
|
2183
|
-
selected_cli,
|
|
2184
|
-
default_model:
|
|
2185
|
-
model_options:
|
|
2462
|
+
selected_cli: 'codex',
|
|
2463
|
+
default_model: codexDefaultModel,
|
|
2464
|
+
model_options: codexModelOptions,
|
|
2186
2465
|
agent_runtimes: [
|
|
2187
2466
|
{
|
|
2188
2467
|
id: 'codex-app-server',
|
|
2189
|
-
default:
|
|
2468
|
+
default: true,
|
|
2190
2469
|
default_model: codexDefaultModel,
|
|
2191
2470
|
model_options: codexModelOptions,
|
|
2192
2471
|
transport: 'app-server',
|
|
2193
2472
|
},
|
|
2194
|
-
{
|
|
2195
|
-
id: 'codex-exec',
|
|
2196
|
-
default: selected_cli === 'codex' && codexTransport === 'exec',
|
|
2197
|
-
default_model: codexDefaultModel,
|
|
2198
|
-
model_options: codexModelOptions,
|
|
2199
|
-
transport: 'exec',
|
|
2200
|
-
},
|
|
2201
|
-
{
|
|
2202
|
-
id: 'codex-acpx',
|
|
2203
|
-
default: selected_cli === 'codex' && codexTransport === 'acpx',
|
|
2204
|
-
default_model: codexDefaultModel,
|
|
2205
|
-
model_options: codexModelOptions,
|
|
2206
|
-
transport: 'acpx',
|
|
2207
|
-
},
|
|
2208
|
-
{
|
|
2209
|
-
id: 'claude-code',
|
|
2210
|
-
default: selected_cli === 'claude',
|
|
2211
|
-
default_model: null,
|
|
2212
|
-
model_options: [],
|
|
2213
|
-
transport: 'cli',
|
|
2214
|
-
},
|
|
2215
|
-
{
|
|
2216
|
-
id: 'gemini-cli',
|
|
2217
|
-
default: selected_cli === 'gemini',
|
|
2218
|
-
default_model: null,
|
|
2219
|
-
model_options: [],
|
|
2220
|
-
transport: 'cli',
|
|
2221
|
-
},
|
|
2222
2473
|
],
|
|
2223
2474
|
};
|
|
2224
2475
|
}
|
|
@@ -2504,7 +2755,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2504
2755
|
try {
|
|
2505
2756
|
const transport = normalizeCodexTransport(decodeURIComponent(codexTransportDefaultMatch[1] || '').trim());
|
|
2506
2757
|
if (!transport) {
|
|
2507
|
-
writeProtocolError(res, 400, 'validation_error', 'invalid_codex_transport', 'transport must be
|
|
2758
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_codex_transport', 'transport must be app-server');
|
|
2508
2759
|
return true;
|
|
2509
2760
|
}
|
|
2510
2761
|
const result = setDefaultCodexTransport(transport);
|
|
@@ -2521,11 +2772,20 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2521
2772
|
}
|
|
2522
2773
|
if (pathname === '/api/computers/{computer_id}/agents/codex/default' && method === 'PUT') {
|
|
2523
2774
|
try {
|
|
2775
|
+
const parsed = await readJsonBody(req);
|
|
2776
|
+
const model = parsed && typeof parsed === 'object' && typeof parsed.model === 'string'
|
|
2777
|
+
? String(parsed.model).trim()
|
|
2778
|
+
: '';
|
|
2524
2779
|
const result = setDefaultCodingCli('codex');
|
|
2780
|
+
if (model) {
|
|
2781
|
+
writeCodexDefaultModel(model);
|
|
2782
|
+
}
|
|
2525
2783
|
writeJson(res, 200, {
|
|
2526
2784
|
ok: true,
|
|
2527
2785
|
...result,
|
|
2528
|
-
status:
|
|
2786
|
+
status: model
|
|
2787
|
+
? readRuntimeModelStatusPayload()
|
|
2788
|
+
: readCachedCodingCliStatusPayload({ force: true }),
|
|
2529
2789
|
});
|
|
2530
2790
|
}
|
|
2531
2791
|
catch (err) {
|
|
@@ -2716,13 +2976,49 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2716
2976
|
numericQueryParam(coerceSingleQueryParam(url.searchParams.get('after')));
|
|
2717
2977
|
const replayAfter = afterSequence ?? readThreadEventLogHead(threadId);
|
|
2718
2978
|
addThreadStreamClient(threadId, res);
|
|
2979
|
+
const sessionPath = walkSessionFiles(path.join(localAgentHome(), 'sessions')).get(threadId);
|
|
2980
|
+
let jsonlOffset = sessionPath && fs.existsSync(sessionPath) ? fs.statSync(sessionPath).size : 0;
|
|
2981
|
+
let jsonlEventIndex = 0;
|
|
2982
|
+
const workspacePath = readThreadWorkspacePath(sessionPath) || process.cwd();
|
|
2983
|
+
const withThreadContext = (chunk) => attachThreadContextToChunk(chunk, readThreadContext({ threadId, workspace: workspacePath }));
|
|
2984
|
+
const stopCodexObserver = process.env.NODE_ENV === 'test'
|
|
2985
|
+
? () => { }
|
|
2986
|
+
: startCodexThreadObserver({
|
|
2987
|
+
threadId,
|
|
2988
|
+
cwd: workspacePath,
|
|
2989
|
+
write: (chunk, id) => writeThreadStreamEvent(res, withThreadContext(chunk), id ? { id } : {}),
|
|
2990
|
+
});
|
|
2719
2991
|
const pingTimer = setInterval(() => {
|
|
2720
2992
|
writeSse(res);
|
|
2721
2993
|
res.flush?.();
|
|
2722
2994
|
}, SPACE_LOG_STREAM_PING_MS);
|
|
2723
2995
|
pingTimer.unref?.();
|
|
2996
|
+
const jsonlPollTimer = sessionPath
|
|
2997
|
+
? setInterval(() => {
|
|
2998
|
+
const read = readAppendedJsonlLines(sessionPath, jsonlOffset);
|
|
2999
|
+
jsonlOffset = read.offset;
|
|
3000
|
+
for (const line of read.lines) {
|
|
3001
|
+
if (!isCodexHistoryTranscriptLine(line))
|
|
3002
|
+
continue;
|
|
3003
|
+
const parsed = parseJsonLine(line);
|
|
3004
|
+
if (!parsed)
|
|
3005
|
+
continue;
|
|
3006
|
+
if (isCodexJsonlMessageRecord(parsed))
|
|
3007
|
+
continue;
|
|
3008
|
+
jsonlEventIndex += 1;
|
|
3009
|
+
writeThreadStreamEvent(res, withThreadContext(codexJsonlEventChunk(parsed)), {
|
|
3010
|
+
id: `jsonl-${jsonlOffset}-${jsonlEventIndex}`,
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
res.flush?.();
|
|
3014
|
+
}, MIRRORED_THREAD_STREAM_JSONL_POLL_MS)
|
|
3015
|
+
: null;
|
|
3016
|
+
jsonlPollTimer?.unref?.();
|
|
2724
3017
|
const cleanup = () => {
|
|
2725
3018
|
clearInterval(pingTimer);
|
|
3019
|
+
if (jsonlPollTimer)
|
|
3020
|
+
clearInterval(jsonlPollTimer);
|
|
3021
|
+
stopCodexObserver();
|
|
2726
3022
|
removeThreadStreamClient(threadId, res);
|
|
2727
3023
|
};
|
|
2728
3024
|
req.on('close', cleanup);
|
|
@@ -2737,7 +3033,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2737
3033
|
const chunk = threadStreamChunkForEvent(event);
|
|
2738
3034
|
if (!chunk)
|
|
2739
3035
|
continue;
|
|
2740
|
-
writeThreadStreamEvent(res, chunk, { id: event.sequence });
|
|
3036
|
+
writeThreadStreamEvent(res, withThreadContext(chunk), { id: event.sequence });
|
|
2741
3037
|
}
|
|
2742
3038
|
res.flush?.();
|
|
2743
3039
|
return true;
|
|
@@ -2764,7 +3060,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
2764
3060
|
const requestedAgentId = parsed && typeof parsed === 'object' && typeof parsed.agent_id === 'string'
|
|
2765
3061
|
? String(parsed.agent_id).trim()
|
|
2766
3062
|
: '';
|
|
2767
|
-
const fallbackAgentId = getAllAgents()[0]?.agent_id || '
|
|
3063
|
+
const fallbackAgentId = getAllAgents()[0]?.agent_id || 'codex';
|
|
2768
3064
|
const agentId = requestedAgentId || fallbackAgentId;
|
|
2769
3065
|
const session = threadId ? adoptMirroredThread(threadId, agentId) : null;
|
|
2770
3066
|
if (!session) {
|