@gkorepanov/ccodex 0.3.0 → 0.3.4
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.
Potentially problematic release.
This version of @gkorepanov/ccodex might be problematic. Click here for more details.
- package/README.md +27 -9
- package/RELEASING.md +14 -15
- package/SSH_VERIFY.md +11 -0
- package/archive/README.md +23 -0
- package/compatibility.json +1 -2
- package/config.example.toml +12 -4
- package/dist/claude/asyncQueue.d.ts +3 -0
- package/dist/claude/asyncQueue.js +13 -2
- package/dist/claude/asyncQueue.js.map +1 -1
- package/dist/claude/commandActions.d.ts +6 -0
- package/dist/claude/commandActions.js +57 -0
- package/dist/claude/commandActions.js.map +1 -0
- package/dist/claude/compactCommand.d.ts +2 -0
- package/dist/claude/compactCommand.js +7 -0
- package/dist/claude/compactCommand.js.map +1 -0
- package/dist/claude/goalTools.d.ts +15 -0
- package/dist/claude/goalTools.js +173 -0
- package/dist/claude/goalTools.js.map +1 -0
- package/dist/claude/hookMapper.d.ts +17 -4
- package/dist/claude/hookMapper.js +7 -5
- package/dist/claude/hookMapper.js.map +1 -1
- package/dist/claude/rateLimits.d.ts +72 -0
- package/dist/claude/rateLimits.js +330 -0
- package/dist/claude/rateLimits.js.map +1 -0
- package/dist/claude/responseItemValidation.d.ts +2 -0
- package/dist/claude/responseItemValidation.js +40 -0
- package/dist/claude/responseItemValidation.js.map +1 -0
- package/dist/claude/resultClassifier.d.ts +8 -2
- package/dist/claude/resultClassifier.js +1 -1
- package/dist/claude/resultClassifier.js.map +1 -1
- package/dist/claude/service.d.ts +108 -28
- package/dist/claude/service.js +1276 -668
- package/dist/claude/service.js.map +1 -1
- package/dist/claude/session/backgroundOutput.d.ts +2 -0
- package/dist/claude/session/backgroundOutput.js +29 -0
- package/dist/claude/session/backgroundOutput.js.map +1 -0
- package/dist/claude/session/commands.d.ts +855 -0
- package/dist/claude/session/commands.js +2 -0
- package/dist/claude/session/commands.js.map +1 -0
- package/dist/claude/session/goalState.d.ts +49 -0
- package/dist/claude/session/goalState.js +407 -0
- package/dist/claude/session/goalState.js.map +1 -0
- package/dist/claude/session/mailbox.d.ts +52 -0
- package/dist/claude/session/mailbox.js +190 -0
- package/dist/claude/session/mailbox.js.map +1 -0
- package/dist/claude/session/outputAdapter.d.ts +11 -0
- package/dist/claude/session/outputAdapter.js +31 -0
- package/dist/claude/session/outputAdapter.js.map +1 -0
- package/dist/claude/session/providerFacts.d.ts +47 -0
- package/dist/claude/session/providerFacts.js +84 -0
- package/dist/claude/session/providerFacts.js.map +1 -0
- package/dist/claude/session/providerRuntime.d.ts +1 -0
- package/dist/claude/session/providerRuntime.js +2 -0
- package/dist/claude/session/providerRuntime.js.map +1 -0
- package/dist/claude/session/providerRuntimeFactory.d.ts +49 -0
- package/dist/claude/session/providerRuntimeFactory.js +127 -0
- package/dist/claude/session/providerRuntimeFactory.js.map +1 -0
- package/dist/claude/session/repository.d.ts +57 -0
- package/dist/claude/session/repository.js +161 -0
- package/dist/claude/session/repository.js.map +1 -0
- package/dist/claude/session/runtime.d.ts +46 -0
- package/dist/claude/session/runtime.js +140 -0
- package/dist/claude/session/runtime.js.map +1 -0
- package/dist/claude/session/scopeState.d.ts +61 -0
- package/dist/claude/session/scopeState.js +74 -0
- package/dist/claude/session/scopeState.js.map +1 -0
- package/dist/claude/session/session.d.ts +281 -0
- package/dist/claude/session/session.js +6664 -0
- package/dist/claude/session/session.js.map +1 -0
- package/dist/claude/session/shellRunner.d.ts +13 -0
- package/dist/claude/session/shellRunner.js +103 -0
- package/dist/claude/session/shellRunner.js.map +1 -0
- package/dist/claude/session/usage.d.ts +2 -0
- package/dist/claude/session/usage.js +10 -0
- package/dist/claude/session/usage.js.map +1 -0
- package/dist/claude/sessionRegistry.d.ts +26 -0
- package/dist/claude/sessionRegistry.js +118 -0
- package/dist/claude/sessionRegistry.js.map +1 -0
- package/dist/claude/shellSupervisor.d.ts +1 -0
- package/dist/claude/shellSupervisor.js +69 -0
- package/dist/claude/shellSupervisor.js.map +1 -0
- package/dist/claude/statusCommand.d.ts +21 -0
- package/dist/claude/statusCommand.js +97 -0
- package/dist/claude/statusCommand.js.map +1 -0
- package/dist/claude/toolMapper.d.ts +9 -1
- package/dist/claude/toolMapper.js +92 -16
- package/dist/claude/toolMapper.js.map +1 -1
- package/dist/claude/transcriptBrancher.d.ts +37 -0
- package/dist/claude/transcriptBrancher.js +103 -0
- package/dist/claude/transcriptBrancher.js.map +1 -0
- package/dist/cli/delegate.js +23 -1
- package/dist/cli/delegate.js.map +1 -1
- package/dist/cli/main.js +28 -12
- package/dist/cli/main.js.map +1 -1
- package/dist/codex/generated/ResponseItemRuntimeSchema.d.ts +8 -0
- package/dist/codex/generated/ResponseItemRuntimeSchema.js +7 -0
- package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -0
- package/dist/compatibility/probe.js +11 -6
- package/dist/compatibility/probe.js.map +1 -1
- package/dist/config/config.d.ts +9 -0
- package/dist/config/config.js +80 -11
- package/dist/config/config.js.map +1 -1
- package/dist/daemon/daemon.js +91 -43
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/daemon/ownership.d.ts +15 -0
- package/dist/daemon/ownership.js +185 -0
- package/dist/daemon/ownership.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +3 -0
- package/dist/daemon/supervisor.js +37 -1
- package/dist/daemon/supervisor.js.map +1 -1
- package/dist/gateway/clientConnection.d.ts +7 -1
- package/dist/gateway/clientConnection.js +435 -48
- package/dist/gateway/clientConnection.js.map +1 -1
- package/dist/gateway/remoteRelay.js +0 -1
- package/dist/gateway/remoteRelay.js.map +1 -1
- package/dist/gateway/server.d.ts +1 -1
- package/dist/gateway/server.js +37 -9
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/subscriptions.d.ts +9 -2
- package/dist/gateway/subscriptions.js +87 -9
- package/dist/gateway/subscriptions.js.map +1 -1
- package/dist/gateway/transientNotice.d.ts +2 -0
- package/dist/gateway/transientNotice.js +42 -1
- package/dist/gateway/transientNotice.js.map +1 -1
- package/dist/handoff/service.d.ts +32 -3
- package/dist/handoff/service.js +304 -26
- package/dist/handoff/service.js.map +1 -1
- package/dist/handoff/titleGeneration.d.ts +28 -0
- package/dist/handoff/titleGeneration.js +151 -0
- package/dist/handoff/titleGeneration.js.map +1 -0
- package/dist/management/doctor.d.ts +1 -1
- package/dist/management/doctor.js +32 -23
- package/dist/management/doctor.js.map +1 -1
- package/dist/management/lifecycle.js +14 -2
- package/dist/management/lifecycle.js.map +1 -1
- package/dist/management/remoteShim.d.ts +10 -0
- package/dist/management/remoteShim.js +65 -0
- package/dist/management/remoteShim.js.map +1 -0
- package/dist/management/setup.d.ts +4 -0
- package/dist/management/setup.js +108 -39
- package/dist/management/setup.js.map +1 -1
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +6 -0
- package/dist/observability/metrics.js.map +1 -1
- package/dist/protocol/errors.d.ts +1 -0
- package/dist/protocol/errors.js +4 -1
- package/dist/protocol/errors.js.map +1 -1
- package/dist/runtime/dependencies.js +2 -2
- package/dist/runtime/dependencies.js.map +1 -1
- package/dist/runtime/providerAvailability.d.ts +24 -0
- package/dist/runtime/providerAvailability.js +107 -0
- package/dist/runtime/providerAvailability.js.map +1 -0
- package/dist/state/stateCommand.d.ts +21 -0
- package/dist/state/stateCommand.js +198 -0
- package/dist/state/stateCommand.js.map +1 -0
- package/dist/state/stockStateTracker.d.ts +14 -0
- package/dist/state/stockStateTracker.js +120 -0
- package/dist/state/stockStateTracker.js.map +1 -0
- package/dist/store/HybridStore.d.ts +118 -2
- package/dist/store/HybridStore.js +23 -1
- package/dist/store/HybridStore.js.map +1 -1
- package/dist/store/memoryStore.d.ts +57 -7
- package/dist/store/memoryStore.js +337 -5
- package/dist/store/memoryStore.js.map +1 -1
- package/dist/store/sqliteStore.d.ts +30 -4
- package/dist/store/sqliteStore.js +395 -42
- package/dist/store/sqliteStore.js.map +1 -1
- package/install.sh +2 -2
- package/package.json +18 -10
- package/dist/claude/interactions.d.ts +0 -23
- package/dist/claude/interactions.js +0 -77
- package/dist/claude/interactions.js.map +0 -1
- package/dist/claude/sessionRuntime.d.ts +0 -102
- package/dist/claude/sessionRuntime.js +0 -1361
- package/dist/claude/sessionRuntime.js.map +0 -1
- /package/{BUILD_BENCHMARKS.md → archive/BUILD_BENCHMARKS.md} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v7 as uuidv7 } from "uuid";
|
|
1
|
+
import { validate as isUuid, v7 as uuidv7 } from "uuid";
|
|
2
2
|
import { WebSocket as WebSocketState } from "ws";
|
|
3
3
|
import { connectStock } from "../codex/stockConnection.js";
|
|
4
4
|
import { isRequest, isResponse, parseRpcMessage } from "../protocol/envelopes.js";
|
|
@@ -7,11 +7,26 @@ import { mergedModelList } from "./modelList.js";
|
|
|
7
7
|
import { StockRpc } from "./stockRpc.js";
|
|
8
8
|
import { mergedLoadedList, mergedThreadList } from "./threadList.js";
|
|
9
9
|
import { ServerRequestIds } from "./serverRequestIds.js";
|
|
10
|
-
import { transientSystemNotice } from "./transientNotice.js";
|
|
11
|
-
|
|
10
|
+
import { transientCommandNotice, transientSystemNotice, } from "./transientNotice.js";
|
|
11
|
+
import { rateLimitNotifications } from "../claude/rateLimits.js";
|
|
12
|
+
import { formatCCodexStatus, isCCodexStatusCommand } from "../claude/statusCommand.js";
|
|
13
|
+
import { claudeCompactCommand } from "../claude/compactCommand.js";
|
|
14
|
+
import { DEFAULT_FEATURES } from "../config/config.js";
|
|
15
|
+
import { providerUnavailableMessage } from "../runtime/providerAvailability.js";
|
|
16
|
+
import { formatCCodexState, isCCodexStateCommand } from "../state/stateCommand.js";
|
|
17
|
+
import { StockStateTracker } from "../state/stockStateTracker.js";
|
|
18
|
+
export function attachClientConnection(client, stockSocket, claudeModels, claude, handoffs, subscriptions, logger, cursors, metrics, recorder, remoteControl, features = DEFAULT_FEATURES, providerAvailability, sharedStockState) {
|
|
12
19
|
const connectionId = uuidv7();
|
|
20
|
+
let resolveClosed;
|
|
21
|
+
let rejectClosed;
|
|
22
|
+
const closed = new Promise((resolve, reject) => {
|
|
23
|
+
resolveClosed = resolve;
|
|
24
|
+
rejectClosed = reject;
|
|
25
|
+
});
|
|
26
|
+
void closed.catch(() => undefined);
|
|
13
27
|
const stock = connectStock(stockSocket);
|
|
14
28
|
const stockRpc = new StockRpc(stock);
|
|
29
|
+
const stockState = sharedStockState ?? new StockStateTracker();
|
|
15
30
|
const queued = [];
|
|
16
31
|
const requestStarted = new Map();
|
|
17
32
|
const forwardedRequests = new Map();
|
|
@@ -32,9 +47,23 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
32
47
|
const params = message.params && typeof message.params === "object"
|
|
33
48
|
? message.params
|
|
34
49
|
: undefined;
|
|
50
|
+
const systemEphemeral = (message.method === "thread/start" || message.method === "thread/fork")
|
|
51
|
+
&& params?.ephemeral === true
|
|
52
|
+
&& (params.threadSource === "system" || (message.method === "thread/start" && params.threadSource !== "user"));
|
|
53
|
+
const internalEphemeralThread = typeof params?.threadId === "string"
|
|
54
|
+
&& handoffs.ownsSystemEphemeral(connectionId, params.threadId);
|
|
55
|
+
const foreground = !systemEphemeral && !internalEphemeralThread
|
|
56
|
+
&& ["thread/start", "thread/resume", "thread/fork", "turn/start"].includes(message.method)
|
|
57
|
+
? "codex"
|
|
58
|
+
: undefined;
|
|
35
59
|
forwardedRequests.set(requestKey(message.id), {
|
|
36
60
|
method: message.method,
|
|
37
61
|
...(typeof params?.threadId === "string" ? { threadId: params.threadId } : {}),
|
|
62
|
+
...(systemEphemeral
|
|
63
|
+
? { systemEphemeral: message.params }
|
|
64
|
+
: {}),
|
|
65
|
+
...(foreground ? { foreground } : {}),
|
|
66
|
+
...(["thread/delete", "thread/unsubscribe"].includes(message.method) ? { clearForeground: true } : {}),
|
|
38
67
|
});
|
|
39
68
|
};
|
|
40
69
|
metrics.connectionOpened();
|
|
@@ -78,6 +107,111 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
78
107
|
completeLatency(id, "claude");
|
|
79
108
|
sendJson({ id, error: { code, message: error instanceof Error ? error.message : String(error) } });
|
|
80
109
|
};
|
|
110
|
+
let foreground;
|
|
111
|
+
let foregroundGeneration = 0;
|
|
112
|
+
let unknownStatusDiagnosed = false;
|
|
113
|
+
const emitRateLimits = (response) => {
|
|
114
|
+
for (const notification of rateLimitNotifications(response))
|
|
115
|
+
sendJson(notification);
|
|
116
|
+
};
|
|
117
|
+
const emitStockRateLimits = (value) => {
|
|
118
|
+
if (!value || typeof value !== "object")
|
|
119
|
+
return;
|
|
120
|
+
const result = value;
|
|
121
|
+
const buckets = result.rateLimitsByLimitId ? Object.values(result.rateLimitsByLimitId) : [];
|
|
122
|
+
const snapshots = buckets.length > 0 ? buckets : result.rateLimits ? [result.rateLimits] : [];
|
|
123
|
+
for (const rateLimits of snapshots)
|
|
124
|
+
sendJson({ method: "account/rateLimits/updated", params: { rateLimits } });
|
|
125
|
+
};
|
|
126
|
+
const combinedStatus = async (threadId) => {
|
|
127
|
+
const availability = providerAvailability
|
|
128
|
+
? await providerAvailability.refreshAll()
|
|
129
|
+
: {
|
|
130
|
+
claude: { provider: "claude", state: "ready" },
|
|
131
|
+
codex: { provider: "codex", state: "ready" },
|
|
132
|
+
};
|
|
133
|
+
const [claudeUsage, codexRateLimits] = await Promise.all([
|
|
134
|
+
availability.claude.state === "ready"
|
|
135
|
+
? claude.readRateLimitStatus(threadId).catch((error) => ({
|
|
136
|
+
rateLimits: claude.cachedRateLimits(),
|
|
137
|
+
unavailableReason: error instanceof Error ? error.message : String(error),
|
|
138
|
+
}))
|
|
139
|
+
: undefined,
|
|
140
|
+
availability.codex.state === "ready"
|
|
141
|
+
? stockRpc.request("account/rateLimits/read", undefined).catch(() => undefined)
|
|
142
|
+
: undefined,
|
|
143
|
+
]);
|
|
144
|
+
return formatCCodexStatus({
|
|
145
|
+
claude: { availability: availability.claude, ...(claudeUsage ? { usage: claudeUsage } : {}) },
|
|
146
|
+
codex: {
|
|
147
|
+
availability: availability.codex,
|
|
148
|
+
...(codexRateLimits ? { rateLimits: codexRateLimits } : {}),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const threadState = async (threadId) => {
|
|
153
|
+
if (claude.ownsThread(threadId))
|
|
154
|
+
return formatCCodexState(claude.stateSnapshot(threadId));
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await stockRpc.request("thread/read", {
|
|
158
|
+
threadId,
|
|
159
|
+
includeTurns: true,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
+
if (!message.includes("includeTurns is unavailable")
|
|
165
|
+
&& !message.includes("ephemeral threads do not support includeTurns"))
|
|
166
|
+
throw error;
|
|
167
|
+
response = await stockRpc.request("thread/read", {
|
|
168
|
+
threadId,
|
|
169
|
+
includeTurns: false,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return formatCCodexState(stockState.snapshot(response.thread));
|
|
173
|
+
};
|
|
174
|
+
const publishForegroundRateLimits = (generation) => {
|
|
175
|
+
const selected = foreground;
|
|
176
|
+
if (!selected)
|
|
177
|
+
return;
|
|
178
|
+
if (selected.provider === "claude") {
|
|
179
|
+
void claude.readRateLimits(selected.threadId).then((response) => {
|
|
180
|
+
if (foregroundGeneration === generation && foreground?.provider === "claude")
|
|
181
|
+
emitRateLimits(response);
|
|
182
|
+
}).catch((error) => logger.warn("claude.rate-limits.publish-failed", { connectionId, error: String(error) }));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
void (async () => {
|
|
186
|
+
if (providerAvailability && (await providerAvailability.read("codex")).state !== "ready")
|
|
187
|
+
return;
|
|
188
|
+
const response = await stockRpc.request("account/rateLimits/read", undefined);
|
|
189
|
+
if (foregroundGeneration === generation && foreground?.provider === "codex")
|
|
190
|
+
emitStockRateLimits(response);
|
|
191
|
+
})().catch((error) => logger.warn("codex.rate-limits.publish-failed", { connectionId, error: String(error) }));
|
|
192
|
+
};
|
|
193
|
+
const selectForeground = (provider, threadId) => {
|
|
194
|
+
foreground = { provider, threadId };
|
|
195
|
+
unknownStatusDiagnosed = false;
|
|
196
|
+
foregroundGeneration += 1;
|
|
197
|
+
publishForegroundRateLimits(foregroundGeneration);
|
|
198
|
+
};
|
|
199
|
+
const clearForeground = (threadId) => {
|
|
200
|
+
if (foreground?.threadId !== threadId)
|
|
201
|
+
return;
|
|
202
|
+
foreground = undefined;
|
|
203
|
+
foregroundGeneration += 1;
|
|
204
|
+
};
|
|
205
|
+
const diagnoseUnknownStatus = (signal) => {
|
|
206
|
+
if (unknownStatusDiagnosed)
|
|
207
|
+
return;
|
|
208
|
+
unknownStatusDiagnosed = true;
|
|
209
|
+
logger.warn("provider.status.unknown", {
|
|
210
|
+
connectionId,
|
|
211
|
+
signal,
|
|
212
|
+
behavior: "preserving provider-labelled stock payload until a foreground operation succeeds",
|
|
213
|
+
});
|
|
214
|
+
};
|
|
81
215
|
const notificationSink = (method, params) => {
|
|
82
216
|
if (method === "error" && params && typeof params === "object") {
|
|
83
217
|
const failure = params;
|
|
@@ -101,9 +235,19 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
101
235
|
const serverRequestSink = (id, method, params) => {
|
|
102
236
|
sendJson({ id: serverRequestIds.wireId(id), method, params });
|
|
103
237
|
};
|
|
104
|
-
subscriptions.attach(connectionId, notificationSink);
|
|
238
|
+
subscriptions.attach(connectionId, notificationSink, serverRequestSink);
|
|
239
|
+
claude.subscribeRateLimits(connectionId, (response) => {
|
|
240
|
+
if (foreground?.provider === "claude")
|
|
241
|
+
emitRateLimits(response);
|
|
242
|
+
});
|
|
243
|
+
const subscribedClaudeThreads = new Set();
|
|
105
244
|
const subscribeClaude = (threadId) => {
|
|
106
245
|
subscriptions.subscribe(threadId, connectionId, notificationSink, serverRequestSink);
|
|
246
|
+
subscribedClaudeThreads.add(threadId);
|
|
247
|
+
};
|
|
248
|
+
const muteClaudeDuringSnapshot = (threadId) => {
|
|
249
|
+
subscribeClaude(threadId);
|
|
250
|
+
subscriptions.mute(threadId, connectionId);
|
|
107
251
|
};
|
|
108
252
|
client.on("message", async (data, isBinary) => {
|
|
109
253
|
recorder.frame(connectionId, "client_to_gateway", data, isBinary);
|
|
@@ -113,7 +257,7 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
113
257
|
: undefined;
|
|
114
258
|
if (message && isResponse(message) && internalServerRequestId) {
|
|
115
259
|
const response = "result" in message ? message.result : { rpcError: message.error };
|
|
116
|
-
if (!claude.resolveServerRequest(internalServerRequestId, response)) {
|
|
260
|
+
if (!await claude.resolveServerRequest(internalServerRequestId, response)) {
|
|
117
261
|
logger.warn("claude.interaction.unknown-response", { connectionId, requestId: internalServerRequestId });
|
|
118
262
|
}
|
|
119
263
|
return;
|
|
@@ -129,6 +273,28 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
129
273
|
sendResult(message.id, await mergedModelList((message.params ?? {}), stockRpc, claudeModels, logger, cursors));
|
|
130
274
|
return;
|
|
131
275
|
}
|
|
276
|
+
if (message.method === "account/rateLimits/read" && foreground?.provider === "claude") {
|
|
277
|
+
sendResult(message.id, await claude.readRateLimits(foreground.threadId));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (message.method === "account/usage/read" && foreground?.provider === "claude") {
|
|
281
|
+
// The captured App status flow does not request this method. If a
|
|
282
|
+
// client does, lifetime/streak Codex statistics have no Claude
|
|
283
|
+
// equivalent and must not be mislabelled as provider usage.
|
|
284
|
+
sendResult(message.id, {
|
|
285
|
+
summary: {
|
|
286
|
+
lifetimeTokens: null,
|
|
287
|
+
peakDailyTokens: null,
|
|
288
|
+
longestRunningTurnSec: null,
|
|
289
|
+
currentStreakDays: null,
|
|
290
|
+
longestStreakDays: null,
|
|
291
|
+
},
|
|
292
|
+
dailyUsageBuckets: null,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (message.method === "account/rateLimits/read" && !foreground)
|
|
297
|
+
diagnoseUnknownStatus("account/rateLimits/read");
|
|
132
298
|
if (message.method === "thread/list") {
|
|
133
299
|
sendResult(message.id, await mergedThreadList((message.params ?? {}), stockRpc, claude, cursors));
|
|
134
300
|
return;
|
|
@@ -141,13 +307,77 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
141
307
|
const params = (message.params ?? {});
|
|
142
308
|
if (params.model && claude.ownsModel(params.model)) {
|
|
143
309
|
const result = await claude.startThread(params);
|
|
310
|
+
handoffs.observeDurableThread(connectionId, "claude");
|
|
144
311
|
sendResult(message.id, result);
|
|
312
|
+
selectForeground("claude", result.thread.id);
|
|
145
313
|
subscribeClaude(result.thread.id);
|
|
146
|
-
claude.announceThread(result.thread);
|
|
314
|
+
await claude.announceThread(result.thread);
|
|
147
315
|
return;
|
|
148
316
|
}
|
|
149
317
|
}
|
|
150
318
|
const params = (message.params ?? {});
|
|
319
|
+
if (params.threadId && message.method === "turn/start"
|
|
320
|
+
&& claude.ownsThread(params.threadId) && !claude.isChildProjection(params.threadId)) {
|
|
321
|
+
const promptedCompact = claudeCompactCommand((message.params ?? {}).input);
|
|
322
|
+
if (promptedCompact) {
|
|
323
|
+
const prepared = await claude.preparePromptedCompact(params.threadId, promptedCompact);
|
|
324
|
+
sendResult(message.id, prepared.response);
|
|
325
|
+
selectForeground("claude", params.threadId);
|
|
326
|
+
await prepared.announce();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (params.threadId && message.method === "turn/start" && features.statusCommand) {
|
|
331
|
+
const turn = (message.params ?? {});
|
|
332
|
+
if (isCCodexStateCommand(turn.input)) {
|
|
333
|
+
if (claude.ownsThread(params.threadId) && !claude.isChildProjection(params.threadId)) {
|
|
334
|
+
const prepared = await claude.prepareStateTurn(turn, () => threadState(params.threadId));
|
|
335
|
+
sendResult(message.id, prepared.response);
|
|
336
|
+
selectForeground("claude", params.threadId);
|
|
337
|
+
await prepared.announce();
|
|
338
|
+
prepared.start();
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
const notice = transientCommandNotice(params.threadId, turn.input, await threadState(params.threadId), turn.clientUserMessageId ?? null);
|
|
342
|
+
sendResult(message.id, notice.response);
|
|
343
|
+
selectForeground(claude.ownsThread(params.threadId) ? "claude" : "codex", params.threadId);
|
|
344
|
+
emitNotice(notice);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (isCCodexStatusCommand(turn.input)) {
|
|
349
|
+
if (claude.ownsThread(params.threadId) && !claude.isChildProjection(params.threadId)) {
|
|
350
|
+
const prepared = await claude.prepareStatusTurn(turn, () => combinedStatus(params.threadId));
|
|
351
|
+
sendResult(message.id, prepared.response);
|
|
352
|
+
selectForeground("claude", params.threadId);
|
|
353
|
+
await prepared.announce();
|
|
354
|
+
prepared.start();
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
const notice = transientCommandNotice(params.threadId, turn.input, await combinedStatus(claude.ownsThread(params.threadId) ? params.threadId : undefined), turn.clientUserMessageId ?? null);
|
|
358
|
+
sendResult(message.id, notice.response);
|
|
359
|
+
selectForeground(claude.ownsThread(params.threadId) ? "claude" : "codex", params.threadId);
|
|
360
|
+
emitNotice(notice);
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (params.threadId && message.method === "turn/start"
|
|
366
|
+
&& !claude.ownsThread(params.threadId) && providerAvailability) {
|
|
367
|
+
let availability = await providerAvailability.read("codex");
|
|
368
|
+
if (availability.state !== "ready")
|
|
369
|
+
availability = await providerAvailability.refresh("codex");
|
|
370
|
+
if (availability.state !== "ready") {
|
|
371
|
+
const notice = transientSystemNotice(params.threadId, providerUnavailableMessage(availability), "error");
|
|
372
|
+
sendResult(message.id, notice.response);
|
|
373
|
+
selectForeground("codex", params.threadId);
|
|
374
|
+
emitNotice(notice);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (params.threadId && message.method === "turn/start") {
|
|
379
|
+
handoffs.observeDurableTurn(connectionId, (message.params ?? {}));
|
|
380
|
+
}
|
|
151
381
|
if (params.threadId && message.method === "thread/settings/update") {
|
|
152
382
|
const update = (message.params ?? {});
|
|
153
383
|
const staged = handoffs.interceptSettings(update);
|
|
@@ -172,25 +402,45 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
172
402
|
}
|
|
173
403
|
if (params.threadId && message.method === "thread/fork") {
|
|
174
404
|
const forkParams = (message.params ?? {});
|
|
405
|
+
if (handoffs.isSystemEphemeralFork(forkParams)) {
|
|
406
|
+
sendResult(message.id, await handoffs.forkSystemEphemeral(forkParams, stockRpc, connectionId));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
175
409
|
if (handoffs.shouldFork(forkParams)) {
|
|
176
410
|
emitTransientNotice(params.threadId, "Compacting before fork. If the App reports ‘Codex request timed out’, ignore it—the new task will still appear in the sidebar.");
|
|
177
411
|
const result = await handoffs.fork(forkParams, stockRpc, connectionId);
|
|
412
|
+
sendResult(message.id, result);
|
|
413
|
+
selectForeground(claude.ownsThread(result.thread.id) ? "claude" : "codex", result.thread.id);
|
|
178
414
|
if (claude.ownsThread(result.thread.id)) {
|
|
179
415
|
subscribeClaude(result.thread.id);
|
|
180
|
-
claude.announceThread(result.thread);
|
|
416
|
+
await claude.announceThread(result.thread);
|
|
181
417
|
}
|
|
182
418
|
else {
|
|
183
419
|
subscriptions.emit(result.thread.id, "thread/started", { thread: result.thread });
|
|
184
420
|
}
|
|
185
|
-
sendResult(message.id, result);
|
|
186
421
|
return;
|
|
187
422
|
}
|
|
188
423
|
}
|
|
424
|
+
if (params.threadId && message.method === "thread/delete"
|
|
425
|
+
&& handoffs.ownsSystemEphemeral(connectionId, params.threadId)) {
|
|
426
|
+
const result = await stockRpc.request("thread/delete", message.params ?? {});
|
|
427
|
+
handoffs.releaseSystemEphemeral(connectionId, params.threadId);
|
|
428
|
+
sendResult(message.id, result);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (params.threadId && message.method === "thread/unsubscribe"
|
|
432
|
+
&& handoffs.ownsSystemEphemeral(connectionId, params.threadId)) {
|
|
433
|
+
const result = await stockRpc.request("thread/unsubscribe", message.params ?? {});
|
|
434
|
+
handoffs.releaseSystemEphemeral(connectionId, params.threadId);
|
|
435
|
+
sendResult(message.id, result);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
189
438
|
if (params.threadId && message.method === "thread/delete" && !claude.ownsThread(params.threadId)
|
|
190
439
|
&& (handoffs.pending(params.threadId) || handoffs.overlay(params.threadId))) {
|
|
191
440
|
const result = await stockRpc.request("thread/delete", message.params ?? {});
|
|
192
441
|
handoffs.clearThread(params.threadId);
|
|
193
442
|
sendResult(message.id, result);
|
|
443
|
+
clearForeground(params.threadId);
|
|
194
444
|
return;
|
|
195
445
|
}
|
|
196
446
|
if (params.threadId && handoffs.overlay(params.threadId)) {
|
|
@@ -200,6 +450,7 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
200
450
|
}
|
|
201
451
|
if (message.method === "thread/resume") {
|
|
202
452
|
sendResult(message.id, await handoffs.resumeOverlay((message.params ?? {}), stockRpc));
|
|
453
|
+
selectForeground("codex", params.threadId);
|
|
203
454
|
return;
|
|
204
455
|
}
|
|
205
456
|
if (message.method === "thread/turns/list") {
|
|
@@ -213,10 +464,29 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
213
464
|
}
|
|
214
465
|
if (params.threadId && claude.ownsThread(params.threadId)) {
|
|
215
466
|
if (message.method === "thread/resume") {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
467
|
+
muteClaudeDuringSnapshot(params.threadId);
|
|
468
|
+
try {
|
|
469
|
+
const prepared = await claude.prepareResume((message.params ?? {}));
|
|
470
|
+
const result = prepared.response;
|
|
471
|
+
const snapshotHighWatermark = claude.eventHighWatermark(params.threadId);
|
|
472
|
+
const tokenUsage = claude.latestTokenUsage(params.threadId);
|
|
473
|
+
sendResult(message.id, result);
|
|
474
|
+
selectForeground("claude", params.threadId);
|
|
475
|
+
subscriptions.unmute(params.threadId, connectionId);
|
|
476
|
+
if (tokenUsage && tokenUsage.sequence <= snapshotHighWatermark) {
|
|
477
|
+
notificationSink(tokenUsage.method, tokenUsage.params);
|
|
478
|
+
}
|
|
479
|
+
for (const event of claude.eventsAfter(params.threadId, snapshotHighWatermark)) {
|
|
480
|
+
notificationSink(event.method, event.params);
|
|
481
|
+
}
|
|
482
|
+
await prepared.notifyGoalSnapshot(notificationSink);
|
|
483
|
+
await claude.replayPendingRequests(params.threadId, connectionId);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
subscriptions.unmute(params.threadId, connectionId);
|
|
487
|
+
subscriptions.unsubscribe(params.threadId, connectionId);
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
220
490
|
return;
|
|
221
491
|
}
|
|
222
492
|
if (message.method === "thread/read") {
|
|
@@ -235,7 +505,11 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
235
505
|
}
|
|
236
506
|
if (message.method === "thread/unsubscribe") {
|
|
237
507
|
subscriptions.unsubscribe(params.threadId, connectionId);
|
|
508
|
+
subscribedClaudeThreads.delete(params.threadId);
|
|
238
509
|
sendResult(message.id, { status: "unsubscribed" });
|
|
510
|
+
clearForeground(params.threadId);
|
|
511
|
+
if (!subscriptions.hasSubscribers(params.threadId))
|
|
512
|
+
await claude.releaseEphemeralThread(params.threadId);
|
|
239
513
|
return;
|
|
240
514
|
}
|
|
241
515
|
if (message.method === "thread/name/set") {
|
|
@@ -243,7 +517,7 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
243
517
|
return;
|
|
244
518
|
}
|
|
245
519
|
if (message.method === "thread/metadata/update") {
|
|
246
|
-
sendResult(message.id, claude.updateThreadMetadata((message.params ?? {})));
|
|
520
|
+
sendResult(message.id, await claude.updateThreadMetadata((message.params ?? {})));
|
|
247
521
|
return;
|
|
248
522
|
}
|
|
249
523
|
if (message.method === "thread/settings/update") {
|
|
@@ -255,20 +529,22 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
255
529
|
return;
|
|
256
530
|
}
|
|
257
531
|
if (message.method === "thread/unarchive") {
|
|
258
|
-
sendResult(message.id, claude.unarchiveThread(params.threadId));
|
|
532
|
+
sendResult(message.id, await claude.unarchiveThread(params.threadId));
|
|
259
533
|
return;
|
|
260
534
|
}
|
|
261
535
|
if (message.method === "thread/delete") {
|
|
262
536
|
const result = await claude.deleteThread(params.threadId);
|
|
263
537
|
handoffs.clearThread(params.threadId);
|
|
264
538
|
sendResult(message.id, result);
|
|
539
|
+
clearForeground(params.threadId);
|
|
265
540
|
return;
|
|
266
541
|
}
|
|
267
542
|
if (message.method === "thread/fork") {
|
|
268
543
|
const result = await claude.forkThread((message.params ?? {}));
|
|
269
544
|
sendResult(message.id, result);
|
|
545
|
+
selectForeground("claude", result.thread.id);
|
|
270
546
|
subscribeClaude(result.thread.id);
|
|
271
|
-
claude.announceThread(result.thread);
|
|
547
|
+
await claude.announceThread(result.thread);
|
|
272
548
|
return;
|
|
273
549
|
}
|
|
274
550
|
if (message.method === "thread/rollback") {
|
|
@@ -287,38 +563,64 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
287
563
|
sendResult(message.id, await claude.shellCommand((message.params ?? {})));
|
|
288
564
|
return;
|
|
289
565
|
}
|
|
566
|
+
if (message.method === "thread/backgroundTerminals/clean") {
|
|
567
|
+
sendResult(message.id, await claude.cleanBackgroundTerminals((message.params ?? {})));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (message.method === "thread/backgroundTerminals/list") {
|
|
571
|
+
sendResult(message.id, await claude.listBackgroundTerminals((message.params ?? {})));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (message.method === "thread/backgroundTerminals/terminate") {
|
|
575
|
+
sendResult(message.id, await claude.terminateBackgroundTerminal((message.params ?? {})));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
290
578
|
if (message.method === "review/start") {
|
|
291
579
|
const prepared = await claude.prepareReview((message.params ?? {}));
|
|
292
|
-
sendResult(message.id, prepared.response);
|
|
293
580
|
if (prepared.forkedThread) {
|
|
294
581
|
subscribeClaude(prepared.forkedThread.id);
|
|
295
|
-
claude.announceThread(prepared.forkedThread);
|
|
582
|
+
await claude.announceThread(prepared.forkedThread);
|
|
296
583
|
}
|
|
297
|
-
prepared.
|
|
584
|
+
sendResult(message.id, prepared.response);
|
|
585
|
+
await prepared.announce();
|
|
298
586
|
prepared.start();
|
|
299
587
|
return;
|
|
300
588
|
}
|
|
301
589
|
if (message.method === "thread/goal/set") {
|
|
302
|
-
|
|
590
|
+
const prepared = await claude.prepareGoalSet((message.params ?? {}));
|
|
591
|
+
try {
|
|
592
|
+
sendResult(message.id, prepared.response);
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
await prepared.notify();
|
|
596
|
+
}
|
|
303
597
|
return;
|
|
304
598
|
}
|
|
305
599
|
if (message.method === "thread/goal/get") {
|
|
306
|
-
sendResult(message.id, claude.getGoal(params.threadId));
|
|
600
|
+
sendResult(message.id, await claude.getGoal(params.threadId));
|
|
307
601
|
return;
|
|
308
602
|
}
|
|
309
603
|
if (message.method === "thread/goal/clear") {
|
|
310
|
-
|
|
604
|
+
const prepared = await claude.prepareGoalClear(params.threadId);
|
|
605
|
+
try {
|
|
606
|
+
sendResult(message.id, prepared.response);
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
await prepared.notify();
|
|
610
|
+
}
|
|
311
611
|
return;
|
|
312
612
|
}
|
|
313
613
|
if (message.method === "turn/start") {
|
|
314
|
-
const
|
|
614
|
+
const turn = (message.params ?? {});
|
|
615
|
+
const prepared = await claude.prepareTurn(turn);
|
|
315
616
|
sendResult(message.id, prepared.response);
|
|
316
|
-
|
|
617
|
+
selectForeground("claude", params.threadId);
|
|
618
|
+
await prepared.announce();
|
|
317
619
|
prepared.start();
|
|
318
620
|
return;
|
|
319
621
|
}
|
|
320
622
|
if (message.method === "turn/interrupt") {
|
|
321
|
-
await claude.interruptTurn((message.params ?? {})
|
|
623
|
+
await claude.interruptTurn((message.params ?? {}));
|
|
322
624
|
sendResult(message.id, {});
|
|
323
625
|
return;
|
|
324
626
|
}
|
|
@@ -341,18 +643,28 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
341
643
|
? message.params
|
|
342
644
|
: undefined;
|
|
343
645
|
const threadId = typeof params?.threadId === "string" ? params.threadId : undefined;
|
|
344
|
-
|
|
646
|
+
const childProjection = Boolean(threadId && claude.isChildProjection(threadId));
|
|
647
|
+
if (message.method === "turn/start" && threadId && !childProjection) {
|
|
345
648
|
const notice = transientSystemNotice(threadId, failure.message, "error");
|
|
346
649
|
sendResult(message.id, notice.response);
|
|
347
650
|
emitNotice(notice);
|
|
348
651
|
return;
|
|
349
652
|
}
|
|
350
|
-
if (threadId)
|
|
653
|
+
if (threadId && !childProjection)
|
|
351
654
|
emitSystemError(threadId, failure.message);
|
|
352
655
|
sendError(message.id, failure.code, failure.message);
|
|
353
|
-
if (threadId) {
|
|
656
|
+
if (threadId && !childProjection) {
|
|
354
657
|
if (claude.ownsThread(threadId)) {
|
|
355
|
-
claude.reportError(threadId, typeof params?.turnId === "string" ? params.turnId : undefined, failure.message, rpcCodexErrorInfo(failure.code))
|
|
658
|
+
await claude.reportError(threadId, typeof params?.turnId === "string" ? params.turnId : undefined, failure.message, rpcCodexErrorInfo(failure.code)).catch((diagnosticError) => {
|
|
659
|
+
logger.warn("claude.request.error-report-failed", {
|
|
660
|
+
connectionId,
|
|
661
|
+
method: message.method,
|
|
662
|
+
originalError: failure.message,
|
|
663
|
+
diagnosticError: diagnosticError instanceof Error
|
|
664
|
+
? diagnosticError.message
|
|
665
|
+
: String(diagnosticError),
|
|
666
|
+
});
|
|
667
|
+
});
|
|
356
668
|
}
|
|
357
669
|
else {
|
|
358
670
|
sendJson({
|
|
@@ -369,12 +681,20 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
369
681
|
return;
|
|
370
682
|
}
|
|
371
683
|
}
|
|
372
|
-
|
|
684
|
+
let forwarded = message;
|
|
685
|
+
if (message && isRequest(message) && message.method === "turn/start") {
|
|
686
|
+
const params = handoffs.prepareTitleTurn(connectionId, (message.params ?? {}));
|
|
687
|
+
if (params !== message.params)
|
|
688
|
+
forwarded = { ...message, params };
|
|
689
|
+
}
|
|
690
|
+
if (forwarded)
|
|
691
|
+
stockState.observeRequest(connectionId, forwarded);
|
|
692
|
+
trackForwardedRequest(forwarded);
|
|
373
693
|
if (stock.readyState === WebSocketState.OPEN) {
|
|
374
|
-
stock.send(data, { binary: isBinary });
|
|
694
|
+
stock.send(forwarded === message ? data : JSON.stringify(forwarded), { binary: isBinary });
|
|
375
695
|
}
|
|
376
696
|
else {
|
|
377
|
-
queued.push({ data, isBinary });
|
|
697
|
+
queued.push({ data: forwarded === message ? data : Buffer.from(JSON.stringify(forwarded)), isBinary });
|
|
378
698
|
}
|
|
379
699
|
});
|
|
380
700
|
stock.once("open", () => {
|
|
@@ -384,6 +704,11 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
384
704
|
});
|
|
385
705
|
stock.on("message", (data, isBinary) => {
|
|
386
706
|
const message = isBinary ? undefined : parseRpcMessage(data);
|
|
707
|
+
if (message) {
|
|
708
|
+
stockState.observeResponse(connectionId, message);
|
|
709
|
+
stockState.observeNotification(message);
|
|
710
|
+
}
|
|
711
|
+
let foregroundAfterForward;
|
|
387
712
|
if (message && "method" in message && !("id" in message) && message.method === "remoteControl/status/changed" && remoteControl) {
|
|
388
713
|
remoteControl.intercept(connectionId, notificationSink, message.params);
|
|
389
714
|
return;
|
|
@@ -392,12 +717,52 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
392
717
|
completeLatency(message.id, "stock");
|
|
393
718
|
const forwarded = forwardedRequests.get(requestKey(message.id));
|
|
394
719
|
forwardedRequests.delete(requestKey(message.id));
|
|
395
|
-
if (forwarded?.
|
|
720
|
+
if (forwarded?.systemEphemeral && "result" in message) {
|
|
721
|
+
const result = message.result && typeof message.result === "object"
|
|
722
|
+
? message.result
|
|
723
|
+
: undefined;
|
|
724
|
+
if (typeof result?.thread?.id === "string") {
|
|
725
|
+
handoffs.registerForwardedEphemeralCandidate(connectionId, result.thread.id, forwarded.systemEphemeral);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (forwarded?.threadId && isUuid(forwarded.threadId) && "error" in message) {
|
|
396
729
|
emitSystemError(forwarded.threadId, message.error.message);
|
|
730
|
+
}
|
|
731
|
+
if (forwarded && "result" in message) {
|
|
732
|
+
const result = message.result && typeof message.result === "object"
|
|
733
|
+
? message.result
|
|
734
|
+
: undefined;
|
|
735
|
+
const resultThreadId = typeof result?.thread?.id === "string" ? result.thread.id : forwarded.threadId;
|
|
736
|
+
if (forwarded.foreground && resultThreadId) {
|
|
737
|
+
handoffs.observeDurableThread(connectionId, "stock");
|
|
738
|
+
foregroundAfterForward = () => selectForeground(forwarded.foreground, resultThreadId);
|
|
739
|
+
}
|
|
740
|
+
else if (forwarded.clearForeground && forwarded.threadId) {
|
|
741
|
+
foregroundAfterForward = () => clearForeground(forwarded.threadId);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
397
744
|
}
|
|
398
745
|
if (message && stockRpc.handle(message))
|
|
399
746
|
return;
|
|
400
747
|
if (message && "method" in message) {
|
|
748
|
+
if (!("id" in message) && message.method === "account/rateLimits/updated" && foreground?.provider === "claude") {
|
|
749
|
+
publishForegroundRateLimits(foregroundGeneration);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (!("id" in message) && message.method === "account/rateLimits/updated" && !foreground) {
|
|
753
|
+
diagnoseUnknownStatus("account/rateLimits/updated");
|
|
754
|
+
}
|
|
755
|
+
if (handoffs.captureInternalStockMessage(connectionId, message)) {
|
|
756
|
+
if (isRequest(message))
|
|
757
|
+
stock.send(JSON.stringify({ id: message.id, result: { decision: "decline" } }));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const rewritten = handoffs.rewriteTitleMessages(message);
|
|
761
|
+
if (rewritten) {
|
|
762
|
+
for (const output of rewritten)
|
|
763
|
+
sendJson(output);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
401
766
|
if (handoffs.suppressStockTargetMessage(connectionId, message)) {
|
|
402
767
|
if (isRequest(message))
|
|
403
768
|
stock.send(JSON.stringify({ id: message.id, result: { decision: "decline" } }));
|
|
@@ -417,17 +782,12 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
417
782
|
if (typeof error?.message === "string")
|
|
418
783
|
emitSystemError(threadId, error.message);
|
|
419
784
|
}
|
|
420
|
-
if (threadId && handoffs.ownsInternalStockThread(threadId)) {
|
|
421
|
-
handoffs.recordInternalStockMessage(message);
|
|
422
|
-
if (isRequest(message))
|
|
423
|
-
stock.send(JSON.stringify({ id: message.id, result: { decision: "decline" } }));
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
785
|
}
|
|
427
786
|
if (client.readyState === WebSocketState.OPEN) {
|
|
428
787
|
recorder.frame(connectionId, "gateway_to_client", data, isBinary);
|
|
429
788
|
client.send(data, { binary: isBinary });
|
|
430
789
|
}
|
|
790
|
+
foregroundAfterForward?.();
|
|
431
791
|
});
|
|
432
792
|
const closeClient = (code, reason) => {
|
|
433
793
|
if (client.readyState === WebSocketState.OPEN)
|
|
@@ -439,17 +799,44 @@ export function attachClientConnection(client, stockSocket, claudeModels, claude
|
|
|
439
799
|
});
|
|
440
800
|
stock.on("close", () => closeClient(1011, "Stock app-server closed"));
|
|
441
801
|
client.on("error", (error) => logger.warn("connection.client.error", { connectionId, error: error.message }));
|
|
802
|
+
let closeStarted = false;
|
|
442
803
|
client.on("close", (code, reason) => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
804
|
+
if (closeStarted)
|
|
805
|
+
return;
|
|
806
|
+
closeStarted = true;
|
|
807
|
+
void (async () => {
|
|
808
|
+
metrics.connectionClosed();
|
|
809
|
+
const releasedThreads = [...subscribedClaudeThreads];
|
|
810
|
+
subscribedClaudeThreads.clear();
|
|
811
|
+
subscriptions.detach(connectionId);
|
|
812
|
+
claude.unsubscribeRateLimits(connectionId);
|
|
813
|
+
const releases = await Promise.allSettled(releasedThreads.map(async (threadId) => {
|
|
814
|
+
if (!subscriptions.hasSubscribers(threadId))
|
|
815
|
+
await claude.releaseEphemeralThread(threadId);
|
|
816
|
+
}));
|
|
817
|
+
remoteControl?.detach(connectionId);
|
|
818
|
+
let detach;
|
|
819
|
+
try {
|
|
820
|
+
await handoffs.detachConnection(connectionId, stockRpc);
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
detach = error;
|
|
824
|
+
}
|
|
825
|
+
stockState.detach(connectionId);
|
|
826
|
+
stockRpc.close(new Error("Client connection closed."));
|
|
827
|
+
if (stock.readyState === WebSocketState.OPEN || stock.readyState === WebSocketState.CONNECTING) {
|
|
828
|
+
stock.close();
|
|
829
|
+
}
|
|
830
|
+
logger.info("connection.closed", { connectionId });
|
|
831
|
+
recorder.connection("closed", connectionId, { code, reason: reason.toString("utf8") });
|
|
832
|
+
const errors = [
|
|
833
|
+
...releases.flatMap((result) => result.status === "rejected" ? [result.reason] : []),
|
|
834
|
+
...(detach === undefined ? [] : [detach]),
|
|
835
|
+
];
|
|
836
|
+
if (errors.length > 0)
|
|
837
|
+
throw new AggregateError(errors, "Client connection cleanup failed.");
|
|
838
|
+
})().then(resolveClosed, rejectClosed);
|
|
453
839
|
});
|
|
840
|
+
return { closed };
|
|
454
841
|
}
|
|
455
842
|
//# sourceMappingURL=clientConnection.js.map
|