@m8i-51/shoal 0.1.12 → 0.1.14
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.
|
@@ -259,7 +259,7 @@ describe("computeWeightedSummary", () => {
|
|
|
259
259
|
|
|
260
260
|
// updateCoverage が 31件目を追加してトリムすることを確認
|
|
261
261
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
262
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ entries })
|
|
262
|
+
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ entries }));
|
|
263
263
|
|
|
264
264
|
// computeWeightedSummary は entries をそのまま使うだけなので、
|
|
265
265
|
// 35件あってもエラーにならないことを確認
|
|
@@ -47,12 +47,14 @@ function makeAgentLog(overrides: Partial<AgentLog> = {}): AgentLog {
|
|
|
47
47
|
return {
|
|
48
48
|
agentId: "a1",
|
|
49
49
|
agentName: "Alice",
|
|
50
|
-
agentType: "
|
|
50
|
+
agentType: "explorer",
|
|
51
51
|
role: "tester",
|
|
52
52
|
status: "completed",
|
|
53
53
|
iterations: 3,
|
|
54
|
+
actions: [],
|
|
54
55
|
issuesPosted: [],
|
|
55
56
|
regressionChecks: [],
|
|
57
|
+
error: null,
|
|
56
58
|
startedAt: "2026-04-27T00:01:00.000Z",
|
|
57
59
|
completedAt: "2026-04-27T00:03:00.000Z",
|
|
58
60
|
...overrides,
|
|
@@ -172,7 +174,7 @@ describe("generateReport", () => {
|
|
|
172
174
|
|
|
173
175
|
it("regression checks がある場合 Progress セクションが表示される", () => {
|
|
174
176
|
const checks: RegressionCheck[] = [
|
|
175
|
-
{ issueNumber: 42, issueTitle: "Login button broken", status: "fixed" },
|
|
177
|
+
{ issueNumber: 42, issueTitle: "Login button broken", status: "fixed", note: "", regressionUrl: null },
|
|
176
178
|
];
|
|
177
179
|
const agent = makeAgentLog({ agentType: "regression", regressionChecks: checks });
|
|
178
180
|
generateReport(makeRunLog({ agents: [agent] }), [], emptyTriage, makeProductSpec(), [], new Map());
|
|
@@ -185,7 +187,7 @@ describe("generateReport", () => {
|
|
|
185
187
|
|
|
186
188
|
it("regression が再発した場合 regressed バッジが表示される", () => {
|
|
187
189
|
const checks: RegressionCheck[] = [
|
|
188
|
-
{ issueNumber: 7, issueTitle: "Crash on submit", status: "regressed" },
|
|
190
|
+
{ issueNumber: 7, issueTitle: "Crash on submit", status: "regressed", note: "", regressionUrl: null },
|
|
189
191
|
];
|
|
190
192
|
const agent = makeAgentLog({ agentType: "regression", regressionChecks: checks });
|
|
191
193
|
generateReport(makeRunLog({ agents: [agent] }), [], emptyTriage, makeProductSpec(), [], new Map());
|
|
@@ -287,7 +287,8 @@ If user management is not accessible from this account, or the app has no role s
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
case "navigate": {
|
|
290
|
-
const { path: navPath } = toolUse.input as { path
|
|
290
|
+
const { path: navPath } = toolUse.input as { path?: string };
|
|
291
|
+
if (!navPath) { resultText = "navigate: missing path"; break; }
|
|
291
292
|
await saveSnapshotBeforeAction(page, observation);
|
|
292
293
|
await page.goto(`${baseUrl}${navPath}`, { waitUntil: "networkidle" });
|
|
293
294
|
await page.waitForTimeout(500);
|
|
@@ -297,7 +298,8 @@ If user management is not accessible from this account, or the app has no role s
|
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
case "click": {
|
|
300
|
-
const { description } = toolUse.input as { description
|
|
301
|
+
const { description } = toolUse.input as { description?: string };
|
|
302
|
+
if (!description) { resultText = "click: missing description"; break; }
|
|
301
303
|
await saveSnapshotBeforeAction(page, observation);
|
|
302
304
|
const escaped = description.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
303
305
|
let clicked = false;
|
|
@@ -316,7 +318,8 @@ If user management is not accessible from this account, or the app has no role s
|
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
case "fill": {
|
|
319
|
-
const { label, value } = toolUse.input as { label
|
|
321
|
+
const { label, value } = toolUse.input as { label?: string; value?: string };
|
|
322
|
+
if (!label || value === undefined) { resultText = "fill: missing label or value"; break; }
|
|
320
323
|
await saveSnapshotBeforeAction(page, observation);
|
|
321
324
|
const byLabel = page.getByLabel(new RegExp(label, "i"));
|
|
322
325
|
const byPlaceholder = page.getByPlaceholder(new RegExp(label, "i"));
|
|
@@ -340,7 +343,8 @@ If user management is not accessible from this account, or the app has no role s
|
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
case "save_account": {
|
|
343
|
-
const { email, password, role } = toolUse.input as { email
|
|
346
|
+
const { email, password, role } = toolUse.input as { email?: string; password?: string; role?: string };
|
|
347
|
+
if (!email || !password || !role) { resultText = "save_account: missing required fields"; break; }
|
|
344
348
|
savedAccounts.push({ email, password, role });
|
|
345
349
|
console.log(` [account-manager] saved account: ${email} (role: ${role})`);
|
|
346
350
|
resultText = `Account saved: ${email} (${role})`;
|
|
@@ -348,7 +352,8 @@ If user management is not accessible from this account, or the app has no role s
|
|
|
348
352
|
}
|
|
349
353
|
|
|
350
354
|
case "post_finding": {
|
|
351
|
-
const { title, body } = toolUse.input as { title
|
|
355
|
+
const { title, body } = toolUse.input as { title?: string; body?: string };
|
|
356
|
+
if (!title || !body) { resultText = "post_finding: missing title or body"; break; }
|
|
352
357
|
saveFinding({
|
|
353
358
|
id: `acct_${Date.now()}`,
|
|
354
359
|
runId,
|
package/framework/llm-client.ts
CHANGED
|
@@ -157,11 +157,11 @@ function fromOpenAIResponse(response: OpenAI.ChatCompletion): Message {
|
|
|
157
157
|
const content: ContentBlock[] = [];
|
|
158
158
|
|
|
159
159
|
if (choice.message.content) {
|
|
160
|
-
content.push({ type: "text", text: choice.message.content });
|
|
160
|
+
content.push({ type: "text", text: choice.message.content } as ContentBlock);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
if (choice.message.tool_calls) {
|
|
164
|
-
for (const tc of choice.message.tool_calls) {
|
|
164
|
+
for (const tc of choice.message.tool_calls.filter((t) => t.type === "function")) {
|
|
165
165
|
let input: Record<string, unknown> = {};
|
|
166
166
|
try {
|
|
167
167
|
input = JSON.parse(tc.function.arguments);
|
|
@@ -172,7 +172,7 @@ function fromOpenAIResponse(response: OpenAI.ChatCompletion): Message {
|
|
|
172
172
|
id: tc.id,
|
|
173
173
|
name: tc.function.name,
|
|
174
174
|
input,
|
|
175
|
-
});
|
|
175
|
+
} as ContentBlock);
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -392,7 +392,7 @@ function fromCodexResponse(response: Record<string, unknown>): Message {
|
|
|
392
392
|
if (i.type === "message") {
|
|
393
393
|
for (const block of (i.content as unknown[]) ?? []) {
|
|
394
394
|
const b = block as Record<string, unknown>;
|
|
395
|
-
if (b.type === "output_text") content.push({ type: "text", text: b.text as string });
|
|
395
|
+
if (b.type === "output_text") content.push({ type: "text", text: b.text as string } as ContentBlock);
|
|
396
396
|
}
|
|
397
397
|
} else if (i.type === "function_call") {
|
|
398
398
|
hasToolUse = true;
|
|
@@ -403,7 +403,7 @@ function fromCodexResponse(response: Record<string, unknown>): Message {
|
|
|
403
403
|
id: (i.call_id ?? i.id) as string,
|
|
404
404
|
name: i.name as string,
|
|
405
405
|
input,
|
|
406
|
-
});
|
|
406
|
+
} as ContentBlock);
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
|
|
@@ -253,7 +253,8 @@ Guidelines for output_spec:
|
|
|
253
253
|
let result: string;
|
|
254
254
|
|
|
255
255
|
if (toolUse.name === "navigate_and_read") {
|
|
256
|
-
const { path } = toolUse.input as { path
|
|
256
|
+
const { path } = toolUse.input as { path?: string };
|
|
257
|
+
if (!path) { result = "navigate_and_read: missing path"; toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result }); continue; }
|
|
257
258
|
try {
|
|
258
259
|
await page.goto(`${baseUrl}${path}`, { waitUntil: "networkidle", timeout: 10000 });
|
|
259
260
|
await page.waitForTimeout(500);
|
|
@@ -268,7 +269,8 @@ Guidelines for output_spec:
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
} else if (toolUse.name === "fetch_url") {
|
|
271
|
-
const { url } = toolUse.input as { url
|
|
272
|
+
const { url } = toolUse.input as { url?: string };
|
|
273
|
+
if (!url) { result = "fetch_url: missing url"; toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result }); continue; }
|
|
272
274
|
try {
|
|
273
275
|
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
|
274
276
|
const text = await res.text();
|
package/framework/triage.ts
CHANGED
|
@@ -143,11 +143,16 @@ Organize feedback collected by multiple agents and post it as issue tickets.
|
|
|
143
143
|
|
|
144
144
|
} else if (toolUse.name === "create_issue") {
|
|
145
145
|
const { title, body, category, merged_finding_ids } = toolUse.input as {
|
|
146
|
-
title
|
|
147
|
-
body
|
|
148
|
-
category
|
|
149
|
-
merged_finding_ids
|
|
146
|
+
title?: string;
|
|
147
|
+
body?: string;
|
|
148
|
+
category?: string;
|
|
149
|
+
merged_finding_ids?: string[];
|
|
150
150
|
};
|
|
151
|
+
if (!title || !body || !category) {
|
|
152
|
+
result = { error: "create_issue: missing required fields" };
|
|
153
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: JSON.stringify(result) });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
151
156
|
const mergedIds = merged_finding_ids ?? [];
|
|
152
157
|
if (mergedIds.length === 0) {
|
|
153
158
|
result = { error: "merged_finding_ids must contain at least one ID" };
|
|
@@ -175,7 +180,12 @@ Organize feedback collected by multiple agents and post it as issue tickets.
|
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
} else if (toolUse.name === "skip_finding") {
|
|
178
|
-
const { finding_id, reason } = toolUse.input as { finding_id
|
|
183
|
+
const { finding_id, reason } = toolUse.input as { finding_id?: string; reason?: string };
|
|
184
|
+
if (!finding_id) {
|
|
185
|
+
result = { error: "skip_finding: missing finding_id" };
|
|
186
|
+
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: JSON.stringify(result) });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
179
189
|
pendingIds.delete(finding_id);
|
|
180
190
|
skippedIds.push(finding_id);
|
|
181
191
|
skipped++;
|
package/package.json
CHANGED