@supen-ai/cli 1.4.8 → 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/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 +82 -153
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/package.json +1 -1
- package/dist/computer.js +1 -1
- package/package.json +1 -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,
|
|
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';
|
|
@@ -22,10 +22,10 @@ import { getMcpManager } from '../../mcp/index.js';
|
|
|
22
22
|
import { logger } from '../../core/logger.js';
|
|
23
23
|
import { buildDaemonOpenApiSpec } from '../../channels/http-routes.js';
|
|
24
24
|
import { writeJson, writeProtocolError, readJsonBody } from '../response.js';
|
|
25
|
-
import { listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
25
|
+
import { appendThreadEvent, listRecentThreadEventsAfter, readThreadEventLogHead, } from '../../core/thread-event-log.js';
|
|
26
26
|
import { projectThreadRuntimeState } from '../../core/thread-runtime-state.js';
|
|
27
27
|
import { attachThreadContextToChunk, readThreadContext, } from '../../core/thread-context.js';
|
|
28
|
-
import { addThreadStreamClient, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
28
|
+
import { addThreadStreamClient, broadcastToThread, removeThreadStreamClient, writeThreadStreamEvent } from '../thread-stream.js';
|
|
29
29
|
const DEFAULT_TOKEN_TTL_MINUTES = 20;
|
|
30
30
|
const SPACE_LOG_EVENT_LIMIT = 200;
|
|
31
31
|
const SPACE_LOG_STREAM_POLL_MS = 1000;
|
|
@@ -882,141 +882,6 @@ function readThreadWorkspacePath(filePath) {
|
|
|
882
882
|
}
|
|
883
883
|
return null;
|
|
884
884
|
}
|
|
885
|
-
const CODEX_THREAD_OBSERVER_STDOUT_MAX_BYTES = 4 * 1024 * 1024;
|
|
886
|
-
function startCodexThreadObserver(input) {
|
|
887
|
-
const codexPath = resolveCliExecutablePath('codex');
|
|
888
|
-
if (!codexPath) {
|
|
889
|
-
input.write({
|
|
890
|
-
type: 'error',
|
|
891
|
-
message: 'codex executable was not found in the daemon tool lookup PATH',
|
|
892
|
-
});
|
|
893
|
-
return () => { };
|
|
894
|
-
}
|
|
895
|
-
const child = spawn(codexPath, ['app-server', '--listen', 'stdio://'], {
|
|
896
|
-
cwd: input.cwd,
|
|
897
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
898
|
-
env: withRuntimeSpaceEnv(buildToolLookupEnv()),
|
|
899
|
-
});
|
|
900
|
-
let buffer = '';
|
|
901
|
-
let nextId = 1;
|
|
902
|
-
let observerEventIndex = 0;
|
|
903
|
-
let initialized = false;
|
|
904
|
-
let closed = false;
|
|
905
|
-
child.on('error', (err) => {
|
|
906
|
-
if (closed)
|
|
907
|
-
return;
|
|
908
|
-
input.write({
|
|
909
|
-
type: 'error',
|
|
910
|
-
message: err instanceof Error ? err.message : String(err),
|
|
911
|
-
});
|
|
912
|
-
});
|
|
913
|
-
child.on('close', () => {
|
|
914
|
-
closed = true;
|
|
915
|
-
});
|
|
916
|
-
child.stdin.on('error', (error) => {
|
|
917
|
-
closed = true;
|
|
918
|
-
if (error?.code === 'EPIPE' || error?.code === 'ECONNRESET')
|
|
919
|
-
return;
|
|
920
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer stdin failed');
|
|
921
|
-
});
|
|
922
|
-
const closeObserver = (signal = 'SIGTERM') => {
|
|
923
|
-
if (closed)
|
|
924
|
-
return;
|
|
925
|
-
closed = true;
|
|
926
|
-
try {
|
|
927
|
-
child.stdin.destroy();
|
|
928
|
-
child.kill(signal);
|
|
929
|
-
}
|
|
930
|
-
catch {
|
|
931
|
-
// Observer cleanup is best-effort; the HTTP stream is already closing.
|
|
932
|
-
}
|
|
933
|
-
};
|
|
934
|
-
const send = (message) => {
|
|
935
|
-
if (closed || child.stdin.destroyed || !child.stdin.writable)
|
|
936
|
-
return;
|
|
937
|
-
try {
|
|
938
|
-
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
939
|
-
}
|
|
940
|
-
catch (error) {
|
|
941
|
-
closed = true;
|
|
942
|
-
const code = error?.code;
|
|
943
|
-
if (code === 'EPIPE' || code === 'ECONNRESET')
|
|
944
|
-
return;
|
|
945
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer write failed');
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
const request = (method, params) => {
|
|
949
|
-
send({ id: nextId++, method, ...(params ? { params } : {}) });
|
|
950
|
-
};
|
|
951
|
-
child.stdout.on('data', (chunk) => {
|
|
952
|
-
if (closed)
|
|
953
|
-
return;
|
|
954
|
-
buffer += String(chunk);
|
|
955
|
-
if (buffer.length > CODEX_THREAD_OBSERVER_STDOUT_MAX_BYTES) {
|
|
956
|
-
logger.debug({ threadId: input.threadId, bytes: buffer.length }, 'Codex thread observer stdout exceeded line buffer limit');
|
|
957
|
-
closeObserver('SIGKILL');
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
while (true) {
|
|
961
|
-
const newlineIndex = buffer.indexOf('\n');
|
|
962
|
-
if (newlineIndex < 0)
|
|
963
|
-
break;
|
|
964
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
965
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
966
|
-
if (!line.startsWith('{'))
|
|
967
|
-
continue;
|
|
968
|
-
let parsed;
|
|
969
|
-
try {
|
|
970
|
-
parsed = JSON.parse(line);
|
|
971
|
-
}
|
|
972
|
-
catch {
|
|
973
|
-
continue;
|
|
974
|
-
}
|
|
975
|
-
if (parsed.id !== undefined && typeof parsed.method === 'string') {
|
|
976
|
-
send({ id: parsed.id, result: {} });
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
if (parsed.id !== undefined) {
|
|
980
|
-
if (!initialized && parsed.id === 1) {
|
|
981
|
-
initialized = true;
|
|
982
|
-
send({ method: 'initialized' });
|
|
983
|
-
request('thread/resume', {
|
|
984
|
-
threadId: input.threadId,
|
|
985
|
-
cwd: input.cwd,
|
|
986
|
-
experimentalRawEvents: true,
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
continue;
|
|
990
|
-
}
|
|
991
|
-
const method = typeof parsed.method === 'string' ? parsed.method : '';
|
|
992
|
-
if (!method)
|
|
993
|
-
continue;
|
|
994
|
-
const params = parsed.params && typeof parsed.params === 'object'
|
|
995
|
-
? parsed.params
|
|
996
|
-
: {};
|
|
997
|
-
const eventThreadId = typeof params.threadId === 'string' ? params.threadId : '';
|
|
998
|
-
if (eventThreadId !== input.threadId)
|
|
999
|
-
continue;
|
|
1000
|
-
observerEventIndex += 1;
|
|
1001
|
-
input.write(codexAppServerEventChunk(method, params), `observer-${Date.now()}-${observerEventIndex}`);
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
child.stderr.on('data', () => { });
|
|
1005
|
-
child.once('error', (error) => {
|
|
1006
|
-
logger.debug({ err: error, threadId: input.threadId }, 'Codex thread observer failed');
|
|
1007
|
-
});
|
|
1008
|
-
request('initialize', {
|
|
1009
|
-
clientInfo: {
|
|
1010
|
-
name: 'supen-daemon-thread-observer',
|
|
1011
|
-
title: 'Supen',
|
|
1012
|
-
version: '0.1.0',
|
|
1013
|
-
},
|
|
1014
|
-
capabilities: { experimentalApi: true },
|
|
1015
|
-
});
|
|
1016
|
-
return () => {
|
|
1017
|
-
closeObserver();
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
885
|
function normalizeThreadStatusToken(value) {
|
|
1021
886
|
return typeof value === 'string'
|
|
1022
887
|
? value.trim().toLowerCase().replace(/[-_\s]/g, '')
|
|
@@ -1465,6 +1330,23 @@ function isMirroredGoalContextMessage(text) {
|
|
|
1465
1330
|
const trimmed = text.trim();
|
|
1466
1331
|
return trimmed.startsWith('<goal_context>') && trimmed.endsWith('</goal_context>');
|
|
1467
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
|
+
}
|
|
1468
1350
|
function mirroredThreadWorkspacePath(threadPath, stateEntry) {
|
|
1469
1351
|
const stateCwd = typeof stateEntry?.cwd === 'string' ? stateEntry.cwd : null;
|
|
1470
1352
|
const threadWorkspace = readThreadWorkspacePath(threadPath);
|
|
@@ -1737,6 +1619,22 @@ export function readCodexThreadHistory(threadId, limit = MIRRORED_THREAD_HISTORY
|
|
|
1737
1619
|
chunk,
|
|
1738
1620
|
});
|
|
1739
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
|
+
}
|
|
1740
1638
|
const collapsedMessages = collapseCompletedAssistantProgressMessages(messages);
|
|
1741
1639
|
const slicedMessages = collapsedMessages.slice(-safeLimit);
|
|
1742
1640
|
const slicedEvents = events.slice(-safeLimit * 8);
|
|
@@ -3202,7 +3100,6 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3202
3100
|
const workspacePath = readThreadWorkspacePath(threadPath) || process.cwd();
|
|
3203
3101
|
const withThreadContext = (chunk) => attachThreadContextToChunk(chunk, readThreadContext({ threadId, workspace: workspacePath }));
|
|
3204
3102
|
let streamClosed = false;
|
|
3205
|
-
let stopCodexObserver = () => { };
|
|
3206
3103
|
let pingTimer = null;
|
|
3207
3104
|
let jsonlPollTimer = null;
|
|
3208
3105
|
const cleanup = () => {
|
|
@@ -3213,22 +3110,8 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3213
3110
|
clearInterval(pingTimer);
|
|
3214
3111
|
if (jsonlPollTimer)
|
|
3215
3112
|
clearInterval(jsonlPollTimer);
|
|
3216
|
-
stopCodexObserver();
|
|
3217
3113
|
removeThreadStreamClient(threadId, res);
|
|
3218
3114
|
};
|
|
3219
|
-
stopCodexObserver = process.env.NODE_ENV === 'test'
|
|
3220
|
-
? () => { }
|
|
3221
|
-
: startCodexThreadObserver({
|
|
3222
|
-
threadId,
|
|
3223
|
-
cwd: workspacePath,
|
|
3224
|
-
write: (chunk, id) => {
|
|
3225
|
-
if (streamClosed)
|
|
3226
|
-
return;
|
|
3227
|
-
const wrote = writeThreadStreamEvent(res, withThreadContext(chunk), id ? { id } : {});
|
|
3228
|
-
if (!wrote)
|
|
3229
|
-
cleanup();
|
|
3230
|
-
},
|
|
3231
|
-
});
|
|
3232
3115
|
pingTimer = setInterval(() => {
|
|
3233
3116
|
if (!writeSse(res)) {
|
|
3234
3117
|
cleanup();
|
|
@@ -3323,6 +3206,52 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
|
|
|
3323
3206
|
writeJson(res, 200, { thread: serializeAdoptedMirroredThread(thread) });
|
|
3324
3207
|
return true;
|
|
3325
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
|
+
}
|
|
3326
3255
|
if (codexProjectsOpenRoute(pathname) && method === 'POST') {
|
|
3327
3256
|
try {
|
|
3328
3257
|
const parsed = await readJsonBody(req);
|