opencode-stm 0.0.0-stage → 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Trevor Leong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,118 @@
1
- # Temporary Holding Version
1
+ # opencode-stm
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An OpenCode plugin for session title management, driven by the agent already working in the conversation.
4
+
5
+ ## Status
6
+
7
+ Version 0.1.0. Validation on OpenCode 1.18.35 passed `npm run typecheck` and the full test suite (225 tests). Live runtime checks against a loopback chat-completions mock provider passed 44 assertions, plus 13 assertions in each of the two DCP plugin orders.
8
+
9
+ Compatibility is claimed only for what that validation exercised: OpenCode 1.18.35 with chat-completions-style providers. No claim is made for other OpenCode versions, other provider wire formats (such as the builtin OpenAI Responses API path or Anthropic), paid provider calls, or prompt-cache and billing behaviour.
10
+
11
+ Reproduce the project checks with:
12
+
13
+ ```sh
14
+ npm test
15
+ npm run typecheck
16
+ ```
17
+
18
+ ## Scope
19
+
20
+ - Agent-callable tools to read, review, rename and lock the current session's title.
21
+ - Activity-based reminders for the agent to review the title during long tool-driven turns; the agent keeps the title or renames it through a permission-checked decision.
22
+ - Conservative protection for manually edited titles, with an explicit unlock.
23
+ - Separately opt-in tools for historical title reads, renames and bounded transcript review.
24
+
25
+ It is not a session-deletion tool and not an automatic bulk-audit service. The design uses OpenCode's SDK without a separate title-generation model or idle background model calls.
26
+
27
+ ## Setup
28
+
29
+ ### Install as an npm plugin (recommended)
30
+
31
+ Prerequisite: OpenCode 1.18.35. OpenCode resolves and installs configured npm plugins at startup, so a global npm install is not required.
32
+
33
+ Add `opencode-stm@0.1.0` to the `plugin` array in the OpenCode configuration (`opencode.json` or `opencode.jsonc` in the project directory or the user config directory):
34
+
35
+ ```json
36
+ {
37
+ "plugin": ["opencode-stm@0.1.0"]
38
+ }
39
+ ```
40
+
41
+ If the configuration already lists plugins, append the package instead of replacing the array.
42
+
43
+ Restart OpenCode afterwards. The plugin is loaded once at startup; if its configuration file is present but invalid, the plugin disables itself and prints a single diagnostic: field-level errors name the offending setting, while YAML parse or file access errors stay generic. Raw values are never echoed.
44
+
45
+ ### Build from source (alternate)
46
+
47
+ Prerequisite: Node.js 24 or newer and npm. From a source checkout of this repository:
48
+
49
+ ```sh
50
+ npm install
51
+ npm run build
52
+ ```
53
+
54
+ Register the built entry point as a file plugin in the OpenCode configuration, using an absolute path to the built `dist/src/index.js`:
55
+
56
+ ```json
57
+ {
58
+ "plugin": ["/absolute/path/to/opencode-stm/dist/src/index.js"]
59
+ }
60
+ ```
61
+
62
+ Restart OpenCode afterwards.
63
+
64
+ ## Configuration
65
+
66
+ Copy `config.example.yaml` to `$XDG_CONFIG_HOME/opencode/opencode-stm/config.yaml` (or `~/.config/opencode/opencode-stm/config.yaml` when `XDG_CONFIG_HOME` is unset or relative). The package ships the same example at `config.example.yaml` (for a project install, `node_modules/opencode-stm/config.example.yaml`). Every field is optional; omitted fields use the defaults shown here:
67
+
68
+ ```yaml
69
+ review:
70
+ enabled: true
71
+ intervalMessages: 20
72
+ maxReminders: 3
73
+ backoffMultiplier: 2
74
+ historical:
75
+ readTitles: false
76
+ renameTitles: false
77
+ readTranscript: false
78
+ ```
79
+
80
+ | Setting | Default | Purpose |
81
+ | --- | --- | --- |
82
+ | `review.enabled` | `true` | Periodic title reviews for root sessions |
83
+ | `review.intervalMessages` | `20` | Eligible messages before the first reminder |
84
+ | `review.maxReminders` | `3` | Maximum reminders per review |
85
+ | `review.backoffMultiplier` | `2` | Multiplier between reminder thresholds |
86
+ | `historical.readTitles` | `false` | Exposes `stm_history_title_read` |
87
+ | `historical.renameTitles` | `false` | Exposes `stm_history_title_rename` |
88
+ | `historical.readTranscript` | `false` | Exposes `stm_history_transcript` and `stm_history_review_end` |
89
+
90
+ Configuration is read once at startup; restart OpenCode after any change. Tool definitions are fixed for the lifetime of the instance. Local state is stored under `$XDG_STATE_HOME/opencode-stm/` (or `~/.local/state/opencode-stm/`) using hashed session identifiers as filenames.
91
+
92
+ ## Behaviour and controls
93
+
94
+ - Reminders anchor to the most recent eligible message and recur at cumulative thresholds (20, 60, 140 eligible messages by default). The agent resolves each review with `stm_title_review` (`keep` or `rename`), which asks for permission first.
95
+ - Locking is conservative: a session whose first observed title is not the native default is locked because its provenance is unknown, and a title change the plugin cannot attribute to its own verified write — including changes made by OpenCode's own auto-titling — locks the session and stops automatic injections. These comparisons are not a background watcher: the title is checked on first observation, before anchoring a new reminder, before replaying an unresolved review's reminders on each outgoing request, and when applying a keep/rename decision. Between those points the plugin does not watch for title changes. An explicit unlock (`stm_title_lock` with `locked: false`) opts the session back in.
96
+ - Title writes have no compare-and-swap. The plugin re-checks the title immediately before writing and reads back after, but it cannot guarantee conflict detection: a manual edit that lands between the pre-write check and the write can be overwritten while the write reports success. Detected mismatches produce `conflict` or `uncertain` results, and the plugin never retries a failed or uncertain write automatically.
97
+
98
+ ## Historical access (opt-in)
99
+
100
+ All historical tools are disabled by default. Each flag independently exposes its own tool; enabling a flag never exposes the other tools. Enabling `historical.readTranscript` sends selected historical transcript content to the active model provider.
101
+
102
+ The flags do not grant user authorisation and do not override OpenCode permission prompts: every mutating action and every historical read asks through the normal allow/ask/deny policy. Note that headless invocations such as `opencode run` cannot be assumed to present an interactive permission prompt.
103
+
104
+ Transcript review is bounded and one-shot: each call fetches one page of up to 8 messages. The first fetch returns the newest page; entries within a page are chronological, and each subsequent page, requested with the issued cursor, is older. At most one 16 KiB chunk is pending per caller. A chunk is delivered once on the next outgoing request of the requesting session and released afterwards; text beyond one chunk continues from stored offsets on later calls. A successful transcript call commits its chunk before returning: OpenCode 1.18.35 aborts the per-call signal shortly after every successful tool return, and the committed chunk ignores that teardown abort so it remains available to the next request. An abort during the call, before the commit, still discards the fetched text and ends the caller's binding. A pending chunk stops being eligible after 60 seconds, checked lazily on the next queue access rather than by a background timer. An observed expiry fails closed: the undelivered chunk is dropped and further transcript calls for that caller are rejected until `stm_history_review_end` ends the review, because the lost chunk's content cannot be recovered and must not be silently skipped; the restarted review refetches from the first page. Because a real session cancellation immediately after a successful call cannot be distinguished from the host's success teardown, the plugin cannot promise real-time erasure of a committed chunk: it is held in memory only, delivered at most once, and released on delivery, review end, session deletion, disposal or the 60-second sweep. Continuation is only possible with cursors the plugin issued to that caller and target; the plugin does not follow arbitrary Link URLs. Because OpenCode 1.18.35 reuses the same transform hook during compaction without a reliable discriminator, a queued chunk may be consumed by a compaction request; the plugin does not claim a compaction exclusion.
105
+
106
+ ## Privacy boundaries
107
+
108
+ Historical access is disabled by default. Transcript review sends selected content to the active model provider. Avoiding raw transcript files and tool outputs does **not** guarantee that content cannot appear in model responses, logs or compaction summaries.
109
+
110
+ - The reminders and transcript chunks are injected transiently into the outgoing request. This is not an end-to-end non-persistence guarantee, and compaction summaries may retain excerpts of the conversation.
111
+ - Provider-side prompt cache and billing effects of the injected text have not been measured; an offline byte-stability check does not cover them.
112
+ - Conversion of the injected synthetic parts to provider wire formats was verified in the prototype only for chat-completions-style providers; conversions for the builtin OpenAI Responses API path and Anthropic are not verified.
113
+ - Other plugins that capture or log outgoing messages can record injected reminders and transcript chunks; observed diagnostic exposure of that kind occurred when the capturing plugin's own debug output was enabled.
114
+ - State file names are hashed session identifiers. The stored metadata contains counters, title digests, lock state, and raw message and part identifiers used for anchoring and replay protection — no raw titles, no transcript text, and no session identifiers. Tool results never contain transcript text.
115
+
116
+ ## Licence
117
+
118
+ [MIT](LICENSE).
@@ -0,0 +1,18 @@
1
+ # opencode-stm example configuration. All values shown are the built-in defaults.
2
+ # Copy this file to $XDG_CONFIG_HOME/opencode/opencode-stm/config.yaml
3
+ # (or ~/.config/opencode/opencode-stm/config.yaml when XDG_CONFIG_HOME is unset
4
+ # or relative). Restart OpenCode after changing configuration.
5
+ #
6
+ # Historical access is disabled by default. Enabling readTranscript sends selected
7
+ # historical content to the active model provider, and enabling any historical
8
+ # flag requires explicit user configuration plus the normal OpenCode permission
9
+ # policy. These flags do not grant authorisation by themselves.
10
+ review:
11
+ enabled: true
12
+ intervalMessages: 20
13
+ maxReminders: 3
14
+ backoffMultiplier: 2
15
+ historical:
16
+ readTitles: false
17
+ renameTitles: false
18
+ readTranscript: false
@@ -0,0 +1,28 @@
1
+ export declare const CONFIG_MAX_BYTES: number;
2
+ export type ReviewConfig = {
3
+ enabled: boolean;
4
+ intervalMessages: number;
5
+ maxReminders: number;
6
+ backoffMultiplier: number;
7
+ };
8
+ export type HistoricalConfig = {
9
+ readTitles: boolean;
10
+ renameTitles: boolean;
11
+ readTranscript: boolean;
12
+ };
13
+ export type StmConfig = {
14
+ review: ReviewConfig;
15
+ historical: HistoricalConfig;
16
+ };
17
+ export declare const DEFAULT_CONFIG: Readonly<StmConfig>;
18
+ export type ConfigResult = {
19
+ ok: true;
20
+ config: StmConfig;
21
+ path: string | null;
22
+ } | {
23
+ ok: false;
24
+ reason: string;
25
+ };
26
+ export declare function resolveConfigPath(env: NodeJS.ProcessEnv, home: string): string;
27
+ export declare function parseConfigText(text: string): ConfigResult;
28
+ export declare function loadConfig(env: NodeJS.ProcessEnv, home: string): ConfigResult;
@@ -0,0 +1,161 @@
1
+ import { lstatSync, readFileSync, statSync } from "node:fs";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { parseAllDocuments } from "yaml";
4
+ export const CONFIG_MAX_BYTES = 64 * 1024;
5
+ export const DEFAULT_CONFIG = Object.freeze({
6
+ review: Object.freeze({ enabled: true, intervalMessages: 20, maxReminders: 3, backoffMultiplier: 2 }),
7
+ historical: Object.freeze({ readTitles: false, renameTitles: false, readTranscript: false }),
8
+ });
9
+ function freshDefaults() {
10
+ return {
11
+ review: { ...DEFAULT_CONFIG.review },
12
+ historical: { ...DEFAULT_CONFIG.historical },
13
+ };
14
+ }
15
+ export function resolveConfigPath(env, home) {
16
+ const xdg = env.XDG_CONFIG_HOME;
17
+ const base = xdg !== undefined && xdg.length > 0 && isAbsolute(xdg) ? xdg : join(home, ".config");
18
+ return join(base, "opencode", "opencode-stm", "config.yaml");
19
+ }
20
+ function isPlainObject(value) {
21
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22
+ }
23
+ const TOP_KEYS = new Set(["review", "historical"]);
24
+ const REVIEW_KEYS = new Set(["enabled", "intervalMessages", "maxReminders", "backoffMultiplier"]);
25
+ const HISTORICAL_KEYS = new Set(["readTitles", "renameTitles", "readTranscript"]);
26
+ function findUnknownKey(input, allowed, prefix) {
27
+ for (const key of Object.keys(input)) {
28
+ if (!allowed.has(key))
29
+ return `config: unknown key ${prefix}${key}`;
30
+ }
31
+ return null;
32
+ }
33
+ function readBoolean(input, key, prefix, fallback) {
34
+ const value = input[key];
35
+ if (value === undefined)
36
+ return fallback;
37
+ if (typeof value !== "boolean")
38
+ return `config: ${prefix}${key} must be a boolean`;
39
+ return value;
40
+ }
41
+ function readInteger(input, key, prefix, min, max, fallback) {
42
+ const value = input[key];
43
+ if (value === undefined)
44
+ return fallback;
45
+ if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
46
+ return `config: ${prefix}${key} must be an integer between ${min} and ${max}`;
47
+ }
48
+ return value;
49
+ }
50
+ export function parseConfigText(text) {
51
+ let documents;
52
+ try {
53
+ documents = parseAllDocuments(text, { uniqueKeys: true, version: "1.2", strict: true });
54
+ }
55
+ catch {
56
+ return { ok: false, reason: "config: invalid YAML" };
57
+ }
58
+ if (documents.length > 1)
59
+ return { ok: false, reason: "config: multiple YAML documents are not supported" };
60
+ const document = documents[0];
61
+ if (document === undefined)
62
+ return { ok: true, config: freshDefaults(), path: null };
63
+ if (document.errors.length > 0)
64
+ return { ok: false, reason: "config: invalid YAML" };
65
+ let raw;
66
+ try {
67
+ // maxAliasCount 0 disallows all alias nodes; it is a toJS option, not a parse option.
68
+ raw = document.toJS({ maxAliasCount: 0 });
69
+ }
70
+ catch {
71
+ return { ok: false, reason: "config: aliases are not supported" };
72
+ }
73
+ if (raw === null || raw === undefined)
74
+ return { ok: true, config: freshDefaults(), path: null };
75
+ if (!isPlainObject(raw))
76
+ return { ok: false, reason: "config: top level must be a mapping" };
77
+ const topError = findUnknownKey(raw, TOP_KEYS, "");
78
+ if (topError !== null)
79
+ return { ok: false, reason: topError };
80
+ const review = { ...DEFAULT_CONFIG.review };
81
+ if (raw.review !== undefined) {
82
+ if (!isPlainObject(raw.review))
83
+ return { ok: false, reason: "config: review must be a mapping" };
84
+ const error = findUnknownKey(raw.review, REVIEW_KEYS, "review.");
85
+ if (error !== null)
86
+ return { ok: false, reason: error };
87
+ const enabled = readBoolean(raw.review, "enabled", "review.", review.enabled);
88
+ if (typeof enabled === "string")
89
+ return { ok: false, reason: enabled };
90
+ const intervalMessages = readInteger(raw.review, "intervalMessages", "review.", 1, 10000, review.intervalMessages);
91
+ if (typeof intervalMessages === "string")
92
+ return { ok: false, reason: intervalMessages };
93
+ const maxReminders = readInteger(raw.review, "maxReminders", "review.", 1, 10, review.maxReminders);
94
+ if (typeof maxReminders === "string")
95
+ return { ok: false, reason: maxReminders };
96
+ const backoffMultiplier = readInteger(raw.review, "backoffMultiplier", "review.", 1, 10, review.backoffMultiplier);
97
+ if (typeof backoffMultiplier === "string")
98
+ return { ok: false, reason: backoffMultiplier };
99
+ review.enabled = enabled;
100
+ review.intervalMessages = intervalMessages;
101
+ review.maxReminders = maxReminders;
102
+ review.backoffMultiplier = backoffMultiplier;
103
+ }
104
+ const historical = { ...DEFAULT_CONFIG.historical };
105
+ if (raw.historical !== undefined) {
106
+ if (!isPlainObject(raw.historical))
107
+ return { ok: false, reason: "config: historical must be a mapping" };
108
+ const error = findUnknownKey(raw.historical, HISTORICAL_KEYS, "historical.");
109
+ if (error !== null)
110
+ return { ok: false, reason: error };
111
+ const readTitles = readBoolean(raw.historical, "readTitles", "historical.", historical.readTitles);
112
+ if (typeof readTitles === "string")
113
+ return { ok: false, reason: readTitles };
114
+ const renameTitles = readBoolean(raw.historical, "renameTitles", "historical.", historical.renameTitles);
115
+ if (typeof renameTitles === "string")
116
+ return { ok: false, reason: renameTitles };
117
+ const readTranscript = readBoolean(raw.historical, "readTranscript", "historical.", historical.readTranscript);
118
+ if (typeof readTranscript === "string")
119
+ return { ok: false, reason: readTranscript };
120
+ historical.readTitles = readTitles;
121
+ historical.renameTitles = renameTitles;
122
+ historical.readTranscript = readTranscript;
123
+ }
124
+ return { ok: true, config: { review, historical }, path: null };
125
+ }
126
+ export function loadConfig(env, home) {
127
+ const path = resolveConfigPath(env, home);
128
+ // lstat distinguishes a truly absent entry from a dangling symlink: statSync
129
+ // reports ENOENT for both, but an existing unusable config must fail closed.
130
+ try {
131
+ lstatSync(path);
132
+ }
133
+ catch (error) {
134
+ if (error.code === "ENOENT") {
135
+ return { ok: true, config: freshDefaults(), path: null };
136
+ }
137
+ return { ok: false, reason: "config: file status is unreadable" };
138
+ }
139
+ let fileStat;
140
+ try {
141
+ fileStat = statSync(path);
142
+ }
143
+ catch {
144
+ return { ok: false, reason: "config: file status is unreadable" };
145
+ }
146
+ if (!fileStat.isFile())
147
+ return { ok: false, reason: "config: path is not a regular file" };
148
+ const size = fileStat.size;
149
+ if (size > CONFIG_MAX_BYTES)
150
+ return { ok: false, reason: `config: file larger than ${CONFIG_MAX_BYTES} bytes` };
151
+ let text;
152
+ try {
153
+ text = readFileSync(path, "utf8");
154
+ }
155
+ catch {
156
+ return { ok: false, reason: "config: file is unreadable" };
157
+ }
158
+ const parsed = parseConfigText(text);
159
+ return parsed.ok ? { ok: true, config: parsed.config, path } : parsed;
160
+ }
161
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Hooks, Plugin, PluginInput, PluginModule } from "@opencode-ai/plugin";
2
+ import { type StmServices } from "./tools.js";
3
+ export declare const STM_PLUGIN_ID = "opencode-stm";
4
+ export type StmRuntime = {
5
+ env?: NodeJS.ProcessEnv;
6
+ home?: string;
7
+ now?: () => number;
8
+ };
9
+ export declare function createTransform(services: StmServices): NonNullable<Hooks["experimental.chat.messages.transform"]>;
10
+ export declare function createEventHook(services: StmServices): NonNullable<Hooks["event"]>;
11
+ export declare function createStm(input: PluginInput, runtime?: StmRuntime): Promise<Hooks>;
12
+ export declare const server: Plugin;
13
+ declare const pluginModule: PluginModule;
14
+ export default pluginModule;
@@ -0,0 +1,210 @@
1
+ // Plugin wiring: transform pipeline, event lifecycle and exported module.
2
+ // The transform mutates the existing outgoing array in place, persists atomic
3
+ // latest-state transitions and never replaces output.messages.
4
+ import { randomUUID } from "node:crypto";
5
+ import { homedir } from "node:os";
6
+ import { loadConfig } from "./config.js";
7
+ import { chunkText, ensureAnchorPart, ensureChunkPart, singleSessionMessages } from "./injection.js";
8
+ import { anchorPartID, classifyMessages, firstObservation, isDefaultTitle, lockExternal, observe, } from "./scheduler.js";
9
+ import { createRootCache, readSession } from "./sdk.js";
10
+ import { createOperationGuard, createStateStore, hashTitle, resolveStateDir } from "./state.js";
11
+ import { createTranscriptQueue } from "./transcript.js";
12
+ import { createCurrentTools, createHistoricalTools } from "./tools.js";
13
+ export const STM_PLUGIN_ID = "opencode-stm";
14
+ // Drop an anchor the observation just created (and any review created with it):
15
+ // it was never verified against the live title, so a later observation must
16
+ // re-create it only after the check passes. Activity advancement is kept.
17
+ function stripUnverifiedAnchor(candidate, base) {
18
+ if (base.review === null && candidate.review !== null) {
19
+ const partID = anchorPartID(candidate.review.id, 0);
20
+ return { ...candidate, review: null, anchors: candidate.anchors.filter((anchor) => anchor.partID !== partID) };
21
+ }
22
+ if (base.review !== null && candidate.review !== null && candidate.review.remindersSent > base.review.remindersSent) {
23
+ const partID = anchorPartID(candidate.review.id, base.review.remindersSent);
24
+ return { ...candidate, review: base.review, anchors: candidate.anchors.filter((anchor) => anchor.partID !== partID) };
25
+ }
26
+ return candidate;
27
+ }
28
+ export function createTransform(services) {
29
+ return async (_input, output) => {
30
+ const single = singleSessionMessages(output);
31
+ if (!single.ok)
32
+ return;
33
+ const sessionID = single.sessionID;
34
+ // Child sessions never receive periodic reviews.
35
+ const root = await services.roots.isRoot(sessionID);
36
+ if (!root.ok || !root.value)
37
+ return;
38
+ // One-shot transcript delivery: attempted on the next eligible transform and
39
+ // released afterwards. OpenCode 1.18.35 reuses this hook during compaction with
40
+ // no reliable discriminator, so a queued chunk may be consumed by a compaction
41
+ // request; the design accepts this limitation and does not claim exclusion.
42
+ const pending = services.queue.take(sessionID, services.now());
43
+ if (pending !== null) {
44
+ ensureChunkPart(single.messages, {
45
+ sessionID,
46
+ messageID: pending.requestMessageID,
47
+ chunkID: pending.id,
48
+ text: chunkText({
49
+ targetSessionID: pending.targetSessionID,
50
+ cursorLabel: pending.cursorLabel,
51
+ page: pending.page,
52
+ text: pending.text,
53
+ complete: pending.complete,
54
+ }),
55
+ });
56
+ }
57
+ const loaded = await services.state.load(sessionID);
58
+ if (!loaded.ok)
59
+ return;
60
+ let fallback = loaded.value;
61
+ if (fallback === null) {
62
+ const read = await readSession(services.client, sessionID);
63
+ if (!read.ok)
64
+ return;
65
+ fallback = firstObservation(isDefaultTitle(read.value.title), hashTitle(read.value.title));
66
+ }
67
+ const observed = classifyMessages(single.messages);
68
+ const run = (base) => observe(base, observed, services.config.review, { newId: () => randomUUID() });
69
+ // Probe with the best-known metadata to decide whether a live title check is
70
+ // needed: before creating a new anchor and, per the approved policy, before
71
+ // replaying the anchors of an unresolved review on every normal request.
72
+ // Retained anchors of resolved reviews need no re-read.
73
+ const probed = run(fallback);
74
+ const needsTitleCheck = probed.anchor !== null || fallback.review !== null;
75
+ if (!needsTitleCheck) {
76
+ // Persist atomically against the latest persisted metadata. The transition
77
+ // is re-run on the freshest state so a concurrent lock, resolution or
78
+ // baseline is preserved instead of overwritten by a stale snapshot.
79
+ const saved = await services.state.update(sessionID, (current) => {
80
+ const base = current ?? fallback;
81
+ const candidate = run(base).meta;
82
+ // A concurrent transition may have moved a threshold within reach after
83
+ // this transform probed. A reminder created here was never title-checked,
84
+ // so drop it and let the next observation create it after re-reading the
85
+ // live title; the activity advancement is kept.
86
+ const next = stripUnverifiedAnchor(candidate, base);
87
+ // If a concurrent transition introduced an unresolved review, replaying
88
+ // its anchors here would skip the mandated title check; defer injection
89
+ // to the next request, which takes the checked path.
90
+ if (base.review !== null)
91
+ return { next, result: null };
92
+ return { next, result: { anchors: next.anchors, locked: next.locked } };
93
+ });
94
+ if (!saved.ok)
95
+ return;
96
+ if (saved.value === null || saved.value.locked)
97
+ return;
98
+ for (const anchor of saved.value.anchors) {
99
+ if (!anchor.obsolete)
100
+ ensureAnchorPart(single.messages, anchor);
101
+ }
102
+ return;
103
+ }
104
+ // Serialise the live title check and the transition with the tools' guarded
105
+ // sections (rename, lock, historical rename) so an attributed title change
106
+ // that lands while this request is assembled is observed coherently: the
107
+ // latest persisted state is never overwritten with a stale snapshot. The
108
+ // guard is never nested here.
109
+ const outcome = await services.guard.run(sessionID, async () => {
110
+ const check = await readSession(services.client, sessionID);
111
+ // Fail closed: without a verified title neither a new anchor nor an
112
+ // unresolved reminder is replayed on this request.
113
+ if (!check.ok)
114
+ return { ok: false, reason: "title-read" };
115
+ const saved = await services.state.update(sessionID, (current) => {
116
+ const base = current ?? fallback;
117
+ // A lock that landed since the probe must win; nothing is injected.
118
+ if (base.locked)
119
+ return { next: base, result: null };
120
+ // Re-validate against the latest persisted digest so a concurrent
121
+ // unlock, keep or rename that already attributed the title is
122
+ // respected here.
123
+ const changed = base.titleDigest === null || hashTitle(check.value.title) !== base.titleDigest;
124
+ const candidate = run(base).meta;
125
+ if (changed) {
126
+ // Unattributed change: lock the latest state conservatively, drop
127
+ // any anchor this observation would have created and replay
128
+ // nothing. The fresh base is locked; a stale snapshot is never
129
+ // written back over a concurrent resolution.
130
+ return { next: lockExternal(stripUnverifiedAnchor(candidate, base)), result: null };
131
+ }
132
+ return { next: candidate, result: { anchors: candidate.anchors, locked: candidate.locked } };
133
+ });
134
+ if (!saved.ok)
135
+ return { ok: false, reason: "state" };
136
+ return { ok: true, plan: saved.value };
137
+ });
138
+ if (!outcome.ok)
139
+ return;
140
+ if (outcome.plan === null || outcome.plan.locked)
141
+ return;
142
+ for (const anchor of outcome.plan.anchors) {
143
+ if (!anchor.obsolete)
144
+ ensureAnchorPart(single.messages, anchor);
145
+ }
146
+ };
147
+ }
148
+ export function createEventHook(services) {
149
+ return async (input) => {
150
+ // Published 1.18.35 event types: only "session.deleted" is consumed; its
151
+ // properties.info is a Session. Runtime payloads are still checked before
152
+ // any cleanup runs.
153
+ const event = input.event;
154
+ if (event.type !== "session.deleted")
155
+ return;
156
+ const id = event.properties.info?.id;
157
+ if (typeof id !== "string" || id.length === 0)
158
+ return;
159
+ await services.state.removeOwned(id);
160
+ services.roots.invalidate(id);
161
+ services.queue.discardCaller(id);
162
+ services.blocked.delete(id);
163
+ for (const [caller, targets] of services.blocked) {
164
+ if (targets.delete(id) && targets.size === 0)
165
+ services.blocked.delete(caller);
166
+ }
167
+ };
168
+ }
169
+ export async function createStm(input, runtime = {}) {
170
+ const env = runtime.env ?? process.env;
171
+ const home = runtime.home ?? homedir();
172
+ const now = runtime.now ?? Date.now;
173
+ // Configuration is read once at startup. An invalid or unreadable file
174
+ // disables every hook and tool with a setting-scoped diagnostic that never
175
+ // echoes the offending raw value.
176
+ const loaded = loadConfig(env, home);
177
+ if (!loaded.ok) {
178
+ console.error(`[${STM_PLUGIN_ID}] disabled: ${loaded.reason}`);
179
+ return { tool: {} };
180
+ }
181
+ const services = {
182
+ client: input.client,
183
+ config: loaded.config,
184
+ state: createStateStore({ stateDir: resolveStateDir(env, home), now }),
185
+ queue: createTranscriptQueue(),
186
+ roots: createRootCache(input.client),
187
+ guard: createOperationGuard(),
188
+ blocked: new Map(),
189
+ now,
190
+ };
191
+ // Tool definitions are fixed for the instance lifetime; config changes need a
192
+ // restart.
193
+ const tools = {
194
+ ...createCurrentTools(services),
195
+ ...createHistoricalTools(services),
196
+ };
197
+ return {
198
+ tool: tools,
199
+ "experimental.chat.messages.transform": createTransform(services),
200
+ event: createEventHook(services),
201
+ dispose: async () => {
202
+ services.queue.dispose();
203
+ services.roots.clear();
204
+ },
205
+ };
206
+ }
207
+ export const server = (input) => createStm(input, {});
208
+ const pluginModule = { id: STM_PLUGIN_ID, server };
209
+ export default pluginModule;
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}