ds-harness-remote 0.5.8 → 0.5.11
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/acp/adapter.d.ts +1 -1
- package/dist/acp/adapter.d.ts.map +1 -1
- package/dist/acp/adapters/claude-history.d.ts +3 -0
- package/dist/acp/adapters/claude-history.d.ts.map +1 -0
- package/dist/acp/adapters/claude-history.js +118 -0
- package/dist/acp/adapters/claude-history.js.map +1 -0
- package/dist/acp/adapters/claude-process.d.ts +21 -0
- package/dist/acp/adapters/claude-process.d.ts.map +1 -0
- package/dist/acp/adapters/claude-process.js +147 -0
- package/dist/acp/adapters/claude-process.js.map +1 -0
- package/dist/acp/adapters/cursor-process.d.ts +12 -4
- package/dist/acp/adapters/cursor-process.d.ts.map +1 -1
- package/dist/acp/adapters/cursor-process.js +86 -4
- package/dist/acp/adapters/cursor-process.js.map +1 -1
- package/dist/acp/adapters/isolated-process.d.ts +45 -0
- package/dist/acp/adapters/isolated-process.d.ts.map +1 -0
- package/dist/acp/adapters/isolated-process.js +299 -0
- package/dist/acp/adapters/isolated-process.js.map +1 -0
- package/dist/acp/adapters/opencode-native-api.d.ts +16 -0
- package/dist/acp/adapters/opencode-native-api.d.ts.map +1 -0
- package/dist/acp/adapters/opencode-native-api.js +103 -0
- package/dist/acp/adapters/opencode-native-api.js.map +1 -0
- package/dist/acp/adapters/opencode-process.d.ts +86 -0
- package/dist/acp/adapters/opencode-process.d.ts.map +1 -0
- package/dist/acp/adapters/opencode-process.js +483 -0
- package/dist/acp/adapters/opencode-process.js.map +1 -0
- package/dist/acp/adapters/opencode.d.ts +8 -0
- package/dist/acp/adapters/opencode.d.ts.map +1 -0
- package/dist/acp/adapters/opencode.js +8 -0
- package/dist/acp/adapters/opencode.js.map +1 -0
- package/dist/acp/adapters/pi-process.d.ts +5 -0
- package/dist/acp/adapters/pi-process.d.ts.map +1 -0
- package/dist/acp/adapters/pi-process.js +5 -0
- package/dist/acp/adapters/pi-process.js.map +1 -0
- package/dist/acp/adapters/session-models.d.ts +33 -0
- package/dist/acp/adapters/session-models.d.ts.map +1 -0
- package/dist/acp/adapters/session-models.js +78 -0
- package/dist/acp/adapters/session-models.js.map +1 -0
- package/dist/acp/gateway.d.ts +10 -0
- package/dist/acp/gateway.d.ts.map +1 -1
- package/dist/acp/gateway.js +405 -71
- package/dist/acp/gateway.js.map +1 -1
- package/dist/acp/history-events.d.ts +8 -0
- package/dist/acp/history-events.d.ts.map +1 -0
- package/dist/acp/history-events.js +119 -0
- package/dist/acp/history-events.js.map +1 -0
- package/dist/acp/method-policy.d.ts +30 -20
- package/dist/acp/method-policy.d.ts.map +1 -1
- package/dist/acp/method-policy.js +6 -5
- package/dist/acp/method-policy.js.map +1 -1
- package/dist/acp/peer-bridge.d.ts +3 -2
- package/dist/acp/peer-bridge.d.ts.map +1 -1
- package/dist/acp/peer-bridge.js +4 -0
- package/dist/acp/peer-bridge.js.map +1 -1
- package/dist/acp/pi-tool-result.d.ts +9 -0
- package/dist/acp/pi-tool-result.d.ts.map +1 -0
- package/dist/acp/pi-tool-result.js +22 -0
- package/dist/acp/pi-tool-result.js.map +1 -0
- package/dist/acp/virtual-catalog.d.ts +50 -0
- package/dist/acp/virtual-catalog.d.ts.map +1 -0
- package/dist/acp/virtual-catalog.js +90 -0
- package/dist/acp/virtual-catalog.js.map +1 -0
- package/dist/acp/virtual-contract.d.ts +172 -0
- package/dist/acp/virtual-contract.d.ts.map +1 -0
- package/dist/acp/virtual-contract.js +216 -0
- package/dist/acp/virtual-contract.js.map +1 -0
- package/dist/acp/virtual-event-carrier.d.ts +51 -0
- package/dist/acp/virtual-event-carrier.d.ts.map +1 -0
- package/dist/acp/virtual-event-carrier.js +222 -0
- package/dist/acp/virtual-event-carrier.js.map +1 -0
- package/dist/acp/virtual-harness.d.ts +18 -112
- package/dist/acp/virtual-harness.d.ts.map +1 -1
- package/dist/acp/virtual-harness.js +238 -952
- package/dist/acp/virtual-harness.js.map +1 -1
- package/dist/acp/virtual-live.d.ts +79 -0
- package/dist/acp/virtual-live.d.ts.map +1 -0
- package/dist/acp/virtual-live.js +514 -0
- package/dist/acp/virtual-live.js.map +1 -0
- package/dist/acp/workspace-tools.js +1 -1
- package/dist/acp/workspace-tools.js.map +1 -1
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +1 -0
- package/dist/acp.js.map +1 -1
- package/dist/client/header.d.ts +10 -0
- package/dist/client/header.d.ts.map +1 -0
- package/dist/client/header.js +169 -0
- package/dist/client/header.js.map +1 -0
- package/dist/client/icons.d.ts +3 -0
- package/dist/client/icons.d.ts.map +1 -0
- package/dist/client/icons.js +3 -0
- package/dist/client/icons.js.map +1 -0
- package/dist/client/navigation.d.ts +5 -0
- package/dist/client/navigation.d.ts.map +1 -0
- package/dist/client/navigation.js +30 -0
- package/dist/client/navigation.js.map +1 -0
- package/dist/client/presentation.d.ts +17 -0
- package/dist/client/presentation.d.ts.map +1 -0
- package/dist/client/presentation.js +208 -0
- package/dist/client/presentation.js.map +1 -0
- package/dist/client/progress.d.ts +10 -0
- package/dist/client/progress.d.ts.map +1 -0
- package/dist/client/progress.js +82 -0
- package/dist/client/progress.js.map +1 -0
- package/dist/client/settings.d.ts +10 -0
- package/dist/client/settings.d.ts.map +1 -0
- package/dist/client/settings.js +335 -0
- package/dist/client/settings.js.map +1 -0
- package/dist/client/shell.d.ts +6 -0
- package/dist/client/shell.d.ts.map +1 -0
- package/dist/client/shell.js +129 -0
- package/dist/client/shell.js.map +1 -0
- package/dist/client/types.d.ts +265 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/workspaces.d.ts +11 -0
- package/dist/client/workspaces.d.ts.map +1 -0
- package/dist/client/workspaces.js +1261 -0
- package/dist/client/workspaces.js.map +1 -0
- package/dist/client-capabilities.d.ts +6 -0
- package/dist/client-capabilities.d.ts.map +1 -0
- package/dist/client-capabilities.js +90 -0
- package/dist/client-capabilities.js.map +1 -0
- package/dist/client-codex-streams.d.ts +23 -0
- package/dist/client-codex-streams.d.ts.map +1 -0
- package/dist/client-codex-streams.js +156 -0
- package/dist/client-codex-streams.js.map +1 -0
- package/dist/client-connection.d.ts +38 -0
- package/dist/client-connection.d.ts.map +1 -0
- package/dist/client-connection.js +210 -0
- package/dist/client-connection.js.map +1 -0
- package/dist/client-control.d.ts +48 -0
- package/dist/client-control.d.ts.map +1 -0
- package/dist/client-control.js +293 -0
- package/dist/client-control.js.map +1 -0
- package/dist/client-locale.d.ts +12 -0
- package/dist/client-locale.d.ts.map +1 -1
- package/dist/client-locale.js +24 -0
- package/dist/client-locale.js.map +1 -1
- package/dist/client-runtime-contract.d.ts +162 -0
- package/dist/client-runtime-contract.d.ts.map +1 -0
- package/dist/client-runtime-contract.js +127 -0
- package/dist/client-runtime-contract.js.map +1 -0
- package/dist/client-runtime.d.ts +24 -126
- package/dist/client-runtime.d.ts.map +1 -1
- package/dist/client-runtime.js +171 -867
- package/dist/client-runtime.js.map +1 -1
- package/dist/client-secure-transport.d.ts +1 -0
- package/dist/client-secure-transport.d.ts.map +1 -1
- package/dist/client-secure-transport.js +35 -16
- package/dist/client-secure-transport.js.map +1 -1
- package/dist/client-workspace-queries.d.ts +12 -0
- package/dist/client-workspace-queries.d.ts.map +1 -0
- package/dist/client-workspace-queries.js +66 -0
- package/dist/client-workspace-queries.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.github.js +2158 -1905
- package/dist/client.js +2158 -1905
- package/dist/client.js.map +1 -1
- package/dist/codex/domain.d.ts.map +1 -1
- package/dist/codex/domain.js +67 -46
- package/dist/codex/domain.js.map +1 -1
- package/dist/codex/method-policy.d.ts +7 -7
- package/dist/codex/virtual-catalog.d.ts +90 -0
- package/dist/codex/virtual-catalog.d.ts.map +1 -0
- package/dist/codex/virtual-catalog.js +317 -0
- package/dist/codex/virtual-catalog.js.map +1 -0
- package/dist/codex/virtual-contract.d.ts +96 -0
- package/dist/codex/virtual-contract.d.ts.map +1 -0
- package/dist/codex/virtual-contract.js +192 -0
- package/dist/codex/virtual-contract.js.map +1 -0
- package/dist/codex/virtual-event-carrier.d.ts +52 -0
- package/dist/codex/virtual-event-carrier.d.ts.map +1 -0
- package/dist/codex/virtual-event-carrier.js +212 -0
- package/dist/codex/virtual-event-carrier.js.map +1 -0
- package/dist/codex/virtual-harness.d.ts +16 -117
- package/dist/codex/virtual-harness.d.ts.map +1 -1
- package/dist/codex/virtual-harness.js +123 -1909
- package/dist/codex/virtual-harness.js.map +1 -1
- package/dist/codex/virtual-history.d.ts +74 -0
- package/dist/codex/virtual-history.d.ts.map +1 -0
- package/dist/codex/virtual-history.js +546 -0
- package/dist/codex/virtual-history.js.map +1 -0
- package/dist/codex/virtual-live.d.ts +75 -0
- package/dist/codex/virtual-live.d.ts.map +1 -0
- package/dist/codex/virtual-live.js +563 -0
- package/dist/codex/virtual-live.js.map +1 -0
- package/dist/codex/virtual-values.d.ts +29 -0
- package/dist/codex/virtual-values.d.ts.map +1 -0
- package/dist/codex/virtual-values.js +75 -0
- package/dist/codex/virtual-values.js.map +1 -0
- package/dist/config.js +4 -4
- package/dist/config.js.map +1 -1
- package/dist/control-runtime.js +2 -2
- package/dist/control-runtime.js.map +1 -1
- package/dist/harness-remote-bridge.d.ts +2 -1
- package/dist/harness-remote-bridge.d.ts.map +1 -1
- package/dist/harness-remote-bridge.js +11 -3
- package/dist/harness-remote-bridge.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17301 -14516
- package/dist/index.js.map +4 -4
- package/dist/plugin-route-dispatcher.d.ts +2 -2
- package/dist/plugin-route-dispatcher.d.ts.map +1 -1
- package/dist/plugin-route-dispatcher.js +2 -2
- package/dist/plugin-route-dispatcher.js.map +1 -1
- package/dist/remote-typert-gateway.js +49 -2
- package/dist/remote-typert-gateway.js.map +1 -1
- package/dist/server-connection.d.ts +1 -0
- package/dist/server-connection.d.ts.map +1 -1
- package/dist/server-connection.js +6 -1
- package/dist/server-connection.js.map +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +16 -5
- package/dist/service.js.map +1 -1
- package/dist/terminal-carrier-compat.d.ts +14 -0
- package/dist/terminal-carrier-compat.d.ts.map +1 -0
- package/dist/terminal-carrier-compat.js +259 -0
- package/dist/terminal-carrier-compat.js.map +1 -0
- package/dist/typert-gateway-contract.d.ts +6 -1
- package/dist/typert-gateway-contract.d.ts.map +1 -1
- package/dist/typert-gateway-switch.d.ts +2 -1
- package/dist/typert-gateway-switch.d.ts.map +1 -1
- package/dist/typert-gateway-switch.js +31 -15
- package/dist/typert-gateway-switch.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dsh-plugin.json +1 -1
- package/package.json +21 -19
|
@@ -1,41 +1,11 @@
|
|
|
1
|
-
import { CodexRemoteClient,
|
|
1
|
+
import { CodexRemoteClient, projectCodexThread } from '@dsh-remote/client-core';
|
|
2
2
|
import { codexPermissionPresetFromResponse } from './permissions.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const MAX_CODEX_PAGES = 32;
|
|
10
|
-
const MAX_LIVE_TOOL_OUTPUT = 128 * 1024;
|
|
11
|
-
const DEFAULT_HISTORY_MESSAGES = 50;
|
|
12
|
-
const SESSION_SEARCH_RESULT_LIMIT = 20;
|
|
13
|
-
const SESSION_SEARCH_SNIPPET_CODE_POINTS = 240;
|
|
14
|
-
const CODEX_DEFAULT_PERMISSION = 'workspace-write';
|
|
15
|
-
const MAX_CODEX_PROMPT_PARTS = 16;
|
|
16
|
-
const MAX_CODEX_PROMPT_TEXT = 256 * 1024;
|
|
17
|
-
const MAX_CODEX_IMAGE_BASE64 = 288 * 1024 * 1024;
|
|
18
|
-
const CODEX_IMAGE_MEDIA_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']);
|
|
19
|
-
const CODEX_IMAGE_ATTACHMENT_PREFIX = 'codex-image:';
|
|
20
|
-
const HOST_WORKSPACE_ENDPOINTS = new Set([
|
|
21
|
-
'workspaceFiles/list', 'workspaceFiles/stat', 'workspaceFiles/read', 'workspaceFiles/readBytes',
|
|
22
|
-
'workspaceFiles/readAll', 'workspaceFiles/readRelated', 'terminal/environment', 'terminal/shells',
|
|
23
|
-
'terminal/list', 'terminal/create', 'terminal/write', 'terminal/resize', 'terminal/rename', 'terminal/close',
|
|
24
|
-
]);
|
|
25
|
-
const HOST_WORKSPACE_STREAM_ENDPOINTS = new Set(['workspaceFiles/changes', 'terminal/follow', 'terminal/retain']);
|
|
26
|
-
function isHostWorkspaceEndpoint(endpoint) {
|
|
27
|
-
return HOST_WORKSPACE_ENDPOINTS.has(endpoint);
|
|
28
|
-
}
|
|
29
|
-
function isHostWorkspaceStreamEndpoint(endpoint) {
|
|
30
|
-
return HOST_WORKSPACE_STREAM_ENDPOINTS.has(endpoint);
|
|
31
|
-
}
|
|
32
|
-
export function codexProjectWorkspaceId(projectId) {
|
|
33
|
-
return `${CODEX_WORKSPACE_PREFIX}project:${projectId}`;
|
|
34
|
-
}
|
|
35
|
-
/** Discover the CodeX projects visible through the Host App Server. */
|
|
36
|
-
export async function discoverCodexVirtualWorkspaces(client, signal) {
|
|
37
|
-
return (await loadCatalog(client, signal)).workspaces;
|
|
38
|
-
}
|
|
3
|
+
import { CODEX_PROVIDER, codexModelParams, containsPath, displayTitle, isCodexDirectoryListing, loadCatalog, loadModelDirectory, modelCatalog, modelSelection, modelSelectionProjection, nativeVisibleWorkspaces, nativeWorkspace, } from './virtual-catalog.js';
|
|
4
|
+
import { business, carrierArgs, CODEX_DEFAULT_PERMISSION, CODEX_IMAGE_ATTACHMENT_PREFIX, codexImageLimitsProjection, codexPermissionsProjection, codexPromptInput, fail, failFrom, failure, isCodexPermissionPreset, isHostWorkspaceEndpoint, isHostWorkspaceStreamEndpoint, isLegacySessionHistoryUnsupported, ok, requestArg, SESSION_SEARCH_RESULT_LIMIT, SESSION_SEARCH_SNIPPET_CODE_POINTS, sessionIdFromAddress, success, truncateCodePoints, } from './virtual-contract.js';
|
|
5
|
+
import { CodexEventCarrier } from './virtual-event-carrier.js';
|
|
6
|
+
import { adaptCodexHistoryPage, isCodexNativeHistoryPage, paginateCodexNativeHistory, projectCodexNativeHistory, } from './virtual-history.js';
|
|
7
|
+
import { CodexLiveProjection } from './virtual-live.js';
|
|
8
|
+
import { array, nativeThreadId, optionalInteger, optionalNonNegativeInteger, optionalPositiveInteger, record, requiredString, string, } from './virtual-values.js';
|
|
39
9
|
/**
|
|
40
10
|
* A plugin-owned virtual Harness target. It projects CodeX Thread/Turn data at
|
|
41
11
|
* the existing ApiProxy/Typert carrier boundary, so every UI layer above the
|
|
@@ -46,6 +16,8 @@ export class CodexVirtualHarness {
|
|
|
46
16
|
host;
|
|
47
17
|
sessionGeneration;
|
|
48
18
|
hostCarrier;
|
|
19
|
+
live;
|
|
20
|
+
events;
|
|
49
21
|
api;
|
|
50
22
|
catalog;
|
|
51
23
|
workspaceStreams = new Set();
|
|
@@ -66,13 +38,55 @@ export class CodexVirtualHarness {
|
|
|
66
38
|
lastProjectionSeq = 0;
|
|
67
39
|
commandSeq = 0;
|
|
68
40
|
selectedWorkspaceId;
|
|
41
|
+
lastOpenedSessionId;
|
|
42
|
+
openedWorkspaceSessions = new Map();
|
|
69
43
|
closed = false;
|
|
70
44
|
constructor(client, host, sessionGeneration = 'legacy', hostCarrier) {
|
|
71
45
|
this.client = client;
|
|
72
46
|
this.host = host;
|
|
73
47
|
this.sessionGeneration = sessionGeneration;
|
|
74
48
|
this.hostCarrier = hostCarrier;
|
|
75
|
-
|
|
49
|
+
const owner = this;
|
|
50
|
+
this.live = new CodexLiveProjection({
|
|
51
|
+
readHistoryPage: (...args) => owner.readHistoryPage(...args),
|
|
52
|
+
models: (...args) => owner.models(...args),
|
|
53
|
+
get pendingRequestIds() { return owner.pendingRequestIds; },
|
|
54
|
+
get follows() { return owner.follows; },
|
|
55
|
+
sessionTitle: (...args) => owner.sessionTitle(...args),
|
|
56
|
+
modelSelection: (...args) => owner.modelSelection(...args),
|
|
57
|
+
permissionSelection: (...args) => owner.permissionSelection(...args),
|
|
58
|
+
get sessionGeneration() { return owner.sessionGeneration; },
|
|
59
|
+
get client() { return owner.client; },
|
|
60
|
+
get closed() { return owner.closed; },
|
|
61
|
+
rememberOpenedSession: (...args) => owner.rememberOpenedSession(...args),
|
|
62
|
+
nextProjectionSeq: (...args) => owner.nextProjectionSeq(...args),
|
|
63
|
+
updateThreadName: (...args) => owner.updateThreadName(...args),
|
|
64
|
+
publishProjection: (...args) => owner.events.publishProjection(...args),
|
|
65
|
+
setPermissionSelection: (...args) => owner.setPermissionSelection(...args),
|
|
66
|
+
emitRemoteEvent: (...args) => owner.events.emitRemoteEvent(...args),
|
|
67
|
+
broadcastRcHost: (...args) => owner.events.broadcastRcHost(...args),
|
|
68
|
+
get selectedModels() { return owner.selectedModels; },
|
|
69
|
+
refreshAndPublishWorkspaces: (...args) => owner.refreshAndPublishWorkspaces(...args),
|
|
70
|
+
get pendingApprovals() { return owner.pendingApprovals; },
|
|
71
|
+
emitApproval: (...args) => owner.events.emitApproval(...args),
|
|
72
|
+
broadcastRcMux: (...args) => owner.events.broadcastRcMux(...args),
|
|
73
|
+
get imageAttachments() { return owner.imageAttachments; },
|
|
74
|
+
});
|
|
75
|
+
this.events = new CodexEventCarrier({
|
|
76
|
+
get workspaceStreams() { return owner.workspaceStreams; },
|
|
77
|
+
refreshCatalog: (...args) => owner.refreshCatalog(...args),
|
|
78
|
+
get selectedWorkspaceId() { return owner.selectedWorkspaceId; },
|
|
79
|
+
get controlStreams() { return owner.controlStreams; },
|
|
80
|
+
get host() { return owner.host; },
|
|
81
|
+
get eventStreams() { return owner.eventStreams; },
|
|
82
|
+
currentCatalog: (...args) => owner.currentCatalog(...args),
|
|
83
|
+
get pendingApprovals() { return owner.pendingApprovals; },
|
|
84
|
+
get client() { return owner.client; },
|
|
85
|
+
dispatch: (...args) => owner.dispatch(...args),
|
|
86
|
+
get rcMuxStreams() { return owner.rcMuxStreams; },
|
|
87
|
+
get rcHostStreams() { return owner.rcHostStreams; },
|
|
88
|
+
});
|
|
89
|
+
this.api = this.events.createApiProxy();
|
|
76
90
|
}
|
|
77
91
|
static remote(core, host, sessionGeneration = 'legacy', hostCarrier) {
|
|
78
92
|
return new CodexVirtualHarness(new CodexRemoteClient(core), host, sessionGeneration, hostCarrier);
|
|
@@ -89,33 +103,41 @@ export class CodexVirtualHarness {
|
|
|
89
103
|
this.catalog = catalog;
|
|
90
104
|
return workspace;
|
|
91
105
|
}
|
|
92
|
-
|
|
106
|
+
get openedSessionId() {
|
|
107
|
+
return this.lastOpenedSessionId;
|
|
108
|
+
}
|
|
109
|
+
get openedSessions() {
|
|
110
|
+
return new Map(this.openedWorkspaceSessions);
|
|
111
|
+
}
|
|
112
|
+
rememberOpenedSession(sessionId) {
|
|
113
|
+
if (this.closed)
|
|
114
|
+
return;
|
|
115
|
+
this.lastOpenedSessionId = sessionId;
|
|
116
|
+
// Native navigation can open another visible Workspace without replacing
|
|
117
|
+
// this carrier. Remember the actual catalog membership, not its entry URL.
|
|
118
|
+
for (const workspace of this.catalog?.workspaces ?? []) {
|
|
119
|
+
if (!workspace.sessionIds.includes(sessionId))
|
|
120
|
+
continue;
|
|
121
|
+
this.openedWorkspaceSessions.delete(workspace.workspaceId);
|
|
122
|
+
this.openedWorkspaceSessions.set(workspace.workspaceId, sessionId);
|
|
123
|
+
if (this.openedWorkspaceSessions.size > 64) {
|
|
124
|
+
this.openedWorkspaceSessions.delete(this.openedWorkspaceSessions.keys().next().value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async preferredSessionId(signal, preferredSessionId = this.openedWorkspaceSessions.get(this.selectedWorkspaceId ?? '')) {
|
|
129
|
+
signal?.throwIfAborted();
|
|
93
130
|
const catalog = this.catalog;
|
|
94
131
|
const selected = catalog?.workspaces.find(workspace => workspace.workspaceId === this.selectedWorkspaceId);
|
|
95
132
|
const selectedSessionIds = new Set(selected?.sessionIds ?? []);
|
|
96
133
|
const sessions = (catalog?.sessions ?? []).filter(session => selectedSessionIds.has(session.id));
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
const preferredNamed = idleNamed.length > 1 ? [...idleNamed.slice(1), idleNamed[0]] : idleNamed;
|
|
105
|
-
const candidates = [...new Set([...preferredNamed, ...idle, ...sessions].map(session => session.id))];
|
|
106
|
-
for (const sessionId of candidates) {
|
|
107
|
-
const threadId = nativeThreadId(sessionId);
|
|
108
|
-
try {
|
|
109
|
-
const thread = await this.fetchThread(threadId, signal);
|
|
110
|
-
return sessionId;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// A currently active or concurrently changing CodeX Thread may reject
|
|
114
|
-
// a full history read. Keep looking so the native UI does not remain
|
|
115
|
-
// on an ungrouped placeholder Session after entering the Workspace.
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return undefined;
|
|
134
|
+
if (sessions.some(session => session.id === preferredSessionId))
|
|
135
|
+
return preferredSessionId;
|
|
136
|
+
// The Host catalog already validated these IDs. Restore the latest Thread
|
|
137
|
+
// regardless of title or busy status; transient history-read failures must
|
|
138
|
+
// not redirect navigation to an older Thread or a new blank Session.
|
|
139
|
+
// Do not probe Threads here: one busy read can stall workspace recovery.
|
|
140
|
+
return sessions.reduce((latest, session) => latest === undefined || session.updatedAt > latest.updatedAt ? session : latest, undefined)?.id;
|
|
119
141
|
}
|
|
120
142
|
async invoke(request) {
|
|
121
143
|
const result = await this.dispatch(`${request.namespace}/${request.method}`, { args: request.args }, request.signal ?? new AbortController().signal);
|
|
@@ -136,7 +158,7 @@ export class CodexVirtualHarness {
|
|
|
136
158
|
}
|
|
137
159
|
const args = carrierArgs(payload);
|
|
138
160
|
switch (endpoint) {
|
|
139
|
-
case '$events/result': return business(await this.answerRemoteEvent(args, signal));
|
|
161
|
+
case '$events/result': return business(await this.events.answerRemoteEvent(args, signal));
|
|
140
162
|
case 'workspace/list': {
|
|
141
163
|
const catalog = await this.refreshCatalog(signal);
|
|
142
164
|
return business(success({
|
|
@@ -154,6 +176,7 @@ export class CodexVirtualHarness {
|
|
|
154
176
|
case 'workspace/insertSessionBefore': return business(await this.workspaceForSession(requestArg(args), signal));
|
|
155
177
|
case 'workspace/archiveSession': return business(await this.archiveSession(requestArg(args), signal));
|
|
156
178
|
case 'session/list': return business(success({ items: await this.sessionSummaries(signal) }));
|
|
179
|
+
case 'session/projections': return business(await this.sessionProjections(requestArg(args), signal));
|
|
157
180
|
case 'session/search': return business(await this.searchSessions(requestArg(args), signal));
|
|
158
181
|
case 'session/create': return business(await this.createSession(requestArg(args), signal));
|
|
159
182
|
case 'session/fork': return business(await this.forkSession(requestArg(args), signal));
|
|
@@ -207,15 +230,15 @@ export class CodexVirtualHarness {
|
|
|
207
230
|
}
|
|
208
231
|
const args = carrierArgs(payload);
|
|
209
232
|
if (endpoint === 'workspace/follow')
|
|
210
|
-
return this.workspaceFollow(signal);
|
|
233
|
+
return this.events.workspaceFollow(signal);
|
|
211
234
|
if (endpoint === 'session/control')
|
|
212
|
-
return this.sessionControl(signal);
|
|
235
|
+
return this.events.sessionControl(signal);
|
|
213
236
|
if (endpoint === 'session/follow')
|
|
214
|
-
return this.sessionFollow(requestArg(args), signal);
|
|
237
|
+
return this.live.sessionFollow(requestArg(args), signal);
|
|
215
238
|
if (endpoint === '$events')
|
|
216
|
-
return this.remoteEvents(signal);
|
|
239
|
+
return this.events.remoteEvents(signal);
|
|
217
240
|
if (endpoint === 'job/list' || endpoint === 'job/follow')
|
|
218
|
-
return this.emptyJobStream(endpoint, signal);
|
|
241
|
+
return this.events.emptyJobStream(endpoint, signal);
|
|
219
242
|
if (this.hostCarrier !== undefined)
|
|
220
243
|
return this.hostCarrier.open(endpoint, payload, signal);
|
|
221
244
|
throw Object.assign(new Error(`CodeX virtual Harness does not implement stream ${endpoint}.`), {
|
|
@@ -291,7 +314,7 @@ export class CodexVirtualHarness {
|
|
|
291
314
|
this.selectedPermissions.delete(sessionId);
|
|
292
315
|
else
|
|
293
316
|
this.selectedPermissions.set(sessionId, preset);
|
|
294
|
-
this.publishProjection(sessionId, 'permissions', codexPermissionsProjection(preset), this.nextProjectionSeq());
|
|
317
|
+
this.events.publishProjection(sessionId, 'permissions', codexPermissionsProjection(preset), this.nextProjectionSeq());
|
|
295
318
|
}
|
|
296
319
|
commandList(args) {
|
|
297
320
|
nativeThreadId(requiredString(args.agentId, 'agentId'));
|
|
@@ -364,14 +387,9 @@ export class CodexVirtualHarness {
|
|
|
364
387
|
};
|
|
365
388
|
this.selectedModels.set(sessionId, selected);
|
|
366
389
|
const seq = Date.now();
|
|
367
|
-
this.publishProjection(sessionId, 'modelSelection', modelSelectionProjection(selected), seq);
|
|
390
|
+
this.events.publishProjection(sessionId, 'modelSelection', modelSelectionProjection(selected), seq);
|
|
368
391
|
return success({ selected });
|
|
369
392
|
}
|
|
370
|
-
publishProjection(sessionId, key, value, seq) {
|
|
371
|
-
for (const queue of this.controlStreams)
|
|
372
|
-
queue.push({ type: 'projection', sessionId, key, value, seq });
|
|
373
|
-
this.broadcastRcMux({ type: 'session/projection', sessionId, key, value, seq });
|
|
374
|
-
}
|
|
375
393
|
nextProjectionSeq() {
|
|
376
394
|
const seq = Math.max(Date.now(), this.lastProjectionSeq + 1);
|
|
377
395
|
this.lastProjectionSeq = seq;
|
|
@@ -448,578 +466,6 @@ export class CodexVirtualHarness {
|
|
|
448
466
|
hasMore: matches.length > SESSION_SEARCH_RESULT_LIMIT,
|
|
449
467
|
});
|
|
450
468
|
}
|
|
451
|
-
async workspaceFollow(signal) {
|
|
452
|
-
const queue = new AsyncValueQueue(signal);
|
|
453
|
-
this.workspaceStreams.add(queue);
|
|
454
|
-
const catalog = await this.refreshCatalog(signal);
|
|
455
|
-
queue.push({
|
|
456
|
-
type: 'baseline',
|
|
457
|
-
value: {
|
|
458
|
-
items: nativeVisibleWorkspaces(catalog, this.selectedWorkspaceId).map(nativeWorkspace),
|
|
459
|
-
archivedSessionIds: catalog.sessions.filter(item => item.archived).map(item => item.id),
|
|
460
|
-
pinnedSessionIds: [],
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
return queue.iterate(() => this.workspaceStreams.delete(queue));
|
|
464
|
-
}
|
|
465
|
-
async sessionControl(signal) {
|
|
466
|
-
const queue = new AsyncValueQueue(signal);
|
|
467
|
-
this.controlStreams.add(queue);
|
|
468
|
-
queue.push({ type: 'baseline', value: { queues: {}, jobs: {}, projections: {} } });
|
|
469
|
-
return queue.iterate(() => this.controlStreams.delete(queue));
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Background jobs belong to the Host Harness session, not to a CodeX virtual
|
|
473
|
-
* Workspace. The native session header mounts a job roster stream as soon as a
|
|
474
|
-
* Session opens, so answer it with an authoritative empty roster instead of a
|
|
475
|
-
* terminal `method-not-found` failure that tears the page down.
|
|
476
|
-
*/
|
|
477
|
-
emptyJobStream(endpoint, signal) {
|
|
478
|
-
const queue = new AsyncValueQueue(signal);
|
|
479
|
-
if (endpoint === 'job/list')
|
|
480
|
-
queue.push({ type: 'rows', jobs: [] });
|
|
481
|
-
return queue.iterate(() => queue.close());
|
|
482
|
-
}
|
|
483
|
-
async remoteEvents(signal) {
|
|
484
|
-
const queue = new AsyncValueQueue(signal);
|
|
485
|
-
const clientId = `codex-events:${this.host.deviceId}:${Date.now().toString(36)}`;
|
|
486
|
-
this.eventStreams.set(clientId, queue);
|
|
487
|
-
const home = (await this.currentCatalog(signal)).workspaces[0]?.path ?? '/';
|
|
488
|
-
queue.push({ type: 'ready', clientId, host: { home } });
|
|
489
|
-
return queue.iterate(() => this.eventStreams.delete(clientId));
|
|
490
|
-
}
|
|
491
|
-
async sessionFollow(request, signal) {
|
|
492
|
-
const sessionId = sessionIdFromAddress(record(request.address));
|
|
493
|
-
const threadId = nativeThreadId(sessionId);
|
|
494
|
-
const history = await this.readHistoryPage(threadId, {
|
|
495
|
-
maxMessages: optionalPositiveInteger(request.maxMessages),
|
|
496
|
-
}, signal);
|
|
497
|
-
const directory = await this.models(signal).catch(() => undefined);
|
|
498
|
-
const queue = new AsyncValueQueue(signal);
|
|
499
|
-
const follow = {
|
|
500
|
-
sessionId,
|
|
501
|
-
threadId,
|
|
502
|
-
queue,
|
|
503
|
-
nextSeq: history.cursor + 1,
|
|
504
|
-
turn: history.nextTurn,
|
|
505
|
-
stepOpen: history.activeTurnId !== undefined,
|
|
506
|
-
startedItems: new Set(),
|
|
507
|
-
completedItems: new Set(),
|
|
508
|
-
streamedBlocks: new Map(),
|
|
509
|
-
nextBlockIndex: 0,
|
|
510
|
-
streamActive: false,
|
|
511
|
-
assistantStreamRevision: 0,
|
|
512
|
-
liveItems: new Map(),
|
|
513
|
-
liveToolOutput: new Map(),
|
|
514
|
-
liveToolResultSeq: new Map(),
|
|
515
|
-
pendingToolImages: [],
|
|
516
|
-
requestId: this.pendingRequestIds.get(sessionId),
|
|
517
|
-
...(history.activeTurnId === undefined ? {} : { activeTurnId: history.activeTurnId }),
|
|
518
|
-
};
|
|
519
|
-
this.follows.add(follow);
|
|
520
|
-
queue.push({
|
|
521
|
-
type: 'snapshot',
|
|
522
|
-
header: history.header,
|
|
523
|
-
cursor: history.cursor,
|
|
524
|
-
records: history.records,
|
|
525
|
-
hasMore: history.hasMore,
|
|
526
|
-
projections: {
|
|
527
|
-
asOfSeq: history.cursor,
|
|
528
|
-
values: {
|
|
529
|
-
title: this.sessionTitle(sessionId),
|
|
530
|
-
sessionListMetadata: { blank: history.cursor < 0, lastPromptAt: null },
|
|
531
|
-
modelSelection: modelSelectionProjection(this.modelSelection(sessionId, directory)),
|
|
532
|
-
permissions: codexPermissionsProjection(this.permissionSelection(sessionId)),
|
|
533
|
-
imageLimits: codexImageLimitsProjection(),
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
...(this.sessionGeneration === 'v3' ? { assistantStream: { revision: 0 } } : {}),
|
|
537
|
-
});
|
|
538
|
-
try {
|
|
539
|
-
const stream = await this.client.subscribe(threadId, frame => this.acceptCodexFrame(follow, frame), signal, () => this.closeFollowAfterRemoteStreamClosed(follow));
|
|
540
|
-
follow.close = () => stream.close();
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
this.follows.delete(follow);
|
|
544
|
-
queue.close();
|
|
545
|
-
throw error;
|
|
546
|
-
}
|
|
547
|
-
return queue.iterate(() => {
|
|
548
|
-
this.follows.delete(follow);
|
|
549
|
-
void follow.close?.().catch(() => undefined);
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
acceptCodexFrame(follow, frame) {
|
|
553
|
-
const params = record(frame.params);
|
|
554
|
-
if (frame.method === 'thread/name/updated') {
|
|
555
|
-
const name = string(params.threadName);
|
|
556
|
-
if (name === undefined)
|
|
557
|
-
return;
|
|
558
|
-
const seq = this.nextProjectionSeq();
|
|
559
|
-
this.updateThreadName(follow.threadId, name);
|
|
560
|
-
const title = name.trim() === '' ? null : name.slice(0, 256);
|
|
561
|
-
this.publishProjection(follow.sessionId, 'title', title, seq);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
if (frame.method === 'thread/settings/updated') {
|
|
565
|
-
const preset = codexPermissionPresetFromResponse(params);
|
|
566
|
-
this.setPermissionSelection(follow.sessionId, preset);
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
if (frame.method === 'turn/started') {
|
|
570
|
-
const turn = record(params.turn);
|
|
571
|
-
this.endAssistantAttempt(follow, { kind: 'abandoned' });
|
|
572
|
-
this.resetLiveTurn(follow);
|
|
573
|
-
follow.turn += 1;
|
|
574
|
-
follow.activeTurnId = string(turn.id);
|
|
575
|
-
follow.requestId = this.pendingRequestIds.get(follow.sessionId);
|
|
576
|
-
follow.stepOpen = true;
|
|
577
|
-
this.pushEvent(follow, 'turn/start', { turn: follow.turn });
|
|
578
|
-
this.pushEvent(follow, 'step/start', { turn: follow.turn, step: 1 });
|
|
579
|
-
this.emitRemoteEvent('api-session/status', [follow.sessionId, true]);
|
|
580
|
-
for (const item of array(turn.items))
|
|
581
|
-
this.acceptCompletedItem(follow, record(item), false);
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
if (frame.method === 'item/started' || frame.method === 'item/completed') {
|
|
585
|
-
const item = record(params.item);
|
|
586
|
-
const itemId = string(item.id);
|
|
587
|
-
if (frame.method === 'item/started' && itemId !== undefined) {
|
|
588
|
-
follow.startedItems.add(itemId);
|
|
589
|
-
follow.liveItems.set(itemId, item);
|
|
590
|
-
if (isToolItemType(string(item.type)))
|
|
591
|
-
this.ensureToolStarted(follow, item);
|
|
592
|
-
}
|
|
593
|
-
if (frame.method === 'item/completed')
|
|
594
|
-
this.acceptCompletedItem(follow, item, true);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
if (frame.method === 'item/agentMessage/delta') {
|
|
598
|
-
const itemId = string(params.itemId) ?? `assistant:${follow.turn}`;
|
|
599
|
-
const delta = string(params.delta);
|
|
600
|
-
if (delta === undefined)
|
|
601
|
-
return;
|
|
602
|
-
this.appendStreamDelta(follow, `agent:${itemId}`, itemId, 'text', delta);
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
if (frame.method === 'item/reasoning/summaryPartAdded') {
|
|
606
|
-
const itemId = string(params.itemId);
|
|
607
|
-
if (itemId !== undefined)
|
|
608
|
-
this.ensureStreamBlock(follow, `reasoning-summary:${itemId}:${integer(params.summaryIndex) ?? 0}`, itemId, 'reasoning');
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
if (frame.method === 'item/reasoning/summaryTextDelta'
|
|
612
|
-
|| frame.method === 'item/reasoning/textDelta'
|
|
613
|
-
|| frame.method === 'item/plan/delta') {
|
|
614
|
-
const itemId = string(params.itemId);
|
|
615
|
-
const delta = string(params.delta);
|
|
616
|
-
if (itemId === undefined || delta === undefined)
|
|
617
|
-
return;
|
|
618
|
-
const key = frame.method === 'item/plan/delta'
|
|
619
|
-
? `plan:${itemId}`
|
|
620
|
-
: frame.method === 'item/reasoning/summaryTextDelta'
|
|
621
|
-
? `reasoning-summary:${itemId}:${integer(params.summaryIndex) ?? 0}`
|
|
622
|
-
: `reasoning-content:${itemId}:${integer(params.contentIndex) ?? 0}`;
|
|
623
|
-
this.appendStreamDelta(follow, key, itemId, 'reasoning', delta);
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (frame.method === 'turn/plan/updated') {
|
|
627
|
-
const todos = array(params.plan).flatMap(value => {
|
|
628
|
-
const item = record(value);
|
|
629
|
-
const content = string(item.step);
|
|
630
|
-
if (content === undefined)
|
|
631
|
-
return [];
|
|
632
|
-
const status = item.status === 'inProgress'
|
|
633
|
-
? 'in_progress'
|
|
634
|
-
: item.status === 'completed' ? 'completed' : 'pending';
|
|
635
|
-
return [{ content, status }];
|
|
636
|
-
});
|
|
637
|
-
this.pushEvent(follow, 'todo/write', { todos });
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
if (frame.method === 'item/commandExecution/outputDelta'
|
|
641
|
-
|| frame.method === 'item/fileChange/outputDelta'
|
|
642
|
-
|| frame.method === 'item/mcpToolCall/progress') {
|
|
643
|
-
const itemId = string(params.itemId);
|
|
644
|
-
const delta = frame.method === 'item/mcpToolCall/progress'
|
|
645
|
-
? string(params.message)
|
|
646
|
-
: string(params.delta);
|
|
647
|
-
if (itemId === undefined || delta === undefined)
|
|
648
|
-
return;
|
|
649
|
-
const type = frame.method === 'item/commandExecution/outputDelta'
|
|
650
|
-
? 'commandExecution'
|
|
651
|
-
: frame.method === 'item/fileChange/outputDelta' ? 'fileChange' : 'mcpToolCall';
|
|
652
|
-
const item = follow.liveItems.get(itemId) ?? { id: itemId, type, status: 'inProgress' };
|
|
653
|
-
follow.liveItems.set(itemId, item);
|
|
654
|
-
this.ensureToolStarted(follow, item);
|
|
655
|
-
const separator = frame.method === 'item/mcpToolCall/progress' && follow.liveToolOutput.has(itemId) ? '\n' : '';
|
|
656
|
-
const output = appendBoundedText(follow.liveToolOutput.get(itemId) ?? '', `${separator}${delta}`);
|
|
657
|
-
follow.liveToolOutput.set(itemId, output);
|
|
658
|
-
this.emitToolResult(follow, item, output);
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
if (frame.method === 'item/fileChange/patchUpdated') {
|
|
662
|
-
const itemId = string(params.itemId);
|
|
663
|
-
if (itemId === undefined)
|
|
664
|
-
return;
|
|
665
|
-
const previous = follow.liveItems.get(itemId) ?? { id: itemId, type: 'fileChange', status: 'inProgress' };
|
|
666
|
-
const item = { ...previous, changes: sanitizedFileChanges(params.changes) };
|
|
667
|
-
follow.liveItems.set(itemId, item);
|
|
668
|
-
this.ensureToolStarted(follow, item);
|
|
669
|
-
this.emitToolResult(follow, item, fileChangeSummary(item));
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
if (frame.method === 'thread/status/changed') {
|
|
673
|
-
const status = record(params.status);
|
|
674
|
-
const running = status.type === 'active';
|
|
675
|
-
this.emitRemoteEvent('api-session/status', [follow.sessionId, running]);
|
|
676
|
-
if (status.type === 'systemError') {
|
|
677
|
-
this.broadcastRcHost({
|
|
678
|
-
type: 'host/agent-error',
|
|
679
|
-
sessionId: follow.sessionId,
|
|
680
|
-
message: 'CodeX reported a thread system error.',
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (frame.method === 'model/rerouted') {
|
|
686
|
-
const model = string(params.toModel);
|
|
687
|
-
if (model === undefined)
|
|
688
|
-
return;
|
|
689
|
-
const previous = this.selectedModels.get(follow.sessionId);
|
|
690
|
-
const selected = {
|
|
691
|
-
provider: CODEX_PROVIDER,
|
|
692
|
-
model,
|
|
693
|
-
...(previous?.reasoningEffort === undefined ? {} : { reasoningEffort: previous.reasoningEffort }),
|
|
694
|
-
};
|
|
695
|
-
this.selectedModels.set(follow.sessionId, selected);
|
|
696
|
-
const seq = this.pushEvent(follow, 'request/context', { provider: CODEX_PROVIDER, model });
|
|
697
|
-
this.publishProjection(follow.sessionId, 'modelSelection', modelSelectionProjection(selected), seq);
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
if (frame.method === 'turn/completed') {
|
|
701
|
-
const turn = record(params.turn);
|
|
702
|
-
for (const item of array(turn.items))
|
|
703
|
-
this.acceptCompletedItem(follow, record(item), true);
|
|
704
|
-
if (follow.stepOpen) {
|
|
705
|
-
this.closeAllStreamBlocks(follow);
|
|
706
|
-
if (follow.streamActive)
|
|
707
|
-
this.finishStream(follow);
|
|
708
|
-
this.endAssistantAttempt(follow, { kind: 'abandoned' });
|
|
709
|
-
this.pushEvent(follow, 'step/end', { turn: follow.turn, step: 1 });
|
|
710
|
-
follow.stepOpen = false;
|
|
711
|
-
}
|
|
712
|
-
this.pushEvent(follow, 'turn/end', {
|
|
713
|
-
turn: follow.turn,
|
|
714
|
-
reason: turn.status === 'failed' || turn.error !== undefined && turn.error !== null
|
|
715
|
-
? { kind: 'error', error: { message: 'CodeX turn failed.', code: 'codex-turn-failed' } }
|
|
716
|
-
: { kind: 'completed' },
|
|
717
|
-
});
|
|
718
|
-
this.emitRemoteEvent('api-session/status', [follow.sessionId, false]);
|
|
719
|
-
this.emitRemoteEvent('api-session/activity', [follow.sessionId, Date.now()]);
|
|
720
|
-
follow.activeTurnId = undefined;
|
|
721
|
-
this.pendingRequestIds.delete(follow.sessionId);
|
|
722
|
-
void this.refreshAndPublishWorkspaces();
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
if (frame.method === 'item/commandExecution/requestApproval'
|
|
726
|
-
|| frame.method === 'item/fileChange/requestApproval') {
|
|
727
|
-
const requestHandle = string(params.requestHandle);
|
|
728
|
-
if (requestHandle === undefined)
|
|
729
|
-
return;
|
|
730
|
-
const command = commandText(params.command);
|
|
731
|
-
this.pendingApprovals.set(requestHandle, { requestHandle, sessionId: follow.sessionId });
|
|
732
|
-
this.emitApproval({
|
|
733
|
-
eventId: requestHandle,
|
|
734
|
-
agentId: follow.sessionId,
|
|
735
|
-
request: {
|
|
736
|
-
toolName: frame.method.includes('fileChange') ? 'CodeX file change' : 'CodeX command',
|
|
737
|
-
...(command === undefined ? {} : { reason: command }),
|
|
738
|
-
},
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
acceptCompletedItem(follow, item, settleAssistant) {
|
|
743
|
-
const itemId = string(item.id) ?? `item:${follow.nextSeq}`;
|
|
744
|
-
if (follow.completedItems.has(itemId))
|
|
745
|
-
return;
|
|
746
|
-
const type = string(item.type);
|
|
747
|
-
if (type === 'agentMessage' && !settleAssistant)
|
|
748
|
-
return;
|
|
749
|
-
follow.completedItems.add(itemId);
|
|
750
|
-
this.closeItemStreamBlocks(follow, itemId, itemText(item));
|
|
751
|
-
if (type === 'agentMessage' && follow.streamActive && follow.streamedBlocks.size === 0)
|
|
752
|
-
this.finishStream(follow);
|
|
753
|
-
const supplementalImages = type === 'agentMessage' ? follow.pendingToolImages : [];
|
|
754
|
-
let assistantMessageSeq;
|
|
755
|
-
for (const event of itemEvents(item, follow.turn, 1, follow.requestId, this.modelSelection(follow.sessionId), follow.sessionId, supplementalImages)) {
|
|
756
|
-
if (event.type === 'tool/call' && follow.startedItems.has(itemId))
|
|
757
|
-
continue;
|
|
758
|
-
if (event.type === 'tool/result')
|
|
759
|
-
this.pushToolResult(follow, itemId, event);
|
|
760
|
-
else {
|
|
761
|
-
const seq = this.pushEvent(follow, event.type, event.data, event.view);
|
|
762
|
-
if (event.type === 'assistant/message')
|
|
763
|
-
assistantMessageSeq = seq;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
if (type === 'agentMessage' && assistantMessageSeq !== undefined) {
|
|
767
|
-
this.endAssistantAttempt(follow, {
|
|
768
|
-
kind: 'committed',
|
|
769
|
-
eventType: 'assistant/message',
|
|
770
|
-
seq: assistantMessageSeq,
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
if (type === 'agentMessage' && supplementalImages.length > 0)
|
|
774
|
-
follow.pendingToolImages = [];
|
|
775
|
-
else if (type !== undefined && isToolItemType(type)) {
|
|
776
|
-
follow.pendingToolImages.push(...toolOutputImageBlocks(item));
|
|
777
|
-
}
|
|
778
|
-
follow.liveItems.delete(itemId);
|
|
779
|
-
follow.liveToolOutput.delete(itemId);
|
|
780
|
-
follow.liveToolResultSeq.delete(itemId);
|
|
781
|
-
follow.liveToolResultSeq.delete(`${itemId}:call`);
|
|
782
|
-
}
|
|
783
|
-
pushEvent(follow, type, data, view, placement) {
|
|
784
|
-
const seq = follow.nextSeq++;
|
|
785
|
-
const event = {
|
|
786
|
-
type,
|
|
787
|
-
seq,
|
|
788
|
-
time: Date.now(),
|
|
789
|
-
data: adaptEventData(type, type === 'assistant/message' && this.sessionGeneration === 'v3' && follow.assistantAttempt !== undefined
|
|
790
|
-
? { ...record(data), stream: follow.assistantAttempt.stream }
|
|
791
|
-
: data, this.sessionGeneration),
|
|
792
|
-
...(isSurfaceEvent(type)
|
|
793
|
-
? placement === undefined
|
|
794
|
-
? { surfaceOp: 'append' }
|
|
795
|
-
: {
|
|
796
|
-
surfaceOp: this.sessionGeneration === 'v3'
|
|
797
|
-
? { op: 'replace', startSeq: placement.replaceSeq, endSeq: placement.replaceSeq }
|
|
798
|
-
: { op: 'replace', start: placement.replaceSeq, end: placement.replaceSeq },
|
|
799
|
-
sourceEventSeqs: [placement.replaceSeq],
|
|
800
|
-
}
|
|
801
|
-
: {}),
|
|
802
|
-
};
|
|
803
|
-
this.cacheImageBlocks(follow.sessionId, event.data);
|
|
804
|
-
if (!follow.rcOnly) {
|
|
805
|
-
follow.queue.push({
|
|
806
|
-
type: 'event',
|
|
807
|
-
event,
|
|
808
|
-
...(view === undefined ? {} : { view }),
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
this.broadcastRcMux({
|
|
812
|
-
type: 'session/event',
|
|
813
|
-
sessionId: follow.sessionId,
|
|
814
|
-
event,
|
|
815
|
-
...(view === undefined ? {} : { view }),
|
|
816
|
-
});
|
|
817
|
-
return seq;
|
|
818
|
-
}
|
|
819
|
-
cacheImageBlocks(sessionId, value) {
|
|
820
|
-
for (const block of collectImageBlocks(value)) {
|
|
821
|
-
const attachment = record(block.attachment);
|
|
822
|
-
const attachmentId = string(attachment.attachmentId);
|
|
823
|
-
const data = string(block.data);
|
|
824
|
-
if (attachmentId === undefined || data === undefined || !attachmentId.startsWith(CODEX_IMAGE_ATTACHMENT_PREFIX))
|
|
825
|
-
continue;
|
|
826
|
-
this.imageAttachments.set(attachmentId, { sessionId, attachment, data });
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
ensureStreamBlock(follow, key, itemId, kind) {
|
|
830
|
-
const current = follow.streamedBlocks.get(key);
|
|
831
|
-
if (current !== undefined)
|
|
832
|
-
return current;
|
|
833
|
-
const block = { itemId, index: follow.nextBlockIndex++, kind, text: '' };
|
|
834
|
-
follow.streamedBlocks.set(key, block);
|
|
835
|
-
follow.streamActive = true;
|
|
836
|
-
this.pushAssistantChunk(follow, { type: 'block-start', index: block.index, blockType: kind });
|
|
837
|
-
return block;
|
|
838
|
-
}
|
|
839
|
-
appendStreamDelta(follow, key, itemId, kind, delta) {
|
|
840
|
-
const block = this.ensureStreamBlock(follow, key, itemId, kind);
|
|
841
|
-
block.text = appendBoundedText(block.text, delta);
|
|
842
|
-
this.pushAssistantChunk(follow, {
|
|
843
|
-
type: kind === 'reasoning' ? 'reasoning-delta' : 'text-delta', index: block.index, text: delta,
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
closeItemStreamBlocks(follow, itemId, fallback) {
|
|
847
|
-
const matches = [...follow.streamedBlocks].filter(([, block]) => block.itemId === itemId);
|
|
848
|
-
for (const [key, block] of matches) {
|
|
849
|
-
const text = block.text || fallback || '';
|
|
850
|
-
this.pushAssistantChunk(follow, {
|
|
851
|
-
type: 'block-end', index: block.index, block: { type: block.kind, text },
|
|
852
|
-
});
|
|
853
|
-
follow.streamedBlocks.delete(key);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
closeAllStreamBlocks(follow) {
|
|
857
|
-
for (const block of follow.streamedBlocks.values()) {
|
|
858
|
-
this.pushAssistantChunk(follow, {
|
|
859
|
-
type: 'block-end', index: block.index, block: { type: block.kind, text: block.text },
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
follow.streamedBlocks.clear();
|
|
863
|
-
}
|
|
864
|
-
finishStream(follow) {
|
|
865
|
-
this.pushAssistantChunk(follow, { type: 'finish', reason: { kind: 'stop' } });
|
|
866
|
-
follow.streamActive = false;
|
|
867
|
-
}
|
|
868
|
-
pushAssistantChunk(follow, chunk) {
|
|
869
|
-
if (this.sessionGeneration !== 'v3' || follow.rcOnly === true) {
|
|
870
|
-
this.pushEvent(follow, 'assistant/chunk', { turn: follow.turn, step: 1, chunk });
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
let attempt = follow.assistantAttempt;
|
|
874
|
-
if (attempt === undefined) {
|
|
875
|
-
attempt = {
|
|
876
|
-
attemptId: `codex-attempt:${follow.activeTurnId ?? follow.turn}:${follow.nextSeq}`,
|
|
877
|
-
startedAfterSeq: follow.nextSeq - 1,
|
|
878
|
-
nextIndex: 0,
|
|
879
|
-
stream: [],
|
|
880
|
-
};
|
|
881
|
-
follow.assistantAttempt = attempt;
|
|
882
|
-
follow.assistantStreamRevision += 1;
|
|
883
|
-
follow.queue.push({
|
|
884
|
-
type: 'assistant-stream',
|
|
885
|
-
frame: {
|
|
886
|
-
type: 'start',
|
|
887
|
-
attemptId: attempt.attemptId,
|
|
888
|
-
revision: follow.assistantStreamRevision,
|
|
889
|
-
startedAfterSeq: attempt.startedAfterSeq,
|
|
890
|
-
turn: follow.turn,
|
|
891
|
-
step: 1,
|
|
892
|
-
},
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
const time = Date.now();
|
|
896
|
-
const index = attempt.nextIndex++;
|
|
897
|
-
attempt.stream.push({ time, chunk });
|
|
898
|
-
follow.assistantStreamRevision += 1;
|
|
899
|
-
follow.queue.push({
|
|
900
|
-
type: 'assistant-stream',
|
|
901
|
-
frame: {
|
|
902
|
-
type: 'chunk',
|
|
903
|
-
attemptId: attempt.attemptId,
|
|
904
|
-
revision: follow.assistantStreamRevision,
|
|
905
|
-
index,
|
|
906
|
-
time,
|
|
907
|
-
chunk,
|
|
908
|
-
},
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
endAssistantAttempt(follow, outcome) {
|
|
912
|
-
const attempt = follow.assistantAttempt;
|
|
913
|
-
if (attempt === undefined)
|
|
914
|
-
return;
|
|
915
|
-
follow.assistantAttempt = undefined;
|
|
916
|
-
follow.assistantStreamRevision += 1;
|
|
917
|
-
if (this.sessionGeneration === 'v3' && follow.rcOnly !== true) {
|
|
918
|
-
follow.queue.push({
|
|
919
|
-
type: 'assistant-stream',
|
|
920
|
-
frame: {
|
|
921
|
-
type: 'end',
|
|
922
|
-
attemptId: attempt.attemptId,
|
|
923
|
-
revision: follow.assistantStreamRevision,
|
|
924
|
-
index: attempt.nextIndex,
|
|
925
|
-
outcome,
|
|
926
|
-
},
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
ensureToolStarted(follow, item) {
|
|
931
|
-
const itemId = string(item.id);
|
|
932
|
-
if (itemId === undefined)
|
|
933
|
-
return;
|
|
934
|
-
if (!follow.startedItems.has(itemId))
|
|
935
|
-
follow.startedItems.add(itemId);
|
|
936
|
-
if (follow.liveItems.get(itemId) !== item)
|
|
937
|
-
follow.liveItems.set(itemId, item);
|
|
938
|
-
const call = itemEvents(item, follow.turn, 1, follow.requestId, this.modelSelection(follow.sessionId), follow.sessionId).find(event => event.type === 'tool/call');
|
|
939
|
-
if (call !== undefined && !follow.liveToolResultSeq.has(`${itemId}:call`)) {
|
|
940
|
-
this.pushEvent(follow, call.type, call.data, call.view);
|
|
941
|
-
follow.liveToolResultSeq.set(`${itemId}:call`, -1);
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
emitToolResult(follow, item, resultText) {
|
|
945
|
-
const itemId = string(item.id);
|
|
946
|
-
if (itemId === undefined)
|
|
947
|
-
return;
|
|
948
|
-
const result = toolResultEvent(item, follow.turn, 1, itemId, resultText);
|
|
949
|
-
this.pushToolResult(follow, itemId, result);
|
|
950
|
-
}
|
|
951
|
-
pushToolResult(follow, itemId, event) {
|
|
952
|
-
const previous = follow.liveToolResultSeq.get(itemId);
|
|
953
|
-
const projected = previous === undefined ? event : toolResultContentReplacement(event);
|
|
954
|
-
const seq = this.pushEvent(follow, projected.type, projected.data, projected.view, previous === undefined ? undefined : { replaceSeq: previous });
|
|
955
|
-
follow.liveToolResultSeq.set(itemId, seq);
|
|
956
|
-
}
|
|
957
|
-
resetLiveTurn(follow) {
|
|
958
|
-
follow.startedItems.clear();
|
|
959
|
-
follow.completedItems.clear();
|
|
960
|
-
follow.streamedBlocks.clear();
|
|
961
|
-
follow.nextBlockIndex = 0;
|
|
962
|
-
follow.streamActive = false;
|
|
963
|
-
follow.assistantAttempt = undefined;
|
|
964
|
-
follow.liveItems.clear();
|
|
965
|
-
follow.liveToolOutput.clear();
|
|
966
|
-
follow.liveToolResultSeq.clear();
|
|
967
|
-
follow.pendingToolImages = [];
|
|
968
|
-
}
|
|
969
|
-
emitRemoteEvent(event, args) {
|
|
970
|
-
for (const queue of this.eventStreams.values())
|
|
971
|
-
queue.push({ type: 'emit', event, args });
|
|
972
|
-
const sessionId = typeof args[0] === 'string' ? args[0] : undefined;
|
|
973
|
-
if (event === 'api-session/status' && sessionId !== undefined && typeof args[1] === 'boolean') {
|
|
974
|
-
this.broadcastRcHost({ type: 'host/session-status', sessionId, running: args[1] });
|
|
975
|
-
}
|
|
976
|
-
else if (event === 'api-session/removed' && sessionId !== undefined) {
|
|
977
|
-
this.broadcastRcHost({ type: 'host/session-removed', sessionId });
|
|
978
|
-
}
|
|
979
|
-
else if (event === 'api-session/added' && isRecord(args[0])) {
|
|
980
|
-
const summary = args[0];
|
|
981
|
-
this.broadcastRcHost({
|
|
982
|
-
type: 'host/session-added',
|
|
983
|
-
sessionId: summary.sessionId,
|
|
984
|
-
blank: summary.blank === true,
|
|
985
|
-
...(typeof summary.cwd === 'string' ? { cwd: summary.cwd } : {}),
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
emitApproval(input) {
|
|
990
|
-
for (const queue of this.eventStreams.values())
|
|
991
|
-
queue.push({
|
|
992
|
-
type: 'waterfall',
|
|
993
|
-
event: 'approval/request',
|
|
994
|
-
eventId: input.eventId,
|
|
995
|
-
agentId: input.agentId,
|
|
996
|
-
request: input.request,
|
|
997
|
-
});
|
|
998
|
-
this.broadcastRcMux({
|
|
999
|
-
type: 'approval/requested',
|
|
1000
|
-
sessionId: input.agentId,
|
|
1001
|
-
approvalId: input.eventId,
|
|
1002
|
-
toolName: string(input.request.toolName) ?? 'CodeX',
|
|
1003
|
-
...(typeof input.request.reason === 'string' ? { reason: input.request.reason } : {}),
|
|
1004
|
-
}, input.eventId);
|
|
1005
|
-
}
|
|
1006
|
-
async answerRemoteEvent(args, signal) {
|
|
1007
|
-
const eventId = string(args.eventId);
|
|
1008
|
-
const outcome = record(args.outcome);
|
|
1009
|
-
if (eventId === undefined)
|
|
1010
|
-
throw new Error('The CodeX approval result is missing its event id.');
|
|
1011
|
-
const pending = this.pendingApprovals.get(eventId);
|
|
1012
|
-
if (pending === undefined)
|
|
1013
|
-
return undefined;
|
|
1014
|
-
this.pendingApprovals.delete(eventId);
|
|
1015
|
-
const decision = outcome.kind === 'result' && outcome.value === 'allowed-once'
|
|
1016
|
-
? 'accept'
|
|
1017
|
-
: outcome.kind === 'result' && outcome.value === 'cancelled'
|
|
1018
|
-
? 'cancel'
|
|
1019
|
-
: 'decline';
|
|
1020
|
-
await this.client.respond(pending.requestHandle, decision, signal);
|
|
1021
|
-
return undefined;
|
|
1022
|
-
}
|
|
1023
469
|
async createWorkspace(request, signal) {
|
|
1024
470
|
const path = string(request.path);
|
|
1025
471
|
if (path === undefined || path.trim() === '')
|
|
@@ -1121,13 +567,13 @@ export class CodexVirtualHarness {
|
|
|
1121
567
|
this.selectedPermissions.set(projected.id, permissionPreset);
|
|
1122
568
|
await this.refreshAndPublishWorkspaces();
|
|
1123
569
|
const seq = this.nextProjectionSeq();
|
|
1124
|
-
this.emitRemoteEvent('api-session/added', [this.sessionSummary(projected, {
|
|
570
|
+
this.events.emitRemoteEvent('api-session/added', [this.sessionSummary(projected, {
|
|
1125
571
|
blank: true,
|
|
1126
572
|
modelSelection: selection,
|
|
1127
573
|
lastPromptAt: null,
|
|
1128
574
|
asOfSeq: seq,
|
|
1129
575
|
})]);
|
|
1130
|
-
this.publishProjection(projected.id, 'title', displayTitle(projected), seq);
|
|
576
|
+
this.events.publishProjection(projected.id, 'title', displayTitle(projected), seq);
|
|
1131
577
|
return success({ sessionId: projected.id });
|
|
1132
578
|
}
|
|
1133
579
|
async forkSession(request, signal) {
|
|
@@ -1143,7 +589,7 @@ export class CodexVirtualHarness {
|
|
|
1143
589
|
this.setPermissionSelection(projected.id, permissionPreset);
|
|
1144
590
|
await this.refreshAndPublishWorkspaces();
|
|
1145
591
|
const seq = this.nextProjectionSeq();
|
|
1146
|
-
this.publishProjection(projected.id, 'title', displayTitle(projected), seq);
|
|
592
|
+
this.events.publishProjection(projected.id, 'title', displayTitle(projected), seq);
|
|
1147
593
|
return success({ sessionId: projected.id });
|
|
1148
594
|
}
|
|
1149
595
|
async prompt(request, signal) {
|
|
@@ -1155,7 +601,7 @@ export class CodexVirtualHarness {
|
|
|
1155
601
|
const threadId = nativeThreadId(sessionId);
|
|
1156
602
|
const selection = this.modelSelection(sessionId, await this.models(signal));
|
|
1157
603
|
const isFreshBlankThread = this.blankThreads.has(threadId) || this.pendingThreads.has(threadId);
|
|
1158
|
-
await this.ensureRcFollow(sessionId);
|
|
604
|
+
await this.live.ensureRcFollow(sessionId);
|
|
1159
605
|
if (!isFreshBlankThread) {
|
|
1160
606
|
const resumeResult = await this.client.request('thread/resume', {
|
|
1161
607
|
threadId,
|
|
@@ -1222,10 +668,25 @@ export class CodexVirtualHarness {
|
|
|
1222
668
|
await this.client.request('thread/name/set', { threadId: nativeThreadId(sessionId), name: title }, signal);
|
|
1223
669
|
const seq = this.nextProjectionSeq();
|
|
1224
670
|
this.updateThreadName(nativeThreadId(sessionId), title);
|
|
1225
|
-
this.publishProjection(sessionId, 'title', title, seq);
|
|
671
|
+
this.events.publishProjection(sessionId, 'title', title, seq);
|
|
1226
672
|
await this.refreshAndPublishWorkspaces();
|
|
1227
673
|
return success({ title, seq });
|
|
1228
674
|
}
|
|
675
|
+
async sessionProjections(request, signal) {
|
|
676
|
+
const sessionId = requiredString(request.sessionId, 'sessionId');
|
|
677
|
+
const catalog = await this.currentCatalog(signal);
|
|
678
|
+
const session = catalog.sessions.find(item => item.id === sessionId);
|
|
679
|
+
if (session === undefined)
|
|
680
|
+
return success(null);
|
|
681
|
+
const history = await this.readHistoryPage(nativeThreadId(sessionId), { maxMessages: 1 }, signal);
|
|
682
|
+
const directory = await this.models(signal).catch(() => undefined);
|
|
683
|
+
return success(record(this.sessionSummary(session, {
|
|
684
|
+
blank: history.cursor < 0,
|
|
685
|
+
modelSelection: this.modelSelection(sessionId, directory),
|
|
686
|
+
lastPromptAt: session.updatedAt || null,
|
|
687
|
+
asOfSeq: history.cursor,
|
|
688
|
+
})).projections);
|
|
689
|
+
}
|
|
1229
690
|
async sessionPage(request, signal) {
|
|
1230
691
|
const sessionId = sessionIdFromAddress(record(request.address));
|
|
1231
692
|
const page = await this.readHistoryPage(nativeThreadId(sessionId), {
|
|
@@ -1241,13 +702,6 @@ export class CodexVirtualHarness {
|
|
|
1241
702
|
return follow.activeTurnId;
|
|
1242
703
|
return undefined;
|
|
1243
704
|
}
|
|
1244
|
-
async fetchThread(threadId, signal) {
|
|
1245
|
-
const result = record(await this.client.request('thread/read', { threadId, includeTurns: false }, signal));
|
|
1246
|
-
const thread = record(result.thread);
|
|
1247
|
-
if (string(thread.id) !== threadId)
|
|
1248
|
-
throw new Error('CodeX returned an invalid Thread history.');
|
|
1249
|
-
return thread;
|
|
1250
|
-
}
|
|
1251
705
|
async readHistoryPage(threadId, page, signal) {
|
|
1252
706
|
let value;
|
|
1253
707
|
try {
|
|
@@ -1286,7 +740,7 @@ export class CodexVirtualHarness {
|
|
|
1286
740
|
const sessionId = history.header.id;
|
|
1287
741
|
const entries = 'entries' in history ? history.entries : history.records;
|
|
1288
742
|
for (const entry of entries)
|
|
1289
|
-
this.cacheImageBlocks(sessionId, entry.event.data);
|
|
743
|
+
this.live.cacheImageBlocks(sessionId, entry.event.data);
|
|
1290
744
|
}
|
|
1291
745
|
async sessionHistory(request, signal) {
|
|
1292
746
|
const sessionId = requiredString(request.sessionId, 'sessionId');
|
|
@@ -1299,8 +753,13 @@ export class CodexVirtualHarness {
|
|
|
1299
753
|
// rc.2 has one global mux rather than the alpha session/follow stream. A
|
|
1300
754
|
// history read is the reliable signal that the native client has opened a
|
|
1301
755
|
// Session, so attach the CodeX live stream here before returning its tail.
|
|
1302
|
-
if (beforeSeq === undefined)
|
|
1303
|
-
await this.ensureRcFollow(sessionId, history);
|
|
756
|
+
if (beforeSeq === undefined) {
|
|
757
|
+
await this.live.ensureRcFollow(sessionId, history);
|
|
758
|
+
signal.throwIfAborted();
|
|
759
|
+
if (this.closed)
|
|
760
|
+
throw new Error('The CodeX virtual Harness is closed.');
|
|
761
|
+
this.rememberOpenedSession(sessionId);
|
|
762
|
+
}
|
|
1304
763
|
return success({
|
|
1305
764
|
events: history.records.map(entry => ({
|
|
1306
765
|
event: entry.event,
|
|
@@ -1321,68 +780,6 @@ export class CodexVirtualHarness {
|
|
|
1321
780
|
}),
|
|
1322
781
|
});
|
|
1323
782
|
}
|
|
1324
|
-
async ensureRcFollow(sessionId, seedHistory) {
|
|
1325
|
-
if (this.followsHas(sessionId))
|
|
1326
|
-
return;
|
|
1327
|
-
const threadId = nativeThreadId(sessionId);
|
|
1328
|
-
// The Host bounds CodeX streams per connection. rc.2 only renders one
|
|
1329
|
-
// active conversation, so release older rc-only follows when navigation
|
|
1330
|
-
// moves to another Session. Alpha follows own their lifecycle separately.
|
|
1331
|
-
const stale = [...this.follows].filter(follow => follow.rcOnly && follow.sessionId !== sessionId);
|
|
1332
|
-
for (const follow of stale) {
|
|
1333
|
-
this.follows.delete(follow);
|
|
1334
|
-
follow.queue.close();
|
|
1335
|
-
await follow.close?.().catch(() => undefined);
|
|
1336
|
-
}
|
|
1337
|
-
const history = seedHistory ?? await this.readHistoryPage(threadId, {}, undefined);
|
|
1338
|
-
const controller = new AbortController();
|
|
1339
|
-
const follow = {
|
|
1340
|
-
sessionId,
|
|
1341
|
-
threadId,
|
|
1342
|
-
queue: new AsyncValueQueue(controller.signal),
|
|
1343
|
-
nextSeq: history.cursor + 1,
|
|
1344
|
-
turn: history.nextTurn,
|
|
1345
|
-
stepOpen: history.activeTurnId !== undefined,
|
|
1346
|
-
startedItems: new Set(),
|
|
1347
|
-
completedItems: new Set(),
|
|
1348
|
-
streamedBlocks: new Map(),
|
|
1349
|
-
nextBlockIndex: 0,
|
|
1350
|
-
streamActive: false,
|
|
1351
|
-
assistantStreamRevision: 0,
|
|
1352
|
-
liveItems: new Map(),
|
|
1353
|
-
liveToolOutput: new Map(),
|
|
1354
|
-
liveToolResultSeq: new Map(),
|
|
1355
|
-
pendingToolImages: [],
|
|
1356
|
-
requestId: this.pendingRequestIds.get(sessionId),
|
|
1357
|
-
rcOnly: true,
|
|
1358
|
-
...(history.activeTurnId === undefined ? {} : { activeTurnId: history.activeTurnId }),
|
|
1359
|
-
};
|
|
1360
|
-
this.follows.add(follow);
|
|
1361
|
-
try {
|
|
1362
|
-
const stream = await this.client.subscribe(threadId, frame => this.acceptCodexFrame(follow, frame), controller.signal, () => this.closeFollowAfterRemoteStreamClosed(follow));
|
|
1363
|
-
follow.close = async () => {
|
|
1364
|
-
controller.abort();
|
|
1365
|
-
await stream.close();
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
catch (error) {
|
|
1369
|
-
this.follows.delete(follow);
|
|
1370
|
-
controller.abort();
|
|
1371
|
-
follow.queue.close();
|
|
1372
|
-
throw error;
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
followsHas(sessionId) {
|
|
1376
|
-
for (const follow of this.follows)
|
|
1377
|
-
if (follow.sessionId === sessionId)
|
|
1378
|
-
return true;
|
|
1379
|
-
return false;
|
|
1380
|
-
}
|
|
1381
|
-
closeFollowAfterRemoteStreamClosed(follow) {
|
|
1382
|
-
this.follows.delete(follow);
|
|
1383
|
-
follow.queue.close();
|
|
1384
|
-
this.emitRemoteEvent('api-session/status', [follow.sessionId, false]);
|
|
1385
|
-
}
|
|
1386
783
|
async refreshAndPublishWorkspaces() {
|
|
1387
784
|
const catalog = await this.refreshCatalog().catch(() => undefined);
|
|
1388
785
|
if (catalog !== undefined)
|
|
@@ -1396,1191 +793,8 @@ export class CodexVirtualHarness {
|
|
|
1396
793
|
queue.push({ type: 'archived', archivedSessionIds: catalog.sessions.filter(item => item.archived).map(item => item.id) });
|
|
1397
794
|
}
|
|
1398
795
|
}
|
|
1399
|
-
createApiProxy() {
|
|
1400
|
-
const call = (endpoint) => async (request, signal) => {
|
|
1401
|
-
const payload = endpoint === 'session.prompt' && isRecord(request.payload)
|
|
1402
|
-
? { ...request.payload, requestId: String(request.rpcId) }
|
|
1403
|
-
: request.payload;
|
|
1404
|
-
const result = await this.dispatch(rcEndpoint(endpoint), { args: { request: payload } }, signal ?? new AbortController().signal);
|
|
1405
|
-
return {
|
|
1406
|
-
rpcId: request.rpcId,
|
|
1407
|
-
result: (result.ok
|
|
1408
|
-
? success(result.value)
|
|
1409
|
-
: failure(result.error.code, result.error.message, result.error.details)),
|
|
1410
|
-
};
|
|
1411
|
-
};
|
|
1412
|
-
return {
|
|
1413
|
-
sessions: {
|
|
1414
|
-
list: call('session.list'),
|
|
1415
|
-
search: call('session.search'),
|
|
1416
|
-
create: call('session.create'),
|
|
1417
|
-
history: call('session.history'),
|
|
1418
|
-
models: call('session.models'),
|
|
1419
|
-
selectModel: call('session.selectModel'),
|
|
1420
|
-
rename: call('session.rename'),
|
|
1421
|
-
fork: call('session.fork'),
|
|
1422
|
-
prompt: call('session.prompt'),
|
|
1423
|
-
attachment: call('session.attachment'),
|
|
1424
|
-
updateQueue: call('session.updateQueue'),
|
|
1425
|
-
cancel: call('session.cancel'),
|
|
1426
|
-
},
|
|
1427
|
-
workspace: {
|
|
1428
|
-
list: call('workspace.list'),
|
|
1429
|
-
create: call('workspace.create'),
|
|
1430
|
-
rename: call('workspace.rename'),
|
|
1431
|
-
delete: call('workspace.delete'),
|
|
1432
|
-
insertBefore: call('workspace.insertBefore'),
|
|
1433
|
-
insertSessionBefore: call('workspace.insertSessionBefore'),
|
|
1434
|
-
archiveSession: call('workspace.archiveSession'),
|
|
1435
|
-
},
|
|
1436
|
-
subagents: {},
|
|
1437
|
-
host: {
|
|
1438
|
-
describe: call('host.describe'),
|
|
1439
|
-
listDirectory: call('host.listDirectory'),
|
|
1440
|
-
},
|
|
1441
|
-
skills: { list: call('skills.list') },
|
|
1442
|
-
agentPresets: {},
|
|
1443
|
-
goals: {},
|
|
1444
|
-
settings: {},
|
|
1445
|
-
credentials: {},
|
|
1446
|
-
llm: {},
|
|
1447
|
-
events: {
|
|
1448
|
-
mux: ((request, signal) => this.rcMux(request, signal)),
|
|
1449
|
-
host: ((request, signal) => this.rcHost(request, signal)),
|
|
1450
|
-
},
|
|
1451
|
-
downloads: {},
|
|
1452
|
-
respond: async (message) => {
|
|
1453
|
-
const pending = this.pendingApprovals.get(String(message.rpcId));
|
|
1454
|
-
if (pending === undefined)
|
|
1455
|
-
return { accepted: false, reason: 'not-pending' };
|
|
1456
|
-
const result = message.result;
|
|
1457
|
-
const outcome = result.ok ? result.value : undefined;
|
|
1458
|
-
const decision = outcome === 'allowed-once' ? 'accept' : outcome === 'cancelled' ? 'cancel' : 'decline';
|
|
1459
|
-
await this.client.respond(pending.requestHandle, decision);
|
|
1460
|
-
this.pendingApprovals.delete(pending.requestHandle);
|
|
1461
|
-
return { accepted: true };
|
|
1462
|
-
},
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
async *rcMux(request, signal) {
|
|
1466
|
-
const queue = new AsyncValueQueue(signal);
|
|
1467
|
-
this.rcMuxStreams.add(queue);
|
|
1468
|
-
const catalog = await this.currentCatalog(signal);
|
|
1469
|
-
for (const session of catalog.sessions)
|
|
1470
|
-
queue.push({
|
|
1471
|
-
rpcId: `${String(request.rpcId)}:${session.id}:subscribed`,
|
|
1472
|
-
payload: { type: 'session/subscribed', sessionId: session.id, lastSeq: -1 },
|
|
1473
|
-
});
|
|
1474
|
-
try {
|
|
1475
|
-
yield* queue;
|
|
1476
|
-
}
|
|
1477
|
-
finally {
|
|
1478
|
-
this.rcMuxStreams.delete(queue);
|
|
1479
|
-
queue.close();
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
async *rcHost(_request, signal) {
|
|
1483
|
-
const queue = new AsyncValueQueue(signal);
|
|
1484
|
-
this.rcHostStreams.add(queue);
|
|
1485
|
-
try {
|
|
1486
|
-
yield* queue;
|
|
1487
|
-
}
|
|
1488
|
-
finally {
|
|
1489
|
-
this.rcHostStreams.delete(queue);
|
|
1490
|
-
queue.close();
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
broadcastRcMux(payload, rpcId = `codex-mux:${Date.now()}:${Math.random()}`) {
|
|
1494
|
-
for (const queue of this.rcMuxStreams)
|
|
1495
|
-
queue.push({ rpcId, payload });
|
|
1496
|
-
}
|
|
1497
|
-
broadcastRcHost(payload) {
|
|
1498
|
-
const frame = { rpcId: `codex-host:${Date.now()}:${Math.random()}`, payload };
|
|
1499
|
-
for (const queue of this.rcHostStreams)
|
|
1500
|
-
queue.push(frame);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
async function loadCatalog(client, signal, pendingThreads) {
|
|
1504
|
-
let projects = await loadCodexProjects(client, signal);
|
|
1505
|
-
const threads = [];
|
|
1506
|
-
let cursor;
|
|
1507
|
-
for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
|
|
1508
|
-
const result = record(await client.request('thread/list', {
|
|
1509
|
-
limit: CODEX_PAGE_LIMIT,
|
|
1510
|
-
sortKey: 'updated_at',
|
|
1511
|
-
sortDirection: 'desc',
|
|
1512
|
-
archived: false,
|
|
1513
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
1514
|
-
}, signal));
|
|
1515
|
-
for (const value of array(result.data)) {
|
|
1516
|
-
const thread = record(value);
|
|
1517
|
-
if (string(thread.id) !== undefined)
|
|
1518
|
-
threads.push(thread);
|
|
1519
|
-
}
|
|
1520
|
-
cursor = typeof result.nextCursor === 'string' && result.nextCursor.length > 0 ? result.nextCursor : undefined;
|
|
1521
|
-
if (cursor === undefined)
|
|
1522
|
-
break;
|
|
1523
|
-
}
|
|
1524
|
-
if (pendingThreads !== undefined) {
|
|
1525
|
-
const listedIds = new Set(threads.map(thread => string(thread.id)).filter((id) => id !== undefined));
|
|
1526
|
-
for (const [threadId, thread] of pendingThreads) {
|
|
1527
|
-
if (listedIds.has(threadId))
|
|
1528
|
-
pendingThreads.delete(threadId);
|
|
1529
|
-
else
|
|
1530
|
-
threads.unshift(thread);
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
if (projects.length === 0) {
|
|
1534
|
-
projects = deriveCodexCwdWorkspaces(threads).map(workspace => ({
|
|
1535
|
-
id: workspace.id,
|
|
1536
|
-
name: workspace.name,
|
|
1537
|
-
roots: [{ path: workspace.path }],
|
|
1538
|
-
position: workspace.position,
|
|
1539
|
-
createdAt: workspace.createdAt,
|
|
1540
|
-
updatedAt: workspace.updatedAt,
|
|
1541
|
-
}));
|
|
1542
|
-
}
|
|
1543
|
-
const projected = threads.map(thread => {
|
|
1544
|
-
const session = projectCodexThread(thread);
|
|
1545
|
-
return session === undefined ? undefined : { thread, session };
|
|
1546
|
-
}).filter((value) => value !== undefined);
|
|
1547
|
-
const projectWorkspaces = projects.map(projectWorkspace);
|
|
1548
|
-
const workspaceByProjectId = new Map(projects.map((project, index) => [project.id, projectWorkspaces[index]]));
|
|
1549
|
-
const visibleSessionIds = new Set();
|
|
1550
|
-
const visibleThreads = [];
|
|
1551
|
-
for (const { thread, session } of projected) {
|
|
1552
|
-
const projectWorkspaceForSession = findProjectWorkspace(thread, session, projects, workspaceByProjectId);
|
|
1553
|
-
if (projectWorkspaceForSession !== undefined) {
|
|
1554
|
-
addSessionToWorkspace(projectWorkspaceForSession, session);
|
|
1555
|
-
visibleSessionIds.add(session.id);
|
|
1556
|
-
visibleThreads.push(thread);
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
const sessions = projected
|
|
1560
|
-
.map(value => value.session)
|
|
1561
|
-
.filter(session => visibleSessionIds.has(session.id));
|
|
1562
|
-
return {
|
|
1563
|
-
threads: visibleThreads,
|
|
1564
|
-
sessions,
|
|
1565
|
-
projects,
|
|
1566
|
-
workspaces: projectWorkspaces,
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1569
|
-
async function loadCodexProjects(client, signal) {
|
|
1570
|
-
const projects = [];
|
|
1571
|
-
let cursor;
|
|
1572
|
-
try {
|
|
1573
|
-
for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
|
|
1574
|
-
const result = record(await client.request('project/list', {
|
|
1575
|
-
limit: CODEX_PAGE_LIMIT,
|
|
1576
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
1577
|
-
}, signal));
|
|
1578
|
-
for (const value of array(result.data)) {
|
|
1579
|
-
const project = projectCodexProject(value, projects.length);
|
|
1580
|
-
if (project !== undefined && !projects.some(item => item.id === project.id))
|
|
1581
|
-
projects.push(project);
|
|
1582
|
-
}
|
|
1583
|
-
cursor = typeof result.nextCursor === 'string' && result.nextCursor.length > 0 ? result.nextCursor : undefined;
|
|
1584
|
-
if (cursor === undefined)
|
|
1585
|
-
break;
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
catch (error) {
|
|
1589
|
-
if (isProjectListUnsupported(error))
|
|
1590
|
-
return [];
|
|
1591
|
-
throw error;
|
|
1592
|
-
}
|
|
1593
|
-
return projects.sort((left, right) => left.position - right.position || left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
|
|
1594
|
-
}
|
|
1595
|
-
async function loadModelDirectory(client, signal) {
|
|
1596
|
-
const models = new Map();
|
|
1597
|
-
let defaultModelId;
|
|
1598
|
-
let cursor;
|
|
1599
|
-
for (let page = 0; page < MAX_CODEX_PAGES; page += 1) {
|
|
1600
|
-
const result = record(await client.request('model/list', {
|
|
1601
|
-
limit: CODEX_PAGE_LIMIT,
|
|
1602
|
-
includeHidden: false,
|
|
1603
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
1604
|
-
}, signal));
|
|
1605
|
-
for (const value of array(result.data)) {
|
|
1606
|
-
const source = record(value);
|
|
1607
|
-
if (source.hidden === true)
|
|
1608
|
-
continue;
|
|
1609
|
-
const id = string(source.model) ?? string(source.id);
|
|
1610
|
-
if (id === undefined || models.has(id))
|
|
1611
|
-
continue;
|
|
1612
|
-
const efforts = array(source.supportedReasoningEfforts).map(value => {
|
|
1613
|
-
const effort = record(value);
|
|
1614
|
-
const effortId = string(effort.reasoningEffort);
|
|
1615
|
-
if (effortId === undefined)
|
|
1616
|
-
return undefined;
|
|
1617
|
-
const description = string(effort.description);
|
|
1618
|
-
return {
|
|
1619
|
-
id: effortId,
|
|
1620
|
-
name: reasoningEffortName(effortId),
|
|
1621
|
-
...(description === undefined ? {} : { description }),
|
|
1622
|
-
};
|
|
1623
|
-
}).filter((value) => value !== undefined);
|
|
1624
|
-
const defaultEffort = string(source.defaultReasoningEffort);
|
|
1625
|
-
const description = string(source.description);
|
|
1626
|
-
const model = {
|
|
1627
|
-
id,
|
|
1628
|
-
name: string(source.displayName) ?? id,
|
|
1629
|
-
...(description === undefined ? {} : { description }),
|
|
1630
|
-
...(efforts.length === 0 ? {} : {
|
|
1631
|
-
reasoning: {
|
|
1632
|
-
efforts,
|
|
1633
|
-
...(defaultEffort === undefined || !efforts.some(effort => effort.id === defaultEffort)
|
|
1634
|
-
? {}
|
|
1635
|
-
: { defaultEffort }),
|
|
1636
|
-
},
|
|
1637
|
-
}),
|
|
1638
|
-
};
|
|
1639
|
-
models.set(id, model);
|
|
1640
|
-
if (source.isDefault === true)
|
|
1641
|
-
defaultModelId = id;
|
|
1642
|
-
}
|
|
1643
|
-
cursor = typeof result.nextCursor === 'string' && result.nextCursor.length > 0 ? result.nextCursor : undefined;
|
|
1644
|
-
if (cursor === undefined)
|
|
1645
|
-
break;
|
|
1646
|
-
}
|
|
1647
|
-
const defaultModel = models.get(defaultModelId ?? '') ?? models.values().next().value;
|
|
1648
|
-
if (defaultModel === undefined)
|
|
1649
|
-
throw new Error('CodeX did not advertise any available models.');
|
|
1650
|
-
const defaultEffort = defaultModel.reasoning?.defaultEffort;
|
|
1651
|
-
return {
|
|
1652
|
-
default: {
|
|
1653
|
-
provider: CODEX_PROVIDER,
|
|
1654
|
-
model: defaultModel.id,
|
|
1655
|
-
...(defaultEffort === undefined ? {} : { reasoningEffort: defaultEffort }),
|
|
1656
|
-
},
|
|
1657
|
-
groups: [{ id: CODEX_PROVIDER, name: 'CodeX', models: [...models.values()] }],
|
|
1658
|
-
models,
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
export function projectCodexNativeHistory(thread, sessionId, sessionGeneration = 'legacy') {
|
|
1662
|
-
const entries = [];
|
|
1663
|
-
let seq = 0;
|
|
1664
|
-
let turnNumber = 0;
|
|
1665
|
-
const active = activeTurnId(thread.turns);
|
|
1666
|
-
const append = (type, data, time = Date.now(), view, sourceEventSeqs) => {
|
|
1667
|
-
const eventSeq = seq++;
|
|
1668
|
-
entries.push({
|
|
1669
|
-
type: 'event',
|
|
1670
|
-
event: {
|
|
1671
|
-
type,
|
|
1672
|
-
seq: eventSeq,
|
|
1673
|
-
time,
|
|
1674
|
-
data: adaptEventData(type, data, sessionGeneration),
|
|
1675
|
-
...(sourceEventSeqs === undefined ? {} : { sourceEventSeqs }),
|
|
1676
|
-
...(isSurfaceEvent(type) ? { surfaceOp: 'append' } : {}),
|
|
1677
|
-
},
|
|
1678
|
-
...(view === undefined ? {} : { view }),
|
|
1679
|
-
});
|
|
1680
|
-
return eventSeq;
|
|
1681
|
-
};
|
|
1682
|
-
for (const rawTurn of array(thread.turns)) {
|
|
1683
|
-
const turn = record(rawTurn);
|
|
1684
|
-
const turnItems = array(turn.items).map(record);
|
|
1685
|
-
const toolImages = turnItems.flatMap(toolOutputImageBlocks);
|
|
1686
|
-
let imageOwner = -1;
|
|
1687
|
-
for (let index = 0; index < turnItems.length; index += 1) {
|
|
1688
|
-
if (turnItems[index].type === 'agentMessage')
|
|
1689
|
-
imageOwner = index;
|
|
1690
|
-
}
|
|
1691
|
-
turnNumber += 1;
|
|
1692
|
-
const turnActive = isActiveCodexTurn(turn);
|
|
1693
|
-
const time = normalizeTime(turn.createdAt) || normalizeTime(turn.startedAt) || normalizeTime(thread.createdAt) || Date.now();
|
|
1694
|
-
append('turn/start', { turn: turnNumber }, time);
|
|
1695
|
-
append('step/start', { turn: turnNumber, step: 1 }, time);
|
|
1696
|
-
for (let itemIndex = 0; itemIndex < turnItems.length; itemIndex += 1) {
|
|
1697
|
-
const item = turnItems[itemIndex];
|
|
1698
|
-
let toolCallSeq;
|
|
1699
|
-
for (const event of itemEvents(item, turnNumber, 1, undefined, modelSelection(), sessionId, itemIndex === imageOwner ? toolImages : [])) {
|
|
1700
|
-
const eventSeq = append(event.type, event.data, normalizeTime(item.createdAt) || time, event.view, event.type === 'tool/result' && toolCallSeq !== undefined ? [toolCallSeq] : undefined);
|
|
1701
|
-
if (event.type === 'tool/call')
|
|
1702
|
-
toolCallSeq = eventSeq;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
if (turnActive)
|
|
1706
|
-
continue;
|
|
1707
|
-
append('step/end', { turn: turnNumber, step: 1 }, normalizeTime(turn.updatedAt) || normalizeTime(turn.completedAt) || time);
|
|
1708
|
-
append('turn/end', {
|
|
1709
|
-
turn: turnNumber,
|
|
1710
|
-
reason: turn.status === 'failed' || turn.error !== undefined && turn.error !== null
|
|
1711
|
-
? { kind: 'error', error: { message: 'CodeX turn failed.', code: 'codex-turn-failed' } }
|
|
1712
|
-
: { kind: 'completed' },
|
|
1713
|
-
}, normalizeTime(turn.updatedAt) || normalizeTime(turn.completedAt) || time);
|
|
1714
|
-
}
|
|
1715
|
-
const projected = projectCodexThread(thread);
|
|
1716
|
-
return {
|
|
1717
|
-
header: {
|
|
1718
|
-
version: sessionGeneration === 'v3' ? 3 : 1,
|
|
1719
|
-
id: sessionId,
|
|
1720
|
-
createdAt: projected?.createdAt ?? Date.now(),
|
|
1721
|
-
...(projected?.cwd === undefined ? {} : { cwd: projected.cwd }),
|
|
1722
|
-
...(sessionGeneration === 'v3' ? { isSeeded: false } : {}),
|
|
1723
|
-
},
|
|
1724
|
-
entries,
|
|
1725
|
-
lastSeq: seq - 1,
|
|
1726
|
-
nextTurn: turnNumber,
|
|
1727
|
-
...(active === undefined ? {} : { activeTurnId: active }),
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
export function paginateCodexNativeHistory(history, request) {
|
|
1731
|
-
const throughSeq = Math.min(request.throughSeq ?? history.lastSeq, history.lastSeq);
|
|
1732
|
-
const endSeq = Math.min(throughSeq, request.beforeSeq === undefined ? throughSeq : request.beforeSeq - 1);
|
|
1733
|
-
const window = endSeq < 0 ? [] : history.entries.filter(entry => entry.event.seq <= endSeq);
|
|
1734
|
-
const maxMessages = request.maxMessages ?? DEFAULT_HISTORY_MESSAGES;
|
|
1735
|
-
let messages = 0;
|
|
1736
|
-
let cut = 0;
|
|
1737
|
-
for (let index = window.length - 1; index >= 0; index -= 1) {
|
|
1738
|
-
const event = window[index].event;
|
|
1739
|
-
if (!isSurfaceEvent(event.type) || event.surfaceOp !== 'append')
|
|
1740
|
-
continue;
|
|
1741
|
-
messages += 1;
|
|
1742
|
-
if (messages >= maxMessages) {
|
|
1743
|
-
cut = Math.min(event.seq, ...(event.sourceEventSeqs ?? []));
|
|
1744
|
-
break;
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
return {
|
|
1748
|
-
header: history.header,
|
|
1749
|
-
cursor: history.lastSeq,
|
|
1750
|
-
nextTurn: history.nextTurn,
|
|
1751
|
-
records: window.filter(entry => entry.event.seq >= cut),
|
|
1752
|
-
hasMore: window.some(entry => entry.event.seq < cut),
|
|
1753
|
-
...(history.activeTurnId === undefined ? {} : { activeTurnId: history.activeTurnId }),
|
|
1754
|
-
};
|
|
1755
|
-
}
|
|
1756
|
-
function adaptEventData(type, data, sessionGeneration) {
|
|
1757
|
-
if (sessionGeneration !== 'v3' || type !== 'assistant/message')
|
|
1758
|
-
return data;
|
|
1759
|
-
const value = record(data);
|
|
1760
|
-
return Array.isArray(value.stream) ? value : { ...value, stream: [] };
|
|
1761
|
-
}
|
|
1762
|
-
function adaptCodexHistoryPage(value, sessionGeneration) {
|
|
1763
|
-
if (sessionGeneration !== 'v3')
|
|
1764
|
-
return value;
|
|
1765
|
-
const page = record(value);
|
|
1766
|
-
const sourceHeader = record(page.header);
|
|
1767
|
-
const { seedLength: _seedLength, ...header } = sourceHeader;
|
|
1768
|
-
const records = Array.isArray(page.records)
|
|
1769
|
-
? page.records.map(raw => {
|
|
1770
|
-
const entry = record(raw);
|
|
1771
|
-
const sourceEvent = record(entry.event);
|
|
1772
|
-
const sourceSurfaceOp = record(sourceEvent.surfaceOp);
|
|
1773
|
-
const surfaceOp = sourceEvent.surfaceOp === 'append'
|
|
1774
|
-
? 'append'
|
|
1775
|
-
: sourceSurfaceOp.op === 'replace'
|
|
1776
|
-
&& integer(sourceSurfaceOp.startSeq) !== undefined
|
|
1777
|
-
&& integer(sourceSurfaceOp.endSeq) !== undefined
|
|
1778
|
-
? sourceSurfaceOp
|
|
1779
|
-
: sourceSurfaceOp.op === 'replace'
|
|
1780
|
-
&& integer(sourceSurfaceOp.start) !== undefined
|
|
1781
|
-
&& integer(sourceSurfaceOp.end) !== undefined
|
|
1782
|
-
? {
|
|
1783
|
-
op: 'replace',
|
|
1784
|
-
startSeq: sourceSurfaceOp.start,
|
|
1785
|
-
endSeq: sourceSurfaceOp.end,
|
|
1786
|
-
}
|
|
1787
|
-
: sourceEvent.surfaceOp;
|
|
1788
|
-
return {
|
|
1789
|
-
...entry,
|
|
1790
|
-
event: {
|
|
1791
|
-
...sourceEvent,
|
|
1792
|
-
data: adaptEventData(string(sourceEvent.type) ?? '', sourceEvent.data, sessionGeneration),
|
|
1793
|
-
...(surfaceOp === undefined ? {} : { surfaceOp }),
|
|
1794
|
-
},
|
|
1795
|
-
};
|
|
1796
|
-
})
|
|
1797
|
-
: page.records;
|
|
1798
|
-
return {
|
|
1799
|
-
...page,
|
|
1800
|
-
header: { ...header, version: 3, isSeeded: sourceHeader.isSeeded === true },
|
|
1801
|
-
records,
|
|
1802
|
-
};
|
|
1803
|
-
}
|
|
1804
|
-
function itemEvents(item, turn, step, requestId, selection = modelSelection(), sessionId = CODEX_SESSION_PREFIX, supplementalImages = []) {
|
|
1805
|
-
const type = string(item.type);
|
|
1806
|
-
const id = string(item.id) ?? `${turn}:${step}:${hashString(JSON.stringify(item))}`;
|
|
1807
|
-
const text = itemText(item);
|
|
1808
|
-
if (type === 'userMessage')
|
|
1809
|
-
return [{
|
|
1810
|
-
type: 'user/message',
|
|
1811
|
-
data: {
|
|
1812
|
-
id,
|
|
1813
|
-
role: 'user',
|
|
1814
|
-
content: messageContent(item, `${sessionId}:${id}`, 'text', text),
|
|
1815
|
-
source: requestId === undefined || requestId === '' ? { kind: 'user' } : { kind: 'user', rpcId: requestId },
|
|
1816
|
-
},
|
|
1817
|
-
}];
|
|
1818
|
-
if (type === 'agentMessage' || type === 'reasoning' || type === 'plan')
|
|
1819
|
-
return [{
|
|
1820
|
-
type: 'assistant/message',
|
|
1821
|
-
data: {
|
|
1822
|
-
turn,
|
|
1823
|
-
step,
|
|
1824
|
-
message: {
|
|
1825
|
-
id,
|
|
1826
|
-
role: 'assistant',
|
|
1827
|
-
content: messageContent(item, `${sessionId}:${id}`, type === 'reasoning' ? 'reasoning' : 'text', text, type === 'agentMessage' ? supplementalImages : []),
|
|
1828
|
-
source: { kind: 'model', provider: selection.provider, model: selection.model },
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
}];
|
|
1832
|
-
if (type !== undefined && isToolItemType(type)) {
|
|
1833
|
-
const name = toolEventName(type);
|
|
1834
|
-
const args = toolArguments(item);
|
|
1835
|
-
return [
|
|
1836
|
-
{
|
|
1837
|
-
type: 'tool/call',
|
|
1838
|
-
data: { turn, step, callId: id, name, arguments: JSON.stringify(args) },
|
|
1839
|
-
view: { for: 'call', view: toolCallView(item, type, args) },
|
|
1840
|
-
},
|
|
1841
|
-
toolResultEvent(item, turn, step, id, toolResultText(item, type, text)),
|
|
1842
|
-
];
|
|
1843
|
-
}
|
|
1844
|
-
if (type === 'error')
|
|
1845
|
-
return [{
|
|
1846
|
-
type: 'assistant/message',
|
|
1847
|
-
data: {
|
|
1848
|
-
turn,
|
|
1849
|
-
step,
|
|
1850
|
-
message: {
|
|
1851
|
-
id,
|
|
1852
|
-
role: 'assistant',
|
|
1853
|
-
content: [{ type: 'text', text: text ?? 'CodeX reported an error.' }],
|
|
1854
|
-
source: { kind: 'model', provider: selection.provider, model: selection.model },
|
|
1855
|
-
},
|
|
1856
|
-
},
|
|
1857
|
-
}];
|
|
1858
|
-
return [];
|
|
1859
|
-
}
|
|
1860
|
-
function messageContent(item, attachmentSeed, fallbackType, fallbackText, supplementalImages = []) {
|
|
1861
|
-
const source = item.content ?? item.input;
|
|
1862
|
-
const blocks = contentBlocks(source, attachmentSeed);
|
|
1863
|
-
const images = contentBlocks(supplementalImages, `${attachmentSeed}:tool-output`);
|
|
1864
|
-
if (blocks.length > 0 || images.length > 0) {
|
|
1865
|
-
return [...(blocks.length > 0 ? blocks : [{ type: fallbackType, text: fallbackText ?? '' }]), ...images];
|
|
1866
|
-
}
|
|
1867
|
-
return [{ type: fallbackType, text: fallbackText ?? '' }];
|
|
1868
|
-
}
|
|
1869
|
-
function toolResultEvent(item, turn, step, id, resultText) {
|
|
1870
|
-
const type = string(item.type) ?? 'unknown';
|
|
1871
|
-
return {
|
|
1872
|
-
type: 'tool/result',
|
|
1873
|
-
data: {
|
|
1874
|
-
turn,
|
|
1875
|
-
step,
|
|
1876
|
-
message: {
|
|
1877
|
-
id: `${id}:result`,
|
|
1878
|
-
role: 'user',
|
|
1879
|
-
content: [{
|
|
1880
|
-
type: 'tool-result',
|
|
1881
|
-
toolCallId: id,
|
|
1882
|
-
content: [{ type: 'text', text: resultText }],
|
|
1883
|
-
...(item.status === 'failed' || item.success === false ? { isError: true } : {}),
|
|
1884
|
-
}],
|
|
1885
|
-
source: { kind: 'tool', callId: id },
|
|
1886
|
-
},
|
|
1887
|
-
},
|
|
1888
|
-
view: { for: 'result', view: toolResultView(item, type, resultText) },
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
function toolResultContentReplacement(event) {
|
|
1892
|
-
const data = record(event.data);
|
|
1893
|
-
const message = record(data.message);
|
|
1894
|
-
const content = array(message.content);
|
|
1895
|
-
const result = record(content[0]);
|
|
1896
|
-
if (result.isError === undefined)
|
|
1897
|
-
return event;
|
|
1898
|
-
const { isError: _isError, ...stableResult } = result;
|
|
1899
|
-
return {
|
|
1900
|
-
...event,
|
|
1901
|
-
data: {
|
|
1902
|
-
...data,
|
|
1903
|
-
message: { ...message, content: [stableResult] },
|
|
1904
|
-
},
|
|
1905
|
-
};
|
|
1906
|
-
}
|
|
1907
|
-
function isToolItemType(type) {
|
|
1908
|
-
return type === 'commandExecution'
|
|
1909
|
-
|| type === 'mcpToolCall'
|
|
1910
|
-
|| type === 'dynamicToolCall'
|
|
1911
|
-
|| type === 'fileChange'
|
|
1912
|
-
|| type === 'functionCallOutput'
|
|
1913
|
-
|| type === 'hookPrompt'
|
|
1914
|
-
|| type === 'collabAgentToolCall'
|
|
1915
|
-
|| type === 'subAgentActivity'
|
|
1916
|
-
|| type === 'webSearch'
|
|
1917
|
-
|| type === 'imageView'
|
|
1918
|
-
|| type === 'imageGeneration'
|
|
1919
|
-
|| type === 'sleep'
|
|
1920
|
-
|| type === 'enteredReviewMode'
|
|
1921
|
-
|| type === 'exitedReviewMode'
|
|
1922
|
-
|| type === 'contextCompaction';
|
|
1923
|
-
}
|
|
1924
|
-
function toolEventName(type) {
|
|
1925
|
-
if (type === 'fileChange')
|
|
1926
|
-
return 'codex.fileChange';
|
|
1927
|
-
if (type === 'commandExecution')
|
|
1928
|
-
return 'codex.command';
|
|
1929
|
-
if (type === 'webSearch')
|
|
1930
|
-
return 'codex.webSearch';
|
|
1931
|
-
if (type === 'collabAgentToolCall' || type === 'subAgentActivity')
|
|
1932
|
-
return 'codex.subagent';
|
|
1933
|
-
if (type === 'imageView' || type === 'imageGeneration')
|
|
1934
|
-
return 'codex.image';
|
|
1935
|
-
if (type === 'enteredReviewMode' || type === 'exitedReviewMode')
|
|
1936
|
-
return 'codex.reviewMode';
|
|
1937
|
-
if (type === 'contextCompaction')
|
|
1938
|
-
return 'codex.compaction';
|
|
1939
|
-
return `codex.${type}`;
|
|
1940
|
-
}
|
|
1941
|
-
function toolResultText(item, type, text) {
|
|
1942
|
-
if (type === 'fileChange')
|
|
1943
|
-
return fileChangeSummary(item);
|
|
1944
|
-
if (type === 'webSearch') {
|
|
1945
|
-
const count = array(item.results).length;
|
|
1946
|
-
return count === 0 ? 'Web search completed.' : `Web search returned ${count} result${count === 1 ? '' : 's'}.`;
|
|
1947
|
-
}
|
|
1948
|
-
if (type === 'imageView')
|
|
1949
|
-
return `Viewed image: ${string(item.path) ?? 'image'}`;
|
|
1950
|
-
if (type === 'imageGeneration') {
|
|
1951
|
-
const savedPath = string(item.savedPath);
|
|
1952
|
-
return savedPath === undefined ? `Image generation ${string(item.status) ?? 'completed'}.` : `Generated image: ${savedPath}`;
|
|
1953
|
-
}
|
|
1954
|
-
if (type === 'contextCompaction')
|
|
1955
|
-
return 'CodeX compacted the conversation context.';
|
|
1956
|
-
if (type === 'enteredReviewMode')
|
|
1957
|
-
return `Entered review mode${string(item.review) ? `: ${string(item.review)}` : '.'}`;
|
|
1958
|
-
if (type === 'exitedReviewMode')
|
|
1959
|
-
return `Exited review mode${string(item.review) ? `: ${string(item.review)}` : '.'}`;
|
|
1960
|
-
if (type === 'sleep')
|
|
1961
|
-
return `Waited ${integer(item.durationMs) ?? 0} ms.`;
|
|
1962
|
-
if (type === 'subAgentActivity') {
|
|
1963
|
-
return [string(record(item.kind).type) ?? string(item.kind) ?? 'Subagent activity', string(item.agentPath)]
|
|
1964
|
-
.filter((value) => value !== undefined).join(': ');
|
|
1965
|
-
}
|
|
1966
|
-
return text ?? itemText(record(item.result)) ?? itemText(record(item.output)) ?? string(item.status) ?? type;
|
|
1967
|
-
}
|
|
1968
|
-
function isSurfaceEvent(type) {
|
|
1969
|
-
return type === 'user/message' || type === 'assistant/message' || type === 'tool/result';
|
|
1970
|
-
}
|
|
1971
|
-
function toolCallView(item, type, args) {
|
|
1972
|
-
if (type === 'commandExecution') {
|
|
1973
|
-
return {
|
|
1974
|
-
card: 'terminal',
|
|
1975
|
-
title: commandText(item.command) ?? 'CodeX command',
|
|
1976
|
-
...(typeof item.cwd === 'string' ? { cwd: item.cwd } : {}),
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
const locations = fileLocations(item);
|
|
1980
|
-
if (type === 'fileChange') {
|
|
1981
|
-
return {
|
|
1982
|
-
card: 'generic',
|
|
1983
|
-
title: locations.length === 0 ? 'CodeX file changes' : `Modify ${locations.length} file${locations.length === 1 ? '' : 's'}`,
|
|
1984
|
-
kind: 'edit',
|
|
1985
|
-
rawInput: args,
|
|
1986
|
-
...(locations.length === 0 ? {} : { locations }),
|
|
1987
|
-
};
|
|
1988
|
-
}
|
|
1989
|
-
if (type === 'webSearch') {
|
|
1990
|
-
return {
|
|
1991
|
-
card: 'generic',
|
|
1992
|
-
title: string(item.query) ?? 'CodeX web search',
|
|
1993
|
-
kind: 'search',
|
|
1994
|
-
rawInput: args,
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
if (type === 'imageView' || type === 'imageGeneration') {
|
|
1998
|
-
const path = string(item.path) ?? string(item.savedPath);
|
|
1999
|
-
return {
|
|
2000
|
-
card: 'generic',
|
|
2001
|
-
title: type === 'imageView' ? 'View image' : 'Generate image',
|
|
2002
|
-
kind: type === 'imageView' ? 'read' : 'other',
|
|
2003
|
-
...(path === undefined ? {} : { locations: [{ path }] }),
|
|
2004
|
-
};
|
|
2005
|
-
}
|
|
2006
|
-
if (type === 'collabAgentToolCall' || type === 'subAgentActivity') {
|
|
2007
|
-
return {
|
|
2008
|
-
card: 'generic',
|
|
2009
|
-
title: type === 'collabAgentToolCall'
|
|
2010
|
-
? `Subagent: ${string(item.tool) ?? 'collaboration'}`
|
|
2011
|
-
: `Subagent activity: ${string(item.agentPath) ?? string(item.agentThreadId) ?? 'agent'}`,
|
|
2012
|
-
kind: 'other',
|
|
2013
|
-
rawInput: args,
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
if (type === 'contextCompaction')
|
|
2017
|
-
return { card: 'generic', title: 'Compact conversation context', kind: 'other' };
|
|
2018
|
-
if (type === 'enteredReviewMode')
|
|
2019
|
-
return { card: 'generic', title: 'Enter review mode', kind: 'other' };
|
|
2020
|
-
if (type === 'exitedReviewMode')
|
|
2021
|
-
return { card: 'generic', title: 'Exit review mode', kind: 'other' };
|
|
2022
|
-
return {
|
|
2023
|
-
card: 'generic',
|
|
2024
|
-
title: toolDisplayName(item, type),
|
|
2025
|
-
kind: 'other',
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
function toolResultView(item, type, resultText) {
|
|
2029
|
-
if (type === 'commandExecution') {
|
|
2030
|
-
return {
|
|
2031
|
-
card: 'terminal',
|
|
2032
|
-
output: resultText,
|
|
2033
|
-
...(typeof item.exitCode === 'number' && Number.isSafeInteger(item.exitCode) ? { exitCode: item.exitCode } : {}),
|
|
2034
|
-
};
|
|
2035
|
-
}
|
|
2036
|
-
return {
|
|
2037
|
-
card: 'generic',
|
|
2038
|
-
...(type === 'fileChange' ? { title: item.status === 'inProgress' ? 'CodeX file changes' : 'CodeX file changes completed' } : {}),
|
|
2039
|
-
...(type === 'mcpToolCall' && item.status === 'inProgress'
|
|
2040
|
-
? { title: 'CodeX MCP tool in progress', content: [{ type: 'text', text: resultText }] }
|
|
2041
|
-
: {}),
|
|
2042
|
-
};
|
|
2043
|
-
}
|
|
2044
|
-
function toolDisplayName(item, type) {
|
|
2045
|
-
return string(item.name)
|
|
2046
|
-
?? string(item.tool)
|
|
2047
|
-
?? string(record(item.tool).name)
|
|
2048
|
-
?? (type === 'mcpToolCall' ? 'CodeX MCP tool' : humanizeItemType(type));
|
|
2049
|
-
}
|
|
2050
|
-
function fileLocations(item) {
|
|
2051
|
-
return array(item.changes)
|
|
2052
|
-
.map(value => string(record(value).path))
|
|
2053
|
-
.filter((path) => path !== undefined && path.length > 0)
|
|
2054
|
-
.map(path => ({ path }));
|
|
2055
|
-
}
|
|
2056
|
-
function nativeWorkspace(view) {
|
|
2057
|
-
const { sessionCount: _sessionCount, ...workspace } = view;
|
|
2058
|
-
return workspace;
|
|
2059
|
-
}
|
|
2060
|
-
function nativeVisibleWorkspaces(catalog, selectedWorkspaceId) {
|
|
2061
|
-
return catalog.workspaces.filter(workspace => workspace.sessionIds.length > 0 || workspace.workspaceId === selectedWorkspaceId);
|
|
2062
|
-
}
|
|
2063
|
-
function projectWorkspace(project) {
|
|
2064
|
-
const root = project.roots[0];
|
|
2065
|
-
return {
|
|
2066
|
-
workspaceId: codexProjectWorkspaceId(project.id),
|
|
2067
|
-
path: root.path,
|
|
2068
|
-
title: project.name.trim() || basename(root.path),
|
|
2069
|
-
sessionIds: [],
|
|
2070
|
-
sessionCount: 0,
|
|
2071
|
-
createdAt: isoTime(project.createdAt),
|
|
2072
|
-
updatedAt: isoTime(project.updatedAt || project.createdAt),
|
|
2073
|
-
};
|
|
2074
|
-
}
|
|
2075
|
-
function projectCodexProject(value, fallbackPosition) {
|
|
2076
|
-
const source = record(value);
|
|
2077
|
-
const id = string(source.id);
|
|
2078
|
-
if (id === undefined)
|
|
2079
|
-
return undefined;
|
|
2080
|
-
const roots = array(source.roots)
|
|
2081
|
-
.map(root => string(record(root).path))
|
|
2082
|
-
.filter((path) => path !== undefined && path.length > 0)
|
|
2083
|
-
.map(path => ({ path }));
|
|
2084
|
-
if (roots.length === 0)
|
|
2085
|
-
return undefined;
|
|
2086
|
-
return {
|
|
2087
|
-
id,
|
|
2088
|
-
name: string(source.name) ?? basename(roots[0].path),
|
|
2089
|
-
roots,
|
|
2090
|
-
position: finiteNumber(source.position) ?? fallbackPosition,
|
|
2091
|
-
createdAt: normalizeTime(source.createdAt),
|
|
2092
|
-
updatedAt: normalizeTime(source.updatedAt),
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
function findProjectWorkspace(thread, session, projects, workspaceByProjectId) {
|
|
2096
|
-
const explicitProjectId = string(thread.projectId);
|
|
2097
|
-
const explicitWorkspace = explicitProjectId === undefined ? undefined : workspaceByProjectId.get(explicitProjectId);
|
|
2098
|
-
if (explicitWorkspace !== undefined)
|
|
2099
|
-
return explicitWorkspace;
|
|
2100
|
-
if (session.cwd === undefined)
|
|
2101
|
-
return undefined;
|
|
2102
|
-
const match = projects
|
|
2103
|
-
.flatMap(project => project.roots.map(root => ({ project, root })))
|
|
2104
|
-
.filter(value => containsPath(value.root.path, session.cwd))
|
|
2105
|
-
.sort((left, right) => {
|
|
2106
|
-
const length = right.root.path.length - left.root.path.length;
|
|
2107
|
-
if (length !== 0)
|
|
2108
|
-
return length;
|
|
2109
|
-
const position = left.project.position - right.project.position;
|
|
2110
|
-
if (position !== 0)
|
|
2111
|
-
return position;
|
|
2112
|
-
return left.project.id.localeCompare(right.project.id);
|
|
2113
|
-
})[0];
|
|
2114
|
-
return match === undefined ? undefined : workspaceByProjectId.get(match.project.id);
|
|
2115
|
-
}
|
|
2116
|
-
function addSessionToWorkspace(workspace, session) {
|
|
2117
|
-
if (!workspace.sessionIds.includes(session.id)) {
|
|
2118
|
-
workspace.sessionIds.push(session.id);
|
|
2119
|
-
workspace.sessionCount = workspace.sessionIds.length;
|
|
2120
|
-
}
|
|
2121
|
-
const createdAt = isoTime(session.createdAt);
|
|
2122
|
-
const updatedAt = isoTime(session.updatedAt || session.createdAt);
|
|
2123
|
-
if (createdAt < workspace.createdAt)
|
|
2124
|
-
workspace.createdAt = createdAt;
|
|
2125
|
-
if (updatedAt > workspace.updatedAt)
|
|
2126
|
-
workspace.updatedAt = updatedAt;
|
|
2127
|
-
}
|
|
2128
|
-
function containsPath(root, candidate) {
|
|
2129
|
-
const normalizedRoot = normalizePathForCompare(root);
|
|
2130
|
-
const normalizedCandidate = normalizePathForCompare(candidate);
|
|
2131
|
-
return normalizedCandidate === normalizedRoot
|
|
2132
|
-
|| normalizedCandidate.startsWith(`${normalizedRoot}/`)
|
|
2133
|
-
|| normalizedCandidate.startsWith(`${normalizedRoot}\\`);
|
|
2134
|
-
}
|
|
2135
|
-
function normalizePathForCompare(path) {
|
|
2136
|
-
return path.replace(/[\\/]+$/u, '') || path;
|
|
2137
|
-
}
|
|
2138
|
-
function modelSelection() {
|
|
2139
|
-
return { provider: CODEX_PROVIDER, model: CODEX_MODEL };
|
|
2140
|
-
}
|
|
2141
|
-
function codexPromptInput(content) {
|
|
2142
|
-
if (content.length === 0 || content.length > MAX_CODEX_PROMPT_PARTS)
|
|
2143
|
-
return undefined;
|
|
2144
|
-
const input = [];
|
|
2145
|
-
for (const value of content) {
|
|
2146
|
-
if (!isRecord(value))
|
|
2147
|
-
return undefined;
|
|
2148
|
-
if (value.type === 'text') {
|
|
2149
|
-
if (typeof value.text !== 'string' || value.text.length === 0 || value.text.length > MAX_CODEX_PROMPT_TEXT)
|
|
2150
|
-
return undefined;
|
|
2151
|
-
input.push({ type: 'text', text: value.text });
|
|
2152
|
-
continue;
|
|
2153
|
-
}
|
|
2154
|
-
if (value.type === 'image') {
|
|
2155
|
-
if (typeof value.mediaType !== 'string' || !CODEX_IMAGE_MEDIA_TYPES.has(value.mediaType)
|
|
2156
|
-
|| typeof value.data !== 'string' || value.data.length > MAX_CODEX_IMAGE_BASE64
|
|
2157
|
-
|| !isCanonicalBase64(value.data))
|
|
2158
|
-
return undefined;
|
|
2159
|
-
input.push({ type: 'image', mediaType: value.mediaType, data: value.data });
|
|
2160
|
-
continue;
|
|
2161
|
-
}
|
|
2162
|
-
return undefined;
|
|
2163
|
-
}
|
|
2164
|
-
return input;
|
|
2165
|
-
}
|
|
2166
|
-
function modelSelectionProjection(selection) {
|
|
2167
|
-
return { lastUsed: selection, next: selection };
|
|
2168
|
-
}
|
|
2169
|
-
function codexPermissionsProjection(preset) {
|
|
2170
|
-
return {
|
|
2171
|
-
options: [
|
|
2172
|
-
{
|
|
2173
|
-
value: 'workspace-write',
|
|
2174
|
-
name: 'workspace-write',
|
|
2175
|
-
description: 'Write inside the workspace; wider command and file access requires one-time approval.',
|
|
2176
|
-
},
|
|
2177
|
-
{
|
|
2178
|
-
value: 'danger-full-access',
|
|
2179
|
-
name: 'danger-full-access',
|
|
2180
|
-
description: 'Full Host file access without approval prompts for subsequent turns in this virtual Session.',
|
|
2181
|
-
},
|
|
2182
|
-
],
|
|
2183
|
-
currentValue: preset ?? 'Host settings (not reported)',
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
function codexImageLimitsProjection() {
|
|
2187
|
-
return {
|
|
2188
|
-
maxImageBytes: 20 * 1024 * 1024,
|
|
2189
|
-
maxImagesPerMessage: MAX_CODEX_PROMPT_PARTS,
|
|
2190
|
-
maxMessageImageBytes: 100 * 1024 * 1024,
|
|
2191
|
-
maxImagePixels: 40_000_000,
|
|
2192
|
-
maxImageDimension: 8_192,
|
|
2193
|
-
mediaTypes: [...CODEX_IMAGE_MEDIA_TYPES],
|
|
2194
|
-
};
|
|
2195
|
-
}
|
|
2196
|
-
function isCodexPermissionPreset(value) {
|
|
2197
|
-
return value === 'workspace-write' || value === 'danger-full-access';
|
|
2198
|
-
}
|
|
2199
|
-
function modelCatalog(directory) {
|
|
2200
|
-
return {
|
|
2201
|
-
default: directory.default,
|
|
2202
|
-
routableProviders: [CODEX_PROVIDER],
|
|
2203
|
-
groups: directory.groups,
|
|
2204
|
-
failures: [],
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
function codexModelParams(selection) {
|
|
2208
|
-
return {
|
|
2209
|
-
model: selection.model,
|
|
2210
|
-
...(selection.reasoningEffort === undefined ? {} : { effort: selection.reasoningEffort }),
|
|
2211
|
-
};
|
|
2212
|
-
}
|
|
2213
|
-
function reasoningEffortName(effort) {
|
|
2214
|
-
const names = {
|
|
2215
|
-
none: 'None',
|
|
2216
|
-
minimal: 'Minimal',
|
|
2217
|
-
low: 'Low',
|
|
2218
|
-
medium: 'Medium',
|
|
2219
|
-
high: 'High',
|
|
2220
|
-
xhigh: 'Extra High',
|
|
2221
|
-
max: 'Max',
|
|
2222
|
-
ultra: 'Ultra',
|
|
2223
|
-
};
|
|
2224
|
-
return names[effort] ?? effort;
|
|
2225
|
-
}
|
|
2226
|
-
function displayTitle(session) {
|
|
2227
|
-
const value = session.title ?? session.preview;
|
|
2228
|
-
return value === undefined || value.trim() === '' ? null : value.slice(0, 256);
|
|
2229
|
-
}
|
|
2230
|
-
function itemText(value) {
|
|
2231
|
-
if (typeof value.text === 'string')
|
|
2232
|
-
return value.text;
|
|
2233
|
-
if (typeof value.content === 'string')
|
|
2234
|
-
return value.content;
|
|
2235
|
-
const content = Array.isArray(value.content)
|
|
2236
|
-
? value.content
|
|
2237
|
-
: Array.isArray(value.input) ? value.input : undefined;
|
|
2238
|
-
if (content !== undefined) {
|
|
2239
|
-
const text = content.map(part => {
|
|
2240
|
-
if (typeof part === 'string')
|
|
2241
|
-
return part;
|
|
2242
|
-
const block = record(part);
|
|
2243
|
-
return string(block.text) ?? string(block.content) ?? '';
|
|
2244
|
-
}).filter(Boolean).join('\n');
|
|
2245
|
-
if (text !== '')
|
|
2246
|
-
return text;
|
|
2247
|
-
}
|
|
2248
|
-
if (Array.isArray(value.summary))
|
|
2249
|
-
return value.summary.filter(item => typeof item === 'string').join('\n');
|
|
2250
|
-
if (typeof value.output === 'string')
|
|
2251
|
-
return value.output;
|
|
2252
|
-
if (typeof value.aggregatedOutput === 'string')
|
|
2253
|
-
return value.aggregatedOutput;
|
|
2254
|
-
return undefined;
|
|
2255
|
-
}
|
|
2256
|
-
function toolArguments(item) {
|
|
2257
|
-
if (item.type === 'commandExecution')
|
|
2258
|
-
return { command: commandText(item.command) ?? item.command ?? '' };
|
|
2259
|
-
if (item.type === 'fileChange')
|
|
2260
|
-
return {
|
|
2261
|
-
changes: array(item.changes).map(value => {
|
|
2262
|
-
const change = record(value);
|
|
2263
|
-
const kind = typeof change.kind === 'string' ? change.kind : string(record(change.kind).type);
|
|
2264
|
-
return {
|
|
2265
|
-
...(typeof change.path === 'string' ? { path: change.path } : {}),
|
|
2266
|
-
...(kind === undefined ? {} : { kind }),
|
|
2267
|
-
};
|
|
2268
|
-
}),
|
|
2269
|
-
};
|
|
2270
|
-
if (item.type === 'webSearch')
|
|
2271
|
-
return { query: string(item.query) ?? '' };
|
|
2272
|
-
if (item.type === 'imageView')
|
|
2273
|
-
return { path: string(item.path) ?? '' };
|
|
2274
|
-
if (item.type === 'imageGeneration')
|
|
2275
|
-
return {
|
|
2276
|
-
...(string(item.revisedPrompt) === undefined ? {} : { prompt: string(item.revisedPrompt) }),
|
|
2277
|
-
transparentBackground: item.transparentBackground === true,
|
|
2278
|
-
};
|
|
2279
|
-
if (item.type === 'collabAgentToolCall')
|
|
2280
|
-
return {
|
|
2281
|
-
tool: string(item.tool) ?? 'collaboration',
|
|
2282
|
-
receiverThreadIds: array(item.receiverThreadIds).filter(value => typeof value === 'string'),
|
|
2283
|
-
...(string(item.model) === undefined ? {} : { model: string(item.model) }),
|
|
2284
|
-
};
|
|
2285
|
-
if (item.type === 'subAgentActivity')
|
|
2286
|
-
return {
|
|
2287
|
-
agentThreadId: string(item.agentThreadId) ?? '',
|
|
2288
|
-
agentPath: string(item.agentPath) ?? '',
|
|
2289
|
-
kind: string(record(item.kind).type) ?? string(item.kind) ?? 'activity',
|
|
2290
|
-
};
|
|
2291
|
-
if (item.type === 'sleep')
|
|
2292
|
-
return { durationMs: integer(item.durationMs) ?? 0 };
|
|
2293
|
-
if (item.type === 'enteredReviewMode' || item.type === 'exitedReviewMode')
|
|
2294
|
-
return {
|
|
2295
|
-
review: string(item.review) ?? '',
|
|
2296
|
-
};
|
|
2297
|
-
if (item.type === 'contextCompaction')
|
|
2298
|
-
return {};
|
|
2299
|
-
return { name: item.name ?? item.tool ?? item.type ?? 'tool', arguments: item.arguments ?? item.input ?? {} };
|
|
2300
|
-
}
|
|
2301
|
-
function fileChangeSummary(item) {
|
|
2302
|
-
const changes = array(item.changes).map(value => {
|
|
2303
|
-
const change = record(value);
|
|
2304
|
-
const path = string(change.path);
|
|
2305
|
-
const kind = typeof change.kind === 'string' ? change.kind : string(record(change.kind).type);
|
|
2306
|
-
return [kind, path].filter((part) => part !== undefined && part.length > 0).join(' ');
|
|
2307
|
-
}).filter(Boolean);
|
|
2308
|
-
return changes.length === 0 ? 'File changes completed.' : changes.join('\n');
|
|
2309
|
-
}
|
|
2310
|
-
function sanitizedFileChanges(value) {
|
|
2311
|
-
return array(value).flatMap(raw => {
|
|
2312
|
-
const change = record(raw);
|
|
2313
|
-
const path = string(change.path);
|
|
2314
|
-
if (path === undefined)
|
|
2315
|
-
return [];
|
|
2316
|
-
const kind = typeof change.kind === 'string' ? change.kind : string(record(change.kind).type);
|
|
2317
|
-
return [{ path, ...(kind === undefined ? {} : { kind }) }];
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
function appendBoundedText(current, delta) {
|
|
2321
|
-
const value = `${current}${delta}`;
|
|
2322
|
-
if (value.length <= MAX_LIVE_TOOL_OUTPUT)
|
|
2323
|
-
return value;
|
|
2324
|
-
return `… earlier output omitted …\n${value.slice(value.length - MAX_LIVE_TOOL_OUTPUT)}`;
|
|
2325
|
-
}
|
|
2326
|
-
function humanizeItemType(type) {
|
|
2327
|
-
const names = {
|
|
2328
|
-
functionCallOutput: 'CodeX function output',
|
|
2329
|
-
hookPrompt: 'CodeX hook prompt',
|
|
2330
|
-
dynamicToolCall: 'CodeX tool',
|
|
2331
|
-
collabAgentToolCall: 'CodeX subagent',
|
|
2332
|
-
subAgentActivity: 'CodeX subagent activity',
|
|
2333
|
-
webSearch: 'CodeX web search',
|
|
2334
|
-
imageView: 'CodeX image view',
|
|
2335
|
-
imageGeneration: 'CodeX image generation',
|
|
2336
|
-
sleep: 'CodeX wait',
|
|
2337
|
-
enteredReviewMode: 'CodeX review mode',
|
|
2338
|
-
exitedReviewMode: 'CodeX review mode',
|
|
2339
|
-
contextCompaction: 'CodeX context compaction',
|
|
2340
|
-
};
|
|
2341
|
-
return names[type] ?? 'CodeX tool';
|
|
2342
|
-
}
|
|
2343
|
-
function commandText(value) {
|
|
2344
|
-
if (typeof value === 'string')
|
|
2345
|
-
return value;
|
|
2346
|
-
if (Array.isArray(value))
|
|
2347
|
-
return value.filter(item => typeof item === 'string').join(' ');
|
|
2348
|
-
const source = record(value);
|
|
2349
|
-
return string(source.command) ?? string(source.text);
|
|
2350
|
-
}
|
|
2351
|
-
function sessionIdFromAddress(address) {
|
|
2352
|
-
if (address.kind === 'session')
|
|
2353
|
-
return requiredString(address.sessionId, 'sessionId');
|
|
2354
|
-
return requiredString(address.childSessionId, 'childSessionId');
|
|
2355
|
-
}
|
|
2356
|
-
function nativeThreadId(sessionId) {
|
|
2357
|
-
if (!sessionId.startsWith(CODEX_SESSION_PREFIX) || sessionId.length === CODEX_SESSION_PREFIX.length) {
|
|
2358
|
-
throw new Error('The selected Session does not belong to CodeX.');
|
|
2359
|
-
}
|
|
2360
|
-
return sessionId.slice(CODEX_SESSION_PREFIX.length);
|
|
2361
|
-
}
|
|
2362
|
-
function activeTurnId(value) {
|
|
2363
|
-
if (!Array.isArray(value))
|
|
2364
|
-
return undefined;
|
|
2365
|
-
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
2366
|
-
const turn = record(value[index]);
|
|
2367
|
-
if (isActiveCodexTurn(turn) && typeof turn.id === 'string' && turn.id.length > 0)
|
|
2368
|
-
return turn.id;
|
|
2369
|
-
}
|
|
2370
|
-
return undefined;
|
|
2371
|
-
}
|
|
2372
|
-
function isActiveCodexTurn(turn) {
|
|
2373
|
-
return turn.status === 'inProgress' || turn.status === 'running';
|
|
2374
|
-
}
|
|
2375
|
-
function carrierArgs(payload) {
|
|
2376
|
-
return record(record(payload).args);
|
|
2377
|
-
}
|
|
2378
|
-
function requestArg(args) {
|
|
2379
|
-
return record(args.request ?? args._request ?? args);
|
|
2380
|
-
}
|
|
2381
|
-
function rcEndpoint(endpoint) {
|
|
2382
|
-
if (endpoint === 'workspace.list')
|
|
2383
|
-
return 'workspace/list';
|
|
2384
|
-
return endpoint.replace('.', '/');
|
|
2385
|
-
}
|
|
2386
|
-
function success(value) {
|
|
2387
|
-
return { ok: true, value };
|
|
2388
|
-
}
|
|
2389
|
-
function failure(code, message, details = {}) {
|
|
2390
|
-
return { ok: false, error: { code, message, details } };
|
|
2391
|
-
}
|
|
2392
|
-
function ok(value) {
|
|
2393
|
-
return value === undefined ? { ok: true } : { ok: true, value };
|
|
2394
|
-
}
|
|
2395
|
-
function business(value) {
|
|
2396
|
-
const result = record(value);
|
|
2397
|
-
if (result.ok === true)
|
|
2398
|
-
return ok(result.value);
|
|
2399
|
-
if (result.ok === false) {
|
|
2400
|
-
const error = record(result.error);
|
|
2401
|
-
return fail(string(error.code) ?? 'internal', string(error.message) ?? 'CodeX virtual Harness rejected the request.', record(error.details));
|
|
2402
|
-
}
|
|
2403
|
-
return ok(value);
|
|
2404
|
-
}
|
|
2405
|
-
function isCodexDirectoryListing(value) {
|
|
2406
|
-
const listing = record(value);
|
|
2407
|
-
return typeof listing.path === 'string'
|
|
2408
|
-
&& typeof listing.home === 'string'
|
|
2409
|
-
&& Array.isArray(listing.crumbs)
|
|
2410
|
-
&& listing.crumbs.every(isCodexDirectoryEntry)
|
|
2411
|
-
&& Array.isArray(listing.entries)
|
|
2412
|
-
&& listing.entries.every(isCodexDirectoryEntry)
|
|
2413
|
-
&& typeof listing.truncated === 'boolean';
|
|
2414
|
-
}
|
|
2415
|
-
function isCodexDirectoryEntry(value) {
|
|
2416
|
-
const entry = record(value);
|
|
2417
|
-
return typeof entry.name === 'string'
|
|
2418
|
-
&& typeof entry.path === 'string'
|
|
2419
|
-
&& typeof entry.hidden === 'boolean';
|
|
2420
|
-
}
|
|
2421
|
-
function fail(code, message, details = {}) {
|
|
2422
|
-
return { ok: false, error: { code, message, details } };
|
|
2423
|
-
}
|
|
2424
|
-
function failFrom(error) {
|
|
2425
|
-
const source = error instanceof Error ? error : new Error(String(error));
|
|
2426
|
-
return fail(errorCode(source) ?? 'internal', source.message, errorDetails(source));
|
|
2427
|
-
}
|
|
2428
|
-
function isLegacySessionHistoryUnsupported(error) {
|
|
2429
|
-
if (!(error instanceof Error))
|
|
2430
|
-
return false;
|
|
2431
|
-
return errorCode(error) === 'METHOD_NOT_ALLOWED'
|
|
2432
|
-
|| errorCode(error) === 'METHOD_NOT_FOUND'
|
|
2433
|
-
|| error.message.includes('The requested Codex method is not available over Remote.');
|
|
2434
|
-
}
|
|
2435
|
-
function errorCode(error) {
|
|
2436
|
-
return 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
2437
|
-
}
|
|
2438
|
-
function errorDetails(error) {
|
|
2439
|
-
return 'details' in error && isRecord(error.details) ? error.details : {};
|
|
2440
|
-
}
|
|
2441
|
-
function record(value) {
|
|
2442
|
-
return isRecord(value) ? value : {};
|
|
2443
|
-
}
|
|
2444
|
-
function isRecord(value) {
|
|
2445
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2446
|
-
}
|
|
2447
|
-
function array(value) {
|
|
2448
|
-
return Array.isArray(value) ? value : [];
|
|
2449
|
-
}
|
|
2450
|
-
function string(value) {
|
|
2451
|
-
return typeof value === 'string' ? value : undefined;
|
|
2452
|
-
}
|
|
2453
|
-
function integer(value) {
|
|
2454
|
-
return typeof value === 'number' && Number.isSafeInteger(value) ? value : undefined;
|
|
2455
|
-
}
|
|
2456
|
-
function finiteNumber(value) {
|
|
2457
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
2458
|
-
}
|
|
2459
|
-
function optionalInteger(value) {
|
|
2460
|
-
if (value === undefined)
|
|
2461
|
-
return undefined;
|
|
2462
|
-
const parsed = integer(value);
|
|
2463
|
-
if (parsed === undefined)
|
|
2464
|
-
throw new Error('The CodeX History cursor is invalid.');
|
|
2465
|
-
return parsed;
|
|
2466
|
-
}
|
|
2467
|
-
function optionalNonNegativeInteger(value) {
|
|
2468
|
-
const parsed = optionalInteger(value);
|
|
2469
|
-
if (parsed !== undefined && parsed < 0)
|
|
2470
|
-
throw new Error('The CodeX History cursor is invalid.');
|
|
2471
|
-
return parsed;
|
|
2472
|
-
}
|
|
2473
|
-
function optionalPositiveInteger(value) {
|
|
2474
|
-
const parsed = optionalInteger(value);
|
|
2475
|
-
if (parsed !== undefined && parsed <= 0)
|
|
2476
|
-
throw new Error('The CodeX History page size is invalid.');
|
|
2477
|
-
return parsed;
|
|
2478
|
-
}
|
|
2479
|
-
function isCodexNativeHistoryPage(value, sessionId) {
|
|
2480
|
-
const page = record(value);
|
|
2481
|
-
const header = record(page.header);
|
|
2482
|
-
return header.id === sessionId
|
|
2483
|
-
&& integer(page.cursor) !== undefined
|
|
2484
|
-
&& integer(page.nextTurn) !== undefined
|
|
2485
|
-
&& Array.isArray(page.records)
|
|
2486
|
-
&& typeof page.hasMore === 'boolean'
|
|
2487
|
-
&& (page.activeTurnId === undefined || typeof page.activeTurnId === 'string');
|
|
2488
|
-
}
|
|
2489
|
-
function truncateCodePoints(value, maximum) {
|
|
2490
|
-
return [...value].slice(0, maximum).join('');
|
|
2491
|
-
}
|
|
2492
|
-
function requiredString(value, field) {
|
|
2493
|
-
if (typeof value !== 'string' || value.length === 0)
|
|
2494
|
-
throw new Error(`The CodeX ${field} is required.`);
|
|
2495
|
-
return value;
|
|
2496
|
-
}
|
|
2497
|
-
function normalizeTime(value) {
|
|
2498
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
2499
|
-
return 0;
|
|
2500
|
-
return value < 10_000_000_000 ? Math.floor(value * 1000) : Math.floor(value);
|
|
2501
|
-
}
|
|
2502
|
-
function isoTime(value) {
|
|
2503
|
-
return new Date(normalizeTime(value) || Date.now()).toISOString();
|
|
2504
|
-
}
|
|
2505
|
-
function isProjectListUnsupported(error) {
|
|
2506
|
-
if (!(error instanceof Error))
|
|
2507
|
-
return false;
|
|
2508
|
-
const code = errorCode(error);
|
|
2509
|
-
return code === 'METHOD_NOT_ALLOWED'
|
|
2510
|
-
|| code === 'METHOD_NOT_FOUND'
|
|
2511
|
-
|| code === 'CODEX_UPSTREAM_ERROR'
|
|
2512
|
-
|| error.message.includes('The requested Codex method is not available over Remote.');
|
|
2513
|
-
}
|
|
2514
|
-
function basename(path) {
|
|
2515
|
-
const normalized = path.replace(/[\\/]+$/u, '');
|
|
2516
|
-
const parts = normalized.split(/[\\/]/u);
|
|
2517
|
-
return parts.at(-1) || path;
|
|
2518
|
-
}
|
|
2519
|
-
function hashString(value) {
|
|
2520
|
-
let hash = 0x811c9dc5;
|
|
2521
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
2522
|
-
hash ^= value.charCodeAt(index);
|
|
2523
|
-
hash = Math.imul(hash, 0x01000193);
|
|
2524
|
-
}
|
|
2525
|
-
return (hash >>> 0).toString(36);
|
|
2526
|
-
}
|
|
2527
|
-
class AsyncValueQueue {
|
|
2528
|
-
signal;
|
|
2529
|
-
values = [];
|
|
2530
|
-
waiters = [];
|
|
2531
|
-
closed = false;
|
|
2532
|
-
onAbort;
|
|
2533
|
-
constructor(signal) {
|
|
2534
|
-
this.signal = signal;
|
|
2535
|
-
this.onAbort = () => this.close();
|
|
2536
|
-
signal.addEventListener('abort', this.onAbort, { once: true });
|
|
2537
|
-
if (signal.aborted)
|
|
2538
|
-
this.close();
|
|
2539
|
-
}
|
|
2540
|
-
push(value) {
|
|
2541
|
-
if (this.closed)
|
|
2542
|
-
return;
|
|
2543
|
-
const waiter = this.waiters.shift();
|
|
2544
|
-
if (waiter === undefined)
|
|
2545
|
-
this.values.push(value);
|
|
2546
|
-
else
|
|
2547
|
-
waiter({ done: false, value });
|
|
2548
|
-
}
|
|
2549
|
-
close() {
|
|
2550
|
-
if (this.closed)
|
|
2551
|
-
return;
|
|
2552
|
-
this.closed = true;
|
|
2553
|
-
this.signal.removeEventListener('abort', this.onAbort);
|
|
2554
|
-
for (const waiter of this.waiters.splice(0))
|
|
2555
|
-
waiter({ done: true, value: undefined });
|
|
2556
|
-
}
|
|
2557
|
-
iterate(dispose) {
|
|
2558
|
-
const queue = this;
|
|
2559
|
-
return {
|
|
2560
|
-
async *[Symbol.asyncIterator]() {
|
|
2561
|
-
try {
|
|
2562
|
-
yield* queue;
|
|
2563
|
-
}
|
|
2564
|
-
finally {
|
|
2565
|
-
dispose();
|
|
2566
|
-
queue.close();
|
|
2567
|
-
}
|
|
2568
|
-
},
|
|
2569
|
-
};
|
|
2570
|
-
}
|
|
2571
|
-
async *[Symbol.asyncIterator]() {
|
|
2572
|
-
while (true) {
|
|
2573
|
-
if (this.values.length > 0) {
|
|
2574
|
-
yield this.values.shift();
|
|
2575
|
-
continue;
|
|
2576
|
-
}
|
|
2577
|
-
if (this.closed)
|
|
2578
|
-
return;
|
|
2579
|
-
const next = await new Promise(resolve => this.waiters.push(resolve));
|
|
2580
|
-
if (next.done)
|
|
2581
|
-
return;
|
|
2582
|
-
yield next.value;
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
796
|
}
|
|
797
|
+
// Keep existing Host and Client imports stable.
|
|
798
|
+
export { codexProjectWorkspaceId, discoverCodexVirtualWorkspaces } from './virtual-catalog.js';
|
|
799
|
+
export { paginateCodexNativeHistory, projectCodexNativeHistory } from './virtual-history.js';
|
|
2586
800
|
//# sourceMappingURL=virtual-harness.js.map
|