@supen-ai/cli 1.4.7 → 1.4.9
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 +5 -236
- package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -1
- package/daemon/dist/core/codex-app-server-host.d.ts +39 -0
- package/daemon/dist/core/codex-app-server-host.d.ts.map +1 -0
- package/daemon/dist/core/codex-app-server-host.js +240 -0
- package/daemon/dist/core/codex-app-server-host.js.map +1 -0
- package/daemon/dist/core/codex-subscription.d.ts.map +1 -1
- package/daemon/dist/core/codex-subscription.js +13 -125
- package/daemon/dist/core/codex-subscription.js.map +1 -1
- package/daemon/dist/core/gateway.d.ts.map +1 -1
- package/daemon/dist/core/gateway.js +30 -4
- package/daemon/dist/core/gateway.js.map +1 -1
- package/daemon/dist/http/routes/automations.d.ts.map +1 -1
- package/daemon/dist/http/routes/automations.js +19 -2
- package/daemon/dist/http/routes/automations.js.map +1 -1
- package/daemon/dist/http/routes/system.d.ts +1 -0
- package/daemon/dist/http/routes/system.d.ts.map +1 -1
- package/daemon/dist/http/routes/system.js +85 -177
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/package.json +1 -1
- package/dist/computer.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -6,12 +6,13 @@ 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,
|
|
9
|
+
import { readSpaceEnvMap, updateSpaceEnvMap, 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';
|
|
13
13
|
import { collectOsTelemetryFields } from '../../core/os-info.js';
|
|
14
14
|
import { getCodingCliStatusResponse, startCodingCliStatusCache, } from '../../core/coding-cli-status-cache.js';
|
|
15
|
+
import { buildToolLookupEnv as buildSharedToolLookupEnv, resolveToolExecutablePath, } from '../../core/tool-lookup.js';
|
|
15
16
|
import { getGatewayInstance, getLlmToken } from '../../core/gateway.js';
|
|
16
17
|
import { normalizeGatewayUplinkUrl, readGatewayConfig, writeGatewayConfig, } from '../../core/gateway-config.js';
|
|
17
18
|
import { ensureThread, getAllAgents, getDailyUsage, getGlobalUsage, getThreadsForAgent, getThreadUiEvents, updateThreadBackendDriverId, updateThreadSdkSessionId, } from '../../core/store.js';
|
|
@@ -21,10 +22,10 @@ import { getMcpManager } from '../../mcp/index.js';
|
|
|
21
22
|
import { logger } from '../../core/logger.js';
|
|
22
23
|
import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
|
|
23
24
|
import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
|
|
24
|
-
import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
25
|
+
import { appendThreadEvent, listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
25
26
|
import { projectThreadRuntimeState } from '../../core/thread-runtime-state.js';
|
|
26
27
|
import { attachThreadContextToChunk, readThreadContext, } from '../../core/thread-context.js';
|
|
27
|
-
import { addThreadStreamClient, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
28
|
+
import { addThreadStreamClient, broadcastToThread, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
28
29
|
const DEFAULT_TOKEN_TTL_MINUTES = 20;
|
|
29
30
|
const SPACE_LOG_EVENT_LIMIT = 200;
|
|
30
31
|
const SPACE_LOG_STREAM_POLL_MS = 1000;
|
|
@@ -881,141 +882,6 @@ function readThreadWorkspacePath(filePath) {
|
|
|
881
882
|
}
|
|
882
883
|
return null;
|
|
883
884
|
}
|
|
884
|
-
const CODEX_THREAD_OBSERVER_STDOUT_MAX_BYTES = 4 * 1024 * 1024;
|
|
885
|
-
function startCodexThreadObserver(input) {
|
|
886
|
-
const codexPath = resolveCliExecutablePath('codex');
|
|
887
|
-
if (!codexPath) {
|
|
888
|
-
input.write({
|
|
889
|
-
type: 'error',
|
|
890
|
-
message: 'codex executable was not found in the daemon tool lookup PATH',
|
|
891
|
-
});
|
|
892
|
-
return () => { };
|
|
893
|
-
}
|
|
894
|
-
const child = spawn(codexPath, ['app-server', '--listen', 'stdio://'], {
|
|
895
|
-
cwd: input.cwd,
|
|
896
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
897
|
-
env: withRuntimeSpaceEnv(buildToolLookupEnv()),
|
|
898
|
-
});
|
|
899
|
-
let buffer = '';
|
|
900
|
-
let nextId = 1;
|
|
901
|
-
let observerEventIndex = 0;
|
|
902
|
-
let initialized = false;
|
|
903
|
-
let closed = false;
|
|
904
|
-
child.on('error', (err) => {
|
|
905
|
-
if (closed)
|
|
906
|
-
return;
|
|
907
|
-
input.write({
|
|
908
|
-
type: 'error',
|
|
909
|
-
message: err instanceof Error ? err.message : String(err),
|
|
910
|
-
});
|
|
911
|
-
});
|
|
912
|
-
child.on('close', () => {
|
|
913
|
-
closed = true;
|
|
914
|
-
});
|
|
915
|
-
child.stdin.on('error', (error) => {
|
|
916
|
-
closed = true;
|
|
917
|
-
if (error?.code === 'EPIPE' || error?.code === 'ECONNRESET')
|
|
918
|
-
return;
|
|
919
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer stdin failed');
|
|
920
|
-
});
|
|
921
|
-
const closeObserver = (signal = 'SIGTERM') => {
|
|
922
|
-
if (closed)
|
|
923
|
-
return;
|
|
924
|
-
closed = true;
|
|
925
|
-
try {
|
|
926
|
-
child.stdin.destroy();
|
|
927
|
-
child.kill(signal);
|
|
928
|
-
}
|
|
929
|
-
catch {
|
|
930
|
-
// Observer cleanup is best-effort; the HTTP stream is already closing.
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
|
-
const send = (message) => {
|
|
934
|
-
if (closed || child.stdin.destroyed || !child.stdin.writable)
|
|
935
|
-
return;
|
|
936
|
-
try {
|
|
937
|
-
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
938
|
-
}
|
|
939
|
-
catch (error) {
|
|
940
|
-
closed = true;
|
|
941
|
-
const code = error?.code;
|
|
942
|
-
if (code === 'EPIPE' || code === 'ECONNRESET')
|
|
943
|
-
return;
|
|
944
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer write failed');
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
const request = (method, params) => {
|
|
948
|
-
send({ id: nextId++, method, ...(params ? { params } : {}) });
|
|
949
|
-
};
|
|
950
|
-
child.stdout.on('data', (chunk) => {
|
|
951
|
-
if (closed)
|
|
952
|
-
return;
|
|
953
|
-
buffer += String(chunk);
|
|
954
|
-
if (buffer.length > CODEX_THREAD_OBSERVER_STDOUT_MAX_BYTES) {
|
|
955
|
-
logger.debug({ threadId: input.threadId, bytes: buffer.length }, 'Codex thread observer stdout exceeded line buffer limit');
|
|
956
|
-
closeObserver('SIGKILL');
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
while (true) {
|
|
960
|
-
const newlineIndex = buffer.indexOf('\n');
|
|
961
|
-
if (newlineIndex < 0)
|
|
962
|
-
break;
|
|
963
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
964
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
965
|
-
if (!line.startsWith('{'))
|
|
966
|
-
continue;
|
|
967
|
-
let parsed;
|
|
968
|
-
try {
|
|
969
|
-
parsed = JSON.parse(line);
|
|
970
|
-
}
|
|
971
|
-
catch {
|
|
972
|
-
continue;
|
|
973
|
-
}
|
|
974
|
-
if (parsed.id !== undefined && typeof parsed.method === 'string') {
|
|
975
|
-
send({ id: parsed.id, result: {} });
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
if (parsed.id !== undefined) {
|
|
979
|
-
if (!initialized && parsed.id === 1) {
|
|
980
|
-
initialized = true;
|
|
981
|
-
send({ method: 'initialized' });
|
|
982
|
-
request('thread/resume', {
|
|
983
|
-
threadId: input.threadId,
|
|
984
|
-
cwd: input.cwd,
|
|
985
|
-
experimentalRawEvents: true,
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
|
-
const method = typeof parsed.method === 'string' ? parsed.method : '';
|
|
991
|
-
if (!method)
|
|
992
|
-
continue;
|
|
993
|
-
const params = parsed.params && typeof parsed.params === 'object'
|
|
994
|
-
? parsed.params
|
|
995
|
-
: {};
|
|
996
|
-
const eventThreadId = typeof params.threadId === 'string' ? params.threadId : '';
|
|
997
|
-
if (eventThreadId !== input.threadId)
|
|
998
|
-
continue;
|
|
999
|
-
observerEventIndex += 1;
|
|
1000
|
-
input.write(codexAppServerEventChunk(method, params), `observer-${Date.now()}-${observerEventIndex}`);
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
child.stderr.on('data', () => { });
|
|
1004
|
-
child.once('error', (error) => {
|
|
1005
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer failed');
|
|
1006
|
-
});
|
|
1007
|
-
request('initialize', {
|
|
1008
|
-
clientInfo: {
|
|
1009
|
-
name: 'supen-daemon-thread-observer',
|
|
1010
|
-
title: 'Supen',
|
|
1011
|
-
version: '0.1.0',
|
|
1012
|
-
},
|
|
1013
|
-
capabilities: { experimentalApi: true },
|
|
1014
|
-
});
|
|
1015
|
-
return () => {
|
|
1016
|
-
closeObserver();
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
885
|
function normalizeThreadStatusToken(value) {
|
|
1020
886
|
return typeof value === 'string'
|
|
1021
887
|
? value.trim().toLowerCase().replace(/[-_\s]/g, '')
|
|
@@ -1464,6 +1330,23 @@ function isMirroredGoalContextMessage(text) {
|
|
|
1464
1330
|
const trimmed = text.trim();
|
|
1465
1331
|
return trimmed.startsWith('<goal_context>') && trimmed.endsWith('</goal_context>');
|
|
1466
1332
|
}
|
|
1333
|
+
function threadGoalEventAction(event) {
|
|
1334
|
+
const raw = event.raw_payload && typeof event.raw_payload === 'object'
|
|
1335
|
+
? event.raw_payload
|
|
1336
|
+
: {};
|
|
1337
|
+
const method = typeof raw.method === 'string' ? raw.method : '';
|
|
1338
|
+
const params = raw.params && typeof raw.params === 'object' ? raw.params : {};
|
|
1339
|
+
if (method === 'thread/goal/cleared')
|
|
1340
|
+
return 'cleared';
|
|
1341
|
+
if (method !== 'thread/goal/updated')
|
|
1342
|
+
return null;
|
|
1343
|
+
const status = typeof params.status === 'string' ? params.status.trim().toLowerCase() : '';
|
|
1344
|
+
if (status === 'paused')
|
|
1345
|
+
return 'paused';
|
|
1346
|
+
if (status === 'active' || status === 'resumed')
|
|
1347
|
+
return 'active';
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1467
1350
|
function mirroredThreadWorkspacePath(threadPath, stateEntry) {
|
|
1468
1351
|
const stateCwd = typeof stateEntry?.cwd === 'string' ? stateEntry.cwd : null;
|
|
1469
1352
|
const threadWorkspace = readThreadWorkspacePath(threadPath);
|
|
@@ -1736,6 +1619,22 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1736
1619
|
chunk,
|
|
1737
1620
|
});
|
|
1738
1621
|
}
|
|
1622
|
+
for (const event of usableEventLogEvents) {
|
|
1623
|
+
const action = threadGoalEventAction(event);
|
|
1624
|
+
if (!action)
|
|
1625
|
+
continue;
|
|
1626
|
+
const eventTimestampMs = Date.parse(event.received_at);
|
|
1627
|
+
const goalTimestampMs = recentGoalContext ? Date.parse(recentGoalContext.timestamp) : 0;
|
|
1628
|
+
if (recentGoalContext && Number.isFinite(eventTimestampMs) && Number.isFinite(goalTimestampMs) && eventTimestampMs < goalTimestampMs) {
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
if (action === 'cleared') {
|
|
1632
|
+
recentGoalContext = null;
|
|
1633
|
+
}
|
|
1634
|
+
else if (recentGoalContext) {
|
|
1635
|
+
recentGoalContext = { ...recentGoalContext, status: action };
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1739
1638
|
const collapsedMessages = collapseCompletedAssistantProgressMessages(messages);
|
|
1740
1639
|
const slicedMessages = collapsedMessages.slice(-safeLimit);
|
|
1741
1640
|
const slicedEvents = events.slice(-safeLimit * 8);
|
|
@@ -1990,13 +1889,6 @@ const DETECTABLE_SPACE_APPS = [
|
|
|
1990
1889
|
{ id: 'gh', command: 'gh', managed: false },
|
|
1991
1890
|
{ id: 'git', command: 'git', managed: false },
|
|
1992
1891
|
];
|
|
1993
|
-
const TOOL_LOOKUP_PATH_ENTRIES = [
|
|
1994
|
-
'/opt/homebrew/bin',
|
|
1995
|
-
'/usr/local/bin',
|
|
1996
|
-
'/opt/homebrew/sbin',
|
|
1997
|
-
'/usr/local/sbin',
|
|
1998
|
-
'/Applications/Codex.app/Contents/Resources',
|
|
1999
|
-
];
|
|
2000
1892
|
const codexConnectRuntime = {
|
|
2001
1893
|
state: 'idle',
|
|
2002
1894
|
updatedAt: null,
|
|
@@ -2227,25 +2119,10 @@ function inferNpmPackagePrefix(packageName, candidatePath) {
|
|
|
2227
2119
|
: beforeNodeModules;
|
|
2228
2120
|
}
|
|
2229
2121
|
function buildToolLookupEnv() {
|
|
2230
|
-
|
|
2231
|
-
const entries = [
|
|
2232
|
-
...TOOL_LOOKUP_PATH_ENTRIES,
|
|
2233
|
-
...existingPath.split(path.delimiter),
|
|
2234
|
-
].filter((entry) => entry.trim().length > 0);
|
|
2235
|
-
return {
|
|
2236
|
-
...process.env,
|
|
2237
|
-
PATH: Array.from(new Set(entries)).join(path.delimiter),
|
|
2238
|
-
};
|
|
2122
|
+
return buildSharedToolLookupEnv();
|
|
2239
2123
|
}
|
|
2240
2124
|
function resolveCliExecutablePath(name) {
|
|
2241
|
-
|
|
2242
|
-
encoding: 'utf8',
|
|
2243
|
-
timeout: 3000,
|
|
2244
|
-
env: buildToolLookupEnv(),
|
|
2245
|
-
});
|
|
2246
|
-
if (detected.status !== 0)
|
|
2247
|
-
return null;
|
|
2248
|
-
return String(detected.stdout || '').split(/\r?\n/g).find(Boolean)?.trim() || null;
|
|
2125
|
+
return resolveToolExecutablePath(name);
|
|
2249
2126
|
}
|
|
2250
2127
|
function detectCliInstalled(name) {
|
|
2251
2128
|
const executablePath = resolveCliExecutablePath(name);
|
|
@@ -3223,7 +3100,6 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3223
3100
|
const workspacePath = readThreadWorkspacePath(threadPath) || process.cwd();
|
|
3224
3101
|
const withThreadContext = (chunk) => attachThreadContextToChunk(chunk, readThreadContext({ threadId, workspace: workspacePath }));
|
|
3225
3102
|
let streamClosed = false;
|
|
3226
|
-
let stopCodexObserver = () => { };
|
|
3227
3103
|
let pingTimer = null;
|
|
3228
3104
|
let jsonlPollTimer = null;
|
|
3229
3105
|
const cleanup = () => {
|
|
@@ -3234,22 +3110,8 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3234
3110
|
clearInterval(pingTimer);
|
|
3235
3111
|
if (jsonlPollTimer)
|
|
3236
3112
|
clearInterval(jsonlPollTimer);
|
|
3237
|
-
stopCodexObserver();
|
|
3238
3113
|
removeThreadStreamClient(threadId, res);
|
|
3239
3114
|
};
|
|
3240
|
-
stopCodexObserver = process.env.NODE_ENV === 'test'
|
|
3241
|
-
? () => { }
|
|
3242
|
-
: startCodexThreadObserver({
|
|
3243
|
-
threadId,
|
|
3244
|
-
cwd: workspacePath,
|
|
3245
|
-
write: (chunk, id) => {
|
|
3246
|
-
if (streamClosed)
|
|
3247
|
-
return;
|
|
3248
|
-
const wrote = writeThreadStreamEvent(res, withThreadContext(chunk), id ? { id } : {});
|
|
3249
|
-
if (!wrote)
|
|
3250
|
-
cleanup();
|
|
3251
|
-
},
|
|
3252
|
-
});
|
|
3253
3115
|
pingTimer = setInterval(() => {
|
|
3254
3116
|
if (!writeSse(res)) {
|
|
3255
3117
|
cleanup();
|
|
@@ -3344,6 +3206,52 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3344
3206
|
writeJson(res, 200, { thread: serializeAdoptedMirroredThread(thread) });
|
|
3345
3207
|
return true;
|
|
3346
3208
|
}
|
|
3209
|
+
const mirroredThreadGoalMatch = codexThreadActionRoute(pathname, 'goal');
|
|
3210
|
+
if (mirroredThreadGoalMatch && (method === 'PATCH' || method === 'DELETE')) {
|
|
3211
|
+
const threadId = decodeURIComponent(mirroredThreadGoalMatch[1] || '').trim();
|
|
3212
|
+
if (!threadId || !codexThreadFiles().has(threadId)) {
|
|
3213
|
+
writeProtocolError(res, 404, 'not_found', 'mirrored_thread_not_found', 'Mirrored task history was not found.');
|
|
3214
|
+
return true;
|
|
3215
|
+
}
|
|
3216
|
+
let status = null;
|
|
3217
|
+
if (method === 'PATCH') {
|
|
3218
|
+
const parsed = await readJsonBody(req);
|
|
3219
|
+
const rawStatus = parsed && typeof parsed === 'object' && typeof parsed.status === 'string'
|
|
3220
|
+
? String(parsed.status).trim().toLowerCase()
|
|
3221
|
+
: '';
|
|
3222
|
+
if (rawStatus === 'active' || rawStatus === 'resumed')
|
|
3223
|
+
status = 'active';
|
|
3224
|
+
if (rawStatus === 'paused')
|
|
3225
|
+
status = 'paused';
|
|
3226
|
+
if (!status) {
|
|
3227
|
+
writeProtocolError(res, 400, 'validation_error', 'invalid_goal_status', 'Expected goal status "active" or "paused".');
|
|
3228
|
+
return true;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
const now = new Date().toISOString();
|
|
3232
|
+
const rawPayload = method === 'DELETE'
|
|
3233
|
+
? { method: 'thread/goal/cleared', params: { threadId, source: 'supen', timestamp: now } }
|
|
3234
|
+
: { method: 'thread/goal/updated', params: { threadId, status, source: 'supen', timestamp: now } };
|
|
3235
|
+
const event = appendThreadEvent({
|
|
3236
|
+
threadId,
|
|
3237
|
+
runtimeThreadId: threadId,
|
|
3238
|
+
source: 'web',
|
|
3239
|
+
eventType: method === 'DELETE' ? 'thread_goal_cleared' : 'thread_goal_updated',
|
|
3240
|
+
rawPayload,
|
|
3241
|
+
receivedAt: now,
|
|
3242
|
+
});
|
|
3243
|
+
const chunk = threadStreamChunkForEvent(event);
|
|
3244
|
+
if (chunk) {
|
|
3245
|
+
broadcastToThread(threadId, attachThreadContextToChunk(chunk, readThreadContext({ threadId })), { id: event.sequence });
|
|
3246
|
+
}
|
|
3247
|
+
writeJson(res, 200, {
|
|
3248
|
+
ok: true,
|
|
3249
|
+
thread_id: threadId,
|
|
3250
|
+
goal: method === 'DELETE' ? null : { status },
|
|
3251
|
+
event_log_head: event.sequence,
|
|
3252
|
+
});
|
|
3253
|
+
return true;
|
|
3254
|
+
}
|
|
3347
3255
|
if (codexProjectsOpenRoute(pathname) && method === 'POST') {
|
|
3348
3256
|
try {
|
|
3349
3257
|
const parsed = await readJsonBody(req);
|