opencode-codeops 1.10.0 → 1.10.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/CHANGELOG.md
CHANGED
|
@@ -126,6 +126,15 @@ Semantics:
|
|
|
126
126
|
For `exec-plan`, a bare flag follows each phase's suggestion, while `--auto-effort=<level>`
|
|
127
127
|
forces that level for the whole run, including every dispatch marker composed during it.
|
|
128
128
|
|
|
129
|
+
## Optional tracing
|
|
130
|
+
|
|
131
|
+
Set `CODEOPS_EFFORT_TRACE=1` (or `true`) in the environment that starts OpenCode to record what
|
|
132
|
+
the plugin does. One content-free JSON line is appended per marker capture and per request to
|
|
133
|
+
`reasoning-effort-trace.jsonl` inside the session's scratch directory. Lines carry only a
|
|
134
|
+
timestamp, event name, message id, agent name, level, source, and applied flag — never prompt
|
|
135
|
+
text or file content. The trace lives with the rest of the session scratch and is removed with
|
|
136
|
+
it. Tracing is off by default and never affects a request.
|
|
137
|
+
|
|
129
138
|
## Suggestion-only guarantee
|
|
130
139
|
|
|
131
140
|
| Allowed | Forbidden |
|
|
@@ -57,6 +57,19 @@ export declare function readSessionEffort(
|
|
|
57
57
|
base?: string
|
|
58
58
|
): EffortLevel | undefined
|
|
59
59
|
|
|
60
|
+
/** Check whether the optional trace environment switch is on. */
|
|
61
|
+
export declare function isEffortTraceEnabled(value: unknown): boolean
|
|
62
|
+
|
|
63
|
+
/** Compute the trace-file path inside the session temp directory. */
|
|
64
|
+
export declare function sessionEffortTracePath(sessionID?: string, base?: string): string
|
|
65
|
+
|
|
66
|
+
/** Append one content-free trace entry to the session trace file. */
|
|
67
|
+
export declare function appendEffortTrace(
|
|
68
|
+
sessionID: string | undefined,
|
|
69
|
+
entry: unknown,
|
|
70
|
+
base?: string
|
|
71
|
+
): boolean
|
|
72
|
+
|
|
60
73
|
/** Read an agent's explicit reasoning entry from the project routing config. */
|
|
61
74
|
export declare function readRoutingReasoning(
|
|
62
75
|
config: unknown,
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
* @module lib/reasoning-effort
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { lstatSync, readFileSync } from "node:fs"
|
|
22
|
+
import { appendFileSync, lstatSync, readFileSync } from "node:fs"
|
|
23
23
|
import { join } from "node:path"
|
|
24
24
|
|
|
25
|
-
import { sessionTmpDir } from "./tmp-hygiene.mjs"
|
|
25
|
+
import { ensureSessionTmpDir, sessionTmpDir } from "./tmp-hygiene.mjs"
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* The four levels a plan or skill may suggest.
|
|
@@ -198,6 +198,58 @@ export function readSessionEffort(sessionID, base) {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Check whether the optional trace environment switch is on.
|
|
203
|
+
*
|
|
204
|
+
* Tracing is opt-in and diagnostic: it is enabled only when the value is
|
|
205
|
+
* exactly `1` or `true`, compared case-insensitively. Every other value,
|
|
206
|
+
* including `undefined`, disables it.
|
|
207
|
+
*
|
|
208
|
+
* @param value - Raw `CODEOPS_EFFORT_TRACE` value
|
|
209
|
+
* @returns True only for `"1"` or `"true"` in any case
|
|
210
|
+
*/
|
|
211
|
+
export function isEffortTraceEnabled(value) {
|
|
212
|
+
if (typeof value !== "string") return false
|
|
213
|
+
const normalized = value.trim().toLowerCase()
|
|
214
|
+
return normalized === "1" || normalized === "true"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Compute the trace-file path inside the session temp directory.
|
|
219
|
+
*
|
|
220
|
+
* @param sessionID - Session identifier
|
|
221
|
+
* @param base - Base temp directory (injectable for tests)
|
|
222
|
+
* @returns Absolute path to `reasoning-effort-trace.jsonl`
|
|
223
|
+
*/
|
|
224
|
+
export function sessionEffortTracePath(sessionID, base) {
|
|
225
|
+
return join(sessionTmpDir(sessionID, base), "reasoning-effort-trace.jsonl")
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Append one content-free trace entry to the session trace file.
|
|
230
|
+
*
|
|
231
|
+
* The entry is serialized as one compact JSON line. Nothing here inspects or
|
|
232
|
+
* interprets the entry; callers must never pass prompt text, file content, or
|
|
233
|
+
* secrets. The session directory is created when missing. Any failure — an
|
|
234
|
+
* unwritable path, an unserializable entry — returns false instead of
|
|
235
|
+
* throwing, because tracing must never affect a request.
|
|
236
|
+
*
|
|
237
|
+
* @param sessionID - Session identifier
|
|
238
|
+
* @param entry - Plain, content-free record to append
|
|
239
|
+
* @param base - Base temp directory (injectable for tests)
|
|
240
|
+
* @returns True when the line was appended, false otherwise
|
|
241
|
+
*/
|
|
242
|
+
export function appendEffortTrace(sessionID, entry, base) {
|
|
243
|
+
try {
|
|
244
|
+
const line = `${JSON.stringify(entry)}\n`
|
|
245
|
+
ensureSessionTmpDir(sessionID, base)
|
|
246
|
+
appendFileSync(sessionEffortTracePath(sessionID, base), line, "utf-8")
|
|
247
|
+
return true
|
|
248
|
+
} catch {
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
201
253
|
/**
|
|
202
254
|
* Read an agent's explicit reasoning entry from the project routing config.
|
|
203
255
|
*
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -10,8 +10,12 @@ import {
|
|
|
10
10
|
removeSessionTmpDir,
|
|
11
11
|
} from "../bin/lib/tmp-hygiene.mjs"
|
|
12
12
|
import {
|
|
13
|
+
appendEffortTrace,
|
|
13
14
|
applyEffort,
|
|
14
15
|
findEffortMarker,
|
|
16
|
+
isEffortLevel,
|
|
17
|
+
isEffortTraceEnabled,
|
|
18
|
+
isRoutingReasoning,
|
|
15
19
|
readRoutingReasoning,
|
|
16
20
|
readSessionEffort,
|
|
17
21
|
resolveEffort,
|
|
@@ -172,6 +176,20 @@ function readRoutingConfig(directory: string): unknown {
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Helper — append one content-free trace line when the optional
|
|
181
|
+
// CODEOPS_EFFORT_TRACE switch is on. Tracing is diagnostic only: it never
|
|
182
|
+
// affects a request and swallows its own failures.
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
function traceEffort(
|
|
185
|
+
enabled: boolean,
|
|
186
|
+
sessionID: string,
|
|
187
|
+
entry: Record<string, unknown>
|
|
188
|
+
): void {
|
|
189
|
+
if (!enabled) return
|
|
190
|
+
appendEffortTrace(sessionID, { ts: new Date().toISOString(), ...entry })
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
// ---------------------------------------------------------------------------
|
|
176
194
|
// CodeOps plugin for OpenCode
|
|
177
195
|
// Replaces: hooks/hooks.json + hook_session_context.sh + hook_marker_guard.sh
|
|
@@ -182,6 +200,7 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
182
200
|
// deduplication set for unsupported-level warnings.
|
|
183
201
|
const effortMarkers = new Map<string, { sessionID: string; level: EffortLevel }>()
|
|
184
202
|
const warnedEffortLevels = new Set<string>()
|
|
203
|
+
const effortTraceEnabled = isEffortTraceEnabled(process.env.CODEOPS_EFFORT_TRACE)
|
|
185
204
|
|
|
186
205
|
return {
|
|
187
206
|
// -----------------------------------------------------------------------
|
|
@@ -280,6 +299,11 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
280
299
|
const level = findEffortMarker(texts)
|
|
281
300
|
if (level !== undefined) {
|
|
282
301
|
effortMarkers.set(output.message.id, { sessionID: input.sessionID, level })
|
|
302
|
+
traceEffort(effortTraceEnabled, input.sessionID, {
|
|
303
|
+
event: "capture",
|
|
304
|
+
messageID: output.message.id,
|
|
305
|
+
level,
|
|
306
|
+
})
|
|
283
307
|
}
|
|
284
308
|
} catch {
|
|
285
309
|
await warnContentFree(client, "Could not scan a message for a reasoning-effort marker.")
|
|
@@ -298,10 +322,36 @@ export const CodeOpsPlugin: Plugin = async ({ client, directory }) => {
|
|
|
298
322
|
const session = readSessionEffort(input.sessionID)
|
|
299
323
|
const routing = readRoutingReasoning(readRoutingConfig(directory), input.agent)
|
|
300
324
|
const level = resolveEffort({ marker, session, routing })
|
|
301
|
-
|
|
325
|
+
const source = isEffortLevel(marker)
|
|
326
|
+
? "marker"
|
|
327
|
+
: isEffortLevel(session)
|
|
328
|
+
? "session"
|
|
329
|
+
: isRoutingReasoning(routing)
|
|
330
|
+
? "routing"
|
|
331
|
+
: "none"
|
|
332
|
+
if (level === undefined) {
|
|
333
|
+
traceEffort(effortTraceEnabled, input.sessionID, {
|
|
334
|
+
event: "apply",
|
|
335
|
+
messageID: input.message.id,
|
|
336
|
+
agent: input.agent,
|
|
337
|
+
level: null,
|
|
338
|
+
source,
|
|
339
|
+
applied: false,
|
|
340
|
+
})
|
|
341
|
+
return
|
|
342
|
+
}
|
|
302
343
|
|
|
303
344
|
const applied = applyEffort(output.options, level, input.model)
|
|
304
|
-
|
|
345
|
+
const changed = applied !== output.options
|
|
346
|
+
traceEffort(effortTraceEnabled, input.sessionID, {
|
|
347
|
+
event: "apply",
|
|
348
|
+
messageID: input.message.id,
|
|
349
|
+
agent: input.agent,
|
|
350
|
+
level,
|
|
351
|
+
source,
|
|
352
|
+
applied: changed,
|
|
353
|
+
})
|
|
354
|
+
if (!changed) {
|
|
305
355
|
if (marker !== undefined) {
|
|
306
356
|
const warningKey = `${input.sessionID}:${marker}`
|
|
307
357
|
if (!warnedEffortLevels.has(warningKey)) {
|
|
Binary file
|