@bitkyc08/opencodex 2.10.0 → 2.10.1
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/AGENTS_INSTALL.md +77 -0
- package/README.md +4 -10
- package/bin/ocx.mjs +71 -18
- package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
- package/gui/dist/assets/index-ChZQsmBY.js +70 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +8 -4
- package/src/adapters/anthropic.ts +208 -14
- package/src/adapters/base.ts +16 -5
- package/src/adapters/cursor/effort-map.ts +3 -2
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/live-transport.ts +105 -95
- package/src/adapters/cursor/native-exec.ts +32 -6
- package/src/adapters/cursor/protobuf-request.ts +20 -15
- package/src/adapters/cursor/request-builder.ts +21 -7
- package/src/adapters/cursor/types.ts +7 -0
- package/src/adapters/google-antigravity-replay.ts +237 -21
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +50 -9
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +10 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +37 -11
- package/src/adapters/openai-chat.ts +284 -83
- package/src/adapters/openai-responses.ts +182 -24
- package/src/bridge.ts +177 -7
- package/src/chat/outbound.ts +78 -23
- package/src/claude/agents-inject.ts +27 -5
- package/src/claude/inbound.ts +11 -1
- package/src/claude/model-info.ts +13 -10
- package/src/claude/outbound.ts +17 -0
- package/src/cli/account-api.ts +24 -0
- package/src/cli/account-auth.ts +31 -6
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +5 -0
- package/src/cli/claude.ts +2 -1
- package/src/cli/doctor.ts +93 -22
- package/src/cli/export-command.ts +26 -12
- package/src/cli/help.ts +8 -6
- package/src/cli/index.ts +56 -22
- package/src/cli/integrations.ts +84 -1
- package/src/cli/observe.ts +54 -1
- package/src/cli/opencode.ts +2 -1
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +1 -1
- package/src/cli/v2.ts +36 -9
- package/src/clients/config-export.ts +687 -10
- package/src/codex/account-lifecycle.ts +30 -5
- package/src/codex/account-usability.ts +22 -2
- package/src/codex/admission.ts +255 -0
- package/src/codex/auth-api.ts +427 -140
- package/src/codex/auth-context.ts +155 -30
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +62 -0
- package/src/codex/catalog/aggregation.ts +14 -1
- package/src/codex/catalog/bundled.ts +282 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/metadata.ts +51 -6
- package/src/codex/catalog/parsing.ts +6 -3
- package/src/codex/catalog/provider-fetch.ts +576 -41
- package/src/codex/catalog/sync.ts +505 -66
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-write-serialization.ts +241 -0
- package/src/codex/catalog.ts +2 -1
- package/src/codex/codex-write-lock.ts +372 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +441 -0
- package/src/codex/desired-state.ts +177 -0
- package/src/codex/features.ts +52 -8
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +257 -0
- package/src/codex/history-lock.ts +241 -0
- package/src/codex/history-migration-guardian.ts +18 -5
- package/src/codex/history-provider.ts +9 -2
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +176 -0
- package/src/codex/inject-coordination.ts +245 -0
- package/src/codex/inject.ts +605 -124
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +10 -1
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +158 -0
- package/src/codex/native-main-owner.ts +315 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1512 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +340 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +557 -0
- package/src/codex/project-config-warnings.ts +18 -4
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +86 -3
- package/src/codex/routing.ts +299 -62
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +39 -13
- package/src/codex/subagent-model-fallback.ts +73 -12
- package/src/codex/transition-state.ts +604 -0
- package/src/codex/upstream-host-health.ts +70 -0
- package/src/codex/user-identity.ts +266 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/config.ts +562 -26
- package/src/generated/jawcode-model-metadata.ts +2 -2
- package/src/grok/inject.ts +15 -4
- package/src/grok/inspect.ts +45 -0
- package/src/images/loop.ts +113 -20
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +165 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +101 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/bounded-body.ts +46 -8
- package/src/lib/bun-runtime.ts +110 -1
- package/src/lib/bun-stream-caps.ts +2 -1
- package/src/lib/redact.ts +407 -2
- package/src/lib/shadow-call.ts +24 -0
- package/src/lib/translator-budget.ts +10 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +154 -2
- package/src/lib/windows-secret-acl.ts +212 -11
- package/src/lib/winsw.ts +9 -3
- package/src/oauth/index.ts +61 -3
- package/src/oauth/key-providers.ts +4 -0
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/store.ts +31 -6
- package/src/oauth/token-guardian.ts +9 -3
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/derive.ts +33 -1
- package/src/providers/free-directory.ts +3 -1
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/openai-sidecar.ts +64 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota.ts +334 -26
- package/src/providers/registry.ts +284 -16
- package/src/providers/xai-transport.ts +11 -4
- package/src/responses/compaction.ts +8 -1
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +44 -2
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +105 -0
- package/src/responses/spill-store.ts +45 -8
- package/src/responses/state.ts +161 -17
- package/src/router.ts +302 -16
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +204 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +590 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile.ts +423 -0
- package/src/routing/quota.ts +124 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/auth-cors.ts +46 -6
- package/src/server/chat-completions.ts +28 -13
- package/src/server/claude-messages.ts +23 -15
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +10 -1
- package/src/server/index.ts +238 -52
- package/src/server/lifecycle.ts +228 -9
- package/src/server/live.ts +14 -3
- package/src/server/management/agent-settings-routes.ts +64 -14
- package/src/server/management/combo-routes.ts +5 -5
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +42 -2
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +32 -113
- package/src/server/management/model-rows.ts +117 -0
- package/src/server/management/native-integration-routes.ts +587 -0
- package/src/server/management/oauth-account-routes.ts +1 -1
- package/src/server/management/provider-routes.ts +218 -117
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +177 -0
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +276 -24
- package/src/server/management/system-routes.ts +4 -0
- package/src/server/management-api.ts +84 -9
- package/src/server/management-auth.ts +43 -5
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +120 -6
- package/src/server/request-log.ts +26 -6
- package/src/server/responses/collaboration.ts +63 -8
- package/src/server/responses/compact.ts +272 -41
- package/src/server/responses/core.ts +730 -132
- package/src/server/responses/fetch-helpers.ts +15 -1
- package/src/server/responses-item-id-repair.ts +32 -3
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/search.ts +51 -6
- package/src/server/sse-payload-rewrite.ts +89 -12
- package/src/server/startup-health-cache.ts +7 -1
- package/src/server/ws-bridge.ts +11 -17
- package/src/service-manager-probe.ts +297 -0
- package/src/service.ts +222 -32
- package/src/tray/windows-tray.ps1 +9 -0
- package/src/tray/windows.ts +15 -7
- package/src/types.ts +194 -14
- package/src/update/index.ts +13 -13
- package/src/update/job.ts +24 -21
- package/src/update/notify.ts +7 -3
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +129 -10
- package/src/usage/log.ts +50 -15
- package/src/usage/summary.ts +4 -4
- package/src/vision/index.ts +6 -1
- package/src/web-search/loop.ts +161 -34
- package/gui/dist/assets/index-OY43ubAq.css +0 -1
- package/gui/dist/assets/index-YwNnKZcL.js +0 -67
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-independent identity and namespace resolution for Codex writes.
|
|
3
|
+
*
|
|
4
|
+
* Bun 1.3.14 made the obvious implementation unsafe: both `os.homedir()` and
|
|
5
|
+
* `os.userInfo().homedir` follow HOME. A service and CLI for the same account
|
|
6
|
+
* could therefore coordinate through different databases. The namespace is
|
|
7
|
+
* keyed only by the effective uid/SID and the canonical CODEX_HOME.
|
|
8
|
+
*
|
|
9
|
+
* Design record: devlog/_plan/260804_codex_write_substrate/005_contract.md §7.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import {
|
|
13
|
+
lstatSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
realpathSync,
|
|
16
|
+
statSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
ResolveCodexCoordinatorDatabasePath,
|
|
22
|
+
ResolveCodexCatalogSerializationDatabasePath,
|
|
23
|
+
ResolveCodexHistorySerializationDatabasePath,
|
|
24
|
+
ResolveEffectiveUserIdentity,
|
|
25
|
+
UserIdentity,
|
|
26
|
+
} from "./convergence-types";
|
|
27
|
+
|
|
28
|
+
const POSIX_PRIVATE_MODE = 0o700;
|
|
29
|
+
const POSIX_TMP_REQUIRED_MODE = 0o1003;
|
|
30
|
+
const POSIX_TMP_PATH = "/tmp";
|
|
31
|
+
const SID_PATTERN = /^S-1-(?:\d+-)+\d+$/i;
|
|
32
|
+
|
|
33
|
+
export class CodexUserIdentityRefusal extends Error {
|
|
34
|
+
readonly code = "CODEX_USER_IDENTITY_REFUSED";
|
|
35
|
+
|
|
36
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = "CodexUserIdentityRefusal";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function refuse(message: string, cause?: unknown): never {
|
|
43
|
+
throw new CodexUserIdentityRefusal(message, cause === undefined ? undefined : { cause });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function powershellValue(expression: string): string {
|
|
47
|
+
let result: ReturnType<typeof Bun.spawnSync>;
|
|
48
|
+
try {
|
|
49
|
+
result = Bun.spawnSync([
|
|
50
|
+
"powershell.exe",
|
|
51
|
+
"-NoLogo",
|
|
52
|
+
"-NoProfile",
|
|
53
|
+
"-NonInteractive",
|
|
54
|
+
"-Command",
|
|
55
|
+
expression,
|
|
56
|
+
], {
|
|
57
|
+
stdin: "ignore",
|
|
58
|
+
stdout: "pipe",
|
|
59
|
+
stderr: "pipe",
|
|
60
|
+
});
|
|
61
|
+
} catch (cause) {
|
|
62
|
+
refuse("Windows effective-account lookup could not start.", cause);
|
|
63
|
+
}
|
|
64
|
+
if (result.exitCode !== 0) refuse("Windows effective-account lookup failed.");
|
|
65
|
+
const value = new TextDecoder().decode(result.stdout).trim();
|
|
66
|
+
if (!value) refuse("Windows effective-account lookup returned an empty value.");
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveWindowsSid(): string {
|
|
71
|
+
const sid = powershellValue(
|
|
72
|
+
"[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value",
|
|
73
|
+
);
|
|
74
|
+
if (!SID_PATTERN.test(sid)) refuse("Windows effective-account lookup returned an invalid SID.");
|
|
75
|
+
return sid.toUpperCase();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const resolveEffectiveUserIdentity: ResolveEffectiveUserIdentity = () => {
|
|
79
|
+
if (process.platform === "win32") {
|
|
80
|
+
return { platform: "win32", sid: resolveWindowsSid() };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const getuid = process.getuid;
|
|
84
|
+
if (typeof getuid !== "function") {
|
|
85
|
+
refuse("The runtime does not expose the effective POSIX uid.");
|
|
86
|
+
}
|
|
87
|
+
let uid: number;
|
|
88
|
+
try {
|
|
89
|
+
uid = getuid.call(process);
|
|
90
|
+
} catch (cause) {
|
|
91
|
+
refuse("The effective POSIX uid lookup failed.", cause);
|
|
92
|
+
}
|
|
93
|
+
if (!Number.isSafeInteger(uid) || uid < 0) {
|
|
94
|
+
refuse("The runtime returned an invalid effective POSIX uid.");
|
|
95
|
+
}
|
|
96
|
+
return { platform: "posix", uid };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function assertPrivatePosixDirectory(path: string, uid: number): void {
|
|
100
|
+
let entry;
|
|
101
|
+
try {
|
|
102
|
+
entry = lstatSync(path);
|
|
103
|
+
} catch (cause) {
|
|
104
|
+
refuse("The Codex coordinator namespace cannot be inspected.", cause);
|
|
105
|
+
}
|
|
106
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
107
|
+
refuse("The Codex coordinator namespace is not a real directory.");
|
|
108
|
+
}
|
|
109
|
+
if (entry.uid !== uid || (entry.mode & 0o777) !== POSIX_PRIVATE_MODE) {
|
|
110
|
+
refuse("The Codex coordinator namespace has unsafe ownership or permissions.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ensurePrivatePosixDirectory(path: string, uid: number): void {
|
|
115
|
+
try {
|
|
116
|
+
mkdirSync(path, { mode: POSIX_PRIVATE_MODE });
|
|
117
|
+
} catch (cause) {
|
|
118
|
+
const code = cause && typeof cause === "object" && "code" in cause
|
|
119
|
+
? String((cause as { code?: unknown }).code)
|
|
120
|
+
: "";
|
|
121
|
+
if (code !== "EEXIST") refuse("The Codex coordinator namespace cannot be created.", cause);
|
|
122
|
+
}
|
|
123
|
+
assertPrivatePosixDirectory(path, uid);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolvePosixRuntimeRoot(uid: number): string {
|
|
127
|
+
let realTmp: string;
|
|
128
|
+
try {
|
|
129
|
+
realTmp = realpathSync.native(POSIX_TMP_PATH);
|
|
130
|
+
const entry = statSync(realTmp);
|
|
131
|
+
if (!entry.isDirectory() || entry.uid !== 0) {
|
|
132
|
+
refuse("The system temporary directory has unsafe ownership.");
|
|
133
|
+
}
|
|
134
|
+
if ((entry.mode & POSIX_TMP_REQUIRED_MODE) !== POSIX_TMP_REQUIRED_MODE) {
|
|
135
|
+
refuse("The system temporary directory lacks sticky world write/search permissions.");
|
|
136
|
+
}
|
|
137
|
+
} catch (cause) {
|
|
138
|
+
if (cause instanceof CodexUserIdentityRefusal) throw cause;
|
|
139
|
+
refuse("The system temporary directory cannot be trusted.", cause);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const root = join(realTmp, `opencodex-runtime-v1-${uid}`);
|
|
143
|
+
ensurePrivatePosixDirectory(root, uid);
|
|
144
|
+
return root;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function resolveWindowsRuntimeRoot(identity: Extract<UserIdentity, { platform: "win32" }>): string {
|
|
148
|
+
if (!SID_PATTERN.test(identity.sid)) refuse("The coordinator identity contains an invalid SID.");
|
|
149
|
+
const localAppData = powershellValue(
|
|
150
|
+
"[Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)",
|
|
151
|
+
);
|
|
152
|
+
if (!isAbsolute(localAppData)) refuse("Windows LocalAppData resolution returned a relative path.");
|
|
153
|
+
|
|
154
|
+
// The SID and known-folder values come from the effective token/.NET OS APIs,
|
|
155
|
+
// never USERPROFILE or LOCALAPPDATA. WP11 adds descriptor/reparse/ACL checks at
|
|
156
|
+
// the stable-database open boundary where those checks can cover SQLite too.
|
|
157
|
+
const root = resolve(localAppData, "OpenCodex", "Runtime", "v1", identity.sid.toUpperCase());
|
|
158
|
+
try {
|
|
159
|
+
mkdirSync(root, { recursive: true });
|
|
160
|
+
} catch (cause) {
|
|
161
|
+
refuse("The Windows coordinator namespace cannot be created.", cause);
|
|
162
|
+
}
|
|
163
|
+
return root;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const resolveCodexCoordinatorDatabasePath: ResolveCodexCoordinatorDatabasePath = (
|
|
167
|
+
identity,
|
|
168
|
+
canonicalCodexHome,
|
|
169
|
+
) => {
|
|
170
|
+
if (!isAbsolute(canonicalCodexHome)) {
|
|
171
|
+
refuse("The canonical CODEX_HOME must be an absolute path.");
|
|
172
|
+
}
|
|
173
|
+
const root = identity.platform === "posix"
|
|
174
|
+
? resolvePosixRuntimeRoot(identity.uid)
|
|
175
|
+
: resolveWindowsRuntimeRoot(identity);
|
|
176
|
+
const locks = join(root, "native-write-locks");
|
|
177
|
+
if (identity.platform === "posix") ensurePrivatePosixDirectory(locks, identity.uid);
|
|
178
|
+
else {
|
|
179
|
+
try {
|
|
180
|
+
mkdirSync(locks, { recursive: true });
|
|
181
|
+
} catch (cause) {
|
|
182
|
+
refuse("The Windows coordinator lock directory cannot be created.", cause);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const homeDigest = createHash("sha256").update(canonicalCodexHome).digest("hex");
|
|
187
|
+
return join(locks, `${homeDigest}.sqlite`);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* K's FINAL database path. Never the native coordinator path.
|
|
192
|
+
*
|
|
193
|
+
* Catalog serialization is a different ownership surface from the native
|
|
194
|
+
* coordinator N: `K -> C` is a legal order and `N -> K` nests, so sharing one
|
|
195
|
+
* database would make the required nesting self-contend. The two live in
|
|
196
|
+
* sibling directories under the same per-user runtime root — same identity
|
|
197
|
+
* namespace, same environment-independent parent, distinct exclusion.
|
|
198
|
+
*
|
|
199
|
+
* Consumers use the returned path verbatim and append nothing
|
|
200
|
+
* (`005_contract.md:1256-1330`).
|
|
201
|
+
*/
|
|
202
|
+
export const resolveCodexCatalogSerializationDatabasePath:
|
|
203
|
+
ResolveCodexCatalogSerializationDatabasePath = (identity, canonicalCodexHome) => {
|
|
204
|
+
if (!isAbsolute(canonicalCodexHome)) {
|
|
205
|
+
refuse("The canonical CODEX_HOME must be an absolute path.");
|
|
206
|
+
}
|
|
207
|
+
const root = identity.platform === "posix"
|
|
208
|
+
? resolvePosixRuntimeRoot(identity.uid)
|
|
209
|
+
: resolveWindowsRuntimeRoot(identity);
|
|
210
|
+
const locks = join(root, "catalog-write-locks");
|
|
211
|
+
if (identity.platform === "posix") ensurePrivatePosixDirectory(locks, identity.uid);
|
|
212
|
+
else {
|
|
213
|
+
try {
|
|
214
|
+
mkdirSync(locks, { recursive: true });
|
|
215
|
+
} catch (cause) {
|
|
216
|
+
refuse("The Windows catalog serialization directory cannot be created.", cause);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const homeDigest = createHash("sha256").update(canonicalCodexHome).digest("hex");
|
|
221
|
+
return join(locks, `${homeDigest}.sqlite`);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* H's FINAL database path.
|
|
226
|
+
*
|
|
227
|
+
* Keyed by the canonical state DB in addition to the canonical home, unlike N and
|
|
228
|
+
* K. One `CODEX_HOME` can name a different `state_5.sqlite` — `model_catalog_json`
|
|
229
|
+
* has the same shape of indirection for catalogs — and two operations against
|
|
230
|
+
* different history databases are not the same exclusion. Hashing only the home
|
|
231
|
+
* would serialize them together, and hashing the raw request path would let two
|
|
232
|
+
* spellings of one database take different locks.
|
|
233
|
+
*/
|
|
234
|
+
export const resolveCodexHistorySerializationDatabasePath:
|
|
235
|
+
ResolveCodexHistorySerializationDatabasePath = (
|
|
236
|
+
identity,
|
|
237
|
+
canonicalCodexHome,
|
|
238
|
+
canonicalStateDbPath,
|
|
239
|
+
) => {
|
|
240
|
+
if (!isAbsolute(canonicalCodexHome)) {
|
|
241
|
+
refuse("The canonical CODEX_HOME must be an absolute path.");
|
|
242
|
+
}
|
|
243
|
+
if (!isAbsolute(canonicalStateDbPath)) {
|
|
244
|
+
refuse("The canonical Codex state database must be an absolute path.");
|
|
245
|
+
}
|
|
246
|
+
const root = identity.platform === "posix"
|
|
247
|
+
? resolvePosixRuntimeRoot(identity.uid)
|
|
248
|
+
: resolveWindowsRuntimeRoot(identity);
|
|
249
|
+
const locks = join(root, "history-write-locks");
|
|
250
|
+
if (identity.platform === "posix") ensurePrivatePosixDirectory(locks, identity.uid);
|
|
251
|
+
else {
|
|
252
|
+
try {
|
|
253
|
+
mkdirSync(locks, { recursive: true });
|
|
254
|
+
} catch (cause) {
|
|
255
|
+
refuse("The Windows history serialization directory cannot be created.", cause);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Both components are length-prefixed so no pair of (home, stateDb) values can
|
|
260
|
+
// collide by concatenation.
|
|
261
|
+
const digest = createHash("sha256")
|
|
262
|
+
.update(`${canonicalCodexHome.length}:${canonicalCodexHome}`)
|
|
263
|
+
.update(`${canonicalStateDbPath.length}:${canonicalStateDbPath}`)
|
|
264
|
+
.digest("hex");
|
|
265
|
+
return join(locks, `${digest}.sqlite`);
|
|
266
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The witness a native Codex write is coordinated by.
|
|
3
|
+
*
|
|
4
|
+
* NOT an `AdmissionSnapshot`, and the difference is the whole reason this file
|
|
5
|
+
* exists. `admitCodexWrite` refuses before it constructs a snapshot unless
|
|
6
|
+
* ownership is `owned` (`admission.ts`), so every snapshot it produces carries
|
|
7
|
+
* `owned` — the field cannot hold `unknown` at all. Reusing that type here would
|
|
8
|
+
* have meant one of two untruths: either the call becomes gated on ownership,
|
|
9
|
+
* which refuses Codex injection on every Windows machine and on Linux without a
|
|
10
|
+
* reachable user bus, or a hand-built snapshot claims an admission that never
|
|
11
|
+
* happened.
|
|
12
|
+
*
|
|
13
|
+
* So this type authorizes THE WRITE, not the DECISION to write. Deciding whether
|
|
14
|
+
* a write should happen at all is admission's job and belongs to its own phase,
|
|
15
|
+
* behind the Windows definition-chain walk.
|
|
16
|
+
*
|
|
17
|
+
* What the id covers, and why it is the OUTPUT rather than the inputs: the bytes
|
|
18
|
+
* `injectCodexConfig` emits depend on the port, the resolved catalog path, the
|
|
19
|
+
* hostname, websocket mode, legacy mode and the managed sub-agent defaults. An
|
|
20
|
+
* enumeration of those was already incomplete once. Hashing the computed
|
|
21
|
+
* candidate bytes closes the class instead of the instance — an input that
|
|
22
|
+
* changes the output changes the id whether or not anyone remembered to list it.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
|
|
26
|
+
export interface CodexWriteCandidate {
|
|
27
|
+
/** The exact string about to replace `config.toml`. */
|
|
28
|
+
readonly configBytes: string;
|
|
29
|
+
/** The exact string about to replace the profile. */
|
|
30
|
+
readonly profileBytes: string;
|
|
31
|
+
/** The RESOLVED catalog path, never the raw option. */
|
|
32
|
+
readonly catalogPath: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CodexWriteEvidence {
|
|
36
|
+
/** sha256 of the `config.toml` bytes this operation read as its input. */
|
|
37
|
+
readonly nativeInputIdentity: string;
|
|
38
|
+
/** Digest of the persisted OpenCodex config these bytes were derived from. */
|
|
39
|
+
readonly persistedIdentity: string;
|
|
40
|
+
/** Generation as observed before the lock; `present:false` means no coordinator yet. */
|
|
41
|
+
readonly generation: Readonly<{ present: boolean; value: number }>;
|
|
42
|
+
/** Content identity of the journal, or a stable marker for absence. */
|
|
43
|
+
readonly journalIdentity: string;
|
|
44
|
+
readonly canonicalTargets: Readonly<{
|
|
45
|
+
config: string;
|
|
46
|
+
profile: string;
|
|
47
|
+
journal: string;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CodexWriteCoordination {
|
|
52
|
+
readonly candidate: CodexWriteCandidate;
|
|
53
|
+
readonly evidence: CodexWriteEvidence;
|
|
54
|
+
/**
|
|
55
|
+
* Recorded context, deliberately OUTSIDE `comparisonId`.
|
|
56
|
+
*
|
|
57
|
+
* The lock compares one id and nothing else, so a field that is copied rather
|
|
58
|
+
* than re-observed detects no drift by being in it — it would only match
|
|
59
|
+
* itself. Ownership is not re-observed under the lock (that would mean running
|
|
60
|
+
* a service-manager subprocess while N and C are held), so it travels as
|
|
61
|
+
* something the operation SAW, not as something the comparison PROVES.
|
|
62
|
+
*/
|
|
63
|
+
readonly observedOwnership: "owned" | "foreign" | "unknown";
|
|
64
|
+
readonly comparisonId: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Collapse a generation to one token.
|
|
69
|
+
*
|
|
70
|
+
* Absent and present-zero are the same authority — no cooperating write has
|
|
71
|
+
* committed — and they cannot be observed the same way: before the lock only
|
|
72
|
+
* absence is visible, and inside it only the committed zero is. Without this
|
|
73
|
+
* they never match and every first write refuses.
|
|
74
|
+
*/
|
|
75
|
+
function generationToken(generation: CodexWriteEvidence["generation"]): string {
|
|
76
|
+
const { present, value } = generation;
|
|
77
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
78
|
+
throw new TypeError(`A config generation must be a non-negative safe integer, got ${String(value)}.`);
|
|
79
|
+
}
|
|
80
|
+
return present && value > 0 ? `gen:${value}` : "gen:0";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function hashCodexWriteCoordination(
|
|
84
|
+
candidate: CodexWriteCandidate,
|
|
85
|
+
evidence: CodexWriteEvidence,
|
|
86
|
+
): string {
|
|
87
|
+
return createHash("sha256")
|
|
88
|
+
.update(JSON.stringify([
|
|
89
|
+
// The output, hashed directly. Two operations that intend different bytes
|
|
90
|
+
// cannot share an id, however they came to differ.
|
|
91
|
+
createHash("sha256").update(candidate.configBytes).digest("hex"),
|
|
92
|
+
createHash("sha256").update(candidate.profileBytes).digest("hex"),
|
|
93
|
+
candidate.catalogPath,
|
|
94
|
+
evidence.nativeInputIdentity,
|
|
95
|
+
evidence.persistedIdentity,
|
|
96
|
+
generationToken(evidence.generation),
|
|
97
|
+
evidence.journalIdentity,
|
|
98
|
+
evidence.canonicalTargets,
|
|
99
|
+
]))
|
|
100
|
+
.digest("hex");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function codexWriteCoordination(
|
|
104
|
+
candidate: CodexWriteCandidate,
|
|
105
|
+
evidence: CodexWriteEvidence,
|
|
106
|
+
observedOwnership: CodexWriteCoordination["observedOwnership"],
|
|
107
|
+
): CodexWriteCoordination {
|
|
108
|
+
return {
|
|
109
|
+
candidate,
|
|
110
|
+
evidence,
|
|
111
|
+
observedOwnership,
|
|
112
|
+
comparisonId: hashCodexWriteCoordination(candidate, evidence),
|
|
113
|
+
};
|
|
114
|
+
}
|