@skillstate/opencode 2.0.7 → 2.2.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/opencode-adapter.d.ts +0 -4
- package/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js +7 -34
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/plugin.d.ts +45 -29
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +88 -92
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
|
@@ -79,9 +79,5 @@ export declare class OpenCodeAdapter implements PlatformAdapter {
|
|
|
79
79
|
* Returns the absolute destination path.
|
|
80
80
|
*/
|
|
81
81
|
savePluginCode(target: string | StatePathRef, options?: GeneratePluginOptions): Promise<string>;
|
|
82
|
-
/**
|
|
83
|
-
* Describe the schema fields for inclusion in prompts.
|
|
84
|
-
*/
|
|
85
|
-
private describeSchema;
|
|
86
82
|
}
|
|
87
83
|
//# sourceMappingURL=opencode-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-adapter.d.ts","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,eAAe,EAChB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"opencode-adapter.d.ts","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,WAAW,EACX,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAW1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,eAAe;IACrD,QAAQ,CAAC,IAAI,cAAc;IAE3B,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAe3D;IAED;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAEhD;IAED;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7C;IAED;;;;;OAKG;IACH,YAAY,CACV,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,cAAc,GACnB,MAAM,CAER;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAkChE;IAED;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAa1D;IAED;;;;;OAKG;IACG,cAAc,CAClB,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAOjB;CAKF"}
|
package/dist/opencode-adapter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PromptTransformer } from '@skillstate/core';
|
|
2
|
-
import { atomicWriteFile, resolveStatePath, } from '@skillstate/core';
|
|
2
|
+
import { atomicWriteFile, describeSchema, REASONING_DISCARDED_NOTE, resolveStatePath, STATE_PATCH_CONTRACT, } from '@skillstate/core';
|
|
3
3
|
/**
|
|
4
4
|
* OpenCode platform adapter (@non-paper; see module doc).
|
|
5
5
|
*/
|
|
@@ -13,7 +13,7 @@ export class OpenCodeAdapter {
|
|
|
13
13
|
*/
|
|
14
14
|
injectState(state, spec) {
|
|
15
15
|
const stateJson = JSON.stringify(state);
|
|
16
|
-
const schemaDesc =
|
|
16
|
+
const schemaDesc = describeSchema(spec.schema);
|
|
17
17
|
return `<skill name="${spec.id}">
|
|
18
18
|
<instructions>${spec.instructions}</instructions>
|
|
19
19
|
${schemaDesc}
|
|
@@ -22,16 +22,9 @@ ${stateJson}
|
|
|
22
22
|
</state>
|
|
23
23
|
</skill>
|
|
24
24
|
|
|
25
|
-
Respond with
|
|
25
|
+
Respond with:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
"state_patch": { "key": "new_value", "obsolete_key": null },
|
|
30
|
-
"action": "your_action_here"
|
|
31
|
-
}
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
In \`state_patch\`, set keys to null to delete them. Only include fields you want to change. Omit fields to leave them unchanged.`;
|
|
27
|
+
${STATE_PATCH_CONTRACT}`;
|
|
35
28
|
}
|
|
36
29
|
/**
|
|
37
30
|
* Extract the state_patch from an LLM response containing a fenced JSON block.
|
|
@@ -89,20 +82,12 @@ plugin; only the current state and the latest observation matter.
|
|
|
89
82
|
1. Read the current state from the state file.
|
|
90
83
|
2. Observe the result of your last action.
|
|
91
84
|
3. Reason about what to do next, given the state and the observation.
|
|
92
|
-
4. Respond with
|
|
85
|
+
4. Respond with:
|
|
93
86
|
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
"state_patch": { "key": "new_value", "obsolete_key": null },
|
|
97
|
-
"action": "next_action_name"
|
|
98
|
-
}
|
|
99
|
-
\`\`\`
|
|
87
|
+
${STATE_PATCH_CONTRACT}
|
|
100
88
|
|
|
101
|
-
- In \`state_patch\`, set keys to null to delete them. Only include fields you
|
|
102
|
-
want to change. Omit fields to leave them unchanged.
|
|
103
89
|
- \`action\` names what you will do next (e.g. "continue", "done").
|
|
104
|
-
-
|
|
105
|
-
into \`state_patch\`.`;
|
|
90
|
+
- ${REASONING_DISCARDED_NOTE}`;
|
|
106
91
|
}
|
|
107
92
|
/**
|
|
108
93
|
* Generate an OpenCode plugin file.
|
|
@@ -147,17 +132,5 @@ export default createSkillStatePlugin({
|
|
|
147
132
|
await atomicWriteFile(dest, this.generatePluginCode(options));
|
|
148
133
|
return dest;
|
|
149
134
|
}
|
|
150
|
-
/* ------------------------------------------------------------------ */
|
|
151
|
-
/* Internal helpers */
|
|
152
|
-
/* ------------------------------------------------------------------ */
|
|
153
|
-
/**
|
|
154
|
-
* Describe the schema fields for inclusion in prompts.
|
|
155
|
-
*/
|
|
156
|
-
describeSchema(schema) {
|
|
157
|
-
const fields = Object.entries(schema)
|
|
158
|
-
.map(([name, field]) => `- ${name} (${field.type}): ${field.description ?? 'no description'}`)
|
|
159
|
-
.join('\n');
|
|
160
|
-
return `## Schema\n${fields}`;
|
|
161
|
-
}
|
|
162
135
|
}
|
|
163
136
|
//# sourceMappingURL=opencode-adapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-adapter.js","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,gBAAgB,
|
|
1
|
+
{"version":3,"file":"opencode-adapter.js","sourceRoot":"","sources":["../src/opencode-adapter.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EACL,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,GAGrB,MAAM,kBAAkB,CAAC;AAS1B;;GAEG;AACH,MAAM,OAAO,eAAe;IACjB,IAAI,GAAG,UAAU,CAAC;IAEnB,WAAW,GAAG,IAAI,iBAAiB,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtE;;;;OAIG;IACH,WAAW,CAAC,KAAiB,EAAE,IAAoB;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE/C,OAAO,gBAAgB,IAAI,CAAC,EAAE;gBAClB,IAAI,CAAC,YAAY;EAC/B,UAAU;;EAEV,SAAS;;;;;;EAMT,oBAAoB,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,QAAgB;QAC3B,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAgB;QAC5B,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;IAED;;;;;OAKG;IACH,YAAY,CACV,KAAiB,EACjB,WAAwB,EACxB,IAAoB;QAEpB,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;;OAOG;IACH,eAAe,CAAC,IAAoB,EAAE,SAAkB;QACtD,MAAM,iBAAiB,GAAG,SAAS,IAAI,oBAAoB,CAAC;QAE5D,OAAO;QACH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;eAClB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC;WACrC,IAAI,CAAC,OAAO;;gBAEP,iBAAiB;;;;IAI7B,IAAI,CAAC,IAAI;;EAEX,IAAI,CAAC,YAAY;;;;yCAIsB,iBAAiB;;;;;;;;;;;;EAYxD,oBAAoB;;;IAGlB,wBAAwB,EAAE,CAAC;IAC7B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,OAA+B;QAChD,MAAM,UAAU,GAAG,OAAO,EAAE,kBAAkB,IAAI,CAAC,CAAC;QACpD,OAAO;;;;;;;;wBAQa,UAAU;;CAEjC,CAAC;IACA,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAClB,MAA6B,EAC7B,OAA+B;QAE/B,MAAM,IAAI,GACR,OAAO,MAAM,KAAK,QAAQ;YACxB,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9D,OAAO,IAAI,CAAC;IACd,CAAC;CAKF"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,61 +1,77 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { mergePatch, resolveHostStateForCwd } from '@skillstate/core';
|
|
2
|
+
import type { OpenCodeMessage, SkillStatePlugin } from './plugin-types.js';
|
|
2
3
|
export * from './plugin-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the per-project state file for a session working directory
|
|
6
|
+
* (`cwd` of the current opencode session) — the core single source of
|
|
7
|
+
* truth (`resolveHostStateForCwd`): `<cwd>/.skillstate/skillstate.json`,
|
|
8
|
+
* or the global bucket `<home>/.skillstate/global/skillstate.json` when
|
|
9
|
+
* cwd equals home. A non-empty `agentId` scopes the file under
|
|
10
|
+
* `<bucket>/agents/<agentId>/skillstate.json`. Pure path arithmetic via
|
|
11
|
+
* `path.resolve`, no filesystem access.
|
|
12
|
+
*/
|
|
13
|
+
export { resolveHostStateForCwd as resolveStatePathForCwd };
|
|
14
|
+
export { mergePatch };
|
|
15
|
+
/** Agent scope used by the plugin when no session id is available. */
|
|
16
|
+
export declare const PLUGIN_DEFAULT_AGENT_ID = "default";
|
|
3
17
|
/** Options for {@link createSkillStatePlugin}. */
|
|
4
18
|
export interface SkillStatePluginOptions {
|
|
5
19
|
/** Non-system messages kept in the prompt (default 3). */
|
|
6
20
|
maxHistoryMessages?: number;
|
|
7
21
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Resolve the per-project state file for a session working directory
|
|
10
|
-
* (`cwd` of the current opencode session):
|
|
11
|
-
*
|
|
12
|
-
* - `cwd === home` — a session launched straight from `$HOME` has no single
|
|
13
|
-
* project, so state goes to the global bucket
|
|
14
|
-
* `<home>/.skillstate/global/skillstate.json`;
|
|
15
|
-
* - any other cwd (including subdirectories of `$HOME`) — the state lives in
|
|
16
|
-
* the project: `<cwd>/.skillstate/skillstate.json`.
|
|
17
|
-
*
|
|
18
|
-
* Pure path arithmetic: both arguments are normalized via `path.resolve`
|
|
19
|
-
* before comparison, and there is NO filesystem access. The same project
|
|
20
|
-
* directory therefore always maps to the same state file no matter where
|
|
21
|
-
* the host was launched from, while different projects never share state.
|
|
22
|
-
* Zero-dep by design (node builtins only) so generated plugins and the MCP
|
|
23
|
-
* server can inline the same semantics. Keep any copy in sync.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveStatePathForCwd(cwd: string, home: string): string;
|
|
26
22
|
/**
|
|
27
23
|
* Read the state file. Missing or corrupt files yield `{}` (best-effort).
|
|
28
24
|
* The on-disk envelope is `{ version: 1, state }` (migrations-compatible);
|
|
29
|
-
* a bare object is tolerated and treated as the state itself.
|
|
25
|
+
* a bare object is tolerated and treated as the state itself. Thin fs
|
|
26
|
+
* adapter over the core hook-runtime {@link readStateEnvelope}.
|
|
30
27
|
*/
|
|
31
28
|
export declare function readSkillState(statePath: string): Record<string, unknown>;
|
|
32
29
|
/**
|
|
33
30
|
* Persist the state file (best-effort: read-only environments are ignored).
|
|
34
31
|
* Creates the parent directory when missing (the per-project resolver may
|
|
35
|
-
* target a fresh `<cwd>/.skillstate
|
|
36
|
-
* envelope so `migrate()`/runtime resume read the
|
|
32
|
+
* target a fresh `<cwd>/.skillstate/agents/<id>/`). Writes the
|
|
33
|
+
* `{ version: 1, state }` envelope so `migrate()`/runtime resume read the
|
|
34
|
+
* same file — via the core hook-runtime {@link saveStateEnvelope} — under
|
|
35
|
+
* the cross-process sync lock {@link lockStateWrite} (2-3 parallel agent
|
|
36
|
+
* processes never interleave state writes).
|
|
37
37
|
*/
|
|
38
38
|
export declare function saveSkillState(statePath: string, state: Record<string, unknown>): void;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Atomic READ-MERGE-WRITE of one `state_patch` (paper ⊕: null deletes):
|
|
41
|
+
* the whole critical section runs inside {@link lockStateWrite}, so two
|
|
42
|
+
* concurrent writers apply BOTH patches instead of racing between the
|
|
43
|
+
* read and the write. Best-effort: lock contention or unwritable state
|
|
44
|
+
* files are swallowed — the tool flow never breaks.
|
|
42
45
|
*/
|
|
43
|
-
export declare function
|
|
46
|
+
export declare function mergeSkillState(statePath: string, patch: Record<string, unknown>): Record<string, unknown>;
|
|
44
47
|
/**
|
|
45
48
|
* Extract the `state_patch` object from an LLM response's fenced ```json
|
|
46
49
|
* block; `null` when there is no block, it is malformed, or it carries no
|
|
47
|
-
* object-shaped `state_patch`.
|
|
50
|
+
* object-shaped `state_patch`. Thin adapter over the core hook-runtime
|
|
51
|
+
* {@link findFencedPatch} (the invalid/truncated outcomes collapse to
|
|
52
|
+
* `null`, preserving the legacy boolean contract).
|
|
48
53
|
*/
|
|
49
54
|
export declare function extractPatch(response: string): Record<string, unknown> | null;
|
|
55
|
+
/**
|
|
56
|
+
* Agent id for an opencode hook call: the 8-char session prefix from
|
|
57
|
+
* `input.sessionID` when the hook carries it, else the first non-synthetic
|
|
58
|
+
* message's `info.sessionID` (the transform hook input is empty — the
|
|
59
|
+
* session lives on the message envelopes), else `'default'` (the plugin
|
|
60
|
+
* trims the history of one session context).
|
|
61
|
+
*/
|
|
62
|
+
export declare function pluginAgentId(input: {
|
|
63
|
+
sessionID?: unknown;
|
|
64
|
+
}, messages?: OpenCodeMessage[]): string;
|
|
50
65
|
/**
|
|
51
66
|
* Build the OpenCode plugin function with the same behavior for every host
|
|
52
67
|
* entry point (thin generated loaders, direct imports).
|
|
53
68
|
*
|
|
54
69
|
* State resolution is ALWAYS per-project: the state file path is computed
|
|
55
70
|
* from the session cwd on EVERY hook call via
|
|
56
|
-
* `resolveStatePathForCwd(process.cwd(), os.homedir())` — each
|
|
57
|
-
* its own `<cwd>/.skillstate
|
|
58
|
-
*
|
|
71
|
+
* `resolveStatePathForCwd(process.cwd(), os.homedir(), agentId)` — each
|
|
72
|
+
* project gets its own `<cwd>/.skillstate/`, each session (sub-agent) its
|
|
73
|
+
* isolated `agents/<session>/` copy, and a session launched from `$HOME`
|
|
74
|
+
* uses the global bucket.
|
|
59
75
|
*/
|
|
60
76
|
export declare function createSkillStatePlugin(options?: SkillStatePluginOptions): SkillStatePlugin;
|
|
61
77
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,UAAU,EAGV,sBAAsB,EAEvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,eAAe,EAEf,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,cAAc,mBAAmB,CAAC;AAElC;;;;;;;;GAQG;AACH,OAAO,EAAE,sBAAsB,IAAI,sBAAsB,EAAE,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,YAAY,CAAC;AAEjD,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWtF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAG7E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9B,QAAQ,CAAC,EAAE,eAAe,EAAE,GAC3B,MAAM,CAWR;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAiF9F"}
|
package/dist/plugin.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Static OpenCode plugin — the SINGLE SOURCE OF TRUTH for the skillstate
|
|
3
3
|
* host integration. `OpenCodeAdapter.generatePluginCode` emits a thin loader
|
|
4
4
|
* that imports `createSkillStatePlugin` from this module; the per-project
|
|
5
|
-
* state resolution lives
|
|
5
|
+
* state resolution lives in `@skillstate/core`
|
|
6
|
+
* (`resolveHostStateForCwd`, re-exported here) and the hook logic
|
|
7
|
+
* (envelope read/write, ⊕ merge, patch extraction) in the core
|
|
8
|
+
* hook-runtime — this module only adapts it to the OpenCode hooks.
|
|
6
9
|
*
|
|
7
10
|
* Hooks (opencode 1.17 contract, verified on host):
|
|
8
11
|
* - `experimental.chat.messages.transform` — entries are `{ info, parts }`
|
|
@@ -13,120 +16,109 @@
|
|
|
13
16
|
* `output.context` so the compaction summary preserves it.
|
|
14
17
|
* - `tool.execute.after` — the tool response is `output.output`; a fenced
|
|
15
18
|
* ```json `state_patch` block is merged (paper ⊕: null deletes) and saved.
|
|
19
|
+
*
|
|
20
|
+
* AGENT-SCOPED STATE: the opencode hook inputs carry the session id
|
|
21
|
+
* (`input.sessionID`; message envelopes carry `info.sessionID`), so every
|
|
22
|
+
* hook scopes the state file to `<cwd>/.skillstate/agents/<session>/` —
|
|
23
|
+
* parallel opencode sessions (sub-agents) never last-writer-win over the
|
|
24
|
+
* main state. When no session id is available the `'default'` agent is
|
|
25
|
+
* used (the plugin trims the history of one session context). Writes go
|
|
26
|
+
* through the core cross-process sync lock (`lockStateWrite`) so a
|
|
27
|
+
* session state file is never interleaved between processes.
|
|
16
28
|
*/
|
|
17
29
|
import * as fs from 'node:fs';
|
|
18
30
|
import * as os from 'node:os';
|
|
19
31
|
import * as path from 'node:path';
|
|
32
|
+
import { findFencedPatch, lockStateWrite, mergePatch, readStateEnvelope, resolveAgentIdFromSession, resolveHostStateForCwd, saveStateEnvelope, } from '@skillstate/core';
|
|
20
33
|
export * from './plugin-types.js';
|
|
21
34
|
/**
|
|
22
35
|
* Resolve the per-project state file for a session working directory
|
|
23
|
-
* (`cwd` of the current opencode session)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* the project: `<cwd>/.skillstate/skillstate.json`.
|
|
30
|
-
*
|
|
31
|
-
* Pure path arithmetic: both arguments are normalized via `path.resolve`
|
|
32
|
-
* before comparison, and there is NO filesystem access. The same project
|
|
33
|
-
* directory therefore always maps to the same state file no matter where
|
|
34
|
-
* the host was launched from, while different projects never share state.
|
|
35
|
-
* Zero-dep by design (node builtins only) so generated plugins and the MCP
|
|
36
|
-
* server can inline the same semantics. Keep any copy in sync.
|
|
36
|
+
* (`cwd` of the current opencode session) — the core single source of
|
|
37
|
+
* truth (`resolveHostStateForCwd`): `<cwd>/.skillstate/skillstate.json`,
|
|
38
|
+
* or the global bucket `<home>/.skillstate/global/skillstate.json` when
|
|
39
|
+
* cwd equals home. A non-empty `agentId` scopes the file under
|
|
40
|
+
* `<bucket>/agents/<agentId>/skillstate.json`. Pure path arithmetic via
|
|
41
|
+
* `path.resolve`, no filesystem access.
|
|
37
42
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return path.join(resolvedHome, '.skillstate', 'global', 'skillstate.json');
|
|
43
|
-
}
|
|
44
|
-
return path.join(resolvedCwd, '.skillstate', 'skillstate.json');
|
|
45
|
-
}
|
|
46
|
-
/** True for plain (non-null, non-array) objects. */
|
|
47
|
-
function isPlainObject(value) {
|
|
48
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
49
|
-
}
|
|
43
|
+
export { resolveHostStateForCwd as resolveStatePathForCwd };
|
|
44
|
+
export { mergePatch };
|
|
45
|
+
/** Agent scope used by the plugin when no session id is available. */
|
|
46
|
+
export const PLUGIN_DEFAULT_AGENT_ID = 'default';
|
|
50
47
|
/**
|
|
51
48
|
* Read the state file. Missing or corrupt files yield `{}` (best-effort).
|
|
52
49
|
* The on-disk envelope is `{ version: 1, state }` (migrations-compatible);
|
|
53
|
-
* a bare object is tolerated and treated as the state itself.
|
|
50
|
+
* a bare object is tolerated and treated as the state itself. Thin fs
|
|
51
|
+
* adapter over the core hook-runtime {@link readStateEnvelope}.
|
|
54
52
|
*/
|
|
55
53
|
export function readSkillState(statePath) {
|
|
56
|
-
|
|
57
|
-
if (fs.existsSync(statePath)) {
|
|
58
|
-
const parsed = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
59
|
-
if (typeof parsed === 'object' &&
|
|
60
|
-
parsed !== null &&
|
|
61
|
-
'state' in parsed &&
|
|
62
|
-
typeof parsed['state'] === 'object' &&
|
|
63
|
-
parsed['state'] !== null) {
|
|
64
|
-
return parsed['state'];
|
|
65
|
-
}
|
|
66
|
-
if (typeof parsed === 'object' && parsed !== null) {
|
|
67
|
-
return parsed;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
// Corrupt or unreadable state file — fall back to empty state.
|
|
73
|
-
}
|
|
74
|
-
return {};
|
|
54
|
+
return readStateEnvelope(statePath, (p) => fs.readFileSync(p, 'utf-8'));
|
|
75
55
|
}
|
|
76
56
|
/**
|
|
77
57
|
* Persist the state file (best-effort: read-only environments are ignored).
|
|
78
58
|
* Creates the parent directory when missing (the per-project resolver may
|
|
79
|
-
* target a fresh `<cwd>/.skillstate
|
|
80
|
-
* envelope so `migrate()`/runtime resume read the
|
|
59
|
+
* target a fresh `<cwd>/.skillstate/agents/<id>/`). Writes the
|
|
60
|
+
* `{ version: 1, state }` envelope so `migrate()`/runtime resume read the
|
|
61
|
+
* same file — via the core hook-runtime {@link saveStateEnvelope} — under
|
|
62
|
+
* the cross-process sync lock {@link lockStateWrite} (2-3 parallel agent
|
|
63
|
+
* processes never interleave state writes).
|
|
81
64
|
*/
|
|
82
65
|
export function saveSkillState(statePath, state) {
|
|
83
66
|
try {
|
|
84
67
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
85
|
-
|
|
68
|
+
lockStateWrite(statePath, fs, () => saveStateEnvelope(statePath, state, (p, data) => fs.writeFileSync(p, data)));
|
|
86
69
|
}
|
|
87
70
|
catch {
|
|
88
71
|
// Best-effort: read-only environments or permission issues.
|
|
89
72
|
}
|
|
90
73
|
}
|
|
91
74
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
75
|
+
* Atomic READ-MERGE-WRITE of one `state_patch` (paper ⊕: null deletes):
|
|
76
|
+
* the whole critical section runs inside {@link lockStateWrite}, so two
|
|
77
|
+
* concurrent writers apply BOTH patches instead of racing between the
|
|
78
|
+
* read and the write. Best-effort: lock contention or unwritable state
|
|
79
|
+
* files are swallowed — the tool flow never breaks.
|
|
94
80
|
*/
|
|
95
|
-
export function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
81
|
+
export function mergeSkillState(statePath, patch) {
|
|
82
|
+
try {
|
|
83
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
84
|
+
let merged = {};
|
|
85
|
+
lockStateWrite(statePath, fs, () => {
|
|
86
|
+
merged = mergePatch(readSkillState(statePath), patch);
|
|
87
|
+
saveStateEnvelope(statePath, merged, (p, data) => fs.writeFileSync(p, data));
|
|
88
|
+
});
|
|
89
|
+
return merged;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return readSkillState(statePath);
|
|
108
93
|
}
|
|
109
|
-
return result;
|
|
110
94
|
}
|
|
111
95
|
/**
|
|
112
96
|
* Extract the `state_patch` object from an LLM response's fenced ```json
|
|
113
97
|
* block; `null` when there is no block, it is malformed, or it carries no
|
|
114
|
-
* object-shaped `state_patch`.
|
|
98
|
+
* object-shaped `state_patch`. Thin adapter over the core hook-runtime
|
|
99
|
+
* {@link findFencedPatch} (the invalid/truncated outcomes collapse to
|
|
100
|
+
* `null`, preserving the legacy boolean contract).
|
|
115
101
|
*/
|
|
116
102
|
export function extractPatch(response) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
103
|
+
const result = findFencedPatch(response);
|
|
104
|
+
return 'patch' in result ? result.patch : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Agent id for an opencode hook call: the 8-char session prefix from
|
|
108
|
+
* `input.sessionID` when the hook carries it, else the first non-synthetic
|
|
109
|
+
* message's `info.sessionID` (the transform hook input is empty — the
|
|
110
|
+
* session lives on the message envelopes), else `'default'` (the plugin
|
|
111
|
+
* trims the history of one session context).
|
|
112
|
+
*/
|
|
113
|
+
export function pluginAgentId(input, messages) {
|
|
114
|
+
const direct = resolveAgentIdFromSession(input?.sessionID);
|
|
115
|
+
if (direct.length > 0)
|
|
116
|
+
return direct;
|
|
117
|
+
const fromMessages = (messages ?? []).find((m) => typeof m.info?.sessionID === 'string' &&
|
|
118
|
+
m.info.sessionID.length > 0 &&
|
|
119
|
+
m.info.sessionID !== 'skillstate');
|
|
120
|
+
const indirect = resolveAgentIdFromSession(fromMessages?.info.sessionID);
|
|
121
|
+
return indirect.length > 0 ? indirect : PLUGIN_DEFAULT_AGENT_ID;
|
|
130
122
|
}
|
|
131
123
|
/** Synthetic message ids for the injected state carrier. */
|
|
132
124
|
const STATE_MESSAGE_ID = 'skillstate-state-inject';
|
|
@@ -136,12 +128,13 @@ const STATE_MESSAGE_ID = 'skillstate-state-inject';
|
|
|
136
128
|
*
|
|
137
129
|
* State resolution is ALWAYS per-project: the state file path is computed
|
|
138
130
|
* from the session cwd on EVERY hook call via
|
|
139
|
-
* `resolveStatePathForCwd(process.cwd(), os.homedir())` — each
|
|
140
|
-
* its own `<cwd>/.skillstate
|
|
141
|
-
*
|
|
131
|
+
* `resolveStatePathForCwd(process.cwd(), os.homedir(), agentId)` — each
|
|
132
|
+
* project gets its own `<cwd>/.skillstate/`, each session (sub-agent) its
|
|
133
|
+
* isolated `agents/<session>/` copy, and a session launched from `$HOME`
|
|
134
|
+
* uses the global bucket.
|
|
142
135
|
*/
|
|
143
136
|
export function createSkillStatePlugin(options = {}) {
|
|
144
|
-
const resolvePath = () =>
|
|
137
|
+
const resolvePath = (agentId) => resolveHostStateForCwd(process.cwd(), os.homedir(), agentId);
|
|
145
138
|
const maxHistory = options.maxHistoryMessages ?? 3;
|
|
146
139
|
return async () => {
|
|
147
140
|
return {
|
|
@@ -150,8 +143,9 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
150
143
|
// plus the last `maxHistory` non-system messages, then injects a
|
|
151
144
|
// synthetic state element. Old messages are DROPPED from the prompt,
|
|
152
145
|
// not just hidden.
|
|
153
|
-
'experimental.chat.messages.transform': async (
|
|
154
|
-
const
|
|
146
|
+
'experimental.chat.messages.transform': async (input, output) => {
|
|
147
|
+
const agentId = pluginAgentId(input, output.messages);
|
|
148
|
+
const state = readSkillState(resolvePath(agentId));
|
|
155
149
|
const messages = output.messages;
|
|
156
150
|
const systemMessages = messages.filter((m) => m.info.role === 'system');
|
|
157
151
|
const trimmed = messages
|
|
@@ -188,8 +182,9 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
188
182
|
// ── Compaction context injection ───────────────────────────────────
|
|
189
183
|
// Before compaction, inject the current state into the context so the
|
|
190
184
|
// compaction summary preserves state even after history is compressed.
|
|
191
|
-
'experimental.session.compacting': async (
|
|
192
|
-
const
|
|
185
|
+
'experimental.session.compacting': async (input, output) => {
|
|
186
|
+
const agentId = pluginAgentId(input);
|
|
187
|
+
const state = readSkillState(resolvePath(agentId));
|
|
193
188
|
if (!Array.isArray(output.context)) {
|
|
194
189
|
output.context = [];
|
|
195
190
|
}
|
|
@@ -197,15 +192,16 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
197
192
|
},
|
|
198
193
|
// ── State persistence from LLM responses ───────────────────────────
|
|
199
194
|
// After tool execution, extract state_patch from the tool response
|
|
200
|
-
// (output.output), merge it
|
|
201
|
-
|
|
195
|
+
// (output.output), and atomically merge it into the session-scoped
|
|
196
|
+
// state (read + merge + write all inside the cross-process lock).
|
|
197
|
+
'tool.execute.after': async (input, output) => {
|
|
202
198
|
const response = output.output ?? '';
|
|
203
199
|
if (typeof response !== 'string')
|
|
204
200
|
return;
|
|
205
|
-
const statePath = resolvePath();
|
|
206
201
|
const patch = extractPatch(response);
|
|
207
202
|
if (patch) {
|
|
208
|
-
|
|
203
|
+
const agentId = pluginAgentId(input);
|
|
204
|
+
mergeSkillState(resolvePath(agentId), patch);
|
|
209
205
|
}
|
|
210
206
|
},
|
|
211
207
|
};
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|