@pinta-ai/pinta-opencode 0.10.0 → 0.11.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
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1
4
+
5
+ - Evaluate the original `tool.execute.after` payload before OpenCode delivers
6
+ supported builtin and raw MCP output. DENY uses fixed safe feedback, preserves
7
+ original masked execution evidence/IDs, and attaches the output target only
8
+ after evaluation. ALLOW, REVIEW and explicit fail-open behavior are unchanged.
9
+ - Return before/after DENY without collector IO. Retain telemetry in a bounded,
10
+ endpoint-aware memory queue using the existing UTF-8 POST limit; flush it
11
+ before the next idle-event POST, rather than waiting behind that POST's ACK.
12
+ The queue is not durable across process termination.
13
+ - Bundle published `@pinta-ai/core` 0.9.1, including its mysql-family-only short
14
+ password masking fix. The effective guard timeout remains 100ms.
15
+ - Isolate private-dependency installation from setup-node's npmjs auth binding.
16
+ The GitHub token is bound only to GitHub Packages; public publishing uses OIDC.
17
+ - Verify the actual OpenCode 1.18.31 CLI with a keyless loopback provider:
18
+ returned builtin success/nonzero exit and raw MCP success/structured output
19
+ are gated. MCP `isError: true` and other thrown executions skip the after hook
20
+ in that host and remain an explicit unsupported error-output boundary.
21
+ - Keep Manager 0.1.11 as the existing envelope floor, without implying it
22
+ supplies native after-phase projection and output-only policy evaluation.
23
+
24
+ Refs PTA-584.
25
+
26
+ ## 0.11.0
27
+
28
+ - Guard default timeout 50ms -> 100ms. At 50ms a manager answering in its
29
+ normal range still lost the race often enough to show up as timeout
30
+ fail-opens. The plugin awaits the verdict in-process before the tool runs
31
+ (`tool.execute.before`), so a tool call can now stall on the guard for up to
32
+ 100ms instead of 50ms. `PINTA_OPENCODE_GUARD_TIMEOUT_MS` / `guardTimeoutMs`
33
+ still override it.
34
+ - `@pinta-ai/core` `^0.8.0` -> `^0.9.0`: the effective timeout (default or
35
+ override) is declared to the manager as `x-pinta-guard-budget-ms`. Manager
36
+ 0.1.10+ plans its own work around it instead of the 50ms it had copied for
37
+ this adaptor, so the backend package check gets 80ms rather than 40ms. Older
38
+ managers ignore the header.
39
+ - README no longer says the guard never blocks tool execution; it does, for up
40
+ to the timeout.
41
+ - Pinta Manager 0.1.11 or later remains the guard-payload compatibility floor.
42
+
43
+ Refs PTA-579.
44
+
3
45
  ## 0.10.0
4
46
 
5
47
  - Emit exact scalar `opencode.model` with source/provider evidence from verified
package/README.md CHANGED
@@ -6,6 +6,10 @@ Unlike the Claude Code / Codex / Copilot adapters (which spawn a process per hoo
6
6
 
7
7
  > Status: spec complete and validated end-to-end against **opencode 1.15.3**. See [`SPEC.md`](./SPEC.md), [`PLAN.md`](./PLAN.md), and the empirical record in [`HYPOTHESIS_VALIDATION.md`](./HYPOTHESIS_VALIDATION.md) (§10–13).
8
8
 
9
+ Returned-output enforcement is additionally verified against **OpenCode 1.18.31**
10
+ using its actual CLI and a keyless loopback provider. The native MCP-error
11
+ limitation below is explicitly excluded from that enforcement claim.
12
+
9
13
  ## How it hooks in
10
14
 
11
15
  opencode fires plugin hooks around every built-in **and** MCP tool. This adapter uses only non-experimental hooks:
@@ -16,9 +20,9 @@ opencode fires plugin hooks around every built-in **and** MCP tool. This adapter
16
20
  | `chat.params` | remember exact-message request model evidence (no extra span) | `{ sessionID, agent, model, message }` |
17
21
  | `event` | lifecycle span (Bronze flatten) + flush on `session.idle` | `{ event: { id, type, properties } }`, every event carries `properties.sessionID` |
18
22
  | `tool.execute.before` | query guard → **`throw` on DENY** + emit span | input `{ tool, sessionID, callID }`, output `{ args }` (full tool args, mutable) |
19
- | `tool.execute.after` | tool-result span (incl. exit code) | output `{ title, output, metadata{ output, exit, truncated, … } }` |
23
+ | `tool.execute.after` | query output guard before delivery + original tool-result span | builtin `{ title, output, metadata{ exit, truncated, … }, attachments? }`; raw MCP `{ content, structuredContent?, isError? }` |
20
24
 
21
- > The `permission.ask` plugin hook is **declared but never triggered** in opencode — do not depend on it. Gating is done in `tool.execute.before` only.
25
+ > Do not depend on the declared `permission.ask` plugin hook. Execution gating uses `tool.execute.before`; output delivery gating uses the awaited `tool.execute.after`.
22
26
 
23
27
  ## Install
24
28
 
@@ -55,9 +59,9 @@ PINTA_OPENCODE_GUARD=https://your-relay.example.com/guard
55
59
  |---|---|
56
60
  | `endpoint` / `PINTA_OPENCODE_ENDPOINT` | Full OTLP/HTTP traces URL. Falls back to `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` → `OTEL_EXPORTER_OTLP_ENDPOINT` (+`/v1/traces`). No endpoint → telemetry disabled. |
57
61
  | `headers` / `PINTA_OPENCODE_HEADERS` | `key=val,key=val` request headers (auth). Falls back to `OTEL_EXPORTER_OTLP_HEADERS`. |
58
- | `guard` / `PINTA_OPENCODE_GUARD` | Optional. POST'd on `tool.execute.before`; a `DENY` blocks the tool. No endpoint → governance disabled. |
62
+ | `guard` / `PINTA_OPENCODE_GUARD` | Optional. POST'd on `tool.execute.before` and `tool.execute.after`; DENY blocks execution or withholds returned output, respectively. No endpoint → governance disabled. |
59
63
  | `token` / `PINTA_OPENCODE_TOKEN` | Sent as `x-pinta-relay-token` on guard + OTLP. |
60
- | `PINTA_OPENCODE_GUARD_TIMEOUT_MS` | Guard client timeout (default `50`; `300` recommended in production for cold-start). |
64
+ | `PINTA_OPENCODE_GUARD_TIMEOUT_MS` | Guard client timeout (default `100`). Declared to the Pinta Manager as its budget, so raising it also lets the manager wait longer. |
61
65
  | `PINTA_OPENCODE_GUARD_DISABLED=1` | Force-disable the guard. |
62
66
 
63
67
  Telemetry and governance are independent — endpoint only, guard only, both, or neither all work.
@@ -75,9 +79,47 @@ body: the OTLP payload itself — { "resourceSpans": [ … one span, opencode.*
75
79
 
76
80
  The verdict is then attached to that same span (`pinta.guard.*`) before it is sent, so the span the guard judged is the span the collector stores.
77
81
 
78
- A `DENY` becomes `throw new Error(userMessage ?? reason ?? "guard_deny")`. Verified effect: **only that tool is blocked** (`tool.execute.after` does not fire), the reason shows as `✗ … failed` + `Error: <reason>` to the model/TUI, and the session stays alive. `ALLOW`/`REVIEW` pass through.
79
-
80
- Guard is **fail-open** (no endpoint / `PINTA_GUARD_DISABLED=1` / non-200 / timeout / error → allow), so it never breaks a session. The 50ms inline call does not block tool execution.
82
+ A before-hook `DENY` becomes `throw new Error(userMessage ?? reason ?? "guard_deny")`. Verified effect: **only that tool is blocked** (`tool.execute.after` does not fire), the reason shows as `✗ … failed` + `Error: <reason>` to the model/TUI, and the session stays alive. `ALLOW`/`REVIEW` pass through.
83
+
84
+ Guard is **fail-open** (no endpoint / `PINTA_OPENCODE_GUARD_DISABLED=1` / non-200 / timeout / error → allow), so infrastructure failures do not break a session. Each guard call is awaited for at most the configured timeout (100ms by default).
85
+
86
+ ### Returned-output enforcement
87
+
88
+ The awaited after hook submits the original redaction-aware `opencode.tool.after`
89
+ span before OpenCode returns the result to the model. Builtin output retains its
90
+ text, exit code and truncation evidence; MCP results retain the full raw result,
91
+ including `content`, `structuredContent` and any `isError` field. The original
92
+ invocation arguments, when provided by the host, remain audit evidence, not new
93
+ operations to evaluate. The guard must recognize
94
+ `opencode.hook=tool.execute.after` and apply returned-content policies without
95
+ rejudging completed input or package operations. The existing Manager envelope
96
+ floor does not establish that output capability.
97
+
98
+ After evaluation, every non-null verdict carries `pinta.guard.target=tool_output`
99
+ on the same span and IDs. This marker is audit metadata, not an input needed to
100
+ activate output policies. DENY throws the fixed safe message
101
+ `Pinta withheld this tool output because it violated an active policy.`
102
+ It never reflects guard-supplied text back to the model. OpenCode surfaces a tool
103
+ error and can continue its session; the adapter does not undo side effects or
104
+ claim that a successfully executed tool failed. The original masked output and
105
+ native exit/`isError` evidence are retained independently of delivery denial.
106
+
107
+ Neither before nor after DENY waits for collector IO. With telemetry configured,
108
+ the original annotated payload is retained in a bounded in-memory queue and
109
+ drained on a later `session.idle` flush before the idle-event POST. The shared 900 KiB UTF-8 POST limit still
110
+ applies; oversized payloads and retention errors are diagnosed without delaying
111
+ or suppressing denial. No telemetry endpoint means no retention. Like the
112
+ existing retry buffer, this queue is not durable across process termination.
113
+
114
+ The host must dispatch the after callback: a tool that throws before returning
115
+ skips it. In OpenCode 1.18.31, **MCP `isError: true` is converted to an exception
116
+ before the after hook**, so this adapter cannot withhold that native error path.
117
+ A directly invoked after-hook test with `isError` is not native error coverage.
118
+ The pinned [MCP conversion](https://github.com/anomalyco/opencode/blob/v1.18.31/packages/opencode/src/mcp/catalog.ts)
119
+ and [tool runner](https://github.com/anomalyco/opencode/blob/v1.18.31/packages/opencode/src/session/tools.ts)
120
+ define this boundary. Returned builtin results, including nonzero shell exit
121
+ codes, and successful raw MCP results use the awaited after hook; undispatched
122
+ host paths are not treated as protected.
81
123
 
82
124
  ## Span conventions
83
125
 
@@ -157,7 +199,7 @@ src/
157
199
  │ ├── trace.ts # ULID trace, keyed by sessionID, rotated on chat.message
158
200
  │ ├── transport.ts # POST OTLP/HTTP traces (5s), in-memory retry queue
159
201
  │ ├── retry-queue.ts # batched flush on next event
160
- │ ├── guard.ts # POST PINTA_GUARD_ENDPOINT (50ms), fail-open
202
+ │ ├── guard.ts # POST PINTA_GUARD_ENDPOINT (100ms), fail-open
161
203
  │ └── redact.ts # Tier-1 redaction + Tier-3 truncation
162
204
  ```
163
205
 
package/dist/index.mjs CHANGED
@@ -31,6 +31,370 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
+ // node_modules/@pinta-ai/core/dist/mysql-password.js
35
+ var require_mysql_password = __commonJS({
36
+ "node_modules/@pinta-ai/core/dist/mysql-password.js"(exports) {
37
+ "use strict";
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.mysqlPasswordRanges = mysqlPasswordRanges;
40
+ var EXECUTABLES = /* @__PURE__ */ new Set([
41
+ "mysql",
42
+ "mysqldump",
43
+ "mysqladmin",
44
+ "mysqlcheck",
45
+ "mysqlimport",
46
+ "mysqlshow",
47
+ "mariadb",
48
+ "mariadb-dump",
49
+ "mariadb-admin",
50
+ "mariadb-check",
51
+ "mariadb-import",
52
+ "mariadb-show"
53
+ ]);
54
+ var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "dash", "zsh", "ksh"]);
55
+ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
56
+ "-u",
57
+ "--user",
58
+ "-h",
59
+ "--host",
60
+ "-P",
61
+ "--port",
62
+ "-D",
63
+ "--database",
64
+ "-S",
65
+ "--socket",
66
+ "-e",
67
+ "--execute",
68
+ "--protocol",
69
+ "--default-character-set",
70
+ "--defaults-file",
71
+ "--defaults-extra-file",
72
+ "--login-path",
73
+ "--init-command",
74
+ "--ssl-ca",
75
+ "--ssl-capath",
76
+ "--ssl-cert",
77
+ "--ssl-key",
78
+ "--ssl-cipher"
79
+ ]);
80
+ var MAX_NESTING = 16;
81
+ function slice(text, start, end = text.value.length) {
82
+ return { value: text.value.slice(start, end), ranges: text.ranges.slice(start, end) };
83
+ }
84
+ function append(target, text, start, end) {
85
+ target.value += text.value.slice(start, end);
86
+ for (let index = start; index < end; index++)
87
+ target.ranges.push(text.ranges[index]);
88
+ }
89
+ function executable(word) {
90
+ if (!word?.literal)
91
+ return void 0;
92
+ return word.value.slice(word.value.lastIndexOf("/") + 1).replace(/\.exe$/, "");
93
+ }
94
+ function executionIndex(words) {
95
+ let index = 0;
96
+ for (let wrappers = 0; wrappers <= MAX_NESTING; wrappers++) {
97
+ while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[index]?.value ?? ""))
98
+ index++;
99
+ const name = executable(words[index]);
100
+ if (!name)
101
+ return void 0;
102
+ if (["!", "if", "then", "elif", "else", "while", "until", "do"].includes(name)) {
103
+ index++;
104
+ continue;
105
+ }
106
+ const optionValues = {
107
+ env: ["-u", "--unset", "-C", "--chdir"],
108
+ sudo: [
109
+ "-u",
110
+ "--user",
111
+ "-g",
112
+ "--group",
113
+ "-p",
114
+ "--prompt",
115
+ "-D",
116
+ "--chdir",
117
+ "-R",
118
+ "--chroot",
119
+ "-C",
120
+ "--close-from",
121
+ "-T",
122
+ "--command-timeout"
123
+ ],
124
+ exec: ["-a"],
125
+ nice: ["-n", "--adjustment"],
126
+ timeout: ["-s", "--signal", "-k", "--kill-after"]
127
+ };
128
+ const flags = {
129
+ env: ["-i", "--ignore-environment", "-0", "--null"],
130
+ sudo: ["-n", "-E", "-H", "-S", "-b", "-k"],
131
+ command: ["-p"],
132
+ exec: ["-c", "-l"],
133
+ nohup: [],
134
+ nice: [],
135
+ time: ["-p"],
136
+ timeout: ["--foreground", "--preserve-status"]
137
+ };
138
+ if (!(name in flags))
139
+ return index;
140
+ index++;
141
+ while (words[index]?.value.startsWith("-")) {
142
+ const option = words[index++].value;
143
+ if (option === "--")
144
+ break;
145
+ if (flags[name].includes(option))
146
+ continue;
147
+ if ((optionValues[name] ?? []).includes(option)) {
148
+ if (!words[index++])
149
+ return void 0;
150
+ continue;
151
+ }
152
+ if ((optionValues[name] ?? []).some((key2) => key2.startsWith("--") ? option.startsWith(key2 + "=") : option.startsWith(key2) && option.length > 2))
153
+ continue;
154
+ if (name === "nice" && /^-\d+$/.test(option))
155
+ continue;
156
+ return void 0;
157
+ }
158
+ if (name === "timeout") {
159
+ if (!/^\d+(?:\.\d+)?[smhd]?$/.test(words[index++]?.value ?? ""))
160
+ return void 0;
161
+ }
162
+ }
163
+ return void 0;
164
+ }
165
+ function substitutionEnd(text, start) {
166
+ let nesting = 1;
167
+ let quote = "";
168
+ for (let index = start; index < text.length; index++) {
169
+ const char = text[index];
170
+ if (char === "\\" && quote !== "'") {
171
+ index++;
172
+ continue;
173
+ }
174
+ if (quote) {
175
+ if (char === quote)
176
+ quote = "";
177
+ continue;
178
+ }
179
+ if (char === "'" || char === '"')
180
+ quote = char;
181
+ else if (char === "(")
182
+ nesting++;
183
+ else if (char === ")" && --nesting === 0)
184
+ return index;
185
+ }
186
+ return text.length;
187
+ }
188
+ function readWord(text, start, out, depth) {
189
+ const word = { value: "", ranges: [], start, end: start, literal: true };
190
+ let quote = "";
191
+ let index = start;
192
+ while (index < text.value.length) {
193
+ const char = text.value[index];
194
+ if (!quote && /[\s;|&()<>{}]/.test(char))
195
+ break;
196
+ if (char === quote) {
197
+ quote = "";
198
+ index++;
199
+ continue;
200
+ }
201
+ if (!quote && (char === "'" || char === '"')) {
202
+ quote = char;
203
+ index++;
204
+ continue;
205
+ }
206
+ if (char === "\\" && quote !== "'") {
207
+ const next = text.value[index + 1];
208
+ if (next === "\n") {
209
+ index += 2;
210
+ continue;
211
+ }
212
+ if (next && (!quote || /[$`"\\]/.test(next))) {
213
+ append(word, text, index + 1, index + 2);
214
+ index += 2;
215
+ continue;
216
+ }
217
+ }
218
+ if (quote !== "'" && text.value.startsWith("$(", index)) {
219
+ const end = substitutionEnd(text.value, index + 2);
220
+ if (text.value[index + 2] !== "(" && depth < MAX_NESTING) {
221
+ scanShell(slice(text, index + 2, end), out, depth + 1);
222
+ }
223
+ append(word, text, index, Math.min(end + 1, text.value.length));
224
+ word.literal = false;
225
+ index = Math.min(end + 1, text.value.length);
226
+ continue;
227
+ }
228
+ if (char === "`" && quote !== "'") {
229
+ let end = index + 1;
230
+ while (end < text.value.length && text.value[end] !== "`") {
231
+ end += text.value[end] === "\\" ? 2 : 1;
232
+ }
233
+ if (depth < MAX_NESTING)
234
+ scanShell(slice(text, index + 1, end), out, depth + 1);
235
+ append(word, text, index, Math.min(end + 1, text.value.length));
236
+ word.literal = false;
237
+ index = Math.min(end + 1, text.value.length);
238
+ continue;
239
+ }
240
+ if (char === "$" && quote !== "'")
241
+ word.literal = false;
242
+ append(word, text, index, index + 1);
243
+ index++;
244
+ }
245
+ word.end = index;
246
+ return word;
247
+ }
248
+ function scanCommand(text, words, out, depth) {
249
+ const index = executionIndex(words);
250
+ if (index === void 0)
251
+ return;
252
+ const name = executable(words[index]);
253
+ if (SHELLS.has(name) && depth < MAX_NESTING) {
254
+ for (let option = index + 1; option < words.length; option++) {
255
+ const value = words[option].value;
256
+ if (value === "--command" || /^-[a-zA-Z]*c[a-zA-Z]*$/.test(value)) {
257
+ const script = words[option + 1];
258
+ if (script)
259
+ scanShell(script, out, depth + 1);
260
+ return;
261
+ }
262
+ if (!value.startsWith("-") || value === "--")
263
+ return;
264
+ if (value === "-o" || value === "-O")
265
+ option++;
266
+ }
267
+ return;
268
+ }
269
+ if (!EXECUTABLES.has(name))
270
+ return;
271
+ for (let option = index + 1; option < words.length; option++) {
272
+ const word = words[option];
273
+ if (word.value === "--")
274
+ return;
275
+ if (VALUE_OPTIONS.has(word.value)) {
276
+ option++;
277
+ continue;
278
+ }
279
+ if (word.value.startsWith("-p") && word.value.length > 2) {
280
+ out.push({ start: text.ranges[word.start].start, end: text.ranges[word.end - 1].end });
281
+ }
282
+ }
283
+ }
284
+ function scanShell(text, out, depth = 0) {
285
+ let words = [];
286
+ let redirect;
287
+ const documents = [];
288
+ let index = 0;
289
+ const flush = () => {
290
+ scanCommand(text, words, out, depth);
291
+ words = [];
292
+ redirect = void 0;
293
+ };
294
+ while (index < text.value.length) {
295
+ const char = text.value[index];
296
+ if (char === "\n") {
297
+ flush();
298
+ index++;
299
+ for (const document of documents.splice(0)) {
300
+ while (index < text.value.length) {
301
+ const end = text.value.indexOf("\n", index);
302
+ const stop = end < 0 ? text.value.length : end;
303
+ const line = text.value.slice(index, stop);
304
+ index = end < 0 ? stop : stop + 1;
305
+ if ((document.stripTabs ? line.replace(/^\t+/, "") : line) === document.delimiter)
306
+ break;
307
+ }
308
+ }
309
+ } else if (/\s/.test(char)) {
310
+ index++;
311
+ } else if (char === "#") {
312
+ const end = text.value.indexOf("\n", index);
313
+ index = end < 0 ? text.value.length : end;
314
+ } else if (text.value.startsWith("((", index)) {
315
+ flush();
316
+ index = Math.min(substitutionEnd(text.value, index + 1) + 1, text.value.length);
317
+ } else if (/[;|&(){}]/.test(char)) {
318
+ flush();
319
+ index++;
320
+ } else if (char === "<" || char === ">") {
321
+ if (words.at(-1)?.end === index && /^\d+$/.test(words.at(-1).value))
322
+ words.pop();
323
+ const match = /^(?:<<<|<<-?|>>|<&|>&|[<>])/.exec(text.value.slice(index));
324
+ redirect = match?.[0] ?? char;
325
+ index += redirect.length;
326
+ } else {
327
+ const word = readWord(text, index, out, depth);
328
+ index = word.end;
329
+ if (redirect === "<<" || redirect === "<<-") {
330
+ documents.push({ delimiter: word.value, stripTabs: redirect === "<<-" });
331
+ }
332
+ if (!redirect)
333
+ words.push(word);
334
+ redirect = void 0;
335
+ }
336
+ }
337
+ flush();
338
+ }
339
+ function jsonString(text, start, end) {
340
+ const result = { value: "", ranges: [] };
341
+ for (let index = start + 1; index < end - 1; index++) {
342
+ if (text.value[index] !== "\\") {
343
+ append(result, text, index, index + 1);
344
+ continue;
345
+ }
346
+ const stop = index + (text.value[index + 1] === "u" ? 6 : 2);
347
+ const decoded = JSON.parse(`"${text.value.slice(index, stop)}"`);
348
+ result.value += decoded;
349
+ for (let unit = 0; unit < decoded.length; unit++) {
350
+ result.ranges.push({ start: text.ranges[index].start, end: text.ranges[stop - 1].end });
351
+ }
352
+ index = stop - 1;
353
+ }
354
+ return result;
355
+ }
356
+ function mysqlPasswordRanges(input) {
357
+ const text = {
358
+ value: input,
359
+ ranges: Array.from({ length: input.length }, (_, index) => ({ start: index, end: index + 1 }))
360
+ };
361
+ const out = [];
362
+ if (/^\s*[{[]/.test(input)) {
363
+ let parsed;
364
+ try {
365
+ parsed = JSON.parse(input);
366
+ } catch {
367
+ scanShell(text, out);
368
+ return out;
369
+ }
370
+ if (parsed !== null && typeof parsed === "object") {
371
+ const strings = /"(?:[^"\\]|\\.)*"/g;
372
+ let match;
373
+ while (match = strings.exec(input)) {
374
+ const key2 = JSON.parse(match[0]);
375
+ if (key2 !== "command" && key2 !== "cmd")
376
+ continue;
377
+ const separator = /^\s*:\s*/.exec(input.slice(strings.lastIndex));
378
+ if (!separator)
379
+ continue;
380
+ const start = strings.lastIndex + separator[0].length;
381
+ if (input[start] !== '"')
382
+ continue;
383
+ strings.lastIndex = start;
384
+ const value = strings.exec(input);
385
+ if (value?.index === start) {
386
+ scanShell(jsonString(text, start, start + value[0].length), out);
387
+ }
388
+ }
389
+ }
390
+ return out;
391
+ }
392
+ scanShell(text, out);
393
+ return out;
394
+ }
395
+ }
396
+ });
397
+
34
398
  // node_modules/@pinta-ai/core/dist/redact.js
