@unblocklabs/unblock-codex-prompt 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unblock Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # Unblock Codex Prompt
2
+
3
+ A small, explicit prompt-management bridge for dedicated OpenClaw Mac agents.
4
+ **Version 0.1.0.** The frozen bridge is pinned to OpenClaw/Codex plugin 2026.9.2.
5
+
6
+ ## What you control
7
+
8
+ - Optional `promptUrl`: a shared policy from Fleet Prompt, replacing both bundled
9
+ policy files below. Agent-specific identity still comes from local documents.
10
+ - `prompts/system.md`: the shared Codex operating contract, with an optional name.
11
+ - `prompts/developer.md`: the reviewed OpenClaw integration policy.
12
+ - Each agent's local snapshot of `SOUL.md`, `IDENTITY.md`, `USER.md`, its eligible
13
+ model-visible OpenClaw skills catalog, and a memory reference (not memory bodies).
14
+
15
+ The snapshot replaces Codex's base instructions through `model_instructions_file`.
16
+ With `frozenContext` enabled, a narrowly patched Codex adapter replaces the generic
17
+ OpenClaw developer policy and suppresses only the soul, memory and OpenClaw-skills
18
+ contributions to collaboration instructions. Default-mode and cron guidance remain.
19
+
20
+ **Still live and untouched:** native AGENTS.md, native skills, tool schemas and
21
+ executor protocol, sandbox/approvals, native collaboration, channel/time/routing
22
+ context and user messages. `extraSystemPrompt` is retained. This is not total
23
+ prompt ownership; other plugins/hooks can still add instructions.
24
+
25
+ Customer documents stay on each agent's machine, outside this package. Snapshot
26
+ files use private permissions. Missing identity documents are recorded as absent.
27
+
28
+ ## Compatibility
29
+
30
+ - Frozen bridge: **OpenClaw 2026.9.2 and @openclaw/codex 2026.9.2 only**. The exact
31
+ original adapter SHA-256 is checked before patching. Unknown builds are refused.
32
+ - Managed local stdio Codex, agent-scoped home, no custom command/arguments.
33
+ - One target agent per installation. Other agents use upstream behavior. Native
34
+ children may inherit the parent's frozen context.
35
+
36
+ This bridge uses two private upstream seams because public prompt hooks cannot
37
+ fix the dropped-context path: one skill-catalog builder and two entry points in
38
+ one Codex adapter bundle. It is deliberately version-pinned, not upgrade-proof.
39
+ The catalog uses OpenClaw's eligibility, allowlist, visibility and size-limit
40
+ logic. Session-specific and execution-directory skills are not snapshotted.
41
+ Remote-node-only skills are not added; this is for dedicated local Mac agents.
42
+
43
+ ## Install and enroll
44
+
45
+ ```sh
46
+ npm ci
47
+ npm run preflight
48
+ npm pack
49
+ openclaw plugins install ./unblocklabs-unblock-codex-prompt-0.1.0.tgz --force --accept-capabilities
50
+ ```
51
+
52
+ Include the plugin in `plugins.allow` if applicable, then configure:
53
+
54
+ ```json5
55
+ {
56
+ plugins: {
57
+ entries: {
58
+ "unblock-codex-prompt": {
59
+ enabled: true,
60
+ config: {
61
+ agentId: "main", agentName: "Bill", frozenContext: true,
62
+ promptUrl: "https://fleet-prompt.unblocklabs.ai/prompt?channel=dev"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ `agentId` defaults to `main`; `agentName` is optional. `frozenContext` defaults to
71
+ false for existing base-prompt-only users. No conversation-access hook grant is
72
+ needed. After initial enrollment, `promptUrl` enables daily shared-prompt refresh
73
+ and overdue startup catch-up. Startup never rebuilds local context or patches the
74
+ adapter. The optional model-catalog service can also refresh metadata.
75
+
76
+ `promptUrl` requires `frozenContext`. Use `channel=dev` for Bill and `channel=prod`
77
+ for approved fleet deployments. The URL must use HTTPS without embedded credentials
78
+ or a fragment. Sync fetches without authentication, refuses redirects, and checks
79
+ plain-text content, a 256 KiB limit, UTF-8, and the endpoint's SHA-256
80
+ `X-Prompt-Revision` header, with a ten-second request timeout. These integrity
81
+ checks are not a signature; the configured HTTPS publisher controls the policy.
82
+
83
+ The remote text replaces the bundled Codex contract; a short developer-layer
84
+ pointer replaces OpenClaw's generic developer policy instead of retaining the old
85
+ `prompts/developer.md` policy copy. The frozen local context is still appended.
86
+ No local documents are sent to the Worker. The installed snapshot is the local
87
+ last-known-good copy: a failed fetch aborts sync before writes, never silently
88
+ falls back to a bundled prompt, and does not disrupt normal turns. Status shows
89
+ the installed URL/revision separately from the configured URL, without fetching.
90
+
91
+ Back up configuration and drain active work. Inspect the installed official
92
+ Codex package location, then use that exact directory:
93
+
94
+ ```sh
95
+ openclaw plugins inspect codex --json
96
+ openclaw codex-prompt sync --adopt --codex-plugin-dir /absolute/path/to/@openclaw/codex
97
+ openclaw codex-prompt status
98
+ ```
99
+
100
+ `--adopt` explicitly takes ownership of an existing custom prompt pointer. Its
101
+ previous value is retained for restoration; leave the original file in place.
102
+ First frozen sync compiles local documents/catalog and installs the guarded
103
+ adapter patch with a private original-source backup. Later syncs reuse its receipt.
104
+
105
+ ## Refresh and activate
106
+
107
+ ```sh
108
+ openclaw codex-prompt sync
109
+ openclaw codex-prompt status
110
+ ```
111
+
112
+ Only explicit `sync` rebuilds local documents, skills, and the adapter policy.
113
+ With `promptUrl`, the daily service fetches the configured Worker channel and
114
+ replaces only the shared prompt prefix when its content changes. The existing
115
+ frozen context is preserved byte-for-byte, without rereading workspace files.
116
+ Editing a source document or restarting does not rebuild that local snapshot.
117
+ Skill bodies remain files the model reads when triggered; their contents are not
118
+ frozen. The catalog and its selection are frozen.
119
+
120
+ **After sync, drain work, restart the Gateway/managed app-server, and use new
121
+ conversations.** Existing native threads may retain earlier base instructions.
122
+ The plugin never interrupts or resets sessions. Capture an actual request after
123
+ provisioning or upgrades; `status` is disk evidence, not live adoption.
124
+
125
+ The patched assembly boundary validates enrollment, pointer and compiled prompt
126
+ hash before replacing/suppressing context. Invalid state aborts assembly.
127
+ This guard does not attest to an old thread's retained prompt or detect every
128
+ possible higher-priority Codex config override. An upgrade replacing the adapter
129
+ removes the guard; startup status is diagnostic, not a global inference gate.
130
+ Restore before upgrading, then revalidate compatibility before reenrollment.
131
+
132
+ ### Daily shared-prompt refresh
133
+
134
+ No additional setting is needed beyond an enrolled `promptUrl`. The service checks
135
+ every 24 hours while the Gateway runs and catches up when overdue on startup.
136
+ Use `openclaw codex-prompt refresh` to run the same remote-prompt and enabled
137
+ model-catalog refresh immediately, without resnapshotting local context.
138
+
139
+ Both components are attempted independently: a catalog outage cannot prevent a
140
+ prompt update, or vice versa. Failed fetches or validation keep the installed
141
+ copy; failures retry hourly. Unchanged prompt content is not rewritten. Status
142
+ includes the remote revision and last successful `checkedAt` timestamp. Updates
143
+ are intended for fresh conversations; existing conversations are left alone.
144
+ The plugin never restarts the Gateway automatically.
145
+
146
+ Existing remote snapshots migrate their shared/context boundary without rereading
147
+ agent files. Ambiguous or externally modified snapshots are refused rather than
148
+ guessed; inspect them and use explicit `sync` to rebuild when appropriate.
149
+
150
+ ## Optional daily model-catalog refresh
151
+
152
+ Set `suppressExplicitDelegationPrompt: true` alongside `frozenContext: true`, then
153
+ run `openclaw codex-prompt sync` (or `openclaw codex-prompt refresh-catalog` for
154
+ catalog-only updates on an already enrolled agent).
155
+
156
+ The plugin fetches the configured Codex provider's `/models` endpoint using its
157
+ existing `env_key` credential. The credential must be available in the OpenClaw
158
+ process environment; OAuth-only/command-auth providers are not implemented. The
159
+ endpoint must return a full Codex `models` catalog, not only an OpenAI-style
160
+ `data` list. The managed model cache supplies only the client-version query
161
+ parameter, never the catalog contents.
162
+
163
+ The plugin preserves upstream model fields and sets only
164
+ `model_messages.multi_agent.mode.explicit` to an empty string on each model. It
165
+ writes `unblock-codex-models.json` in the managed Codex home and sets the top-level
166
+ `model_catalog_json` pointer. It refuses to overwrite another owner's pointer.
167
+ Manual edits to this generated catalog are intentionally replaced on refresh.
168
+
169
+ While the Gateway is running, the service refreshes every 24 hours and catches
170
+ up if overdue on startup. Failures retain the installed catalog and retry hourly;
171
+ there is no fallback to stale cached metadata. Stopping the service cancels its
172
+ timer and waits for an in-flight refresh. Status exposes last/next refresh times
173
+ and verifies the local catalog hash without fetching. A successful catalog-only
174
+ `refresh-catalog` never changes the fleet prompt or local context snapshot. The
175
+ daily service and `refresh` also check the shared prompt when `promptUrl` is set.
176
+
177
+ Restart the Gateway after first activating the catalog pointer. On the validated
178
+ managed Codex 0.153.4 build, a fresh conversation adopted refreshed catalog
179
+ contents without a Gateway restart. Content-only updates report
180
+ `newSessionRequired: true`, but `restartRequired: false`. Existing conversations
181
+ are not reset, and their adoption is not guaranteed. The plugin never restarts
182
+ the Gateway automatically; revalidate this behavior when upgrading Codex.
183
+
184
+ ## Restore
185
+
186
+ ```sh
187
+ openclaw codex-prompt restore
188
+ openclaw codex-prompt status
189
+ ```
190
+
191
+ Restore recovers exact original adapter bytes, deactivates enrollment and restores
192
+ the previous TOML pointer. It refuses foreign edits and retains backup, receipt
193
+ and generated files. Restart and use fresh conversations to activate it.
194
+ Restore **before** disabling/uninstalling or changing target agents. Disabling
195
+ without restoring leaves the frozen base but re-enables live injections.
196
+
197
+ ## Safety and scope
198
+
199
+ - No model-provider endpoint, model, credential or permission changes. Explicit
200
+ sync/daily refresh can download the public prompt; opt-in catalog refresh uses the existing
201
+ provider authentication path. Credentials are never written into catalog files.
202
+ - Parsed TOML edits preserve unrelated bytes. Regular-file checks, atomic swaps,
203
+ concurrency checks and a prompt sync lock protect updates. There is no
204
+ cross-file transaction; inspect a failed sync and rerun after resolving errors.
205
+ - Do not run concurrent sync/install/restore operations. If a crash left an empty
206
+ lock directory, confirm no sync is running before removing it.
207
+ - A prompt guides behavior; it is not a security boundary or obedience guarantee.
208
+ - Frozen prompts do not inherit upstream base-policy improvements. Review upgrades.
209
+ - Never commit customer snapshots, raw captures, credentials or host backups.
210
+
211
+ ## Development and release
212
+
213
+ ```sh
214
+ npm run preflight
215
+ npm run release:check -- v0.1.0
216
+ ```
217
+
218
+ Tests cover compilation, explicit refresh, native/private-body exclusion,
219
+ target/disabled/restored behavior, cron/default preservation, malformed state,
220
+ pointer ownership and foreign edits. See [VALIDATION.md](VALIDATION.md) for live
221
+ proof and limits.
222
+
223
+ Install with
224
+ `openclaw plugins install npm:@unblocklabs/unblock-codex-prompt@0.1.0 --accept-capabilities`.
225
+ The existing [GitHub Release workflow](RELEASING.md) publishes npm only when a
226
+ GitHub Release is published. Normal pushes do not release the package.
227
+
228
+ References: [Codex configuration](https://developers.openai.com/codex/config-reference),
229
+ [OpenClaw Codex hook boundaries](https://docs.openclaw.ai/plugins/codex-harness-runtime/hooks).
package/RELEASING.md ADDED
@@ -0,0 +1,72 @@
1
+ # Releasing
2
+
3
+ Publishing requires explicit owner approval.
4
+
5
+ This mirrors the `unblock-memory` release-on-release workflow: normal pushes and
6
+ pull requests run CI only. A **published GitHub Release** runs npm publication.
7
+ Do not create a release merely to test the workflow.
8
+
9
+ ## One-time npm setup
10
+
11
+ The package is `@unblocklabs/unblock-codex-prompt` with public access.
12
+
13
+ - Verify publishing rights to the `@unblocklabs` scope.
14
+ - For the first publish, configure an appropriately scoped `NPM_TOKEN` repository
15
+ secret if npm trusted publishing cannot yet be configured for the new package.
16
+ Do not put a token in source or copy a host's `.npmrc` into the repository.
17
+ - Configure npm's GitHub Actions trusted publisher for organization
18
+ `unblocklabs-ai`, repository `unblock-codex-prompt`, workflow `release.yml`.
19
+ The workflow uses Node 24, npm 11.13.0, `id-token: write`, and provenance.
20
+ Once OIDC is configured, the token can be removed.
21
+ - Without valid publishing credentials/trust, the release job fails; it does not
22
+ report a successful release while skipping npm.
23
+
24
+ If first-package bootstrapping is needed, an authenticated owner can publish the
25
+ reviewed version once with `npm publish --access public`, then configure trust:
26
+
27
+ ```sh
28
+ npm trust github @unblocklabs/unblock-codex-prompt --repo unblocklabs-ai/unblock-codex-prompt --file release.yml --yes
29
+ ```
30
+
31
+ The Release workflow verifies the registry's tarball integrity against its own
32
+ packed artifact before accepting an already-published version. Different bytes
33
+ fail the job. Subsequent new versions publish through OIDC with provenance; a
34
+ local bootstrap publication does not have GitHub Actions provenance.
35
+
36
+ ClawHub publishing is not configured.
37
+
38
+ ## Release checklist
39
+
40
+ 1. Update `package.json`, `package-lock.json` (including its root package), and
41
+ `openclaw.plugin.json` to the same version.
42
+ 2. Review the prompt diff separately from code changes. Render Bill's name and
43
+ verify a real fresh-session Responses request after meaningful prompt or
44
+ compatibility changes. Keep captures private; publish only sanitized findings.
45
+ 3. Run `npm ci`, `npm run preflight`, and `npm run release:check -- vX.Y.Z`.
46
+ 4. Commit and push reviewed changes to `main`; require passing CI.
47
+ 5. With explicit release approval, publish a GitHub Release `vX.Y.Z` targeting
48
+ `main`. Use release notes that explain user-visible changes and activation.
49
+ 6. Watch the Release workflow. Verify local/main/tag commit parity and
50
+ `npm view @unblocklabs/unblock-codex-prompt@X.Y.Z version dist.integrity`.
51
+ 7. Update one fleet node, sync, restart safely, and verify before wider rollout.
52
+
53
+ The workflow checks release-tag ancestry and all three version-bearing files,
54
+ runs preflight, then publishes with `latest` (or `next` for a prerelease).
55
+
56
+ ## Private request-capture protocol
57
+
58
+ Use a localhost-only Responses receiver and a new capture directory, mode 0700.
59
+ Never capture real user turns intentionally; use dedicated no-delivery sessions.
60
+ Back up the managed Codex TOML privately before temporarily changing the provider
61
+ endpoint. Preserve credentials and unrelated settings, and arrange restoration
62
+ even if the test fails.
63
+
64
+ Capture template A, make one unmistakable but harmless temporary template change,
65
+ sync, and capture template B in a **different native thread**. Assert the rendered
66
+ base prompt appears exactly once (allowing Codex's outer-whitespace trimming),
67
+ the marker changes, and OpenClaw developer guidance still exists separately.
68
+ Inspect both `instructions` and developer text items in `input`: Bill's current
69
+ Codex build carries the base prompt in `input`, not `instructions`. A synthetic receiver
70
+ proves wire composition, not model obedience. Restore the canonical template and
71
+ real endpoint, verify a real model turn, and check Gateway health. Never commit
72
+ the marker, raw captures, local ports/PIDs, private config, or test conversations.
@@ -0,0 +1,37 @@
1
+ type Receipt = {
2
+ schema: 1;
3
+ packageDir: string;
4
+ path: string;
5
+ originalSha256: string;
6
+ patchedSha256: string;
7
+ };
8
+ export declare function readReceipt(agentDir: string): Promise<Receipt | null>;
9
+ export declare function patchAdapterSource(source: string, agentDir: string, agentId: string, runtimePath: string): string;
10
+ export declare function prepareAdapter(agentDir: string, agentId: string, suppliedDir?: string): Promise<{
11
+ path: string;
12
+ current: string;
13
+ original: string;
14
+ patched: string;
15
+ receipt: {
16
+ schema: 1;
17
+ packageDir: string;
18
+ path: string;
19
+ originalSha256: string;
20
+ patchedSha256: string;
21
+ };
22
+ }>;
23
+ export declare function installAdapter(agentDir: string, plan: Awaited<ReturnType<typeof prepareAdapter>>): Promise<{
24
+ adapterChanged: boolean;
25
+ restartRequired: boolean;
26
+ }>;
27
+ export declare function adapterStatus(agentDir: string): Promise<{
28
+ installed: boolean;
29
+ path?: never;
30
+ supportedVersion?: never;
31
+ } | {
32
+ installed: boolean;
33
+ path: string;
34
+ supportedVersion: string;
35
+ }>;
36
+ export declare function restoreAdapter(agentDir: string): Promise<void>;
37
+ export {};
@@ -0,0 +1,104 @@
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { readOptional, replaceFile, sha256 } from "./manager.js";
5
+ import { SUPPORTED_OPENCLAW, openclawRoot } from "./compiler.js";
6
+ const ORIGINAL_SHA256 = "344d4a6332d59b5eb1d7551d6d308563f339d5d2251809c8f25e8cec04ede342";
7
+ const MARKER = "// unblock-codex-prompt bridge v1";
8
+ const developer = "function buildDeveloperInstructions(params, options = {}) {";
9
+ const collaboration = "function buildTurnScopedCollaborationInstructions(params, options = {}) {";
10
+ function receiptPath(agentDir) { return join(agentDir, "codex-home", ".unblock-codex-prompt-adapter.json"); }
11
+ function backupPath(agentDir) { return join(agentDir, "codex-home", ".unblock-codex-prompt-adapter-original.js"); }
12
+ export async function readReceipt(agentDir) {
13
+ const source = await readOptional(receiptPath(agentDir));
14
+ if (source === null)
15
+ return null;
16
+ const value = JSON.parse(source);
17
+ if (!value || typeof value !== "object" || !("schema" in value) || value.schema !== 1 ||
18
+ !("packageDir" in value) || typeof value.packageDir !== "string" ||
19
+ !("path" in value) || typeof value.path !== "string" ||
20
+ !("originalSha256" in value) || value.originalSha256 !== ORIGINAL_SHA256 ||
21
+ !("patchedSha256" in value) || typeof value.patchedSha256 !== "string" ||
22
+ value.path !== join(value.packageDir, "dist", "thread-lifecycle-DPK30jad.js"))
23
+ throw new Error("Invalid adapter receipt");
24
+ return { schema: 1, packageDir: value.packageDir, path: value.path, originalSha256: value.originalSha256, patchedSha256: value.patchedSha256 };
25
+ }
26
+ export function patchAdapterSource(source, agentDir, agentId, runtimePath) {
27
+ if (source.includes(MARKER) || source.split(developer).length !== 2 || source.split(collaboration).length !== 2)
28
+ throw new Error("Unsupported Codex adapter shape");
29
+ const prefix = `${MARKER}
30
+ import { createRequire as unblockPromptCreateRequire } from "node:module";
31
+ const unblockPromptRequire = unblockPromptCreateRequire(import.meta.url);
32
+ function unblockFrozenPolicy(params) {
33
+ const plugins = params.config?.plugins;
34
+ const entry = plugins?.entries?.["unblock-codex-prompt"];
35
+ if (plugins?.enabled === false || entry?.enabled !== true || entry.config?.frozenContext !== true ||
36
+ (Array.isArray(plugins.allow) && !plugins.allow.includes("unblock-codex-prompt"))) return null;
37
+ const target = entry.config?.agentId ?? "main";
38
+ if (params.agentId !== target) return null;
39
+ if (target !== ${JSON.stringify(agentId)}) throw new Error("Frozen bridge target changed; restore and reinstall the adapter");
40
+ return unblockPromptRequire(${JSON.stringify(runtimePath)}).readFrozenPolicy(${JSON.stringify(agentDir)});
41
+ }
42
+ `;
43
+ return prefix + source.replace(developer, `${developer}
44
+ \tconst unblockPolicy = unblockFrozenPolicy(params);
45
+ \tif (unblockPolicy !== null) return [unblockPolicy, params.extraSystemPrompt].filter(section => typeof section === "string" && section.trim()).join("\\n\\n");`)
46
+ .replace(collaboration, `${collaboration}
47
+ \tif (unblockFrozenPolicy(params) !== null) return params.trigger === "cron" ? buildCronCollaborationInstructions() : buildDefaultCollaborationInstructions();`);
48
+ }
49
+ export async function prepareAdapter(agentDir, agentId, suppliedDir) {
50
+ await openclawRoot();
51
+ const receipt = await readReceipt(agentDir);
52
+ const packageDir = suppliedDir ? resolve(suppliedDir) : receipt?.packageDir;
53
+ if (!packageDir)
54
+ throw new Error("First frozen sync requires --codex-plugin-dir from openclaw plugins inspect codex --json");
55
+ if (receipt && receipt.packageDir !== packageDir)
56
+ throw new Error("Restore the previous adapter before changing Codex installations");
57
+ const pkg = JSON.parse(await readFile(join(packageDir, "package.json"), "utf8"));
58
+ if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== "@openclaw/codex" || !("version" in pkg) || pkg.version !== SUPPORTED_OPENCLAW)
59
+ throw new Error(`Frozen bridge requires @openclaw/codex ${SUPPORTED_OPENCLAW}`);
60
+ const files = (await readdir(join(packageDir, "dist"))).filter(name => /^thread-lifecycle-[\w-]+\.js$/u.test(name));
61
+ if (files.length !== 1 || files[0] !== "thread-lifecycle-DPK30jad.js")
62
+ throw new Error("Unsupported Codex bundle layout");
63
+ const path = join(packageDir, "dist", files[0]);
64
+ const current = await readOptional(path);
65
+ if (current === null)
66
+ throw new Error("Missing Codex adapter");
67
+ const original = receipt ? await readOptional(backupPath(agentDir)) : current;
68
+ if (original === null || sha256(original) !== ORIGINAL_SHA256)
69
+ throw new Error("Unknown Codex adapter bytes; refusing to patch");
70
+ if (receipt && sha256(current) !== receipt.patchedSha256 && current !== original)
71
+ throw new Error("Codex adapter changed by another writer; refusing to overwrite");
72
+ const patched = patchAdapterSource(original, agentDir, agentId, fileURLToPath(new URL("./runtime.js", import.meta.url)));
73
+ return { path, current, original, patched, receipt: { schema: 1, packageDir, path, originalSha256: ORIGINAL_SHA256, patchedSha256: sha256(patched) } };
74
+ }
75
+ export async function installAdapter(agentDir, plan) {
76
+ const backup = await readOptional(backupPath(agentDir));
77
+ if (backup !== null && backup !== plan.original)
78
+ throw new Error("Foreign adapter backup");
79
+ await replaceFile(backupPath(agentDir), backup, plan.original);
80
+ // Receipt first allows crash recovery; source swap is atomic and guarded.
81
+ await replaceFile(receiptPath(agentDir), await readOptional(receiptPath(agentDir)), `${JSON.stringify(plan.receipt, null, 2)}\n`);
82
+ return { adapterChanged: await replaceFile(plan.path, plan.current, plan.patched), restartRequired: true };
83
+ }
84
+ export async function adapterStatus(agentDir) {
85
+ const receipt = await readReceipt(agentDir);
86
+ if (!receipt)
87
+ return { installed: false };
88
+ const current = await readOptional(receipt.path);
89
+ return { installed: current !== null && sha256(current) === receipt.patchedSha256, path: receipt.path, supportedVersion: SUPPORTED_OPENCLAW };
90
+ }
91
+ export async function restoreAdapter(agentDir) {
92
+ const receipt = await readReceipt(agentDir);
93
+ if (!receipt)
94
+ return;
95
+ const current = await readOptional(receipt.path);
96
+ const original = await readOptional(backupPath(agentDir));
97
+ if (original === null || sha256(original) !== receipt.originalSha256)
98
+ throw new Error("Adapter backup invalid");
99
+ if (current === original)
100
+ return;
101
+ if (current === null || sha256(current) !== receipt.patchedSha256)
102
+ throw new Error("Foreign adapter edit; refusing restore");
103
+ await replaceFile(receipt.path, current, original);
104
+ }
@@ -0,0 +1,51 @@
1
+ export declare const CATALOG_REFRESH_MS: number;
2
+ export declare function patchCatalog(value: unknown): {
3
+ models: {
4
+ model_messages: {
5
+ multi_agent: {
6
+ mode: {
7
+ explicit: string;
8
+ };
9
+ };
10
+ };
11
+ }[];
12
+ };
13
+ /** Use the configured provider, not the override file or its refreshable cache. */
14
+ export declare function fetchUpstreamCatalog(agentDir: string, configText: string, request?: typeof fetch): Promise<unknown>;
15
+ export declare function catalogStatus(agentDir: string): Promise<{
16
+ enabled: boolean;
17
+ catalogPath: string;
18
+ pointerMatches: boolean;
19
+ contentMatches: boolean;
20
+ refreshedAt: number | null;
21
+ nextRefreshAt: number | null;
22
+ modelCount: number;
23
+ }>;
24
+ export declare function refreshCatalog(agentDir: string, options?: {
25
+ automatic?: boolean;
26
+ now?: number;
27
+ fetchCatalog?: typeof fetchUpstreamCatalog;
28
+ }): Promise<{
29
+ skipped: boolean;
30
+ changed: boolean;
31
+ catalogChanged?: never;
32
+ configChanged?: never;
33
+ modelCount?: never;
34
+ refreshedAt?: never;
35
+ sha256?: never;
36
+ restartRequired?: never;
37
+ newSessionRequired?: never;
38
+ } | {
39
+ changed: boolean;
40
+ catalogChanged: boolean;
41
+ configChanged: boolean;
42
+ modelCount: number;
43
+ refreshedAt: number;
44
+ sha256: string;
45
+ restartRequired: boolean;
46
+ newSessionRequired: boolean;
47
+ skipped?: never;
48
+ }>;
49
+ export declare function restoreCatalog(agentDir: string): Promise<void>;
50
+ /** Start promptly if overdue, persist successful refresh time, retry failures hourly. */
51
+ export declare function startCatalogRefresh(run: () => Promise<void>, nextAt: number, onError: () => void): () => Promise<void>;