@tryinget/pi-agent-vent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +78 -0
- package/README.md +188 -0
- package/biome.jsonc +43 -0
- package/docs/adr/2026-05-22-agent-vent-retention-delete-policy.md +65 -0
- package/docs/engineering.local.md +57 -0
- package/docs/project/2026-05-21-agent-vent-design.md +182 -0
- package/docs/project/2026-05-21-agent-vent-implementation-plan.md +66 -0
- package/docs/project/extension-sop.md +52 -0
- package/docs/project/product-posture.md +191 -0
- package/docs/project/trusted-publishing.md +53 -0
- package/docs/project/vision.md +70 -0
- package/examples/.gitkeep +0 -0
- package/extensions/agent-vent.ts +1384 -0
- package/package.json +95 -0
- package/policy/engineering-lane.json +22 -0
- package/policy/security-policy.json +10 -0
- package/prompts/implementation-planning.md +20 -0
- package/prompts/security-review.md +20 -0
- package/scripts/docs-list.sh +50 -0
- package/scripts/quality-gate.sh +168 -0
- package/scripts/release-artifact-check.mjs +221 -0
- package/scripts/release-check.sh +196 -0
- package/scripts/release-smoke-check.mjs +367 -0
- package/scripts/release-smoke.sh +90 -0
- package/scripts/validate-structure.mjs +248 -0
- package/scripts/validate-structure.sh +145 -0
- package/src/vent-store.js +2839 -0
- package/tests/.gitkeep +0 -0
- package/tests/agent-vent-extension.test.js +762 -0
- package/tests/release-artifact-check.test.js +168 -0
- package/tests/release-smoke-check.test.js +235 -0
- package/tests/vent-store.test.js +1972 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import agentVentExtension from "../extensions/agent-vent.ts";
|
|
7
|
+
import { createCurationEvent, createVentRecord, readReviewEvents } from "../src/vent-store.js";
|
|
8
|
+
|
|
9
|
+
function createMockPi() {
|
|
10
|
+
const tools = new Map();
|
|
11
|
+
const commands = new Map();
|
|
12
|
+
return {
|
|
13
|
+
tools,
|
|
14
|
+
commands,
|
|
15
|
+
api: {
|
|
16
|
+
registerTool(tool) {
|
|
17
|
+
tools.set(tool.name, tool);
|
|
18
|
+
},
|
|
19
|
+
registerCommand(name, command) {
|
|
20
|
+
commands.set(name, command);
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test("extension registers agent_vent tool and command aliases", () => {
|
|
27
|
+
const pi = createMockPi();
|
|
28
|
+
agentVentExtension(pi.api);
|
|
29
|
+
|
|
30
|
+
assert.equal(pi.tools.has("agent_vent"), true);
|
|
31
|
+
assert.equal(pi.commands.has("agent_vent"), true);
|
|
32
|
+
assert.equal(pi.commands.has("agent-vent"), true);
|
|
33
|
+
assert.match(pi.tools.get("agent_vent").description, /frustration/i);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("agent_vent tool schema stays aligned with retention candidate and compare command contracts", () => {
|
|
37
|
+
const pi = createMockPi();
|
|
38
|
+
agentVentExtension(pi.api);
|
|
39
|
+
const schemaText = JSON.stringify(pi.tools.get("agent_vent").parameters);
|
|
40
|
+
|
|
41
|
+
assert.match(schemaText, /"compare"/);
|
|
42
|
+
assert.match(schemaText, /"candidates"/);
|
|
43
|
+
assert.match(schemaText, /"history"/);
|
|
44
|
+
assert.match(schemaText, /retentionCandidateState/);
|
|
45
|
+
assert.match(schemaText, /"reviewed"/);
|
|
46
|
+
assert.match(schemaText, /"all"/);
|
|
47
|
+
assert.match(schemaText, /candidates, history, preview, archive, or restore/);
|
|
48
|
+
assert.match(schemaText, /outcomes, compare, export, or retention planning/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("agent_vent records minimized local diagnostics without external authority claims", async () => {
|
|
52
|
+
const pi = createMockPi();
|
|
53
|
+
agentVentExtension(pi.api);
|
|
54
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-extension-"));
|
|
55
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
56
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
57
|
+
try {
|
|
58
|
+
const tool = pi.tools.get("agent_vent");
|
|
59
|
+
const result = await tool.execute(
|
|
60
|
+
"tool-call-1",
|
|
61
|
+
{
|
|
62
|
+
action: "record",
|
|
63
|
+
summary: "Repeated reload loses tool registration",
|
|
64
|
+
category: "tool-failure",
|
|
65
|
+
severity: "high",
|
|
66
|
+
tool: "pi reload",
|
|
67
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
68
|
+
tags: ["reload"],
|
|
69
|
+
},
|
|
70
|
+
undefined,
|
|
71
|
+
undefined,
|
|
72
|
+
{
|
|
73
|
+
cwd: "/repo",
|
|
74
|
+
sessionManager: { getSessionFile: () => "/tmp/session.jsonl" },
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
assert.match(result.content[0].text, /Recorded agent vent/);
|
|
79
|
+
assert.equal(result.details.record.category, "tool_failure");
|
|
80
|
+
assert.equal(result.details.record.context.source, "agent_vent_tool");
|
|
81
|
+
assert.equal(result.details.record.context.cwd, "/repo");
|
|
82
|
+
assert.equal(result.details.record.context.sessionFile, "session.jsonl");
|
|
83
|
+
assert.equal(fs.existsSync(path.join(dir, "vents.jsonl")), true);
|
|
84
|
+
|
|
85
|
+
await tool.execute(
|
|
86
|
+
"tool-call-1b",
|
|
87
|
+
{
|
|
88
|
+
action: "record",
|
|
89
|
+
summary: "Reload registration duplicate wording",
|
|
90
|
+
category: "tool-failure",
|
|
91
|
+
severity: "medium",
|
|
92
|
+
recurrenceKey: "reload-registration-dupe",
|
|
93
|
+
tool: "pi reload",
|
|
94
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
95
|
+
tags: ["reload"],
|
|
96
|
+
},
|
|
97
|
+
undefined,
|
|
98
|
+
undefined,
|
|
99
|
+
{
|
|
100
|
+
cwd: "/repo",
|
|
101
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const curateResult = await tool.execute(
|
|
106
|
+
"tool-call-1c",
|
|
107
|
+
{
|
|
108
|
+
action: "curate",
|
|
109
|
+
curationAction: "merge",
|
|
110
|
+
sourceRecurrenceKey: "tool_failure:reload-registration-dupe",
|
|
111
|
+
targetRecurrenceKey: "tool_failure:repeated-reload-loses-tool-registration",
|
|
112
|
+
curationNote: "same local issue token=abc123",
|
|
113
|
+
},
|
|
114
|
+
undefined,
|
|
115
|
+
undefined,
|
|
116
|
+
{
|
|
117
|
+
cwd: "/repo",
|
|
118
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
assert.match(curateResult.content[0].text, /local diagnostic curation projection only/);
|
|
122
|
+
assert.match(curateResult.details.curationEvent.note, /token=\[REDACTED\]/);
|
|
123
|
+
|
|
124
|
+
const reviewResult = await tool.execute(
|
|
125
|
+
"tool-call-2",
|
|
126
|
+
{ action: "review" },
|
|
127
|
+
undefined,
|
|
128
|
+
undefined,
|
|
129
|
+
{
|
|
130
|
+
cwd: "/repo",
|
|
131
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
assert.match(reviewResult.content[0].text, /Agent vent review queue/);
|
|
135
|
+
assert.match(reviewResult.content[0].text, /no AK task, GitHub issue, incident, evidence/);
|
|
136
|
+
assert.equal(reviewResult.details.reviewQueue.items[0].reviewState, "new");
|
|
137
|
+
assert.equal(reviewResult.details.reviewQueue.groupCount, 1);
|
|
138
|
+
assert.equal(reviewResult.details.reviewQueue.items[0].count, 2);
|
|
139
|
+
assert.match(reviewResult.content[0].text, /review show/);
|
|
140
|
+
|
|
141
|
+
const reviewDetailResult = await tool.execute(
|
|
142
|
+
"tool-call-2b",
|
|
143
|
+
{
|
|
144
|
+
action: "review",
|
|
145
|
+
recurrenceKey: "tool_failure:reload-registration-dupe",
|
|
146
|
+
limit: 1000,
|
|
147
|
+
},
|
|
148
|
+
undefined,
|
|
149
|
+
undefined,
|
|
150
|
+
{
|
|
151
|
+
cwd: "/repo",
|
|
152
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
assert.match(reviewDetailResult.content[0].text, /Agent vent review detail/);
|
|
156
|
+
assert.match(
|
|
157
|
+
reviewDetailResult.content[0].text,
|
|
158
|
+
/Requested key resolved through local curation/,
|
|
159
|
+
);
|
|
160
|
+
assert.equal(reviewDetailResult.details.reviewDetail.group.count, 2);
|
|
161
|
+
assert.equal(reviewDetailResult.details.reviewDetail.samples.length, 2);
|
|
162
|
+
|
|
163
|
+
const facetsResult = await tool.execute(
|
|
164
|
+
"tool-call-2c",
|
|
165
|
+
{ action: "facets" },
|
|
166
|
+
undefined,
|
|
167
|
+
undefined,
|
|
168
|
+
{
|
|
169
|
+
cwd: "/repo",
|
|
170
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
assert.match(facetsResult.content[0].text, /Agent vent facets/);
|
|
174
|
+
assert.match(facetsResult.content[0].text, /not owner routing/);
|
|
175
|
+
assert.equal(facetsResult.details.facets.records.tools[0].name, "pi-reload");
|
|
176
|
+
assert.equal(facetsResult.details.facets.records.packages[0].count, 2);
|
|
177
|
+
|
|
178
|
+
const filteredReviewResult = await tool.execute(
|
|
179
|
+
"tool-call-2d",
|
|
180
|
+
{
|
|
181
|
+
action: "review",
|
|
182
|
+
category: "tool_failure",
|
|
183
|
+
tags: ["reload"],
|
|
184
|
+
tool: "pi reload",
|
|
185
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
186
|
+
},
|
|
187
|
+
undefined,
|
|
188
|
+
undefined,
|
|
189
|
+
{
|
|
190
|
+
cwd: "/repo",
|
|
191
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
assert.match(filteredReviewResult.content[0].text, /Filters:/);
|
|
195
|
+
assert.match(filteredReviewResult.content[0].text, /not owner routing/);
|
|
196
|
+
assert.equal(filteredReviewResult.details.reviewQueue.matchingGroupCount, 1);
|
|
197
|
+
assert.equal(filteredReviewResult.details.reviewQueue.filters.tool, "pi-reload");
|
|
198
|
+
|
|
199
|
+
const setReviewResult = await tool.execute(
|
|
200
|
+
"tool-call-3",
|
|
201
|
+
{
|
|
202
|
+
action: "set_review",
|
|
203
|
+
recurrenceKey: "tool_failure:reload-registration-dupe",
|
|
204
|
+
reviewState: "acknowledged",
|
|
205
|
+
reviewNote: "Operator acknowledged token=abc123 locally",
|
|
206
|
+
},
|
|
207
|
+
undefined,
|
|
208
|
+
undefined,
|
|
209
|
+
{
|
|
210
|
+
cwd: "/repo",
|
|
211
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
assert.match(setReviewResult.content[0].text, /local diagnostic review state only/);
|
|
215
|
+
assert.equal(
|
|
216
|
+
setReviewResult.details.reviewEvent.recurrenceKey,
|
|
217
|
+
"tool_failure:repeated-reload-loses-tool-registration",
|
|
218
|
+
);
|
|
219
|
+
assert.equal(setReviewResult.details.reviewEvent.state, "acknowledged");
|
|
220
|
+
assert.match(setReviewResult.details.reviewEvent.note, /token=\[REDACTED\]/);
|
|
221
|
+
|
|
222
|
+
const outcomesResult = await tool.execute(
|
|
223
|
+
"tool-call-3b",
|
|
224
|
+
{
|
|
225
|
+
action: "outcomes",
|
|
226
|
+
reviewState: "acknowledged",
|
|
227
|
+
category: "tool_failure",
|
|
228
|
+
tags: ["reload"],
|
|
229
|
+
tool: "pi reload",
|
|
230
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
231
|
+
},
|
|
232
|
+
undefined,
|
|
233
|
+
undefined,
|
|
234
|
+
{
|
|
235
|
+
cwd: "/repo",
|
|
236
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
assert.match(outcomesResult.content[0].text, /Agent vent review outcomes/);
|
|
240
|
+
assert.match(outcomesResult.content[0].text, /acknowledged: 1 group/);
|
|
241
|
+
assert.match(outcomesResult.content[0].text, /export this outcome bucket/);
|
|
242
|
+
assert.match(outcomesResult.content[0].text, /not owner routing/);
|
|
243
|
+
assert.equal(outcomesResult.details.reviewOutcomes.counts.acknowledged, 1);
|
|
244
|
+
assert.equal(outcomesResult.details.reviewOutcomes.filters.tool, "pi-reload");
|
|
245
|
+
|
|
246
|
+
const compareResult = await tool.execute(
|
|
247
|
+
"tool-call-3c",
|
|
248
|
+
{
|
|
249
|
+
action: "compare",
|
|
250
|
+
category: "tool_failure",
|
|
251
|
+
tags: ["reload"],
|
|
252
|
+
tool: "pi reload",
|
|
253
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
254
|
+
},
|
|
255
|
+
undefined,
|
|
256
|
+
undefined,
|
|
257
|
+
{
|
|
258
|
+
cwd: "/repo",
|
|
259
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
assert.match(compareResult.content[0].text, /Agent vent review comparison/);
|
|
263
|
+
assert.match(compareResult.content[0].text, /acknowledged: groups=1/);
|
|
264
|
+
assert.match(
|
|
265
|
+
compareResult.content[0].text,
|
|
266
|
+
/retention planning: \/agent_vent retention candidates acknowledged category=tool_failure tool=pi-reload package=tryinget-pi-agent-vent tag=reload/,
|
|
267
|
+
);
|
|
268
|
+
assert.match(
|
|
269
|
+
compareResult.content[0].text,
|
|
270
|
+
/export bucket: \/agent_vent export markdown acknowledged category=tool_failure tool=pi-reload package=tryinget-pi-agent-vent tag=reload/,
|
|
271
|
+
);
|
|
272
|
+
assert.match(
|
|
273
|
+
compareResult.content[0].text,
|
|
274
|
+
/intentionally emits no archive or restore confirmation tokens/,
|
|
275
|
+
);
|
|
276
|
+
assert.doesNotMatch(
|
|
277
|
+
compareResult.content[0].text,
|
|
278
|
+
/archive:[a-f0-9]|was filed|owner was assigned/,
|
|
279
|
+
);
|
|
280
|
+
assert.equal(compareResult.details.reviewComparison.totals.acknowledged.groups, 1);
|
|
281
|
+
assert.equal(compareResult.details.reviewComparison.filters.tool, "pi-reload");
|
|
282
|
+
|
|
283
|
+
const retentionCandidatesResult = await tool.execute(
|
|
284
|
+
"tool-call-3d",
|
|
285
|
+
{
|
|
286
|
+
action: "retention",
|
|
287
|
+
retentionAction: "candidates",
|
|
288
|
+
reviewState: "acknowledged",
|
|
289
|
+
category: "tool_failure",
|
|
290
|
+
tags: ["reload"],
|
|
291
|
+
tool: "pi reload",
|
|
292
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
293
|
+
},
|
|
294
|
+
undefined,
|
|
295
|
+
undefined,
|
|
296
|
+
{
|
|
297
|
+
cwd: "/repo",
|
|
298
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
assert.match(retentionCandidatesResult.content[0].text, /Agent vent retention candidates/);
|
|
302
|
+
assert.match(retentionCandidatesResult.content[0].text, /does not archive records/);
|
|
303
|
+
assert.match(retentionCandidatesResult.content[0].text, /retention preview/);
|
|
304
|
+
assert.doesNotMatch(retentionCandidatesResult.content[0].text, /Confirmation token: archive:/);
|
|
305
|
+
assert.equal(retentionCandidatesResult.details.retention.candidateCount, 1);
|
|
306
|
+
assert.equal(retentionCandidatesResult.details.retention.filters.tool, "pi-reload");
|
|
307
|
+
|
|
308
|
+
const allRetentionCandidatesResult = await tool.execute(
|
|
309
|
+
"tool-call-3e",
|
|
310
|
+
{
|
|
311
|
+
action: "retention",
|
|
312
|
+
retentionAction: "candidates",
|
|
313
|
+
retentionCandidateState: "all",
|
|
314
|
+
},
|
|
315
|
+
undefined,
|
|
316
|
+
undefined,
|
|
317
|
+
{
|
|
318
|
+
cwd: "/repo",
|
|
319
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
assert.equal(allRetentionCandidatesResult.details.retention.stateFilter, "all");
|
|
323
|
+
|
|
324
|
+
const draftResult = await tool.execute(
|
|
325
|
+
"tool-call-4",
|
|
326
|
+
{
|
|
327
|
+
action: "draft",
|
|
328
|
+
draftTarget: "github_issue",
|
|
329
|
+
recurrenceKey: "tool_failure:repeated-reload-loses-tool-registration",
|
|
330
|
+
limit: 2,
|
|
331
|
+
},
|
|
332
|
+
undefined,
|
|
333
|
+
undefined,
|
|
334
|
+
{
|
|
335
|
+
cwd: "/repo",
|
|
336
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
assert.match(draftResult.content[0].text, /Draft-only GitHub issue text/);
|
|
340
|
+
assert.match(draftResult.content[0].text, /No AK task, GitHub issue, incident, evidence/);
|
|
341
|
+
assert.match(draftResult.content[0].text, /Local diagnostic facets \(not owner routing\)/);
|
|
342
|
+
assert.match(draftResult.content[0].text, /Tools: pi-reload/);
|
|
343
|
+
assert.equal(draftResult.details.draft.samples.length, 2);
|
|
344
|
+
assert.equal(draftResult.details.draft.group.count, 2);
|
|
345
|
+
|
|
346
|
+
const statsResult = await tool.execute(
|
|
347
|
+
"tool-call-5",
|
|
348
|
+
{ action: "stats" },
|
|
349
|
+
undefined,
|
|
350
|
+
undefined,
|
|
351
|
+
{
|
|
352
|
+
cwd: "/repo",
|
|
353
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
assert.match(statsResult.content[0].text, /Agent vent lifecycle stats/);
|
|
357
|
+
assert.equal(statsResult.details.lifecycle.counts.vents, 2);
|
|
358
|
+
assert.equal(statsResult.details.lifecycle.counts.curationEvents, 1);
|
|
359
|
+
|
|
360
|
+
const exportResult = await tool.execute(
|
|
361
|
+
"tool-call-6",
|
|
362
|
+
{
|
|
363
|
+
action: "export",
|
|
364
|
+
exportFormat: "json",
|
|
365
|
+
category: "tool_failure",
|
|
366
|
+
tags: ["reload"],
|
|
367
|
+
tool: "pi reload",
|
|
368
|
+
packageName: "@tryinget/pi-agent-vent",
|
|
369
|
+
},
|
|
370
|
+
undefined,
|
|
371
|
+
undefined,
|
|
372
|
+
{
|
|
373
|
+
cwd: "/repo",
|
|
374
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
const exported = JSON.parse(exportResult.content[0].text);
|
|
378
|
+
assert.equal(exported.counts.reviewStates.acknowledged, 1);
|
|
379
|
+
assert.equal(exported.scope.hasFilters, true);
|
|
380
|
+
assert.equal(exported.scope.matchingGroups, 1);
|
|
381
|
+
assert.equal(exported.scope.filters.tool, "pi-reload");
|
|
382
|
+
assert.match(exportResult.content[0].text, /Local diagnostic projection only/);
|
|
383
|
+
|
|
384
|
+
const pathResult = await tool.execute("tool-call-7", { action: "path" }, undefined, undefined, {
|
|
385
|
+
cwd: "/repo",
|
|
386
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
387
|
+
});
|
|
388
|
+
assert.match(
|
|
389
|
+
pathResult.content[0].text,
|
|
390
|
+
/curation projections are local diagnostics, not tasks, issues, incidents, evidence, telemetry, or ASC\/self state/,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const previewResult = await tool.execute(
|
|
394
|
+
"tool-call-8",
|
|
395
|
+
{
|
|
396
|
+
action: "retention",
|
|
397
|
+
retentionAction: "preview",
|
|
398
|
+
recurrenceKey: "tool_failure:repeated-reload-loses-tool-registration",
|
|
399
|
+
},
|
|
400
|
+
undefined,
|
|
401
|
+
undefined,
|
|
402
|
+
{
|
|
403
|
+
cwd: "/repo",
|
|
404
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
assert.match(previewResult.content[0].text, /Confirmation token: archive:/);
|
|
408
|
+
assert.equal(previewResult.details.retention.archivable, true);
|
|
409
|
+
|
|
410
|
+
const archiveResult = await tool.execute(
|
|
411
|
+
"tool-call-9",
|
|
412
|
+
{
|
|
413
|
+
action: "retention",
|
|
414
|
+
retentionAction: "archive",
|
|
415
|
+
recurrenceKey: "tool_failure:repeated-reload-loses-tool-registration",
|
|
416
|
+
confirmationToken: previewResult.details.retention.confirmationToken,
|
|
417
|
+
retentionNote: "archive locally token=abc123",
|
|
418
|
+
},
|
|
419
|
+
undefined,
|
|
420
|
+
undefined,
|
|
421
|
+
{
|
|
422
|
+
cwd: "/repo",
|
|
423
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
assert.match(archiveResult.content[0].text, /Archived 2 local diagnostic record/);
|
|
427
|
+
assert.match(archiveResult.content[0].text, /No AK task, GitHub issue, incident, evidence/);
|
|
428
|
+
assert.equal(fs.existsSync(archiveResult.details.retention.backupPath), true);
|
|
429
|
+
|
|
430
|
+
const historyResult = await tool.execute(
|
|
431
|
+
"tool-call-9b",
|
|
432
|
+
{
|
|
433
|
+
action: "retention",
|
|
434
|
+
retentionAction: "history",
|
|
435
|
+
},
|
|
436
|
+
undefined,
|
|
437
|
+
undefined,
|
|
438
|
+
{
|
|
439
|
+
cwd: "/repo",
|
|
440
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
assert.match(historyResult.content[0].text, /Agent vent retention history/);
|
|
444
|
+
assert.match(
|
|
445
|
+
historyResult.content[0].text,
|
|
446
|
+
/rollback candidate: \/agent_vent retention restore/,
|
|
447
|
+
);
|
|
448
|
+
assert.match(historyResult.content[0].text, /read-only receipt projection/i);
|
|
449
|
+
assert.equal(
|
|
450
|
+
historyResult.details.retention.items[0].restoreConfirmationToken,
|
|
451
|
+
archiveResult.details.retention.restoreConfirmationToken,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const restoreResult = await tool.execute(
|
|
455
|
+
"tool-call-10",
|
|
456
|
+
{
|
|
457
|
+
action: "retention",
|
|
458
|
+
retentionAction: "restore",
|
|
459
|
+
backupPath: archiveResult.details.retention.backupPath,
|
|
460
|
+
confirmationToken: archiveResult.details.retention.restoreConfirmationToken,
|
|
461
|
+
},
|
|
462
|
+
undefined,
|
|
463
|
+
undefined,
|
|
464
|
+
{
|
|
465
|
+
cwd: "/repo",
|
|
466
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
assert.match(restoreResult.content[0].text, /Restored local diagnostic backup/);
|
|
470
|
+
} finally {
|
|
471
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
472
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
473
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("agent_vent command rejects unknown review filter keys without creating stores", async () => {
|
|
478
|
+
const pi = createMockPi();
|
|
479
|
+
agentVentExtension(pi.api);
|
|
480
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-command-filter-"));
|
|
481
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
482
|
+
const oldLog = console.log;
|
|
483
|
+
const messages = [];
|
|
484
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
485
|
+
console.log = (message) => messages.push(String(message));
|
|
486
|
+
try {
|
|
487
|
+
const unsafeTarget = path.join(dir, "unsafe-target.jsonl");
|
|
488
|
+
fs.writeFileSync(unsafeTarget, "", "utf8");
|
|
489
|
+
fs.symlinkSync(unsafeTarget, path.join(dir, "vents.jsonl"));
|
|
490
|
+
await pi.commands.get("agent_vent").handler("review owner=github", { hasUI: false });
|
|
491
|
+
assert.match(messages[0], /Unknown \/agent_vent review filter\(s\): owner/);
|
|
492
|
+
assert.match(messages[0], /category=bug/);
|
|
493
|
+
assert.doesNotMatch(messages[0], /symlink/);
|
|
494
|
+
await pi.commands.get("agent_vent").handler("review owner=", { hasUI: false });
|
|
495
|
+
assert.match(messages[1], /Unknown \/agent_vent review filter\(s\): owner/);
|
|
496
|
+
assert.doesNotMatch(messages[1], /symlink/);
|
|
497
|
+
await pi.commands.get("agent_vent").handler("review category=bgu", { hasUI: false });
|
|
498
|
+
assert.match(messages[2], /Invalid \/agent_vent review filter value\(s\): category=bgu/);
|
|
499
|
+
await pi.commands.get("agent_vent").handler("review show", { hasUI: false });
|
|
500
|
+
assert.match(messages[3], /Usage: \/agent_vent review show <recurrenceKey> \[limit\]/);
|
|
501
|
+
await pi.commands.get("agent_vent").handler("review set resolved bug:legacy", { hasUI: false });
|
|
502
|
+
assert.match(messages[4], /Invalid \/agent_vent review state: resolved/);
|
|
503
|
+
await pi.commands.get("agent_vent").handler("outcomes owner=github", { hasUI: false });
|
|
504
|
+
assert.match(messages[5], /Unknown \/agent_vent outcomes filter\(s\): owner/);
|
|
505
|
+
assert.doesNotMatch(messages[5], /symlink/);
|
|
506
|
+
await pi.commands.get("agent_vent").handler("outcomes owner=", { hasUI: false });
|
|
507
|
+
assert.match(messages[6], /Unknown \/agent_vent outcomes filter\(s\): owner/);
|
|
508
|
+
assert.doesNotMatch(messages[6], /symlink/);
|
|
509
|
+
await pi.commands.get("agent_vent").handler("outcomes resolved", { hasUI: false });
|
|
510
|
+
assert.match(messages[7], /Invalid \/agent_vent outcomes state: resolved/);
|
|
511
|
+
await pi.commands.get("agent_vent").handler("compare owner=", { hasUI: false });
|
|
512
|
+
assert.match(messages[8], /Unknown \/agent_vent compare filter\(s\): owner/);
|
|
513
|
+
assert.doesNotMatch(messages[8], /symlink/);
|
|
514
|
+
await pi.commands.get("agent_vent").handler("compare resolved", { hasUI: false });
|
|
515
|
+
assert.match(messages[9], /Invalid \/agent_vent compare argument: resolved/);
|
|
516
|
+
assert.doesNotMatch(messages[9], /symlink/);
|
|
517
|
+
await pi.commands.get("agent_vent").handler("compare new", { hasUI: false });
|
|
518
|
+
assert.match(messages[10], /Invalid \/agent_vent compare argument: new/);
|
|
519
|
+
assert.doesNotMatch(messages[10], /symlink/);
|
|
520
|
+
await pi.commands.get("agent_vent").handler("retention candidates owner=", { hasUI: false });
|
|
521
|
+
assert.match(messages[11], /Unknown \/agent_vent retention candidates filter\(s\): owner/);
|
|
522
|
+
assert.doesNotMatch(messages[11], /symlink/);
|
|
523
|
+
await pi.commands.get("agent_vent").handler("retention candidates resolved", { hasUI: false });
|
|
524
|
+
assert.match(messages[12], /Invalid \/agent_vent retention candidates state: resolved/);
|
|
525
|
+
assert.doesNotMatch(messages[12], /symlink/);
|
|
526
|
+
await pi.commands.get("agent_vent").handler("export owner=", { hasUI: false });
|
|
527
|
+
assert.match(messages[13], /Unknown \/agent_vent export filter\(s\): owner/);
|
|
528
|
+
assert.doesNotMatch(messages[13], /symlink/);
|
|
529
|
+
await pi.commands.get("agent_vent").handler("export category=bgu", { hasUI: false });
|
|
530
|
+
assert.match(messages[14], /Invalid \/agent_vent export filter value\(s\): category=bgu/);
|
|
531
|
+
assert.doesNotMatch(messages[14], /symlink/);
|
|
532
|
+
await pi.commands.get("agent_vent").handler("export tag=", { hasUI: false });
|
|
533
|
+
assert.match(messages[15], /Invalid \/agent_vent export filter value\(s\): tag=/);
|
|
534
|
+
assert.doesNotMatch(messages[15], /symlink/);
|
|
535
|
+
await pi.commands.get("agent_vent").handler("retention history owner=", { hasUI: false });
|
|
536
|
+
assert.match(messages[16], /Invalid \/agent_vent retention history argument: owner=/);
|
|
537
|
+
assert.doesNotMatch(messages[16], /symlink/);
|
|
538
|
+
await pi.commands.get("agent_vent").handler("retention history", { hasUI: false });
|
|
539
|
+
assert.match(messages[17], /No agent vent retention events found yet/);
|
|
540
|
+
assert.doesNotMatch(messages[17], /symlink/);
|
|
541
|
+
} finally {
|
|
542
|
+
console.log = oldLog;
|
|
543
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
544
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
545
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test("agent_vent tool retention history does not read active vent store", async () => {
|
|
550
|
+
const pi = createMockPi();
|
|
551
|
+
agentVentExtension(pi.api);
|
|
552
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-tool-history-"));
|
|
553
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
554
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
555
|
+
try {
|
|
556
|
+
const unsafeTarget = path.join(dir, "unsafe-target.jsonl");
|
|
557
|
+
fs.writeFileSync(unsafeTarget, "", "utf8");
|
|
558
|
+
fs.symlinkSync(unsafeTarget, path.join(dir, "vents.jsonl"));
|
|
559
|
+
const result = await pi.tools
|
|
560
|
+
.get("agent_vent")
|
|
561
|
+
.execute(
|
|
562
|
+
"tool-call-history-empty",
|
|
563
|
+
{ action: "retention", retentionAction: "history" },
|
|
564
|
+
undefined,
|
|
565
|
+
undefined,
|
|
566
|
+
{
|
|
567
|
+
cwd: "/repo",
|
|
568
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
569
|
+
},
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
assert.match(result.content[0].text, /No agent vent retention events found yet/);
|
|
573
|
+
assert.doesNotMatch(result.content[0].text, /symlink/);
|
|
574
|
+
assert.equal(result.details.retention.totalEvents, 0);
|
|
575
|
+
} finally {
|
|
576
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
577
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
578
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("agent_vent command round-trips quoted legacy recurrence keys", async () => {
|
|
583
|
+
const pi = createMockPi();
|
|
584
|
+
agentVentExtension(pi.api);
|
|
585
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-legacy-key-"));
|
|
586
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
587
|
+
const oldLog = console.log;
|
|
588
|
+
const messages = [];
|
|
589
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
590
|
+
console.log = (message) => messages.push(String(message));
|
|
591
|
+
try {
|
|
592
|
+
const legacy = {
|
|
593
|
+
...createVentRecord({ summary: "Legacy spaced key", category: "bug" }),
|
|
594
|
+
recurrenceKey: 'bug:legacy key "quoted"',
|
|
595
|
+
};
|
|
596
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
597
|
+
fs.writeFileSync(path.join(dir, "vents.jsonl"), `${JSON.stringify(legacy)}\n`, "utf8");
|
|
598
|
+
await pi.commands
|
|
599
|
+
.get("agent_vent")
|
|
600
|
+
.handler('review set acknowledged "bug:legacy key \\"quoted\\"" seen locally', {
|
|
601
|
+
hasUI: false,
|
|
602
|
+
});
|
|
603
|
+
assert.match(messages[0], /Set local review state/);
|
|
604
|
+
assert.equal(
|
|
605
|
+
readReviewEvents(path.join(dir, "review-events.jsonl")).events[0].recurrenceKey,
|
|
606
|
+
legacy.recurrenceKey,
|
|
607
|
+
);
|
|
608
|
+
} finally {
|
|
609
|
+
console.log = oldLog;
|
|
610
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
611
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
612
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("agent_vent record feedback summarizes resolved curated groups", async () => {
|
|
617
|
+
const pi = createMockPi();
|
|
618
|
+
agentVentExtension(pi.api);
|
|
619
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-record-curated-feedback-"));
|
|
620
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
621
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
622
|
+
try {
|
|
623
|
+
const primary = createVentRecord({
|
|
624
|
+
summary: "Primary curated record group",
|
|
625
|
+
category: "bug",
|
|
626
|
+
recurrenceKey: "primary-record",
|
|
627
|
+
});
|
|
628
|
+
const duplicate = createVentRecord({
|
|
629
|
+
summary: "Duplicate curated record group",
|
|
630
|
+
category: "bug",
|
|
631
|
+
recurrenceKey: "duplicate-record",
|
|
632
|
+
});
|
|
633
|
+
const curation = createCurationEvent({
|
|
634
|
+
action: "merge",
|
|
635
|
+
sourceRecurrenceKey: duplicate.recurrenceKey,
|
|
636
|
+
targetRecurrenceKey: primary.recurrenceKey,
|
|
637
|
+
});
|
|
638
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
639
|
+
fs.writeFileSync(
|
|
640
|
+
path.join(dir, "vents.jsonl"),
|
|
641
|
+
`${JSON.stringify(primary)}\n${JSON.stringify(duplicate)}\n`,
|
|
642
|
+
"utf8",
|
|
643
|
+
);
|
|
644
|
+
fs.writeFileSync(
|
|
645
|
+
path.join(dir, "curation-events.jsonl"),
|
|
646
|
+
`${JSON.stringify(curation)}\n`,
|
|
647
|
+
"utf8",
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const result = await pi.tools.get("agent_vent").execute(
|
|
651
|
+
"tool-call-record-curated-feedback",
|
|
652
|
+
{
|
|
653
|
+
action: "record",
|
|
654
|
+
summary: "Third curated record group",
|
|
655
|
+
category: "bug",
|
|
656
|
+
recurrenceKey: "duplicate-record",
|
|
657
|
+
},
|
|
658
|
+
undefined,
|
|
659
|
+
undefined,
|
|
660
|
+
{
|
|
661
|
+
cwd: "/repo",
|
|
662
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
assert.equal(result.details.recurrenceGroup.recurrenceKey, primary.recurrenceKey);
|
|
667
|
+
assert.equal(result.details.recurrenceGroup.count, 3);
|
|
668
|
+
} finally {
|
|
669
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
670
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
671
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
test("agent_vent command review set accepts curated source keys", async () => {
|
|
676
|
+
const pi = createMockPi();
|
|
677
|
+
agentVentExtension(pi.api);
|
|
678
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-command-curated-review-"));
|
|
679
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
680
|
+
const oldLog = console.log;
|
|
681
|
+
const messages = [];
|
|
682
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
683
|
+
console.log = (message) => messages.push(String(message));
|
|
684
|
+
try {
|
|
685
|
+
const primary = createVentRecord({
|
|
686
|
+
summary: "Primary curated command group",
|
|
687
|
+
category: "bug",
|
|
688
|
+
recurrenceKey: "primary-command",
|
|
689
|
+
});
|
|
690
|
+
const duplicate = createVentRecord({
|
|
691
|
+
summary: "Duplicate curated command group",
|
|
692
|
+
category: "bug",
|
|
693
|
+
recurrenceKey: "duplicate-command",
|
|
694
|
+
});
|
|
695
|
+
const curation = createCurationEvent({
|
|
696
|
+
action: "merge",
|
|
697
|
+
sourceRecurrenceKey: duplicate.recurrenceKey,
|
|
698
|
+
targetRecurrenceKey: primary.recurrenceKey,
|
|
699
|
+
});
|
|
700
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
701
|
+
fs.writeFileSync(
|
|
702
|
+
path.join(dir, "vents.jsonl"),
|
|
703
|
+
`${JSON.stringify(primary)}\n${JSON.stringify(duplicate)}\n`,
|
|
704
|
+
"utf8",
|
|
705
|
+
);
|
|
706
|
+
fs.writeFileSync(
|
|
707
|
+
path.join(dir, "curation-events.jsonl"),
|
|
708
|
+
`${JSON.stringify(curation)}\n`,
|
|
709
|
+
"utf8",
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
await pi.commands
|
|
713
|
+
.get("agent_vent")
|
|
714
|
+
.handler(`review set acknowledged ${duplicate.recurrenceKey} seen via source key`, {
|
|
715
|
+
hasUI: false,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
assert.match(messages[0], /Set local review state/);
|
|
719
|
+
assert.equal(
|
|
720
|
+
readReviewEvents(path.join(dir, "review-events.jsonl")).events[0].recurrenceKey,
|
|
721
|
+
primary.recurrenceKey,
|
|
722
|
+
);
|
|
723
|
+
} finally {
|
|
724
|
+
console.log = oldLog;
|
|
725
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
726
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
727
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("agent_vent fails closed for unknown review groups", async () => {
|
|
732
|
+
const pi = createMockPi();
|
|
733
|
+
agentVentExtension(pi.api);
|
|
734
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-extension-"));
|
|
735
|
+
const oldDir = process.env.PI_AGENT_VENT_DIR;
|
|
736
|
+
process.env.PI_AGENT_VENT_DIR = dir;
|
|
737
|
+
try {
|
|
738
|
+
const tool = pi.tools.get("agent_vent");
|
|
739
|
+
await assert.rejects(
|
|
740
|
+
() =>
|
|
741
|
+
tool.execute(
|
|
742
|
+
"tool-call-unknown-review",
|
|
743
|
+
{
|
|
744
|
+
action: "set_review",
|
|
745
|
+
recurrenceKey: "bug:no-such-group",
|
|
746
|
+
reviewState: "dismissed",
|
|
747
|
+
},
|
|
748
|
+
undefined,
|
|
749
|
+
undefined,
|
|
750
|
+
{
|
|
751
|
+
cwd: "/repo",
|
|
752
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
753
|
+
},
|
|
754
|
+
),
|
|
755
|
+
/unknown recurrence group/,
|
|
756
|
+
);
|
|
757
|
+
} finally {
|
|
758
|
+
if (oldDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
759
|
+
else process.env.PI_AGENT_VENT_DIR = oldDir;
|
|
760
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
761
|
+
}
|
|
762
|
+
});
|