@skillstate/codex 2.0.7 → 2.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/dist/codex-adapter.d.ts +34 -12
- package/dist/codex-adapter.d.ts.map +1 -1
- package/dist/codex-adapter.js +66 -255
- package/dist/codex-adapter.js.map +1 -1
- package/package.json +1 -1
package/dist/codex-adapter.d.ts
CHANGED
|
@@ -38,6 +38,15 @@ export interface CodexHooksConfigOptions {
|
|
|
38
38
|
* embedded copy because they cannot import `@skillstate/*`).
|
|
39
39
|
*/
|
|
40
40
|
export declare const resolveStateForCwd: typeof resolveHostStateForCwd;
|
|
41
|
+
/**
|
|
42
|
+
* OpenAI Codex platform adapter (@non-paper; see module doc).
|
|
43
|
+
*
|
|
44
|
+
* Every generated hook script is a SELF-CONTAINED CommonJS file (Node
|
|
45
|
+
* builtins only, no `@skillstate/*` import): Codex executes them directly
|
|
46
|
+
* via `node <script> <event>` with the hook JSON on stdin, and each script
|
|
47
|
+
* resolves the per-project state from `input.cwd` at runtime — so one
|
|
48
|
+
* global `hooks.json` + one script directory serve every project.
|
|
49
|
+
*/
|
|
41
50
|
export declare class CodexAdapter {
|
|
42
51
|
readonly name = "codex";
|
|
43
52
|
/**
|
|
@@ -81,42 +90,55 @@ export declare class CodexAdapter {
|
|
|
81
90
|
* cwd-resolving and never bakes an absolute state path in.
|
|
82
91
|
*/
|
|
83
92
|
generateHookScript(event: CodexHookEvent, statePath?: string | StatePathRef): string;
|
|
93
|
+
/**
|
|
94
|
+
* Inject-script template (`UserPromptSubmit` / `SessionStart`): brand
|
|
95
|
+
* header + the shared hook-runtime snippet ({@link hookRuntimeSnippet}
|
|
96
|
+
* inlined via `fn.toString()`) + a stdin loop that emits
|
|
97
|
+
* `{ hookSpecificOutput: { hookEventName, additionalContext } }` with the
|
|
98
|
+
* current state JSON read from the SESSION cwd. Brand specifics stay in
|
|
99
|
+
* the adapter (comment lines only); the `contextSuffix` is the shared
|
|
100
|
+
* {@link HISTORY_UNRELIABLE_NOTE} from `@skillstate/core`.
|
|
101
|
+
*/
|
|
102
|
+
private buildInjectScriptTemplate;
|
|
84
103
|
/**
|
|
85
104
|
* Generate a SKILL.md for Codex's skill directory
|
|
86
105
|
* (`~/.codex/skills/<name>/SKILL.md`). The body instructs the agent to
|
|
87
106
|
* treat the hook-injected state as authoritative (history is not
|
|
88
|
-
* reliable), to
|
|
89
|
-
* to persist via `state.patch` — the PostToolUse hook also merges any
|
|
107
|
+
* reliable), to orient via `state.summary` (full dump via `state.get`),
|
|
108
|
+
* and to persist via `state.patch` — the PostToolUse hook also merges any
|
|
90
109
|
* fenced ```json `state_patch` block printed by a Bash tool call.
|
|
91
110
|
*/
|
|
92
111
|
generateSkillMd(spec: ProceduralSpec, statePath?: string): string;
|
|
93
112
|
/**
|
|
94
|
-
* Generate the hooks.json document and persist it via
|
|
95
|
-
* Both the destination and the embedded state path
|
|
96
|
-
* `{ root, name }` refs confined by
|
|
97
|
-
* absolute destination path.
|
|
113
|
+
* Generate the hooks.json document and persist it via the shared
|
|
114
|
+
* `saveGenerated`. Both the destination and the embedded state path
|
|
115
|
+
* accept raw strings or `{ root, name }` refs confined by
|
|
116
|
+
* `resolveStatePath`. Returns the absolute destination path.
|
|
98
117
|
*/
|
|
99
118
|
saveHooksConfig(target: string | StatePathRef, statePath: string | StatePathRef, options?: CodexHooksConfigOptions): Promise<string>;
|
|
100
119
|
/**
|
|
101
120
|
* Merge the skillstate hook groups into an existing `hooks.json` text.
|
|
102
121
|
* Idempotent: if any skillstate command is already wired, the document is
|
|
103
122
|
* returned unchanged. Existing (non-skillstate) hooks are preserved.
|
|
123
|
+
*
|
|
124
|
+
* The mechanics live in the shared {@link mergeHookGroups}; this method
|
|
125
|
+
* supplies only the format specifics (generated hooks.json groups —
|
|
126
|
+
* with the codex `description`/`statusMessage`/`additionalContextLimit`
|
|
127
|
+
* fields — and the `node <script> <event>` skillstate commands).
|
|
104
128
|
*/
|
|
105
129
|
mergeHooksConfig(existingJson: string, options?: CodexHooksConfigOptions): string;
|
|
106
130
|
/**
|
|
107
|
-
* Generate a hook script and persist it via
|
|
108
|
-
* is the script destination (usually
|
|
131
|
+
* Generate a hook script and persist it via the shared `saveGenerated`
|
|
132
|
+
* (atomic temp-rename write). `target` is the script destination (usually
|
|
109
133
|
* {@link CodexAdapter.codexHookScriptPath}); `statePath` is forwarded to
|
|
110
134
|
* {@link generateHookScript}. Returns the absolute destination path.
|
|
111
135
|
*/
|
|
112
136
|
saveHookScript(event: CodexHookEvent, target: string | StatePathRef, statePath?: string | StatePathRef): Promise<string>;
|
|
113
137
|
/**
|
|
114
|
-
* Generate a SKILL.md and persist it via `
|
|
115
|
-
* absolute destination path.
|
|
138
|
+
* Generate a SKILL.md and persist it via the shared `saveGenerated`.
|
|
139
|
+
* Returns the absolute destination path.
|
|
116
140
|
*/
|
|
117
141
|
saveSkillMd(target: string | StatePathRef, spec: ProceduralSpec, statePath?: string): Promise<string>;
|
|
118
|
-
/** Resolve a `string | StatePathRef` via `resolveStatePath` (throws on `..`). */
|
|
119
|
-
private resolve;
|
|
120
142
|
/** PostToolUse script: extract state_patch, ⊕ merge, persist. */
|
|
121
143
|
private buildPostToolUseScript;
|
|
122
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-adapter.d.ts","sourceRoot":"","sources":["../src/codex-adapter.ts"],"names":[],"mappings":"AA4BA,OAAO,
|
|
1
|
+
{"version":3,"file":"codex-adapter.d.ts","sourceRoot":"","sources":["../src/codex-adapter.ts"],"names":[],"mappings":"AA4BA,OAAO,EAIL,sBAAsB,EAIvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErE,+EAA+E;AAC/E,MAAM,MAAM,cAAc,GACtB,oBAAoB,GACpB,uBAAuB,GACvB,eAAe,CAAC;AAEpB,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,2EAIgB,CAAC;AAE/C,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD,2DAA2D;AAC3D,eAAO,MAAM,2BAA2B,WAAW,CAAC;AAEpD;;;GAGG;AACH,eAAO,MAAM,8BAA8B,OAAO,CAAC;AAEnD,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,4DAA4D;AAC5D,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,+BAAyB,CAAC;AAEzD;;;;;;;;GAQG;AAEH,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,WAAW;IAExB;;;;;OAKG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAEpE;IAED;;;;;;;;;;;OAWG;IACH,mBAAmB,CACjB,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,OAAO,CAAC,EAAE,uBAAuB,GAChC,MAAM,CA6CR;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAChB,KAAK,EAAE,cAAc,EACrB,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,GAChC,MAAM,CAiBR;IAED;;;;;;;;OAQG;IACH,OAAO,CAAC,yBAAyB;IAiDjC;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhE;IAED;;;;;OAKG;IACG,eAAe,CACnB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,MAAM,CAAC,CAEjB;IAED;;;;;;;;;OASG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,MAAM,CAahF;IAED;;;;;OAKG;IACG,cAAc,CAClB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,GAChC,OAAO,CAAC,MAAM,CAAC,CAEjB;IAED;;;OAGG;IACG,WAAW,CACf,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,IAAI,EAAE,cAAc,EACpB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAEjB;IAMD,iEAAiE;IACjE,OAAO,CAAC,sBAAsB;CAkE/B"}
|
package/dist/codex-adapter.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* @non-paper — no adapters exist in arXiv 2608.26263v3.
|
|
27
27
|
*/
|
|
28
28
|
import * as path from 'node:path';
|
|
29
|
-
import {
|
|
29
|
+
import { HISTORY_UNRELIABLE_NOTE, hookRuntimeSnippet, mergeHookGroups, resolveHostStateForCwd, resolveTarget, saveGenerated, skillMdBody, } from '@skillstate/core';
|
|
30
30
|
/** All {@link CodexHookEvent} values in generation order. */
|
|
31
31
|
export const CODEX_HOOK_EVENTS = [
|
|
32
32
|
'user-prompt-submit',
|
|
@@ -62,10 +62,6 @@ export const resolveStateForCwd = resolveHostStateForCwd;
|
|
|
62
62
|
* resolves the per-project state from `input.cwd` at runtime — so one
|
|
63
63
|
* global `hooks.json` + one script directory serve every project.
|
|
64
64
|
*/
|
|
65
|
-
/** Narrow record check for hooks.json documents (module scope). */
|
|
66
|
-
function isPlainObjectDoc(value) {
|
|
67
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
68
|
-
}
|
|
69
65
|
export class CodexAdapter {
|
|
70
66
|
name = 'codex';
|
|
71
67
|
/**
|
|
@@ -90,7 +86,7 @@ export class CodexAdapter {
|
|
|
90
86
|
* file's directory).
|
|
91
87
|
*/
|
|
92
88
|
generateHooksConfig(statePath, options) {
|
|
93
|
-
const resolved =
|
|
89
|
+
const resolved = resolveTarget(statePath);
|
|
94
90
|
const scriptDir = options?.scriptDir ?? path.dirname(resolved);
|
|
95
91
|
const command = (event) => options?.command ?? `node ${JSON.stringify(this.codexHookScriptPath(scriptDir, event))} ${event}`;
|
|
96
92
|
const timeout = options?.timeoutSeconds ?? CODEX_HOOK_TIMEOUT_SECONDS;
|
|
@@ -148,49 +144,45 @@ export class CodexAdapter {
|
|
|
148
144
|
* cwd-resolving and never bakes an absolute state path in.
|
|
149
145
|
*/
|
|
150
146
|
generateHookScript(event, statePath) {
|
|
151
|
-
const resolved = statePath === undefined ? undefined :
|
|
147
|
+
const resolved = statePath === undefined ? undefined : resolveTarget(statePath);
|
|
152
148
|
const header = `// State file (per-project resolver): ${resolved ?? '<cwd>/.skillstate/skillstate.json (global bucket when cwd === home)'}`;
|
|
153
149
|
if (event === 'post-tool-use') {
|
|
154
150
|
return this.buildPostToolUseScript(header);
|
|
155
151
|
}
|
|
156
152
|
const hookEventName = event === 'session-start-compact' ? 'SessionStart' : 'UserPromptSubmit';
|
|
153
|
+
// A1 text is shared: both hosts append the SAME HISTORY_UNRELIABLE_NOTE
|
|
154
|
+
// (MCP tools + fenced state_patch channel) after the state JSON.
|
|
155
|
+
return this.buildInjectScriptTemplate({
|
|
156
|
+
header,
|
|
157
|
+
hookEventName,
|
|
158
|
+
contextSuffix: HISTORY_UNRELIABLE_NOTE,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Inject-script template (`UserPromptSubmit` / `SessionStart`): brand
|
|
163
|
+
* header + the shared hook-runtime snippet ({@link hookRuntimeSnippet}
|
|
164
|
+
* inlined via `fn.toString()`) + a stdin loop that emits
|
|
165
|
+
* `{ hookSpecificOutput: { hookEventName, additionalContext } }` with the
|
|
166
|
+
* current state JSON read from the SESSION cwd. Brand specifics stay in
|
|
167
|
+
* the adapter (comment lines only); the `contextSuffix` is the shared
|
|
168
|
+
* {@link HISTORY_UNRELIABLE_NOTE} from `@skillstate/core`.
|
|
169
|
+
*/
|
|
170
|
+
buildInjectScriptTemplate(options) {
|
|
157
171
|
return [
|
|
158
172
|
'#!/usr/bin/env node',
|
|
159
|
-
`// skillstate Codex hook — ${hookEventName} (generated by @skillstate/codex).`,
|
|
173
|
+
`// skillstate Codex hook — ${options.hookEventName} (generated by @skillstate/codex).`,
|
|
160
174
|
'// Self-contained CommonJS: reads one hook JSON document on stdin,',
|
|
161
175
|
'// resolves the state from the SESSION cwd, and emits',
|
|
162
176
|
'// { hookSpecificOutput: { hookEventName, additionalContext } }.',
|
|
163
|
-
header,
|
|
177
|
+
options.header,
|
|
164
178
|
"'use strict';",
|
|
165
179
|
'const fs = require("fs");',
|
|
166
180
|
'const os = require("os");',
|
|
167
181
|
'const path = require("path");',
|
|
168
182
|
'',
|
|
169
|
-
`const HOOK_EVENT_NAME = ${JSON.stringify(hookEventName)};`,
|
|
170
|
-
'',
|
|
171
|
-
'function resolveStatePathForCwd(cwd) {',
|
|
172
|
-
' const resolvedCwd = path.resolve(cwd);',
|
|
173
|
-
' const resolvedHome = path.resolve(os.homedir());',
|
|
174
|
-
' if (resolvedCwd === resolvedHome) {',
|
|
175
|
-
' return path.join(resolvedHome, ".skillstate", "global", "skillstate.json");',
|
|
176
|
-
' }',
|
|
177
|
-
' return path.join(resolvedCwd, ".skillstate", "skillstate.json");',
|
|
178
|
-
'}',
|
|
183
|
+
`const HOOK_EVENT_NAME = ${JSON.stringify(options.hookEventName)};`,
|
|
179
184
|
'',
|
|
180
|
-
|
|
181
|
-
' try {',
|
|
182
|
-
' if (fs.existsSync(statePath)) {',
|
|
183
|
-
' const parsed = JSON.parse(fs.readFileSync(statePath, "utf-8"));',
|
|
184
|
-
' if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {',
|
|
185
|
-
' if (parsed.state !== null && typeof parsed.state === "object" && !Array.isArray(parsed.state)) {',
|
|
186
|
-
' return parsed.state;',
|
|
187
|
-
' }',
|
|
188
|
-
' return parsed;',
|
|
189
|
-
' }',
|
|
190
|
-
' }',
|
|
191
|
-
' } catch (error) {}',
|
|
192
|
-
' return {};',
|
|
193
|
-
'}',
|
|
185
|
+
hookRuntimeSnippet(),
|
|
194
186
|
'',
|
|
195
187
|
'let raw = "";',
|
|
196
188
|
'process.stdin.setEncoding("utf-8");',
|
|
@@ -203,12 +195,15 @@ export class CodexAdapter {
|
|
|
203
195
|
' cwd = input.cwd;',
|
|
204
196
|
' }',
|
|
205
197
|
' } catch (error) {}',
|
|
206
|
-
' const state =
|
|
198
|
+
' const state = readStateEnvelope(',
|
|
199
|
+
' resolveStatePathForCwd(path.resolve(cwd), path.resolve(os.homedir())),',
|
|
200
|
+
' (p) => fs.readFileSync(p, "utf-8"),',
|
|
201
|
+
' );',
|
|
207
202
|
' const output = {',
|
|
208
203
|
' hookSpecificOutput: {',
|
|
209
204
|
' hookEventName: HOOK_EVENT_NAME,',
|
|
210
205
|
' additionalContext: "Current skill state (JSON): " + JSON.stringify(state)',
|
|
211
|
-
|
|
206
|
+
` + ${JSON.stringify(options.contextSuffix)},`,
|
|
212
207
|
' },',
|
|
213
208
|
' };',
|
|
214
209
|
' process.stdout.write(JSON.stringify(output));',
|
|
@@ -220,154 +215,67 @@ export class CodexAdapter {
|
|
|
220
215
|
* Generate a SKILL.md for Codex's skill directory
|
|
221
216
|
* (`~/.codex/skills/<name>/SKILL.md`). The body instructs the agent to
|
|
222
217
|
* treat the hook-injected state as authoritative (history is not
|
|
223
|
-
* reliable), to
|
|
224
|
-
* to persist via `state.patch` — the PostToolUse hook also merges any
|
|
218
|
+
* reliable), to orient via `state.summary` (full dump via `state.get`),
|
|
219
|
+
* and to persist via `state.patch` — the PostToolUse hook also merges any
|
|
225
220
|
* fenced ```json `state_patch` block printed by a Bash tool call.
|
|
226
221
|
*/
|
|
227
222
|
generateSkillMd(spec, statePath) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`version: ${spec.version}`,
|
|
235
|
-
'execution_context:',
|
|
236
|
-
` state_path: ${resolvedStatePath}`,
|
|
237
|
-
' format: json',
|
|
238
|
-
'---',
|
|
239
|
-
'',
|
|
240
|
-
`# ${spec.name}`,
|
|
241
|
-
'',
|
|
242
|
-
spec.instructions,
|
|
243
|
-
'',
|
|
244
|
-
'## Execution Context',
|
|
245
|
-
'',
|
|
246
|
-
`Your execution state lives at \`${resolvedStatePath}\` (per project; a`,
|
|
247
|
-
'session started in $HOME uses `~/.skillstate/global/skillstate.json`).',
|
|
248
|
-
'The skillstate Codex hooks:',
|
|
249
|
-
'',
|
|
250
|
-
'- inject the CURRENT state as developer context on every prompt submit',
|
|
251
|
-
' (`UserPromptSubmit`) and re-inject it after compaction',
|
|
252
|
-
' (`SessionStart` matcher `^compact$`);',
|
|
253
|
-
'- watch every Bash tool result and merge a fenced ```json block carrying',
|
|
254
|
-
' a `state_patch` into the state file (`PostToolUse`).',
|
|
255
|
-
'',
|
|
256
|
-
'The injected state is authoritative — history is not reliable. Never',
|
|
257
|
-
'reconstruct execution context from the conversation.',
|
|
258
|
-
'',
|
|
259
|
-
'## Process',
|
|
260
|
-
'',
|
|
261
|
-
'1. Read the current state from the injected context, or fetch it with',
|
|
262
|
-
' the skillstate MCP tool `state.get`.',
|
|
263
|
-
'2. Observe the result of your last action.',
|
|
264
|
-
'3. Reason about what to do next, given the state and the observation.',
|
|
265
|
-
'4. Persist progress with the skillstate MCP tool `state.patch` (sparse',
|
|
266
|
-
' patch; set a key to `null` to delete it), and/or emit a fenced JSON',
|
|
267
|
-
' block with exactly two keys inside a Bash tool call so the',
|
|
268
|
-
' `PostToolUse` hook merges it:',
|
|
269
|
-
'',
|
|
270
|
-
`${fence}json`,
|
|
271
|
-
'{',
|
|
272
|
-
' "state_patch": { "key": "new_value", "obsolete_key": null },',
|
|
273
|
-
' "action": "next_action_name"',
|
|
274
|
-
'}',
|
|
275
|
-
fence,
|
|
276
|
-
'',
|
|
277
|
-
'- In `state_patch`, set keys to `null` to delete them. Only include',
|
|
278
|
-
' fields you want to change. Omit fields to leave them unchanged.',
|
|
279
|
-
'- Put anything you need to survive into `state_patch`; never rely on',
|
|
280
|
-
' the conversation remembering it.',
|
|
281
|
-
'- `action` names what you will do next (e.g. "continue", "done").',
|
|
282
|
-
'',
|
|
283
|
-
].join('\n');
|
|
223
|
+
return skillMdBody({
|
|
224
|
+
hostLabel: 'Codex',
|
|
225
|
+
injectionPhrase: 'provided as developer context',
|
|
226
|
+
spec,
|
|
227
|
+
statePath,
|
|
228
|
+
});
|
|
284
229
|
}
|
|
285
230
|
/**
|
|
286
|
-
* Generate the hooks.json document and persist it via
|
|
287
|
-
* Both the destination and the embedded state path
|
|
288
|
-
* `{ root, name }` refs confined by
|
|
289
|
-
* absolute destination path.
|
|
231
|
+
* Generate the hooks.json document and persist it via the shared
|
|
232
|
+
* `saveGenerated`. Both the destination and the embedded state path
|
|
233
|
+
* accept raw strings or `{ root, name }` refs confined by
|
|
234
|
+
* `resolveStatePath`. Returns the absolute destination path.
|
|
290
235
|
*/
|
|
291
236
|
async saveHooksConfig(target, statePath, options) {
|
|
292
|
-
|
|
293
|
-
await atomicWriteFile(dest, this.generateHooksConfig(statePath, options));
|
|
294
|
-
return dest;
|
|
237
|
+
return saveGenerated(target, this.generateHooksConfig(statePath, options));
|
|
295
238
|
}
|
|
296
239
|
/**
|
|
297
240
|
* Merge the skillstate hook groups into an existing `hooks.json` text.
|
|
298
241
|
* Idempotent: if any skillstate command is already wired, the document is
|
|
299
242
|
* returned unchanged. Existing (non-skillstate) hooks are preserved.
|
|
243
|
+
*
|
|
244
|
+
* The mechanics live in the shared {@link mergeHookGroups}; this method
|
|
245
|
+
* supplies only the format specifics (generated hooks.json groups —
|
|
246
|
+
* with the codex `description`/`statusMessage`/`additionalContextLimit`
|
|
247
|
+
* fields — and the `node <script> <event>` skillstate commands).
|
|
300
248
|
*/
|
|
301
249
|
mergeHooksConfig(existingJson, options) {
|
|
302
|
-
let doc = {};
|
|
303
|
-
try {
|
|
304
|
-
doc = JSON.parse(existingJson);
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
// Missing or malformed user file: start from a fresh document.
|
|
308
|
-
}
|
|
309
|
-
if (!isPlainObjectDoc(doc.hooks)) {
|
|
310
|
-
doc.hooks = {};
|
|
311
|
-
}
|
|
312
250
|
const scriptDir = options?.scriptDir ?? '<stateDir>/hooks';
|
|
313
251
|
const command = (event) => `node ${JSON.stringify(this.codexHookScriptPath(scriptDir, event))} ${event}`;
|
|
314
|
-
const skillstateCommands = new Set(CODEX_HOOK_EVENTS.map((event) => JSON.stringify(command(event))));
|
|
315
|
-
let alreadyWired = false;
|
|
316
|
-
for (const groups of Object.values(doc.hooks)) {
|
|
317
|
-
if (!Array.isArray(groups))
|
|
318
|
-
continue;
|
|
319
|
-
for (const group of groups) {
|
|
320
|
-
if (!isPlainObjectDoc(group) || !Array.isArray(group['hooks']))
|
|
321
|
-
continue;
|
|
322
|
-
for (const handler of group['hooks']) {
|
|
323
|
-
if (isPlainObjectDoc(handler) && skillstateCommands.has(JSON.stringify(handler['command']))) {
|
|
324
|
-
alreadyWired = true;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (alreadyWired) {
|
|
330
|
-
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
331
|
-
}
|
|
332
252
|
const statePathRef = { root: path.dirname(options?.scriptDir ?? '.'), name: 'skillstate.json' };
|
|
333
253
|
const generated = JSON.parse(this.generateHooksConfig(statePathRef, { ...options, scriptDir }));
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
254
|
+
return mergeHookGroups({
|
|
255
|
+
existingJson,
|
|
256
|
+
generatedGroups: generated.hooks,
|
|
257
|
+
commandsOf: new Set(CODEX_HOOK_EVENTS.map((event) => JSON.stringify(command(event)))),
|
|
258
|
+
});
|
|
341
259
|
}
|
|
342
260
|
/**
|
|
343
|
-
* Generate a hook script and persist it via
|
|
344
|
-
* is the script destination (usually
|
|
261
|
+
* Generate a hook script and persist it via the shared `saveGenerated`
|
|
262
|
+
* (atomic temp-rename write). `target` is the script destination (usually
|
|
345
263
|
* {@link CodexAdapter.codexHookScriptPath}); `statePath` is forwarded to
|
|
346
264
|
* {@link generateHookScript}. Returns the absolute destination path.
|
|
347
265
|
*/
|
|
348
266
|
async saveHookScript(event, target, statePath) {
|
|
349
|
-
|
|
350
|
-
await atomicWriteFile(dest, this.generateHookScript(event, statePath));
|
|
351
|
-
return dest;
|
|
267
|
+
return saveGenerated(target, this.generateHookScript(event, statePath));
|
|
352
268
|
}
|
|
353
269
|
/**
|
|
354
|
-
* Generate a SKILL.md and persist it via `
|
|
355
|
-
* absolute destination path.
|
|
270
|
+
* Generate a SKILL.md and persist it via the shared `saveGenerated`.
|
|
271
|
+
* Returns the absolute destination path.
|
|
356
272
|
*/
|
|
357
273
|
async saveSkillMd(target, spec, statePath) {
|
|
358
|
-
|
|
359
|
-
await atomicWriteFile(dest, this.generateSkillMd(spec, statePath));
|
|
360
|
-
return dest;
|
|
274
|
+
return saveGenerated(target, this.generateSkillMd(spec, statePath));
|
|
361
275
|
}
|
|
362
276
|
/* ------------------------------------------------------------------ */
|
|
363
277
|
/* Internal helpers */
|
|
364
278
|
/* ------------------------------------------------------------------ */
|
|
365
|
-
/** Resolve a `string | StatePathRef` via `resolveStatePath` (throws on `..`). */
|
|
366
|
-
resolve(target) {
|
|
367
|
-
return typeof target === 'string'
|
|
368
|
-
? target
|
|
369
|
-
: resolveStatePath(target.root, target.name);
|
|
370
|
-
}
|
|
371
279
|
/** PostToolUse script: extract state_patch, ⊕ merge, persist. */
|
|
372
280
|
buildPostToolUseScript(header) {
|
|
373
281
|
const fence = '```';
|
|
@@ -384,109 +292,9 @@ export class CodexAdapter {
|
|
|
384
292
|
'const os = require("os");',
|
|
385
293
|
'const path = require("path");',
|
|
386
294
|
'',
|
|
387
|
-
'function isPlainObject(value) {',
|
|
388
|
-
' return typeof value === "object" && value !== null && !Array.isArray(value);',
|
|
389
|
-
'}',
|
|
390
|
-
'',
|
|
391
|
-
'function resolveStatePathForCwd(cwd) {',
|
|
392
|
-
' const resolvedCwd = path.resolve(cwd);',
|
|
393
|
-
' const resolvedHome = path.resolve(os.homedir());',
|
|
394
|
-
' if (resolvedCwd === resolvedHome) {',
|
|
395
|
-
' return path.join(resolvedHome, ".skillstate", "global", "skillstate.json");',
|
|
396
|
-
' }',
|
|
397
|
-
' return path.join(resolvedCwd, ".skillstate", "skillstate.json");',
|
|
398
|
-
'}',
|
|
399
|
-
'',
|
|
400
|
-
'function readState(statePath) {',
|
|
401
|
-
' try {',
|
|
402
|
-
' if (fs.existsSync(statePath)) {',
|
|
403
|
-
' const parsed = JSON.parse(fs.readFileSync(statePath, "utf-8"));',
|
|
404
|
-
' if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {',
|
|
405
|
-
' if (parsed.state !== null && typeof parsed.state === "object" && !Array.isArray(parsed.state)) {',
|
|
406
|
-
' return parsed.state;',
|
|
407
|
-
' }',
|
|
408
|
-
' return parsed;',
|
|
409
|
-
' }',
|
|
410
|
-
' }',
|
|
411
|
-
' } catch (error) {}',
|
|
412
|
-
' return {};',
|
|
413
|
-
'}',
|
|
414
|
-
'',
|
|
415
|
-
'// Paper ⊕ merge: null deletes a key, nested plain objects merge',
|
|
416
|
-
'// recursively, everything else replaces.',
|
|
417
|
-
'function mergePatch(base, patch) {',
|
|
418
|
-
' const result = { ...base };',
|
|
419
|
-
' for (const key of Object.keys(patch)) {',
|
|
420
|
-
' const value = patch[key];',
|
|
421
|
-
' if (value === null) {',
|
|
422
|
-
' delete result[key];',
|
|
423
|
-
' } else if (isPlainObject(value) && isPlainObject(result[key])) {',
|
|
424
|
-
' result[key] = mergePatch(result[key], value);',
|
|
425
|
-
' } else {',
|
|
426
|
-
' result[key] = value;',
|
|
427
|
-
' }',
|
|
428
|
-
' }',
|
|
429
|
-
' return result;',
|
|
430
|
-
'}',
|
|
431
|
-
'',
|
|
432
|
-
'function readResponseText(response) {',
|
|
433
|
-
' if (typeof response === "string") return response;',
|
|
434
|
-
' if (isPlainObject(response)) {',
|
|
435
|
-
' if (typeof response.content === "string") return response.content;',
|
|
436
|
-
' if (typeof response.text === "string") return response.text;',
|
|
437
|
-
' return JSON.stringify(response);',
|
|
438
|
-
' }',
|
|
439
|
-
' return response === null || response === undefined ? "" : String(response);',
|
|
440
|
-
'}',
|
|
441
|
-
'',
|
|
442
295
|
`const FENCE = ${JSON.stringify(fence)};`,
|
|
443
|
-
'const FENCE_RE = /' + '```' + 'json\\s*\\n?([\\s\\S]*?)\\n?\\s*' + '```' + '/;',
|
|
444
|
-
'// An open fence that never closes (truncated output) is still a patch',
|
|
445
|
-
'// attempt: match from ```json to end-of-text so it classifies as invalid.',
|
|
446
|
-
'const OPEN_FENCE_RE = /' + '```' + 'json\\s*\\n?([\\s\\S]+)$/',
|
|
447
|
-
'',
|
|
448
|
-
'// Look for a fenced ```json block: { patch } when it parses and carries',
|
|
449
|
-
'// an object state_patch, { invalid: true } when a block exists but is',
|
|
450
|
-
'// malformed, { absent: true } when there is no block at all.',
|
|
451
|
-
'function findFencedPatch(text) {',
|
|
452
|
-
' const match = text.match(FENCE_RE) || text.match(OPEN_FENCE_RE);',
|
|
453
|
-
' if (!match) return { absent: true };',
|
|
454
|
-
' try {',
|
|
455
|
-
' const parsed = JSON.parse(match[1]);',
|
|
456
|
-
' if (isPlainObject(parsed) && isPlainObject(parsed.state_patch)) {',
|
|
457
|
-
' return { patch: parsed.state_patch };',
|
|
458
|
-
' }',
|
|
459
|
-
' } catch (error) {}',
|
|
460
|
-
' return { invalid: true };',
|
|
461
|
-
'}',
|
|
462
296
|
'',
|
|
463
|
-
|
|
464
|
-
'// Ordinary JSON output without a state_patch key is simply not a patch.',
|
|
465
|
-
'function findRawPatch(text) {',
|
|
466
|
-
' const trimmed = text.trim();',
|
|
467
|
-
' const candidates = [];',
|
|
468
|
-
' try {',
|
|
469
|
-
' candidates.push(JSON.parse(trimmed));',
|
|
470
|
-
' } catch (error) {}',
|
|
471
|
-
' const first = trimmed.indexOf("{");',
|
|
472
|
-
' const last = trimmed.lastIndexOf("}");',
|
|
473
|
-
' if (first !== -1 && last > first) {',
|
|
474
|
-
' try {',
|
|
475
|
-
' candidates.push(JSON.parse(trimmed.slice(first, last + 1)));',
|
|
476
|
-
' } catch (error) {}',
|
|
477
|
-
' }',
|
|
478
|
-
' for (const candidate of candidates) {',
|
|
479
|
-
' if (isPlainObject(candidate)) {',
|
|
480
|
-
' if (isPlainObject(candidate.state_patch)) {',
|
|
481
|
-
' return { patch: candidate.state_patch };',
|
|
482
|
-
' }',
|
|
483
|
-
' if (Object.prototype.hasOwnProperty.call(candidate, "state_patch")) {',
|
|
484
|
-
' return { invalid: true };',
|
|
485
|
-
' }',
|
|
486
|
-
' }',
|
|
487
|
-
' }',
|
|
488
|
-
' return { absent: true };',
|
|
489
|
-
'}',
|
|
297
|
+
hookRuntimeSnippet(),
|
|
490
298
|
'',
|
|
491
299
|
'let raw = "";',
|
|
492
300
|
'process.stdin.setEncoding("utf-8");',
|
|
@@ -499,7 +307,7 @@ export class CodexAdapter {
|
|
|
499
307
|
' if (typeof input.cwd === "string" && input.cwd.length > 0) {',
|
|
500
308
|
' cwd = input.cwd;',
|
|
501
309
|
' }',
|
|
502
|
-
' const statePath = resolveStatePathForCwd(cwd);',
|
|
310
|
+
' const statePath = resolveStatePathForCwd(path.resolve(cwd), path.resolve(os.homedir()));',
|
|
503
311
|
' const response = input.tool_response;',
|
|
504
312
|
' let result;',
|
|
505
313
|
' if (isPlainObject(response) && isPlainObject(response.state_patch)) {',
|
|
@@ -510,10 +318,13 @@ export class CodexAdapter {
|
|
|
510
318
|
' if (result.absent) result = findRawPatch(text);',
|
|
511
319
|
' }',
|
|
512
320
|
' if (result.patch !== undefined) {',
|
|
513
|
-
' const merged = mergePatch(
|
|
321
|
+
' const merged = mergePatch(',
|
|
322
|
+
' readStateEnvelope(statePath, (p) => fs.readFileSync(p, "utf-8")),',
|
|
323
|
+
' result.patch,',
|
|
324
|
+
' );',
|
|
514
325
|
' try {',
|
|
515
326
|
' fs.mkdirSync(path.dirname(statePath), { recursive: true });',
|
|
516
|
-
'
|
|
327
|
+
' saveStateEnvelope(statePath, merged, (p, data) => fs.writeFileSync(p, data));',
|
|
517
328
|
' } catch (writeError) {',
|
|
518
329
|
" output.systemMessage = 'skillstate: failed to persist state (' + writeError.message + ')';",
|
|
519
330
|
' }',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../src/codex-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"codex-adapter.js","sourceRoot":"","sources":["../src/codex-adapter.ts"],"names":[],"mappings":"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"}
|