@bitkyc08/opencodex 2.67.0 → 2.68.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 +3 -0
- package/gui/dist/assets/App-DCBismRi.css +1 -0
- package/gui/dist/assets/App-mpsibsFj.js +51 -0
- package/gui/dist/assets/Tray-Bn8WhKBH.js +1 -0
- package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
- package/gui/dist/assets/index-kUE8tuAK.js +86 -0
- package/gui/dist/assets/quota-summary-DL-bNuNL.js +1 -0
- package/gui/dist/index.html +21 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-guard.ts +13 -1
- package/src/adapters/anthropic-image-normalize.ts +3 -2
- package/src/adapters/anthropic-output-schema.ts +36 -0
- package/src/adapters/anthropic.ts +10 -2
- package/src/adapters/base.ts +7 -0
- package/src/adapters/codebuddy/live-models.ts +180 -0
- package/src/adapters/coding-agent/protocol.ts +91 -12
- package/src/adapters/coding-agent/turn.ts +32 -27
- package/src/adapters/devin/cloud-direct/index.ts +1 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +18 -4
- package/src/adapters/devin.ts +9 -6
- package/src/adapters/google-http.ts +4 -4
- package/src/adapters/google.ts +93 -3
- package/src/adapters/kiro/adapter.ts +6 -2
- package/src/adapters/kiro/stream.ts +12 -2
- package/src/adapters/kiro/usage.ts +3 -1
- package/src/adapters/kiro-errors.ts +11 -1
- package/src/adapters/kiro-events.ts +27 -1
- package/src/adapters/kiro-refusal.ts +30 -0
- package/src/adapters/kiro-retry.ts +71 -31
- package/src/adapters/openai-chat/deepseek-artifact-schema.ts +45 -0
- package/src/adapters/openai-chat/messages.ts +31 -4
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +52 -7
- package/src/adapters/openai-chat/tool-call-id-remint.ts +65 -0
- package/src/adapters/openai-chat/tool-schema.ts +6 -2
- package/src/adapters/openai-chat.ts +2 -2
- package/src/adapters/openai-responses/muse-tool-choice.ts +31 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -3
- package/src/adapters/opencode-go-additional-tools.ts +19 -10
- package/src/adapters/physical-send.ts +10 -3
- package/src/adapters/registry.ts +2 -1
- package/src/adapters/responses-tool-schema.ts +31 -3
- package/src/adapters/run-turn-queue.ts +63 -23
- package/src/adapters/unique-tool-call-ids.ts +63 -0
- package/src/adapters/xai-web-search.ts +32 -2
- package/src/bridge/sse.ts +14 -2
- package/src/chat/inbound.ts +20 -6
- package/src/claude/agents-inject.ts +4 -0
- package/src/claude/desktop-3p.ts +53 -13
- package/src/claude/desktop-profile.ts +41 -4
- package/src/claude/inbound-model-options.ts +14 -2
- package/src/claude/inbound.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +17 -1
- package/src/claude/intercept/local-ca.ts +7 -1
- package/src/claude/message-threads.ts +28 -0
- package/src/cli/account-api.ts +13 -0
- package/src/cli/account-auth.ts +49 -2
- package/src/cli/account-extended.ts +4 -2
- package/src/cli/account.ts +4 -2
- package/src/cli/capabilities.ts +17 -0
- package/src/cli/claude.ts +3 -1
- package/src/cli/dispatch.ts +62 -5
- package/src/cli/index.ts +84 -79
- package/src/cli/minimax.ts +4 -2
- package/src/cli/opencode.ts +6 -1
- package/src/cli/restart-handoff.ts +110 -0
- package/src/cli/status.ts +51 -0
- package/src/client/connect.ts +34 -15
- package/src/client/link-ingress.ts +102 -0
- package/src/client/link-join.ts +37 -16
- package/src/client/link-relay.ts +228 -45
- package/src/client/link-state.ts +54 -0
- package/src/client/link-status.ts +39 -0
- package/src/client/link-teardown.ts +2 -2
- package/src/client/link-tunnel.ts +615 -86
- package/src/client/machine-api.ts +10 -4
- package/src/client/machine-listener.ts +57 -10
- package/src/client/runtime.ts +210 -35
- package/src/clients/config-export/constants.ts +1 -12
- package/src/clients/config-export/contracts.ts +2 -0
- package/src/clients/config-export/model-metadata.ts +9 -3
- package/src/clients/config-export/omp.ts +1 -1
- package/src/clients/config-export/zcode-store.ts +2 -2
- package/src/clients/config-export.ts +36 -3
- package/src/codex/app-server-processes.ts +32 -0
- package/src/codex/app-server-restart-service.ts +20 -3
- package/src/codex/catalog/access-programs.ts +50 -0
- package/src/codex/catalog/build-entries.ts +13 -0
- package/src/codex/catalog/derive-entry.ts +1 -0
- package/src/codex/catalog/model-hints.ts +11 -0
- package/src/codex/catalog/parsing.ts +6 -2
- package/src/codex/catalog/provider-models.ts +81 -0
- package/src/codex/catalog/retained-sync.ts +3 -0
- package/src/codex/codex-write-lock.ts +2 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desired-state.ts +14 -4
- package/src/codex/home.ts +21 -3
- package/src/codex/inject/restore.ts +17 -0
- package/src/codex/inject/routing-classify.ts +3 -2
- package/src/codex/inject-coordination.ts +4 -1
- package/src/codex/inject.ts +8 -1
- package/src/codex/journal.ts +6 -2
- package/src/codex/management-convergence.ts +9 -0
- package/src/codex/model-entitlements.ts +28 -5
- package/src/codex/routing/idle-window.ts +58 -0
- package/src/codex/routing-drift.ts +134 -0
- package/src/codex/routing-healer.ts +419 -0
- package/src/codex/routing.ts +10 -0
- package/src/codex/runtime.ts +188 -57
- package/src/codex/sibling-handoff.ts +71 -0
- package/src/codex/sibling-start.ts +139 -0
- package/src/codex/sync.ts +5 -3
- package/src/combos/failover.ts +32 -5
- package/src/combos/request.ts +11 -3
- package/src/combos/reset-window.ts +10 -5
- package/src/combos/resolve.ts +36 -6
- package/src/config/diagnostics.ts +2 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/paths.ts +7 -1
- package/src/config/process-state.ts +10 -1
- package/src/config/schema/compaction-recovery.ts +15 -0
- package/src/config/schema/config-schema.ts +2 -0
- package/src/config/schema/leaf-validators.ts +9 -3
- package/src/generated/compatibility-version.json +360 -204
- package/src/images/loop.ts +47 -32
- package/src/integrations/catalog-refresh.ts +4 -0
- package/src/integrations/omp-yaml-source.ts +1 -0
- package/src/lib/claude-request-projection.ts +101 -0
- package/src/lib/local-account-switch-capability.ts +48 -0
- package/src/lib/local-upstream.ts +146 -0
- package/src/lib/package-tree-integrity.ts +25 -3
- package/src/lib/package-tree-retarget.ts +130 -0
- package/src/lib/process-control.ts +4 -1
- package/src/lib/system-restart-contract.ts +106 -0
- package/src/lib/upstream-retry.ts +57 -1
- package/src/link/ports.ts +9 -0
- package/src/link/ssh-argv.ts +16 -0
- package/src/link/ssh-runner.ts +70 -2
- package/src/link/tunnel-state.ts +58 -9
- package/src/oauth/account-quota-rank.ts +29 -18
- package/src/oauth/generic-account-failover.ts +179 -22
- package/src/oauth/index.ts +17 -4
- package/src/oauth/kiro-account-load.ts +107 -0
- package/src/oauth/kiro-device-login.ts +312 -0
- package/src/oauth/kiro-terminal-failover.ts +28 -0
- package/src/oauth/login-flow-state.ts +7 -1
- package/src/oauth/pool-settings-capability.ts +23 -14
- package/src/oauth/store.ts +56 -3
- package/src/oauth/types.ts +4 -0
- package/src/plugins/loader.ts +350 -0
- package/src/plugins/upstream-hooks.ts +119 -0
- package/src/protocols/encoders/adapter-events.ts +9 -2
- package/src/providers/account-quota-disk.ts +42 -4
- package/src/providers/codebuddy-models.ts +6 -2
- package/src/providers/command-code-efforts.ts +33 -10
- package/src/providers/kiro-account-state-disk.ts +44 -0
- package/src/providers/kiro-model-catalog.ts +162 -0
- package/src/providers/kiro-models.ts +5 -4
- package/src/providers/kiro-quota-metrics.ts +35 -0
- package/src/providers/kiro-usage.ts +118 -14
- package/src/providers/quota/account-cache.ts +43 -7
- package/src/providers/quota/antigravity.ts +8 -3
- package/src/providers/quota/kiro-account-probe.ts +12 -0
- package/src/providers/quota/vendor-probes-key.ts +31 -26
- package/src/providers/quota/vendor-probes-oauth.ts +16 -7
- package/src/providers/quota-types.ts +14 -0
- package/src/providers/quota.ts +21 -21
- package/src/providers/registry/entries-extended.ts +5 -3
- package/src/providers/request-pacing.ts +410 -12
- package/src/remote-control/workspace-codex-runtime.ts +4 -3
- package/src/responses/citation-markers.ts +132 -65
- package/src/responses/hosted-tool-policy.ts +14 -3
- package/src/responses/parser-content.ts +3 -2
- package/src/responses/parser.ts +4 -1
- package/src/responses/schema.ts +4 -0
- package/src/responses/visualization-directives.ts +182 -0
- package/src/server/chat-native-sse.ts +24 -12
- package/src/server/chat-native.ts +137 -68
- package/src/server/claude-messages.ts +147 -12
- package/src/server/index/link-listener.ts +7 -0
- package/src/server/index/optional-listeners.ts +4 -1
- package/src/server/index/package-tree-guard.ts +35 -16
- package/src/server/index/serve-options.ts +4 -2
- package/src/server/index/startup-warnings.ts +6 -0
- package/src/server/index.ts +7 -7
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/inference/context.ts +30 -2
- package/src/server/local-account-switch-auth.ts +79 -0
- package/src/server/management/agent-settings-routes.ts +11 -3
- package/src/server/management/config-routes.ts +28 -11
- package/src/server/management/context.ts +2 -0
- package/src/server/management/link-routes.ts +123 -39
- package/src/server/management/logs-usage-routes.ts +9 -42
- package/src/server/management/model-rows.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +67 -12
- package/src/server/management/route-registry.ts +5 -5
- package/src/server/management/sibling-guard.ts +60 -0
- package/src/server/management/storage-log-guard-routes.ts +72 -6
- package/src/server/management/system-restart.ts +92 -135
- package/src/server/management/system-routes.ts +1 -1
- package/src/server/management-api.ts +31 -3
- package/src/server/management-auth.ts +3 -0
- package/src/server/port-reclaim.ts +111 -1
- package/src/server/proxy-liveness.ts +16 -0
- package/src/server/relay.ts +16 -15
- package/src/server/request-log-filter.ts +28 -27
- package/src/server/request-log.ts +7 -3
- package/src/server/request-metrics.ts +28 -0
- package/src/server/responses/adapter-continuation.ts +151 -21
- package/src/server/responses/adapter-delivery.ts +21 -7
- package/src/server/responses/adapter-dispatch.ts +239 -51
- package/src/server/responses/codex-ws-pool.ts +5 -4
- package/src/server/responses/codex-ws-request.ts +4 -1
- package/src/server/responses/compact.ts +8 -4
- package/src/server/responses/compaction-recovery-policy.ts +116 -0
- package/src/server/responses/compaction-recovery.ts +330 -0
- package/src/server/responses/core-combo-failure.ts +1 -0
- package/src/server/responses/core-combo.ts +5 -1
- package/src/server/responses/core-lifetime.ts +22 -0
- package/src/server/responses/core-options.ts +16 -1
- package/src/server/responses/core.ts +14 -4
- package/src/server/responses/empty-completion-guard.ts +2 -0
- package/src/server/responses/fetch-helpers.ts +82 -31
- package/src/server/responses/passthrough-delivery.ts +3 -2
- package/src/server/responses/passthrough-dispatch.ts +12 -2
- package/src/server/responses/passthrough-execution.ts +2 -2
- package/src/server/responses/request-send-budget.ts +21 -5
- package/src/server/responses/request-transport.ts +116 -18
- package/src/server/responses/reset-replay.ts +32 -0
- package/src/server/responses/run-turn-execution.ts +136 -19
- package/src/server/responses/sidecar-execution.ts +41 -8
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/responses/ws-upstream.ts +44 -4
- package/src/server/restart-replacement.ts +382 -0
- package/src/server/startup-health-cache.ts +23 -4
- package/src/server/stop-teardown.ts +35 -1
- package/src/server/system-env.ts +3 -0
- package/src/service/windows-taskxml.ts +11 -7
- package/src/service/windows-wrapper-exit.ts +8 -0
- package/src/stall-timeout.ts +35 -10
- package/src/storage/policy-job.ts +4 -0
- package/src/storage/scanner.ts +177 -29
- package/src/storage/storage-mutation-coordinator.ts +17 -0
- package/src/tray/windows-tray.ps1 +50 -23
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +5 -1
- package/src/types/request.ts +13 -2
- package/src/update/index.ts +3 -1
- package/src/update/job.ts +6 -2
- package/src/update/mise-launcher-target.ts +100 -0
- package/src/update/worker-launch.ts +46 -0
- package/src/usage/log.ts +2 -0
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-BJsT8Icc.css +0 -1
- package/gui/dist/assets/App-D3pNGiN4.js +0 -51
- package/gui/dist/assets/Tray-yb05wCjb.js +0 -1
- package/gui/dist/assets/index-CouvvtMV.js +0 -86
- package/gui/dist/assets/tray-data-CYGdjCJ7.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OcxClaudeCodeConfig } from "../types";
|
|
2
|
-
import { isAnthropicOutputSchema } from "../adapters/anthropic-output-schema";
|
|
2
|
+
import { isAnthropicOutputSchema, satisfiesOpenAiStrictSchema } from "../adapters/anthropic-output-schema";
|
|
3
3
|
import { resolveAlias } from "./alias";
|
|
4
4
|
import { stripOneMillionMarker } from "./context-windows";
|
|
5
5
|
import { isUnresolvedDesktop3pAlias, resolveDesktop3pAlias } from "./desktop-3p";
|
|
@@ -104,7 +104,19 @@ export function formatFromOutputConfig(outputConfig: unknown): Rec | undefined {
|
|
|
104
104
|
|| !isRec(format.schema)
|
|
105
105
|
|| !isAnthropicOutputSchema(format.schema)
|
|
106
106
|
) return undefined;
|
|
107
|
-
|
|
107
|
+
// `strict` is stated rather than left to the destination's default. A schema with an optional
|
|
108
|
+
// property is legal to Anthropic and a hard 400 under OpenAI strict mode ("'required' ... an
|
|
109
|
+
// array including every key in properties"), which takes down every structured-output turn on
|
|
110
|
+
// a route whose canonical target is unavailable. Satisfying strict by adding the missing keys
|
|
111
|
+
// to `required` would change the caller's contract, so the optionality is preserved and the
|
|
112
|
+
// strict claim is dropped instead.
|
|
113
|
+
return {
|
|
114
|
+
type: "json_schema",
|
|
115
|
+
name: "response",
|
|
116
|
+
schema: format.schema,
|
|
117
|
+
// Strict Structured Outputs also needs an object at the root; a root anyOf/oneOf is refused.
|
|
118
|
+
strict: format.schema.type === "object" && satisfiesOpenAiStrictSchema(format.schema),
|
|
119
|
+
};
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
/**
|
package/src/claude/inbound.ts
CHANGED
|
@@ -513,7 +513,7 @@ function translateAnthropicRequest(
|
|
|
513
513
|
// value, so do not attach the similarly named internal catalog sentinel.
|
|
514
514
|
body.reasoning = { effort: "none" };
|
|
515
515
|
} else if (isRec(thinking) || outputConfigEffort !== undefined) {
|
|
516
|
-
const reasoning: Rec = { summary: "auto" };
|
|
516
|
+
const reasoning: Rec = { summary: isRec(thinking) && thinking.display === "omitted" ? "none" : "auto" };
|
|
517
517
|
if (outputConfigEffort !== undefined) {
|
|
518
518
|
// Adaptive wire: /effort arrives as output_config.effort (devlog 080).
|
|
519
519
|
reasoning.effort = outputConfigEffort;
|
|
@@ -32,6 +32,8 @@ export interface ConnectProxyOptions {
|
|
|
32
32
|
authToken?: string | (() => string | null);
|
|
33
33
|
/** Hostnames (lowercase) whose 443 tunnels are spliced onto `interceptPort`. */
|
|
34
34
|
interceptHosts?: readonly string[];
|
|
35
|
+
/** Optional exact CONNECT authorities (host:port); empty denies all, absent keeps blind relay. */
|
|
36
|
+
allowedTargets?: readonly string[];
|
|
35
37
|
/** Per-connection override, consulted before interceptHosts; null keeps the default. */
|
|
36
38
|
selectTunnel?: (host: string, port: number, request: ConnectRequestInfo) => TunnelDecision | null | Promise<TunnelDecision | null>;
|
|
37
39
|
/** Test seam: dial the real destination for a blind tunnel. */
|
|
@@ -67,7 +69,8 @@ function connectRequestInfo(head: string): ConnectRequestInfo {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
type ResolvedConnectProxyOptions = Required<Pick<ConnectProxyOptions, "interceptPort" | "interceptHosts" | "dialUpstream">>
|
|
70
|
-
& Pick<ConnectProxyOptions, "selectTunnel" | "authToken"
|
|
72
|
+
& Pick<ConnectProxyOptions, "selectTunnel" | "authToken">
|
|
73
|
+
& { allowedTargets?: ReadonlySet<string> };
|
|
71
74
|
|
|
72
75
|
export interface ConnectProxyHandle {
|
|
73
76
|
port: number;
|
|
@@ -186,6 +189,10 @@ function handleConnection(socket: Socket, options: ResolvedConnectProxyOptions):
|
|
|
186
189
|
respond(socket, 403, "Forbidden");
|
|
187
190
|
return;
|
|
188
191
|
}
|
|
192
|
+
if (options.allowedTargets && !options.allowedTargets.has(`${target.host}:${target.port}`)) {
|
|
193
|
+
respond(socket, 403, "Forbidden");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
189
196
|
const dialFor = (selected: TunnelDecision | null): void => {
|
|
190
197
|
if (socket.destroyed) return;
|
|
191
198
|
const choice = selected ?? (target.port === 443 && options.interceptHosts.includes(target.host)
|
|
@@ -258,10 +265,19 @@ function handleConnection(socket: Socket, options: ResolvedConnectProxyOptions):
|
|
|
258
265
|
|
|
259
266
|
/** Bind the CONNECT proxy on 127.0.0.1. Rejects when the port is unavailable. */
|
|
260
267
|
export function startConnectProxy(port: number, options: ConnectProxyOptions): Promise<ConnectProxyHandle> {
|
|
268
|
+
// Snapshot caller-owned policy before listening. Reuse the request parser's
|
|
269
|
+
// host normalization; malformed policy must not silently disable restrictions.
|
|
270
|
+
const allowedTargets = options.allowedTargets === undefined ? undefined : new Set(options.allowedTargets.map(authority => {
|
|
271
|
+
const target = typeof authority === "string" && !/[\s\r\n]/.test(authority)
|
|
272
|
+
? parseConnectRequestLine(`CONNECT ${authority} HTTP/1.1`) : null;
|
|
273
|
+
if (!target || /[*/\\?#@%]/.test(target.host)) throw new Error("Invalid CONNECT allowed target");
|
|
274
|
+
return `${target.host}:${target.port}`;
|
|
275
|
+
}));
|
|
261
276
|
const resolved: ResolvedConnectProxyOptions = {
|
|
262
277
|
interceptPort: options.interceptPort,
|
|
263
278
|
authToken: options.authToken,
|
|
264
279
|
interceptHosts: options.interceptHosts ?? CLAUDE_INTERCEPT_HOSTS,
|
|
280
|
+
allowedTargets,
|
|
265
281
|
selectTunnel: options.selectTunnel,
|
|
266
282
|
dialUpstream: options.dialUpstream ?? ((host: string, targetPort: number) => connect({ host, port: targetPort })),
|
|
267
283
|
};
|
|
@@ -193,12 +193,18 @@ export interface LocalInterceptCa extends PemKeyPair {
|
|
|
193
193
|
|
|
194
194
|
export interface AuthorityOptions {
|
|
195
195
|
commonName: string;
|
|
196
|
+
/** Optional whole-day lifetime for short-lived authorities; defaults to the existing 3650 days. */
|
|
197
|
+
validityDays?: number;
|
|
196
198
|
permittedDnsNames?: readonly string[];
|
|
197
199
|
/** With permittedDnsNames: also exclude every IP address (default true). */
|
|
198
200
|
excludeAllIpAddresses?: boolean;
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
export function createCertificateAuthority(options: AuthorityOptions): LocalInterceptCa {
|
|
204
|
+
const validityDays = options.validityDays ?? CA_VALIDITY_DAYS;
|
|
205
|
+
if (!Number.isInteger(validityDays) || validityDays < 1 || validityDays > CA_VALIDITY_DAYS) {
|
|
206
|
+
throw new Error("CA validityDays must be an integer between 1 and 3650");
|
|
207
|
+
}
|
|
202
208
|
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
203
209
|
const name = distinguishedName(options.commonName);
|
|
204
210
|
const der = issueCertificate({
|
|
@@ -206,7 +212,7 @@ export function createCertificateAuthority(options: AuthorityOptions): LocalInte
|
|
|
206
212
|
issuer: name,
|
|
207
213
|
subjectKey: publicKey,
|
|
208
214
|
signingKey: privateKey,
|
|
209
|
-
validityDays
|
|
215
|
+
validityDays,
|
|
210
216
|
extensions: [
|
|
211
217
|
extension(OID.basicConstraints, true, sequence(boolean(true), tlv(0x02, Uint8Array.of(0)))),
|
|
212
218
|
// keyCertSign | cRLSign
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's message-threads beta, which it only enables against first-party Anthropic, sends
|
|
3
|
+
* a `thread` object on subagent turns. A `continue` carries just the messages after
|
|
4
|
+
* `previous_message_id` and may omit `system` and `tools`, because Anthropic replays them from the
|
|
5
|
+
* stored thread. A translated route has no such store, so translating that delta silently drops the
|
|
6
|
+
* task, the instructions and the earlier turns.
|
|
7
|
+
*
|
|
8
|
+
* Claude Code reads this error code as "threads are unsupported for this model": it resends the
|
|
9
|
+
* same turn with the full conversation and keeps that model stateless for the rest of the session.
|
|
10
|
+
*/
|
|
11
|
+
export const MESSAGE_THREAD_UNSUPPORTED_ERROR_CODE = "thread_unsupported_request";
|
|
12
|
+
|
|
13
|
+
export function carriesMessageThread(body: unknown): boolean {
|
|
14
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return false;
|
|
15
|
+
const thread = (body as Record<string, unknown>).thread;
|
|
16
|
+
return thread !== null && typeof thread === "object" && !Array.isArray(thread);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function messageThreadUnsupportedResponse(): Response {
|
|
20
|
+
return new Response(JSON.stringify({
|
|
21
|
+
type: "error",
|
|
22
|
+
error: {
|
|
23
|
+
type: "invalid_request_error",
|
|
24
|
+
message: "message threads are not supported on translated routes",
|
|
25
|
+
details: { error_code: MESSAGE_THREAD_UNSUPPORTED_ERROR_CODE },
|
|
26
|
+
},
|
|
27
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
28
|
+
}
|
package/src/cli/account-api.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface AccountRow {
|
|
|
27
27
|
masked?: string;
|
|
28
28
|
active: boolean;
|
|
29
29
|
needsReauth?: boolean;
|
|
30
|
+
autoSelectable?: boolean;
|
|
31
|
+
skipReason?: "needs_reauth" | "suspended" | "cooldown" | "quota_exhausted";
|
|
30
32
|
selectionExcludedReason?: "plan_excluded";
|
|
31
33
|
selectionExcludedPlan?: string;
|
|
32
34
|
/** Registered credential that is still excluded from routing until validation completes. */
|
|
@@ -343,6 +345,8 @@ interface OAuthAccountDto {
|
|
|
343
345
|
email?: string;
|
|
344
346
|
active?: boolean;
|
|
345
347
|
needsReauth?: boolean;
|
|
348
|
+
autoSelectable?: boolean;
|
|
349
|
+
skipReason?: unknown;
|
|
346
350
|
/** Always sent by the management route; explicitly `null` when the tier is unknown. */
|
|
347
351
|
plan?: string | null;
|
|
348
352
|
quota?: CodexQuotaDto | null;
|
|
@@ -350,6 +354,11 @@ interface OAuthAccountDto {
|
|
|
350
354
|
quotaFailure?: unknown;
|
|
351
355
|
}
|
|
352
356
|
|
|
357
|
+
function isKiroSkipReason(value: unknown): value is NonNullable<AccountRow["skipReason"]> {
|
|
358
|
+
return value === "needs_reauth" || value === "suspended"
|
|
359
|
+
|| value === "cooldown" || value === "quota_exhausted";
|
|
360
|
+
}
|
|
361
|
+
|
|
353
362
|
async function fetchOAuthRows(
|
|
354
363
|
deps: AccountDeps,
|
|
355
364
|
baseUrl: string,
|
|
@@ -376,6 +385,10 @@ async function fetchOAuthRows(
|
|
|
376
385
|
email: a.email,
|
|
377
386
|
active: a.active ?? a.id === activeId,
|
|
378
387
|
needsReauth: a.needsReauth,
|
|
388
|
+
...(name === "kiro" && typeof a.autoSelectable === "boolean"
|
|
389
|
+
? { autoSelectable: a.autoSelectable } : {}),
|
|
390
|
+
...(name === "kiro" && a.autoSelectable === false && isKiroSkipReason(a.skipReason)
|
|
391
|
+
? { skipReason: a.skipReason } : {}),
|
|
379
392
|
// Forward the server's answer verbatim. An absent key means the proxy predates tier
|
|
380
393
|
// reporting while `null` means it checked and found no tier — collapsing either
|
|
381
394
|
// direction would destroy the one distinction this field exists to make.
|
package/src/cli/account-auth.ts
CHANGED
|
@@ -34,7 +34,7 @@ function writeStdoutFully(text: string): void {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const USAGE = `Usage:
|
|
37
|
-
ocx account login <provider> [--id <account-id>] [--reauth] [--device] [--code -] [--no-wait] [--json]
|
|
37
|
+
ocx account login <provider> [--id <account-id>] [--reauth] [--device] [--method builder-id|google|github] [--code -] [--no-wait] [--json]
|
|
38
38
|
ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
|
|
39
39
|
ocx account cancel <provider> [--flow <flow-id>] [--json] (--flow required for codex)
|
|
40
40
|
ocx account reset-credits <account-id|main> [--consume --yes [--operation-id <uuid>]] [--json]
|
|
@@ -72,6 +72,13 @@ interface LoginStart {
|
|
|
72
72
|
deviceCode?: string;
|
|
73
73
|
/** Whether the host actually opened a browser. Absent from older proxies. */
|
|
74
74
|
browserLaunch?: "started" | "failed" | "skipped";
|
|
75
|
+
method?: "builder-id" | "google" | "github";
|
|
76
|
+
userCode?: string;
|
|
77
|
+
verificationUri?: string;
|
|
78
|
+
verificationUriComplete?: string;
|
|
79
|
+
expiresAt?: number;
|
|
80
|
+
state?: string;
|
|
81
|
+
warning?: string;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
/**
|
|
@@ -94,6 +101,14 @@ const STDIN_SENTINEL = "-";
|
|
|
94
101
|
|
|
95
102
|
/** Providers whose ONLY login is already a device flow; --device is redundant, not wrong. */
|
|
96
103
|
const DEVICE_NATIVE_PROVIDERS = new Set(["kimi", "nous", "github-copilot"]);
|
|
104
|
+
const stripTerminalControls = (value: string): string => value.replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/g, "");
|
|
105
|
+
export function formatKiroDeviceInstructions(start: { verificationUriComplete?: string; verificationUri?: string; userCode?: string; flowId?: string }): string {
|
|
106
|
+
return [
|
|
107
|
+
stripTerminalControls(start.verificationUriComplete ?? start.verificationUri ?? ""),
|
|
108
|
+
start.userCode ? `User code: ${stripTerminalControls(start.userCode)}` : "",
|
|
109
|
+
start.flowId ? `Flow: ${stripTerminalControls(start.flowId)}` : "",
|
|
110
|
+
].filter(Boolean).join("\n");
|
|
111
|
+
}
|
|
97
112
|
|
|
98
113
|
const ARGV_WARNING =
|
|
99
114
|
"warning: the authorization code was passed as a command-line argument, so it is now in your shell history and was visible in the process list while this ran. Pipe it on stdin instead, or pass `-` to read from stdin.";
|
|
@@ -128,9 +143,17 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
128
143
|
const noWait = takeFlag(args, "--no-wait");
|
|
129
144
|
const reauth = takeFlag(args, "--reauth");
|
|
130
145
|
const device = takeFlag(args, "--device");
|
|
146
|
+
const method = takeOption(args, "--method");
|
|
131
147
|
const id = takeOption(args, "--id");
|
|
132
148
|
const suppliedCode = takeOptionWithSyntax(args, "--code");
|
|
133
149
|
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
150
|
+
if (method !== undefined) {
|
|
151
|
+
if (provider !== "kiro" || !["builder-id", "google", "github"].includes(method)) {
|
|
152
|
+
throw new CliUsageError("--method requires kiro and builder-id, google, or github", USAGE);
|
|
153
|
+
}
|
|
154
|
+
if (reauth || id) throw new CliUsageError("native Kiro device login only adds accounts; remove and re-add to reauthenticate", USAGE);
|
|
155
|
+
if (device || suppliedCode) throw new CliUsageError("--method cannot be combined with --device or --code", USAGE);
|
|
156
|
+
}
|
|
134
157
|
// A bare leftover here is plausibly the authorization code itself: this flow takes one
|
|
135
158
|
// through --code, and a user who pastes it as a positional would otherwise see it echoed
|
|
136
159
|
// back in the usage error. `ocx login codex` reaches this parser too, so the paste lands
|
|
@@ -147,6 +170,30 @@ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
147
170
|
// opens the browser flow and polls, and must not block on stdin.
|
|
148
171
|
const code = await resolveCode(suppliedCode, deps, false);
|
|
149
172
|
|
|
173
|
+
if (method) {
|
|
174
|
+
const start = await runtimeRequest<LoginStart>("/api/oauth/login", {
|
|
175
|
+
method: "POST", body: JSON.stringify({ provider: "kiro", method }),
|
|
176
|
+
}, deps);
|
|
177
|
+
if (!wantsJson) {
|
|
178
|
+
const block = formatKiroDeviceInstructions(start);
|
|
179
|
+
if (block) writeStdoutFully(`${block}\n`);
|
|
180
|
+
}
|
|
181
|
+
if (noWait) { printData(start, wantsJson, []); return; }
|
|
182
|
+
if (!start.flowId) throw new CliUsageError("Kiro device login did not return a flow id");
|
|
183
|
+
for (let attempt = 0; attempt < 450; attempt++) {
|
|
184
|
+
await Bun.sleep(2_000);
|
|
185
|
+
const state = await runtimeRequest<LoginStart>(`/api/oauth/status?provider=kiro&flowId=${encodeURIComponent(start.flowId)}`, {}, deps);
|
|
186
|
+
if (state.state === "done") {
|
|
187
|
+
printData(state, wantsJson, ["Logged in to kiro.", ...(state.warning ? [`Warning: ${state.warning}`] : [])]);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (state.state === "failed" || state.state === "expired" || state.state === "cancelled") {
|
|
191
|
+
throw new CliUsageError(`Kiro device login ${state.state}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw new CliUsageError("Kiro device login timed out");
|
|
195
|
+
}
|
|
196
|
+
|
|
150
197
|
if (CODEX_NAMES.has(provider)) {
|
|
151
198
|
const start = await runtimeRequest<LoginStart>("/api/codex-auth/login", {
|
|
152
199
|
method: "POST",
|
|
@@ -290,7 +337,7 @@ async function cancel(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
290
337
|
}
|
|
291
338
|
const result = await runtimeRequest(codex ? "/api/codex-auth/login/cancel" : "/api/oauth/login/cancel", {
|
|
292
339
|
method: "POST",
|
|
293
|
-
body: JSON.stringify(codex ? { flowId } : { provider }),
|
|
340
|
+
body: JSON.stringify(codex ? { flowId } : { provider, ...(provider === "kiro" && flowId ? { flowId } : {}) }),
|
|
294
341
|
}, deps);
|
|
295
342
|
printData(result, wantsJson, [`Cancelled ${provider} login.`]);
|
|
296
343
|
}
|
|
@@ -45,7 +45,7 @@ const EXTENDED_USAGE = `Usage:
|
|
|
45
45
|
ocx account pause <provider> <id|alias|main> [--json]
|
|
46
46
|
ocx account resume <provider> <id|alias|main> [--json]
|
|
47
47
|
ocx account pause-exhausted <provider> [--json]
|
|
48
|
-
ocx account strategy <provider> [<quota|round-robin|fill-first|reset-first>] [--json]
|
|
48
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
49
49
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
50
50
|
ocx account remove <provider> <id|alias|main> --yes [--json]
|
|
51
51
|
ocx account clear-cooldown <provider> <id|alias|main> [--json]
|
|
@@ -961,7 +961,9 @@ async function poolSetting(
|
|
|
961
961
|
console.log(JSON.stringify(payload, null, 2));
|
|
962
962
|
} else {
|
|
963
963
|
if (field === "strategy" && autoSwitchThreshold !== undefined) {
|
|
964
|
-
const thresholdSummary = strategy === "
|
|
964
|
+
const thresholdSummary = strategy === "least-loaded"
|
|
965
|
+
? "fewest active requests"
|
|
966
|
+
: strategy === "round-robin"
|
|
965
967
|
? "threshold not used"
|
|
966
968
|
: autoSwitchThreshold > 0
|
|
967
969
|
? (strategy === "fill-first"
|
package/src/cli/account.ts
CHANGED
|
@@ -52,7 +52,7 @@ const ACCOUNT_USAGE = `Usage:
|
|
|
52
52
|
ocx account pause <provider> <account-id|alias|main> [--json]
|
|
53
53
|
ocx account resume <provider> <account-id|alias|main> [--json]
|
|
54
54
|
ocx account pause-exhausted <provider> [--json]
|
|
55
|
-
ocx account strategy <provider> [<quota|round-robin|fill-first|reset-first>] [--json]
|
|
55
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first|least-loaded|reset-first>] [--json]
|
|
56
56
|
ocx account sticky <provider> [<1-100>] [--json]
|
|
57
57
|
ocx account remove <provider> <account-or-key-id|alias|main> --yes [--json]
|
|
58
58
|
ocx account clear-cooldown <provider> <account-id|alias|main> [--json]
|
|
@@ -104,7 +104,9 @@ function statusText(row: AccountRow): string {
|
|
|
104
104
|
// held out -- so printing only one of the two would hide exactly the confusing case (#2703).
|
|
105
105
|
if (row.paused) parts.push("paused");
|
|
106
106
|
if (row.active) parts.push(row.type === "codex" ? "selected" : "active");
|
|
107
|
-
if (row.needsReauth) parts.push("needs-reauth");
|
|
107
|
+
if (row.needsReauth && !(row.provider === "kiro" && row.skipReason === "needs_reauth")) parts.push("needs-reauth");
|
|
108
|
+
if (row.provider === "kiro" && row.autoSelectable === false)
|
|
109
|
+
parts.push(row.skipReason ? `not-auto-selected(${row.skipReason})` : "not-auto-selected");
|
|
108
110
|
if (row.validationPending) parts.push("validation-pending");
|
|
109
111
|
if (row.selectionExcludedReason === "plan_excluded") {
|
|
110
112
|
parts.push(`not-auto-selected(plan=${row.selectionExcludedPlan ?? row.plan ?? "unknown"})`);
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -386,6 +386,23 @@ export const CAPABILITIES: readonly Capability[] = [
|
|
|
386
386
|
"Values accepted by `set` are parsed as JSON when valid, so booleans, numbers, arrays, objects, and null can be passed directly.",
|
|
387
387
|
],
|
|
388
388
|
},
|
|
389
|
+
{
|
|
390
|
+
command: ["account", "login"],
|
|
391
|
+
summary: "Log in to an OAuth provider; Kiro can add a native device account.",
|
|
392
|
+
routes: [
|
|
393
|
+
{ method: "POST", path: "/api/oauth/login" },
|
|
394
|
+
{ method: "GET", path: "/api/oauth/status" },
|
|
395
|
+
],
|
|
396
|
+
flags: [
|
|
397
|
+
{ name: "--method", value: "string", summary: "For Kiro: builder-id, google, or github device login (add only)." },
|
|
398
|
+
{ name: "--reauth", value: "boolean", summary: "Reauthenticate a selected existing account." },
|
|
399
|
+
{ name: "--id", value: "string", summary: "Account id for reauthentication." },
|
|
400
|
+
{ name: "--no-wait", value: "boolean", summary: "Return after the login flow starts." },
|
|
401
|
+
{ name: "--json", value: "boolean", summary: "Emit flow state as JSON." },
|
|
402
|
+
],
|
|
403
|
+
mutates: true,
|
|
404
|
+
json: "payload",
|
|
405
|
+
},
|
|
389
406
|
{
|
|
390
407
|
command: ["account", "history"],
|
|
391
408
|
summary: "Cached quota observations for one stored Codex pool account.",
|
package/src/cli/claude.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { aliasForNative, aliasForRoute, legacyAliasForNative, legacyAliasForRout
|
|
|
34
34
|
import { desktop3pAlias } from "../claude/desktop-3p";
|
|
35
35
|
import { inspectDesktopFirstParty } from "../claude/desktop-first-party";
|
|
36
36
|
import { isClaudeInterceptProxyUrl, type ClaudeInterceptSettingsState } from "../claude/intercept/settings";
|
|
37
|
+
import { withoutSiblingMarker } from "../codex/sibling-start";
|
|
37
38
|
|
|
38
39
|
export interface ClaudeLaunchEnv {
|
|
39
40
|
[key: string]: string | undefined;
|
|
@@ -506,7 +507,8 @@ export async function ensureProxyForClaude(deps: ClaudeProxyEnsureDeps = {}): Pr
|
|
|
506
507
|
detached: true,
|
|
507
508
|
stdio: "ignore",
|
|
508
509
|
windowsHide: true,
|
|
509
|
-
|
|
510
|
+
// An ordinary owner: a stray sibling marker would otherwise mark it before any probe.
|
|
511
|
+
env: withProcessRuntimeProvenance(withoutSiblingMarker({ ...process.env, OCX_SERVICE: "1" })),
|
|
510
512
|
});
|
|
511
513
|
child.unref();
|
|
512
514
|
const deadline = Date.now() + 8_000;
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
localClientSkipMessage,
|
|
22
22
|
setIntegrationEnabled,
|
|
23
23
|
shouldSyncCodexOnStart,
|
|
24
|
+
type LocalClientSkipReason,
|
|
24
25
|
} from "../codex/desired-state";
|
|
26
|
+
import { siblingSkipMessage } from "../codex/sibling-start";
|
|
25
27
|
import { syncModelsToCodex } from "../codex/sync";
|
|
26
28
|
import { collectOrcaCodexHomeDiagnostic } from "../codex/home";
|
|
27
29
|
import { restoreNativeCodexAsync, type CodexNativeRestoreResult } from "../codex/inject";
|
|
@@ -692,13 +694,15 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
692
694
|
switch (deps.args[1]) {
|
|
693
695
|
case "install": {
|
|
694
696
|
const r = installCodexShim();
|
|
697
|
+
const { healthy, summary } = diagnoseCodexShim();
|
|
695
698
|
const { collectCodexShimReadinessWarnings } = await import("./codex-shim-readiness");
|
|
696
|
-
const warnings =
|
|
699
|
+
const warnings = healthy
|
|
697
700
|
? collectCodexShimReadinessWarnings()
|
|
698
701
|
: [];
|
|
699
702
|
console.log(`${r.installed && warnings.length === 0 ? "✅ " : "⚠️ "}${r.message}`);
|
|
700
703
|
for (const warning of warnings) console.warn(` ${warning}`);
|
|
701
|
-
|
|
704
|
+
if (!healthy) console.error(`Codex shim installation is unhealthy: ${summary}`);
|
|
705
|
+
return healthy ? 0 : 1;
|
|
702
706
|
}
|
|
703
707
|
case "status":
|
|
704
708
|
console.log(codexShimStatus());
|
|
@@ -999,7 +1003,7 @@ export const DISPATCH_ALIASES: ReadonlyMap<string, string> = aliasTargets;
|
|
|
999
1003
|
/** Resolve the runner key for a command, following registry aliases to the
|
|
1000
1004
|
* canonical runner. Returns undefined when the command is unknown. */
|
|
1001
1005
|
/** What `handleStart` does about a live proxy it found before binding. */
|
|
1002
|
-
export type StartOwnerDecision = "refuse" | "service-stay-out" | "sibling";
|
|
1006
|
+
export type StartOwnerDecision = "refuse" | "service-stay-out" | "sibling" | "await-parent";
|
|
1003
1007
|
|
|
1004
1008
|
/**
|
|
1005
1009
|
* Pure decision for `handleStart` when the pre-bind probe found a live proxy.
|
|
@@ -1011,21 +1015,74 @@ export type StartOwnerDecision = "refuse" | "service-stay-out" | "sibling";
|
|
|
1011
1015
|
* decision allows isolated homes on one machine to remain independent.
|
|
1012
1016
|
* The service wrapper always passes the configured port and keeps its exact
|
|
1013
1017
|
* stay-out-of-the-way semantics: it never takes the sibling path.
|
|
1018
|
+
*
|
|
1019
|
+
* `"await-parent"` comes first and only for an exact pid match: the live proxy is the draining
|
|
1020
|
+
* process that spawned this start as its restart replacement (`OCX_RESTART_PARENT_PID`, already
|
|
1021
|
+
* checked against the real parent pid). Refusing it would leave no proxy once that parent exits,
|
|
1022
|
+
* so the caller waits for it instead (`src/cli/restart-handoff.ts`). Every other owner, and a
|
|
1023
|
+
* live proxy whose pid could not be verified, keeps the table above.
|
|
1014
1024
|
*/
|
|
1015
1025
|
export function decideStartWithLiveOwner(input: {
|
|
1016
1026
|
livePort: number;
|
|
1017
1027
|
requestedPort: number | undefined;
|
|
1018
1028
|
ocxService: string | undefined;
|
|
1029
|
+
livePid?: number | null;
|
|
1030
|
+
restartParentPid?: number | null;
|
|
1019
1031
|
}): StartOwnerDecision {
|
|
1032
|
+
if (input.restartParentPid != null && input.livePid === input.restartParentPid) return "await-parent";
|
|
1020
1033
|
const sibling = input.requestedPort !== undefined
|
|
1021
1034
|
&& input.requestedPort !== input.livePort
|
|
1022
|
-
// Only the exact "1" sentinel is service context — the same check
|
|
1035
|
+
// Only the exact "1" sentinel is service context — the same check the exit teardown
|
|
1023
1036
|
// uses — so an env value like "0" or "false" cannot reach the stay-out path.
|
|
1024
1037
|
&& input.ocxService !== "1";
|
|
1025
1038
|
if (sibling) return "sibling";
|
|
1026
1039
|
return input.ocxService === "1" ? "service-stay-out" : "refuse";
|
|
1027
1040
|
}
|
|
1028
1041
|
|
|
1042
|
+
/** Which shared client state `handleStart`'s exit cleanup tears down. */
|
|
1043
|
+
export interface StartExitTeardown {
|
|
1044
|
+
revertSystemEnv: boolean;
|
|
1045
|
+
restoreNativeCodex: boolean;
|
|
1046
|
+
stripGrokConfig: boolean;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Pure exit-teardown decision for `handleStart`'s `syncCleanup`.
|
|
1051
|
+
*
|
|
1052
|
+
* A sibling instance tears down nothing: the Codex routing, the Grok fence and the system env
|
|
1053
|
+
* belong to the live proxy it runs beside, and restoring them would take Codex off a proxy that
|
|
1054
|
+
* is still serving it (`src/codex/sibling-start.ts`). A dashboard drain-and-restart (#563) keeps
|
|
1055
|
+
* everything for the replacement process. Under a service manager — only the exact `"1"`
|
|
1056
|
+
* sentinel — a crash/respawn keeps routing and the fence, and only the environment comes down.
|
|
1057
|
+
* The caller still applies its own external-provider and service-ownership checks.
|
|
1058
|
+
*/
|
|
1059
|
+
export function decideStartExitTeardown(input: {
|
|
1060
|
+
sibling: boolean;
|
|
1061
|
+
recycling: boolean;
|
|
1062
|
+
ocxService: string | undefined;
|
|
1063
|
+
}): StartExitTeardown {
|
|
1064
|
+
if (input.sibling || input.recycling) {
|
|
1065
|
+
return { revertSystemEnv: false, restoreNativeCodex: false, stripGrokConfig: false };
|
|
1066
|
+
}
|
|
1067
|
+
const preserveRouting = input.ocxService === "1";
|
|
1068
|
+
return { revertSystemEnv: true, restoreNativeCodex: !preserveRouting, stripGrokConfig: !preserveRouting };
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* The one startup line for "nothing was written to Codex".
|
|
1073
|
+
*
|
|
1074
|
+
* Three very different facts reach it: the user's own OFF switch, a hub declining to rewrite its
|
|
1075
|
+
* own local clients, and a sibling instance leaving the live proxy's routing alone. Printing the
|
|
1076
|
+
* toggle's wording for the gate is what made operators hunt for a switch they never set (#4236).
|
|
1077
|
+
* `port` is the sibling's own bound port, named in its line.
|
|
1078
|
+
*/
|
|
1079
|
+
export function startupLeftCodexNativeLine(reason: LocalClientSkipReason, port?: number): string {
|
|
1080
|
+
if (reason === "sibling") return ` ${siblingSkipMessage(port)}`;
|
|
1081
|
+
return reason === "hub-gated"
|
|
1082
|
+
? ` ${HUB_GATED_SKIP_MESSAGE} Startup left Codex native.`
|
|
1083
|
+
: " Codex integration OFF; startup left Codex native.";
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1029
1086
|
/** What `chooseListenPort` does when the preferred port stayed busy through prefer-retry. */
|
|
1030
1087
|
export type BusyPreferredPortDecision =
|
|
1031
1088
|
| "hop"
|
|
@@ -1057,7 +1114,7 @@ export type BusyPreferredPortDecision =
|
|
|
1057
1114
|
*
|
|
1058
1115
|
* Service-wrapper context keeps the semantics `decideStartWithLiveOwner` gives it: a
|
|
1059
1116
|
* healthy proxy on the port means the port is served, and the wrapper's
|
|
1060
|
-
*
|
|
1117
|
+
* retry loop must receive the intentional stay-out signal rather than respawn every 5 seconds.
|
|
1061
1118
|
*/
|
|
1062
1119
|
export function decideBusyPreferredPort(input: {
|
|
1063
1120
|
preferredPort: number;
|