@skillstate/opencode 3.0.0 → 3.0.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/README.md +196 -131
- package/dist/feedback.d.ts +178 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +235 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +33 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/dist/mode.d.ts +97 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +111 -0
- package/dist/mode.js.map +1 -0
- package/dist/opencode-adapter.d.ts +4 -3
- package/dist/opencode-adapter.d.ts.map +1 -1
- package/dist/opencode-adapter.js.map +1 -1
- package/dist/paper-mode.d.ts +421 -0
- package/dist/paper-mode.d.ts.map +1 -0
- package/dist/paper-mode.js +445 -0
- package/dist/paper-mode.js.map +1 -0
- package/dist/plugin.d.ts +158 -8
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +788 -21
- package/dist/plugin.js.map +1 -1
- package/dist/response-sink.d.ts +208 -0
- package/dist/response-sink.d.ts.map +1 -0
- package/dist/response-sink.js +243 -0
- package/dist/response-sink.js.map +1 -0
- package/dist/runtime.d.ts +203 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +332 -0
- package/dist/runtime.js.map +1 -0
- package/dist/spec-loader.d.ts +81 -0
- package/dist/spec-loader.d.ts.map +1 -0
- package/dist/spec-loader.js +163 -0
- package/dist/spec-loader.js.map +1 -0
- package/dist/step-boundary.d.ts +91 -0
- package/dist/step-boundary.d.ts.map +1 -0
- package/dist/step-boundary.js +109 -0
- package/dist/step-boundary.js.map +1 -0
- package/dist/system-hint.d.ts +70 -3
- package/dist/system-hint.d.ts.map +1 -1
- package/dist/system-hint.js +90 -11
- package/dist/system-hint.js.map +1 -1
- package/dist/tools.d.ts +35 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +16 -0
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paper mode — the model-facing context rebuilt as `Aₜ = (P, Σₜ, Oₜ)`.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this module exists, and why it contradicts what came before ────────
|
|
5
|
+
*
|
|
6
|
+
* The previous version of this plugin refused to touch `event.messages` at
|
|
7
|
+
* all. That was a genuine fix for a real defect — the v1 plugin truncated
|
|
8
|
+
* history and injected state as a synthetic `role: "user"` message, which
|
|
9
|
+
* deleted the task statement and displaced the user's request — but it was
|
|
10
|
+
* a fix in the wrong direction.
|
|
11
|
+
*
|
|
12
|
+
* SKILL.state (arXiv:2608.26263v3) does not merely permit discarding the
|
|
13
|
+
* transcript, it requires it:
|
|
14
|
+
*
|
|
15
|
+
* > "The language model never receives previous observations, previous
|
|
16
|
+
* > actions, or previous reasoning traces." (§3)
|
|
17
|
+
*
|
|
18
|
+
* and Appendix A.4 fixes the prompt shape exactly:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* Instructions:
|
|
22
|
+
* {skill.instructions}
|
|
23
|
+
*
|
|
24
|
+
* Skill Execution State:
|
|
25
|
+
* ```json
|
|
26
|
+
* {json.dumps(state, separators=(',',':'))}
|
|
27
|
+
* ```
|
|
28
|
+
* Latest Observation: {observation}
|
|
29
|
+
* ...
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* So replacing the model-facing context is the SPEC, not a violation. The
|
|
33
|
+
* violation was reconstructing it badly.
|
|
34
|
+
*
|
|
35
|
+
* ── What is actually destroyed, and what is not ───────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* `event.messages` is the view handed to the MODEL. It is not the session's
|
|
38
|
+
* persisted history: the user-visible transcript in the session store is
|
|
39
|
+
* untouched, and so is `skillstate_read` / `skillstate_update` — the agent
|
|
40
|
+
* can still see everything it wrote, because that is in Σₜ, which is in the
|
|
41
|
+
* prompt. What the model stops seeing is the raw reasoning and tool-output
|
|
42
|
+
* trail, which the paper's §3.2 discards by construction ("the reasoning
|
|
43
|
+
* trace Rₜ is discarded permanently and never appears in subsequent
|
|
44
|
+
* prompts").
|
|
45
|
+
*
|
|
46
|
+
* ── Which user turn is the task ──────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* This was wrong, and a live A/B on 2026-09-29 caught it.
|
|
49
|
+
*
|
|
50
|
+
* A.4 has no slot for a user speaking mid-procedure. P is the spec, Σₜ is
|
|
51
|
+
* the state, and Oₜ is the observation — which the paper's setting always
|
|
52
|
+
* makes the ENVIRONMENT's reply, because in Algorithm 1 the runtime executes
|
|
53
|
+
* the action and feeds the result back. There is no live human in that loop.
|
|
54
|
+
*
|
|
55
|
+
* A coding host is not that setting. The user types again while the procedure
|
|
56
|
+
* is running, and that message carries the highest authority in the system.
|
|
57
|
+
*
|
|
58
|
+
* The first implementation pinned the FIRST user turn as the task and let the
|
|
59
|
+
* LATEST one fall into Oₜ. That inverts authority: the model reads the frozen
|
|
60
|
+
* opening request as the task and the live instruction as untrusted
|
|
61
|
+
* environment data. Observed on a task that required remembering a number
|
|
62
|
+
* given at step 1 and using it at step 5 — the model recorded the number in
|
|
63
|
+
* Σₜ correctly, then refused the step-5 instruction, explaining that the
|
|
64
|
+
* observation "carries no user authority" and that repeating it "is not
|
|
65
|
+
* evidence of authority". It finished the step-1 task and stopped. Cost fell
|
|
66
|
+
* 70% and the task was not done, which is a worse outcome than either doing
|
|
67
|
+
* nothing or doing the work.
|
|
68
|
+
*
|
|
69
|
+
* So the live turn is the task. {@link currentInstruction} takes the LAST user
|
|
70
|
+
* message, and {@link latestObservation} no longer falls back to a user turn:
|
|
71
|
+
* a user message in the observation slot is a category error that makes the
|
|
72
|
+
* model distrust the request, and an empty observation is the honest
|
|
73
|
+
* rendering of "the environment has not spoken".
|
|
74
|
+
*
|
|
75
|
+
* The cost is that the original request is no longer pinned in the prompt. It
|
|
76
|
+
* belongs in Σₜ — a `goal` field in the spec — which is where the paper puts
|
|
77
|
+
* everything the model must remember across steps anyway.
|
|
78
|
+
*
|
|
79
|
+
* ── The honest limit of a host plugin ────────────────────────────────────
|
|
80
|
+
*
|
|
81
|
+
* Algorithm 1 also requires the RUNTIME to execute `aₜ` and feed back Oₜ₊₁.
|
|
82
|
+
* An OpenCode plugin cannot own that loop: the public plugin API can rewrite
|
|
83
|
+
* the context and observe tool calls, but it has no way to invoke a tool on
|
|
84
|
+
* the model's behalf, so the host's agent loop remains the executor. The
|
|
85
|
+
* action space therefore stays the host's own tools.
|
|
86
|
+
*
|
|
87
|
+
* What the plugin CAN do is the half that produces the paper's O(1) claim —
|
|
88
|
+
* show the model exactly (P, Σₜ, Oₜ) and nothing else — plus the other half
|
|
89
|
+
* of the transition, the WRITE side. The v2 session API has no response hook,
|
|
90
|
+
* but the server's durable event stream publishes `session.text.ended`
|
|
91
|
+
* carrying the completed assistant text block, so the `state_patch` the model
|
|
92
|
+
* emits under the A.4 directive can be parsed and applied to Σₜ. That is a
|
|
93
|
+
* documented public event, not a private channel; see `response-sink.ts`.
|
|
94
|
+
*
|
|
95
|
+
* The two halves together are Algorithm 1's data flow with the host as the
|
|
96
|
+
* executor. Owning the executor end-to-end — including retry-with-rollback
|
|
97
|
+
* and the action being an opaque string the runtime dispatches — requires
|
|
98
|
+
* {@link SkillStateRuntime} with an LLM function and an action executor; see
|
|
99
|
+
* `packages/bench`.
|
|
100
|
+
*/
|
|
101
|
+
import { PromptTransformer } from '@skillstate/core';
|
|
102
|
+
import { applyFeedback, applyObservation } from './feedback.js';
|
|
103
|
+
/** Stable id for the synthetic paper prompt, so the host can diff turns. */
|
|
104
|
+
export const PAPER_MESSAGE_ID = 'skillstate-paper-prompt';
|
|
105
|
+
/** The synthetic id used for the system-fragment preamble. */
|
|
106
|
+
const PAPER_PREAMBLE_MARK = '<skillstate-task>';
|
|
107
|
+
const transformer = new PromptTransformer();
|
|
108
|
+
/**
|
|
109
|
+
* The text a content part carries, whatever shape it arrives in.
|
|
110
|
+
*
|
|
111
|
+
* Two shapes exist and missing the second one is the single worst bug this
|
|
112
|
+
* module ever had:
|
|
113
|
+
*
|
|
114
|
+
* - `{ type: 'text', text: '…' }` — user, assistant and system turns;
|
|
115
|
+
* - `{ type: 'tool-result', result: { type: 'text', value: '…' } }` — what
|
|
116
|
+
* OpenCode v2 actually emits for a tool result.
|
|
117
|
+
*
|
|
118
|
+
* A reader that handled only the first made Oₜ **permanently empty**: the
|
|
119
|
+
* host sends `result.value`, not `text`, so every observation came back as
|
|
120
|
+
* `''` and the model never saw the result of any tool it had just run. It
|
|
121
|
+
* would read a file, answer correctly in that same turn, and have no trace
|
|
122
|
+
* of the value on the next one — which presented as "the model will not
|
|
123
|
+
* record what it discovers" and cost a long hunt through prompt slots and
|
|
124
|
+
* model choice before anyone looked at the shape of the payload.
|
|
125
|
+
*
|
|
126
|
+
* The result body is unwrapped generically rather than assuming one layout:
|
|
127
|
+
* a bare string, `{ value }`, `{ output }`, or a nested `{ content }` all
|
|
128
|
+
* appear across host versions, and an observation that silently empties on
|
|
129
|
+
* a shape change is the failure mode that costs a debugging session.
|
|
130
|
+
*/
|
|
131
|
+
function partText(part, depth = 0) {
|
|
132
|
+
// Hard depth cap, not a comment claiming one. A malformed or
|
|
133
|
+
// self-referential payload is a real possibility in a message the plugin
|
|
134
|
+
// does not own, and this runs inside the agent loop: an unbounded walk
|
|
135
|
+
// there is a hang, not a wrong answer.
|
|
136
|
+
if (depth > 4)
|
|
137
|
+
return '';
|
|
138
|
+
if (typeof part === 'string')
|
|
139
|
+
return part;
|
|
140
|
+
if (typeof part !== 'object' || part === null)
|
|
141
|
+
return '';
|
|
142
|
+
const record = part;
|
|
143
|
+
const type = record['type'];
|
|
144
|
+
if (type === 'text' && typeof record['text'] === 'string')
|
|
145
|
+
return record['text'];
|
|
146
|
+
if (type === 'tool-result')
|
|
147
|
+
return resultText(record['result'], depth + 1);
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
function resultText(result, depth) {
|
|
151
|
+
if (typeof result === 'string')
|
|
152
|
+
return result;
|
|
153
|
+
if (typeof result !== 'object' || result === null)
|
|
154
|
+
return '';
|
|
155
|
+
const record = result;
|
|
156
|
+
for (const key of ['value', 'output', 'text']) {
|
|
157
|
+
const candidate = record[key];
|
|
158
|
+
if (typeof candidate === 'string')
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
return partText(record['content'], depth + 1);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A message's readable text, joined across its parts.
|
|
165
|
+
*
|
|
166
|
+
* Order is preserved and parts are newline-joined, because a turn can carry
|
|
167
|
+
* both a file listing and a tool result and dropping either loses a fact the
|
|
168
|
+
* model needed.
|
|
169
|
+
*/
|
|
170
|
+
function textOf(message) {
|
|
171
|
+
if (!Array.isArray(message.content))
|
|
172
|
+
return '';
|
|
173
|
+
return message.content
|
|
174
|
+
.map((part) => partText(part))
|
|
175
|
+
.filter((text) => text.length > 0)
|
|
176
|
+
.join('\n');
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The user's live instruction — what the model must act on this step.
|
|
180
|
+
*
|
|
181
|
+
* The LAST user text message. Not the first: pinning the opening request and
|
|
182
|
+
* demoting the live one to the observation slot is what made a model reject
|
|
183
|
+
* the user's own instruction as "untrusted" during the 2026-09-29 A/B. See
|
|
184
|
+
* the module header for that failure in full.
|
|
185
|
+
*
|
|
186
|
+
* Returns `''` for a session with no user turn, which is not a case a
|
|
187
|
+
* procedure produces but must not crash on.
|
|
188
|
+
*/
|
|
189
|
+
export function currentInstruction(messages) {
|
|
190
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
191
|
+
const message = messages[i];
|
|
192
|
+
if (message.role !== 'user')
|
|
193
|
+
continue;
|
|
194
|
+
const text = textOf(message).trim();
|
|
195
|
+
if (text.length > 0)
|
|
196
|
+
return text;
|
|
197
|
+
}
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The latest environment observation — Oₜ.
|
|
202
|
+
*
|
|
203
|
+
* Preference order, and why:
|
|
204
|
+
*
|
|
205
|
+
* 1. the newest TOOL message — that is the result of the previous aₜ, which
|
|
206
|
+
* is exactly what the paper feeds back;
|
|
207
|
+
* 2. `''` — the environment has not spoken.
|
|
208
|
+
*
|
|
209
|
+
* There is deliberately NO fallback to a user turn, and removing one is the
|
|
210
|
+
* fix for the 2026-09-29 failure. A user message in this slot is a category
|
|
211
|
+
* error: A.4's grammar says the observation is what the environment returned,
|
|
212
|
+
* so a request placed there reads as data about the world rather than as
|
|
213
|
+
* something to do. The model acted on that reading exactly — it recorded the
|
|
214
|
+
* step-1 number correctly and then refused the step-5 instruction because,
|
|
215
|
+
* in its own words, the observation "carries no user authority".
|
|
216
|
+
*
|
|
217
|
+
* The live instruction now travels in P via {@link currentInstruction}, where
|
|
218
|
+
* it is unambiguously the request. At step 0 the observation is empty and the
|
|
219
|
+
* template still renders "Latest Observation: " — the faithful rendering of a
|
|
220
|
+
* procedure that has not run yet.
|
|
221
|
+
*
|
|
222
|
+
* `now` stamps {@link Observation.timestamp}. A.4 never renders the
|
|
223
|
+
* timestamp, so it cannot change the prompt; it is set because the core type
|
|
224
|
+
* requires it and because a sink that later needs to order observations has
|
|
225
|
+
* something to order by.
|
|
226
|
+
*/
|
|
227
|
+
export function latestObservation(messages, now = Date.now()) {
|
|
228
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
229
|
+
const message = messages[i];
|
|
230
|
+
if (message.role === 'tool') {
|
|
231
|
+
return { content: textOf(message).trim(), timestamp: now, source: 'tool' };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { content: '', timestamp: now, source: 'empty' };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The specification P, with the live request carried as a preamble.
|
|
238
|
+
*
|
|
239
|
+
* A spec is authored before the task is known, so it cannot contain the
|
|
240
|
+
* request. Putting the current instruction at the TOP of the instructions —
|
|
241
|
+
* above the spec's own text, not below it — is what makes it read as the
|
|
242
|
+
* request rather than as another paragraph of standing guidance. The
|
|
243
|
+
* 2026-09-29 A/B showed that a model told to reason about a "task block"
|
|
244
|
+
* will start treating the surrounding structure as material to analyse rather
|
|
245
|
+
* than as instructions to follow, so the marker's contents have to be
|
|
246
|
+
* unambiguously the thing to do right now.
|
|
247
|
+
*
|
|
248
|
+
* The marker makes the block assertable: a test can check the live
|
|
249
|
+
* instruction is present rather than merely intended.
|
|
250
|
+
*/
|
|
251
|
+
export function proceduralSpecWithTask(spec, task) {
|
|
252
|
+
if (task.length === 0)
|
|
253
|
+
return spec;
|
|
254
|
+
return {
|
|
255
|
+
...spec,
|
|
256
|
+
instructions: `${PAPER_PREAMBLE_MARK}\n${task}\n</skillstate-task>\n\n${spec.instructions}`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Build the A.4 prompt from the host's transcript.
|
|
261
|
+
*
|
|
262
|
+
* Pure: it reads the messages and returns the prompt plus a description of
|
|
263
|
+
* what it chose, without mutating anything. {@link applyPaperContext} is the
|
|
264
|
+
* half that writes.
|
|
265
|
+
*/
|
|
266
|
+
export function buildPaperPrompt(options) {
|
|
267
|
+
const { spec, state, messages } = options;
|
|
268
|
+
// The live instruction, not the opening request. See the module header for
|
|
269
|
+
// why pinning the first message cost a real task.
|
|
270
|
+
const instruction = currentInstruction(messages);
|
|
271
|
+
const observed = latestObservation(messages);
|
|
272
|
+
// The runtime's own report rides in Oₜ, which is the environment's channel.
|
|
273
|
+
let content = observed.content;
|
|
274
|
+
if (options.continuation !== undefined) {
|
|
275
|
+
// The marker is chosen here, beside the text, because a report labelled
|
|
276
|
+
// with an order's marker is an order wearing a report's clothes. That
|
|
277
|
+
// happened: `[next step - do it now ...]` in front of "step 1 ended; your
|
|
278
|
+
// state patch was applied" told the model to hurry something that was
|
|
279
|
+
// already over.
|
|
280
|
+
content = applyObservation(content, options.continuationKind === 'report' ? REPORT_MARKER : ORDER_MARKER, options.continuation);
|
|
281
|
+
}
|
|
282
|
+
const observation = content === observed.content ? observed : { ...observed, content };
|
|
283
|
+
const effective = proceduralSpecWithTask(spec, instruction);
|
|
284
|
+
// A rejected patch is NOT an observation, and putting it in Oₜ said so.
|
|
285
|
+
//
|
|
286
|
+
// §5.1 line 8 is explicit: `A_t ← A_t + corrective_feedback` — the correction
|
|
287
|
+
// is appended to the PROMPT, not to the environment's reply. §6.3 repeats it
|
|
288
|
+
// in words: "appended to the same bounded prompt". The core runtime has always
|
|
289
|
+
// done that, and the adapter did not, so the same event landed in two
|
|
290
|
+
// different slots depending on which entry point ran.
|
|
291
|
+
//
|
|
292
|
+
// The adapter's original reason was that Oₜ was the only place that could
|
|
293
|
+
// carry an addition without touching the A.4 render. That reason confuses the
|
|
294
|
+
// template with the prompt: §5.2 makes A.4 the template, and §5.1 line 8
|
|
295
|
+
// makes the prompt that template plus a corrective suffix. Byte-verbatim is a
|
|
296
|
+
// property of the template, and appending after it keeps it.
|
|
297
|
+
const base = transformer.formatPaper(effective, state, observation);
|
|
298
|
+
return {
|
|
299
|
+
prompt: options.feedback === undefined ? base : `${base}\n\n${applyFeedback('', options.feedback)}`,
|
|
300
|
+
task: instruction,
|
|
301
|
+
observation,
|
|
302
|
+
observationSource: observed.source,
|
|
303
|
+
discardedMessages: messages.length,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The note the host contributes beside P, when the model has to act.
|
|
308
|
+
*
|
|
309
|
+
* ── Why this exists, from a measured failure ─────────────────────────────
|
|
310
|
+
*
|
|
311
|
+
* A.4 says to emit `{state_patch, action}` and nothing else. It does not say
|
|
312
|
+
* who runs `action`, because in the paper a runtime does: Algorithm 1 has the
|
|
313
|
+
* runtime execute aₜ and feed Oₜ₊₁ back. Here the executor is OpenCode's own
|
|
314
|
+
* agent loop, and the model has no way to know that. So it does the reasonable
|
|
315
|
+
* thing with an ambiguous instruction — it writes
|
|
316
|
+
*
|
|
317
|
+
* ```json
|
|
318
|
+
* {"state_patch": {"total": 17, "files": 1}, "action": "Read file src/cfg2.ts"}
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* and stops, because it has done exactly what P asked and nothing on the
|
|
322
|
+
* wire will ever execute that string. Measured on a task with eight files:
|
|
323
|
+
* three runs, three patches applied correctly, and then a hard stop after
|
|
324
|
+
* file one. The state machinery worked; the loop never turned.
|
|
325
|
+
*
|
|
326
|
+
* The fix cannot go in P, and two reasons make that a hard rule rather than
|
|
327
|
+
* taste. P is the paper's Appendix A.4, kept byte-identical so a claim about
|
|
328
|
+
* conformance stays checkable (`tests/opencode/paper-mode.test.ts`); and a
|
|
329
|
+
* correction injected there would move with the state it is supposed to
|
|
330
|
+
* accompany. So the note lives in the system slot, which the host owns and
|
|
331
|
+
* which is already replaced wholesale — see {@link applyPaperContext}.
|
|
332
|
+
*
|
|
333
|
+
* It states a fact about the wiring, not an order, for the same reason the
|
|
334
|
+
* notes fragment avoids imperatives: an injected instruction that displaces
|
|
335
|
+
* the task is the v1 failure, and this is a task the model must finish.
|
|
336
|
+
*/
|
|
337
|
+
/**
|
|
338
|
+
* The marker that puts the runtime's pending action in Oₜ.
|
|
339
|
+
*
|
|
340
|
+
* The wording is measured, not chosen. A bare `[next step] read src/cfg2.ts`
|
|
341
|
+
* was read by the model as a topic and answered with a narration of it —
|
|
342
|
+
* "I'll read cfg3.ts next, as directed by the observation" — which is a whole
|
|
343
|
+
* extra turn for a sentence of text. Across 51 steps the model patched 19 and
|
|
344
|
+
* narrated on the rest, so roughly two thirds of the budget went to the model
|
|
345
|
+
* confirming that it had understood the directive before acting on it.
|
|
346
|
+
*
|
|
347
|
+
* A step is not free and the state only advances on the patching ones, so that
|
|
348
|
+
* ratio set the pace of the whole run: 2.9 steps per file, which is what put a
|
|
349
|
+
* thirty-file task over a sixty-four step ceiling.
|
|
350
|
+
*
|
|
351
|
+
* IT DID NOT WORK, and the way it failed is what §2 forbids. The imperative was
|
|
352
|
+
* meant to collapse the acknowledgement into the action. Instead the model
|
|
353
|
+
* accepted the directive and complied with it, turn after turn: "I'll read
|
|
354
|
+
* cfg3.ts next, as directed by the observation", and then read cfg3.ts. Fifty-four
|
|
355
|
+
* reads for thirty files, one grep used three times as a side errand. The order
|
|
356
|
+
* was the model's own past action, so it never looked for a better way than the
|
|
357
|
+
* one already written down.
|
|
358
|
+
*
|
|
359
|
+
* The string survives behind SKILLSTATE_CONTINUATION=1 because a dead fix whose
|
|
360
|
+
* cost is measured is worth more than a fix nobody can price. The default is
|
|
361
|
+
* REPORT_MARKER.
|
|
362
|
+
*/
|
|
363
|
+
export const REPORT_MARKER = '[runtime]';
|
|
364
|
+
/** @deprecated kept only so the order path names the same constant it always did. */
|
|
365
|
+
export const CONTINUATION_MARKER = '[next step — do this now, do not describe it first]';
|
|
366
|
+
/** The same, under the name the call site reads it by. */
|
|
367
|
+
export const ORDER_MARKER = CONTINUATION_MARKER;
|
|
368
|
+
/**
|
|
369
|
+
* The one thing the host's own loop needs the model to know.
|
|
370
|
+
*
|
|
371
|
+
* Its second sentence used to read "each step ends with a real tool call — read
|
|
372
|
+
* the next file, or answer and stop." That clause was this repository's
|
|
373
|
+
* instruction, not a host constraint, and it pointed the wrong way: a
|
|
374
|
+
* thirty-file task produced 51, 53 and 54 `read` calls for thirty files, while
|
|
375
|
+
* a control with no step driver at all read one file and ran a single grep —
|
|
376
|
+
* nine calls for the same work.
|
|
377
|
+
*
|
|
378
|
+
* Worth being precise about how much this was worth, because the first version
|
|
379
|
+
* of this comment claimed the note CAUSED those counts and that is not
|
|
380
|
+
* supported. The transcripts show the model batching anyway — up to nine
|
|
381
|
+
* consecutive `read` calls inside a single turn. So the note was pushing the
|
|
382
|
+
* wrong way and was not obeyed literally. It is one defect, and removing it is
|
|
383
|
+
* right on its own terms; it is not the whole of the 43% of calls that are
|
|
384
|
+
* re-reads.
|
|
385
|
+
*
|
|
386
|
+
* The host does not limit a turn to one tool call. What limits it is eq. 1: the
|
|
387
|
+
* prompt is (P, Σₜ, Oₜ) and there is ONE Oₜ, so a turn that makes several calls
|
|
388
|
+
* keeps only the last result and the model must re-read the rest. That is not
|
|
389
|
+
* a guess — a turn that batched nine reads produced a model that said "the last
|
|
390
|
+
* observation re-read cfg8.ts" and then went and read cfg8 again, and the
|
|
391
|
+
* thirty-file run reached 103 reads for 29 files.
|
|
392
|
+
*
|
|
393
|
+
* So the note says the truth the model needs: one call per step, because the
|
|
394
|
+
* second one would be lost. An earlier version of this comment went the other
|
|
395
|
+
* way and told the model it could make as many calls as it liked. That was
|
|
396
|
+
* written before the single-observation consequence was understood, and it
|
|
397
|
+
* invited exactly the data loss the paper's own equation makes unavoidable.
|
|
398
|
+
*/
|
|
399
|
+
export const HOST_ACTION_NOTE = [
|
|
400
|
+
'The `action` field is a label, not a command: nothing executes it.',
|
|
401
|
+
'A step ends when you call a tool, so end each step with a real tool call —',
|
|
402
|
+
'or answer and stop. Emitting a state_patch on its own ends the run, however',
|
|
403
|
+
'correct the patch was. One tool call per step: this prompt shows you only the',
|
|
404
|
+
'LAST result, so a second call in the same step would leave you unable to',
|
|
405
|
+
'remember the first, and you would have to read it again.',
|
|
406
|
+
].join(' ');
|
|
407
|
+
/**
|
|
408
|
+
* Replace the model-facing context with exactly (P, Σₜ, Oₜ).
|
|
409
|
+
*
|
|
410
|
+
* Two edits, and both are required:
|
|
411
|
+
*
|
|
412
|
+
* - the A.4 prompt becomes the ONLY message, so no reasoning, action or tool
|
|
413
|
+
* output from earlier steps survives into this dispatch;
|
|
414
|
+
* - the host's own system prompt is KEPT. An earlier version replaced it
|
|
415
|
+
* wholesale, on the reasoning that P is the entire instruction surface and
|
|
416
|
+
* the default prompt tells the model to prefer parallel tool calls, which
|
|
417
|
+
* is incoherent with a single-step state machine. Measured: that reasoning
|
|
418
|
+
* was wrong about a consequence, because the default system prompt is also
|
|
419
|
+
* what carries the host's tool-use discipline. Replacing it with one
|
|
420
|
+
* sentence produced a model that emitted a correct patch and then never
|
|
421
|
+
* called a tool again — the run ended after the first file, three times
|
|
422
|
+
* running, on two models. The "parallel calls" worry is real but costs
|
|
423
|
+
* less than a dead loop; the trade is measured, not assumed.
|
|
424
|
+
*
|
|
425
|
+
* `systemPrefix` is where a host that cannot be the runtime says so; see
|
|
426
|
+
* {@link HOST_ACTION_NOTE}. It is optional because a deployment where
|
|
427
|
+
* something else does own the executor has no such gap to describe.
|
|
428
|
+
*
|
|
429
|
+
* The array is mutated in place: the host keeps the original reference.
|
|
430
|
+
*/
|
|
431
|
+
export function applyPaperContext(event, built, systemPrefix) {
|
|
432
|
+
const message = {
|
|
433
|
+
id: PAPER_MESSAGE_ID,
|
|
434
|
+
role: 'user',
|
|
435
|
+
content: [{ type: 'text', text: built.prompt }],
|
|
436
|
+
metadata: {},
|
|
437
|
+
};
|
|
438
|
+
event.messages.length = 0;
|
|
439
|
+
event.messages.push(message);
|
|
440
|
+
if (event.system !== undefined && systemPrefix !== undefined) {
|
|
441
|
+
event.system.push({ type: 'text', text: systemPrefix });
|
|
442
|
+
}
|
|
443
|
+
return message;
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=paper-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paper-mode.js","sourceRoot":"","sources":["../src/paper-mode.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -27,14 +27,33 @@
|
|
|
27
27
|
*
|
|
28
28
|
* ── The v2 design ────────────────────────────────────────────────────────
|
|
29
29
|
*
|
|
30
|
+
* Two modes, each enforced by a test. They are different contracts with the
|
|
31
|
+
* model, not variants of one behaviour:
|
|
32
|
+
*
|
|
33
|
+
* - **`notes` (default).** Contribute one additive, bounded fragment to
|
|
34
|
+
* `event.system` and leave the transcript alone. The agent sees its own
|
|
35
|
+
* history the way the host intends, and the saved notes ride alongside it.
|
|
36
|
+
* This is the mode that fixed the v1 failure, and it is the default for
|
|
37
|
+
* exactly that reason.
|
|
38
|
+
* - **`paper` (opt-in).** Replace the model-facing context with
|
|
39
|
+
* Aₜ = (P, Σₜ, Oₜ) — the paper's Appendix A.4 prompt, byte-verbatim — and
|
|
40
|
+
* apply the `state_patch` the model emits in response. This is the paper's
|
|
41
|
+
* specification, and it is a real behavioural change: the model stops
|
|
42
|
+
* seeing its transcript, because §3.2 discards the reasoning trace by
|
|
43
|
+
* construction. Select it with `mode: "paper"` in the project's
|
|
44
|
+
* `skillstate.json` or `SKILLSTATE_MODE=paper`; see `mode.ts`.
|
|
45
|
+
*
|
|
46
|
+
* The default is `notes` and must stay that way: a default that discards the
|
|
47
|
+
* user's task is the v1 bug under a new name.
|
|
48
|
+
*
|
|
30
49
|
* Three rules, each enforced by a test:
|
|
31
50
|
*
|
|
32
|
-
* - **
|
|
33
|
-
* fragment to `event.system` and leaves the transcript alone.
|
|
34
|
-
* `tests/opencode/context-integrity.test.ts`.
|
|
35
|
-
* - **Never inject behavioural instructions.** The system
|
|
36
|
-
* describes what the notes are and when to use them; it contains
|
|
37
|
-
* "you must", no "always", and no output format. See
|
|
51
|
+
* - **Notes mode never mutates `event.messages`.** The plugin contributes
|
|
52
|
+
* one additive fragment to `event.system` and leaves the transcript alone.
|
|
53
|
+
* See `tests/opencode/context-integrity.test.ts`.
|
|
54
|
+
* - **Never inject behavioural instructions in notes mode.** The system
|
|
55
|
+
* fragment describes what the notes are and when to use them; it contains
|
|
56
|
+
* no "you must", no "always", and no output format. See
|
|
38
57
|
* `system-hint.ts`.
|
|
39
58
|
* - **Inert until used.** A project with no state file gets no system
|
|
40
59
|
* fragment at all and behaves exactly like vanilla OpenCode. No files are
|
|
@@ -66,17 +85,148 @@
|
|
|
66
85
|
import { Plugin } from '@opencode/plugin';
|
|
67
86
|
/** Stable plugin id — scopes plugin storage and identifies it in `/api/plugin`. */
|
|
68
87
|
export declare const PLUGIN_ID = "skillstate";
|
|
88
|
+
/**
|
|
89
|
+
* The action carried forward when a turn produced no usable patch.
|
|
90
|
+
*
|
|
91
|
+
* NOT `__invalid_patch__`, though the paper names that sentinel at §5.1 line
|
|
92
|
+
* 9. It is a return value there — what the step function hands back to signal
|
|
93
|
+
* that Σ is unchanged — and forwarding it into the prompt as the next action
|
|
94
|
+
* is meaningless to a model: it is a name, not a request. The retry instruction
|
|
95
|
+
* the model actually needs already rides in Oₜ through the feedback queue, so
|
|
96
|
+
* this only has to say "keep going", and the queue says why.
|
|
97
|
+
*/
|
|
98
|
+
export declare const CONTINUE_ACTION = "continue";
|
|
99
|
+
/**
|
|
100
|
+
* Record every event type the plugin actually receives.
|
|
101
|
+
*
|
|
102
|
+
* @non-paper diagnostics, same file. The advance is triggered by one event
|
|
103
|
+
* type and one, and a trigger that never fires is indistinguishable from one
|
|
104
|
+
* that is wired wrong — so the arrival counts have to be visible. Cheap, and
|
|
105
|
+
* it would have saved guessing.
|
|
106
|
+
*/
|
|
107
|
+
export declare function recordEvent(path: string | undefined, type: string): void;
|
|
108
|
+
/**
|
|
109
|
+
* Whether the host has just executed a tool for this request.
|
|
110
|
+
*
|
|
111
|
+
* A tool result in the transcript is the observable edge of "an action ran".
|
|
112
|
+
* There is no event that says so in a shape this plugin can trust — and an
|
|
113
|
+
* event the host does not wait for is what caused the read-after-write race
|
|
114
|
+
* fixed in `response-sink.ts`, so the transcript is the more reliable of the
|
|
115
|
+
* two here as well as the more available one.
|
|
116
|
+
*/
|
|
117
|
+
/** Tool-result parts in the newest tool message, counted. */
|
|
118
|
+
export declare function countToolResults(messages: ReadonlyArray<{
|
|
119
|
+
role: string;
|
|
120
|
+
content: unknown;
|
|
121
|
+
}>): number;
|
|
122
|
+
/**
|
|
123
|
+
* Append what the host actually handed us to a file, for diagnosis.
|
|
124
|
+
*
|
|
125
|
+
* @non-paper diagnostics. Enabled by `SKILLSTATE_DEBUG_PROMPT=<path>`.
|
|
126
|
+
*
|
|
127
|
+
* This exists because of a bug that was invisible from the inside for a
|
|
128
|
+
* long time. The model would run a tool, get the answer, and never record
|
|
129
|
+
* it — which looks exactly like a model refusing to cooperate, and sent the
|
|
130
|
+
* search through prompt slots, model choice and spec wording. The cause was
|
|
131
|
+
* the SHAPE: OpenCode v2 delivers a tool result as
|
|
132
|
+
* `{ type: 'tool-result', result: { value } }`, so a reader that only knew
|
|
133
|
+
* `{ type: 'text', text }` made Oₜ permanently empty without ever throwing.
|
|
134
|
+
*
|
|
135
|
+
* The dump records the part types alongside the extracted text, so that
|
|
136
|
+
* class of failure is visible on sight: a `tool-result` in the list next to
|
|
137
|
+
* an empty `observation` says the reader, not the model, is at fault.
|
|
138
|
+
*
|
|
139
|
+
* Append-only so a session's turns accumulate in order, and every failure
|
|
140
|
+
* is swallowed — diagnostics must never break the agent loop.
|
|
141
|
+
*/
|
|
142
|
+
export declare function dumpPromptShape(path: string | undefined, messages: ReadonlyArray<{
|
|
143
|
+
role: string;
|
|
144
|
+
content: unknown;
|
|
145
|
+
}>, state?: Record<string, unknown>): void;
|
|
146
|
+
/**
|
|
147
|
+
* One line per turn of the anti-drift diagnostic.
|
|
148
|
+
*
|
|
149
|
+
* The drift notice has a claim attached to it — "the model drifts, the notice
|
|
150
|
+
* brings it back" — and neither half can be checked from inside the process.
|
|
151
|
+
* A notice in a prompt is not an observation of a model: the fragment may be
|
|
152
|
+
* built correctly and the model may ignore it, and the two look identical
|
|
153
|
+
* from the code's side. Worse, both look identical from the *outside* too,
|
|
154
|
+
* which is what made the earlier `tool-result` bug so expensive to find.
|
|
155
|
+
*
|
|
156
|
+
* So each line carries the evidence that distinguishes them:
|
|
157
|
+
*
|
|
158
|
+
* - `notice` — was the drift sentence in the fragment that went out this turn;
|
|
159
|
+
* - `writes` — how many times the state file had changed when it went out, so
|
|
160
|
+
* a notice that repeats forever is visible as a flat counter;
|
|
161
|
+
* - `fragments` — how many turns had passed without a change.
|
|
162
|
+
*
|
|
163
|
+
* That is enough to say "the notice fired and the state moved afterwards" or
|
|
164
|
+
* "the notice fired and nothing happened", which is the only claim worth
|
|
165
|
+
* making about it.
|
|
166
|
+
*
|
|
167
|
+
* @non-paper diagnostics. Enabled by `SKILLSTATE_DEBUG_DRIFT=<path>`.
|
|
168
|
+
* Separate from {@link dumpPromptShape} because it answers a different
|
|
169
|
+
* question: that one asks what the host sent, this one asks what the model
|
|
170
|
+
* did about what we sent.
|
|
171
|
+
*/
|
|
172
|
+
export declare function dumpDrift(path: string | undefined, record: {
|
|
173
|
+
readonly scope: string;
|
|
174
|
+
readonly turns: number;
|
|
175
|
+
readonly notice: boolean;
|
|
176
|
+
readonly writes: number;
|
|
177
|
+
}): void;
|
|
178
|
+
/**
|
|
179
|
+
* One line per step, for the run that answered correctly while its state
|
|
180
|
+
* under-reported the work. Enabled by `SKILLSTATE_DEBUG_STEPS=<path>`.
|
|
181
|
+
*
|
|
182
|
+
* The 30-file measurement produced the most confusing result in this project's
|
|
183
|
+
* history: paper mode answered correctly, its state ended 25/30, and the
|
|
184
|
+
* control's ended 30/30 complete. Twenty-five patches were emitted and all
|
|
185
|
+
* twenty-five landed, so no patch was lost — the model read every file and
|
|
186
|
+
* declined to patch the last five, while the loop kept driving it. From the
|
|
187
|
+
* outside that is indistinguishable from the loop stopping, from the model
|
|
188
|
+
* silently abandoning the protocol, and from the ceiling being hit.
|
|
189
|
+
*
|
|
190
|
+
* So this prints the thing that tells those apart: at every step, whether a
|
|
191
|
+
* patch was applied, what the state looked like, and whether the driver asked
|
|
192
|
+
* again. Every previous wrong guess in this file came from reasoning about the
|
|
193
|
+
* loop instead of watching it.
|
|
194
|
+
*/
|
|
195
|
+
export declare function dumpStepTrace(path: string | undefined, record: {
|
|
196
|
+
readonly sessionID: string;
|
|
197
|
+
readonly step: number;
|
|
198
|
+
readonly attempt: number;
|
|
199
|
+
readonly applied: boolean;
|
|
200
|
+
readonly done: number;
|
|
201
|
+
readonly total: number | null;
|
|
202
|
+
readonly drove: boolean;
|
|
203
|
+
readonly note: string;
|
|
204
|
+
}): void;
|
|
205
|
+
/**
|
|
206
|
+
* The step ceiling, or `undefined` to keep the default.
|
|
207
|
+
*
|
|
208
|
+
* A malformed value is ignored rather than thrown on or silently clamped: a
|
|
209
|
+
* typo in an environment variable should leave the ceiling where the code says
|
|
210
|
+
* it is, not quietly become some other number that then gets measured.
|
|
211
|
+
*/
|
|
212
|
+
export declare function maxStepsFromEnv(): number | undefined;
|
|
69
213
|
/**
|
|
70
214
|
* The plugin definition.
|
|
71
215
|
*
|
|
72
|
-
* `setup` wires
|
|
216
|
+
* `setup` wires the session registry, the project state store, the native
|
|
217
|
+
* tools, the mode resolver and the one `context` hook, then returns a cleanup
|
|
218
|
+
* function.
|
|
73
219
|
*
|
|
74
220
|
* - a {@link SessionRegistry}, fed by the server event stream, so a
|
|
75
221
|
* sub-agent session is recognised and given its own state file;
|
|
76
222
|
* - a {@link ProjectStateStore} rooted at the plugin's own project
|
|
77
223
|
* location, so two checkouts served by one OpenCode server never share
|
|
78
224
|
* state;
|
|
79
|
-
* -
|
|
225
|
+
* - a {@link SpecResolver} for paper mode's P, so a project that ships its
|
|
226
|
+
* own `skill-spec.json` gets its own procedure;
|
|
227
|
+
* - a {@link PaperStateSink}, in paper mode only, which applies the
|
|
228
|
+
* `state_patch` the model emits;
|
|
229
|
+
* - native tools plus a single `context` hook whose body depends on the mode.
|
|
80
230
|
*
|
|
81
231
|
* The event subscription is the only resource the plugin owns, so the
|
|
82
232
|
* returned cleanup aborts it. Hook and tool registrations are disposed by
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAkB1C,mFAAmF;AACnF,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,aAAa,CAAC;AAqJ1C;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAOxE;AAED;;;;;;;;GAQG;AACH,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,MAAM,CAUpG;AAUD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EAC3D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CA4BN;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,MAAM,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5G,IAAI,CAON;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,MAAM,EAAE;IACN,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACA,IAAI,CAON;AAED;;;;;;GAMG;AAEH,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAQpD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,gBAAgB,eAgf3B,CAAC;eAEY,gBAAgB"}
|