osborn 0.9.173 → 0.9.175
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/dist/claude-llm.d.ts +6 -0
- package/dist/claude-llm.js +92 -2
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/dist/claude-llm.d.ts
CHANGED
|
@@ -259,6 +259,12 @@ export declare class ClaudeLLM extends llm.LLM {
|
|
|
259
259
|
* Public so ClaudeLLMStream can call it via this.#llmRef.spawnReviewer().
|
|
260
260
|
*/
|
|
261
261
|
spawnReviewer(agentId: string, writerOutput: string, emitter: EventEmitter): Promise<void>;
|
|
262
|
+
/**
|
|
263
|
+
* Dispatcher v1 — auto-spawn a tester after a writer sub-agent completes,
|
|
264
|
+
* in parallel with spawnReviewer. Runs tests against the diff and emits
|
|
265
|
+
* dispatch_rejected with verdict 'TEST-FAIL' if they fail.
|
|
266
|
+
*/
|
|
267
|
+
spawnTester(agentId: string, _writerOutput: string, emitter: EventEmitter): Promise<void>;
|
|
262
268
|
/**
|
|
263
269
|
* Dispatcher v1 — research gate: vet a researcher sub-agent's output before
|
|
264
270
|
* it reaches the main agent. Emits dispatch_rejected with verdict 'NEEDS-MORE'
|
package/dist/claude-llm.js
CHANGED
|
@@ -1410,6 +1410,95 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
1410
1410
|
console.error('[DISPATCH] reviewer spawn failed (non-fatal):', err);
|
|
1411
1411
|
}
|
|
1412
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Dispatcher v1 — auto-spawn a tester after a writer sub-agent completes,
|
|
1415
|
+
* in parallel with spawnReviewer. Runs tests against the diff and emits
|
|
1416
|
+
* dispatch_rejected with verdict 'TEST-FAIL' if they fail.
|
|
1417
|
+
*/
|
|
1418
|
+
async spawnTester(agentId, _writerOutput, emitter) {
|
|
1419
|
+
// Use a scoped key so reviewer's dedup guard (keyed on raw agentId) doesn't block us.
|
|
1420
|
+
const testerKey = `${agentId}:tester`;
|
|
1421
|
+
if (this.#dispatchedFor.has(testerKey))
|
|
1422
|
+
return;
|
|
1423
|
+
this.#dispatchedFor.add(testerKey);
|
|
1424
|
+
try {
|
|
1425
|
+
let gitDiff = '';
|
|
1426
|
+
try {
|
|
1427
|
+
const { execSync } = await import('child_process');
|
|
1428
|
+
const diffStat = execSync('git diff HEAD~1 HEAD --stat 2>/dev/null', { cwd: this.#opts.workingDirectory, timeout: 5000 }).toString().trim();
|
|
1429
|
+
const diff = execSync('git diff HEAD~1 HEAD 2>/dev/null', { cwd: this.#opts.workingDirectory, timeout: 5000 }).toString().trim();
|
|
1430
|
+
gitDiff = diffStat ? `\n\n<git_diff_stat>\n${diffStat}\n</git_diff_stat>\n\n<git_diff>\n${diff.slice(0, 6000)}\n</git_diff>` : '';
|
|
1431
|
+
}
|
|
1432
|
+
catch {
|
|
1433
|
+
// non-fatal
|
|
1434
|
+
}
|
|
1435
|
+
const prompt = [
|
|
1436
|
+
'Run tests and verify that the change below did not introduce regressions.',
|
|
1437
|
+
'Use the git diff as your scope — run the full existing test suite to establish a baseline,',
|
|
1438
|
+
'then target tests at the specific changed files/functions.',
|
|
1439
|
+
'If no test suite exists, run the build or execute the changed code directly to confirm it works.',
|
|
1440
|
+
'End your reply with exactly `RESULT: PASS` or `RESULT: FAIL` followed by a brief summary.',
|
|
1441
|
+
gitDiff,
|
|
1442
|
+
].join('\n');
|
|
1443
|
+
const testerOptions = {
|
|
1444
|
+
cwd: this.#opts.workingDirectory,
|
|
1445
|
+
permissionMode: 'default',
|
|
1446
|
+
systemPrompt: NAMED_AGENTS.tester.prompt,
|
|
1447
|
+
allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'Write', 'Edit'],
|
|
1448
|
+
hooks: {
|
|
1449
|
+
PreToolUse: [{
|
|
1450
|
+
matcher: '.*',
|
|
1451
|
+
hooks: [async (input) => {
|
|
1452
|
+
const toolName = input?.tool_name || 'unknown';
|
|
1453
|
+
const toolInput = input?.tool_input || {};
|
|
1454
|
+
emitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: 'tester' });
|
|
1455
|
+
return {};
|
|
1456
|
+
}],
|
|
1457
|
+
}],
|
|
1458
|
+
PostToolUse: [{
|
|
1459
|
+
matcher: '.*',
|
|
1460
|
+
hooks: [async (input) => {
|
|
1461
|
+
const toolName = input?.tool_name || 'unknown';
|
|
1462
|
+
const toolInput = input?.tool_input || {};
|
|
1463
|
+
const toolResponse = input?.tool_response;
|
|
1464
|
+
emitter.emit('tool_result', { name: toolName, input: toolInput, response: toolResponse, agentRole: 'tester' });
|
|
1465
|
+
return {};
|
|
1466
|
+
}],
|
|
1467
|
+
}],
|
|
1468
|
+
},
|
|
1469
|
+
};
|
|
1470
|
+
console.log(`[DISPATCH] spawning tester for agentId=${agentId.slice(0, 8)}`);
|
|
1471
|
+
const testerQuery = query({ prompt, options: testerOptions });
|
|
1472
|
+
this.#activeQueries.add(testerQuery);
|
|
1473
|
+
this.#activeQueriesById.set(testerKey, testerQuery);
|
|
1474
|
+
let testerText = '';
|
|
1475
|
+
try {
|
|
1476
|
+
for await (const msg of testerQuery) {
|
|
1477
|
+
const m = msg;
|
|
1478
|
+
if (m.type === 'result' && m.result) {
|
|
1479
|
+
testerText = String(m.result);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
finally {
|
|
1484
|
+
this.#activeQueries.delete(testerQuery);
|
|
1485
|
+
this.#activeQueriesById.delete(testerKey);
|
|
1486
|
+
}
|
|
1487
|
+
const resultMatch = testerText.match(/RESULT:\s*(PASS|FAIL)/i);
|
|
1488
|
+
const result = resultMatch ? resultMatch[1].toUpperCase() : null;
|
|
1489
|
+
if (result === 'FAIL') {
|
|
1490
|
+
console.log(`[DISPATCH] tester FAIL for agentId=${agentId.slice(0, 8)}`);
|
|
1491
|
+
statusManager.upsertDispatch(agentId, { dispatchState: 'rejected', artifact: testerText });
|
|
1492
|
+
emitter.emit('dispatch_rejected', { tuid: agentId, verdict: 'TEST-FAIL', review: testerText });
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
console.log(`[DISPATCH] tester PASS for agentId=${agentId.slice(0, 8)}`);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
catch (err) {
|
|
1499
|
+
console.error('[DISPATCH] tester spawn failed (non-fatal):', err);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1413
1502
|
/**
|
|
1414
1503
|
* Dispatcher v1 — research gate: vet a researcher sub-agent's output before
|
|
1415
1504
|
* it reaches the main agent. Emits dispatch_rejected with verdict 'NEEDS-MORE'
|
|
@@ -1996,11 +2085,12 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
1996
2085
|
const aid = input?.agent_id ?? ('sa-' + Date.now());
|
|
1997
2086
|
statusManager.upsertDispatch(aid, { subagentType: at, dispatchState: 'completed', artifact: msg });
|
|
1998
2087
|
this.#eventEmitter.emit('task_completed', { agent_type: at, agent_id: aid, last_assistant_message: String(msg).slice(0, 400) });
|
|
1999
|
-
// Infinite-loop guard — never re-dispatch the reviewer or reasoner.
|
|
2000
|
-
if (at === 'reviewer' || at === 'reasoner')
|
|
2088
|
+
// Infinite-loop guard — never re-dispatch the reviewer, tester, or reasoner.
|
|
2089
|
+
if (at === 'reviewer' || at === 'tester' || at === 'reasoner')
|
|
2001
2090
|
return {};
|
|
2002
2091
|
if (at === 'writer' && msg) {
|
|
2003
2092
|
void this.#llmRef.spawnReviewer(aid, msg, this.#eventEmitter);
|
|
2093
|
+
void this.#llmRef.spawnTester(aid, msg, this.#eventEmitter);
|
|
2004
2094
|
}
|
|
2005
2095
|
else if (at === 'researcher' && msg) {
|
|
2006
2096
|
void this.#llmRef.spawnResearchGate(aid, msg, this.#eventEmitter);
|
package/dist/index.js
CHANGED
|
@@ -6088,7 +6088,10 @@ async function main() {
|
|
|
6088
6088
|
}
|
|
6089
6089
|
// Feature B — per-flow stop (does NOT affect other running flows)
|
|
6090
6090
|
else if (data.type === 'stop_dispatch' && currentLLM) {
|
|
6091
|
-
|
|
6091
|
+
const agentId = String(data.agentId);
|
|
6092
|
+
currentLLM.stopAgent?.(agentId);
|
|
6093
|
+
// Always confirm so frontend clears the card (covers ghost/stale flows too)
|
|
6094
|
+
await sendToFrontend({ type: 'agent_stopped', agent_id: agentId });
|
|
6092
6095
|
}
|
|
6093
6096
|
else if (data.type === 'join_meeting') {
|
|
6094
6097
|
const meetingUrl = data.url;
|