@junctionpanel/server 0.1.62 → 0.1.64
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/dist/server/client/daemon-client-transport-utils.d.ts.map +1 -1
- package/dist/server/client/daemon-client-transport-utils.js +8 -6
- package/dist/server/client/daemon-client-transport-utils.js.map +1 -1
- package/dist/server/client/daemon-client.d.ts +24 -0
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +232 -2
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +5 -1
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +18 -2
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +3 -0
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +13 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +3 -0
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +1 -0
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +2 -0
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +27 -11
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +1 -0
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +59 -0
- package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +31 -0
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +1022 -50
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.js +1 -0
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +95 -14
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +22 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +10 -0
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +4 -4
- package/dist/server/server/session.d.ts +26 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +439 -5
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/voice-config.d.ts +10 -0
- package/dist/server/server/voice-config.d.ts.map +1 -0
- package/dist/server/server/voice-config.js +44 -0
- package/dist/server/server/voice-config.js.map +1 -0
- package/dist/server/server/websocket-server.d.ts +26 -2
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +133 -41
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/shared/binary-mux.d.ts.map +1 -1
- package/dist/server/shared/binary-mux.js +3 -2
- package/dist/server/shared/binary-mux.js.map +1 -1
- package/dist/server/shared/messages.d.ts +8394 -2300
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +135 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +58 -10
- package/dist/server/terminal/terminal.js.map +1 -1
- package/package.json +3 -3
|
@@ -58,6 +58,9 @@ const WORKSPACE_STATUS_WATCH_DEBOUNCE_MS = 250;
|
|
|
58
58
|
const WORKSPACE_STATUS_GIT_REFRESH_MS = 3000;
|
|
59
59
|
const WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS = 15000;
|
|
60
60
|
const WORKSPACE_STATUS_PR_PASSIVE_REFRESH_MS = 60000;
|
|
61
|
+
const PROVIDER_CHILD_THREADS_REFRESH_DEBOUNCE_MS = 250;
|
|
62
|
+
const PROVIDER_CHILD_THREADS_PERSISTED_LIST_INITIAL_LIMIT = 500;
|
|
63
|
+
const PROVIDER_CHILD_THREADS_PERSISTED_LIST_MAX_LIMIT = 50000;
|
|
61
64
|
const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
|
|
62
65
|
const TERMINAL_STREAM_MAX_PENDING_BYTES = 2 * 1024 * 1024;
|
|
63
66
|
const TERMINAL_STREAM_MAX_PENDING_CHUNKS = 2048;
|
|
@@ -168,6 +171,12 @@ export class Session {
|
|
|
168
171
|
this.checkoutDiffSubscriptions = new Map();
|
|
169
172
|
this.checkoutDiffTargets = new Map();
|
|
170
173
|
this.workspaceGitOperationStates = sharedWorkspaceGitOperationStates;
|
|
174
|
+
this.providerChildThreadEntries = [];
|
|
175
|
+
this.providerChildThreadEntriesFingerprint = null;
|
|
176
|
+
this.providerChildThreadEntriesHydrated = false;
|
|
177
|
+
this.providerChildThreadRefreshTimer = null;
|
|
178
|
+
this.providerChildThreadRefreshPromise = null;
|
|
179
|
+
this.providerChildThreadRefreshQueued = false;
|
|
171
180
|
const { clientId, userId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, junctionHome, agentManager, agentStorage, createAgentMcpTransport, terminalManager, agentProviderRuntimeSettings, connectionContext, } = options;
|
|
172
181
|
this.clientId = clientId;
|
|
173
182
|
this.userId = userId;
|
|
@@ -393,8 +402,14 @@ export class Session {
|
|
|
393
402
|
this.unsubscribeAgentEvents = this.agentManager.subscribe((event) => {
|
|
394
403
|
if (event.type === 'agent_state') {
|
|
395
404
|
void this.forwardAgentUpdate(event.agent);
|
|
405
|
+
if (event.agent.provider === 'codex') {
|
|
406
|
+
this.scheduleProviderChildThreadRefresh();
|
|
407
|
+
}
|
|
396
408
|
return;
|
|
397
409
|
}
|
|
410
|
+
if (this.shouldRefreshProviderChildThreadsForStreamEvent(event.event)) {
|
|
411
|
+
this.scheduleProviderChildThreadRefresh();
|
|
412
|
+
}
|
|
398
413
|
// Reduce bandwidth/CPU on mobile: only forward high-frequency agent stream events
|
|
399
414
|
// for the focused agent, with a short grace window while backgrounded.
|
|
400
415
|
// History catch-up is handled via pull-based `fetch_agent_timeline_request`.
|
|
@@ -597,12 +612,16 @@ export class Session {
|
|
|
597
612
|
snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, {
|
|
598
613
|
...extractTimestamps(record),
|
|
599
614
|
...extractTimelineSnapshot(record),
|
|
615
|
+
parentAgentId: record.parentAgentId ?? null,
|
|
600
616
|
});
|
|
601
617
|
this.sessionLogger.info({ agentId, provider: record.provider }, 'Agent resumed from persistence');
|
|
602
618
|
}
|
|
603
619
|
else {
|
|
604
620
|
const config = buildSessionConfig(record);
|
|
605
|
-
snapshot = await this.agentManager.createAgent(config, agentId, {
|
|
621
|
+
snapshot = await this.agentManager.createAgent(config, agentId, {
|
|
622
|
+
labels: record.labels,
|
|
623
|
+
parentAgentId: record.parentAgentId ?? null,
|
|
624
|
+
});
|
|
606
625
|
this.sessionLogger.info({ agentId, provider: record.provider }, 'Agent created from stored config');
|
|
607
626
|
}
|
|
608
627
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
@@ -788,6 +807,112 @@ export class Session {
|
|
|
788
807
|
checkout,
|
|
789
808
|
};
|
|
790
809
|
}
|
|
810
|
+
shouldRefreshProviderChildThreadsForStreamEvent(event) {
|
|
811
|
+
if (event.provider !== 'codex') {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
if (event.type === 'turn_completed') {
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
return (event.type === 'timeline' &&
|
|
818
|
+
event.item.type === 'tool_call' &&
|
|
819
|
+
event.item.name === 'spawn_agent' &&
|
|
820
|
+
event.item.detail.type === 'sub_agent');
|
|
821
|
+
}
|
|
822
|
+
serializeProviderChildThreadEntries(entries) {
|
|
823
|
+
return JSON.stringify(entries.map(({ thread, project }) => ({
|
|
824
|
+
id: thread.id,
|
|
825
|
+
threadId: thread.threadId,
|
|
826
|
+
parentThreadId: thread.parentThreadId ?? null,
|
|
827
|
+
rootThreadId: thread.rootThreadId ?? null,
|
|
828
|
+
parentAgentId: thread.parentAgentId,
|
|
829
|
+
cwd: thread.cwd,
|
|
830
|
+
title: thread.title,
|
|
831
|
+
status: thread.status,
|
|
832
|
+
model: thread.model ?? null,
|
|
833
|
+
createdAt: thread.createdAt,
|
|
834
|
+
updatedAt: thread.updatedAt,
|
|
835
|
+
persistence: {
|
|
836
|
+
provider: thread.persistence?.provider ?? null,
|
|
837
|
+
sessionId: thread.persistence?.sessionId ?? null,
|
|
838
|
+
nativeHandle: thread.persistence?.nativeHandle ?? null,
|
|
839
|
+
},
|
|
840
|
+
agentId: thread.agentId ?? null,
|
|
841
|
+
agentNickname: thread.agentNickname ?? null,
|
|
842
|
+
agentRole: thread.agentRole ?? null,
|
|
843
|
+
projectKey: project.projectKey,
|
|
844
|
+
projectName: project.projectName,
|
|
845
|
+
checkout: {
|
|
846
|
+
cwd: project.checkout.cwd,
|
|
847
|
+
isGit: project.checkout.isGit,
|
|
848
|
+
currentBranch: project.checkout.currentBranch,
|
|
849
|
+
remoteUrl: project.checkout.remoteUrl,
|
|
850
|
+
isJunctionOwnedWorktree: project.checkout.isJunctionOwnedWorktree,
|
|
851
|
+
mainRepoRoot: project.checkout.mainRepoRoot,
|
|
852
|
+
},
|
|
853
|
+
})));
|
|
854
|
+
}
|
|
855
|
+
emitProviderChildThreadsChanged(payload) {
|
|
856
|
+
this.emit({
|
|
857
|
+
type: 'provider_child_threads_changed',
|
|
858
|
+
payload,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
setProviderChildThreadEntries(entries, options) {
|
|
862
|
+
const fingerprint = this.serializeProviderChildThreadEntries(entries);
|
|
863
|
+
this.providerChildThreadEntriesHydrated = true;
|
|
864
|
+
if (this.providerChildThreadEntriesFingerprint === fingerprint) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
this.providerChildThreadEntries = entries;
|
|
868
|
+
this.providerChildThreadEntriesFingerprint = fingerprint;
|
|
869
|
+
if (options?.emit) {
|
|
870
|
+
this.emitProviderChildThreadsChanged({ entries });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async refreshProviderChildThreadEntries(options) {
|
|
874
|
+
if (this.providerChildThreadRefreshPromise) {
|
|
875
|
+
if (options?.emit) {
|
|
876
|
+
this.providerChildThreadRefreshQueued = true;
|
|
877
|
+
}
|
|
878
|
+
await this.providerChildThreadRefreshPromise;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const refreshPromise = (async () => {
|
|
882
|
+
const entries = await this.listProviderChildThreadEntries();
|
|
883
|
+
this.setProviderChildThreadEntries(entries, { emit: options?.emit });
|
|
884
|
+
})();
|
|
885
|
+
this.providerChildThreadRefreshPromise = refreshPromise;
|
|
886
|
+
try {
|
|
887
|
+
await refreshPromise;
|
|
888
|
+
}
|
|
889
|
+
finally {
|
|
890
|
+
if (this.providerChildThreadRefreshPromise === refreshPromise) {
|
|
891
|
+
this.providerChildThreadRefreshPromise = null;
|
|
892
|
+
}
|
|
893
|
+
if (this.providerChildThreadRefreshQueued) {
|
|
894
|
+
this.providerChildThreadRefreshQueued = false;
|
|
895
|
+
await this.refreshProviderChildThreadEntries({ emit: true });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
scheduleProviderChildThreadRefresh() {
|
|
900
|
+
if (this.providerChildThreadRefreshTimer) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
this.providerChildThreadRefreshTimer = setTimeout(() => {
|
|
904
|
+
this.providerChildThreadRefreshTimer = null;
|
|
905
|
+
void this.refreshProviderChildThreadEntries({ emit: true }).catch((error) => {
|
|
906
|
+
this.sessionLogger.debug({ err: error }, 'Failed to refresh provider child thread cache');
|
|
907
|
+
});
|
|
908
|
+
}, PROVIDER_CHILD_THREADS_REFRESH_DEBOUNCE_MS);
|
|
909
|
+
}
|
|
910
|
+
async ensureProviderChildThreadEntriesHydrated() {
|
|
911
|
+
if (this.providerChildThreadEntriesHydrated) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
await this.refreshProviderChildThreadEntries({ emit: false });
|
|
915
|
+
}
|
|
791
916
|
async forwardAgentUpdate(agent) {
|
|
792
917
|
try {
|
|
793
918
|
const subscription = this.agentUpdatesSubscription;
|
|
@@ -820,6 +945,9 @@ export class Session {
|
|
|
820
945
|
}
|
|
821
946
|
async forwardStoredAgentRecordUpdate(record) {
|
|
822
947
|
try {
|
|
948
|
+
if (record.provider === 'codex') {
|
|
949
|
+
this.scheduleProviderChildThreadRefresh();
|
|
950
|
+
}
|
|
823
951
|
const subscription = this.agentUpdatesSubscription;
|
|
824
952
|
if (!subscription || record.internal) {
|
|
825
953
|
return;
|
|
@@ -863,6 +991,12 @@ export class Session {
|
|
|
863
991
|
case 'fetch_agent_request':
|
|
864
992
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
|
865
993
|
break;
|
|
994
|
+
case 'fetch_provider_child_threads_request':
|
|
995
|
+
await this.handleFetchProviderChildThreads(msg);
|
|
996
|
+
break;
|
|
997
|
+
case 'fetch_provider_thread_timeline_request':
|
|
998
|
+
await this.handleFetchProviderThreadTimeline(msg);
|
|
999
|
+
break;
|
|
866
1000
|
case 'delete_agent_request':
|
|
867
1001
|
await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
|
|
868
1002
|
break;
|
|
@@ -1251,6 +1385,9 @@ export class Session {
|
|
|
1251
1385
|
}
|
|
1252
1386
|
async handleDeleteAgentRequest(agentId, requestId) {
|
|
1253
1387
|
this.sessionLogger.info({ agentId }, `Deleting agent ${agentId} from registry`);
|
|
1388
|
+
const storedRecord = await this.agentStorage.get(agentId);
|
|
1389
|
+
const liveAgent = this.agentManager.getAgent(agentId);
|
|
1390
|
+
const shouldRefreshProviderChildThreads = storedRecord?.provider === 'codex' || liveAgent?.provider === 'codex';
|
|
1254
1391
|
// Prevent the persistence hook from re-creating the record while we close/delete.
|
|
1255
1392
|
this.agentStorage.beginDelete(agentId);
|
|
1256
1393
|
try {
|
|
@@ -1278,6 +1415,9 @@ export class Session {
|
|
|
1278
1415
|
agentId,
|
|
1279
1416
|
});
|
|
1280
1417
|
}
|
|
1418
|
+
if (shouldRefreshProviderChildThreads) {
|
|
1419
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1420
|
+
}
|
|
1281
1421
|
}
|
|
1282
1422
|
async handleArchiveAgentRequest(agentId, requestId, dirtyWorktreeBehavior) {
|
|
1283
1423
|
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
@@ -1316,6 +1456,9 @@ export class Session {
|
|
|
1316
1456
|
archivedWorktree: await this.buildArchivedWorktreeState(archivedRecord.cwd, archivedAt),
|
|
1317
1457
|
};
|
|
1318
1458
|
await this.agentStorage.upsert(archivedRecord);
|
|
1459
|
+
if (archivedRecord.provider === 'codex') {
|
|
1460
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1461
|
+
}
|
|
1319
1462
|
if (liveAgent) {
|
|
1320
1463
|
this.agentManager.notifyAgentState(agentId);
|
|
1321
1464
|
}
|
|
@@ -1604,7 +1747,7 @@ export class Session {
|
|
|
1604
1747
|
* Handle create agent request
|
|
1605
1748
|
*/
|
|
1606
1749
|
async handleCreateAgentRequest(msg) {
|
|
1607
|
-
const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, initialRunOptions, git, bootstrapSetupOverride, generalPreferencesApplied, images, labels, } = msg;
|
|
1750
|
+
const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, initialRunOptions, git, bootstrapSetupOverride, generalPreferencesApplied, images, labels, parentAgentId, } = msg;
|
|
1608
1751
|
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ''}`);
|
|
1609
1752
|
const trimmedPrompt = initialPrompt?.trim() ?? '';
|
|
1610
1753
|
const hasInitialMessage = trimmedPrompt.length > 0 || Boolean(images?.length);
|
|
@@ -1618,10 +1761,36 @@ export class Session {
|
|
|
1618
1761
|
const mergedLabels = autoWorkspaceName
|
|
1619
1762
|
? { ...labels, 'junction:workspace': autoWorkspaceName }
|
|
1620
1763
|
: labels;
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1764
|
+
const resumeHandle = toAgentPersistenceHandle(this.sessionLogger, initialRunOptions?.resumeFrom ?? null);
|
|
1765
|
+
const agentLabels = applyNotificationRelayOwnerLabel(mergedLabels, this.userId);
|
|
1766
|
+
const existingLiveAgent = resumeHandle
|
|
1767
|
+
? this.findLiveAgentByPersistenceHandle({
|
|
1768
|
+
handle: resumeHandle,
|
|
1769
|
+
parentAgentId: parentAgentId ?? null,
|
|
1770
|
+
})
|
|
1771
|
+
: null;
|
|
1772
|
+
let snapshot = existingLiveAgent
|
|
1773
|
+
? existingLiveAgent
|
|
1774
|
+
: resumeHandle
|
|
1775
|
+
? await this.agentManager.resumeAgentFromPersistence(resumeHandle, sessionConfig, undefined, {
|
|
1776
|
+
labels: agentLabels,
|
|
1777
|
+
parentAgentId: parentAgentId ?? null,
|
|
1778
|
+
})
|
|
1779
|
+
: await this.agentManager.createAgent(sessionConfig, undefined, {
|
|
1780
|
+
labels: agentLabels,
|
|
1781
|
+
parentAgentId: parentAgentId ?? null,
|
|
1782
|
+
});
|
|
1783
|
+
if (resumeHandle) {
|
|
1784
|
+
if (existingLiveAgent && sessionConfig.title?.trim()) {
|
|
1785
|
+
await this.agentManager.setTitle(existingLiveAgent.id, sessionConfig.title.trim());
|
|
1786
|
+
}
|
|
1787
|
+
await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
|
|
1788
|
+
snapshot = this.agentManager.getAgent(snapshot.id) ?? snapshot;
|
|
1789
|
+
}
|
|
1624
1790
|
await this.forwardAgentUpdate(snapshot);
|
|
1791
|
+
if (snapshot.provider === 'codex') {
|
|
1792
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1793
|
+
}
|
|
1625
1794
|
const runInitialPrompt = async () => {
|
|
1626
1795
|
if (!hasInitialMessage) {
|
|
1627
1796
|
return;
|
|
@@ -1791,6 +1960,9 @@ export class Session {
|
|
|
1791
1960
|
const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
|
|
1792
1961
|
await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
|
|
1793
1962
|
await this.forwardAgentUpdate(snapshot);
|
|
1963
|
+
if (snapshot.provider === 'codex') {
|
|
1964
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1965
|
+
}
|
|
1794
1966
|
const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
|
|
1795
1967
|
if (requestId) {
|
|
1796
1968
|
const agentPayload = await this.getAgentPayloadById(snapshot.id);
|
|
@@ -1849,10 +2021,14 @@ export class Session {
|
|
|
1849
2021
|
snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, {
|
|
1850
2022
|
...extractTimestamps(record),
|
|
1851
2023
|
...extractTimelineSnapshot(record),
|
|
2024
|
+
parentAgentId: record.parentAgentId ?? null,
|
|
1852
2025
|
});
|
|
1853
2026
|
}
|
|
1854
2027
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
1855
2028
|
await this.forwardAgentUpdate(snapshot);
|
|
2029
|
+
if (snapshot.provider === 'codex') {
|
|
2030
|
+
this.scheduleProviderChildThreadRefresh();
|
|
2031
|
+
}
|
|
1856
2032
|
const timelineSize = this.agentManager.getTimeline(agentId).length;
|
|
1857
2033
|
if (requestId) {
|
|
1858
2034
|
this.emit({
|
|
@@ -5429,6 +5605,260 @@ export class Session {
|
|
|
5429
5605
|
});
|
|
5430
5606
|
}
|
|
5431
5607
|
}
|
|
5608
|
+
resolveAgentSessionId(agent) {
|
|
5609
|
+
return agent.runtimeInfo?.sessionId ?? agent.persistence?.sessionId ?? null;
|
|
5610
|
+
}
|
|
5611
|
+
findLiveAgentByPersistenceHandle(input) {
|
|
5612
|
+
const targetSessionId = input.handle.sessionId.trim();
|
|
5613
|
+
if (!targetSessionId) {
|
|
5614
|
+
return null;
|
|
5615
|
+
}
|
|
5616
|
+
for (const agent of this.agentManager.listAgents()) {
|
|
5617
|
+
if (agent.provider !== input.handle.provider) {
|
|
5618
|
+
continue;
|
|
5619
|
+
}
|
|
5620
|
+
if (input.parentAgentId !== undefined
|
|
5621
|
+
&& (agent.parentAgentId ?? null) !== (input.parentAgentId ?? null)) {
|
|
5622
|
+
continue;
|
|
5623
|
+
}
|
|
5624
|
+
const agentSessionId = agent.persistence?.sessionId ?? agent.runtimeInfo?.sessionId ?? null;
|
|
5625
|
+
if (agentSessionId === targetSessionId) {
|
|
5626
|
+
return agent;
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
return null;
|
|
5630
|
+
}
|
|
5631
|
+
buildProviderChildThreadId(input) {
|
|
5632
|
+
return `${input.provider}:thread:${input.threadId}`;
|
|
5633
|
+
}
|
|
5634
|
+
resolveProviderChildThreadStatus(input) {
|
|
5635
|
+
return input.parentAgent.status === 'running' ? 'running' : 'idle';
|
|
5636
|
+
}
|
|
5637
|
+
buildProviderThreadTimelineEpoch(threadId) {
|
|
5638
|
+
return `provider-thread:${threadId}`;
|
|
5639
|
+
}
|
|
5640
|
+
normalizeTimelineItemForInheritedPrefixComparison(item) {
|
|
5641
|
+
if (item.type !== 'tool_call') {
|
|
5642
|
+
return item;
|
|
5643
|
+
}
|
|
5644
|
+
if (item.name === 'spawn_agent' && item.detail.type === 'sub_agent') {
|
|
5645
|
+
return {
|
|
5646
|
+
type: item.type,
|
|
5647
|
+
name: item.name,
|
|
5648
|
+
detail: {
|
|
5649
|
+
type: item.detail.type,
|
|
5650
|
+
subAgentType: item.detail.subAgentType ?? null,
|
|
5651
|
+
description: item.detail.description ?? null,
|
|
5652
|
+
},
|
|
5653
|
+
};
|
|
5654
|
+
}
|
|
5655
|
+
return item;
|
|
5656
|
+
}
|
|
5657
|
+
serializeTimelineItemFingerprint(item) {
|
|
5658
|
+
return JSON.stringify(this.normalizeTimelineItemForInheritedPrefixComparison(item));
|
|
5659
|
+
}
|
|
5660
|
+
countInheritedTimelinePrefix(parentTimeline, childTimeline) {
|
|
5661
|
+
let index = 0;
|
|
5662
|
+
while (index < parentTimeline.length && index < childTimeline.length) {
|
|
5663
|
+
if (this.serializeTimelineItemFingerprint(parentTimeline[index])
|
|
5664
|
+
!== this.serializeTimelineItemFingerprint(childTimeline[index])) {
|
|
5665
|
+
break;
|
|
5666
|
+
}
|
|
5667
|
+
index += 1;
|
|
5668
|
+
}
|
|
5669
|
+
return index;
|
|
5670
|
+
}
|
|
5671
|
+
buildProviderThreadTimelineRows(input) {
|
|
5672
|
+
const createdAtMs = input.createdAt.getTime();
|
|
5673
|
+
return input.timeline.map((item, index) => ({
|
|
5674
|
+
seq: index,
|
|
5675
|
+
timestamp: new Date(createdAtMs + index).toISOString(),
|
|
5676
|
+
item,
|
|
5677
|
+
}));
|
|
5678
|
+
}
|
|
5679
|
+
async listPersistedProviderChildThreadCandidates() {
|
|
5680
|
+
let limit = PROVIDER_CHILD_THREADS_PERSISTED_LIST_INITIAL_LIMIT;
|
|
5681
|
+
let previousCount = -1;
|
|
5682
|
+
while (true) {
|
|
5683
|
+
const descriptors = await this.agentManager.listPersistedAgents({
|
|
5684
|
+
provider: 'codex',
|
|
5685
|
+
limit,
|
|
5686
|
+
includeTimeline: false,
|
|
5687
|
+
});
|
|
5688
|
+
if (descriptors.length < limit) {
|
|
5689
|
+
return descriptors;
|
|
5690
|
+
}
|
|
5691
|
+
if (descriptors.length === previousCount || limit >= PROVIDER_CHILD_THREADS_PERSISTED_LIST_MAX_LIMIT) {
|
|
5692
|
+
return descriptors;
|
|
5693
|
+
}
|
|
5694
|
+
previousCount = descriptors.length;
|
|
5695
|
+
limit = Math.min(limit * 2, PROVIDER_CHILD_THREADS_PERSISTED_LIST_MAX_LIMIT);
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
async listProviderChildThreadEntries() {
|
|
5699
|
+
const agents = await this.listAgentPayloads();
|
|
5700
|
+
const parentAgentBySessionId = new Map();
|
|
5701
|
+
for (const agent of agents) {
|
|
5702
|
+
const sessionId = this.resolveAgentSessionId(agent);
|
|
5703
|
+
if (!sessionId) {
|
|
5704
|
+
continue;
|
|
5705
|
+
}
|
|
5706
|
+
if (!parentAgentBySessionId.has(sessionId)) {
|
|
5707
|
+
parentAgentBySessionId.set(sessionId, agent);
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
const placementByCwd = new Map();
|
|
5711
|
+
const getPlacement = (cwd) => {
|
|
5712
|
+
const existing = placementByCwd.get(cwd);
|
|
5713
|
+
if (existing) {
|
|
5714
|
+
return existing;
|
|
5715
|
+
}
|
|
5716
|
+
const next = this.buildProjectPlacement(cwd);
|
|
5717
|
+
placementByCwd.set(cwd, next);
|
|
5718
|
+
return next;
|
|
5719
|
+
};
|
|
5720
|
+
const persistedThreads = await this.listPersistedProviderChildThreadCandidates();
|
|
5721
|
+
const entries = [];
|
|
5722
|
+
const seenKeys = new Set();
|
|
5723
|
+
const existingEntryByThreadId = new Map(this.providerChildThreadEntries.map((entry) => [entry.thread.threadId, entry]));
|
|
5724
|
+
for (const descriptor of persistedThreads) {
|
|
5725
|
+
const threadId = descriptor.sessionId.trim();
|
|
5726
|
+
const parentSessionId = descriptor.parentSessionId?.trim() ?? '';
|
|
5727
|
+
if (!threadId || !parentSessionId) {
|
|
5728
|
+
continue;
|
|
5729
|
+
}
|
|
5730
|
+
const parentAgent = parentAgentBySessionId.get(parentSessionId);
|
|
5731
|
+
if (!parentAgent) {
|
|
5732
|
+
const existingEntry = existingEntryByThreadId.get(threadId);
|
|
5733
|
+
if (existingEntry) {
|
|
5734
|
+
entries.push(existingEntry);
|
|
5735
|
+
seenKeys.add(`codex:${threadId}`);
|
|
5736
|
+
}
|
|
5737
|
+
continue;
|
|
5738
|
+
}
|
|
5739
|
+
const dedupeKey = `codex:${threadId}`;
|
|
5740
|
+
if (seenKeys.has(dedupeKey)) {
|
|
5741
|
+
continue;
|
|
5742
|
+
}
|
|
5743
|
+
seenKeys.add(dedupeKey);
|
|
5744
|
+
entries.push({
|
|
5745
|
+
thread: {
|
|
5746
|
+
id: this.buildProviderChildThreadId({
|
|
5747
|
+
provider: 'codex',
|
|
5748
|
+
threadId,
|
|
5749
|
+
}),
|
|
5750
|
+
provider: 'codex',
|
|
5751
|
+
threadId,
|
|
5752
|
+
parentThreadId: descriptor.parentSessionId ?? null,
|
|
5753
|
+
rootThreadId: descriptor.rootSessionId ?? null,
|
|
5754
|
+
parentAgentId: parentAgent.id,
|
|
5755
|
+
cwd: descriptor.cwd,
|
|
5756
|
+
title: descriptor.title,
|
|
5757
|
+
status: this.resolveProviderChildThreadStatus({ parentAgent }),
|
|
5758
|
+
model: descriptor.model ?? null,
|
|
5759
|
+
createdAt: descriptor.createdAt.toISOString(),
|
|
5760
|
+
updatedAt: descriptor.lastActivityAt.toISOString(),
|
|
5761
|
+
persistence: descriptor.persistence,
|
|
5762
|
+
agentId: descriptor.agentId ?? null,
|
|
5763
|
+
agentNickname: descriptor.agentNickname ?? null,
|
|
5764
|
+
agentRole: descriptor.agentRole ?? null,
|
|
5765
|
+
},
|
|
5766
|
+
project: await getPlacement(descriptor.cwd),
|
|
5767
|
+
});
|
|
5768
|
+
}
|
|
5769
|
+
entries.sort((left, right) => new Date(right.thread.updatedAt).getTime() - new Date(left.thread.updatedAt).getTime());
|
|
5770
|
+
return entries;
|
|
5771
|
+
}
|
|
5772
|
+
async handleFetchProviderThreadTimeline(request) {
|
|
5773
|
+
const detailMode = request.detailMode ?? 'full';
|
|
5774
|
+
try {
|
|
5775
|
+
await this.ensureProviderChildThreadEntriesHydrated();
|
|
5776
|
+
const isKnownChildThread = this.providerChildThreadEntries.some((entry) => entry.thread.provider === 'codex' && entry.thread.threadId === request.threadId);
|
|
5777
|
+
if (!isKnownChildThread) {
|
|
5778
|
+
throw new SessionRequestError('provider_thread_not_found', `Provider thread not found: ${request.threadId}`);
|
|
5779
|
+
}
|
|
5780
|
+
const descriptor = await this.agentManager.readPersistedAgent('codex', {
|
|
5781
|
+
sessionId: request.threadId,
|
|
5782
|
+
includeTimeline: true,
|
|
5783
|
+
});
|
|
5784
|
+
if (!descriptor) {
|
|
5785
|
+
throw new SessionRequestError('provider_thread_not_found', `Provider thread not found: ${request.threadId}`);
|
|
5786
|
+
}
|
|
5787
|
+
let inheritedPrefixCount = 0;
|
|
5788
|
+
if (descriptor.parentSessionId) {
|
|
5789
|
+
const parentDescriptor = await this.agentManager.readPersistedAgent('codex', {
|
|
5790
|
+
sessionId: descriptor.parentSessionId,
|
|
5791
|
+
includeTimeline: true,
|
|
5792
|
+
});
|
|
5793
|
+
if (parentDescriptor) {
|
|
5794
|
+
inheritedPrefixCount = this.countInheritedTimelinePrefix(parentDescriptor.timeline, descriptor.timeline);
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
const timelineRows = this.buildProviderThreadTimelineRows({
|
|
5798
|
+
timeline: descriptor.timeline.slice(inheritedPrefixCount),
|
|
5799
|
+
createdAt: descriptor.createdAt,
|
|
5800
|
+
});
|
|
5801
|
+
const entries = projectTimelineRows(timelineRows, 'codex', 'projected').map((entry) => ({
|
|
5802
|
+
...entry,
|
|
5803
|
+
item: sanitizeTimelineItemForTransport(entry.item, detailMode),
|
|
5804
|
+
}));
|
|
5805
|
+
this.emit({
|
|
5806
|
+
type: 'fetch_provider_thread_timeline_response',
|
|
5807
|
+
payload: {
|
|
5808
|
+
requestId: request.requestId,
|
|
5809
|
+
provider: 'codex',
|
|
5810
|
+
threadId: descriptor.sessionId,
|
|
5811
|
+
epoch: this.buildProviderThreadTimelineEpoch(descriptor.sessionId),
|
|
5812
|
+
inheritedPrefixCount,
|
|
5813
|
+
entries,
|
|
5814
|
+
error: null,
|
|
5815
|
+
},
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
catch (error) {
|
|
5819
|
+
this.sessionLogger.error({ err: error, threadId: request.threadId }, 'Failed to handle fetch_provider_thread_timeline_request');
|
|
5820
|
+
this.emit({
|
|
5821
|
+
type: 'fetch_provider_thread_timeline_response',
|
|
5822
|
+
payload: {
|
|
5823
|
+
requestId: request.requestId,
|
|
5824
|
+
provider: request.provider,
|
|
5825
|
+
threadId: request.threadId,
|
|
5826
|
+
epoch: this.buildProviderThreadTimelineEpoch(request.threadId),
|
|
5827
|
+
inheritedPrefixCount: 0,
|
|
5828
|
+
entries: [],
|
|
5829
|
+
error: error instanceof Error ? error.message : 'Failed to fetch provider thread timeline',
|
|
5830
|
+
},
|
|
5831
|
+
});
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
async handleFetchProviderChildThreads(request) {
|
|
5835
|
+
try {
|
|
5836
|
+
await this.ensureProviderChildThreadEntriesHydrated();
|
|
5837
|
+
this.emit({
|
|
5838
|
+
type: 'fetch_provider_child_threads_response',
|
|
5839
|
+
payload: {
|
|
5840
|
+
requestId: request.requestId,
|
|
5841
|
+
entries: this.providerChildThreadEntries,
|
|
5842
|
+
},
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
catch (error) {
|
|
5846
|
+
const code = error instanceof SessionRequestError
|
|
5847
|
+
? error.code
|
|
5848
|
+
: 'fetch_provider_child_threads_failed';
|
|
5849
|
+
const message = error instanceof Error ? error.message : 'Failed to fetch provider child threads';
|
|
5850
|
+
this.sessionLogger.error({ err: error }, 'Failed to handle fetch_provider_child_threads_request');
|
|
5851
|
+
this.emit({
|
|
5852
|
+
type: 'rpc_error',
|
|
5853
|
+
payload: {
|
|
5854
|
+
requestId: request.requestId,
|
|
5855
|
+
requestType: request.type,
|
|
5856
|
+
error: message,
|
|
5857
|
+
code,
|
|
5858
|
+
},
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5432
5862
|
async handleFetchAgent(agentIdOrIdentifier, requestId) {
|
|
5433
5863
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
5434
5864
|
if (!resolved.ok) {
|
|
@@ -5823,6 +6253,10 @@ export class Session {
|
|
|
5823
6253
|
this.unsubscribeAgentEvents();
|
|
5824
6254
|
this.unsubscribeAgentEvents = null;
|
|
5825
6255
|
}
|
|
6256
|
+
if (this.providerChildThreadRefreshTimer) {
|
|
6257
|
+
clearTimeout(this.providerChildThreadRefreshTimer);
|
|
6258
|
+
this.providerChildThreadRefreshTimer = null;
|
|
6259
|
+
}
|
|
5826
6260
|
// Abort any ongoing operations
|
|
5827
6261
|
this.abortController.abort();
|
|
5828
6262
|
// Close MCP clients
|