@bitkyc08/opencodex 2.25.0 → 2.26.0
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/bin/ocx.mjs +59 -7
- package/gui/dist/assets/index-RL6b1bTV.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +18 -0
- package/src/adapters/client-fingerprint.ts +0 -2
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/live-models.ts +117 -30
- package/src/adapters/cursor/live-transport.ts +137 -56
- package/src/adapters/cursor/native-exec-fs.ts +1 -1
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/protobuf-request.ts +89 -21
- package/src/adapters/cursor/tool-definitions.ts +18 -7
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +7 -0
- package/src/adapters/cursor.ts +2 -0
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google.ts +23 -3
- package/src/adapters/openai-chat.ts +38 -13
- package/src/adapters/openai-responses.ts +129 -9
- package/src/adapters/registry.ts +30 -1
- package/src/bridge.ts +50 -12
- package/src/chat/inbound.ts +1 -0
- package/src/claude/agents-inject.ts +3 -2
- package/src/cli/dispatch.ts +19 -6
- package/src/cli/help.ts +2 -1
- package/src/cli/integrations.ts +35 -0
- package/src/cli/minimax.ts +8 -2
- package/src/cli/registry.ts +13 -2
- package/src/clients/config-export.ts +120 -1
- package/src/codex/account-store.ts +17 -1
- package/src/codex/auth-api.ts +51 -17
- package/src/codex/catalog/effort.ts +8 -5
- package/src/codex/catalog/provider-fetch.ts +39 -27
- package/src/codex/catalog/sync.ts +30 -8
- package/src/codex/inject.ts +39 -13
- package/src/codex/main-account.ts +29 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +25 -0
- package/src/codex/prompt-journal.ts +6 -2
- package/src/codex/quota-rejection.ts +21 -7
- package/src/codex/refresh.ts +4 -2
- package/src/codex/sync.ts +106 -3
- package/src/codex/warmup.ts +187 -81
- package/src/config.ts +85 -46
- package/src/generated/compatibility-version.json +127 -83
- package/src/grok/inject.ts +1 -1
- package/src/images/loop.ts +1 -0
- package/src/integrations/registry.ts +7 -0
- package/src/lab/automation/config-persistence.ts +2 -2
- package/src/lab/automation/persistence.ts +2 -2
- package/src/lab/ledger/purge.ts +2 -2
- package/src/lab/subject/behavior-fingerprint.ts +2 -2
- package/src/lib/config-ownership.ts +5 -2
- package/src/lib/destination-policy.ts +18 -1
- package/src/lib/provider-outbound.ts +7 -0
- package/src/lib/redact.ts +11 -0
- package/src/lib/tool-argument-integers.ts +50 -6
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-retry.ts +2 -1
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/oauth/google-antigravity.ts +2 -2
- package/src/oauth/index.ts +60 -8
- package/src/providers/antigravity-models.ts +58 -4
- package/src/providers/command-code-efforts.ts +36 -9
- package/src/providers/context-cap.ts +9 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fastwire.ts +453 -0
- package/src/providers/registry.ts +21 -1
- package/src/providers/service-tier.ts +173 -89
- package/src/responses/parser.ts +25 -16
- package/src/responses/reasoning-replay-cache.ts +30 -0
- package/src/responses/thought-signature-replay.ts +74 -4
- package/src/router.ts +6 -0
- package/src/routing/compatibility/behavior.ts +27 -16
- package/src/server/index.ts +10 -1
- package/src/server/management/oauth-account-routes.ts +10 -2
- package/src/server/management/shared.ts +1 -1
- package/src/server/management/system-routes.ts +15 -0
- package/src/server/request-log.ts +57 -3
- package/src/server/responses/agent-task-recovery.ts +6 -1
- package/src/server/responses/core.ts +209 -40
- package/src/server/responses/fetch-helpers.ts +15 -36
- package/src/server/responses/responses-field-backfill.ts +173 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
- package/src/service.ts +38 -36
- package/src/storage/cleanup.ts +2 -2
- package/src/tray/windows.ts +3 -2
- package/src/types.ts +101 -14
- package/src/update/job.ts +9 -18
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +34 -5
- package/src/usage/log.ts +74 -4
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/web-search/anthropic-executor.ts +8 -6
- package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
package/src/usage/log.ts
CHANGED
|
@@ -4,8 +4,9 @@ import { join } from "node:path";
|
|
|
4
4
|
import { getConfigDir } from "../config";
|
|
5
5
|
import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
|
|
6
6
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
7
|
+
import { sanitizeLogMetadataString } from "../lib/redact";
|
|
7
8
|
import { usageDisplayTotalTokens } from "./totals";
|
|
8
|
-
import type { OcxUsage } from "../types";
|
|
9
|
+
import type { AttemptTierOutcome, OcxUsage } from "../types";
|
|
9
10
|
import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
|
|
10
11
|
import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
11
12
|
|
|
@@ -61,6 +62,8 @@ export interface PersistedUsageAttempt {
|
|
|
61
62
|
effectiveEffort?: string;
|
|
62
63
|
reasoningWireField?: string;
|
|
63
64
|
reasoningWireValue?: string | number | boolean;
|
|
65
|
+
/** Adapter-produced tier fact for this physical attempt; absent on pre-B0 rows. */
|
|
66
|
+
tierOutcome?: AttemptTierOutcome;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
export interface PersistedUsageEntry {
|
|
@@ -87,12 +90,16 @@ export interface PersistedUsageEntry {
|
|
|
87
90
|
effectiveEffort?: string;
|
|
88
91
|
reasoningWireField?: string;
|
|
89
92
|
reasoningWireValue?: string | number | boolean;
|
|
93
|
+
/** Raw caller tier captured before routing, sanitized and bounded for durable logs. */
|
|
94
|
+
callerServiceTier?: string;
|
|
90
95
|
requestedServiceTier?: string;
|
|
91
96
|
requestedSpeedLabel?: string;
|
|
92
97
|
configuredServiceTier?: string;
|
|
93
98
|
configuredSpeedLabel?: string;
|
|
94
99
|
modelSupportsServiceTier?: boolean;
|
|
95
100
|
responseServiceTier?: string;
|
|
101
|
+
/** Summary of the final physical attempt for dashboard consumers. */
|
|
102
|
+
tierOutcome?: AttemptTierOutcome;
|
|
96
103
|
status: number;
|
|
97
104
|
durationMs: number;
|
|
98
105
|
/** TTFT relative to the request start (WP4); unset for non-streaming/tool-only. */
|
|
@@ -218,6 +225,15 @@ const USAGE_STATUSES = new Set<UsageStatus>([
|
|
|
218
225
|
"estimated",
|
|
219
226
|
]);
|
|
220
227
|
const LAB_ROUTE_SUBJECT_ID_RE = /^[0-9a-f]{64}$/;
|
|
228
|
+
const FAST_OUTCOMES = new Set<AttemptTierOutcome["fastOutcome"]>([
|
|
229
|
+
"not-requested", "applied", "downgraded", "unknown",
|
|
230
|
+
]);
|
|
231
|
+
const TIER_CONFIRMATIONS = new Set<AttemptTierOutcome["confirmation"]>([
|
|
232
|
+
"confirmed", "assumed", "downgraded", "unknown",
|
|
233
|
+
]);
|
|
234
|
+
const FAST_DOWNGRADE_REASONS = new Set<NonNullable<AttemptTierOutcome["fastDowngradeReason"]>>([
|
|
235
|
+
"route-unsupported", "wire-unavailable", "response-declined",
|
|
236
|
+
]);
|
|
221
237
|
|
|
222
238
|
export function isLabRouteSubjectId(value: unknown): value is string {
|
|
223
239
|
return typeof value === "string" && LAB_ROUTE_SUBJECT_ID_RE.test(value);
|
|
@@ -246,6 +262,53 @@ function normalizeAttemptUsage(raw: unknown): OcxUsage | null {
|
|
|
246
262
|
return normalizeUsageValue(usage as unknown as OcxUsage) ?? null;
|
|
247
263
|
}
|
|
248
264
|
|
|
265
|
+
function normalizeAttemptTierOutcome(raw: unknown): AttemptTierOutcome | null {
|
|
266
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
267
|
+
const outcome = raw as Record<string, unknown>;
|
|
268
|
+
if (typeof outcome.fastOutcome !== "string"
|
|
269
|
+
|| !FAST_OUTCOMES.has(outcome.fastOutcome as AttemptTierOutcome["fastOutcome"])
|
|
270
|
+
|| typeof outcome.confirmation !== "string"
|
|
271
|
+
|| !TIER_CONFIRMATIONS.has(outcome.confirmation as AttemptTierOutcome["confirmation"])) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
if ("canonical" in outcome && outcome.canonical !== "priority") return null;
|
|
275
|
+
if ("wireKind" in outcome
|
|
276
|
+
&& outcome.wireKind !== null
|
|
277
|
+
&& outcome.wireKind !== "service-tier"
|
|
278
|
+
&& outcome.wireKind !== "anthropic-speed") return null;
|
|
279
|
+
if ("wireValue" in outcome && outcome.wireValue !== null && typeof outcome.wireValue !== "string") return null;
|
|
280
|
+
if ("fastDowngradeReason" in outcome
|
|
281
|
+
&& (typeof outcome.fastDowngradeReason !== "string"
|
|
282
|
+
|| !FAST_DOWNGRADE_REASONS.has(outcome.fastDowngradeReason as NonNullable<AttemptTierOutcome["fastDowngradeReason"]>))) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
if ("callerTierDropped" in outcome && typeof outcome.callerTierDropped !== "boolean") return null;
|
|
286
|
+
if ("callerFastSuppressedByConfig" in outcome
|
|
287
|
+
&& typeof outcome.callerFastSuppressedByConfig !== "boolean") return null;
|
|
288
|
+
if ("responseServiceTier" in outcome && typeof outcome.responseServiceTier !== "string") return null;
|
|
289
|
+
const wireValue = sanitizeLogMetadataString(outcome.wireValue);
|
|
290
|
+
const responseServiceTier = sanitizeLogMetadataString(outcome.responseServiceTier);
|
|
291
|
+
return {
|
|
292
|
+
...(outcome.canonical === "priority" ? { canonical: "priority" as const } : {}),
|
|
293
|
+
...(outcome.wireKind === null || outcome.wireKind === "service-tier" || outcome.wireKind === "anthropic-speed"
|
|
294
|
+
? { wireKind: outcome.wireKind }
|
|
295
|
+
: {}),
|
|
296
|
+
...(outcome.wireValue === null
|
|
297
|
+
? { wireValue: null }
|
|
298
|
+
: wireValue ? { wireValue } : {}),
|
|
299
|
+
fastOutcome: outcome.fastOutcome as AttemptTierOutcome["fastOutcome"],
|
|
300
|
+
...(typeof outcome.fastDowngradeReason === "string"
|
|
301
|
+
? { fastDowngradeReason: outcome.fastDowngradeReason as NonNullable<AttemptTierOutcome["fastDowngradeReason"]> }
|
|
302
|
+
: {}),
|
|
303
|
+
...(typeof outcome.callerTierDropped === "boolean" ? { callerTierDropped: outcome.callerTierDropped } : {}),
|
|
304
|
+
...(typeof outcome.callerFastSuppressedByConfig === "boolean"
|
|
305
|
+
? { callerFastSuppressedByConfig: outcome.callerFastSuppressedByConfig }
|
|
306
|
+
: {}),
|
|
307
|
+
confirmation: outcome.confirmation as AttemptTierOutcome["confirmation"],
|
|
308
|
+
...(responseServiceTier ? { responseServiceTier } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
249
312
|
function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
250
313
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
251
314
|
const attempt = raw as Record<string, unknown>;
|
|
@@ -272,6 +335,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
272
335
|
&& !isNonNegativeFiniteNumber(attempt.totalTokens)) return null;
|
|
273
336
|
const usage = "usage" in attempt ? normalizeAttemptUsage(attempt.usage) : undefined;
|
|
274
337
|
if ("usage" in attempt && usage === null) return null;
|
|
338
|
+
const tierOutcome = "tierOutcome" in attempt
|
|
339
|
+
? normalizeAttemptTierOutcome(attempt.tierOutcome)
|
|
340
|
+
: undefined;
|
|
275
341
|
const recoveryKinds = Array.isArray(attempt.recoveryKinds)
|
|
276
342
|
? [...new Set(attempt.recoveryKinds.filter(
|
|
277
343
|
(value): value is AttemptRecoveryKind => typeof value === "string"
|
|
@@ -321,6 +387,7 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
321
387
|
? { reasoningWireValue: capMetadataString(attempt.reasoningWireValue) }
|
|
322
388
|
: { reasoningWireValue: attempt.reasoningWireValue }
|
|
323
389
|
: {}),
|
|
390
|
+
...(tierOutcome ? { tierOutcome } : {}),
|
|
324
391
|
};
|
|
325
392
|
}
|
|
326
393
|
|
|
@@ -357,6 +424,9 @@ export function normalizeUsageEntryForTest(entry: PersistedUsageEntry): Persiste
|
|
|
357
424
|
|
|
358
425
|
function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
359
426
|
const attempts = normalizedAttempts(entry.attempts);
|
|
427
|
+
const tierOutcome = entry.tierOutcome ? normalizeAttemptTierOutcome(entry.tierOutcome) : undefined;
|
|
428
|
+
const callerServiceTier = sanitizeLogMetadataString(entry.callerServiceTier);
|
|
429
|
+
const responseServiceTier = sanitizeLogMetadataString(entry.responseServiceTier);
|
|
360
430
|
const routeDecision = entry.routeDecision
|
|
361
431
|
? normalizeRouteDecisionTrace(entry.routeDecision)
|
|
362
432
|
: undefined;
|
|
@@ -397,6 +467,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
397
467
|
? { reasoningWireValue: capMetadataString(entry.reasoningWireValue) }
|
|
398
468
|
: { reasoningWireValue: entry.reasoningWireValue }
|
|
399
469
|
: {}),
|
|
470
|
+
...(callerServiceTier ? { callerServiceTier } : {}),
|
|
400
471
|
...(typeof entry.requestedServiceTier === "string" && entry.requestedServiceTier
|
|
401
472
|
? { requestedServiceTier: capMetadataString(entry.requestedServiceTier) }
|
|
402
473
|
: {}),
|
|
@@ -412,9 +483,8 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
412
483
|
...(typeof entry.modelSupportsServiceTier === "boolean"
|
|
413
484
|
? { modelSupportsServiceTier: entry.modelSupportsServiceTier }
|
|
414
485
|
: {}),
|
|
415
|
-
...(
|
|
416
|
-
|
|
417
|
-
: {}),
|
|
486
|
+
...(responseServiceTier ? { responseServiceTier } : {}),
|
|
487
|
+
...(tierOutcome ? { tierOutcome } : {}),
|
|
418
488
|
status: entry.status,
|
|
419
489
|
durationMs: entry.durationMs,
|
|
420
490
|
...(isNonNegativeFiniteNumber(entry.firstOutputMs)
|
|
@@ -3,7 +3,7 @@ import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fin
|
|
|
3
3
|
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
4
4
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
5
5
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
6
|
-
import { getValidAccessToken } from "../oauth";
|
|
6
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
7
7
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
|
|
8
8
|
import type { DescribeOutcome, VisionSettings } from "./describe";
|
|
9
9
|
|
|
@@ -67,8 +67,8 @@ export async function parseAnthropicVisionSSE(res: Response): Promise<DescribeOu
|
|
|
67
67
|
const delta = isRecord(data.delta) ? data.delta : {};
|
|
68
68
|
if (delta.type === "text_delta" && typeof delta.text === "string") text += delta.text;
|
|
69
69
|
} else if (data.type === "error") {
|
|
70
|
-
|
|
71
|
-
terminalError =
|
|
70
|
+
// Provider-authored stream errors can contain credentials, paths, or response bodies.
|
|
71
|
+
terminalError = "anthropic vision sidecar stream error";
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
|
|
@@ -116,7 +116,7 @@ export async function describeImageAnthropic(
|
|
|
116
116
|
try {
|
|
117
117
|
token = await getValidAccessToken(providerName);
|
|
118
118
|
} catch (error) {
|
|
119
|
-
return { text: "", error: `anthropic vision sidecar auth failed: ${
|
|
119
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(error)}` };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const headers: Record<string, string> = {
|
|
@@ -166,7 +166,11 @@ export async function describeImageAnthropic(
|
|
|
166
166
|
if (!res.ok) {
|
|
167
167
|
const responseText = await res.text().catch(() => "");
|
|
168
168
|
console.warn(`[vision] anthropic sidecar HTTP ${res.status} (${Date.now() - startedAt}ms)`);
|
|
169
|
-
|
|
169
|
+
if (res.status === 401) {
|
|
170
|
+
return { text: "", error: `anthropic vision sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(responseText))}` };
|
|
171
|
+
}
|
|
172
|
+
// Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
|
|
173
|
+
return { text: "", error: `anthropic vision sidecar HTTP ${res.status}` };
|
|
170
174
|
}
|
|
171
175
|
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
172
176
|
try {
|
|
@@ -177,7 +181,7 @@ export async function describeImageAnthropic(
|
|
|
177
181
|
} catch (error) {
|
|
178
182
|
const kind = error instanceof Error && error.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
179
183
|
console.warn(`[vision] anthropic sidecar ${kind} (${Date.now() - startedAt}ms)`);
|
|
180
|
-
return { text: "", error:
|
|
184
|
+
return { text: "", error: `anthropic vision sidecar ${kind}` };
|
|
181
185
|
} finally {
|
|
182
186
|
sidecarExit();
|
|
183
187
|
linkedSignal.cleanup();
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../types";
|
|
2
|
-
import { getValidAccessToken } from "../oauth";
|
|
2
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
3
3
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
|
|
4
4
|
import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fingerprint";
|
|
5
5
|
import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
|
|
6
|
-
import { redactSecretString } from "../lib/redact";
|
|
7
6
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
8
7
|
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
9
8
|
import type { WebSearchSource } from "./parse";
|
|
@@ -127,7 +126,7 @@ export async function runAnthropicWebSearch(
|
|
|
127
126
|
try {
|
|
128
127
|
token = await getValidAccessToken(providerName);
|
|
129
128
|
} catch (e) {
|
|
130
|
-
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${
|
|
129
|
+
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
|
|
131
130
|
}
|
|
132
131
|
const headers: Record<string, string> = {
|
|
133
132
|
"Content-Type": "application/json",
|
|
@@ -174,8 +173,11 @@ export async function runAnthropicWebSearch(
|
|
|
174
173
|
const t = await res.text().catch(() => "");
|
|
175
174
|
detachBodyGuard();
|
|
176
175
|
console.warn(`[web-search] anthropic sidecar HTTP ${res.status} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
if (res.status === 401) {
|
|
177
|
+
return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(t))}` };
|
|
178
|
+
}
|
|
179
|
+
// Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
|
|
180
|
+
return { text: "", sources: [], error: `sidecar HTTP ${res.status}` };
|
|
179
181
|
}
|
|
180
182
|
try {
|
|
181
183
|
return await parseAnthropicSidecarSSE(res);
|
|
@@ -185,7 +187,7 @@ export async function runAnthropicWebSearch(
|
|
|
185
187
|
} catch (e) {
|
|
186
188
|
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
187
189
|
console.warn(`[web-search] anthropic sidecar ${kind} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
|
|
188
|
-
return { text: "", sources: [], error:
|
|
190
|
+
return { text: "", sources: [], error: `anthropic sidecar ${kind}` };
|
|
189
191
|
} finally {
|
|
190
192
|
sidecarExit();
|
|
191
193
|
linkedSignal.cleanup();
|