@bitkyc08/opencodex 2.68.0 → 2.69.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 +25 -17
- package/gui/dist/assets/App-DDbgOTh6.js +51 -0
- package/gui/dist/assets/{Tray-Bn8WhKBH.js → Tray-uqKRqfvw.js} +1 -1
- package/gui/dist/assets/index-BF9QnKO_.css +1 -0
- package/gui/dist/assets/index-CuaBJkG4.js +86 -0
- package/gui/dist/assets/{quota-summary-DL-bNuNL.js → quota-summary-COppQSVq.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/factory-droid.svg +8 -0
- package/package.json +1 -1
- package/src/adapters/anthropic-output-schema.ts +109 -11
- package/src/adapters/coding-agent/protocol.ts +106 -11
- package/src/adapters/coding-agent/turn.ts +23 -18
- package/src/adapters/google-errors.ts +16 -1
- package/src/adapters/google-http.ts +14 -0
- package/src/adapters/openai-chat/passthrough.ts +2 -0
- package/src/adapters/openai-chat/summary-budget.ts +72 -0
- package/src/adapters/openai-chat/tool-call-id-remint.ts +11 -2
- package/src/adapters/openai-chat.ts +6 -9
- package/src/bridge/response-json.ts +3 -1
- package/src/bridge/sse.ts +3 -1
- package/src/claude/desktop-picker.ts +6 -2
- package/src/claude/desktop-system-proxy.ts +155 -0
- package/src/claude/inbound-model-options.ts +10 -2
- package/src/claude/inbound.ts +1 -1
- package/src/claude/intercept/picker-ca-cleanup.ts +31 -0
- package/src/claude/intercept/picker-ca.ts +283 -83
- package/src/claude/intercept/picker-models.ts +4 -1
- package/src/claude/intercept/picker-trust.ts +15 -2
- package/src/claude/intercept/runtime.ts +99 -32
- package/src/cli/account-api.ts +5 -2
- package/src/cli/account-extended.ts +47 -11
- package/src/cli/account-target.ts +12 -7
- package/src/cli/account.ts +47 -3
- package/src/cli/capabilities.ts +33 -7
- package/src/cli/claude-agent-startup-sync.ts +2 -0
- package/src/cli/claude-desktop.ts +37 -13
- package/src/cli/cross-home-owner.ts +138 -0
- package/src/cli/dispatch.ts +1 -1
- package/src/cli/doctor.ts +18 -8
- package/src/cli/ensure-desired-integrations.ts +5 -0
- package/src/cli/export-command.ts +6 -6
- package/src/cli/help.ts +1 -1
- package/src/cli/index.ts +15 -18
- package/src/cli/integrations.ts +8 -1
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +106 -4
- package/src/cli/models.ts +6 -1
- package/src/cli/opencode.ts +2 -83
- package/src/cli/registry.ts +3 -2
- package/src/cli/status.ts +86 -7
- package/src/client/connect.ts +24 -10
- package/src/client/link-ingress.ts +3 -1
- package/src/client/link-join.ts +76 -9
- package/src/client/link-relay-transport.ts +184 -0
- package/src/client/link-relay.ts +16 -4
- package/src/client/machine-listener.ts +2 -0
- package/src/clients/config-export/constants.ts +8 -0
- package/src/clients/config-export/contracts.ts +13 -1
- package/src/clients/config-export/droid.ts +71 -0
- package/src/clients/config-export/kilo.ts +116 -0
- package/src/clients/config-export.ts +33 -2
- package/src/codex/auth-api/routes.ts +1 -1
- package/src/codex/catalog/access-programs.ts +14 -2
- package/src/codex/catalog/provider-models.ts +1 -1
- package/src/codex/catalog-auto-refresh.ts +122 -0
- package/src/codex/config-drift-heal.ts +80 -0
- package/src/codex/desktop-switches.ts +7 -5
- package/src/codex/inject/bounded-config-reader.ts +69 -0
- package/src/codex/inject/config-toml.ts +31 -3
- package/src/codex/inject/routing-target.ts +6 -1
- package/src/codex/inject.ts +4 -0
- package/src/codex/model-entitlements.ts +32 -5
- package/src/codex/native-profile-startup.ts +17 -5
- package/src/codex/project-config-warnings.ts +72 -21
- package/src/codex/quota-auto-refresh-state.ts +8 -2
- package/src/codex/quota-auto-refresh.ts +80 -9
- package/src/codex/sync.ts +10 -0
- package/src/combos/failover.ts +4 -1
- package/src/combos/jev.ts +15 -4
- package/src/combos/types.ts +16 -0
- package/src/config/diagnostics.ts +19 -1
- package/src/config/load-degrade.ts +29 -0
- package/src/config/macos-system-proxy.ts +114 -0
- package/src/config/proxy-env.ts +67 -20
- package/src/config/schema/config-schema.ts +15 -0
- package/src/config/schema/leaf-validators.ts +23 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +220 -144
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +12 -3
- package/src/integrations/droid-settings.ts +65 -0
- package/src/integrations/kilo-candidates.ts +30 -0
- package/src/integrations/mutation-plan.ts +62 -7
- package/src/integrations/native/ownership-preflight.ts +46 -12
- package/src/integrations/registry.ts +136 -7
- package/src/integrations/state.ts +52 -4
- package/src/integrations/target.ts +3 -0
- package/src/integrations/writer.ts +55 -14
- package/src/lib/account-selection-events.ts +14 -0
- package/src/lib/jsonc.ts +110 -0
- package/src/lib/standalone.ts +9 -1
- package/src/lib/windows-system-proxy.ts +101 -1
- package/src/link/relay-auth.ts +29 -0
- package/src/link/ssh-argv.ts +6 -3
- package/src/link/ssh-runner.ts +5 -5
- package/src/oauth/generic-account-failover.ts +52 -8
- package/src/oauth/index.ts +37 -14
- package/src/oauth/store.ts +96 -11
- package/src/oauth/token-guardian.ts +7 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +5 -3
- package/src/providers/fastwire.ts +3 -0
- package/src/providers/key-failover.ts +13 -5
- package/src/providers/kiro-model-catalog.ts +4 -3
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-tiers-destination.ts +9 -0
- package/src/providers/quota/account-cache.ts +17 -3
- package/src/providers/quota/vendor-probes-oauth.ts +2 -0
- package/src/providers/quota.ts +2 -2
- package/src/responses/code-mode-helper-compat.ts +14 -1
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/server/auth-cors.ts +1 -0
- package/src/server/images.ts +70 -20
- package/src/server/index/link-listener.ts +20 -3
- package/src/server/index/link-relay-sessions.ts +138 -0
- package/src/server/index/optional-listeners.ts +11 -1
- package/src/server/index/serve-options.ts +12 -0
- package/src/server/inference/client-encoder-delivery.ts +1 -0
- package/src/server/local-management-read-client.ts +21 -2
- package/src/server/management/agent-settings-routes.ts +19 -5
- package/src/server/management/combo-routes.ts +5 -3
- package/src/server/management/config-routes.ts +32 -6
- package/src/server/management/model-routes.ts +232 -7
- package/src/server/management/model-rows.ts +199 -1
- package/src/server/management/oauth-account-routes.ts +63 -3
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/port-reclaim.ts +170 -32
- package/src/server/responses/adapter-delivery.ts +6 -4
- package/src/server/responses/adapter-dispatch.ts +72 -2
- package/src/server/responses/core-combo.ts +6 -1
- package/src/server/responses/core-normalize.ts +19 -4
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/memory-models.ts +169 -0
- package/src/server/responses/passthrough-delivery.ts +4 -0
- package/src/server/responses/passthrough-dispatch.ts +10 -0
- package/src/server/responses/request-prepare.ts +86 -4
- package/src/server/responses/request-transport.ts +4 -0
- package/src/server/responses/run-turn-execution.ts +5 -2
- package/src/server/responses/shadow-target-availability.ts +17 -4
- package/src/server/responses/sidecar-execution.ts +18 -2
- package/src/server/responses/skills-snapshot.ts +239 -0
- package/src/server/responses-undeclared-tool-guard.ts +43 -6
- package/src/server/search.ts +1 -1
- package/src/service/guards.ts +4 -5
- package/src/service/state.ts +39 -2
- package/src/service.ts +2 -2
- package/src/types/config.ts +46 -4
- package/src/types/provider.ts +9 -0
- package/src/types/request.ts +6 -0
- package/src/types/tools.ts +38 -8
- package/src/types.ts +3 -0
- package/src/update/async-check.ts +5 -1
- package/src/update/index.ts +30 -12
- package/src/update/job.ts +3 -1
- package/src/update/pnpm-read-policy.d.mts +4 -0
- package/src/update/pnpm-read-policy.mjs +44 -0
- package/src/update/worker-launch.ts +152 -11
- package/src/usage/cost.ts +6 -3
- package/src/usage/log.ts +4 -0
- package/src/web-search/backends.ts +2 -2
- package/src/web-search/devin-executor.ts +1 -1
- package/src/web-search/sidecar-providers.ts +5 -5
- package/gui/dist/assets/App-mpsibsFj.js +0 -51
- package/gui/dist/assets/index-BDUBS8PW.css +0 -1
- package/gui/dist/assets/index-kUE8tuAK.js +0 -86
|
@@ -226,6 +226,35 @@ export function collectDeclaredBareWireToolNames(body: unknown): Set<string> {
|
|
|
226
226
|
return names;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/** Custom declarations with no foreign namespace, for code-mode exec recovery. */
|
|
230
|
+
export function collectDeclaredBareCustomWireToolNames(body: unknown): Set<string> {
|
|
231
|
+
const names = new Set<string>();
|
|
232
|
+
if (!isPlainObject(body)) return names;
|
|
233
|
+
const specGroups: unknown[] = [body.tools];
|
|
234
|
+
if (Array.isArray(body.input)) {
|
|
235
|
+
for (const item of body.input) {
|
|
236
|
+
if (isPlainObject(item) && (item.type === "additional_tools" || item.type === "tool_search_output")) {
|
|
237
|
+
specGroups.push(item.tools);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const add = (tool: unknown): void => {
|
|
242
|
+
if (isPlainObject(tool) && tool.type === "custom" && typeof tool.name === "string") names.add(tool.name);
|
|
243
|
+
};
|
|
244
|
+
for (const specs of specGroups) {
|
|
245
|
+
if (!Array.isArray(specs)) continue;
|
|
246
|
+
for (const spec of specs) {
|
|
247
|
+
if (!isPlainObject(spec)) continue;
|
|
248
|
+
if (spec.type === "namespace") {
|
|
249
|
+
if (spec.name === BUILTIN_FUNCTIONS_NAMESPACE && Array.isArray(spec.tools)) {
|
|
250
|
+
for (const inner of spec.tools) add(inner);
|
|
251
|
+
}
|
|
252
|
+
} else add(spec);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return names;
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
function addNamelessClientCallTypes(callTypes: Set<string>, specs: unknown): void {
|
|
230
259
|
if (!Array.isArray(specs)) return;
|
|
231
260
|
for (const spec of specs) {
|
|
@@ -349,6 +378,7 @@ export function hasExplicitWireToolCatalog(body: unknown): boolean {
|
|
|
349
378
|
* @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
|
|
350
379
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
351
380
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
381
|
+
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
352
382
|
* @returns The undeclared tool call name if unauthorized, or undefined if permitted.
|
|
353
383
|
*/
|
|
354
384
|
function undeclaredNameInItem(
|
|
@@ -357,6 +387,7 @@ function undeclaredNameInItem(
|
|
|
357
387
|
declaredNamelessClientCallTypes: ReadonlySet<string>,
|
|
358
388
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
359
389
|
declaredBare?: ReadonlySet<string>,
|
|
390
|
+
declaredCustom?: ReadonlySet<string>,
|
|
360
391
|
): string | undefined {
|
|
361
392
|
if (!isPlainObject(item)) return undefined;
|
|
362
393
|
if (typeof item.type !== "string") return undefined;
|
|
@@ -396,7 +427,7 @@ function undeclaredNameInItem(
|
|
|
396
427
|
) return undefined;
|
|
397
428
|
return name;
|
|
398
429
|
}
|
|
399
|
-
const effectiveName = normalizeDeclaredToolName(name, declared, declaredBare);
|
|
430
|
+
const effectiveName = normalizeDeclaredToolName(name, declared, declaredBare, declaredCustom);
|
|
400
431
|
if (declared.has(effectiveName)) return undefined;
|
|
401
432
|
return name;
|
|
402
433
|
}
|
|
@@ -409,6 +440,7 @@ function undeclaredNameInItem(
|
|
|
409
440
|
* @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
|
|
410
441
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
411
442
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
443
|
+
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
412
444
|
* @returns The name of the first undeclared tool call, or undefined.
|
|
413
445
|
*/
|
|
414
446
|
export function undeclaredToolCallName(
|
|
@@ -417,18 +449,19 @@ export function undeclaredToolCallName(
|
|
|
417
449
|
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
418
450
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
419
451
|
declaredBare?: ReadonlySet<string>,
|
|
452
|
+
declaredCustom?: ReadonlySet<string>,
|
|
420
453
|
): string | undefined {
|
|
421
454
|
if (!isPlainObject(payload)) return undefined;
|
|
422
455
|
if (payload.type === "response.output_item.added" || payload.type === "response.output_item.done") {
|
|
423
|
-
return undeclaredNameInItem(payload.item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
|
|
456
|
+
return undeclaredNameInItem(payload.item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
424
457
|
}
|
|
425
458
|
if (payload.type === "response.function_call_arguments.done" && typeof payload.name === "string") {
|
|
426
459
|
const fakeItem = { type: "function_call", name: payload.name, namespace: payload.namespace };
|
|
427
|
-
return undeclaredNameInItem(fakeItem, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
|
|
460
|
+
return undeclaredNameInItem(fakeItem, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
428
461
|
}
|
|
429
462
|
// Sparse gateways skip incremental items and only ever ship the terminal snapshot.
|
|
430
463
|
if (payload.type === "response.completed" || payload.type === "response.incomplete") {
|
|
431
|
-
return undeclaredToolCallNameInResponse(payload.response, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
|
|
464
|
+
return undeclaredToolCallNameInResponse(payload.response, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
432
465
|
}
|
|
433
466
|
return undefined;
|
|
434
467
|
}
|
|
@@ -441,6 +474,7 @@ export function undeclaredToolCallName(
|
|
|
441
474
|
* @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
|
|
442
475
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
443
476
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
477
|
+
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
444
478
|
* @returns The name of the first undeclared tool call, or undefined.
|
|
445
479
|
*/
|
|
446
480
|
export function undeclaredToolCallNameInResponse(
|
|
@@ -449,10 +483,11 @@ export function undeclaredToolCallNameInResponse(
|
|
|
449
483
|
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
450
484
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
451
485
|
declaredBare?: ReadonlySet<string>,
|
|
486
|
+
declaredCustom?: ReadonlySet<string>,
|
|
452
487
|
): string | undefined {
|
|
453
488
|
if (!isPlainObject(response) || !Array.isArray(response.output)) return undefined;
|
|
454
489
|
for (const item of response.output) {
|
|
455
|
-
const name = undeclaredNameInItem(item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
|
|
490
|
+
const name = undeclaredNameInItem(item, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
456
491
|
if (name !== undefined) return name;
|
|
457
492
|
}
|
|
458
493
|
return undefined;
|
|
@@ -667,6 +702,7 @@ function failedBlocks(name: string, newline: string): readonly string[] {
|
|
|
667
702
|
* @param declaredNamelessClientCallTypes - Nameless client call types declared by the request.
|
|
668
703
|
* @param providerExecutedCallTypes - Call types executed by the provider.
|
|
669
704
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
705
|
+
* @param declaredCustom - Current bare custom declarations eligible for code-mode recovery.
|
|
670
706
|
* @returns An SSE block rewrite function.
|
|
671
707
|
*/
|
|
672
708
|
export function createUndeclaredToolCallGuardBlockRewrite(
|
|
@@ -674,6 +710,7 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
674
710
|
declaredNamelessClientCallTypes: ReadonlySet<string> = EMPTY_DECLARED_NAMELESS_CLIENT_CALL_TYPES,
|
|
675
711
|
providerExecutedCallTypes: ProviderExecutedCallTypes = EMPTY_PROVIDER_EXECUTED_CALL_TYPES,
|
|
676
712
|
declaredBare?: ReadonlySet<string>,
|
|
713
|
+
declaredCustom?: ReadonlySet<string>,
|
|
677
714
|
): SseBlockRewrite {
|
|
678
715
|
let tripped = false;
|
|
679
716
|
return (block: string) => {
|
|
@@ -686,7 +723,7 @@ export function createUndeclaredToolCallGuardBlockRewrite(
|
|
|
686
723
|
} catch {
|
|
687
724
|
return [block];
|
|
688
725
|
}
|
|
689
|
-
const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare);
|
|
726
|
+
const name = undeclaredToolCallName(parsed, declared, declaredNamelessClientCallTypes, providerExecutedCallTypes, declaredBare, declaredCustom);
|
|
690
727
|
if (name !== undefined) {
|
|
691
728
|
tripped = true;
|
|
692
729
|
return failedBlocks(name, block.includes("\r\n") ? "\r\n" : "\n");
|
package/src/server/search.ts
CHANGED
package/src/service/guards.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
|
10
10
|
import { isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
11
11
|
import { diagnoseService } from "./diagnostics";
|
|
12
12
|
import type { ServiceDiagnostic } from "./diagnostics";
|
|
13
|
-
import { currentCodexHome, currentOpenCodexHome,
|
|
13
|
+
import { currentCodexHome, currentOpenCodexHome, resolveServiceState, serviceCodexHomeMatchesInstall, servicePathMatchesInstall } from "./state";
|
|
14
14
|
import { resolveCodexSqliteHome } from "../codex/paths";
|
|
15
15
|
import type { CodexHomeDeps } from "../codex/home";
|
|
16
16
|
import { isLoopbackHostname } from "../codex/loopback-target";
|
|
@@ -58,9 +58,8 @@ export function assertServiceEnvironmentMatchesInstall(deps: CodexHomeDeps = {})
|
|
|
58
58
|
`Rerun with CODEX_HOME=${state.codexHome} so native Codex restore updates the recorded home.`,
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (expectedOpenCodexHome !== actualOpenCodexHome) {
|
|
61
|
+
const actualOpenCodexHome = currentOpenCodexHome();
|
|
62
|
+
if (!servicePathMatchesInstall(state.opencodexHome, actualOpenCodexHome, deps)) {
|
|
64
63
|
throw new ServiceOwnershipError(
|
|
65
64
|
`Service was installed with OPENCODEX_HOME=${state.opencodexHome}, but current OPENCODEX_HOME=${currentOpenCodexHome()}. ` +
|
|
66
65
|
"Run the service command from the same OpenCodex home so service state and secrets match.",
|
|
@@ -68,7 +67,7 @@ export function assertServiceEnvironmentMatchesInstall(deps: CodexHomeDeps = {})
|
|
|
68
67
|
}
|
|
69
68
|
if (state.codexSqliteHome !== undefined) {
|
|
70
69
|
const actualCodexSqliteHome = resolveCodexSqliteHome({ codexHome: actualCodexHome });
|
|
71
|
-
if (
|
|
70
|
+
if (!servicePathMatchesInstall(state.codexSqliteHome, actualCodexSqliteHome, deps)) {
|
|
72
71
|
throw new ServiceOwnershipError(
|
|
73
72
|
`Service was installed with Codex SQLite home=${state.codexSqliteHome}, but the current Codex SQLite home=${actualCodexSqliteHome}. ` +
|
|
74
73
|
"Run the service command with the same sqlite_home configuration and CODEX_SQLITE_HOME so native Codex history restore updates the correct database.",
|
package/src/service/state.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants as fsConstants, existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { accessSync, constants as fsConstants, existsSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { delimiter, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
4
4
|
import { expandUserPath, getConfigDir } from "../config";
|
|
@@ -857,8 +857,45 @@ export function serviceHomeMatches(a: string, b: string): boolean {
|
|
|
857
857
|
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
export type ServicePathComparison = "same" | "different" | "unknown";
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Tri-state physical-home compare. A realpath failure (EACCES, EPERM, a
|
|
864
|
+
* vanished directory, transient I/O) is "unknown", not "different": callers
|
|
865
|
+
* deciding whether a home is foreign must not turn an unreadable resolution
|
|
866
|
+
* into a definitive mismatch. Lifecycle guards may still fail closed on
|
|
867
|
+
* "unknown".
|
|
868
|
+
*/
|
|
869
|
+
export function compareServicePathToInstall(recorded: string, current: string, deps: CodexHomeDeps = {}): ServicePathComparison {
|
|
870
|
+
if (serviceHomeMatches(recorded, current)) return "same";
|
|
871
|
+
const realpath = deps.realpathSync ?? realpathSync;
|
|
872
|
+
let currentPhysical: string;
|
|
873
|
+
try {
|
|
874
|
+
currentPhysical = realpath(current);
|
|
875
|
+
} catch {
|
|
876
|
+
return "unknown";
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
return serviceHomeMatches(realpath(recorded), currentPhysical) ? "same" : "different";
|
|
880
|
+
} catch (error) {
|
|
881
|
+
// The spellings already differ. A recorded path that no longer exists cannot be an alias of
|
|
882
|
+
// the current home, so it stays a mismatch (a stale mount keeps the foreign-owner refusal);
|
|
883
|
+
// only an error that leaves existence unproven, such as EACCES, is indeterminate.
|
|
884
|
+
const code = (error as NodeJS.ErrnoException | null)?.code;
|
|
885
|
+
return code === "ENOENT" || code === "ENOTDIR" ? "different" : "unknown";
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/** Lexical compare first; when spellings differ, compare the directories both resolve to so a
|
|
890
|
+
* junction or symlink spelling recorded by an older install still names the same home.
|
|
891
|
+
* Fails closed on an indeterminate resolution — ownership classification needs the
|
|
892
|
+
* tri-state {@link compareServicePathToInstall} instead. */
|
|
893
|
+
export function servicePathMatchesInstall(recorded: string, current: string, deps: CodexHomeDeps = {}): boolean {
|
|
894
|
+
return compareServicePathToInstall(recorded, current, deps) === "same";
|
|
895
|
+
}
|
|
896
|
+
|
|
860
897
|
export function serviceCodexHomeMatchesInstall(recordedHome: string, deps: CodexHomeDeps = {}): boolean {
|
|
861
|
-
return
|
|
898
|
+
return servicePathMatchesInstall(recordedHome, currentCodexHome(deps), deps);
|
|
862
899
|
}
|
|
863
900
|
|
|
864
901
|
/** Single accessor for backend-sensitive service code — v1/legacy state maps to scheduler. */
|
package/src/service.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* restore it via the command.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export type { ServiceBackend, ServiceInstallState, ServiceStateEvidence, ServiceStateResolution, ServiceOwner, ServiceOwnership, ServiceOwnershipSubject, ServiceOwnershipResolution, ServiceStateSwapDeps, RecordServiceOwnerRequest, RecordServiceOwnerDeps, ReleaseServiceOwnerDeps, RemoveServiceStateDeps } from "./service/state";
|
|
10
|
-
export { SERVICE_MANAGED_ENV, SERVICE_OWNERSHIP_PROTOCOL_VERSION, SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION, stableLauncherEntry, serviceLogPath, serviceStatePaths, serviceStatePathsForOpenCodexHome, parseServiceInstallState, parseServiceOwnership, inspectServiceStateEvidence, resolveServiceState, currentServiceHomes, serviceHomeMatches, serviceCodexHomeMatchesInstall, readServiceBackend, serviceReinstallArgs, serviceInstallArgs, ServiceStateConflictError, ServiceOwnershipSubjectMismatchError, ServiceOwnershipSubjectUnknownError, ServiceTakeoverCompatibilityChangedError, swapServiceInstallState, removeServiceInstallStateRecords, serviceOwnership, resolveServiceOwnership, sameServiceOwnershipSubject, desktopOwnsService, ownershipGrantedTo, recordServiceOwner, releaseServiceOwner } from "./service/state";
|
|
9
|
+
export type { ServiceBackend, ServiceInstallState, ServiceStateEvidence, ServiceStateResolution, ServiceOwner, ServiceOwnership, ServiceOwnershipSubject, ServiceOwnershipResolution, ServicePathComparison, ServiceStateSwapDeps, RecordServiceOwnerRequest, RecordServiceOwnerDeps, ReleaseServiceOwnerDeps, RemoveServiceStateDeps } from "./service/state";
|
|
10
|
+
export { SERVICE_MANAGED_ENV, SERVICE_OWNERSHIP_PROTOCOL_VERSION, SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION, stableLauncherEntry, serviceLogPath, serviceStatePaths, serviceStatePathsForOpenCodexHome, parseServiceInstallState, parseServiceOwnership, inspectServiceStateEvidence, resolveServiceState, currentServiceHomes, serviceHomeMatches, serviceCodexHomeMatchesInstall, servicePathMatchesInstall, compareServicePathToInstall, readServiceBackend, serviceReinstallArgs, serviceInstallArgs, ServiceStateConflictError, ServiceOwnershipSubjectMismatchError, ServiceOwnershipSubjectUnknownError, ServiceTakeoverCompatibilityChangedError, swapServiceInstallState, removeServiceInstallStateRecords, serviceOwnership, resolveServiceOwnership, sameServiceOwnershipSubject, desktopOwnsService, ownershipGrantedTo, recordServiceOwner, releaseServiceOwner } from "./service/state";
|
|
11
11
|
export type { OwnershipMutationLeaseOptions, OwnershipMutationLease } from "./service/ownership-mutation-lease.mjs";
|
|
12
12
|
export { acquireOwnershipMutationLease, withOwnershipMutationLease } from "./service/ownership-mutation-lease.mjs";
|
|
13
13
|
export type { ManagingCliRole, ManagingCliObservation, RegisteredManagingCliInvocation, ServiceTakeoverCompatibilityInput, ServiceTakeoverCompatibility } from "./service/ownership-compatibility";
|
package/src/types/config.ts
CHANGED
|
@@ -362,6 +362,18 @@ export interface OcxConfigRebaseProvenance {
|
|
|
362
362
|
deletedTopLevelKeys: string[];
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
|
|
366
|
+
export type SkillsCatalogRefresh = "per_session" | "per_turn";
|
|
367
|
+
|
|
368
|
+
export interface OcxSkillsConfig {
|
|
369
|
+
/**
|
|
370
|
+
* Refresh policy for the runtime skills catalog (#5569).
|
|
371
|
+
* `per_session` (default): snapshots incoming `<skills_instructions>` on the first turn of a trustworthy session and reuses it across turns to preserve the Anthropic prompt cache.
|
|
372
|
+
* `per_turn`: re-derives/passes through incoming skills instructions every turn (previous behavior).
|
|
373
|
+
*/
|
|
374
|
+
catalog_refresh?: SkillsCatalogRefresh;
|
|
375
|
+
}
|
|
376
|
+
|
|
365
377
|
export type OcxRuntimeRole = "standalone" | "hub" | "client";
|
|
366
378
|
|
|
367
379
|
export interface OcxHubConfig {
|
|
@@ -487,6 +499,8 @@ export interface OcxConfig {
|
|
|
487
499
|
client?: OcxClientConnectionConfig;
|
|
488
500
|
/** Operator-facing redaction policy for management and CLI projections. */
|
|
489
501
|
privacy?: OcxPrivacyConfig;
|
|
502
|
+
/** Runtime skills catalog session snapshotting settings (#5569). */
|
|
503
|
+
skills?: OcxSkillsConfig;
|
|
490
504
|
/** Opt-in process-local aggregate request metrics on the authenticated management plane. */
|
|
491
505
|
metricsExport?: { enabled?: boolean };
|
|
492
506
|
/** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
|
|
@@ -724,6 +738,27 @@ export interface OcxConfig {
|
|
|
724
738
|
};
|
|
725
739
|
/** Opt-in failure-only recovery; never replaces the initial compaction model. */
|
|
726
740
|
compactionRecovery?: { enabled: boolean; model: string; allowDevinInvalidArgument?: boolean };
|
|
741
|
+
/**
|
|
742
|
+
* Destination model for Codex's own memory pipeline, per phase
|
|
743
|
+
* (src/server/responses/memory-models.ts).
|
|
744
|
+
*
|
|
745
|
+
* Codex runs Phase 1 ("extract") once per finished thread to summarize that thread's rollout,
|
|
746
|
+
* and Phase 2 ("consolidation") once as an agent run that merges the summaries into the files
|
|
747
|
+
* under `$CODEX_HOME/memories`. Without an entry here each phase keeps its existing route,
|
|
748
|
+
* including any configured shadow-call interception.
|
|
749
|
+
*
|
|
750
|
+
* A phase is recognized from Codex's turn metadata, never inferred from the model id, the timing
|
|
751
|
+
* or the token counts: Phase 1 shares `gpt-5.6-luna` with the app's title/commit helper calls,
|
|
752
|
+
* and `shadowCallIntercept` is the setting for those. A configured phase wins over that
|
|
753
|
+
* intercept, because the memory decision is the more specific one.
|
|
754
|
+
*
|
|
755
|
+
* `model` is required for a configured phase; omitting the phase leaves its route in place.
|
|
756
|
+
* `reasoningEffort` overrides the effort Codex hard-codes for that phase.
|
|
757
|
+
*/
|
|
758
|
+
memoryModels?: {
|
|
759
|
+
extract?: { model: string; reasoningEffort?: string };
|
|
760
|
+
consolidation?: { model: string; reasoningEffort?: string };
|
|
761
|
+
};
|
|
727
762
|
/**
|
|
728
763
|
* Models hidden from Codex discovery without blocking direct proxy calls. Routed provider ids
|
|
729
764
|
* are excluded from the catalog + /v1/models entirely. Account-qualified native ids hide only
|
|
@@ -869,10 +904,12 @@ export interface OcxConfig {
|
|
|
869
904
|
* HTTP URLs are mirrored into HTTP_PROXY/HTTPS_PROXY when unset. SOCKS5 URLs are mirrored
|
|
870
905
|
* into ALL_PROXY, clear inherited HTTP(S)_PROXY, and use OpenCodex's SOCKS5 transport.
|
|
871
906
|
* Loopback stays in NO_PROXY.
|
|
872
|
-
* The literal `"auto"` reads
|
|
873
|
-
* once at
|
|
874
|
-
*
|
|
875
|
-
*
|
|
907
|
+
* The literal `"auto"` reads Windows WinINET or macOS static HTTP/HTTPS proxy settings
|
|
908
|
+
* once at startup. Inherited scheme proxies win; on macOS, inherited ALL_PROXY also skips
|
|
909
|
+
* discovery. A macOS `*.<domain>` exception maps to `.<domain>` (including the apex),
|
|
910
|
+
* exact link-local CIDRs are omitted with a warning, and other unsafe exceptions
|
|
911
|
+
* refuse discovery without environment writes.
|
|
912
|
+
* PAC/WPAD, SOCKS-only settings, and live changes are not followed.
|
|
876
913
|
*/
|
|
877
914
|
proxy?: string;
|
|
878
915
|
/**
|
|
@@ -1226,6 +1263,11 @@ export interface OcxComboTarget {
|
|
|
1226
1263
|
* target currently advertises; an explicit list must be non-empty.
|
|
1227
1264
|
*/
|
|
1228
1265
|
reasoningEfforts?: OcxComboDefaultEffort[];
|
|
1266
|
+
/**
|
|
1267
|
+
* Operator-authored capability description sent only to the JEV decision
|
|
1268
|
+
* service for this target. The built-in model profile always applies.
|
|
1269
|
+
*/
|
|
1270
|
+
modelProfile?: string;
|
|
1229
1271
|
/**
|
|
1230
1272
|
* Marks an emergency-only target. Inert unless the combo sets
|
|
1231
1273
|
* `cooldownWaitPolicy`, and never makes a target permanently ineligible —
|
package/src/types/provider.ts
CHANGED
|
@@ -218,6 +218,8 @@ export interface AttemptTierOutcome {
|
|
|
218
218
|
callerFastSuppressedByConfig?: boolean;
|
|
219
219
|
confirmation: "confirmed" | "assumed" | "downgraded" | "unknown";
|
|
220
220
|
responseServiceTier?: string;
|
|
221
|
+
/** False retains the raw echo as evidence only, including during cost estimation. */
|
|
222
|
+
responseTierAuthoritative?: boolean;
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/**
|
|
@@ -307,6 +309,13 @@ export interface OcxProviderConfig {
|
|
|
307
309
|
* absence derives from the final model adapter.
|
|
308
310
|
*/
|
|
309
311
|
fastWire?: FastWire | null;
|
|
312
|
+
/**
|
|
313
|
+
* Whether echoed service_tier can confirm or deny Fast. Set false for a relay whose
|
|
314
|
+
* response metadata cannot establish the granted tier. Absence keeps legacy authority;
|
|
315
|
+
* canonical ChatGPT Codex forwarding always treats the echo as non-authoritative.
|
|
316
|
+
* Observation only: this does not enable Fast or change request serialization.
|
|
317
|
+
*/
|
|
318
|
+
responseTierAuthoritative?: boolean;
|
|
310
319
|
baseUrl: string;
|
|
311
320
|
/**
|
|
312
321
|
* Optional relative resource path for key-auth openai-responses requests. Must start with `/`
|
package/src/types/request.ts
CHANGED
|
@@ -143,6 +143,12 @@ export interface OcxParsedRequest {
|
|
|
143
143
|
_compactionRequest?: boolean;
|
|
144
144
|
/** Manual compaction moved to another provider: summarize portably even on a canonical ChatGPT target. */
|
|
145
145
|
_portableCompaction?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Codex memory pipeline phase this turn belongs to, when `memoryModels` routes it
|
|
148
|
+
* (src/server/responses/memory-models.ts). Read at the effort choke point, which runs after the
|
|
149
|
+
* route is known.
|
|
150
|
+
*/
|
|
151
|
+
_memoryModelPhase?: "extract" | "consolidation";
|
|
146
152
|
/**
|
|
147
153
|
* True when the current request newly introduced a stored compaction summary/marker. Historical
|
|
148
154
|
* markers restored by previous_response_id expansion were already acknowledged and do not reset
|
package/src/types/tools.ts
CHANGED
|
@@ -70,8 +70,9 @@ export function dottedToolName(namespace: string | undefined, name: string): str
|
|
|
70
70
|
* `apply_patch`, `view_image`, or one of the goal helpers (`create_goal`, `get_goal`,
|
|
71
71
|
* `update_goal`, #5495) instead of the declared `exec`. Accept these nested helper names only
|
|
72
72
|
* when the request catalog actually declares `exec` and does not itself declare the emitted name
|
|
73
|
-
* (an MCP server may legitimately advertise one under its own namespace). The list
|
|
74
|
-
*
|
|
73
|
+
* (an MCP server may legitimately advertise one under its own namespace). The helper list itself
|
|
74
|
+
* is closed; the one other admission through `exec` is a direct `mcp__<server>__<tool>` call,
|
|
75
|
+
* which `isCodeModeMcpDirectName` recognizes.
|
|
75
76
|
*/
|
|
76
77
|
const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
77
78
|
const CODE_MODE_HELPER_TOOL_NAMES = [
|
|
@@ -105,6 +106,23 @@ export const CODE_MODE_HELPER_WIRE_NAMES: ReadonlySet<string> = new Set<string>(
|
|
|
105
106
|
CODE_MODE_HELPER_TOOL_NAMES,
|
|
106
107
|
);
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* A flattened MCP wire name (`mcp__<server>__<tool>`) emitted as a direct tool call.
|
|
111
|
+
*
|
|
112
|
+
* Codex code mode reaches the host's nested tools through `tools.<name>(...)` inside `exec`,
|
|
113
|
+
* so none of them are declared; routed models (observed: Kimi K3, GLM 5.3) occasionally skip the
|
|
114
|
+
* wrapper and call the flattened name directly. Under a code-mode catalog the call is compiled
|
|
115
|
+
* into the equivalent `tools.<name>(...)` exec body instead of failing closed — capability-
|
|
116
|
+
* equivalent, since the model could have written that JavaScript itself. Server and tool must
|
|
117
|
+
* both be non-empty so a bare `mcp__` prefix never qualifies.
|
|
118
|
+
*/
|
|
119
|
+
export function isCodeModeMcpDirectName(name: string): boolean {
|
|
120
|
+
if (!name.startsWith("mcp__")) return false;
|
|
121
|
+
const rest = name.slice("mcp__".length);
|
|
122
|
+
const separator = rest.indexOf("__");
|
|
123
|
+
return separator > 0 && separator + "__".length < rest.length;
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
/**
|
|
109
127
|
* Spellings that may never be MANUFACTURED as a bare alias for a namespaced tool.
|
|
110
128
|
*
|
|
@@ -137,19 +155,24 @@ export const NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES: ReadonlySet<string> = new Set
|
|
|
137
155
|
* The same wrapper may surround an already-flattened namespace identity; accept that exact
|
|
138
156
|
* declared suffix without treating its child name as a bare declaration.
|
|
139
157
|
* Also normalizes nested helper names (`exec_command`, `shell_command`, `write_stdin`,
|
|
140
|
-
* `apply_patch`, `view_image`, `create_goal`, `get_goal`, `update_goal`)
|
|
141
|
-
* `exec` when code-mode `exec` is declared in the
|
|
158
|
+
* `apply_patch`, `view_image`, `create_goal`, `get_goal`, `update_goal`) and direct
|
|
159
|
+
* `mcp__<server>__<tool>` calls to `exec` when code-mode `exec` is declared in the
|
|
160
|
+
* request catalog. MCP recovery additionally requires explicit custom-tool provenance;
|
|
161
|
+
* a structured function named `exec` is not a JavaScript executor.
|
|
142
162
|
*
|
|
143
163
|
* @param name - The tool name emitted on the wire by the provider.
|
|
144
164
|
* @param declared - All wire tool names declared in the request catalog, including aliases.
|
|
145
165
|
* @param declaredBare - Explicitly declared bare tool names without namespace provenance.
|
|
146
166
|
* When omitted, falls back to `declared`.
|
|
167
|
+
* @param declaredCustom - Custom wire identities from the caller's catalog, never bare aliases
|
|
168
|
+
* manufactured from foreign namespaces.
|
|
147
169
|
* @returns The normalized tool name to expose downstream.
|
|
148
170
|
*/
|
|
149
171
|
export function normalizeDeclaredToolName(
|
|
150
172
|
name: string,
|
|
151
173
|
declared: ReadonlySet<string> | undefined,
|
|
152
174
|
declaredBare?: ReadonlySet<string>,
|
|
175
|
+
declaredCustom?: ReadonlySet<string>,
|
|
153
176
|
): string {
|
|
154
177
|
if (!declared) return name;
|
|
155
178
|
if (declared.has(name)) return name;
|
|
@@ -172,11 +195,12 @@ export function normalizeDeclaredToolName(
|
|
|
172
195
|
candidate = bare;
|
|
173
196
|
} else if (
|
|
174
197
|
// Code mode never declares bare helper names; a provider that invents `default.`
|
|
175
|
-
// for one still means the nested helper.
|
|
176
|
-
//
|
|
198
|
+
// for one still means the nested helper. The same wrapper can surround a direct MCP
|
|
199
|
+
// name, but only a custom exec declaration authorizes that recovery.
|
|
177
200
|
bare.length > 0
|
|
178
201
|
&& declared.has(CODE_MODE_EXEC_TOOL_NAME)
|
|
179
|
-
&& (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(bare)
|
|
202
|
+
&& ((CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(bare)
|
|
203
|
+
|| (declaredCustom?.has(CODE_MODE_EXEC_TOOL_NAME) && isCodeModeMcpDirectName(bare)))
|
|
180
204
|
&& !declared.has("default." + bare)
|
|
181
205
|
&& !declared.has("default__" + bare)
|
|
182
206
|
) {
|
|
@@ -192,7 +216,13 @@ export function normalizeDeclaredToolName(
|
|
|
192
216
|
if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
|
|
193
217
|
return candidate;
|
|
194
218
|
}
|
|
195
|
-
|
|
219
|
+
if ((CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(candidate)) {
|
|
220
|
+
return CODE_MODE_EXEC_TOOL_NAME;
|
|
221
|
+
}
|
|
222
|
+
// A direct `mcp__<server>__<tool>` call names a nested host tool the code-mode catalog
|
|
223
|
+
// never declares; `compileCodeModeHelperInput` turns it into the `tools.<name>(...)`
|
|
224
|
+
// exec body the model could have written itself.
|
|
225
|
+
return declaredCustom?.has(CODE_MODE_EXEC_TOOL_NAME) && isCodeModeMcpDirectName(candidate)
|
|
196
226
|
? CODE_MODE_EXEC_TOOL_NAME
|
|
197
227
|
: candidate;
|
|
198
228
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
dottedToolName,
|
|
9
9
|
namespacedToolName,
|
|
10
10
|
normalizeDeclaredToolName,
|
|
11
|
+
isCodeModeMcpDirectName,
|
|
11
12
|
toolChoiceAliases,
|
|
12
13
|
createToolChoiceResolver,
|
|
13
14
|
toolChoiceCandidates,
|
|
@@ -77,6 +78,8 @@ export type {
|
|
|
77
78
|
OcxConnectedClientId,
|
|
78
79
|
OcxClientConnectionConfig,
|
|
79
80
|
OcxConfig,
|
|
81
|
+
SkillsCatalogRefresh,
|
|
82
|
+
OcxSkillsConfig,
|
|
80
83
|
OcxAccountPoolRotationStrategy,
|
|
81
84
|
OcxAccountPoolQuotaWindow,
|
|
82
85
|
OcxComboCooldownWaitPolicy,
|
|
@@ -2,6 +2,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { unprivilegedOwnershipMutationEnvironment } from "../service/ownership-mutation-lease.mjs";
|
|
3
3
|
import { PKG, registrySpawnTarget, type Channel, type Installer } from "./index";
|
|
4
4
|
import type { PnpmGlobalOwner } from "./pnpm-global-install.mjs";
|
|
5
|
+
import { PNPM_READ_CWD, pnpmReadEnvironment } from "./pnpm-read-policy.mjs";
|
|
5
6
|
|
|
6
7
|
export const REGISTRY_DEADLINE_MS = 12_000;
|
|
7
8
|
export const REGISTRY_OUTPUT_LIMIT = 4_096;
|
|
@@ -64,7 +65,10 @@ export async function latestVersionAsync(
|
|
|
64
65
|
child = deps.spawnFn(target.bin, target.args, {
|
|
65
66
|
stdio: ["pipe", "pipe", "pipe"],
|
|
66
67
|
windowsHide: true,
|
|
67
|
-
|
|
68
|
+
cwd: installer === "pnpm" ? PNPM_READ_CWD : undefined,
|
|
69
|
+
env: installer === "pnpm"
|
|
70
|
+
? pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(target.env ?? process.env))
|
|
71
|
+
: unprivilegedOwnershipMutationEnvironment(target.env ?? process.env),
|
|
68
72
|
...target.options,
|
|
69
73
|
}) as ChildProcessWithoutNullStreams;
|
|
70
74
|
} catch {
|
package/src/update/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
|
|
|
41
41
|
import { withoutSiblingMarker } from "../codex/sibling-start";
|
|
42
42
|
import { packageVersion } from "../lib/package-version";
|
|
43
43
|
import { selfLaunchArgv } from "../lib/self-launch-argv";
|
|
44
|
+
import { PNPM_READ_CWD, withPnpmCommandCwd, pnpmReadEnvironment } from "./pnpm-read-policy.mjs";
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* A `codex-history-backup-*.json` surviving a stop means the native-history restore was
|
|
@@ -98,27 +99,33 @@ function runPnpmCandidate(
|
|
|
98
99
|
commandPath: string,
|
|
99
100
|
args: readonly string[],
|
|
100
101
|
capture = false,
|
|
102
|
+
spawn: typeof spawnSync = spawnSync,
|
|
101
103
|
): { status: number | null; stdout?: string | null; stderr?: string | null } {
|
|
102
104
|
const invocation = pnpmInvocationForPath(commandPath, args);
|
|
103
105
|
if (!invocation) return { status: 1 };
|
|
104
|
-
return
|
|
106
|
+
return spawn(invocation.file, invocation.args, {
|
|
105
107
|
stdio: capture ? "pipe" : "ignore",
|
|
106
108
|
encoding: "utf8",
|
|
107
109
|
timeout: 20_000,
|
|
108
110
|
windowsHide: true,
|
|
109
|
-
|
|
111
|
+
cwd: PNPM_READ_CWD,
|
|
112
|
+
env: pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(process.env)),
|
|
110
113
|
...invocation.options,
|
|
111
114
|
});
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
/** Resolve the exact pnpm executable/group/bin that own this package. */
|
|
115
|
-
export function resolveCurrentPnpmGlobalOwner(
|
|
118
|
+
export function resolveCurrentPnpmGlobalOwner(
|
|
119
|
+
invoked = process.argv[1],
|
|
120
|
+
deps: { commandPaths?: readonly string[]; spawn?: typeof spawnSync } = {},
|
|
121
|
+
): PnpmGlobalOwnerResult {
|
|
122
|
+
const spawn = deps.spawn ?? spawnSync;
|
|
116
123
|
return resolvePnpmGlobalOwner({
|
|
117
124
|
packageName: PKG,
|
|
118
125
|
packagePath: packageRoot(),
|
|
119
|
-
commandPaths: resolvePnpmCommands(),
|
|
126
|
+
commandPaths: deps.commandPaths ?? resolvePnpmCommands(),
|
|
120
127
|
runningShimPath: runningPnpmShimPath(invoked),
|
|
121
|
-
runPnpm: runPnpmCandidate,
|
|
128
|
+
runPnpm: (commandPath, args, capture) => runPnpmCandidate(commandPath, args, capture, spawn),
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -136,22 +143,26 @@ function ownerPnpmTarget(
|
|
|
136
143
|
};
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
function runOwnedPnpm(
|
|
146
|
+
export function runOwnedPnpm(
|
|
140
147
|
owner: PnpmGlobalOwner,
|
|
141
148
|
args: readonly string[],
|
|
142
149
|
capture: boolean,
|
|
143
150
|
stdio: "inherit" | "pipe" | "ignore" = capture ? "pipe" : "inherit",
|
|
151
|
+
spawn: typeof spawnSync = spawnSync,
|
|
144
152
|
): { status: number | null; stdout?: string | null; stderr?: string | null } {
|
|
145
153
|
const target = ownerPnpmTarget(owner, args);
|
|
146
154
|
if (!target) return { status: 1 };
|
|
147
|
-
return
|
|
155
|
+
return withPnpmCommandCwd(args, cwd => spawn(target.bin, target.args, {
|
|
148
156
|
stdio,
|
|
149
157
|
encoding: "utf8",
|
|
150
158
|
timeout: 180_000,
|
|
151
159
|
windowsHide: true,
|
|
152
|
-
|
|
160
|
+
// Reads probe from the package dir; `add -g`/rollback children run from a neutral
|
|
161
|
+
// directory so a Windows cwd handle never pins open the package pnpm is replacing.
|
|
162
|
+
cwd,
|
|
163
|
+
env: pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(target.env)),
|
|
153
164
|
...target.options,
|
|
154
|
-
});
|
|
165
|
+
}));
|
|
155
166
|
}
|
|
156
167
|
|
|
157
168
|
/** Re-read the owning group's active package and return its verified launcher. */
|
|
@@ -276,16 +287,20 @@ export function latestVersion(
|
|
|
276
287
|
tag: string,
|
|
277
288
|
installer: Installer = detectInstall(),
|
|
278
289
|
owner?: PnpmGlobalOwner,
|
|
290
|
+
spawn: typeof spawnSync = spawnSync,
|
|
279
291
|
): string | null {
|
|
280
292
|
const resolvedOwner = installer === "pnpm" ? selectedPnpmOwner(owner) : undefined;
|
|
281
293
|
if (installer === "pnpm" && !resolvedOwner) return null;
|
|
282
294
|
const manager = registrySpawnTarget(installer, ["view", `${PKG}@${tag}`, "version"], resolvedOwner);
|
|
283
295
|
if (!manager) return null;
|
|
284
|
-
const r =
|
|
296
|
+
const r = spawn(manager.bin, manager.args, {
|
|
285
297
|
encoding: "utf8",
|
|
286
298
|
timeout: 12000,
|
|
287
299
|
windowsHide: true,
|
|
288
|
-
|
|
300
|
+
cwd: installer === "pnpm" ? PNPM_READ_CWD : undefined,
|
|
301
|
+
env: installer === "pnpm"
|
|
302
|
+
? pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(manager.env ?? process.env))
|
|
303
|
+
: unprivilegedOwnershipMutationEnvironment(manager.env ?? process.env),
|
|
289
304
|
...manager.options,
|
|
290
305
|
});
|
|
291
306
|
return r.status === 0 && typeof r.stdout === "string" ? (r.stdout.trim() || null) : null;
|
|
@@ -342,7 +357,10 @@ export function checkUpdatePackageIntegrity(
|
|
|
342
357
|
encoding: "utf8",
|
|
343
358
|
timeout: 12000,
|
|
344
359
|
windowsHide: true,
|
|
345
|
-
|
|
360
|
+
cwd: installer === "pnpm" ? PNPM_READ_CWD : undefined,
|
|
361
|
+
env: installer === "pnpm"
|
|
362
|
+
? pnpmReadEnvironment(unprivilegedOwnershipMutationEnvironment(target.env ?? process.env))
|
|
363
|
+
: unprivilegedOwnershipMutationEnvironment(target.env ?? process.env),
|
|
346
364
|
...target.options,
|
|
347
365
|
});
|
|
348
366
|
});
|
package/src/update/job.ts
CHANGED
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
} from "./npm-cache-preflight.mjs";
|
|
63
63
|
import { guiUpdateWorkerCommand } from "./worker-launch";
|
|
64
64
|
import { withoutSiblingMarker } from "../codex/sibling-start";
|
|
65
|
+
import type { WorkerLaunchContext } from "./worker-launch";
|
|
65
66
|
|
|
66
67
|
const RELEASE_NOTES_URL = "https://github.com/lidge-jun/opencodex/releases/latest";
|
|
67
68
|
const UPDATE_JOB_FILENAME = "update-job.json";
|
|
@@ -568,6 +569,7 @@ export function spawnGuiUpdateWorker(
|
|
|
568
569
|
jobId: string,
|
|
569
570
|
channel: Channel,
|
|
570
571
|
restart: boolean,
|
|
572
|
+
context: WorkerLaunchContext = {},
|
|
571
573
|
): UpdateWorkerProcess {
|
|
572
574
|
const args = selfLaunchArgv([
|
|
573
575
|
"__gui-update-worker",
|
|
@@ -576,7 +578,7 @@ export function spawnGuiUpdateWorker(
|
|
|
576
578
|
restart ? "restart" : "no-restart",
|
|
577
579
|
]);
|
|
578
580
|
if (process.platform !== "win32") {
|
|
579
|
-
const launch = guiUpdateWorkerCommand(process.execPath, args);
|
|
581
|
+
const launch = guiUpdateWorkerCommand(process.execPath, args, context);
|
|
580
582
|
return spawn(launch.command, launch.argv, {
|
|
581
583
|
detached: true,
|
|
582
584
|
stdio: "ignore",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const PNPM_READ_CWD: string;
|
|
2
|
+
/** The callback must complete synchronously before its temporary workspace is cleaned. */
|
|
3
|
+
export declare function withPnpmCommandCwd<T>(args: readonly string[], run: (cwd: string) => T): T;
|
|
4
|
+
export declare function pnpmReadEnvironment(env?: Record<string, string | undefined>): Record<string, string | undefined>;
|