@junctionpanel/server 0.1.61 → 0.1.63
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.d.ts +28 -1
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +236 -3
- 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/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 +593 -80
- 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 +24 -1
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +61 -2
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/shared/messages.d.ts +8398 -2298
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +136 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/package.json +3 -3
|
@@ -29,7 +29,7 @@ import { expandTilde } from '../utils/path.js';
|
|
|
29
29
|
import { searchHomeDirectories, searchWorkspaceEntries, searchWorkspaceEntriesAtGitRef, searchGitRepositories, checkIsGitRepo, } from '../utils/directory-suggestions.js';
|
|
30
30
|
import { cloneRepository } from '../utils/git-clone.js';
|
|
31
31
|
import { initRepository } from '../utils/git-init.js';
|
|
32
|
-
import { resolveClientMessageId } from './client-message-id.js';
|
|
32
|
+
import { normalizeClientMessageId, resolveClientMessageId } from './client-message-id.js';
|
|
33
33
|
import { deriveProjectGroupingKey, deriveProjectGroupingName } from '../shared/project-grouping.js';
|
|
34
34
|
import { DEFAULT_DAEMON_PACKAGE_NAME, resolveDaemonPackageVersion, } from './daemon-package-context.js';
|
|
35
35
|
import { runDaemonDoctor } from './daemon-doctor.js';
|
|
@@ -48,6 +48,8 @@ const READ_ONLY_GIT_ENV = {
|
|
|
48
48
|
const DEFAULT_STORED_TIMELINE_FETCH_LIMIT = 200;
|
|
49
49
|
const pendingAgentInitializations = new Map();
|
|
50
50
|
const pendingAgentMessageExecutions = new Map();
|
|
51
|
+
const CREATE_AGENT_REPLAY_WINDOW_MS = 30000;
|
|
52
|
+
const pendingCreateAgentRequests = new Map();
|
|
51
53
|
const sharedWorkspaceGitOperationStates = new Map();
|
|
52
54
|
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
|
53
55
|
const CHECKOUT_DIFF_WATCH_DEBOUNCE_MS = 150;
|
|
@@ -56,9 +58,36 @@ const WORKSPACE_STATUS_WATCH_DEBOUNCE_MS = 250;
|
|
|
56
58
|
const WORKSPACE_STATUS_GIT_REFRESH_MS = 3000;
|
|
57
59
|
const WORKSPACE_STATUS_PR_ACTIVE_REFRESH_MS = 15000;
|
|
58
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;
|
|
59
64
|
const TERMINAL_STREAM_WINDOW_BYTES = 256 * 1024;
|
|
60
65
|
const TERMINAL_STREAM_MAX_PENDING_BYTES = 2 * 1024 * 1024;
|
|
61
66
|
const TERMINAL_STREAM_MAX_PENDING_CHUNKS = 2048;
|
|
67
|
+
function buildCreateAgentReplayKey(clientId, clientMessageId) {
|
|
68
|
+
return JSON.stringify([clientId, clientMessageId]);
|
|
69
|
+
}
|
|
70
|
+
function deletePendingCreateAgentRequest(replayKey, expectedEntry) {
|
|
71
|
+
const existingEntry = pendingCreateAgentRequests.get(replayKey);
|
|
72
|
+
if (!existingEntry || (expectedEntry && existingEntry !== expectedEntry)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
clearTimeout(existingEntry.evictionTimer);
|
|
76
|
+
pendingCreateAgentRequests.delete(replayKey);
|
|
77
|
+
}
|
|
78
|
+
function storePendingCreateAgentRequest(replayKey, promise) {
|
|
79
|
+
const entry = {
|
|
80
|
+
promise,
|
|
81
|
+
evictionTimer: setTimeout(() => {
|
|
82
|
+
deletePendingCreateAgentRequest(replayKey, entry);
|
|
83
|
+
}, CREATE_AGENT_REPLAY_WINDOW_MS),
|
|
84
|
+
};
|
|
85
|
+
pendingCreateAgentRequests.set(replayKey, entry);
|
|
86
|
+
void promise.catch(() => {
|
|
87
|
+
deletePendingCreateAgentRequest(replayKey, entry);
|
|
88
|
+
});
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
62
91
|
const DIRTY_WORKTREE_CONFIRMATION_REQUIRED = 'dirty_worktree_confirmation_required';
|
|
63
92
|
class SessionRequestError extends Error {
|
|
64
93
|
constructor(code, message) {
|
|
@@ -142,6 +171,12 @@ export class Session {
|
|
|
142
171
|
this.checkoutDiffSubscriptions = new Map();
|
|
143
172
|
this.checkoutDiffTargets = new Map();
|
|
144
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;
|
|
145
180
|
const { clientId, userId, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, junctionHome, agentManager, agentStorage, createAgentMcpTransport, terminalManager, agentProviderRuntimeSettings, connectionContext, } = options;
|
|
146
181
|
this.clientId = clientId;
|
|
147
182
|
this.userId = userId;
|
|
@@ -367,8 +402,14 @@ export class Session {
|
|
|
367
402
|
this.unsubscribeAgentEvents = this.agentManager.subscribe((event) => {
|
|
368
403
|
if (event.type === 'agent_state') {
|
|
369
404
|
void this.forwardAgentUpdate(event.agent);
|
|
405
|
+
if (event.agent.provider === 'codex') {
|
|
406
|
+
this.scheduleProviderChildThreadRefresh();
|
|
407
|
+
}
|
|
370
408
|
return;
|
|
371
409
|
}
|
|
410
|
+
if (this.shouldRefreshProviderChildThreadsForStreamEvent(event.event)) {
|
|
411
|
+
this.scheduleProviderChildThreadRefresh();
|
|
412
|
+
}
|
|
372
413
|
// Reduce bandwidth/CPU on mobile: only forward high-frequency agent stream events
|
|
373
414
|
// for the focused agent, with a short grace window while backgrounded.
|
|
374
415
|
// History catch-up is handled via pull-based `fetch_agent_timeline_request`.
|
|
@@ -571,12 +612,16 @@ export class Session {
|
|
|
571
612
|
snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, {
|
|
572
613
|
...extractTimestamps(record),
|
|
573
614
|
...extractTimelineSnapshot(record),
|
|
615
|
+
parentAgentId: record.parentAgentId ?? null,
|
|
574
616
|
});
|
|
575
617
|
this.sessionLogger.info({ agentId, provider: record.provider }, 'Agent resumed from persistence');
|
|
576
618
|
}
|
|
577
619
|
else {
|
|
578
620
|
const config = buildSessionConfig(record);
|
|
579
|
-
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
|
+
});
|
|
580
625
|
this.sessionLogger.info({ agentId, provider: record.provider }, 'Agent created from stored config');
|
|
581
626
|
}
|
|
582
627
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
@@ -762,6 +807,112 @@ export class Session {
|
|
|
762
807
|
checkout,
|
|
763
808
|
};
|
|
764
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
|
+
}
|
|
765
916
|
async forwardAgentUpdate(agent) {
|
|
766
917
|
try {
|
|
767
918
|
const subscription = this.agentUpdatesSubscription;
|
|
@@ -794,6 +945,9 @@ export class Session {
|
|
|
794
945
|
}
|
|
795
946
|
async forwardStoredAgentRecordUpdate(record) {
|
|
796
947
|
try {
|
|
948
|
+
if (record.provider === 'codex') {
|
|
949
|
+
this.scheduleProviderChildThreadRefresh();
|
|
950
|
+
}
|
|
797
951
|
const subscription = this.agentUpdatesSubscription;
|
|
798
952
|
if (!subscription || record.internal) {
|
|
799
953
|
return;
|
|
@@ -837,6 +991,12 @@ export class Session {
|
|
|
837
991
|
case 'fetch_agent_request':
|
|
838
992
|
await this.handleFetchAgent(msg.agentId, msg.requestId);
|
|
839
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;
|
|
840
1000
|
case 'delete_agent_request':
|
|
841
1001
|
await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
|
|
842
1002
|
break;
|
|
@@ -1225,6 +1385,9 @@ export class Session {
|
|
|
1225
1385
|
}
|
|
1226
1386
|
async handleDeleteAgentRequest(agentId, requestId) {
|
|
1227
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';
|
|
1228
1391
|
// Prevent the persistence hook from re-creating the record while we close/delete.
|
|
1229
1392
|
this.agentStorage.beginDelete(agentId);
|
|
1230
1393
|
try {
|
|
@@ -1252,6 +1415,9 @@ export class Session {
|
|
|
1252
1415
|
agentId,
|
|
1253
1416
|
});
|
|
1254
1417
|
}
|
|
1418
|
+
if (shouldRefreshProviderChildThreads) {
|
|
1419
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1420
|
+
}
|
|
1255
1421
|
}
|
|
1256
1422
|
async handleArchiveAgentRequest(agentId, requestId, dirtyWorktreeBehavior) {
|
|
1257
1423
|
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
@@ -1290,6 +1456,9 @@ export class Session {
|
|
|
1290
1456
|
archivedWorktree: await this.buildArchivedWorktreeState(archivedRecord.cwd, archivedAt),
|
|
1291
1457
|
};
|
|
1292
1458
|
await this.agentStorage.upsert(archivedRecord);
|
|
1459
|
+
if (archivedRecord.provider === 'codex') {
|
|
1460
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1461
|
+
}
|
|
1293
1462
|
if (liveAgent) {
|
|
1294
1463
|
this.agentManager.notifyAgentState(agentId);
|
|
1295
1464
|
}
|
|
@@ -1557,7 +1726,7 @@ export class Session {
|
|
|
1557
1726
|
const archivedAt = await this.getArchivedAt(agentId);
|
|
1558
1727
|
if (archivedAt) {
|
|
1559
1728
|
this.handleAgentRunError(agentId, new Error(`Agent ${agentId} is archived`), 'Refusing to send prompt to archived agent');
|
|
1560
|
-
return;
|
|
1729
|
+
return false;
|
|
1561
1730
|
}
|
|
1562
1731
|
try {
|
|
1563
1732
|
await this.performAgentMessageSend({
|
|
@@ -1567,107 +1736,186 @@ export class Session {
|
|
|
1567
1736
|
images,
|
|
1568
1737
|
runOptions,
|
|
1569
1738
|
});
|
|
1739
|
+
return true;
|
|
1570
1740
|
}
|
|
1571
1741
|
catch (error) {
|
|
1572
1742
|
this.handleAgentRunError(agentId, error, 'Failed to send prompt to agent');
|
|
1743
|
+
return false;
|
|
1573
1744
|
}
|
|
1574
1745
|
}
|
|
1575
1746
|
/**
|
|
1576
1747
|
* Handle create agent request
|
|
1577
1748
|
*/
|
|
1578
1749
|
async handleCreateAgentRequest(msg) {
|
|
1579
|
-
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;
|
|
1580
1751
|
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ''}`);
|
|
1752
|
+
const trimmedPrompt = initialPrompt?.trim() ?? '';
|
|
1753
|
+
const hasInitialMessage = trimmedPrompt.length > 0 || Boolean(images?.length);
|
|
1754
|
+
const normalizedClientMessageId = hasInitialMessage ? normalizeClientMessageId(clientMessageId) : undefined;
|
|
1755
|
+
const createReplayKey = normalizedClientMessageId
|
|
1756
|
+
? buildCreateAgentReplayKey(this.clientId, normalizedClientMessageId)
|
|
1757
|
+
: null;
|
|
1581
1758
|
try {
|
|
1582
|
-
const
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1759
|
+
const createAgentLaunch = async (clearReplayEntryOnDeferredFailure) => {
|
|
1760
|
+
const { sessionConfig, worktreeConfig, autoWorkspaceName } = await this.buildAgentSessionConfig(config, git, worktreeName, labels);
|
|
1761
|
+
const mergedLabels = autoWorkspaceName
|
|
1762
|
+
? { ...labels, 'junction:workspace': autoWorkspaceName }
|
|
1763
|
+
: labels;
|
|
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;
|
|
1594
1789
|
}
|
|
1595
|
-
this.
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1790
|
+
await this.forwardAgentUpdate(snapshot);
|
|
1791
|
+
if (snapshot.provider === 'codex') {
|
|
1792
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1793
|
+
}
|
|
1794
|
+
const runInitialPrompt = async () => {
|
|
1795
|
+
if (!hasInitialMessage) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (trimmedPrompt.length > 0) {
|
|
1799
|
+
scheduleAgentMetadataGeneration({
|
|
1800
|
+
agentManager: this.agentManager,
|
|
1801
|
+
agentId: snapshot.id,
|
|
1802
|
+
cwd: snapshot.cwd,
|
|
1803
|
+
initialPrompt: trimmedPrompt,
|
|
1804
|
+
explicitTitle: snapshot.config.title,
|
|
1805
|
+
junctionHome: this.junctionHome,
|
|
1806
|
+
logger: this.sessionLogger,
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
const sendSucceeded = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, normalizedClientMessageId ?? resolveClientMessageId(clientMessageId), images, normalizeAgentRunOptions({
|
|
1810
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
1811
|
+
...(initialRunOptions ?? {}),
|
|
1812
|
+
}));
|
|
1813
|
+
if (!sendSucceeded) {
|
|
1814
|
+
clearReplayEntryOnDeferredFailure?.();
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
const handleInitialPromptError = (promptError) => {
|
|
1818
|
+
clearReplayEntryOnDeferredFailure?.();
|
|
1819
|
+
this.sessionLogger.error({ err: promptError, agentId: snapshot.id }, `Failed to run initial prompt for agent ${snapshot.id}`);
|
|
1820
|
+
this.emit({
|
|
1821
|
+
type: 'activity_log',
|
|
1822
|
+
payload: {
|
|
1823
|
+
id: uuidv4(),
|
|
1824
|
+
timestamp: new Date(),
|
|
1825
|
+
type: 'error',
|
|
1826
|
+
content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
|
|
1827
|
+
},
|
|
1828
|
+
});
|
|
1829
|
+
};
|
|
1830
|
+
if (hasInitialMessage && !worktreeConfig) {
|
|
1831
|
+
void runInitialPrompt().catch(handleInitialPromptError);
|
|
1610
1832
|
}
|
|
1611
|
-
if (
|
|
1612
|
-
|
|
1613
|
-
agentManager: this.agentManager,
|
|
1833
|
+
if (worktreeConfig) {
|
|
1834
|
+
void runAsyncWorktreeBootstrap({
|
|
1614
1835
|
agentId: snapshot.id,
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1836
|
+
worktree: worktreeConfig,
|
|
1837
|
+
setupOverride: bootstrapSetupOverride,
|
|
1838
|
+
generalPreferencesApplied,
|
|
1839
|
+
terminalManager: this.terminalManager,
|
|
1840
|
+
appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
|
|
1841
|
+
agentManager: this.agentManager,
|
|
1842
|
+
agentId: snapshot.id,
|
|
1843
|
+
item,
|
|
1844
|
+
}),
|
|
1845
|
+
emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
|
|
1846
|
+
agentManager: this.agentManager,
|
|
1847
|
+
agentId: snapshot.id,
|
|
1848
|
+
item,
|
|
1849
|
+
}),
|
|
1850
|
+
onSetupSettled: async (result) => {
|
|
1851
|
+
if (!hasInitialMessage || result.setupStatus === 'failed') {
|
|
1852
|
+
if (hasInitialMessage && result.setupStatus === 'failed') {
|
|
1853
|
+
clearReplayEntryOnDeferredFailure?.();
|
|
1854
|
+
}
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
await runInitialPrompt().catch(handleInitialPromptError);
|
|
1858
|
+
},
|
|
1619
1859
|
logger: this.sessionLogger,
|
|
1860
|
+
}).catch((bootstrapError) => {
|
|
1861
|
+
clearReplayEntryOnDeferredFailure?.();
|
|
1862
|
+
this.sessionLogger.error({ err: bootstrapError, agentId: snapshot.id }, `Failed to bootstrap worktree for agent ${snapshot.id}`);
|
|
1620
1863
|
});
|
|
1621
1864
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
...(initialRunOptions ?? {}),
|
|
1625
|
-
}));
|
|
1865
|
+
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1866
|
+
return snapshot;
|
|
1626
1867
|
};
|
|
1627
|
-
|
|
1628
|
-
|
|
1868
|
+
let reusedExisting = false;
|
|
1869
|
+
let snapshot;
|
|
1870
|
+
let agentPayload;
|
|
1871
|
+
if (createReplayKey) {
|
|
1872
|
+
const existingEntry = pendingCreateAgentRequests.get(createReplayKey);
|
|
1873
|
+
if (existingEntry) {
|
|
1874
|
+
reusedExisting = true;
|
|
1875
|
+
snapshot = await existingEntry.promise;
|
|
1876
|
+
}
|
|
1877
|
+
else {
|
|
1878
|
+
reusedExisting = false;
|
|
1879
|
+
let clearReplayEntryOnDeferredFailure = () => { };
|
|
1880
|
+
const nextPromise = createAgentLaunch(() => {
|
|
1881
|
+
clearReplayEntryOnDeferredFailure();
|
|
1882
|
+
});
|
|
1883
|
+
const nextEntry = storePendingCreateAgentRequest(createReplayKey, nextPromise);
|
|
1884
|
+
clearReplayEntryOnDeferredFailure = () => {
|
|
1885
|
+
deletePendingCreateAgentRequest(createReplayKey, nextEntry);
|
|
1886
|
+
};
|
|
1887
|
+
try {
|
|
1888
|
+
snapshot = await nextPromise;
|
|
1889
|
+
}
|
|
1890
|
+
catch (error) {
|
|
1891
|
+
deletePendingCreateAgentRequest(createReplayKey, nextEntry);
|
|
1892
|
+
throw error;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
agentPayload = await this.getAgentPayloadById(snapshot.id);
|
|
1896
|
+
if (!agentPayload) {
|
|
1897
|
+
agentPayload = await this.buildAgentPayload(snapshot);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
snapshot = await createAgentLaunch();
|
|
1902
|
+
agentPayload = await this.getAgentPayloadById(snapshot.id);
|
|
1903
|
+
if (!agentPayload) {
|
|
1904
|
+
throw new Error(`Agent ${snapshot.id} not found after creation`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
if (requestId) {
|
|
1629
1908
|
this.emit({
|
|
1630
|
-
type: '
|
|
1909
|
+
type: 'status',
|
|
1631
1910
|
payload: {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
type: 'error',
|
|
1635
|
-
content: `Initial prompt failed: ${promptError?.message ?? promptError}`,
|
|
1636
|
-
},
|
|
1637
|
-
});
|
|
1638
|
-
};
|
|
1639
|
-
if (hasInitialMessage && !worktreeConfig) {
|
|
1640
|
-
void runInitialPrompt().catch(handleInitialPromptError);
|
|
1641
|
-
}
|
|
1642
|
-
if (worktreeConfig) {
|
|
1643
|
-
void runAsyncWorktreeBootstrap({
|
|
1644
|
-
agentId: snapshot.id,
|
|
1645
|
-
worktree: worktreeConfig,
|
|
1646
|
-
setupOverride: bootstrapSetupOverride,
|
|
1647
|
-
generalPreferencesApplied,
|
|
1648
|
-
terminalManager: this.terminalManager,
|
|
1649
|
-
appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
|
|
1650
|
-
agentManager: this.agentManager,
|
|
1651
|
-
agentId: snapshot.id,
|
|
1652
|
-
item,
|
|
1653
|
-
}),
|
|
1654
|
-
emitLiveTimelineItem: (item) => emitLiveTimelineItemIfAgentKnown({
|
|
1655
|
-
agentManager: this.agentManager,
|
|
1911
|
+
status: 'agent_created',
|
|
1912
|
+
reusedExisting,
|
|
1656
1913
|
agentId: snapshot.id,
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
onSetupSettled: async (result) => {
|
|
1660
|
-
if (!hasInitialMessage || result.setupStatus === 'failed') {
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
await runInitialPrompt().catch(handleInitialPromptError);
|
|
1914
|
+
requestId,
|
|
1915
|
+
agent: agentPayload,
|
|
1664
1916
|
},
|
|
1665
|
-
logger: this.sessionLogger,
|
|
1666
|
-
}).catch((bootstrapError) => {
|
|
1667
|
-
this.sessionLogger.error({ err: bootstrapError, agentId: snapshot.id }, `Failed to bootstrap worktree for agent ${snapshot.id}`);
|
|
1668
1917
|
});
|
|
1669
1918
|
}
|
|
1670
|
-
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1671
1919
|
}
|
|
1672
1920
|
catch (error) {
|
|
1673
1921
|
this.sessionLogger.error({ err: error }, 'Failed to create agent');
|
|
@@ -1712,6 +1960,9 @@ export class Session {
|
|
|
1712
1960
|
const snapshot = await this.agentManager.resumeAgentFromPersistence(handle, overrides);
|
|
1713
1961
|
await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
|
|
1714
1962
|
await this.forwardAgentUpdate(snapshot);
|
|
1963
|
+
if (snapshot.provider === 'codex') {
|
|
1964
|
+
this.scheduleProviderChildThreadRefresh();
|
|
1965
|
+
}
|
|
1715
1966
|
const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
|
|
1716
1967
|
if (requestId) {
|
|
1717
1968
|
const agentPayload = await this.getAgentPayloadById(snapshot.id);
|
|
@@ -1770,10 +2021,14 @@ export class Session {
|
|
|
1770
2021
|
snapshot = await this.agentManager.resumeAgentFromPersistence(handle, buildConfigOverrides(record), agentId, {
|
|
1771
2022
|
...extractTimestamps(record),
|
|
1772
2023
|
...extractTimelineSnapshot(record),
|
|
2024
|
+
parentAgentId: record.parentAgentId ?? null,
|
|
1773
2025
|
});
|
|
1774
2026
|
}
|
|
1775
2027
|
await this.agentManager.hydrateTimelineFromProvider(agentId);
|
|
1776
2028
|
await this.forwardAgentUpdate(snapshot);
|
|
2029
|
+
if (snapshot.provider === 'codex') {
|
|
2030
|
+
this.scheduleProviderChildThreadRefresh();
|
|
2031
|
+
}
|
|
1777
2032
|
const timelineSize = this.agentManager.getTimeline(agentId).length;
|
|
1778
2033
|
if (requestId) {
|
|
1779
2034
|
this.emit({
|
|
@@ -5350,6 +5605,260 @@ export class Session {
|
|
|
5350
5605
|
});
|
|
5351
5606
|
}
|
|
5352
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
|
+
}
|
|
5353
5862
|
async handleFetchAgent(agentIdOrIdentifier, requestId) {
|
|
5354
5863
|
const resolved = await this.resolveAgentIdentifier(agentIdOrIdentifier);
|
|
5355
5864
|
if (!resolved.ok) {
|
|
@@ -5744,6 +6253,10 @@ export class Session {
|
|
|
5744
6253
|
this.unsubscribeAgentEvents();
|
|
5745
6254
|
this.unsubscribeAgentEvents = null;
|
|
5746
6255
|
}
|
|
6256
|
+
if (this.providerChildThreadRefreshTimer) {
|
|
6257
|
+
clearTimeout(this.providerChildThreadRefreshTimer);
|
|
6258
|
+
this.providerChildThreadRefreshTimer = null;
|
|
6259
|
+
}
|
|
5747
6260
|
// Abort any ongoing operations
|
|
5748
6261
|
this.abortController.abort();
|
|
5749
6262
|
// Close MCP clients
|