@skillstate/opencode 2.2.1 → 3.0.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/README.md +86 -91
- package/dist/index.d.ts +28 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/opencode-adapter.d.ts +9 -46
- package/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js +1 -64
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/plugin.d.ts +82 -72
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +138 -201
- package/dist/plugin.js.map +1 -1
- package/dist/session-registry.d.ts +148 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +236 -0
- package/dist/session-registry.js.map +1 -0
- package/dist/state-store.d.ts +126 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +186 -0
- package/dist/state-store.js.map +1 -0
- package/dist/system-hint.d.ts +62 -0
- package/dist/system-hint.d.ts.map +1 -0
- package/dist/system-hint.js +87 -0
- package/dist/system-hint.js.map +1 -0
- package/dist/tools.d.ts +114 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +334 -0
- package/dist/tools.js.map +1 -0
- package/package.json +3 -2
- package/dist/plugin-types.d.ts +0 -64
- package/dist/plugin-types.d.ts.map +0 -1
- package/dist/plugin-types.js +0 -8
- package/dist/plugin-types.js.map +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-agent session registry for the OpenCode v2 plugin.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode spawns a CHILD SESSION for every sub-agent (the `task` tool and
|
|
5
|
+
* every custom agent). Those child sessions are first-class: they run the
|
|
6
|
+
* agent loop, call tools, and would otherwise all write to the SAME project
|
|
7
|
+
* state file — the last writer wins and the main agent's notes are silently
|
|
8
|
+
* clobbered mid-task.
|
|
9
|
+
*
|
|
10
|
+
* The v1 plugin inferred this from an in-process `Map` fed by an `event`
|
|
11
|
+
* hook. v2 exposes a real event stream (`ctx.event.subscribe()`), so the
|
|
12
|
+
* registry is an explicit, testable value object instead of module-level
|
|
13
|
+
* mutable state shared by tests and a single plugin instance.
|
|
14
|
+
*
|
|
15
|
+
* PARENT EDGES come from the server's own event stream. In OpenCode v2 the
|
|
16
|
+
* relevant payloads are:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* { type: "session.created", data: { sessionID: string, parentID?: string } }
|
|
20
|
+
* { type: "session.forked", data: { sessionID: string, parentID: string } }
|
|
21
|
+
* { type: "session.deleted", data: { sessionID: string } }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* There is no `session.updated` event in v2, and the parent lives on
|
|
25
|
+
* `data.parentID` — not on a `properties.info` envelope as it did in v1.
|
|
26
|
+
* A session with a non-empty `parentID` is a sub-agent; a session with an
|
|
27
|
+
* absent or empty `parentID` is a root session. The registry never guesses
|
|
28
|
+
* from the id shape — a session that has not been observed on the stream
|
|
29
|
+
* yet is treated as a ROOT session (the safe default: it gets the shared
|
|
30
|
+
* project file, which is what a plain single-session user expects).
|
|
31
|
+
*
|
|
32
|
+
* LIFECYCLE: entries are refreshed on every event and evicted after
|
|
33
|
+
* {@link DEFAULT_SESSION_TTL_MS} of silence, so a long-lived OpenCode
|
|
34
|
+
* process does not accumulate one entry per session it has ever seen.
|
|
35
|
+
*/
|
|
36
|
+
/** How long a session stays registered after its last event. */
|
|
37
|
+
export declare const DEFAULT_SESSION_TTL_MS: number;
|
|
38
|
+
/** A session as observed on the OpenCode event stream. */
|
|
39
|
+
export interface SessionRecord {
|
|
40
|
+
/** The host session id, verbatim. */
|
|
41
|
+
readonly id: string;
|
|
42
|
+
/** The parent session id, or `null` for a root session. */
|
|
43
|
+
readonly parentID: string | null;
|
|
44
|
+
/** Event receive time (epoch ms) — drives TTL eviction. */
|
|
45
|
+
readonly seenAt: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Options for {@link SessionRegistry}. Tests inject a clock and a TTL so
|
|
49
|
+
* eviction is deterministic.
|
|
50
|
+
*/
|
|
51
|
+
export interface SessionRegistryOptions {
|
|
52
|
+
/** Clock in epoch milliseconds. Defaults to `Date.now`. */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
/** Idle time after which a session is evicted. Defaults to 6h. */
|
|
55
|
+
ttlMs?: number;
|
|
56
|
+
/** Cap on retained sessions; the oldest are evicted first. */
|
|
57
|
+
maxSessions?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Tracks the session→parent forest reported by the OpenCode event stream.
|
|
61
|
+
*
|
|
62
|
+
* Every method is pure with respect to the plugin's other state: the
|
|
63
|
+
* registry only ever answers questions about the shape of the session tree.
|
|
64
|
+
*/
|
|
65
|
+
export declare class SessionRegistry {
|
|
66
|
+
private readonly sessions;
|
|
67
|
+
private readonly now;
|
|
68
|
+
private readonly ttlMs;
|
|
69
|
+
private readonly maxSessions;
|
|
70
|
+
constructor(options?: SessionRegistryOptions);
|
|
71
|
+
/**
|
|
72
|
+
* Fold one server event into the registry.
|
|
73
|
+
*
|
|
74
|
+
* `session.created` and `session.forked` register (or re-parent) a
|
|
75
|
+
* session; `session.deleted` forgets it. Any other event, and any
|
|
76
|
+
* malformed payload, is ignored — the stream carries far more than
|
|
77
|
+
* sessions and one odd record must never break the subscription loop.
|
|
78
|
+
*
|
|
79
|
+
* Returns the {@link SessionRecord} it wrote, or `null` when nothing was
|
|
80
|
+
* registered (including a deletion).
|
|
81
|
+
*/
|
|
82
|
+
ingestEvent(event: unknown): SessionRecord | null;
|
|
83
|
+
/** The recorded session, or `undefined` when it was never observed. */
|
|
84
|
+
get(sessionID: string): SessionRecord | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Whether `sessionID` is a sub-agent session (a session with a parent).
|
|
87
|
+
*
|
|
88
|
+
* An UNOBSERVED session answers `false`: the plugin then treats it as a
|
|
89
|
+
* root session, which is the behaviour a single-session user expects and
|
|
90
|
+
* never creates a surprise `agents/` directory.
|
|
91
|
+
*/
|
|
92
|
+
isSubAgent(sessionID: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The topmost ancestor of `sessionID` (itself when it is a root
|
|
95
|
+
* session). Follows parent edges with a visited set so a cycle — which a
|
|
96
|
+
* buggy or hostile event stream could produce — terminates instead of
|
|
97
|
+
* hanging the agent loop.
|
|
98
|
+
*/
|
|
99
|
+
rootOf(sessionID: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Every session below `sessionID` in the tree - i.e. every session whose
|
|
102
|
+
* parent chain passes through `sessionID`, excluding `sessionID` itself.
|
|
103
|
+
*
|
|
104
|
+
* "Root ancestor" would be the wrong definition here: asked about a
|
|
105
|
+
* sub-agent it would also return that sub-agent, because the sub-agent's
|
|
106
|
+
* root is further up. Walking up to the queried session instead answers
|
|
107
|
+
* both cases uniformly, and a root session still gets every sub-agent.
|
|
108
|
+
*
|
|
109
|
+
* Sorted by id for a stable, testable result.
|
|
110
|
+
*/
|
|
111
|
+
descendantsOf(sessionID: string): string[];
|
|
112
|
+
/**
|
|
113
|
+
* Whether `candidate` sits somewhere below `ancestor`. Cycle-safe: a
|
|
114
|
+
* parent chain that loops back on itself terminates instead of hanging
|
|
115
|
+
* the agent loop.
|
|
116
|
+
*/
|
|
117
|
+
private isDescendantOf;
|
|
118
|
+
/** Every registered session, oldest-observed first. */
|
|
119
|
+
list(): readonly SessionRecord[];
|
|
120
|
+
/** How many sessions are currently registered. */
|
|
121
|
+
get size(): number;
|
|
122
|
+
/** Forget every session (test isolation, and a clean reload). */
|
|
123
|
+
clear(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Drop sessions idle for longer than the TTL, then enforce the size cap
|
|
126
|
+
* by dropping the least-recently-seen entries. Called after every
|
|
127
|
+
* ingest; also exposed for tests.
|
|
128
|
+
*/
|
|
129
|
+
evict(): void;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The on-disk scope name for a session: a stable, filesystem-safe id that
|
|
133
|
+
* keeps SUB-AGENT sessions in their own directory and root sessions on the
|
|
134
|
+
* shared project file.
|
|
135
|
+
*
|
|
136
|
+
* Returns `''` for a root session (the shared project state) and a
|
|
137
|
+
* sanitized `<parent>-<session>` id for a sub-agent.
|
|
138
|
+
*
|
|
139
|
+
* The child part is the FULL sanitized session id, not a short prefix. The
|
|
140
|
+
* v1 code truncated to 8 characters, which silently collapses two sibling
|
|
141
|
+
* sub-agents into one state file whenever their ids share a prefix — they
|
|
142
|
+
* overwrite each other's notes with no error. `sanitizeAgentId` caps the
|
|
143
|
+
* result at 64 characters, so the directory name stays bounded. The parent
|
|
144
|
+
* part is truncated to 8 characters because it only has to disambiguate
|
|
145
|
+
* which root a sub-agent belongs to.
|
|
146
|
+
*/
|
|
147
|
+
export declare function stateScopeFor(registry: SessionRegistry, sessionID: string): string;
|
|
148
|
+
//# sourceMappingURL=session-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,QAAqB,CAAC;AAEzD,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,OAAO,GAAE,sBAA2B,EAI/C;IAED;;;;;;;;;;OAUG;IACH,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAehD;IAED,uEAAuE;IACvE,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEhD;IAED;;;;;;OAMG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAErC;IAED;;;;;OAKG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUhC;IAED;;;;;;;;;;OAUG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAQzC;IAED;;;;OAIG;IACH,OAAO,CAAC,cAAc;IActB,uDAAuD;IACvD,IAAI,IAAI,SAAS,aAAa,EAAE,CAE/B;IAED,kDAAkD;IAClD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,iEAAiE;IACjE,KAAK,IAAI,IAAI,CAEZ;IAED;;;;OAIG;IACH,KAAK,IAAI,IAAI,CAYZ;CACF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-agent session registry for the OpenCode v2 plugin.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode spawns a CHILD SESSION for every sub-agent (the `task` tool and
|
|
5
|
+
* every custom agent). Those child sessions are first-class: they run the
|
|
6
|
+
* agent loop, call tools, and would otherwise all write to the SAME project
|
|
7
|
+
* state file — the last writer wins and the main agent's notes are silently
|
|
8
|
+
* clobbered mid-task.
|
|
9
|
+
*
|
|
10
|
+
* The v1 plugin inferred this from an in-process `Map` fed by an `event`
|
|
11
|
+
* hook. v2 exposes a real event stream (`ctx.event.subscribe()`), so the
|
|
12
|
+
* registry is an explicit, testable value object instead of module-level
|
|
13
|
+
* mutable state shared by tests and a single plugin instance.
|
|
14
|
+
*
|
|
15
|
+
* PARENT EDGES come from the server's own event stream. In OpenCode v2 the
|
|
16
|
+
* relevant payloads are:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* { type: "session.created", data: { sessionID: string, parentID?: string } }
|
|
20
|
+
* { type: "session.forked", data: { sessionID: string, parentID: string } }
|
|
21
|
+
* { type: "session.deleted", data: { sessionID: string } }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* There is no `session.updated` event in v2, and the parent lives on
|
|
25
|
+
* `data.parentID` — not on a `properties.info` envelope as it did in v1.
|
|
26
|
+
* A session with a non-empty `parentID` is a sub-agent; a session with an
|
|
27
|
+
* absent or empty `parentID` is a root session. The registry never guesses
|
|
28
|
+
* from the id shape — a session that has not been observed on the stream
|
|
29
|
+
* yet is treated as a ROOT session (the safe default: it gets the shared
|
|
30
|
+
* project file, which is what a plain single-session user expects).
|
|
31
|
+
*
|
|
32
|
+
* LIFECYCLE: entries are refreshed on every event and evicted after
|
|
33
|
+
* {@link DEFAULT_SESSION_TTL_MS} of silence, so a long-lived OpenCode
|
|
34
|
+
* process does not accumulate one entry per session it has ever seen.
|
|
35
|
+
*/
|
|
36
|
+
import { sanitizeAgentId } from '@skillstate/core';
|
|
37
|
+
/** How long a session stays registered after its last event. */
|
|
38
|
+
export const DEFAULT_SESSION_TTL_MS = 6 * 60 * 60 * 1000;
|
|
39
|
+
const DEFAULT_MAX_SESSIONS = 512;
|
|
40
|
+
function readSessionEvent(event) {
|
|
41
|
+
if (typeof event !== 'object' || event === null)
|
|
42
|
+
return null;
|
|
43
|
+
const type = event.type;
|
|
44
|
+
if (typeof type !== 'string')
|
|
45
|
+
return null;
|
|
46
|
+
if (type !== 'session.created' && type !== 'session.forked' && type !== 'session.deleted') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const data = event.data;
|
|
50
|
+
if (typeof data !== 'object' || data === null)
|
|
51
|
+
return null;
|
|
52
|
+
const record = data;
|
|
53
|
+
if (typeof record.sessionID !== 'string' || record.sessionID.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
if (type === 'session.deleted')
|
|
56
|
+
return { kind: 'delete', id: record.sessionID };
|
|
57
|
+
const parent = typeof record.parentID === 'string' && record.parentID.length > 0 ? record.parentID : null;
|
|
58
|
+
return { kind: 'upsert', id: record.sessionID, parentID: parent };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Tracks the session→parent forest reported by the OpenCode event stream.
|
|
62
|
+
*
|
|
63
|
+
* Every method is pure with respect to the plugin's other state: the
|
|
64
|
+
* registry only ever answers questions about the shape of the session tree.
|
|
65
|
+
*/
|
|
66
|
+
export class SessionRegistry {
|
|
67
|
+
sessions = new Map();
|
|
68
|
+
now;
|
|
69
|
+
ttlMs;
|
|
70
|
+
maxSessions;
|
|
71
|
+
constructor(options = {}) {
|
|
72
|
+
this.now = options.now ?? Date.now;
|
|
73
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_SESSION_TTL_MS;
|
|
74
|
+
this.maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fold one server event into the registry.
|
|
78
|
+
*
|
|
79
|
+
* `session.created` and `session.forked` register (or re-parent) a
|
|
80
|
+
* session; `session.deleted` forgets it. Any other event, and any
|
|
81
|
+
* malformed payload, is ignored — the stream carries far more than
|
|
82
|
+
* sessions and one odd record must never break the subscription loop.
|
|
83
|
+
*
|
|
84
|
+
* Returns the {@link SessionRecord} it wrote, or `null` when nothing was
|
|
85
|
+
* registered (including a deletion).
|
|
86
|
+
*/
|
|
87
|
+
ingestEvent(event) {
|
|
88
|
+
const parsed = readSessionEvent(event);
|
|
89
|
+
if (parsed === null)
|
|
90
|
+
return null;
|
|
91
|
+
if (parsed.kind === 'delete') {
|
|
92
|
+
this.sessions.delete(parsed.id);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const record = {
|
|
96
|
+
id: parsed.id,
|
|
97
|
+
parentID: parsed.parentID,
|
|
98
|
+
seenAt: this.now(),
|
|
99
|
+
};
|
|
100
|
+
this.sessions.set(record.id, record);
|
|
101
|
+
this.evict();
|
|
102
|
+
return record;
|
|
103
|
+
}
|
|
104
|
+
/** The recorded session, or `undefined` when it was never observed. */
|
|
105
|
+
get(sessionID) {
|
|
106
|
+
return this.sessions.get(sessionID);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether `sessionID` is a sub-agent session (a session with a parent).
|
|
110
|
+
*
|
|
111
|
+
* An UNOBSERVED session answers `false`: the plugin then treats it as a
|
|
112
|
+
* root session, which is the behaviour a single-session user expects and
|
|
113
|
+
* never creates a surprise `agents/` directory.
|
|
114
|
+
*/
|
|
115
|
+
isSubAgent(sessionID) {
|
|
116
|
+
return this.sessions.get(sessionID)?.parentID != null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The topmost ancestor of `sessionID` (itself when it is a root
|
|
120
|
+
* session). Follows parent edges with a visited set so a cycle — which a
|
|
121
|
+
* buggy or hostile event stream could produce — terminates instead of
|
|
122
|
+
* hanging the agent loop.
|
|
123
|
+
*/
|
|
124
|
+
rootOf(sessionID) {
|
|
125
|
+
let current = sessionID;
|
|
126
|
+
const visited = new Set([sessionID]);
|
|
127
|
+
for (;;) {
|
|
128
|
+
const record = this.sessions.get(current);
|
|
129
|
+
const parent = record?.parentID;
|
|
130
|
+
if (parent === undefined || parent === null || visited.has(parent))
|
|
131
|
+
return current;
|
|
132
|
+
visited.add(parent);
|
|
133
|
+
current = parent;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Every session below `sessionID` in the tree - i.e. every session whose
|
|
138
|
+
* parent chain passes through `sessionID`, excluding `sessionID` itself.
|
|
139
|
+
*
|
|
140
|
+
* "Root ancestor" would be the wrong definition here: asked about a
|
|
141
|
+
* sub-agent it would also return that sub-agent, because the sub-agent's
|
|
142
|
+
* root is further up. Walking up to the queried session instead answers
|
|
143
|
+
* both cases uniformly, and a root session still gets every sub-agent.
|
|
144
|
+
*
|
|
145
|
+
* Sorted by id for a stable, testable result.
|
|
146
|
+
*/
|
|
147
|
+
descendantsOf(sessionID) {
|
|
148
|
+
const result = [];
|
|
149
|
+
for (const record of this.sessions.values()) {
|
|
150
|
+
if (record.id !== sessionID && this.isDescendantOf(record.id, sessionID)) {
|
|
151
|
+
result.push(record.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result.sort();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether `candidate` sits somewhere below `ancestor`. Cycle-safe: a
|
|
158
|
+
* parent chain that loops back on itself terminates instead of hanging
|
|
159
|
+
* the agent loop.
|
|
160
|
+
*/
|
|
161
|
+
isDescendantOf(candidate, ancestor) {
|
|
162
|
+
let current = candidate;
|
|
163
|
+
const visited = new Set([candidate]);
|
|
164
|
+
for (;;) {
|
|
165
|
+
const record = this.sessions.get(current);
|
|
166
|
+
const parent = record?.parentID ?? null;
|
|
167
|
+
if (parent === null)
|
|
168
|
+
return false;
|
|
169
|
+
if (parent === ancestor)
|
|
170
|
+
return true;
|
|
171
|
+
if (visited.has(parent))
|
|
172
|
+
return false;
|
|
173
|
+
visited.add(parent);
|
|
174
|
+
current = parent;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Every registered session, oldest-observed first. */
|
|
178
|
+
list() {
|
|
179
|
+
return [...this.sessions.values()].sort((a, b) => a.seenAt - b.seenAt || a.id.localeCompare(b.id));
|
|
180
|
+
}
|
|
181
|
+
/** How many sessions are currently registered. */
|
|
182
|
+
get size() {
|
|
183
|
+
return this.sessions.size;
|
|
184
|
+
}
|
|
185
|
+
/** Forget every session (test isolation, and a clean reload). */
|
|
186
|
+
clear() {
|
|
187
|
+
this.sessions.clear();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Drop sessions idle for longer than the TTL, then enforce the size cap
|
|
191
|
+
* by dropping the least-recently-seen entries. Called after every
|
|
192
|
+
* ingest; also exposed for tests.
|
|
193
|
+
*/
|
|
194
|
+
evict() {
|
|
195
|
+
const cutoff = this.now() - this.ttlMs;
|
|
196
|
+
for (const [id, record] of this.sessions) {
|
|
197
|
+
if (record.seenAt < cutoff)
|
|
198
|
+
this.sessions.delete(id);
|
|
199
|
+
}
|
|
200
|
+
if (this.sessions.size <= this.maxSessions)
|
|
201
|
+
return;
|
|
202
|
+
const ordered = [...this.sessions.entries()].sort((a, b) => a[1].seenAt - b[1].seenAt || a[0].localeCompare(b[0]));
|
|
203
|
+
for (const [id] of ordered.slice(0, this.sessions.size - this.maxSessions)) {
|
|
204
|
+
this.sessions.delete(id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The on-disk scope name for a session: a stable, filesystem-safe id that
|
|
210
|
+
* keeps SUB-AGENT sessions in their own directory and root sessions on the
|
|
211
|
+
* shared project file.
|
|
212
|
+
*
|
|
213
|
+
* Returns `''` for a root session (the shared project state) and a
|
|
214
|
+
* sanitized `<parent>-<session>` id for a sub-agent.
|
|
215
|
+
*
|
|
216
|
+
* The child part is the FULL sanitized session id, not a short prefix. The
|
|
217
|
+
* v1 code truncated to 8 characters, which silently collapses two sibling
|
|
218
|
+
* sub-agents into one state file whenever their ids share a prefix — they
|
|
219
|
+
* overwrite each other's notes with no error. `sanitizeAgentId` caps the
|
|
220
|
+
* result at 64 characters, so the directory name stays bounded. The parent
|
|
221
|
+
* part is truncated to 8 characters because it only has to disambiguate
|
|
222
|
+
* which root a sub-agent belongs to.
|
|
223
|
+
*/
|
|
224
|
+
export function stateScopeFor(registry, sessionID) {
|
|
225
|
+
const record = registry.get(sessionID);
|
|
226
|
+
if (record === undefined || record.parentID === null)
|
|
227
|
+
return '';
|
|
228
|
+
const child = sanitizeAgentId(sessionID);
|
|
229
|
+
const parentPrefix = sanitizeAgentId(record.parentID).slice(0, 8);
|
|
230
|
+
if (child.length === 0)
|
|
231
|
+
return parentPrefix;
|
|
232
|
+
if (parentPrefix.length === 0)
|
|
233
|
+
return child;
|
|
234
|
+
return `${parentPrefix}-${child}`;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project state persistence for the OpenCode v2 plugin.
|
|
3
|
+
*
|
|
4
|
+
* ONE store, THREE guarantees:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Per-project addressing.** The state file is derived from the plugin
|
|
7
|
+
* instance's location (`ctx.location.project.canonical`), never from
|
|
8
|
+
* `process.cwd()`. The v1 plugin called `process.cwd()` on every hook;
|
|
9
|
+
* in v2 a single OpenCode server serves many projects, so the process
|
|
10
|
+
* cwd is simply the wrong answer. Two checkouts no longer share state.
|
|
11
|
+
*
|
|
12
|
+
* 2. **Per-session isolation.** A root session owns the shared project
|
|
13
|
+
* file; a sub-agent session owns `agents/<scope>/skillstate.json` (see
|
|
14
|
+
* `stateScopeFor`). Parallel sub-agents therefore never last-writer-win
|
|
15
|
+
* over the main session's notes.
|
|
16
|
+
*
|
|
17
|
+
* 3. **Never lose a write.** Every mutation runs inside the core
|
|
18
|
+
* cross-process lock ({@link withStateLock}) and lands via
|
|
19
|
+
* {@link atomicWriteFile} (temp sibling → fsync → rename), so a crash
|
|
20
|
+
* mid-write can never leave a truncated or interleaved state file. Reads
|
|
21
|
+
* never throw: a missing or corrupt file reads as `{}`.
|
|
22
|
+
*
|
|
23
|
+
* The store is deliberately schema-free. It does not validate against a
|
|
24
|
+
* procedural spec: OpenCode sessions are free-form work, and forcing a
|
|
25
|
+
* fixed `goal`/`progress`/`next_steps` shape is what made the v1 prompt
|
|
26
|
+
* rewrite the user's actual task into a state machine.
|
|
27
|
+
*/
|
|
28
|
+
import type { StatePatch } from '@skillstate/core';
|
|
29
|
+
/** A skillstate document: a plain JSON object. */
|
|
30
|
+
export type StateDocument = Record<string, unknown>;
|
|
31
|
+
/** Options for {@link ProjectStateStore}. */
|
|
32
|
+
export interface ProjectStateStoreOptions {
|
|
33
|
+
/**
|
|
34
|
+
* The project directory the state file lives under. Prefer
|
|
35
|
+
* `ctx.location.project.canonical` — the canonical checkout, stable
|
|
36
|
+
* across worktrees.
|
|
37
|
+
*/
|
|
38
|
+
directory: string;
|
|
39
|
+
/** Home directory, for the `$HOME` global bucket. Defaults to `os.homedir()`. */
|
|
40
|
+
home?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The keys a patch actually changed, split by direction. Mirrors the
|
|
44
|
+
* `state.patch` MCP contract so behaviour is identical across hosts.
|
|
45
|
+
*/
|
|
46
|
+
export interface StateChanges {
|
|
47
|
+
readonly added: readonly string[];
|
|
48
|
+
readonly updated: readonly string[];
|
|
49
|
+
readonly deleted: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/** Top-level diff between two state documents. Pure. */
|
|
52
|
+
export declare function diffDocuments(before: StateDocument, after: StateDocument): StateChanges;
|
|
53
|
+
/**
|
|
54
|
+
* The state file for one session scope inside one project.
|
|
55
|
+
*
|
|
56
|
+
* `scope === ''` is the shared project file; any other scope is a
|
|
57
|
+
* sub-agent's isolated copy. Delegates to the core resolver (shared with
|
|
58
|
+
* the MCP server, the CLI and the other host adapters) so every host agrees
|
|
59
|
+
* on where a project's state lives.
|
|
60
|
+
*/
|
|
61
|
+
export declare function statePathFor(directory: string, scope: string, home?: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Reads and merges the per-project state documents for a plugin instance.
|
|
64
|
+
*
|
|
65
|
+
* Stateless between calls apart from the filesystem, so it is safe to keep
|
|
66
|
+
* one instance per plugin and to construct it in tests without any global
|
|
67
|
+
* setup.
|
|
68
|
+
*/
|
|
69
|
+
export declare class ProjectStateStore {
|
|
70
|
+
private readonly directory;
|
|
71
|
+
private readonly home;
|
|
72
|
+
constructor(options: ProjectStateStoreOptions);
|
|
73
|
+
/** The project directory this store addresses. */
|
|
74
|
+
get projectDirectory(): string;
|
|
75
|
+
/** The absolute state file path for a scope (`''` = shared). */
|
|
76
|
+
pathFor(scope: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a state file already exists for this scope. The plugin uses
|
|
79
|
+
* this to decide whether to inject the system hint at all — an untouched
|
|
80
|
+
* project must behave exactly like vanilla OpenCode.
|
|
81
|
+
*/
|
|
82
|
+
exists(scope: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Read a scope's state. A missing, unreadable or corrupt file reads as
|
|
85
|
+
* `{}` — a best-effort read that never breaks the agent loop.
|
|
86
|
+
*/
|
|
87
|
+
read(scope: string): StateDocument;
|
|
88
|
+
/**
|
|
89
|
+
* Apply a patch to a scope's state and persist it.
|
|
90
|
+
*
|
|
91
|
+
* The read, the merge and the write all happen inside the cross-process
|
|
92
|
+
* lock, so two sub-agents writing disjoint keys both survive — neither
|
|
93
|
+
* can clobber the other's write by reading a stale snapshot.
|
|
94
|
+
*
|
|
95
|
+
* Returns the merged document and the top-level change sets. A `null`
|
|
96
|
+
* value in the patch deletes that key (paper ⊕).
|
|
97
|
+
*/
|
|
98
|
+
patch(scope: string, patch: StatePatch): Promise<{
|
|
99
|
+
state: StateDocument;
|
|
100
|
+
changes: StateChanges;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Fold a sub-agent's document into this scope's document.
|
|
104
|
+
*
|
|
105
|
+
* `keep` decides what happens when both sides set the same scalar:
|
|
106
|
+
* `'existing'` (default) keeps the receiving document's value, `'source'`
|
|
107
|
+
* takes the sub-agent's. Keys only present in the sub-agent are always
|
|
108
|
+
* taken, and `null` in either document is a deletion.
|
|
109
|
+
*
|
|
110
|
+
* The source document is left on disk — the merge is not destructive, so
|
|
111
|
+
* a sub-agent's notes remain inspectable after the fold.
|
|
112
|
+
*/
|
|
113
|
+
merge(scope: string, sourceScope: string, keep?: 'existing' | 'source'): Promise<{
|
|
114
|
+
state: StateDocument;
|
|
115
|
+
changes: StateChanges;
|
|
116
|
+
}>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Fold `source` into `target` under a conflict policy. Pure.
|
|
120
|
+
*
|
|
121
|
+
* Nested plain objects recurse. A `null` on either side is a deletion and
|
|
122
|
+
* wins outright — an explicit "remove this" is not a scalar conflict. Every
|
|
123
|
+
* other scalar conflict follows `keep`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function mergeDocuments(target: StateDocument, source: StateDocument, keep: 'existing' | 'source'): StateDocument;
|
|
126
|
+
//# sourceMappingURL=state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAaH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE/D,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,GACnB,YAAY,CAed;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAqB,GAC1B,MAAM,CAER;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAE9B,YAAY,OAAO,EAAE,wBAAwB,EAG5C;IAED,kDAAkD;IAClD,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED,gEAAgE;IAChE,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7B;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM7B;IAED;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAQjC;IAED;;;;;;;;;OASG;IACG,KAAK,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE,CAAC,CAY1D;IAED;;;;;;;;;;OAUG;IACG,KAAK,CACT,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,UAAU,GAAG,QAAqB,GACvC,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE,CAAC,CAoB1D;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,UAAU,GAAG,QAAQ,GAC1B,aAAa,CAsBf"}
|