@unblocklabs/unblock-codex-prompt 0.2.0 → 0.2.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/README.md +11 -7
- package/dist/adapter.d.ts +28 -13
- package/dist/adapter.js +147 -69
- package/dist/index.js +7 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Unblock Codex Prompt
|
|
2
2
|
|
|
3
3
|
A small, explicit prompt-management bridge for dedicated OpenClaw Mac agents.
|
|
4
|
-
**Version 0.2.
|
|
4
|
+
**Version 0.2.1.** The frozen bridge supports OpenClaw/Codex 2026.9.2 and newer
|
|
5
5
|
stable releases with a compatible context-assembly layout.
|
|
6
6
|
|
|
7
7
|
## What you control
|
|
@@ -29,7 +29,8 @@ files use private permissions. Missing identity documents are recorded as absent
|
|
|
29
29
|
## Compatibility
|
|
30
30
|
|
|
31
31
|
- Frozen bridge: **OpenClaw and @openclaw/codex >=2026.9.2**. Reviewed layouts cover
|
|
32
|
-
2026.9.2 and 2026.9.
|
|
32
|
+
2026.9.2, 2026.9.3, 2026.9.4 and 2026.9.8, including parent-local context and split
|
|
33
|
+
`dist/.setup/` bundles. Bundle names
|
|
33
34
|
may change; fingerprints of the context-assembly functions must still match.
|
|
34
35
|
Changed assembly is refused pending review, rather than patched blindly.
|
|
35
36
|
- Managed local stdio Codex, agent-scoped home, no custom command/arguments.
|
|
@@ -37,8 +38,11 @@ files use private permissions. Missing identity documents are recorded as absent
|
|
|
37
38
|
children may inherit the parent's frozen context.
|
|
38
39
|
|
|
39
40
|
This bridge uses two private upstream seams because public prompt hooks cannot
|
|
40
|
-
fix the dropped-context path: one skill-catalog builder and
|
|
41
|
-
|
|
41
|
+
fix the dropped-context path: one skill-catalog builder and guarded Codex context
|
|
42
|
+
carriers. Newer builds also require parent-local suppression and removal of the
|
|
43
|
+
separate thread-level skills append. The installer backs up and tracks every
|
|
44
|
+
patched bundle; old single-file receipts remain restorable. Runtime module paths
|
|
45
|
+
come from the loader's selected entry, never its temporary execution capture.
|
|
42
46
|
It validates private seams and is not a promise of compatibility with unseen layouts.
|
|
43
47
|
The catalog uses OpenClaw's eligibility, allowlist, visibility and size-limit
|
|
44
48
|
logic. Session-specific and execution-directory skills are not snapshotted.
|
|
@@ -50,7 +54,7 @@ Remote-node-only skills are not added; this is for dedicated local Mac agents.
|
|
|
50
54
|
npm ci
|
|
51
55
|
npm run preflight
|
|
52
56
|
npm pack
|
|
53
|
-
openclaw plugins install ./unblocklabs-unblock-codex-prompt-0.2.
|
|
57
|
+
openclaw plugins install ./unblocklabs-unblock-codex-prompt-0.2.1.tgz --force --accept-capabilities
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
Include the plugin in `plugins.allow` if applicable, then configure:
|
|
@@ -236,7 +240,7 @@ without restoring leaves the frozen base but re-enables live injections.
|
|
|
236
240
|
|
|
237
241
|
```sh
|
|
238
242
|
npm run preflight
|
|
239
|
-
npm run release:check -- v0.2.
|
|
243
|
+
npm run release:check -- v0.2.1
|
|
240
244
|
```
|
|
241
245
|
|
|
242
246
|
Tests cover compilation, explicit refresh, native/private-body exclusion,
|
|
@@ -246,7 +250,7 @@ proof and limits.
|
|
|
246
250
|
|
|
247
251
|
Install with
|
|
248
252
|
the packed archive above for an unreleased candidate. After publication, use
|
|
249
|
-
`openclaw plugins install npm:@unblocklabs/unblock-codex-prompt@0.2.
|
|
253
|
+
`openclaw plugins install npm:@unblocklabs/unblock-codex-prompt@0.2.1 --accept-capabilities`.
|
|
250
254
|
The existing [GitHub Release workflow](RELEASING.md) publishes npm only when a
|
|
251
255
|
GitHub Release is published. Normal pushes do not release the package.
|
|
252
256
|
|
package/dist/adapter.d.ts
CHANGED
|
@@ -1,24 +1,37 @@
|
|
|
1
|
-
type
|
|
2
|
-
schema: 1;
|
|
3
|
-
packageDir: string;
|
|
1
|
+
type AdapterFile = {
|
|
4
2
|
path: string;
|
|
5
3
|
originalSha256: string;
|
|
6
4
|
patchedSha256: string;
|
|
5
|
+
previousPatchedSha256?: string;
|
|
6
|
+
};
|
|
7
|
+
type Receipt = ({
|
|
8
|
+
schema: 1;
|
|
9
|
+
packageDir: string;
|
|
10
|
+
} & AdapterFile) | {
|
|
11
|
+
schema: 2;
|
|
12
|
+
packageDir: string;
|
|
13
|
+
files: AdapterFile[];
|
|
7
14
|
};
|
|
8
15
|
export declare function readReceipt(agentDir: string): Promise<Receipt | null>;
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function prepareAdapter(agentDir: string, agentId: string, suppliedDir
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
export declare function patchAdapterSources(sources: string[], agentDir: string, agentId: string, runtimePath: string): string[];
|
|
17
|
+
export declare function prepareAdapter(agentDir: string, agentId: string, suppliedDir: string | undefined, runtimePath: string): Promise<{
|
|
18
|
+
files: {
|
|
19
|
+
patched: string;
|
|
20
|
+
path: string;
|
|
21
|
+
current: string;
|
|
22
|
+
original: string;
|
|
23
|
+
}[];
|
|
15
24
|
receipt: {
|
|
16
|
-
schema:
|
|
25
|
+
schema: 2;
|
|
17
26
|
packageDir: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
files: {
|
|
28
|
+
previousPatchedSha256?: string;
|
|
29
|
+
path: string;
|
|
30
|
+
originalSha256: string;
|
|
31
|
+
patchedSha256: string;
|
|
32
|
+
}[];
|
|
21
33
|
};
|
|
34
|
+
previousReceipt: string | null;
|
|
22
35
|
}>;
|
|
23
36
|
export declare function installAdapter(agentDir: string, plan: Awaited<ReturnType<typeof prepareAdapter>>): Promise<{
|
|
24
37
|
adapterChanged: boolean;
|
|
@@ -27,10 +40,12 @@ export declare function installAdapter(agentDir: string, plan: Awaited<ReturnTyp
|
|
|
27
40
|
export declare function adapterStatus(agentDir: string): Promise<{
|
|
28
41
|
installed: boolean;
|
|
29
42
|
path?: never;
|
|
43
|
+
paths?: never;
|
|
30
44
|
minimumVersion?: never;
|
|
31
45
|
} | {
|
|
32
46
|
installed: boolean;
|
|
33
47
|
path: string;
|
|
48
|
+
paths: string[];
|
|
34
49
|
minimumVersion: string;
|
|
35
50
|
}>;
|
|
36
51
|
export declare function restoreAdapter(agentDir: string): Promise<void>;
|
package/dist/adapter.js
CHANGED
|
@@ -1,62 +1,93 @@
|
|
|
1
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
1
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
2
2
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { parse } from "acorn";
|
|
5
4
|
import { readOptional, replaceFile, sha256 } from "./manager.js";
|
|
6
5
|
import { openclawRoot } from "./compiler.js";
|
|
7
6
|
import { assertSupportedVersion, MINIMUM_OPENCLAW } from "./compatibility.js";
|
|
8
7
|
const MARKER = "// unblock-codex-prompt bridge v1";
|
|
9
|
-
const BUNDLE_NAME = /^thread-lifecycle-[\w-]+\.m?js$/u;
|
|
8
|
+
const BUNDLE_NAME = /^thread-(lifecycle|requests)-[\w-]+\.m?js$/u;
|
|
10
9
|
const HASH = /^[a-f0-9]{64}$/u;
|
|
11
|
-
//
|
|
12
|
-
// or unrelated code. New releases can use either reviewed layout unchanged.
|
|
10
|
+
// Reviewed context carriers, independent of release-specific bundle names.
|
|
13
11
|
const CONTEXT_HASHES = {
|
|
14
12
|
legacy: "1fb45c46c50a63f68e8b6bdfbc5d7ae2d323470c00db69779d159592868940bb",
|
|
15
13
|
parent: "5ee80edf3f3887e841def5d64896a3d27c35063f6d4803dceeb8b8bf07b654f0",
|
|
14
|
+
splitParent: "6515e383260e0edd4379a2e694d9b066ddb02a79b55fd7494bf7f85affee72c6",
|
|
16
15
|
collaboration: "aea7e95ca43f0997b0a3f616f327132bfdd057bb731e6eead132fba21de3f981",
|
|
16
|
+
legacyThread: "703a32a0b18eacd45a9fd3d68c04a13527bdbeaae50a11a6b76768b84b99a9ed",
|
|
17
|
+
thread: "3aa4c414cb449cdb9167e6ed2df9397559176310ce7cff882652bd669ac8cee8",
|
|
17
18
|
};
|
|
18
19
|
function receiptPath(agentDir) { return join(agentDir, "codex-home", ".unblock-codex-prompt-adapter.json"); }
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
function receiptFiles(receipt) { return receipt.schema === 1 ? [receipt] : receipt.files; }
|
|
21
|
+
function backupPath(agentDir, file, legacy = false) {
|
|
22
|
+
return join(agentDir, "codex-home", legacy ? ".unblock-codex-prompt-adapter-original.js" : ".unblock-codex-prompt-adapter-original-" + basename(file.path));
|
|
23
|
+
}
|
|
24
|
+
function ownsSource(file, source) {
|
|
25
|
+
if (source === null)
|
|
26
|
+
return false;
|
|
27
|
+
const hash = sha256(source);
|
|
28
|
+
return hash === file.originalSha256 || hash === file.patchedSha256 || hash === file.previousPatchedSha256;
|
|
29
|
+
}
|
|
30
|
+
function parseReceipt(source) {
|
|
22
31
|
if (source === null)
|
|
23
32
|
return null;
|
|
24
33
|
const value = JSON.parse(source);
|
|
25
|
-
if (!value || typeof value !== "object" || !("schema" in value) || value.schema !== 1 ||
|
|
26
|
-
!("packageDir" in value) || typeof value.packageDir !== "string" ||
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
value.path !== join(value.packageDir, "dist", basename(value.path)))
|
|
34
|
+
if (!value || typeof value !== "object" || !("schema" in value) || (value.schema !== 1 && value.schema !== 2) ||
|
|
35
|
+
!("packageDir" in value) || typeof value.packageDir !== "string" || !isAbsolute(value.packageDir))
|
|
36
|
+
throw new Error("Invalid adapter receipt");
|
|
37
|
+
const packageDir = value.packageDir;
|
|
38
|
+
const entries = value.schema === 1 ? [value] : "files" in value && Array.isArray(value.files) ? value.files : [];
|
|
39
|
+
if (!entries.length)
|
|
32
40
|
throw new Error("Invalid adapter receipt");
|
|
33
|
-
|
|
41
|
+
const files = entries.map((entry) => {
|
|
42
|
+
if (!entry || typeof entry !== "object" || !("path" in entry) || typeof entry.path !== "string" ||
|
|
43
|
+
!BUNDLE_NAME.test(basename(entry.path)) ||
|
|
44
|
+
![join(packageDir, "dist", basename(entry.path)), ...(value.schema === 2 ? [join(packageDir, "dist", ".setup", basename(entry.path))] : [])].includes(entry.path) ||
|
|
45
|
+
(value.schema === 1 && !basename(entry.path).startsWith("thread-lifecycle-")) ||
|
|
46
|
+
!("originalSha256" in entry) || typeof entry.originalSha256 !== "string" || !HASH.test(entry.originalSha256) ||
|
|
47
|
+
!("patchedSha256" in entry) || typeof entry.patchedSha256 !== "string" || !HASH.test(entry.patchedSha256))
|
|
48
|
+
throw new Error("Invalid adapter receipt");
|
|
49
|
+
const previous = "previousPatchedSha256" in entry ? entry.previousPatchedSha256 : undefined;
|
|
50
|
+
if (previous !== undefined && (typeof previous !== "string" || !HASH.test(previous)))
|
|
51
|
+
throw new Error("Invalid adapter receipt");
|
|
52
|
+
return { path: entry.path, originalSha256: entry.originalSha256, patchedSha256: entry.patchedSha256,
|
|
53
|
+
...(typeof previous === "string" ? { previousPatchedSha256: previous } : {}) };
|
|
54
|
+
});
|
|
55
|
+
if (new Set(files.map(file => basename(file.path))).size !== files.length)
|
|
56
|
+
throw new Error("Invalid adapter receipt");
|
|
57
|
+
return value.schema === 1 ? { schema: 1, packageDir, ...files[0] } : { schema: 2, packageDir, files };
|
|
34
58
|
}
|
|
35
|
-
export function
|
|
36
|
-
|
|
59
|
+
export async function readReceipt(agentDir) { return parseReceipt(await readOptional(receiptPath(agentDir))); }
|
|
60
|
+
export function patchAdapterSources(sources, agentDir, agentId, runtimePath) {
|
|
61
|
+
if (sources.some(source => source.includes(MARKER)))
|
|
37
62
|
throw new Error("Unsupported Codex adapter shape: already patched");
|
|
38
|
-
const functions = parse(source, { ecmaVersion: "latest", sourceType: "module" }).body
|
|
63
|
+
const functions = sources.flatMap((source, bundle) => parse(source, { ecmaVersion: "latest", sourceType: "module" }).body
|
|
64
|
+
.filter(node => node.type === "FunctionDeclaration").map(node => ({ node, bundle })));
|
|
39
65
|
const find = (name) => {
|
|
40
|
-
const matches = functions.filter(
|
|
66
|
+
const matches = functions.filter(fn => fn.node.id?.name === name);
|
|
41
67
|
if (matches.length !== 1)
|
|
42
|
-
throw new Error(
|
|
68
|
+
throw new Error("Unsupported Codex adapter shape: " + name);
|
|
43
69
|
return matches[0];
|
|
44
70
|
};
|
|
45
71
|
const developer = find("buildDeveloperInstructions");
|
|
46
72
|
const collaboration = find("buildTurnScopedCollaborationInstructions");
|
|
47
|
-
const parent = functions.some(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
const parent = functions.some(fn => fn.node.id?.name === "buildCodexParentLocalInstructions") ? find("buildCodexParentLocalInstructions") : null;
|
|
74
|
+
const thread = functions.some(fn => fn.node.id?.name === "buildCodexThreadConfiguration") ? find("buildCodexThreadConfiguration") : null;
|
|
75
|
+
for (const { node, bundle } of [developer, collaboration, ...(parent ? [parent] : []), ...(thread ? [thread] : [])]) {
|
|
76
|
+
if (node.async || node.generator || node.params.length !== 2 || node.params[0]?.type !== "Identifier" || node.params[0].name !== "params" ||
|
|
77
|
+
!/^(options|options\s*=\s*\{\})$/u.test(sources[bundle].slice(node.params[1].start, node.params[1].end)))
|
|
51
78
|
throw new Error("Unsupported Codex adapter shape: parameters");
|
|
52
79
|
}
|
|
53
80
|
for (const name of ["buildDefaultCollaborationInstructions", "buildCronCollaborationInstructions"]) {
|
|
54
|
-
|
|
81
|
+
const helper = find(name);
|
|
82
|
+
if (helper.node.params.length !== 0 || helper.bundle !== collaboration.bundle)
|
|
55
83
|
throw new Error("Unsupported Codex adapter shape: mode helpers");
|
|
56
84
|
}
|
|
57
|
-
const fingerprint = (fn) => sha256(
|
|
85
|
+
const fingerprint = (fn) => sha256(sources[fn.bundle].slice(fn.node.start, fn.node.end));
|
|
86
|
+
const split = parent !== null && fingerprint(parent) === CONTEXT_HASHES.splitParent;
|
|
58
87
|
if (fingerprint(collaboration) !== (parent ? CONTEXT_HASHES.collaboration : CONTEXT_HASHES.legacy) ||
|
|
59
|
-
(parent &&
|
|
88
|
+
(parent && (parent.bundle !== collaboration.bundle || ![CONTEXT_HASHES.parent, CONTEXT_HASHES.splitParent].includes(fingerprint(parent)))) ||
|
|
89
|
+
(thread && thread.bundle !== developer.bundle) ||
|
|
90
|
+
(split ? !thread || fingerprint(thread) !== CONTEXT_HASHES.thread : thread !== null && fingerprint(thread) !== CONTEXT_HASHES.legacyThread))
|
|
60
91
|
throw new Error("Unsupported Codex context assembly; review the changed upstream functions before syncing");
|
|
61
92
|
const prefix = `${MARKER}
|
|
62
93
|
import { createRequire as unblockPromptCreateRequire } from "node:module";
|
|
@@ -73,18 +104,27 @@ function unblockFrozenPolicy(params) {
|
|
|
73
104
|
}
|
|
74
105
|
`;
|
|
75
106
|
const insertions = [
|
|
76
|
-
{
|
|
77
|
-
{
|
|
78
|
-
...(parent ? [{
|
|
107
|
+
{ fn: developer, text: '\n\tconst unblockPolicy = unblockFrozenPolicy(params);\n\tif (unblockPolicy !== null) return [unblockPolicy, params.gitCoauthorPrompt, params.extraSystemPrompt].filter(section => typeof section === "string" && section.trim()).join("\\n\\n");' },
|
|
108
|
+
{ fn: collaboration, text: '\n\tif (unblockFrozenPolicy(params) !== null) return params.trigger === "cron" ? buildCronCollaborationInstructions() : buildDefaultCollaborationInstructions();' },
|
|
109
|
+
...(parent ? [{ fn: parent, text: '\n\tif (unblockFrozenPolicy(params) !== null) return params.trigger === "cron" ? buildCronCollaborationInstructions() : null;' }] : []),
|
|
110
|
+
// 2026.9.8 moved skills to the start/resume/fork carrier, outside developer policy.
|
|
111
|
+
...(split && thread ? [{ fn: thread, text: '\n\tif (unblockFrozenPolicy(params) !== null) options = { ...options, skillsInstructions: undefined };' }] : []),
|
|
79
112
|
];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
return sources.map((source, bundle) => {
|
|
114
|
+
const edits = insertions.filter(edit => edit.fn.bundle === bundle).sort((a, b) => b.fn.node.body.start - a.fn.node.body.start);
|
|
115
|
+
for (const { fn, text } of edits) {
|
|
116
|
+
const at = fn.node.body.start + 1;
|
|
117
|
+
source = source.slice(0, at) + text + source.slice(at);
|
|
118
|
+
}
|
|
119
|
+
return edits.length ? prefix + source : source;
|
|
120
|
+
});
|
|
84
121
|
}
|
|
85
|
-
export async function prepareAdapter(agentDir, agentId, suppliedDir) {
|
|
122
|
+
export async function prepareAdapter(agentDir, agentId, suppliedDir, runtimePath) {
|
|
86
123
|
await openclawRoot();
|
|
87
|
-
|
|
124
|
+
if (!isAbsolute(runtimePath) || await readOptional(runtimePath) === null)
|
|
125
|
+
throw new Error("Missing durable plugin runtime");
|
|
126
|
+
const previousReceipt = await readOptional(receiptPath(agentDir));
|
|
127
|
+
const receipt = parseReceipt(previousReceipt);
|
|
88
128
|
const packageDir = suppliedDir ? resolve(suppliedDir) : receipt?.packageDir;
|
|
89
129
|
if (!packageDir)
|
|
90
130
|
throw new Error("First frozen sync requires --codex-plugin-dir from openclaw plugins inspect codex --json");
|
|
@@ -94,50 +134,88 @@ export async function prepareAdapter(agentDir, agentId, suppliedDir) {
|
|
|
94
134
|
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== "@openclaw/codex")
|
|
95
135
|
throw new Error("Expected the official @openclaw/codex package");
|
|
96
136
|
assertSupportedVersion("version" in pkg ? pkg.version : undefined, "@openclaw/codex");
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
137
|
+
const dist = join(packageDir, "dist");
|
|
138
|
+
if (!(await lstat(dist)).isDirectory())
|
|
99
139
|
throw new Error("Unsupported Codex bundle layout");
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
throw new Error("
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
140
|
+
const entries = await readdir(dist, { withFileTypes: true });
|
|
141
|
+
const setup = entries.find(entry => entry.name === ".setup");
|
|
142
|
+
if (setup && !setup.isDirectory())
|
|
143
|
+
throw new Error("Unsupported Codex bundle layout");
|
|
144
|
+
const paths = entries.filter(entry => BUNDLE_NAME.test(entry.name)).map(entry => join(dist, entry.name));
|
|
145
|
+
if (setup)
|
|
146
|
+
paths.push(...(await readdir(join(dist, ".setup"))).filter(name => BUNDLE_NAME.test(name)).map(name => join(dist, ".setup", name)));
|
|
147
|
+
paths.sort();
|
|
148
|
+
if (!paths.length || (receipt && receiptFiles(receipt).some(file => !paths.includes(file.path))))
|
|
149
|
+
throw new Error("Unsupported Codex bundle layout; restore before changing installations");
|
|
150
|
+
const bundles = await Promise.all(paths.map(async (path) => {
|
|
151
|
+
const current = await readOptional(path);
|
|
152
|
+
if (current === null)
|
|
153
|
+
throw new Error("Missing Codex adapter");
|
|
154
|
+
const file = receipt && receiptFiles(receipt).find(file => file.path === path);
|
|
155
|
+
const original = file ? await readOptional(backupPath(agentDir, file, receipt?.schema === 1)) : current;
|
|
156
|
+
if (original === null || (file && sha256(original) !== file.originalSha256))
|
|
157
|
+
throw new Error("Codex adapter backup invalid; refusing to patch");
|
|
158
|
+
if (file && !ownsSource(file, current))
|
|
159
|
+
throw new Error("Codex adapter changed by another writer; refusing to overwrite");
|
|
160
|
+
return { path, current, original };
|
|
161
|
+
}));
|
|
162
|
+
const patches = patchAdapterSources(bundles.map(bundle => bundle.original), agentDir, agentId, runtimePath);
|
|
163
|
+
const files = bundles.flatMap((bundle, index) => {
|
|
164
|
+
const patched = patches[index];
|
|
165
|
+
return patched === bundle.original ? [] : [{ ...bundle, patched }];
|
|
166
|
+
});
|
|
167
|
+
if (receipt && (files.length !== receiptFiles(receipt).length || files.some(file => !receiptFiles(receipt).some(previous => previous.path === file.path))))
|
|
106
168
|
throw new Error("Codex adapter path changed; restore before changing installations");
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return {
|
|
169
|
+
const nextReceipt = { schema: 2, packageDir, files: files.map(file => ({
|
|
170
|
+
path: file.path, originalSha256: sha256(file.original), patchedSha256: sha256(file.patched),
|
|
171
|
+
// Receipt-first upgrades must remain recoverable if source swapping is interrupted.
|
|
172
|
+
...(file.current !== file.original && file.current !== file.patched ? { previousPatchedSha256: sha256(file.current) } : {}),
|
|
173
|
+
})) };
|
|
174
|
+
return { files, receipt: nextReceipt, previousReceipt };
|
|
113
175
|
}
|
|
114
176
|
export async function installAdapter(agentDir, plan) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
177
|
+
// Validate every target before any bundle changes; retain exact backups for recovery.
|
|
178
|
+
const backups = await Promise.all(plan.files.map(async (file) => {
|
|
179
|
+
const path = backupPath(agentDir, file);
|
|
180
|
+
const backup = await readOptional(path);
|
|
181
|
+
if (backup !== null && backup !== file.original)
|
|
182
|
+
throw new Error("Foreign adapter backup");
|
|
183
|
+
if (await readOptional(file.path) !== file.current)
|
|
184
|
+
throw new Error("Concurrent adapter edit; refusing to overwrite");
|
|
185
|
+
return { path, backup, original: file.original };
|
|
186
|
+
}));
|
|
187
|
+
for (const backup of backups)
|
|
188
|
+
await replaceFile(backup.path, backup.backup, backup.original);
|
|
189
|
+
await replaceFile(receiptPath(agentDir), plan.previousReceipt, JSON.stringify(plan.receipt, null, 2) + "\n");
|
|
190
|
+
let adapterChanged = false;
|
|
191
|
+
for (const file of plan.files)
|
|
192
|
+
adapterChanged = await replaceFile(file.path, file.current, file.patched) || adapterChanged;
|
|
193
|
+
return { adapterChanged, restartRequired: true };
|
|
122
194
|
}
|
|
123
195
|
export async function adapterStatus(agentDir) {
|
|
124
196
|
const receipt = await readReceipt(agentDir);
|
|
125
197
|
if (!receipt)
|
|
126
198
|
return { installed: false };
|
|
127
|
-
const
|
|
128
|
-
|
|
199
|
+
const files = receiptFiles(receipt);
|
|
200
|
+
const installed = (await Promise.all(files.map(async (file) => {
|
|
201
|
+
const current = await readOptional(file.path);
|
|
202
|
+
return current !== null && sha256(current) === file.patchedSha256;
|
|
203
|
+
}))).every(Boolean);
|
|
204
|
+
return { installed, path: files[0].path, paths: files.map(file => file.path), minimumVersion: MINIMUM_OPENCLAW };
|
|
129
205
|
}
|
|
130
206
|
export async function restoreAdapter(agentDir) {
|
|
131
207
|
const receipt = await readReceipt(agentDir);
|
|
132
208
|
if (!receipt)
|
|
133
209
|
return;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
210
|
+
const files = await Promise.all(receiptFiles(receipt).map(async (file) => {
|
|
211
|
+
const current = await readOptional(file.path);
|
|
212
|
+
const original = await readOptional(backupPath(agentDir, file, receipt.schema === 1));
|
|
213
|
+
if (original === null || sha256(original) !== file.originalSha256)
|
|
214
|
+
throw new Error("Adapter backup invalid");
|
|
215
|
+
if (!ownsSource(file, current))
|
|
216
|
+
throw new Error("Foreign adapter edit; refusing restore");
|
|
217
|
+
return { path: file.path, current, original };
|
|
218
|
+
}));
|
|
219
|
+
for (const file of files)
|
|
220
|
+
await replaceFile(file.path, file.current, file.original);
|
|
143
221
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { buildSkillsCatalog, compilePrompt } from "./compiler.js";
|
|
|
8
8
|
import { readFrozenPolicy } from "./runtime.js";
|
|
9
9
|
import { fetchPrompt, REMOTE_POLICY } from "./remote.js";
|
|
10
10
|
import { readOptional } from "./manager.js";
|
|
11
|
-
import { join } from "node:path";
|
|
11
|
+
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
12
12
|
import { CATALOG_REFRESH_MS, catalogStatus, refreshCatalog, restoreCatalog, startCatalogRefresh } from "./catalog.js";
|
|
13
13
|
import { codexSkillsStatus, refreshCodexSkills, removeCodexSkills } from "./skills.js";
|
|
14
14
|
async function refresh(target, automatic = false) {
|
|
@@ -148,7 +148,12 @@ export default definePluginEntry({
|
|
|
148
148
|
sync.action(async () => {
|
|
149
149
|
const target = await resolveTarget(config);
|
|
150
150
|
const options = sync.opts();
|
|
151
|
-
|
|
151
|
+
// Captured import.meta.url paths expire with the CLI. Older hosts expose the
|
|
152
|
+
// published dist/index.js via source; newer hosts select it via runtimeSource.
|
|
153
|
+
const source = "runtimeSource" in api && typeof api.runtimeSource === "string" ? api.runtimeSource : api.source;
|
|
154
|
+
if (target.settings.frozenContext && (!isAbsolute(source) || basename(source) !== "index.js"))
|
|
155
|
+
throw new Error("Frozen sync requires the packaged plugin runtime entry");
|
|
156
|
+
const plan = target.settings.frozenContext ? await prepareAdapter(target.agentDir, target.settings.agentId, options.codexPluginDir, join(dirname(source), "runtime.js")) : null;
|
|
152
157
|
const remote = target.settings.promptUrl ? await fetchPrompt(target.settings.promptUrl) : null;
|
|
153
158
|
const prompt = remote?.prompt ?? await bundledPrompt(target.settings.agentName);
|
|
154
159
|
const compiled = target.settings.frozenContext ? await compilePrompt(prompt, remote ? REMOTE_POLICY : await readFile(new URL("../prompts/developer.md", import.meta.url), "utf8"), target.workspaceDir, await buildSkillsCatalog(config, target.settings.agentId, target.workspaceDir)) : null;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "unblock-codex-prompt",
|
|
3
3
|
"name": "Unblock Codex Prompt",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "Maintain an agent-scoped Codex base prompt and its model_instructions_file pointer.",
|
|
6
6
|
"activation": { "onStartup": true },
|
|
7
7
|
"configSchema": {
|