@skillstate/opencode 2.1.0 → 2.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/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js +11 -33
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/plugin.d.ts +33 -9
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +75 -19
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -2
|
@@ -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;AAS1B,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,CAWhE;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, describeSchema,
|
|
2
|
+
import { atomicWriteFile, describeSchema, resolveStatePath, skillMdBody, STATE_PATCH_CONTRACT, } from '@skillstate/core';
|
|
3
3
|
/**
|
|
4
4
|
* OpenCode platform adapter (@non-paper; see module doc).
|
|
5
5
|
*/
|
|
@@ -56,38 +56,16 @@ ${STATE_PATCH_CONTRACT}`;
|
|
|
56
56
|
* the plugin's `messages.transform` hook — the LLM sees only the state.
|
|
57
57
|
*/
|
|
58
58
|
generateSkillMd(spec, statePath) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# ${spec.name}
|
|
70
|
-
|
|
71
|
-
${spec.instructions}
|
|
72
|
-
|
|
73
|
-
## Execution Context
|
|
74
|
-
|
|
75
|
-
Your execution state is persisted at \`${resolvedStatePath}\`. Read it at the
|
|
76
|
-
start of every step to recover where you are — never rely on conversation
|
|
77
|
-
history to carry state between steps. History is automatically trimmed by the
|
|
78
|
-
plugin; only the current state and the latest observation matter.
|
|
79
|
-
|
|
80
|
-
## Process
|
|
81
|
-
|
|
82
|
-
1. Read the current state from the state file.
|
|
83
|
-
2. Observe the result of your last action.
|
|
84
|
-
3. Reason about what to do next, given the state and the observation.
|
|
85
|
-
4. Respond with:
|
|
86
|
-
|
|
87
|
-
${STATE_PATCH_CONTRACT}
|
|
88
|
-
|
|
89
|
-
- \`action\` names what you will do next (e.g. "continue", "done").
|
|
90
|
-
- ${REASONING_DISCARDED_NOTE}`;
|
|
59
|
+
return skillMdBody({
|
|
60
|
+
hostLabel: 'OpenCode',
|
|
61
|
+
injectionPhrase: 'injected into the message list before every model call',
|
|
62
|
+
hooks: {
|
|
63
|
+
inject: 'messages.transform',
|
|
64
|
+
reInject: 'session.compacting',
|
|
65
|
+
},
|
|
66
|
+
spec,
|
|
67
|
+
statePath: statePath ?? './.skillstate/skillstate.json',
|
|
68
|
+
});
|
|
91
69
|
}
|
|
92
70
|
/**
|
|
93
71
|
* Generate an OpenCode plugin file.
|
|
@@ -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,cAAc,EACd,
|
|
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,gBAAgB,EAChB,WAAW,EACX,oBAAoB,GACrB,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,OAAO,WAAW,CAAC;YACjB,SAAS,EAAE,UAAU;YACrB,eAAe,EAAE,wDAAwD;YACzE,KAAK,EAAE;gBACL,MAAM,EAAE,oBAAoB;gBAC5B,QAAQ,EAAE,oBAAoB;aAC/B;YACD,IAAI;YACJ,SAAS,EAAE,SAAS,IAAI,+BAA+B;SACxD,CAAC,CAAC;IACL,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,16 +1,19 @@
|
|
|
1
1
|
import { mergePatch, resolveHostStateForCwd } from '@skillstate/core';
|
|
2
|
-
import type { SkillStatePlugin } from './plugin-types.js';
|
|
2
|
+
import type { OpenCodeMessage, SkillStatePlugin } from './plugin-types.js';
|
|
3
3
|
export * from './plugin-types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the per-project state file for a session working directory
|
|
6
6
|
* (`cwd` of the current opencode session) — the core single source of
|
|
7
7
|
* truth (`resolveHostStateForCwd`): `<cwd>/.skillstate/skillstate.json`,
|
|
8
8
|
* or the global bucket `<home>/.skillstate/global/skillstate.json` when
|
|
9
|
-
* cwd equals home.
|
|
10
|
-
*
|
|
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.
|
|
11
12
|
*/
|
|
12
13
|
export { resolveHostStateForCwd as resolveStatePathForCwd };
|
|
13
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";
|
|
14
17
|
/** Options for {@link createSkillStatePlugin}. */
|
|
15
18
|
export interface SkillStatePluginOptions {
|
|
16
19
|
/** Non-system messages kept in the prompt (default 3). */
|
|
@@ -26,11 +29,21 @@ export declare function readSkillState(statePath: string): Record<string, unknow
|
|
|
26
29
|
/**
|
|
27
30
|
* Persist the state file (best-effort: read-only environments are ignored).
|
|
28
31
|
* Creates the parent directory when missing (the per-project resolver may
|
|
29
|
-
* target a fresh `<cwd>/.skillstate
|
|
30
|
-
* envelope so `migrate()`/runtime resume read the
|
|
31
|
-
* hook-runtime {@link saveStateEnvelope}
|
|
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).
|
|
32
37
|
*/
|
|
33
38
|
export declare function saveSkillState(statePath: string, state: Record<string, unknown>): void;
|
|
39
|
+
/**
|
|
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.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mergeSkillState(statePath: string, patch: Record<string, unknown>): Record<string, unknown>;
|
|
34
47
|
/**
|
|
35
48
|
* Extract the `state_patch` object from an LLM response's fenced ```json
|
|
36
49
|
* block; `null` when there is no block, it is malformed, or it carries no
|
|
@@ -39,15 +52,26 @@ export declare function saveSkillState(statePath: string, state: Record<string,
|
|
|
39
52
|
* `null`, preserving the legacy boolean contract).
|
|
40
53
|
*/
|
|
41
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;
|
|
42
65
|
/**
|
|
43
66
|
* Build the OpenCode plugin function with the same behavior for every host
|
|
44
67
|
* entry point (thin generated loaders, direct imports).
|
|
45
68
|
*
|
|
46
69
|
* State resolution is ALWAYS per-project: the state file path is computed
|
|
47
70
|
* from the session cwd on EVERY hook call via
|
|
48
|
-
* `resolveStatePathForCwd(process.cwd(), os.homedir())` — each
|
|
49
|
-
* its own `<cwd>/.skillstate
|
|
50
|
-
*
|
|
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.
|
|
51
75
|
*/
|
|
52
76
|
export declare function createSkillStatePlugin(options?: SkillStatePluginOptions): SkillStatePlugin;
|
|
53
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
|
@@ -16,22 +16,34 @@
|
|
|
16
16
|
* `output.context` so the compaction summary preserves it.
|
|
17
17
|
* - `tool.execute.after` — the tool response is `output.output`; a fenced
|
|
18
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.
|
|
19
28
|
*/
|
|
20
29
|
import * as fs from 'node:fs';
|
|
21
30
|
import * as os from 'node:os';
|
|
22
31
|
import * as path from 'node:path';
|
|
23
|
-
import { findFencedPatch, mergePatch, readStateEnvelope, resolveHostStateForCwd, saveStateEnvelope, } from '@skillstate/core';
|
|
32
|
+
import { findFencedPatch, lockStateWrite, mergePatch, readStateEnvelope, resolveAgentIdFromSession, resolveHostStateForCwd, saveStateEnvelope, } from '@skillstate/core';
|
|
24
33
|
export * from './plugin-types.js';
|
|
25
34
|
/**
|
|
26
35
|
* Resolve the per-project state file for a session working directory
|
|
27
36
|
* (`cwd` of the current opencode session) — the core single source of
|
|
28
37
|
* truth (`resolveHostStateForCwd`): `<cwd>/.skillstate/skillstate.json`,
|
|
29
38
|
* or the global bucket `<home>/.skillstate/global/skillstate.json` when
|
|
30
|
-
* cwd equals home.
|
|
31
|
-
*
|
|
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.
|
|
32
42
|
*/
|
|
33
43
|
export { resolveHostStateForCwd as resolveStatePathForCwd };
|
|
34
44
|
export { mergePatch };
|
|
45
|
+
/** Agent scope used by the plugin when no session id is available. */
|
|
46
|
+
export const PLUGIN_DEFAULT_AGENT_ID = 'default';
|
|
35
47
|
/**
|
|
36
48
|
* Read the state file. Missing or corrupt files yield `{}` (best-effort).
|
|
37
49
|
* The on-disk envelope is `{ version: 1, state }` (migrations-compatible);
|
|
@@ -44,19 +56,42 @@ export function readSkillState(statePath) {
|
|
|
44
56
|
/**
|
|
45
57
|
* Persist the state file (best-effort: read-only environments are ignored).
|
|
46
58
|
* Creates the parent directory when missing (the per-project resolver may
|
|
47
|
-
* target a fresh `<cwd>/.skillstate
|
|
48
|
-
* envelope so `migrate()`/runtime resume read the
|
|
49
|
-
* hook-runtime {@link saveStateEnvelope}
|
|
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).
|
|
50
64
|
*/
|
|
51
65
|
export function saveSkillState(statePath, state) {
|
|
52
66
|
try {
|
|
53
67
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
54
|
-
saveStateEnvelope(statePath, state, (p, data) => fs.writeFileSync(p, data));
|
|
68
|
+
lockStateWrite(statePath, fs, () => saveStateEnvelope(statePath, state, (p, data) => fs.writeFileSync(p, data)));
|
|
55
69
|
}
|
|
56
70
|
catch {
|
|
57
71
|
// Best-effort: read-only environments or permission issues.
|
|
58
72
|
}
|
|
59
73
|
}
|
|
74
|
+
/**
|
|
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.
|
|
80
|
+
*/
|
|
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);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
60
95
|
/**
|
|
61
96
|
* Extract the `state_patch` object from an LLM response's fenced ```json
|
|
62
97
|
* block; `null` when there is no block, it is malformed, or it carries no
|
|
@@ -68,6 +103,23 @@ export function extractPatch(response) {
|
|
|
68
103
|
const result = findFencedPatch(response);
|
|
69
104
|
return 'patch' in result ? result.patch : null;
|
|
70
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;
|
|
122
|
+
}
|
|
71
123
|
/** Synthetic message ids for the injected state carrier. */
|
|
72
124
|
const STATE_MESSAGE_ID = 'skillstate-state-inject';
|
|
73
125
|
/**
|
|
@@ -76,12 +128,13 @@ const STATE_MESSAGE_ID = 'skillstate-state-inject';
|
|
|
76
128
|
*
|
|
77
129
|
* State resolution is ALWAYS per-project: the state file path is computed
|
|
78
130
|
* from the session cwd on EVERY hook call via
|
|
79
|
-
* `resolveStatePathForCwd(process.cwd(), os.homedir())` — each
|
|
80
|
-
* its own `<cwd>/.skillstate
|
|
81
|
-
*
|
|
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.
|
|
82
135
|
*/
|
|
83
136
|
export function createSkillStatePlugin(options = {}) {
|
|
84
|
-
const resolvePath = () => resolveHostStateForCwd(process.cwd(), os.homedir());
|
|
137
|
+
const resolvePath = (agentId) => resolveHostStateForCwd(process.cwd(), os.homedir(), agentId);
|
|
85
138
|
const maxHistory = options.maxHistoryMessages ?? 3;
|
|
86
139
|
return async () => {
|
|
87
140
|
return {
|
|
@@ -90,8 +143,9 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
90
143
|
// plus the last `maxHistory` non-system messages, then injects a
|
|
91
144
|
// synthetic state element. Old messages are DROPPED from the prompt,
|
|
92
145
|
// not just hidden.
|
|
93
|
-
'experimental.chat.messages.transform': async (
|
|
94
|
-
const
|
|
146
|
+
'experimental.chat.messages.transform': async (input, output) => {
|
|
147
|
+
const agentId = pluginAgentId(input, output.messages);
|
|
148
|
+
const state = readSkillState(resolvePath(agentId));
|
|
95
149
|
const messages = output.messages;
|
|
96
150
|
const systemMessages = messages.filter((m) => m.info.role === 'system');
|
|
97
151
|
const trimmed = messages
|
|
@@ -128,8 +182,9 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
128
182
|
// ── Compaction context injection ───────────────────────────────────
|
|
129
183
|
// Before compaction, inject the current state into the context so the
|
|
130
184
|
// compaction summary preserves state even after history is compressed.
|
|
131
|
-
'experimental.session.compacting': async (
|
|
132
|
-
const
|
|
185
|
+
'experimental.session.compacting': async (input, output) => {
|
|
186
|
+
const agentId = pluginAgentId(input);
|
|
187
|
+
const state = readSkillState(resolvePath(agentId));
|
|
133
188
|
if (!Array.isArray(output.context)) {
|
|
134
189
|
output.context = [];
|
|
135
190
|
}
|
|
@@ -137,15 +192,16 @@ export function createSkillStatePlugin(options = {}) {
|
|
|
137
192
|
},
|
|
138
193
|
// ── State persistence from LLM responses ───────────────────────────
|
|
139
194
|
// After tool execution, extract state_patch from the tool response
|
|
140
|
-
// (output.output), merge it
|
|
141
|
-
|
|
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) => {
|
|
142
198
|
const response = output.output ?? '';
|
|
143
199
|
if (typeof response !== 'string')
|
|
144
200
|
return;
|
|
145
|
-
const statePath = resolvePath();
|
|
146
201
|
const patch = extractPatch(response);
|
|
147
202
|
if (patch) {
|
|
148
|
-
|
|
203
|
+
const agentId = pluginAgentId(input);
|
|
204
|
+
mergeSkillState(resolvePath(agentId), patch);
|
|
149
205
|
}
|
|
150
206
|
},
|
|
151
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EACL,eAAe,EACf,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAO1B,cAAc,mBAAmB,CAAC;AAElC;;;;;;;;GAQG;AACH,OAAO,EAAE,sBAAsB,IAAI,sBAAsB,EAAE,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,sEAAsE;AACtE,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAQjD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,iBAAiB,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAA4B,CAAC;AACrG,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,KAA8B;IAC9E,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,cAAc,CACZ,SAAS,EACT,EAAE,EACF,GAAG,EAAE,CAAC,iBAAiB,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAClF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,KAA8B;IAE9B,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,GAA4B,EAAE,CAAC;QACzC,cAAc,CAAC,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE;YACjC,MAAM,GAAG,UAAU,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;YACtD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACzC,OAAO,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA8B,EAC9B,QAA4B;IAE5B,MAAM,MAAM,GAAG,yBAAyB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,MAAM,YAAY,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CACxC,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,QAAQ;QACrC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QAC3B,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,YAAY,CACpC,CAAC;IACF,MAAM,QAAQ,GAAG,yBAAyB,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACzE,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,uBAAuB,CAAC;AAClE,CAAC;AAED,4DAA4D;AAC5D,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAO,GAA4B,EAAE;IAC1E,MAAM,WAAW,GAAG,CAAC,OAAe,EAAU,EAAE,CAC9C,sBAAsB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC,CAAC;IAEnD,OAAO,KAAK,IAAI,EAAE;QAChB,OAAO;YACL,sEAAsE;YACtE,mEAAmE;YACnE,iEAAiE;YACjE,qEAAqE;YACrE,mBAAmB;YACnB,sCAAsC,EAAE,KAAK,EAC3C,KAAK,EACL,MAAM,EACS,EAAE;gBACjB,MAAM,OAAO,GAAG,aAAa,CAAC,KAAgC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjF,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;gBACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;gBACjC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;gBACxE,MAAM,OAAO,GAAG,QAAQ;qBACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;qBACvC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;gBAEtB,oEAAoE;gBACpE,uCAAuC;gBACvC,MAAM,YAAY,GAAoB;oBACpC,IAAI,EAAE;wBACJ,EAAE,EAAE,gBAAgB;wBACpB,SAAS,EAAE,YAAY;wBACvB,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;wBACpB,KAAK,EAAE,YAAY;wBACnB,KAAK,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE;qBAC3D;oBACD,KAAK,EAAE;wBACL;4BACE,EAAE,EAAE,GAAG,gBAAgB,OAAO;4BAC9B,SAAS,EAAE,YAAY;4BACvB,SAAS,EAAE,gBAAgB;4BAC3B,IAAI,EAAE,MAAM;4BACZ,SAAS,EAAE,IAAI;4BACf,IAAI,EAAE,+BAA+B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;yBAC7D;qBACF;iBACF,CAAC;gBAEF,oEAAoE;gBACpE,gEAAgE;gBAChE,MAAM,IAAI,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,OAAO,EAAE,YAAY,CAAC,CAAC;gBAC3D,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBACpB,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;YACzB,CAAC;YAED,sEAAsE;YACtE,sEAAsE;YACtE,uEAAuE;YACvE,iCAAiC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAiB,EAAE;gBACxE,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrC,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;gBACnD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;oBACnC,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;gBACtB,CAAC;gBACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9D,CAAC;YAED,sEAAsE;YACtE,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAiB,EAAE;gBAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;gBACrC,IAAI,OAAO,QAAQ,KAAK,QAAQ;oBAAE,OAAO;gBACzC,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;gBACrC,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;oBACrC,eAAe,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;SACwB,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillstate/opencode",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "OpenCode platform adapter for the skillstate runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@skillstate/core": "^2.
|
|
28
|
+
"@skillstate/core": "^2.2.1"
|
|
29
29
|
}
|
|
30
30
|
}
|