@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 +21 -0
- package/README.md +229 -0
- package/RELEASING.md +72 -0
- package/dist/adapter.d.ts +37 -0
- package/dist/adapter.js +104 -0
- package/dist/catalog.d.ts +51 -0
- package/dist/catalog.js +185 -0
- package/dist/compiler.d.ts +23 -0
- package/dist/compiler.js +57 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +48 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +153 -0
- package/dist/manager.d.ts +80 -0
- package/dist/manager.js +218 -0
- package/dist/remote.d.ts +9 -0
- package/dist/remote.js +44 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +30 -0
- package/dist/toml.d.ts +3 -0
- package/dist/toml.js +49 -0
- package/openclaw.plugin.json +25 -0
- package/package.json +44 -0
- package/prompts/developer.md +22 -0
- package/prompts/system.md +173 -0
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 {};
|
package/dist/adapter.js
ADDED
|
@@ -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>;
|