35
399
  var require_redact = __commonJS({
36
400
  "node_modules/@pinta-ai/core/dist/redact.js"(exports) {
@@ -43,6 +407,7 @@ var require_redact = __commonJS({
43
407
  exports.resolveOverlaps = resolveOverlaps;
44
408
  exports.applyMatches = applyMatches;
45
409
  exports.redact = redact;
410
+ var mysql_password_js_1 = require_mysql_password();
46
411
  exports.MAX_BYTES = 102400;
47
412
  function truncateTo(input, maxBytes) {
48
413
  const buf = Buffer.from(input, "utf-8");
@@ -96,7 +461,7 @@ var require_redact = __commonJS({
96
461
  },
97
462
  {
98
463
  type: "cli_password_short",
99
- // mysql -p<pass>; only on bash context.
464
+ // Option shape only. collectMatches also requires a literal mysql-family executable.
100
465
  regex: /\s-p([^\s'"]+)/g,
101
466
  captureGroup: 1,
102
467
  requireContext: "bash"
@@ -114,6 +479,19 @@ var require_redact = __commonJS({
114
479
  for (const pattern of exports.PATTERNS) {
115
480
  if (pattern.requireContext && pattern.requireContext !== opts.context)
116
481
  continue;
482
+ if (pattern.type === "cli_password_short") {
483
+ for (const { start, end } of (0, mysql_password_js_1.mysqlPasswordRanges)(input)) {
484
+ out.push({
485
+ start,
486
+ end,
487
+ replaceStart: start,
488
+ replaceEnd: end,
489
+ type: pattern.type,
490
+ replacement: `-p[REDACTED:${pattern.type}]`
491
+ });
492
+ }
493
+ continue;
494
+ }
117
495
  const re = new RegExp(pattern.regex.source, pattern.regex.flags);
118
496
  let m;
119
497
  while ((m = re.exec(input)) !== null) {
@@ -155,7 +533,7 @@ var require_redact = __commonJS({
155
533
  const sorted = [...matches].sort((a, b) => b.replaceStart - a.replaceStart);
156
534
  let out = input;
157
535
  for (const m of sorted) {
158
- out = out.slice(0, m.replaceStart) + `[REDACTED:${m.type}]` + out.slice(m.replaceEnd);
536
+ out = out.slice(0, m.replaceStart) + (m.replacement ?? `[REDACTED:${m.type}]`) + out.slice(m.replaceEnd);
159
537
  }
160
538
  return out;
161
539
  }
@@ -356,10 +734,11 @@ var require_guard = __commonJS({
356
734
  "node_modules/@pinta-ai/core/dist/guard.js"(exports) {
357
735
  "use strict";
358
736
  Object.defineProperty(exports, "__esModule", { value: true });
359
- exports.GUARD_STATUS_REFUSED = void 0;
737
+ exports.GUARD_STATUS_REFUSED = exports.GUARD_BUDGET_HEADER = void 0;
360
738
  exports.evaluateGuard = evaluateGuard2;
361
739
  var DEFAULT_TIMEOUT_MS2 = 1e4;
362
740
  var DEFAULT_UA = "pinta-core";
741
+ exports.GUARD_BUDGET_HEADER = "x-pinta-guard-budget-ms";
363
742
  function failOpen2(clientRttMs, failOpenReason) {
364
743
  return {
365
744
  decision: "ALLOW",
@@ -391,7 +770,10 @@ var require_guard = __commonJS({
391
770
  // Spread rather than defaulted: an empty string is not "unknown" to the
392
771
  // manager, it is a claim that fails validation, and sending one would
393
772
  // make every adapter that never sets this look like it tried.
394
- ...opts.agentType ? { "x-pinta-agent-type": opts.agentType } : {}
773
+ ...opts.agentType ? { "x-pinta-agent-type": opts.agentType } : {},
774
+ // Same rule: the manager discards a non-positive or non-numeric budget
775
+ // and falls back to its table, so such a value is not sent at all.
776
+ ...Number.isFinite(timeoutMs) && timeoutMs > 0 ? { [exports.GUARD_BUDGET_HEADER]: String(timeoutMs) } : {}
395
777
  },
396
778
  body: JSON.stringify(payload),
397
779
  signal: controller.signal
@@ -577,7 +959,7 @@ var require_retry_queue = __commonJS({
577
959
  }
578
960
  };
579
961
  exports.DiskRetryQueue = DiskRetryQueue;
580
- var MemoryRetryQueue = class {
962
+ var MemoryRetryQueue2 = class {
581
963
  entries = [];
582
964
  enqueue(payload) {
583
965
  this.entries.push(payload);
@@ -595,7 +977,7 @@ var require_retry_queue = __commonJS({
595
977
  return this.entries.length;
596
978
  }
597
979
  };
598
- exports.MemoryRetryQueue = MemoryRetryQueue;
980
+ exports.MemoryRetryQueue = MemoryRetryQueue2;
599
981
  }
600
982
  });
601
983
 
@@ -1026,7 +1408,7 @@ var require_dist = __commonJS({
1026
1408
  "node_modules/@pinta-ai/core/dist/index.js"(exports) {
1027
1409
  "use strict";
1028
1410
  Object.defineProperty(exports, "__esModule", { value: true });
1029
- exports.loadEnvFile = exports.parseEnvFile = exports.envFilePath = exports.MemorySessionTraceManager = exports.DiskSessionTraceManager = exports.generateUlid = exports.TraceManager = exports.MAX_POST_BYTES = exports.parseHeadersEnv = exports.envOptionsResolver = exports.postOtlp = exports.MemoryTransport = exports.DiskTransport = exports.MemoryRetryQueue = exports.DiskRetryQueue = exports.GUARD_STATUS_REFUSED = exports.evaluateGuard = exports.MAX_SPAN_BYTES = exports.enforceSpanBudget = exports.mergeBatch = exports.attachGuard = exports.buildPayload = exports.clientAttrs = exports.guardAttrs = exports.attrsFromRecord = exports.toOtlpValue = exports.snakeCase = exports.newSpanId = exports.ulidToTraceId = exports.MAX_BYTES = exports.PATTERNS = exports.applyMatches = exports.resolveOverlaps = exports.collectMatches = exports.truncateTo = exports.truncate = exports.redact = void 0;
1411
+ exports.loadEnvFile = exports.parseEnvFile = exports.envFilePath = exports.MemorySessionTraceManager = exports.DiskSessionTraceManager = exports.generateUlid = exports.TraceManager = exports.MAX_POST_BYTES = exports.parseHeadersEnv = exports.envOptionsResolver = exports.postOtlp = exports.MemoryTransport = exports.DiskTransport = exports.MemoryRetryQueue = exports.DiskRetryQueue = exports.GUARD_BUDGET_HEADER = exports.GUARD_STATUS_REFUSED = exports.evaluateGuard = exports.MAX_SPAN_BYTES = exports.enforceSpanBudget = exports.mergeBatch = exports.attachGuard = exports.buildPayload = exports.clientAttrs = exports.guardAttrs = exports.attrsFromRecord = exports.toOtlpValue = exports.snakeCase = exports.newSpanId = exports.ulidToTraceId = exports.MAX_BYTES = exports.PATTERNS = exports.applyMatches = exports.resolveOverlaps = exports.collectMatches = exports.truncateTo = exports.truncate = exports.redact = void 0;
1030
1412
  var redact_js_1 = require_redact();
1031
1413
  Object.defineProperty(exports, "redact", { enumerable: true, get: function() {
1032
1414
  return redact_js_1.redact;
@@ -1096,6 +1478,9 @@ var require_dist = __commonJS({
1096
1478
  Object.defineProperty(exports, "GUARD_STATUS_REFUSED", { enumerable: true, get: function() {
1097
1479
  return guard_js_1.GUARD_STATUS_REFUSED;
1098
1480
  } });
1481
+ Object.defineProperty(exports, "GUARD_BUDGET_HEADER", { enumerable: true, get: function() {
1482
+ return guard_js_1.GUARD_BUDGET_HEADER;
1483
+ } });
1099
1484
  var retry_queue_js_1 = require_retry_queue();
1100
1485
  Object.defineProperty(exports, "DiskRetryQueue", { enumerable: true, get: function() {
1101
1486
  return retry_queue_js_1.DiskRetryQueue;
@@ -1209,7 +1594,7 @@ function resolveConfig(options = {}) {
1209
1594
  if (relayToken && !Object.keys(headers).some((k) => k.toLowerCase() === "x-pinta-relay-token")) {
1210
1595
  headers["x-pinta-relay-token"] = relayToken;
1211
1596
  }
1212
- const guardTimeoutMs = options.guardTimeoutMs ?? (Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS) || 50);
1597
+ const guardTimeoutMs = options.guardTimeoutMs ?? (Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS) || 100);
1213
1598
  return {
1214
1599
  endpoint: resolveEndpoint(options),
1215
1600
  headers,
@@ -1229,6 +1614,23 @@ var Transport = class extends import_core3.MemoryTransport {
1229
1614
  logPrefix: "pinta-opencode",
1230
1615
  resolveOptions: () => config.endpoint ? { endpoint: config.endpoint, headers: config.headers } : null
1231
1616
  });
1617
+ this.config = config;
1618
+ }
1619
+ deferred = new import_core3.MemoryRetryQueue();
1620
+ /** A decided DENY must return to the host without collector IO. */
1621
+ defer(payload) {
1622
+ if (!this.config.endpoint) return;
1623
+ const bytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
1624
+ if (bytes > import_core3.MAX_POST_BYTES) {
1625
+ process.stderr.write(`[pinta-opencode] deferred payload exceeds POST budget (${bytes} bytes); dropped
1626
+ `);
1627
+ return;
1628
+ }
1629
+ this.deferred.enqueue(payload);
1630
+ }
1631
+ async flush() {
1632
+ await super.flush();
1633
+ for (const payload of this.deferred.drain()) await super.send(payload);
1232
1634
  }
1233
1635
  };
1234
1636
 
@@ -1242,10 +1644,10 @@ var import_core8 = __toESM(require_dist(), 1);
1242
1644
  var import_core5 = __toESM(require_dist(), 1);
1243
1645
 
1244
1646
  // src/core/version.ts
1245
- var ADAPTER_VERSION = "0.10.0";
1647
+ var ADAPTER_VERSION = "0.11.1";
1246
1648
 
1247
1649
  // src/core/guard.ts
1248
- var DEFAULT_TIMEOUT_MS = 50;
1650
+ var DEFAULT_TIMEOUT_MS = 100;
1249
1651
  var GUARD_UA = `pinta-opencode/${ADAPTER_VERSION}`;
1250
1652
  function evaluateGuard(payload, endpoint, opts = {}) {
1251
1653
  return (0, import_core5.evaluateGuard)(payload, endpoint, {
@@ -1609,6 +2011,7 @@ var Telemetry = class {
1609
2011
  const props = record(ev.properties) ?? {};
1610
2012
  const sessionId = eventSessionID(ev.type, props);
1611
2013
  const model = this.models.event(ev.type, props);
2014
+ if (ev.type === "session.idle") await this.transport.flush();
1612
2015
  await this.emit(`opencode.event.${ev.type ?? "unknown"}`, sessionId, modelFields({
1613
2016
  hook: "event",
1614
2017
  event_type: ev.type,
@@ -1616,7 +2019,6 @@ var Telemetry = class {
1616
2019
  cwd: process.cwd(),
1617
2020
  ...props
1618
2021
  }, model));
1619
- if (ev.type === "session.idle") await this.transport.flush();
1620
2022
  }
1621
2023
  /**
1622
2024
  * Tool span from `tool.execute.before`, before the guard has been asked. The
@@ -1638,17 +2040,23 @@ var Telemetry = class {
1638
2040
  async toolBefore(input, args, guard) {
1639
2041
  await this.send((0, import_core7.attachGuard)(this.toolBeforePayload(input, args), guard));
1640
2042
  }
1641
- /** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
1642
- async toolAfter(input, output) {
2043
+ /** Raw MCP results arrive before OpenCode converts their content to output. */
2044
+ toolAfterPayload(input, output) {
1643
2045
  const meta = output.metadata ?? {};
1644
- await this.emit("opencode.tool.after", input.sessionID, modelFields({
2046
+ return this.build("opencode.tool.after", input.sessionID, modelFields({
1645
2047
  ...toolIdentity("tool.execute.after", input),
2048
+ tool_input: input.args,
1646
2049
  title: output.title,
1647
- tool_response: output.output,
2050
+ tool_response: output.content !== void 0 ? output : output.output,
2051
+ attachments: output.attachments,
1648
2052
  exit: meta.exit,
1649
2053
  truncated: meta.truncated
1650
2054
  }, this.models.afterTool(input)));
1651
2055
  }
2056
+ /** Tool result span from `tool.execute.after`, incl. exit code / truncation. */
2057
+ async toolAfter(input, output) {
2058
+ await this.send(this.toolAfterPayload(input, output));
2059
+ }
1652
2060
  };
1653
2061
  function toolIdentity(hook, input) {
1654
2062
  return {
@@ -1670,6 +2078,7 @@ function warn(scope, err) {
1670
2078
  process.stderr.write(`[pinta-opencode] ${scope}: ${err?.message ?? String(err)}
1671
2079
  `);
1672
2080
  }
2081
+ var OUTPUT_DENIED_MESSAGE = "Pinta withheld this tool output because it violated an active policy.";
1673
2082
  function failOpen(scope, fn) {
1674
2083
  return async (...args) => {
1675
2084
  try {
@@ -1684,18 +2093,26 @@ var PintaOpencode = async (_input, options) => {
1684
2093
  const transport = new Transport({ endpoint: config.endpoint, headers: config.headers });
1685
2094
  const trace = new import_core4.MemorySessionTraceManager();
1686
2095
  const telemetry = new Telemetry(transport, trace, config);
1687
- async function guardAndTrace(input, args) {
2096
+ async function guardAndTrace(hook, build) {
1688
2097
  let guard = null;
1689
2098
  try {
1690
- const payload = telemetry.toolBeforePayload(input, args);
2099
+ const payload = build();
1691
2100
  guard = await evaluateGuard(
1692
2101
  payload,
1693
2102
  config.guardEndpoint,
1694
2103
  { timeoutMs: config.guardTimeoutMs, token: config.relayToken, disabled: config.guardDisabled }
1695
2104
  );
1696
- await telemetry.send((0, import_core8.attachGuard)(payload, guard));
2105
+ (0, import_core8.attachGuard)(payload, guard);
2106
+ if (hook === "tool.execute.after" && guard) {
2107
+ payload.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({
2108
+ key: "pinta.guard.target",
2109
+ value: { stringValue: "tool_output" }
2110
+ });
2111
+ }
2112
+ if (guard?.decision === "DENY") transport.defer(payload);
2113
+ else await telemetry.send(payload);
1697
2114
  } catch (err) {
1698
- warn("tool.execute.before", err);
2115
+ warn(hook, err);
1699
2116
  }
1700
2117
  return guard;
1701
2118
  }
@@ -1715,16 +2132,15 @@ var PintaOpencode = async (_input, options) => {
1715
2132
  // ★ governance gate: guard query → DENY throws (blocks just this tool).
1716
2133
  // Telemetry/guard-infra errors are fail-open; only a DENY decision escapes.
1717
2134
  "tool.execute.before": async (input, output) => {
1718
- const guard = await guardAndTrace(input, output?.args);
2135
+ const guard = await guardAndTrace("tool.execute.before", () => telemetry.toolBeforePayload(input, output?.args));
1719
2136
  if (guard?.decision === "DENY") {
1720
2137
  throw new Error(guard.userMessage ?? guard.reason ?? "guard_deny");
1721
2138
  }
1722
2139
  },
1723
- // tool-result telemetry.
1724
- "tool.execute.after": failOpen(
1725
- "tool.execute.after",
1726
- (input, output) => telemetry.toolAfter(input, output)
1727
- )
2140
+ "tool.execute.after": async (input, output) => {
2141
+ const guard = await guardAndTrace("tool.execute.after", () => telemetry.toolAfterPayload(input, output));
2142
+ if (guard?.decision === "DENY") throw new Error(OUTPUT_DENIED_MESSAGE);
2143
+ }
1728
2144
  };
1729
2145
  };
1730
2146
  var plugin_default = PintaOpencode;
package/dist/plugin.js CHANGED
@@ -1,21 +1,26 @@
1
1
  import { createRequire as __pintaCreateRequire } from 'module'; const require = __pintaCreateRequire(import.meta.url);
2
- var _t=Object.create;var Pe=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Ot=Object.getPrototypeOf,Pt=Object.prototype.hasOwnProperty;var y=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var P=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);var St=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Tt(e))!Pt.call(r,s)&&s!==t&&Pe(r,s,{get:()=>e[s],enumerable:!(n=vt(e,s))||n.enumerable});return r};var b=(r,e,t)=>(t=r!=null?_t(Ot(r)):{},St(e||!r||!r.__esModule?Pe(t,"default",{value:r,enumerable:!0}):t,r));var ee=P(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});h.PATTERNS=h.MAX_BYTES=void 0;h.truncateTo=Se;h.truncate=Et;h.collectMatches=Ee;h.resolveOverlaps=be;h.applyMatches=Ie;h.redact=bt;h.MAX_BYTES=102400;function Se(r,e){let t=Buffer.from(r,"utf-8");return t.length<=e?r:`${t.subarray(0,Math.max(0,e)).toString("utf-8")}\u2026[TRUNCATED:${t.length}]`}function Et(r){return Se(r,h.MAX_BYTES)}h.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function Ee(r,e){let t=[];for(let n of h.PATTERNS){if(n.requireContext&&n.requireContext!==e.context)continue;let s=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=s.exec(r))!==null;){let c=n.captureGroup??0,o=i[c];if(o===void 0){i.index===s.lastIndex&&s.lastIndex++;continue}let u=i.index,d=i.index+i[0].length,l=u+i[0].indexOf(o),O=l+o.length;t.push({start:u,end:d,replaceStart:l,replaceEnd:O,type:n.type}),i.index===s.lastIndex&&s.lastIndex++}}return t}function be(r){let e=[...r].sort((s,i)=>s.start!==i.start?s.start-i.start:i.end-i.start-(s.end-s.start)),t=[],n=-1;for(let s of e)s.start<n||(t.push(s),n=s.end);return t}function Ie(r,e){let t=[...e].sort((s,i)=>i.replaceStart-s.replaceStart),n=r;for(let s of t)n=n.slice(0,s.replaceStart)+`[REDACTED:${s.type}]`+n.slice(s.replaceEnd);return n}function bt(r,e={}){if(r.length===0)return r;let t=Ee(r,e);if(t.length===0)return r;let n=be(t);return Ie(r,n)}});var re=P(p=>{"use strict";var It=p&&p.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(p,"__esModule",{value:!0});p.MAX_SPAN_BYTES=void 0;p.ulidToTraceId=Ae;p.newSpanId=ke;p.snakeCase=wt;p.toOtlpValue=Re;p.attrsFromRecord=At;p.guardAttrs=xe;p.clientAttrs=je;p.enforceSpanBudget=Ne;p.buildPayload=kt;p.attachGuard=Ce;p.mergeBatch=Rt;var Dt=It(y("crypto")),te=ee(),Mt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ae(r){if(r.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${r.length}`);let e=0n;for(let n of r){let s=Mt.indexOf(n);if(s<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);e=e<<5n|BigInt(s)}let t=(1n<<128n)-1n;return e&=t,e.toString(16).padStart(32,"0")}function ke(){return Dt.default.randomBytes(8).toString("hex")}function wt(r){return r.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var De=new Set;function V(r,e,t){let n=(0,te.truncate)(e);if((t.skipRedactKeys??De).has(r))return n;let s=(t.bashContextKeys??De).has(r)?"bash":void 0;return(0,te.redact)(n,{context:s})}function Re(r,e,t={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:V(r,e,t)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:V(r,JSON.stringify(e),t)}}catch{return{stringValue:V(r,String(e),t)}}default:return{stringValue:V(r,String(e),t)}}}function At(r,e,t={}){let n=[];for(let[s,i]of Object.entries(r)){let c=`${e}.${s}`,o=Re(c,i,t);o!==null&&n.push({key:c,value:o})}return n}function xe(r){let e=[{key:"pinta.guard.decision",value:{stringValue:r.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:r.durationMs}}];return r.reason&&e.push({key:"pinta.guard.matched_rule",value:{stringValue:r.reason}}),r.failOpenReason&&e.push({key:"pinta.guard.fail_open_reason",value:{stringValue:r.failOpenReason}}),e}function je(r){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(r.rttMs)?{intValue:r.rttMs}:{doubleValue:r.rttMs}},{key:"pinta.client.op",value:{stringValue:r.op}}]}p.MAX_SPAN_BYTES=800*1024;function Me(r){let e=JSON.stringify(r);return e===void 0?0:Buffer.byteLength(e,"utf-8")}var we=1024;function Ne(r,e=p.MAX_SPAN_BYTES){let t=Me(r);for(let n=0;n<100&&t>e;n++){let s=null,i=0;for(let u of r.attributes){if(!("stringValue"in u.value))continue;let d=Buffer.byteLength(u.value.stringValue,"utf-8");d>i&&(i=d,s=u.value)}if(s===null||i<=we)break;let c=t-e,o=Math.max(we,i-c-64);s.stringValue=(0,te.truncateTo)(s.stringValue,o),t=Me(r)}return r}function kt(r){let e=r.now??Date.now(),t=(BigInt(e)*1000000n).toString(),n=[...r.attributes],s={traceId:Ae(r.traceId),spanId:ke(),name:r.spanName,kind:r.spanKind??1,startTimeUnixNano:t,endTimeUnixNano:t,attributes:n};Ne(s);let i={resourceSpans:[{resource:{attributes:r.resource},scopeSpans:[{scope:r.scope,spans:[s]}]}]};return Ce(i,r.guard,r.clientTiming)}function Ce(r,e,t){let n=r.resourceSpans[0]?.scopeSpans[0]?.spans[0];if(!n)return r;e&&n.attributes.push(...xe(e));let s=t??(e?.clientRttMs!==void 0?{op:"guard",rttMs:e.clientRttMs}:null);return s&&n.attributes.push(...je(s)),r}function Rt(r){let e=[];for(let t of r)e.push(...t.resourceSpans);return{resourceSpans:e}}});var qe=P(R=>{"use strict";Object.defineProperty(R,"__esModule",{value:!0});R.GUARD_STATUS_REFUSED=void 0;R.evaluateGuard=Ct;var xt=1e4,jt="pinta-core";function G(r,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:r,clientRttMs:r,failOpenReason:e}}var Nt=new Set(["ALLOW","DENY","REVIEW"]);R.GUARD_STATUS_REFUSED=410;async function Ct(r,e,t={}){if(!e||t.disabled)return null;let n=t.timeoutMs??xt,s=Date.now(),i=new AbortController,c=setTimeout(()=>i.abort(),n);try{let o=await fetch(e,{method:"POST",headers:{"content-type":"application/json","user-agent":t.userAgent??jt,"x-pinta-relay-token":t.token??"",...t.agentType?{"x-pinta-agent-type":t.agentType}:{}},body:JSON.stringify(r),signal:i.signal});if(o.status===R.GUARD_STATUS_REFUSED)return G(Date.now()-s,"refused");if(o.status!==200)return G(Date.now()-s,"error");let u=await o.json(),d=u?.durationMs===void 0||typeof u.durationMs=="number"&&Number.isFinite(u.durationMs);if(!u||!Nt.has(u.decision)||!d)return G(Date.now()-s,"error");let l=Date.now()-s;return{decision:u.decision,reason:u.reason??null,userMessage:u.userMessage??null,durationMs:u.durationMs??l,clientRttMs:l}}catch(o){let u=o.name,d=u==="AbortError"||u==="TimeoutError"?"timeout":"error";return G(Date.now()-s,d)}finally{clearTimeout(c)}}});var L=P(q=>{"use strict";var qt=q&&q.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(q,"__esModule",{value:!0});q.atomicWriteFileSync=Bt;var Be=qt(y("fs"));function Bt(r,e){let t=`${r}.${process.pid}.tmp`;Be.default.writeFileSync(t,e),Be.default.renameSync(t,r)}});var oe=P(I=>{"use strict";var Fe=I&&I.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(I,"__esModule",{value:!0});I.MemoryRetryQueue=I.DiskRetryQueue=void 0;var m=Fe(y("fs")),U=Fe(y("path")),Ft=L(),X=1e3,$t=50,Vt=5,ne=class{filePath;lockPath;logPrefix;constructor(e,t){this.filePath=U.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=t}enqueue(e){try{m.default.mkdirSync(U.default.dirname(this.filePath),{recursive:!0});let t=JSON.stringify({savedAt:new Date().toISOString(),payload:e})+`
3
- `;m.default.appendFileSync(this.filePath,t),this.trim()}catch(t){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${t}
4
- `)}}readAll(){try{let e=m.default.readFileSync(this.filePath,"utf-8"),t=[];for(let n of e.split(`
5
- `))if(n.trim())try{let s=JSON.parse(n);Array.isArray(s?.payload?.resourceSpans)&&t.push(s)}catch{}return t}catch{return[]}}rewrite(e){try{if(e.length===0){m.default.existsSync(this.filePath)&&m.default.unlinkSync(this.filePath);return}m.default.mkdirSync(U.default.dirname(this.filePath),{recursive:!0}),(0,Ft.atomicWriteFileSync)(this.filePath,e.map(t=>JSON.stringify(t)).join(`
2
+ var Dt=Object.create;var Ae=Object.defineProperty;var It=Object.getOwnPropertyDescriptor;var At=Object.getOwnPropertyNames;var Mt=Object.getPrototypeOf,wt=Object.prototype.hasOwnProperty;var v=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,n)=>(typeof require<"u"?require:e)[n]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var T=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var kt=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of At(e))!wt.call(t,s)&&s!==n&&Ae(t,s,{get:()=>e[s],enumerable:!(r=It(e,s))||r.enumerable});return t};var D=(t,e,n)=>(n=t!=null?Dt(Mt(t)):{},kt(e||!t||!t.__esModule?Ae(n,"default",{value:t,enumerable:!0}):n,t));var Re=T(se=>{"use strict";Object.defineProperty(se,"__esModule",{value:!0});se.mysqlPasswordRanges=$t;var Rt=new Set(["mysql","mysqldump","mysqladmin","mysqlcheck","mysqlimport","mysqlshow","mariadb","mariadb-dump","mariadb-admin","mariadb-check","mariadb-import","mariadb-show"]),xt=new Set(["sh","bash","dash","zsh","ksh"]),jt=new Set(["-u","--user","-h","--host","-P","--port","-D","--database","-S","--socket","-e","--execute","--protocol","--default-character-set","--defaults-file","--defaults-extra-file","--login-path","--init-command","--ssl-ca","--ssl-capath","--ssl-cert","--ssl-key","--ssl-cipher"]),X=16;function Me(t,e,n=t.value.length){return{value:t.value.slice(e,n),ranges:t.ranges.slice(e,n)}}function G(t,e,n,r){t.value+=e.value.slice(n,r);for(let s=n;s<r;s++)t.ranges.push(e.ranges[s])}function we(t){if(t?.literal)return t.value.slice(t.value.lastIndexOf("/")+1).replace(/\.exe$/,"")}function Nt(t){let e=0;for(let n=0;n<=X;n++){for(;/^[A-Za-z_][A-Za-z0-9_]*=/.test(t[e]?.value??"");)e++;let r=we(t[e]);if(!r)return;if(["!","if","then","elif","else","while","until","do"].includes(r)){e++;continue}let s={env:["-u","--unset","-C","--chdir"],sudo:["-u","--user","-g","--group","-p","--prompt","-D","--chdir","-R","--chroot","-C","--close-from","-T","--command-timeout"],exec:["-a"],nice:["-n","--adjustment"],timeout:["-s","--signal","-k","--kill-after"]},i={env:["-i","--ignore-environment","-0","--null"],sudo:["-n","-E","-H","-S","-b","-k"],command:["-p"],exec:["-c","-l"],nohup:[],nice:[],time:["-p"],timeout:["--foreground","--preserve-status"]};if(!(r in i))return e;for(e++;t[e]?.value.startsWith("-");){let a=t[e++].value;if(a==="--")break;if(!i[r].includes(a)){if((s[r]??[]).includes(a)){if(!t[e++])return;continue}if(!(s[r]??[]).some(o=>o.startsWith("--")?a.startsWith(o+"="):a.startsWith(o)&&a.length>2)&&!(r==="nice"&&/^-\d+$/.test(a)))return}}if(r==="timeout"&&!/^\d+(?:\.\d+)?[smhd]?$/.test(t[e++]?.value??""))return}}function ke(t,e){let n=1,r="";for(let s=e;s<t.length;s++){let i=t[s];if(i==="\\"&&r!=="'"){s++;continue}if(r){i===r&&(r="");continue}if(i==="'"||i==='"')r=i;else if(i==="(")n++;else if(i===")"&&--n===0)return s}return t.length}function qt(t,e,n,r){let s={value:"",ranges:[],start:e,end:e,literal:!0},i="",a=e;for(;a<t.value.length;){let o=t.value[a];if(!i&&/[\s;|&()<>{}]/.test(o))break;if(o===i){i="",a++;continue}if(!i&&(o==="'"||o==='"')){i=o,a++;continue}if(o==="\\"&&i!=="'"){let u=t.value[a+1];if(u===`
3
+ `){a+=2;continue}if(u&&(!i||/[$`"\\]/.test(u))){G(s,t,a+1,a+2),a+=2;continue}}if(i!=="'"&&t.value.startsWith("$(",a)){let u=ke(t.value,a+2);t.value[a+2]!=="("&&r<X&&x(Me(t,a+2,u),n,r+1),G(s,t,a,Math.min(u+1,t.value.length)),s.literal=!1,a=Math.min(u+1,t.value.length);continue}if(o==="`"&&i!=="'"){let u=a+1;for(;u<t.value.length&&t.value[u]!=="`";)u+=t.value[u]==="\\"?2:1;r<X&&x(Me(t,a+1,u),n,r+1),G(s,t,a,Math.min(u+1,t.value.length)),s.literal=!1,a=Math.min(u+1,t.value.length);continue}o==="$"&&i!=="'"&&(s.literal=!1),G(s,t,a,a+1),a++}return s.end=a,s}function Ct(t,e,n,r){let s=Nt(e);if(s===void 0)return;let i=we(e[s]);if(xt.has(i)&&r<X){for(let a=s+1;a<e.length;a++){let o=e[a].value;if(o==="--command"||/^-[a-zA-Z]*c[a-zA-Z]*$/.test(o)){let u=e[a+1];u&&x(u,n,r+1);return}if(!o.startsWith("-")||o==="--")return;(o==="-o"||o==="-O")&&a++}return}if(Rt.has(i))for(let a=s+1;a<e.length;a++){let o=e[a];if(o.value==="--")return;if(jt.has(o.value)){a++;continue}o.value.startsWith("-p")&&o.value.length>2&&n.push({start:t.ranges[o.start].start,end:t.ranges[o.end-1].end})}}function x(t,e,n=0){let r=[],s,i=[],a=0,o=()=>{Ct(t,r,e,n),r=[],s=void 0};for(;a<t.value.length;){let u=t.value[a];if(u===`
4
+ `){o(),a++;for(let l of i.splice(0))for(;a<t.value.length;){let d=t.value.indexOf(`
5
+ `,a),h=d<0?t.value.length:d,$=t.value.slice(a,h);if(a=d<0?h:h+1,(l.stripTabs?$.replace(/^\t+/,""):$)===l.delimiter)break}}else if(/\s/.test(u))a++;else if(u==="#"){let l=t.value.indexOf(`
6
+ `,a);a=l<0?t.value.length:l}else if(t.value.startsWith("((",a))o(),a=Math.min(ke(t.value,a+1)+1,t.value.length);else if(/[;|&(){}]/.test(u))o(),a++;else if(u==="<"||u===">")r.at(-1)?.end===a&&/^\d+$/.test(r.at(-1).value)&&r.pop(),s=/^(?:<<<|<<-?|>>|<&|>&|[<>])/.exec(t.value.slice(a))?.[0]??u,a+=s.length;else{let l=qt(t,a,e,n);a=l.end,(s==="<<"||s==="<<-")&&i.push({delimiter:l.value,stripTabs:s==="<<-"}),s||r.push(l),s=void 0}}o()}function Bt(t,e,n){let r={value:"",ranges:[]};for(let s=e+1;s<n-1;s++){if(t.value[s]!=="\\"){G(r,t,s,s+1);continue}let i=s+(t.value[s+1]==="u"?6:2),a=JSON.parse(`"${t.value.slice(s,i)}"`);r.value+=a;for(let o=0;o<a.length;o++)r.ranges.push({start:t.ranges[s].start,end:t.ranges[i-1].end});s=i-1}return r}function $t(t){let e={value:t,ranges:Array.from({length:t.length},(r,s)=>({start:s,end:s+1}))},n=[];if(/^\s*[{[]/.test(t)){let r;try{r=JSON.parse(t)}catch{return x(e,n),n}if(r!==null&&typeof r=="object"){let s=/"(?:[^"\\]|\\.)*"/g,i;for(;i=s.exec(t);){let a=JSON.parse(i[0]);if(a!=="command"&&a!=="cmd")continue;let o=/^\s*:\s*/.exec(t.slice(s.lastIndex));if(!o)continue;let u=s.lastIndex+o[0].length;if(t[u]!=='"')continue;s.lastIndex=u;let l=s.exec(t);l?.index===u&&x(Bt(e,u,u+l[0].length),n)}}return n}return x(e,n),n}});var oe=T(m=>{"use strict";Object.defineProperty(m,"__esModule",{value:!0});m.PATTERNS=m.MAX_BYTES=void 0;m.truncateTo=xe;m.truncate=Ft;m.collectMatches=je;m.resolveOverlaps=Ne;m.applyMatches=qe;m.redact=Ut;var Gt=Re();m.MAX_BYTES=102400;function xe(t,e){let n=Buffer.from(t,"utf-8");return n.length<=e?t:`${n.subarray(0,Math.max(0,e)).toString("utf-8")}\u2026[TRUNCATED:${n.length}]`}function Ft(t){return xe(t,m.MAX_BYTES)}m.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function je(t,e){let n=[];for(let r of m.PATTERNS){if(r.requireContext&&r.requireContext!==e.context)continue;if(r.type==="cli_password_short"){for(let{start:a,end:o}of(0,Gt.mysqlPasswordRanges)(t))n.push({start:a,end:o,replaceStart:a,replaceEnd:o,type:r.type,replacement:`-p[REDACTED:${r.type}]`});continue}let s=new RegExp(r.regex.source,r.regex.flags),i;for(;(i=s.exec(t))!==null;){let a=r.captureGroup??0,o=i[a];if(o===void 0){i.index===s.lastIndex&&s.lastIndex++;continue}let u=i.index,l=i.index+i[0].length,d=u+i[0].indexOf(o),h=d+o.length;n.push({start:u,end:l,replaceStart:d,replaceEnd:h,type:r.type}),i.index===s.lastIndex&&s.lastIndex++}}return n}function Ne(t){let e=[...t].sort((s,i)=>s.start!==i.start?s.start-i.start:i.end-i.start-(s.end-s.start)),n=[],r=-1;for(let s of e)s.start<r||(n.push(s),r=s.end);return n}function qe(t,e){let n=[...e].sort((s,i)=>i.replaceStart-s.replaceStart),r=t;for(let s of n)r=r.slice(0,s.replaceStart)+(s.replacement??`[REDACTED:${s.type}]`)+r.slice(s.replaceEnd);return r}function Ut(t,e={}){if(t.length===0)return t;let n=je(t,e);if(n.length===0)return t;let r=Ne(n);return qe(t,r)}});var ae=T(p=>{"use strict";var Vt=p&&p.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(p,"__esModule",{value:!0});p.MAX_SPAN_BYTES=void 0;p.ulidToTraceId=Ge;p.newSpanId=Fe;p.snakeCase=Yt;p.toOtlpValue=Ue;p.attrsFromRecord=Wt;p.guardAttrs=Ve;p.clientAttrs=Le;p.enforceSpanBudget=Xe;p.buildPayload=zt;p.attachGuard=Ye;p.mergeBatch=Zt;var Lt=Vt(v("crypto")),ie=oe(),Xt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ge(t){if(t.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${t.length}`);let e=0n;for(let r of t){let s=Xt.indexOf(r);if(s<0)throw new Error(`ulidToTraceId: invalid Crockford char "${r}"`);e=e<<5n|BigInt(s)}let n=(1n<<128n)-1n;return e&=n,e.toString(16).padStart(32,"0")}function Fe(){return Lt.default.randomBytes(8).toString("hex")}function Yt(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var Ce=new Set;function Y(t,e,n){let r=(0,ie.truncate)(e);if((n.skipRedactKeys??Ce).has(t))return r;let s=(n.bashContextKeys??Ce).has(t)?"bash":void 0;return(0,ie.redact)(r,{context:s})}function Ue(t,e,n={}){if(e==null)return null;switch(typeof e){case"string":return{stringValue:Y(t,e,n)};case"boolean":return{boolValue:e};case"number":return Number.isInteger(e)?{intValue:e}:{doubleValue:e};case"object":try{return{stringValue:Y(t,JSON.stringify(e),n)}}catch{return{stringValue:Y(t,String(e),n)}}default:return{stringValue:Y(t,String(e),n)}}}function Wt(t,e,n={}){let r=[];for(let[s,i]of Object.entries(t)){let a=`${e}.${s}`,o=Ue(a,i,n);o!==null&&r.push({key:a,value:o})}return r}function Ve(t){let e=[{key:"pinta.guard.decision",value:{stringValue:t.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:t.durationMs}}];return t.reason&&e.push({key:"pinta.guard.matched_rule",value:{stringValue:t.reason}}),t.failOpenReason&&e.push({key:"pinta.guard.fail_open_reason",value:{stringValue:t.failOpenReason}}),e}function Le(t){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(t.rttMs)?{intValue:t.rttMs}:{doubleValue:t.rttMs}},{key:"pinta.client.op",value:{stringValue:t.op}}]}p.MAX_SPAN_BYTES=800*1024;function Be(t){let e=JSON.stringify(t);return e===void 0?0:Buffer.byteLength(e,"utf-8")}var $e=1024;function Xe(t,e=p.MAX_SPAN_BYTES){let n=Be(t);for(let r=0;r<100&&n>e;r++){let s=null,i=0;for(let u of t.attributes){if(!("stringValue"in u.value))continue;let l=Buffer.byteLength(u.value.stringValue,"utf-8");l>i&&(i=l,s=u.value)}if(s===null||i<=$e)break;let a=n-e,o=Math.max($e,i-a-64);s.stringValue=(0,ie.truncateTo)(s.stringValue,o),n=Be(t)}return t}function zt(t){let e=t.now??Date.now(),n=(BigInt(e)*1000000n).toString(),r=[...t.attributes],s={traceId:Ge(t.traceId),spanId:Fe(),name:t.spanName,kind:t.spanKind??1,startTimeUnixNano:n,endTimeUnixNano:n,attributes:r};Xe(s);let i={resourceSpans:[{resource:{attributes:t.resource},scopeSpans:[{scope:t.scope,spans:[s]}]}]};return Ye(i,t.guard,t.clientTiming)}function Ye(t,e,n){let r=t.resourceSpans[0]?.scopeSpans[0]?.spans[0];if(!r)return t;e&&r.attributes.push(...Ve(e));let s=n??(e?.clientRttMs!==void 0?{op:"guard",rttMs:e.clientRttMs}:null);return s&&r.attributes.push(...Le(s)),t}function Zt(t){let e=[];for(let n of t)e.push(...n.resourceSpans);return{resourceSpans:e}}});var We=T(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.GUARD_STATUS_REFUSED=P.GUARD_BUDGET_HEADER=void 0;P.evaluateGuard=Qt;var Kt=1e4,Ht="pinta-core";P.GUARD_BUDGET_HEADER="x-pinta-guard-budget-ms";function W(t,e){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:t,clientRttMs:t,failOpenReason:e}}var Jt=new Set(["ALLOW","DENY","REVIEW"]);P.GUARD_STATUS_REFUSED=410;async function Qt(t,e,n={}){if(!e||n.disabled)return null;let r=n.timeoutMs??Kt,s=Date.now(),i=new AbortController,a=setTimeout(()=>i.abort(),r);try{let o=await fetch(e,{method:"POST",headers:{"content-type":"application/json","user-agent":n.userAgent??Ht,"x-pinta-relay-token":n.token??"",...n.agentType?{"x-pinta-agent-type":n.agentType}:{},...Number.isFinite(r)&&r>0?{[P.GUARD_BUDGET_HEADER]:String(r)}:{}},body:JSON.stringify(t),signal:i.signal});if(o.status===P.GUARD_STATUS_REFUSED)return W(Date.now()-s,"refused");if(o.status!==200)return W(Date.now()-s,"error");let u=await o.json(),l=u?.durationMs===void 0||typeof u.durationMs=="number"&&Number.isFinite(u.durationMs);if(!u||!Jt.has(u.decision)||!l)return W(Date.now()-s,"error");let d=Date.now()-s;return{decision:u.decision,reason:u.reason??null,userMessage:u.userMessage??null,durationMs:u.durationMs??d,clientRttMs:d}}catch(o){let u=o.name,l=u==="AbortError"||u==="TimeoutError"?"timeout":"error";return W(Date.now()-s,l)}finally{clearTimeout(a)}}});var z=T(F=>{"use strict";var en=F&&F.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(F,"__esModule",{value:!0});F.atomicWriteFileSync=tn;var ze=en(v("fs"));function tn(t,e){let n=`${t}.${process.pid}.tmp`;ze.default.writeFileSync(n,e),ze.default.renameSync(n,t)}});var le=T(I=>{"use strict";var Ze=I&&I.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(I,"__esModule",{value:!0});I.MemoryRetryQueue=I.DiskRetryQueue=void 0;var y=Ze(v("fs")),Z=Ze(v("path")),nn=z(),K=1e3,rn=50,sn=5,ue=class{filePath;lockPath;logPrefix;constructor(e,n){this.filePath=Z.default.join(e,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=n}enqueue(e){try{y.default.mkdirSync(Z.default.dirname(this.filePath),{recursive:!0});let n=JSON.stringify({savedAt:new Date().toISOString(),payload:e})+`
7
+ `;y.default.appendFileSync(this.filePath,n),this.trim()}catch(n){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${n}
8
+ `)}}readAll(){try{let e=y.default.readFileSync(this.filePath,"utf-8"),n=[];for(let r of e.split(`
9
+ `))if(r.trim())try{let s=JSON.parse(r);Array.isArray(s?.payload?.resourceSpans)&&n.push(s)}catch{}return n}catch{return[]}}rewrite(e){try{if(e.length===0){y.default.existsSync(this.filePath)&&y.default.unlinkSync(this.filePath);return}y.default.mkdirSync(Z.default.dirname(this.filePath),{recursive:!0}),(0,nn.atomicWriteFileSync)(this.filePath,e.map(n=>JSON.stringify(n)).join(`
6
10
  `)+`
7
- `)}catch(t){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${t}
8
- `)}}tryAcquireLock(){let e=Date.now();try{m.default.mkdirSync(U.default.dirname(this.lockPath),{recursive:!0})}catch(t){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${t}
9
- `),!1}for(;Date.now()-e<$t;)try{let t=m.default.openSync(this.lockPath,"wx");return m.default.writeSync(t,String(process.pid)),m.default.closeSync(t),!0}catch(t){if(t?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${t}
10
- `),!1;try{let i=m.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){m.default.unlinkSync(this.lockPath);continue}}catch{}let n=Vt,s=Date.now()+n;for(;Date.now()<s;);}return!1}release(){try{m.default.unlinkSync(this.lockPath)}catch{}}trim(){let e=this.readAll();if(e.length<=X)return;let t=e.length-X;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${t} oldest entries
11
- `),this.rewrite(e.slice(t))}};I.DiskRetryQueue=ne;var se=class{entries=[];enqueue(e){this.entries.push(e),this.entries.length>X&&this.entries.splice(0,this.entries.length-X)}drain(){let e=this.entries;return this.entries=[],e}get size(){return this.entries.length}};I.MemoryRetryQueue=se});var Xe=P(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.MemoryTransport=f.DiskTransport=f.MAX_POST_BYTES=void 0;f.parseHeadersEnv=Le;f.envOptionsResolver=Ue;f.postOtlp=B;var $e=oe(),Ve=re(),Gt=5e3;f.MAX_POST_BYTES=900*1024;function Y(r){let e=JSON.stringify(r);return e===void 0?0:Buffer.byteLength(e,"utf-8")}function Ge(r,e,t){let n=[],s=[],i=[],c=0;for(let o of r){let u=e(o);if(u>t){s.push(o);continue}i.length>0&&c+u>t&&(n.push(i),i=[],c=0),i.push(o),c+=u}return i.length>0&&n.push(i),{chunks:n,oversize:s}}function Le(r){if(!r)return{};if(typeof r=="object")return{...r};let e={};for(let t of r.split(",")){let[n,...s]=t.split("=");n&&s.length>0&&(e[n.trim()]=s.join("=").trim())}return e}function Ue(){let r=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,e=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,t;return r?t=r.replace(/\/+$/,""):e&&(t=e.replace(/\/+$/,"")+"/v1/traces"),t?{endpoint:t,headers:Le(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function B(r,e,t){let n=new AbortController,s=setTimeout(()=>n.abort(),Gt);try{let i=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r),signal:n.signal});if(!i.ok){let c="";try{c=(await i.text()).slice(0,200)}catch{}let o=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${t}] OTLP POST ${i.status} ${e.endpoint}${o}${c?` body=${c}`:""}
12
- `),!1}return!0}catch(i){return process.stderr.write(`[${t}] OTLP POST failed: ${i.message??String(i)}
13
- `),!1}finally{clearTimeout(s)}}var ie=class{queue;logPrefix;resolveOptions;constructor(e){this.queue=new $e.DiskRetryQueue(e.pluginData,e.logPrefix),this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions??Ue}async send(e){let t=this.resolveOptions();if(!t||this.dropIfOversized(e))return;await B(e,t,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(e&&this.queue.tryAcquireLock())try{let t=this.queue.readAll();if(t.length===0)return;let{chunks:n,oversize:s}=Ge(t,o=>Y(o.payload),f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized retry ${s.length===1?"entry":"entries"} (each > ${f.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
14
- `);let i=[].concat(...n),c=0;for(let o of n){if(!await B((0,Ve.mergeBatch)(o.map(d=>d.payload)),e,this.logPrefix))break;c+=o.length}this.queue.rewrite(i.slice(c))}catch(t){process.stderr.write(`[${this.logPrefix}] flush failed: ${t?.message??String(t)}
15
- `)}finally{this.queue.release()}}dropIfOversized(e){let t=Y(e);return t<=f.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${t} > ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
16
- `),!0)}};f.DiskTransport=ie;var ae=class{queue=new $e.MemoryRetryQueue;logPrefix;resolveOptions;constructor(e){this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions}async send(e){let t=this.resolveOptions();if(!t)return;if(Y(e)>f.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
17
- `);return}await B(e,t,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(!e)return;let t=this.queue.drain();if(t.length===0)return;let{chunks:n,oversize:s}=Ge(t,Y,f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized buffered ${s.length===1?"payload":"payloads"} (each > ${f.MAX_POST_BYTES} bytes \u2014 undeliverable)
18
- `);let i=[].concat(...n),c=0;for(let o of n){if(!await B((0,Ve.mergeBatch)(o),e,this.logPrefix))break;c+=o.length}for(let o of i.slice(c))this.queue.enqueue(o)}};f.MemoryTransport=ae});var le=P(A=>{"use strict";var de=A&&A.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(A,"__esModule",{value:!0});A.TraceManager=void 0;A.generateUlid=ze;var Ye=de(y("fs")),Lt=de(y("path")),Ut=de(y("crypto")),Xt=L(),ue="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ze(){let r=Date.now(),e="",t=r;for(let i=0;i<10;i++)e=ue[t&31]+e,t=Math.floor(t/32);let n=Ut.default.randomBytes(10),s="";for(let i=0;i<10;i++)s+=ue[n[i]&31];for(;s.length<16;)s+=ue[0];return e+s}var ce=class{tracePath;constructor(e){this.tracePath=e}newTrace(){let e=ze();return this.save(e),e}currentTrace(){try{let e=Ye.default.readFileSync(this.tracePath,"utf-8"),{traceId:t}=JSON.parse(e);if(t)return t}catch{}return this.newTrace()}save(e){try{Ye.default.mkdirSync(Lt.default.dirname(this.tracePath),{recursive:!0}),(0,Xt.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:e}))}catch(t){process.stderr.write(`[pinta-core] trace persistence failed: ${t}
19
- `)}}};A.TraceManager=ce});var He=P(D=>{"use strict";var Ke=D&&D.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(D,"__esModule",{value:!0});D.MemorySessionTraceManager=D.DiskSessionTraceManager=void 0;var We=Ke(y("fs")),Yt=Ke(y("path")),Ze=le(),zt=L(),Je=200,fe=class{tracePath;maxSessions;constructor(e,t={}){this.tracePath=e,this.maxSessions=t.maxSessions??Je}read(){try{let e=JSON.parse(We.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{We.default.mkdirSync(Yt.default.dirname(this.tracePath),{recursive:!0});let t=Object.entries(e),n=t.length>this.maxSessions?Object.fromEntries(t.slice(-this.maxSessions)):e;(0,zt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(e){let t=e||"default",n=(0,Ze.generateUlid)(),s=this.read();return s[t]=n,this.write(s),n}currentTrace(e){let t=e||"default",n=this.read()[t];return n||this.newTrace(t)}};D.DiskSessionTraceManager=fe;var pe=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??Je}newTrace(e){let t=e||"default",n=(0,Ze.generateUlid)();if(this.map.set(t,n),this.map.size>this.maxSessions){let s=this.map.keys().next().value;s!==void 0&&this.map.delete(s)}return n}currentTrace(e){let t=e||"default";return this.map.get(t)??this.newTrace(t)}};D.MemorySessionTraceManager=pe});var tt=P(k=>{"use strict";var ge=k&&k.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(k,"__esModule",{value:!0});k.envFilePath=Zt;k.parseEnvFile=et;k.loadEnvFile=Jt;var Wt=ge(y("node:fs")),Kt=ge(y("node:os")),Qe=ge(y("node:path"));function Zt(r,e,t){let n=t?process.env[t]:void 0,s=n&&n.length>0?n:Qe.default.join(Kt.default.homedir(),r);return Qe.default.join(s,e)}function et(r){let e={};for(let t of r.split(`
20
- `)){let n=t.trim();if(!n||n.startsWith("#"))continue;let s=n.indexOf("=");if(s<0)continue;let i=n.slice(0,s).trim(),c=n.slice(s+1).trim();(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'"))&&(c=c.slice(1,-1)),i&&(e[i]=c)}return e}function Jt(r){let e;try{e=Wt.default.readFileSync(r,"utf-8")}catch{return}let t=et(e);for(let[n,s]of Object.entries(t))process.env[n]===void 0&&(process.env[n]=s)}});var S=P(a=>{"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.loadEnvFile=a.parseEnvFile=a.envFilePath=a.MemorySessionTraceManager=a.DiskSessionTraceManager=a.generateUlid=a.TraceManager=a.MAX_POST_BYTES=a.parseHeadersEnv=a.envOptionsResolver=a.postOtlp=a.MemoryTransport=a.DiskTransport=a.MemoryRetryQueue=a.DiskRetryQueue=a.GUARD_STATUS_REFUSED=a.evaluateGuard=a.MAX_SPAN_BYTES=a.enforceSpanBudget=a.mergeBatch=a.attachGuard=a.buildPayload=a.clientAttrs=a.guardAttrs=a.attrsFromRecord=a.toOtlpValue=a.snakeCase=a.newSpanId=a.ulidToTraceId=a.MAX_BYTES=a.PATTERNS=a.applyMatches=a.resolveOverlaps=a.collectMatches=a.truncateTo=a.truncate=a.redact=void 0;var M=ee();Object.defineProperty(a,"redact",{enumerable:!0,get:function(){return M.redact}});Object.defineProperty(a,"truncate",{enumerable:!0,get:function(){return M.truncate}});Object.defineProperty(a,"truncateTo",{enumerable:!0,get:function(){return M.truncateTo}});Object.defineProperty(a,"collectMatches",{enumerable:!0,get:function(){return M.collectMatches}});Object.defineProperty(a,"resolveOverlaps",{enumerable:!0,get:function(){return M.resolveOverlaps}});Object.defineProperty(a,"applyMatches",{enumerable:!0,get:function(){return M.applyMatches}});Object.defineProperty(a,"PATTERNS",{enumerable:!0,get:function(){return M.PATTERNS}});Object.defineProperty(a,"MAX_BYTES",{enumerable:!0,get:function(){return M.MAX_BYTES}});var _=re();Object.defineProperty(a,"ulidToTraceId",{enumerable:!0,get:function(){return _.ulidToTraceId}});Object.defineProperty(a,"newSpanId",{enumerable:!0,get:function(){return _.newSpanId}});Object.defineProperty(a,"snakeCase",{enumerable:!0,get:function(){return _.snakeCase}});Object.defineProperty(a,"toOtlpValue",{enumerable:!0,get:function(){return _.toOtlpValue}});Object.defineProperty(a,"attrsFromRecord",{enumerable:!0,get:function(){return _.attrsFromRecord}});Object.defineProperty(a,"guardAttrs",{enumerable:!0,get:function(){return _.guardAttrs}});Object.defineProperty(a,"clientAttrs",{enumerable:!0,get:function(){return _.clientAttrs}});Object.defineProperty(a,"buildPayload",{enumerable:!0,get:function(){return _.buildPayload}});Object.defineProperty(a,"attachGuard",{enumerable:!0,get:function(){return _.attachGuard}});Object.defineProperty(a,"mergeBatch",{enumerable:!0,get:function(){return _.mergeBatch}});Object.defineProperty(a,"enforceSpanBudget",{enumerable:!0,get:function(){return _.enforceSpanBudget}});Object.defineProperty(a,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return _.MAX_SPAN_BYTES}});var rt=qe();Object.defineProperty(a,"evaluateGuard",{enumerable:!0,get:function(){return rt.evaluateGuard}});Object.defineProperty(a,"GUARD_STATUS_REFUSED",{enumerable:!0,get:function(){return rt.GUARD_STATUS_REFUSED}});var nt=oe();Object.defineProperty(a,"DiskRetryQueue",{enumerable:!0,get:function(){return nt.DiskRetryQueue}});Object.defineProperty(a,"MemoryRetryQueue",{enumerable:!0,get:function(){return nt.MemoryRetryQueue}});var x=Xe();Object.defineProperty(a,"DiskTransport",{enumerable:!0,get:function(){return x.DiskTransport}});Object.defineProperty(a,"MemoryTransport",{enumerable:!0,get:function(){return x.MemoryTransport}});Object.defineProperty(a,"postOtlp",{enumerable:!0,get:function(){return x.postOtlp}});Object.defineProperty(a,"envOptionsResolver",{enumerable:!0,get:function(){return x.envOptionsResolver}});Object.defineProperty(a,"parseHeadersEnv",{enumerable:!0,get:function(){return x.parseHeadersEnv}});Object.defineProperty(a,"MAX_POST_BYTES",{enumerable:!0,get:function(){return x.MAX_POST_BYTES}});var st=le();Object.defineProperty(a,"TraceManager",{enumerable:!0,get:function(){return st.TraceManager}});Object.defineProperty(a,"generateUlid",{enumerable:!0,get:function(){return st.generateUlid}});var ot=He();Object.defineProperty(a,"DiskSessionTraceManager",{enumerable:!0,get:function(){return ot.DiskSessionTraceManager}});Object.defineProperty(a,"MemorySessionTraceManager",{enumerable:!0,get:function(){return ot.MemorySessionTraceManager}});var he=tt();Object.defineProperty(a,"envFilePath",{enumerable:!0,get:function(){return he.envFilePath}});Object.defineProperty(a,"parseEnvFile",{enumerable:!0,get:function(){return he.parseEnvFile}});Object.defineProperty(a,"loadEnvFile",{enumerable:!0,get:function(){return he.loadEnvFile}})});var at=b(S(),1);import Qt from"node:fs";import me from"node:path";var F=b(S(),1);function Ht(){return(0,F.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function it(r=Ht()){(0,F.loadEnvFile)(r)}function ye(...r){return r.find(e=>e)||void 0}function er(r){let e=ye(r.endpoint,process.env.PINTA_OPENCODE_ENDPOINT,process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);if(e)return e.replace(/\/+$/,"");let t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT;if(t)return t.replace(/\/+$/,"")+"/v1/traces"}var tr="opencode-ai";function rr(){let r=me.dirname(process.execPath);for(let e=0;e<5;e++){try{let n=JSON.parse(Qt.readFileSync(me.join(r,"package.json"),"utf8"));if(n.name===tr&&typeof n.version=="string"&&n.version)return n.version}catch{}let t=me.dirname(r);if(t===r)break;r=t}}function nr(){let r=process.env.OPENCODE_VERSION?.trim();return r||rr()}function ut(r={}){it();let e=ye(r.token,process.env.PINTA_OPENCODE_TOKEN),t=(0,at.parseHeadersEnv)(r.headers??process.env.PINTA_OPENCODE_HEADERS??process.env.OTEL_EXPORTER_OTLP_HEADERS);e&&!Object.keys(t).some(s=>s.toLowerCase()==="x-pinta-relay-token")&&(t["x-pinta-relay-token"]=e);let n=r.guardTimeoutMs??(Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS)||50);return{endpoint:er(r),headers:t,guardEndpoint:ye(r.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:n,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:nr()}}var ct=b(S(),1),z=class extends ct.MemoryTransport{constructor(e){super({logPrefix:"pinta-opencode",resolveOptions:()=>e.endpoint?{endpoint:e.endpoint,headers:e.headers}:null})}};var _e=b(S(),1);var mt=b(S(),1);var dt=b(S(),1);var W="0.10.0";var sr=50,or=`pinta-opencode/${W}`;function lt(r,e,t={}){return(0,dt.evaluateGuard)(r,e,{timeoutMs:t.timeoutMs??sr,token:t.token,disabled:t.disabled,userAgent:or})}var ht=b(S(),1);import ve from"os";var j=b(S(),1);var K;function ir(){if(K===void 0)try{K=ve.userInfo().username}catch{K=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return K}var ft=W,ar=new Set(["opencode.hook","opencode.event_type","opencode.tool_name","opencode.session_id","opencode.sessionID","opencode.tool_use_id","opencode.cwd","opencode.agent","opencode.model","opencode.exit","opencode.truncated","opencode.title"]),ur=new Set(["opencode.tool_input","opencode.tool_response"]),cr={skipRedactKeys:ar,bashContextKeys:ur};function dr(r){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,j.attrsFromRecord)(r,"opencode",cr)),e}function lr(r){return[{key:"service.name",value:{stringValue:"opencode"}},...r?[{key:"service.version",value:{stringValue:r}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-opencode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:ft}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:ir()}},{key:"host.name",value:{stringValue:ve.hostname()}},{key:"host.arch",value:{stringValue:ve.arch()}}]}function pt(r){return(0,j.buildPayload)({traceId:r.traceId,spanName:r.name,attributes:dr(r.fields),resource:lr(r.serviceVersion),scope:{name:"pinta-opencode",version:ft},now:r.now})}var N={messages:1024,requests:512,calls:2048,endedSessions:128,ttlMs:9e5},fr=new Set(["unknown","undefined","null","n/a","none","-","auto","default"]);function g(r){if(typeof r!="string")return;let e=r.trim();return e&&e.length<=1024?e:void 0}function w(r){let e=g(r);if(!(!e||e.startsWith("{")||e.startsWith("[")||fr.has(e.toLowerCase())))return e}function E(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:void 0}function v(r,e,t){let n=E(r),s=n?w(n.modelID)??w(n.id):w(r);if(s)return{model:s,source:n?`${e}.${w(n.modelID)?"modelID":"id"}`:e,provider:w(n?.providerID??t)}}function T(...r){return JSON.stringify(r)}function C(...r){let e=r.map(g).filter(t=>t!==void 0);return e.every(t=>t===e[0])?e[0]:void 0}function Te(r,e){let t=E(e.info),n=E(e.part);return typeof r=="string"&&r.startsWith("session.")?C(e.sessionID,t?.id):C(e.sessionID,t?.sessionID,n?.sessionID)}var $=class{constructor(e,t){this.limit=e;this.now=t}entries=new Map;get(e){let t=this.entries.get(e);if(t&&t.expires>this.now())return t.value;this.entries.delete(e)}set(e,t){this.entries.delete(e),this.entries.set(e,{value:t,expires:this.now()+N.ttlMs}),this.entries.size>this.limit&&this.entries.delete(this.entries.keys().next().value)}removeWhere(e){for(let[t,n]of this.entries)e(n.value)&&this.entries.delete(t)}clearSession(e){this.removeWhere(t=>t.sessionID===e)}},Z=class{constructor(e=Date.now){this.now=e;this.messages=new $(N.messages,e),this.requests=new $(N.requests,e),this.calls=new $(N.calls,e)}messages;requests;calls;ended=new Map;chatMessage(e,t){let n=C(e.sessionID,t?.message?.sessionID);if(!n)return;this.ended.delete(n);let s=C(e.messageID,t?.message?.id);if(!s)return;let i=g(t?.message?.agent??e.agent),c=t?.message?.model!==void 0;this.requests.set(T(n,s,i??""),{sessionID:n,messageID:s,evidence:v(c?t.message?.model:e.model,c?"requested:chat.message.message.model":"requested:chat.message.model")})}chatParams(e){let t=C(e.sessionID,e.message?.sessionID),n=g(e.message?.id),s=g(e.agent);!t||!n||!s||this.ended.has(t)||this.requests.set(T(t,n,s),{sessionID:t,messageID:n,evidence:v(e.model,"requested:chat.params.model")})}fresh(e){return e===void 0||typeof e=="number"&&Number.isFinite(e)&&e>=this.now()-N.ttlMs}clearSession(e){this.messages.clearSession(e),this.requests.clearSession(e),this.calls.clearSession(e)}event(e,t){let n=Te(e,t),s=E(t.info),i=E(t.part),c=["session.idle","session.deleted","session.error"].includes(e??"")||e==="session.status"&&E(t.status)?.type==="idle";if(n&&c?(this.clearSession(n),this.ended.delete(n),this.ended.set(n,!0),this.ended.size>N.endedSessions&&this.ended.delete(this.ended.keys().next().value)):n&&e==="session.created"&&(this.clearSession(n),this.ended.delete(n)),e==="message.updated"&&s){if(!C(t.sessionID,s.sessionID)&&(t.sessionID||s.sessionID))return;let o=g(s.id),u=g(s.agent),d=s.role,l=d==="assistant"?v(w(s.modelID),"reported:message.updated.info.modelID",s.providerID)??v(s.model,"reported:message.updated.info.model"):d==="user"?v(s.model,"requested:message.updated.info.model"):void 0;if(!l&&d==="user"&&!("model"in s)&&n&&o&&(l=this.requests.get(T(n,o,u??""))?.evidence),d==="user"&&"model"in s&&!l&&n&&o&&this.requests.set(T(n,o,u??""),{sessionID:n,messageID:o}),l&&(l={...l,agent:u,messageID:o}),n&&o&&!this.ended.has(n)&&this.fresh(E(s.time)?.created)){let O=this.messages.get(T(n,o)),Oe=O?.ambiguous||!!(d==="assistant"&&O?.evidence&&l&&(O.evidence.model!==l.model||O.evidence.provider!==l.provider||O.evidence.agent!==l.agent));this.messages.set(T(n,o),{sessionID:n,messageID:o,evidence:Oe?void 0:l,ambiguous:Oe})}return l}if(n&&e==="message.removed"){let o=g(t.messageID);this.messages.removeWhere(u=>u.sessionID===n&&u.messageID===o),this.requests.removeWhere(u=>u.sessionID===n&&u.messageID===o),this.calls.removeWhere(u=>u.sessionID===n&&u.messageID===o)}if(n&&e==="message.part.removed"){let o=g(t.partID);o&&this.calls.removeWhere(u=>u.sessionID===n&&u.partID===o)}if(e==="message.part.delta"){let o=g(t.messageID);return n&&o&&!this.ended.has(n)?this.messages.get(T(n,o))?.evidence:void 0}if(e==="message.part.updated"&&i){if(!n||this.ended.has(n)||!this.fresh(t.time))return;let o=g(i.messageID);if(!o)return;let u=g(i.callID);if(i.type==="tool"&&u){let d=T(n,u),l=this.calls.get(d);if(!l?.closed){let O=g(i.tool);this.calls.set(d,{sessionID:n,messageID:o,partID:g(i.id),tool:O,pinned:l?.pinned,ambiguous:l?.ambiguous||!!(l&&(l.messageID!==o||l.tool!==O))})}}return this.messages.get(T(n,o))?.evidence}return typeof e=="string"&&e.startsWith("session.")?v(s?.model,`selected:${e}.info.model`)??v(t.model,`selected:${e}.model`):v(t.model,"reported:event.model")??v(w(t.modelID),"reported:event.modelID",t.providerID)}tool(e,t){let n=v(w(e.modelID),"reported:tool.modelID",e.providerID)??v(e.model,"reported:tool.model",e.providerID),s=g(e.sessionID),i=g(e.callID);if(!s||!i)return n;let c=T(s,i),o=this.calls.get(c),u=n;if(!("model"in e)&&!("modelID"in e)&&o&&!o.closed&&!o.ambiguous&&!this.ended.has(s)&&(!o.tool||o.tool===e.tool)){let d=this.messages.get(T(s,o.messageID));d?.ambiguous||(u=o.pinned??d?.evidence),(e.agent&&u?.agent&&e.agent!==u.agent||e.messageID&&e.messageID!==o.messageID)&&(u=void 0)}return o&&!o.closed&&this.calls.set(c,{...o,pinned:t?void 0:u,closed:t}),u}beforeTool(e){return this.tool(e,!1)}afterTool(e){return this.tool(e,!0)}};function J(r,e){let{model:t,model_source:n,...s}=r;return"model"in r&&t!==e?.model&&(s.model_raw=t),"model_source"in r&&(s.model_source_raw=n),e&&(s.model=e.model,s.model_source=e.source,e.provider&&("provider"in s&&s.provider!==e.provider&&(s.provider_raw=s.provider),s.provider=e.provider),s.agent===void 0&&(s.agent=e.agent),s.message_id===void 0&&(s.message_id=e.messageID)),s}var H=class{constructor(e,t,n){this.transport=e;this.trace=t;this.config=n}models=new Z;build(e,t,n){let s=this.trace.currentTrace(t);return pt({name:e,traceId:s,fields:n,serviceVersion:this.config.serviceVersion})}async send(e){await this.transport.send(e)}async emit(e,t,n){await this.send(this.build(e,t,n))}chatMessage(e,t){this.models.chatMessage(e,t)}chatParams(e){this.models.chatParams(e)}async lifecycle(e){let t=E(e.properties)??{},n=Te(e.type,t),s=this.models.event(e.type,t);await this.emit(`opencode.event.${e.type??"unknown"}`,n,J({hook:"event",event_type:e.type,session_id:n,cwd:process.cwd(),...t},s)),e.type==="session.idle"&&await this.transport.flush()}toolBeforePayload(e,t){return this.build("opencode.tool.before",e.sessionID,J({...gt("tool.execute.before",e),tool_input:t},this.models.beforeTool(e)))}async toolBefore(e,t,n){await this.send((0,ht.attachGuard)(this.toolBeforePayload(e,t),n))}async toolAfter(e,t){let n=t.metadata??{};await this.emit("opencode.tool.after",e.sessionID,J({...gt("tool.execute.after",e),title:t.title,tool_response:t.output,exit:n.exit,truncated:n.truncated},this.models.afterTool(e)))}};function gt(r,e){return{hook:r,tool_name:e.tool,session_id:e.sessionID,tool_use_id:e.callID,cwd:process.cwd(),...e.model!==void 0?{model:e.model}:{},...e.modelID!==void 0?{modelID:e.modelID}:{},...e.providerID!==void 0?{providerID:e.providerID}:{},...e.agent!==void 0?{agent:e.agent}:{},...e.messageID!==void 0?{message_id:e.messageID}:{}}}function yt(r,e){process.stderr.write(`[pinta-opencode] ${r}: ${e?.message??String(e)}
21
- `)}function Q(r,e){return async(...t)=>{try{await e(...t)}catch(n){yt(r,n)}}}var pr=async(r,e)=>{let t=ut(e??{}),n=new z({endpoint:t.endpoint,headers:t.headers}),s=new _e.MemorySessionTraceManager,i=new H(n,s,t);async function c(o,u){let d=null;try{let l=i.toolBeforePayload(o,u);d=await lt(l,t.guardEndpoint,{timeoutMs:t.guardTimeoutMs,token:t.relayToken,disabled:t.guardDisabled}),await i.send((0,mt.attachGuard)(l,d))}catch(l){yt("tool.execute.before",l)}return d}return{"chat.message":Q("chat.message",async(o,u)=>{s.newTrace(o?.sessionID),i.chatMessage(o,u)}),"chat.params":Q("chat.params",async(o,u)=>{i.chatParams(o)}),event:Q("event",async o=>{o?.event&&await i.lifecycle(o.event)}),"tool.execute.before":async(o,u)=>{let d=await c(o,u?.args);if(d?.decision==="DENY")throw new Error(d.userMessage??d.reason??"guard_deny")},"tool.execute.after":Q("tool.execute.after",(o,u)=>i.toolAfter(o,u))}},zr=pr;export{pr as PintaOpencode,zr as default};
11
+ `)}catch(n){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${n}
12
+ `)}}tryAcquireLock(){let e=Date.now();try{y.default.mkdirSync(Z.default.dirname(this.lockPath),{recursive:!0})}catch(n){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${n}
13
+ `),!1}for(;Date.now()-e<rn;)try{let n=y.default.openSync(this.lockPath,"wx");return y.default.writeSync(n,String(process.pid)),y.default.closeSync(n),!0}catch(n){if(n?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${n}
14
+ `),!1;try{let i=y.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){y.default.unlinkSync(this.lockPath);continue}}catch{}let r=sn,s=Date.now()+r;for(;Date.now()<s;);}return!1}release(){try{y.default.unlinkSync(this.lockPath)}catch{}}trim(){let e=this.readAll();if(e.length<=K)return;let n=e.length-K;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${n} oldest entries
15
+ `),this.rewrite(e.slice(n))}};I.DiskRetryQueue=ue;var ce=class{entries=[];enqueue(e){this.entries.push(e),this.entries.length>K&&this.entries.splice(0,this.entries.length-K)}drain(){let e=this.entries;return this.entries=[],e}get size(){return this.entries.length}};I.MemoryRetryQueue=ce});var tt=T(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.MemoryTransport=f.DiskTransport=f.MAX_POST_BYTES=void 0;f.parseHeadersEnv=Qe;f.envOptionsResolver=et;f.postOtlp=U;var Ke=le(),He=ae(),on=5e3;f.MAX_POST_BYTES=900*1024;function H(t){let e=JSON.stringify(t);return e===void 0?0:Buffer.byteLength(e,"utf-8")}function Je(t,e,n){let r=[],s=[],i=[],a=0;for(let o of t){let u=e(o);if(u>n){s.push(o);continue}i.length>0&&a+u>n&&(r.push(i),i=[],a=0),i.push(o),a+=u}return i.length>0&&r.push(i),{chunks:r,oversize:s}}function Qe(t){if(!t)return{};if(typeof t=="object")return{...t};let e={};for(let n of t.split(",")){let[r,...s]=n.split("=");r&&s.length>0&&(e[r.trim()]=s.join("=").trim())}return e}function et(){let t=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,e=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,n;return t?n=t.replace(/\/+$/,""):e&&(n=e.replace(/\/+$/,"")+"/v1/traces"),n?{endpoint:n,headers:Qe(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function U(t,e,n){let r=new AbortController,s=setTimeout(()=>r.abort(),on);try{let i=await fetch(e.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(t),signal:r.signal});if(!i.ok){let a="";try{a=(await i.text()).slice(0,200)}catch{}let o=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${n}] OTLP POST ${i.status} ${e.endpoint}${o}${a?` body=${a}`:""}
16
+ `),!1}return!0}catch(i){return process.stderr.write(`[${n}] OTLP POST failed: ${i.message??String(i)}
17
+ `),!1}finally{clearTimeout(s)}}var de=class{queue;logPrefix;resolveOptions;constructor(e){this.queue=new Ke.DiskRetryQueue(e.pluginData,e.logPrefix),this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions??et}async send(e){let n=this.resolveOptions();if(!n||this.dropIfOversized(e))return;await U(e,n,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(e&&this.queue.tryAcquireLock())try{let n=this.queue.readAll();if(n.length===0)return;let{chunks:r,oversize:s}=Je(n,o=>H(o.payload),f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized retry ${s.length===1?"entry":"entries"} (each > ${f.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
18
+ `);let i=[].concat(...r),a=0;for(let o of r){if(!await U((0,He.mergeBatch)(o.map(l=>l.payload)),e,this.logPrefix))break;a+=o.length}this.queue.rewrite(i.slice(a))}catch(n){process.stderr.write(`[${this.logPrefix}] flush failed: ${n?.message??String(n)}
19
+ `)}finally{this.queue.release()}}dropIfOversized(e){let n=H(e);return n<=f.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${n} > ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
20
+ `),!0)}};f.DiskTransport=de;var fe=class{queue=new Ke.MemoryRetryQueue;logPrefix;resolveOptions;constructor(e){this.logPrefix=e.logPrefix,this.resolveOptions=e.resolveOptions}async send(e){let n=this.resolveOptions();if(!n)return;if(H(e)>f.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${f.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
21
+ `);return}await U(e,n,this.logPrefix)||this.queue.enqueue(e)}async flush(){let e=this.resolveOptions();if(!e)return;let n=this.queue.drain();if(n.length===0)return;let{chunks:r,oversize:s}=Je(n,H,f.MAX_POST_BYTES);s.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${s.length} oversized buffered ${s.length===1?"payload":"payloads"} (each > ${f.MAX_POST_BYTES} bytes \u2014 undeliverable)
22
+ `);let i=[].concat(...r),a=0;for(let o of r){if(!await U((0,He.mergeBatch)(o),e,this.logPrefix))break;a+=o.length}for(let o of i.slice(a))this.queue.enqueue(o)}};f.MemoryTransport=fe});var me=T(k=>{"use strict";var he=k&&k.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(k,"__esModule",{value:!0});k.TraceManager=void 0;k.generateUlid=rt;var nt=he(v("fs")),an=he(v("path")),un=he(v("crypto")),cn=z(),pe="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function rt(){let t=Date.now(),e="",n=t;for(let i=0;i<10;i++)e=pe[n&31]+e,n=Math.floor(n/32);let r=un.default.randomBytes(10),s="";for(let i=0;i<10;i++)s+=pe[r[i]&31];for(;s.length<16;)s+=pe[0];return e+s}var ge=class{tracePath;constructor(e){this.tracePath=e}newTrace(){let e=rt();return this.save(e),e}currentTrace(){try{let e=nt.default.readFileSync(this.tracePath,"utf-8"),{traceId:n}=JSON.parse(e);if(n)return n}catch{}return this.newTrace()}save(e){try{nt.default.mkdirSync(an.default.dirname(this.tracePath),{recursive:!0}),(0,cn.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:e}))}catch(n){process.stderr.write(`[pinta-core] trace persistence failed: ${n}
23
+ `)}}};k.TraceManager=ge});var ut=T(A=>{"use strict";var ot=A&&A.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(A,"__esModule",{value:!0});A.MemorySessionTraceManager=A.DiskSessionTraceManager=void 0;var st=ot(v("fs")),ln=ot(v("path")),it=me(),dn=z(),at=200,ye=class{tracePath;maxSessions;constructor(e,n={}){this.tracePath=e,this.maxSessions=n.maxSessions??at}read(){try{let e=JSON.parse(st.default.readFileSync(this.tracePath,"utf-8"));if(e&&typeof e=="object"&&!("traceId"in e))return e}catch{}return{}}write(e){try{st.default.mkdirSync(ln.default.dirname(this.tracePath),{recursive:!0});let n=Object.entries(e),r=n.length>this.maxSessions?Object.fromEntries(n.slice(-this.maxSessions)):e;(0,dn.atomicWriteFileSync)(this.tracePath,JSON.stringify(r))}catch{}}newTrace(e){let n=e||"default",r=(0,it.generateUlid)(),s=this.read();return s[n]=r,this.write(s),r}currentTrace(e){let n=e||"default",r=this.read()[n];return r||this.newTrace(n)}};A.DiskSessionTraceManager=ye;var ve=class{map=new Map;maxSessions;constructor(e={}){this.maxSessions=e.maxSessions??at}newTrace(e){let n=e||"default",r=(0,it.generateUlid)();if(this.map.set(n,r),this.map.size>this.maxSessions){let s=this.map.keys().next().value;s!==void 0&&this.map.delete(s)}return r}currentTrace(e){let n=e||"default";return this.map.get(n)??this.newTrace(n)}};A.MemorySessionTraceManager=ve});var dt=T(R=>{"use strict";var _e=R&&R.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(R,"__esModule",{value:!0});R.envFilePath=gn;R.parseEnvFile=lt;R.loadEnvFile=hn;var fn=_e(v("node:fs")),pn=_e(v("node:os")),ct=_e(v("node:path"));function gn(t,e,n){let r=n?process.env[n]:void 0,s=r&&r.length>0?r:ct.default.join(pn.default.homedir(),t);return ct.default.join(s,e)}function lt(t){let e={};for(let n of t.split(`
24
+ `)){let r=n.trim();if(!r||r.startsWith("#"))continue;let s=r.indexOf("=");if(s<0)continue;let i=r.slice(0,s).trim(),a=r.slice(s+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),i&&(e[i]=a)}return e}function hn(t){let e;try{e=fn.default.readFileSync(t,"utf-8")}catch{return}let n=lt(e);for(let[r,s]of Object.entries(n))process.env[r]===void 0&&(process.env[r]=s)}});var S=T(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});c.loadEnvFile=c.parseEnvFile=c.envFilePath=c.MemorySessionTraceManager=c.DiskSessionTraceManager=c.generateUlid=c.TraceManager=c.MAX_POST_BYTES=c.parseHeadersEnv=c.envOptionsResolver=c.postOtlp=c.MemoryTransport=c.DiskTransport=c.MemoryRetryQueue=c.DiskRetryQueue=c.GUARD_BUDGET_HEADER=c.GUARD_STATUS_REFUSED=c.evaluateGuard=c.MAX_SPAN_BYTES=c.enforceSpanBudget=c.mergeBatch=c.attachGuard=c.buildPayload=c.clientAttrs=c.guardAttrs=c.attrsFromRecord=c.toOtlpValue=c.snakeCase=c.newSpanId=c.ulidToTraceId=c.MAX_BYTES=c.PATTERNS=c.applyMatches=c.resolveOverlaps=c.collectMatches=c.truncateTo=c.truncate=c.redact=void 0;var M=oe();Object.defineProperty(c,"redact",{enumerable:!0,get:function(){return M.redact}});Object.defineProperty(c,"truncate",{enumerable:!0,get:function(){return M.truncate}});Object.defineProperty(c,"truncateTo",{enumerable:!0,get:function(){return M.truncateTo}});Object.defineProperty(c,"collectMatches",{enumerable:!0,get:function(){return M.collectMatches}});Object.defineProperty(c,"resolveOverlaps",{enumerable:!0,get:function(){return M.resolveOverlaps}});Object.defineProperty(c,"applyMatches",{enumerable:!0,get:function(){return M.applyMatches}});Object.defineProperty(c,"PATTERNS",{enumerable:!0,get:function(){return M.PATTERNS}});Object.defineProperty(c,"MAX_BYTES",{enumerable:!0,get:function(){return M.MAX_BYTES}});var _=ae();Object.defineProperty(c,"ulidToTraceId",{enumerable:!0,get:function(){return _.ulidToTraceId}});Object.defineProperty(c,"newSpanId",{enumerable:!0,get:function(){return _.newSpanId}});Object.defineProperty(c,"snakeCase",{enumerable:!0,get:function(){return _.snakeCase}});Object.defineProperty(c,"toOtlpValue",{enumerable:!0,get:function(){return _.toOtlpValue}});Object.defineProperty(c,"attrsFromRecord",{enumerable:!0,get:function(){return _.attrsFromRecord}});Object.defineProperty(c,"guardAttrs",{enumerable:!0,get:function(){return _.guardAttrs}});Object.defineProperty(c,"clientAttrs",{enumerable:!0,get:function(){return _.clientAttrs}});Object.defineProperty(c,"buildPayload",{enumerable:!0,get:function(){return _.buildPayload}});Object.defineProperty(c,"attachGuard",{enumerable:!0,get:function(){return _.attachGuard}});Object.defineProperty(c,"mergeBatch",{enumerable:!0,get:function(){return _.mergeBatch}});Object.defineProperty(c,"enforceSpanBudget",{enumerable:!0,get:function(){return _.enforceSpanBudget}});Object.defineProperty(c,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return _.MAX_SPAN_BYTES}});var Te=We();Object.defineProperty(c,"evaluateGuard",{enumerable:!0,get:function(){return Te.evaluateGuard}});Object.defineProperty(c,"GUARD_STATUS_REFUSED",{enumerable:!0,get:function(){return Te.GUARD_STATUS_REFUSED}});Object.defineProperty(c,"GUARD_BUDGET_HEADER",{enumerable:!0,get:function(){return Te.GUARD_BUDGET_HEADER}});var ft=le();Object.defineProperty(c,"DiskRetryQueue",{enumerable:!0,get:function(){return ft.DiskRetryQueue}});Object.defineProperty(c,"MemoryRetryQueue",{enumerable:!0,get:function(){return ft.MemoryRetryQueue}});var j=tt();Object.defineProperty(c,"DiskTransport",{enumerable:!0,get:function(){return j.DiskTransport}});Object.defineProperty(c,"MemoryTransport",{enumerable:!0,get:function(){return j.MemoryTransport}});Object.defineProperty(c,"postOtlp",{enumerable:!0,get:function(){return j.postOtlp}});Object.defineProperty(c,"envOptionsResolver",{enumerable:!0,get:function(){return j.envOptionsResolver}});Object.defineProperty(c,"parseHeadersEnv",{enumerable:!0,get:function(){return j.parseHeadersEnv}});Object.defineProperty(c,"MAX_POST_BYTES",{enumerable:!0,get:function(){return j.MAX_POST_BYTES}});var pt=me();Object.defineProperty(c,"TraceManager",{enumerable:!0,get:function(){return pt.TraceManager}});Object.defineProperty(c,"generateUlid",{enumerable:!0,get:function(){return pt.generateUlid}});var gt=ut();Object.defineProperty(c,"DiskSessionTraceManager",{enumerable:!0,get:function(){return gt.DiskSessionTraceManager}});Object.defineProperty(c,"MemorySessionTraceManager",{enumerable:!0,get:function(){return gt.MemorySessionTraceManager}});var Oe=dt();Object.defineProperty(c,"envFilePath",{enumerable:!0,get:function(){return Oe.envFilePath}});Object.defineProperty(c,"parseEnvFile",{enumerable:!0,get:function(){return Oe.parseEnvFile}});Object.defineProperty(c,"loadEnvFile",{enumerable:!0,get:function(){return Oe.loadEnvFile}})});var mt=D(S(),1);import yn from"node:fs";import Ee from"node:path";var V=D(S(),1);function mn(){return(0,V.envFilePath)(".config/opencode","pinta-opencode.env","OPENCODE_CONFIG_DIR")}function ht(t=mn()){(0,V.loadEnvFile)(t)}function Pe(...t){return t.find(e=>e)||void 0}function vn(t){let e=Pe(t.endpoint,process.env.PINTA_OPENCODE_ENDPOINT,process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT);if(e)return e.replace(/\/+$/,"");let n=process.env.OTEL_EXPORTER_OTLP_ENDPOINT;if(n)return n.replace(/\/+$/,"")+"/v1/traces"}var _n="opencode-ai";function Tn(){let t=Ee.dirname(process.execPath);for(let e=0;e<5;e++){try{let r=JSON.parse(yn.readFileSync(Ee.join(t,"package.json"),"utf8"));if(r.name===_n&&typeof r.version=="string"&&r.version)return r.version}catch{}let n=Ee.dirname(t);if(n===t)break;t=n}}function On(){let t=process.env.OPENCODE_VERSION?.trim();return t||Tn()}function yt(t={}){ht();let e=Pe(t.token,process.env.PINTA_OPENCODE_TOKEN),n=(0,mt.parseHeadersEnv)(t.headers??process.env.PINTA_OPENCODE_HEADERS??process.env.OTEL_EXPORTER_OTLP_HEADERS);e&&!Object.keys(n).some(s=>s.toLowerCase()==="x-pinta-relay-token")&&(n["x-pinta-relay-token"]=e);let r=t.guardTimeoutMs??(Number(process.env.PINTA_OPENCODE_GUARD_TIMEOUT_MS)||100);return{endpoint:vn(t),headers:n,guardEndpoint:Pe(t.guard,process.env.PINTA_OPENCODE_GUARD),relayToken:e,guardTimeoutMs:r,guardDisabled:process.env.PINTA_OPENCODE_GUARD_DISABLED==="1",serviceVersion:On()}}var N=D(S(),1),J=class extends N.MemoryTransport{constructor(n){super({logPrefix:"pinta-opencode",resolveOptions:()=>n.endpoint?{endpoint:n.endpoint,headers:n.headers}:null});this.config=n}deferred=new N.MemoryRetryQueue;defer(n){if(!this.config.endpoint)return;let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>N.MAX_POST_BYTES){process.stderr.write(`[pinta-opencode] deferred payload exceeds POST budget (${r} bytes); dropped
25
+ `);return}this.deferred.enqueue(n)}async flush(){await super.flush();for(let n of this.deferred.drain())await super.send(n)}};var Se=D(S(),1);var St=D(S(),1);var vt=D(S(),1);var Q="0.11.1";var En=100,Pn=`pinta-opencode/${Q}`;function _t(t,e,n={}){return(0,vt.evaluateGuard)(t,e,{timeoutMs:n.timeoutMs??En,token:n.token,disabled:n.disabled,userAgent:Pn})}var Pt=D(S(),1);import be from"os";var q=D(S(),1);var ee;function Sn(){if(ee===void 0)try{ee=be.userInfo().username}catch{ee=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return ee}var Tt=Q,bn=new Set(["opencode.hook","opencode.event_type","opencode.tool_name","opencode.session_id","opencode.sessionID","opencode.tool_use_id","opencode.cwd","opencode.agent","opencode.model","opencode.exit","opencode.truncated","opencode.title"]),Dn=new Set(["opencode.tool_input","opencode.tool_response"]),In={skipRedactKeys:bn,bashContextKeys:Dn};function An(t){let e=[{key:"ingest.type",value:{stringValue:"opencode"}}];return e.push(...(0,q.attrsFromRecord)(t,"opencode",In)),e}function Mn(t){return[{key:"service.name",value:{stringValue:"opencode"}},...t?[{key:"service.version",value:{stringValue:t}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-opencode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:Tt}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:Sn()}},{key:"host.name",value:{stringValue:be.hostname()}},{key:"host.arch",value:{stringValue:be.arch()}}]}function Ot(t){return(0,q.buildPayload)({traceId:t.traceId,spanName:t.name,attributes:An(t.fields),resource:Mn(t.serviceVersion),scope:{name:"pinta-opencode",version:Tt},now:t.now})}var C={messages:1024,requests:512,calls:2048,endedSessions:128,ttlMs:9e5},wn=new Set(["unknown","undefined","null","n/a","none","-","auto","default"]);function g(t){if(typeof t!="string")return;let e=t.trim();return e&&e.length<=1024?e:void 0}function w(t){let e=g(t);if(!(!e||e.startsWith("{")||e.startsWith("[")||wn.has(e.toLowerCase())))return e}function b(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)?t:void 0}function O(t,e,n){let r=b(t),s=r?w(r.modelID)??w(r.id):w(t);if(s)return{model:s,source:r?`${e}.${w(r.modelID)?"modelID":"id"}`:e,provider:w(r?.providerID??n)}}function E(...t){return JSON.stringify(t)}function B(...t){let e=t.map(g).filter(n=>n!==void 0);return e.every(n=>n===e[0])?e[0]:void 0}function De(t,e){let n=b(e.info),r=b(e.part);return typeof t=="string"&&t.startsWith("session.")?B(e.sessionID,n?.id):B(e.sessionID,n?.sessionID,r?.sessionID)}var L=class{constructor(e,n){this.limit=e;this.now=n}entries=new Map;get(e){let n=this.entries.get(e);if(n&&n.expires>this.now())return n.value;this.entries.delete(e)}set(e,n){this.entries.delete(e),this.entries.set(e,{value:n,expires:this.now()+C.ttlMs}),this.entries.size>this.limit&&this.entries.delete(this.entries.keys().next().value)}removeWhere(e){for(let[n,r]of this.entries)e(r.value)&&this.entries.delete(n)}clearSession(e){this.removeWhere(n=>n.sessionID===e)}},te=class{constructor(e=Date.now){this.now=e;this.messages=new L(C.messages,e),this.requests=new L(C.requests,e),this.calls=new L(C.calls,e)}messages;requests;calls;ended=new Map;chatMessage(e,n){let r=B(e.sessionID,n?.message?.sessionID);if(!r)return;this.ended.delete(r);let s=B(e.messageID,n?.message?.id);if(!s)return;let i=g(n?.message?.agent??e.agent),a=n?.message?.model!==void 0;this.requests.set(E(r,s,i??""),{sessionID:r,messageID:s,evidence:O(a?n.message?.model:e.model,a?"requested:chat.message.message.model":"requested:chat.message.model")})}chatParams(e){let n=B(e.sessionID,e.message?.sessionID),r=g(e.message?.id),s=g(e.agent);!n||!r||!s||this.ended.has(n)||this.requests.set(E(n,r,s),{sessionID:n,messageID:r,evidence:O(e.model,"requested:chat.params.model")})}fresh(e){return e===void 0||typeof e=="number"&&Number.isFinite(e)&&e>=this.now()-C.ttlMs}clearSession(e){this.messages.clearSession(e),this.requests.clearSession(e),this.calls.clearSession(e)}event(e,n){let r=De(e,n),s=b(n.info),i=b(n.part),a=["session.idle","session.deleted","session.error"].includes(e??"")||e==="session.status"&&b(n.status)?.type==="idle";if(r&&a?(this.clearSession(r),this.ended.delete(r),this.ended.set(r,!0),this.ended.size>C.endedSessions&&this.ended.delete(this.ended.keys().next().value)):r&&e==="session.created"&&(this.clearSession(r),this.ended.delete(r)),e==="message.updated"&&s){if(!B(n.sessionID,s.sessionID)&&(n.sessionID||s.sessionID))return;let o=g(s.id),u=g(s.agent),l=s.role,d=l==="assistant"?O(w(s.modelID),"reported:message.updated.info.modelID",s.providerID)??O(s.model,"reported:message.updated.info.model"):l==="user"?O(s.model,"requested:message.updated.info.model"):void 0;if(!d&&l==="user"&&!("model"in s)&&r&&o&&(d=this.requests.get(E(r,o,u??""))?.evidence),l==="user"&&"model"in s&&!d&&r&&o&&this.requests.set(E(r,o,u??""),{sessionID:r,messageID:o}),d&&(d={...d,agent:u,messageID:o}),r&&o&&!this.ended.has(r)&&this.fresh(b(s.time)?.created)){let h=this.messages.get(E(r,o)),$=h?.ambiguous||!!(l==="assistant"&&h?.evidence&&d&&(h.evidence.model!==d.model||h.evidence.provider!==d.provider||h.evidence.agent!==d.agent));this.messages.set(E(r,o),{sessionID:r,messageID:o,evidence:$?void 0:d,ambiguous:$})}return d}if(r&&e==="message.removed"){let o=g(n.messageID);this.messages.removeWhere(u=>u.sessionID===r&&u.messageID===o),this.requests.removeWhere(u=>u.sessionID===r&&u.messageID===o),this.calls.removeWhere(u=>u.sessionID===r&&u.messageID===o)}if(r&&e==="message.part.removed"){let o=g(n.partID);o&&this.calls.removeWhere(u=>u.sessionID===r&&u.partID===o)}if(e==="message.part.delta"){let o=g(n.messageID);return r&&o&&!this.ended.has(r)?this.messages.get(E(r,o))?.evidence:void 0}if(e==="message.part.updated"&&i){if(!r||this.ended.has(r)||!this.fresh(n.time))return;let o=g(i.messageID);if(!o)return;let u=g(i.callID);if(i.type==="tool"&&u){let l=E(r,u),d=this.calls.get(l);if(!d?.closed){let h=g(i.tool);this.calls.set(l,{sessionID:r,messageID:o,partID:g(i.id),tool:h,pinned:d?.pinned,ambiguous:d?.ambiguous||!!(d&&(d.messageID!==o||d.tool!==h))})}}return this.messages.get(E(r,o))?.evidence}return typeof e=="string"&&e.startsWith("session.")?O(s?.model,`selected:${e}.info.model`)??O(n.model,`selected:${e}.model`):O(n.model,"reported:event.model")??O(w(n.modelID),"reported:event.modelID",n.providerID)}tool(e,n){let r=O(w(e.modelID),"reported:tool.modelID",e.providerID)??O(e.model,"reported:tool.model",e.providerID),s=g(e.sessionID),i=g(e.callID);if(!s||!i)return r;let a=E(s,i),o=this.calls.get(a),u=r;if(!("model"in e)&&!("modelID"in e)&&o&&!o.closed&&!o.ambiguous&&!this.ended.has(s)&&(!o.tool||o.tool===e.tool)){let l=this.messages.get(E(s,o.messageID));l?.ambiguous||(u=o.pinned??l?.evidence),(e.agent&&u?.agent&&e.agent!==u.agent||e.messageID&&e.messageID!==o.messageID)&&(u=void 0)}return o&&!o.closed&&this.calls.set(a,{...o,pinned:n?void 0:u,closed:n}),u}beforeTool(e){return this.tool(e,!1)}afterTool(e){return this.tool(e,!0)}};function ne(t,e){let{model:n,model_source:r,...s}=t;return"model"in t&&n!==e?.model&&(s.model_raw=n),"model_source"in t&&(s.model_source_raw=r),e&&(s.model=e.model,s.model_source=e.source,e.provider&&("provider"in s&&s.provider!==e.provider&&(s.provider_raw=s.provider),s.provider=e.provider),s.agent===void 0&&(s.agent=e.agent),s.message_id===void 0&&(s.message_id=e.messageID)),s}var re=class{constructor(e,n,r){this.transport=e;this.trace=n;this.config=r}models=new te;build(e,n,r){let s=this.trace.currentTrace(n);return Ot({name:e,traceId:s,fields:r,serviceVersion:this.config.serviceVersion})}async send(e){await this.transport.send(e)}async emit(e,n,r){await this.send(this.build(e,n,r))}chatMessage(e,n){this.models.chatMessage(e,n)}chatParams(e){this.models.chatParams(e)}async lifecycle(e){let n=b(e.properties)??{},r=De(e.type,n),s=this.models.event(e.type,n);e.type==="session.idle"&&await this.transport.flush(),await this.emit(`opencode.event.${e.type??"unknown"}`,r,ne({hook:"event",event_type:e.type,session_id:r,cwd:process.cwd(),...n},s))}toolBeforePayload(e,n){return this.build("opencode.tool.before",e.sessionID,ne({...Et("tool.execute.before",e),tool_input:n},this.models.beforeTool(e)))}async toolBefore(e,n,r){await this.send((0,Pt.attachGuard)(this.toolBeforePayload(e,n),r))}toolAfterPayload(e,n){let r=n.metadata??{};return this.build("opencode.tool.after",e.sessionID,ne({...Et("tool.execute.after",e),tool_input:e.args,title:n.title,tool_response:n.content!==void 0?n:n.output,attachments:n.attachments,exit:r.exit,truncated:r.truncated},this.models.afterTool(e)))}async toolAfter(e,n){await this.send(this.toolAfterPayload(e,n))}};function Et(t,e){return{hook:t,tool_name:e.tool,session_id:e.sessionID,tool_use_id:e.callID,cwd:process.cwd(),...e.model!==void 0?{model:e.model}:{},...e.modelID!==void 0?{modelID:e.modelID}:{},...e.providerID!==void 0?{providerID:e.providerID}:{},...e.agent!==void 0?{agent:e.agent}:{},...e.messageID!==void 0?{message_id:e.messageID}:{}}}function bt(t,e){process.stderr.write(`[pinta-opencode] ${t}: ${e?.message??String(e)}
26
+ `)}var kn="Pinta withheld this tool output because it violated an active policy.";function Ie(t,e){return async(...n)=>{try{await e(...n)}catch(r){bt(t,r)}}}var Rn=async(t,e)=>{let n=yt(e??{}),r=new J({endpoint:n.endpoint,headers:n.headers}),s=new Se.MemorySessionTraceManager,i=new re(r,s,n);async function a(o,u){let l=null;try{let d=u();l=await _t(d,n.guardEndpoint,{timeoutMs:n.guardTimeoutMs,token:n.relayToken,disabled:n.guardDisabled}),(0,St.attachGuard)(d,l),o==="tool.execute.after"&&l&&d.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({key:"pinta.guard.target",value:{stringValue:"tool_output"}}),l?.decision==="DENY"?r.defer(d):await i.send(d)}catch(d){bt(o,d)}return l}return{"chat.message":Ie("chat.message",async(o,u)=>{s.newTrace(o?.sessionID),i.chatMessage(o,u)}),"chat.params":Ie("chat.params",async(o,u)=>{i.chatParams(o)}),event:Ie("event",async o=>{o?.event&&await i.lifecycle(o.event)}),"tool.execute.before":async(o,u)=>{let l=await a("tool.execute.before",()=>i.toolBeforePayload(o,u?.args));if(l?.decision==="DENY")throw new Error(l.userMessage??l.reason??"guard_deny")},"tool.execute.after":async(o,u)=>{if((await a("tool.execute.after",()=>i.toolAfterPayload(o,u)))?.decision==="DENY")throw new Error(kn)}}},pr=Rn;export{Rn as PintaOpencode,pr as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinta-ai/pinta-opencode",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "OTLP forwarder + guard for opencode (in-process plugin)",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",
@@ -36,7 +36,7 @@
36
36
  "prepublishOnly": "npm run build"
37
37
  },
38
38
  "devDependencies": {
39
- "@pinta-ai/core": "^0.8.0",
39
+ "@pinta-ai/core": "^0.9.1",
40
40
  "@types/node": "^22.0.0",
41
41
  "esbuild": "0.24.2",
42
42
  "typescript": "^5.7.0",