osborn 0.9.218 → 0.9.220
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 +16 -17
- package/dist/claude-llm.js +89 -65
- package/package.json +1 -1
package/dist/claude-llm.d.ts
CHANGED
|
@@ -59,23 +59,13 @@ export declare const NAMED_AGENTS: {
|
|
|
59
59
|
description: string;
|
|
60
60
|
tools: string[];
|
|
61
61
|
grounded: boolean;
|
|
62
|
-
|
|
63
|
-
write: "anywhere";
|
|
64
|
-
};
|
|
65
|
-
reminder: string;
|
|
62
|
+
criticalSystemReminder_EXPERIMENTAL: string;
|
|
66
63
|
model: string;
|
|
67
64
|
prompt: string;
|
|
68
65
|
};
|
|
69
66
|
tester: {
|
|
70
67
|
description: string;
|
|
71
68
|
tools: string[];
|
|
72
|
-
policy: {
|
|
73
|
-
write: {
|
|
74
|
-
extensions: RegExp;
|
|
75
|
-
label: string;
|
|
76
|
-
matchBasename: boolean;
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
69
|
model: string;
|
|
80
70
|
prompt: string;
|
|
81
71
|
};
|
|
@@ -89,12 +79,6 @@ export declare const NAMED_AGENTS: {
|
|
|
89
79
|
reviewer: {
|
|
90
80
|
description: string;
|
|
91
81
|
tools: string[];
|
|
92
|
-
policy: {
|
|
93
|
-
write: {
|
|
94
|
-
extensions: RegExp;
|
|
95
|
-
label: string;
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
82
|
model: string;
|
|
99
83
|
prompt: string;
|
|
100
84
|
};
|
|
@@ -125,6 +109,21 @@ export declare function applyTurbo(agents: Record<string, any>, turbo: boolean):
|
|
|
125
109
|
* Adversarial agents (reviewer/tester) leave `grounded` unset → untouched, stay blind.
|
|
126
110
|
*/
|
|
127
111
|
export declare function applyGrounding(agents: Record<string, any>, sessionId: string | null, workingDir: string | undefined): Record<string, any>;
|
|
112
|
+
/**
|
|
113
|
+
* Decide a Write/Edit/MultiEdit for the acting agent (null = main orchestrator).
|
|
114
|
+
* Pure lookup against WRITE_RULES. Returns:
|
|
115
|
+
* 'allow' → fall through (→ canUseTool workspace auto-approve, or {}).
|
|
116
|
+
* 'defer' → PreToolUse 'ask' (canUseTool decides).
|
|
117
|
+
* 'deny' → hard block with reason.
|
|
118
|
+
*/
|
|
119
|
+
export declare function decideWrite(agentType: string | null, filePath: string): {
|
|
120
|
+
decision: 'allow' | 'defer' | 'deny';
|
|
121
|
+
reason?: string;
|
|
122
|
+
};
|
|
123
|
+
export declare const VERIFIER_CHAIN: Record<string, {
|
|
124
|
+
then: string[];
|
|
125
|
+
mode?: 'parallel' | 'sequential';
|
|
126
|
+
}>;
|
|
128
127
|
/**
|
|
129
128
|
* Claude LLM - Wraps Claude Agent SDK for LiveKit
|
|
130
129
|
* Research mode: reads anything, writes only to session workspace
|
package/dist/claude-llm.js
CHANGED
|
@@ -402,6 +402,7 @@ export const NAMED_AGENTS = {
|
|
|
402
402
|
'Return findings to the orchestrator — never directly to the user.',
|
|
403
403
|
'Run several researchers in parallel when there are independent threads to investigate.',
|
|
404
404
|
'Hand off back to the orchestrator; it decides whether to invoke planner or writer next.',
|
|
405
|
+
'After you return, a reasoner-based gate judges whether your findings are COMPLETE and well-sourced; thin or unsourced findings get sent back to you — so cite paths/line numbers and state what you looked for but did NOT find.',
|
|
405
406
|
].join('\n'),
|
|
406
407
|
},
|
|
407
408
|
reasoner: {
|
|
@@ -468,10 +469,8 @@ export const NAMED_AGENTS = {
|
|
|
468
469
|
].join(' '),
|
|
469
470
|
tools: ['Read', 'Write', 'Edit', 'MultiEdit', 'Bash', 'Glob', 'Grep', 'NotebookRead', 'NotebookEdit'],
|
|
470
471
|
grounded: true, // applyGrounding() injects the osborn-recall command
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// Pinned into the writer's system prompt as a hard-to-ignore reminder.
|
|
474
|
-
reminder: 'BEFORE you write implementation code: make sure a test exists for the behavior you are about to change. If none exists, say so explicitly in your report so the tester can cover it — the tester is the agent that writes tests. NEVER weaken, skip, or delete a test to make your change pass.',
|
|
472
|
+
// SDK-native soft-behavior field — pinned into the writer's system prompt.
|
|
473
|
+
criticalSystemReminder_EXPERIMENTAL: 'BEFORE you write implementation code: make sure a test exists for the behavior you are about to change. If none exists, say so explicitly in your report so the tester can cover it — the tester is the agent that writes tests. NEVER weaken, skip, or delete a test to make your change pass.',
|
|
475
474
|
model: 'opus',
|
|
476
475
|
prompt: [
|
|
477
476
|
'You are Osborn\'s writer agent. You execute file changes with a verify-first approach.',
|
|
@@ -514,6 +513,7 @@ export const NAMED_AGENTS = {
|
|
|
514
513
|
'Invoked AFTER the planner produces a written plan — the writer is the SOLE agent that edits files.',
|
|
515
514
|
'Do not invoke writer until a plan exists for any multi-step change.',
|
|
516
515
|
'When the writer returns, the orchestrator invokes tester AND reviewer in parallel before surfacing results.',
|
|
516
|
+
'Because that verification runs automatically after you finish, make the change review-ready and leave the tree in a runnable state — do not skip cleanup expecting a second pass.',
|
|
517
517
|
].join('\n'),
|
|
518
518
|
},
|
|
519
519
|
tester: {
|
|
@@ -523,9 +523,7 @@ export const NAMED_AGENTS = {
|
|
|
523
523
|
'Returns structured pass/fail results with exact output — does NOT edit files.',
|
|
524
524
|
'NOT GROUNDED: no session index access — runs tests with fresh eyes, adversarial validation.',
|
|
525
525
|
].join(' '),
|
|
526
|
-
tools: ['Bash', 'Read', 'Glob', 'Grep', 'Write', 'Edit'],
|
|
527
|
-
// Fail-closed: may ONLY write test files (basename .test/.spec.[jt]sx?).
|
|
528
|
-
policy: { write: { extensions: /\.(test|spec)\.[jt]sx?$/, label: 'test', matchBasename: true } },
|
|
526
|
+
tools: ['Bash', 'Read', 'Glob', 'Grep', 'Write', 'Edit'], // write-gate: test files only (WRITE_RULES.tester)
|
|
529
527
|
model: 'sonnet',
|
|
530
528
|
prompt: [
|
|
531
529
|
'You are Osborn\'s tester agent. Your job is running tests and builds, then reporting results.',
|
|
@@ -674,9 +672,7 @@ export const NAMED_AGENTS = {
|
|
|
674
672
|
'and returns an ACCEPT or REJECT verdict with specific, actionable feedback. May write documentation files (.md etc.) only.',
|
|
675
673
|
'NOT GROUNDED: no session index access — reviews with fresh eyes for unbiased adversarial check.',
|
|
676
674
|
].join(' '),
|
|
677
|
-
tools: ['Read', 'Glob', 'Grep', 'Bash', 'Write', 'Edit'],
|
|
678
|
-
// Fail-closed: may ONLY write documentation files.
|
|
679
|
-
policy: { write: { extensions: /\.(md|markdown|mdx|txt|rst|adoc)$/i, label: 'documentation' } },
|
|
675
|
+
tools: ['Read', 'Glob', 'Grep', 'Bash', 'Write', 'Edit'], // write-gate: docs only (WRITE_RULES.reviewer)
|
|
680
676
|
model: 'sonnet',
|
|
681
677
|
prompt: [
|
|
682
678
|
'You are Osborn\'s reviewer agent. You are the VERIFY step in a generator-verifier loop.',
|
|
@@ -844,59 +840,53 @@ export function applyGrounding(agents, sessionId, workingDir) {
|
|
|
844
840
|
}
|
|
845
841
|
return out;
|
|
846
842
|
}
|
|
847
|
-
const
|
|
843
|
+
const WRITE_RULES = {
|
|
844
|
+
writer: 'anywhere',
|
|
845
|
+
tester: { extensions: /\.(test|spec)\.[jt]sx?$/, label: 'test', matchBasename: true },
|
|
846
|
+
reviewer: { extensions: /\.(md|markdown|mdx|txt|rst|adoc)$/i, label: 'documentation' },
|
|
847
|
+
};
|
|
848
848
|
/** Path is inside the per-session sandbox workspace. */
|
|
849
849
|
function isWorkspacePath(filePath) {
|
|
850
850
|
return !!filePath && (filePath.includes('/osb/') ||
|
|
851
851
|
filePath.includes('.osborn/sessions/') ||
|
|
852
852
|
filePath.includes('.osborn/research/'));
|
|
853
853
|
}
|
|
854
|
-
/** Effective write policy for the acting agent (null agentType = main orchestrator). */
|
|
855
|
-
function resolveWritePolicy(agentType, roster) {
|
|
856
|
-
const def = agentType ? roster?.[agentType] : null;
|
|
857
|
-
return def?.policy?.write ?? DEFAULT_WRITE_POLICY;
|
|
858
|
-
}
|
|
859
854
|
/**
|
|
860
|
-
* Decide a Write/Edit/MultiEdit
|
|
861
|
-
*
|
|
862
|
-
* '
|
|
863
|
-
* '
|
|
855
|
+
* Decide a Write/Edit/MultiEdit for the acting agent (null = main orchestrator).
|
|
856
|
+
* Pure lookup against WRITE_RULES. Returns:
|
|
857
|
+
* 'allow' → fall through (→ canUseTool workspace auto-approve, or {}).
|
|
858
|
+
* 'defer' → PreToolUse 'ask' (canUseTool decides).
|
|
859
|
+
* 'deny' → hard block with reason.
|
|
864
860
|
*/
|
|
865
|
-
function decideWrite(
|
|
866
|
-
|
|
861
|
+
export function decideWrite(agentType, filePath) {
|
|
862
|
+
const rule = (agentType && WRITE_RULES[agentType]) || 'workspace';
|
|
863
|
+
if (rule === 'anywhere')
|
|
867
864
|
return { decision: 'defer' };
|
|
868
|
-
if (
|
|
865
|
+
if (rule === 'workspace') {
|
|
869
866
|
if (filePath && !isWorkspacePath(filePath)) {
|
|
870
867
|
return { decision: 'deny', reason: 'Research mode: writes restricted to session workspace.' };
|
|
871
868
|
}
|
|
872
869
|
return { decision: 'allow' };
|
|
873
870
|
}
|
|
874
871
|
// Extension whitelist — fail closed (empty/unknown path denied).
|
|
875
|
-
const target =
|
|
876
|
-
if (!filePath || !
|
|
872
|
+
const target = rule.matchBasename ? (filePath ? basename(resolve(filePath)) : '') : filePath;
|
|
873
|
+
if (!filePath || !rule.extensions.test(target)) {
|
|
877
874
|
const reason = filePath
|
|
878
|
-
? `Write denied: ${filePath} is not a ${
|
|
875
|
+
? `Write denied: ${filePath} is not a ${rule.label} file. This agent may only write ${rule.label} files.`
|
|
879
876
|
: 'Write denied: could not determine target file path. Failing closed.';
|
|
880
877
|
return { decision: 'deny', reason };
|
|
881
878
|
}
|
|
882
879
|
return { decision: 'defer' };
|
|
883
880
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (reminder && !rest.criticalSystemReminder_EXPERIMENTAL) {
|
|
894
|
-
rest.criticalSystemReminder_EXPERIMENTAL = reminder;
|
|
895
|
-
}
|
|
896
|
-
out[name] = rest;
|
|
897
|
-
}
|
|
898
|
-
return out;
|
|
899
|
-
}
|
|
881
|
+
// ── Verifier chaining ───────────────────────────────────────────────────────
|
|
882
|
+
// A plain name→verifiers lookup read by the SubagentStop hook. Same wiring the
|
|
883
|
+
// old hardcoded branches had (writer→reviewer+tester, researcher→reasoner gate),
|
|
884
|
+
// gathered in one visible place. Names resolve to spawnReviewer / spawnTester /
|
|
885
|
+
// spawnResearchGate. Any agent not listed chains to nothing.
|
|
886
|
+
export const VERIFIER_CHAIN = {
|
|
887
|
+
writer: { then: ['reviewer', 'tester'], mode: 'parallel' },
|
|
888
|
+
researcher: { then: ['reasoner'] },
|
|
889
|
+
};
|
|
900
890
|
const RESEARCH_TOOLS = [
|
|
901
891
|
'Read', 'Write', 'Edit', 'Glob', 'Grep',
|
|
902
892
|
'Bash', 'WebSearch', 'WebFetch',
|
|
@@ -2016,10 +2006,6 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2016
2006
|
? getSessionWorkspace(this.#opts.workingDirectory, sessionId)
|
|
2017
2007
|
: null;
|
|
2018
2008
|
const allowedTools = this.#opts.allowedTools || [];
|
|
2019
|
-
// Roster the PreToolUse write-gate reads policy from — the PRE-strip view
|
|
2020
|
-
// (retains `policy`), so DB-backed custom agents (set_agents) get their
|
|
2021
|
-
// write policy enforced too. The SDK receives the finalized copy (agents:).
|
|
2022
|
-
const enforcementRoster = this.#opts.agents ?? NAMED_AGENTS;
|
|
2023
2009
|
const sdkOptions = {
|
|
2024
2010
|
cwd: this.#opts.workingDirectory,
|
|
2025
2011
|
permissionMode: this.#opts.permissionMode,
|
|
@@ -2137,23 +2123,22 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2137
2123
|
}
|
|
2138
2124
|
console.log(`🔧 Tool call ${turnToolCallCount}/${TOOL_CALL_BUDGET}: ${toolName}`);
|
|
2139
2125
|
}
|
|
2140
|
-
// Write/Edit/MultiEdit access control —
|
|
2141
|
-
//
|
|
2142
|
-
// hardcoded per-role branches
|
|
2126
|
+
// Write/Edit/MultiEdit access control — plain WRITE_RULES lookup
|
|
2127
|
+
// keyed by agent name (see decideWrite). Same behavior the old
|
|
2128
|
+
// hardcoded per-role branches had, now in one visible place.
|
|
2143
2129
|
if (toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit') {
|
|
2144
2130
|
const filePath = String(toolInput.file_path || '');
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
console.log(`🔎 Write gate: agent=${agentType ?? 'main'} policy=${JSON.stringify(policy)} path="${filePath || '(none)'}" → ${decision}`);
|
|
2131
|
+
const { decision, reason } = decideWrite(agentType, filePath);
|
|
2132
|
+
console.log(`🔎 Write gate: agent=${agentType ?? 'main'} path="${filePath || '(none)'}" → ${decision}`);
|
|
2148
2133
|
if (decision === 'deny') {
|
|
2149
2134
|
this.#eventEmitter.emit('tool_blocked', { name: toolName, reason });
|
|
2150
2135
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny' }, reason };
|
|
2151
2136
|
}
|
|
2152
2137
|
if (decision === 'defer') {
|
|
2153
|
-
// Writer (
|
|
2154
|
-
//
|
|
2155
|
-
//
|
|
2156
|
-
if (
|
|
2138
|
+
// Writer ('anywhere') surfaces its write in the live trace before
|
|
2139
|
+
// the permission dialog; adversarial agents (reviewer/tester) do
|
|
2140
|
+
// NOT emit here — parity with the prior hardcoded branches.
|
|
2141
|
+
if (agentType === 'writer') {
|
|
2157
2142
|
this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main' });
|
|
2158
2143
|
}
|
|
2159
2144
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'ask' } };
|
|
@@ -2501,6 +2486,27 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2501
2486
|
return {};
|
|
2502
2487
|
}]
|
|
2503
2488
|
}],
|
|
2489
|
+
// Idle-agent guard / end-of-run self-review. When the main agent tries
|
|
2490
|
+
// to end its turn while background work is still in flight, block ONCE
|
|
2491
|
+
// (stop_hook_active guards against a loop) and force one more turn so it
|
|
2492
|
+
// either waits + synthesizes, or explicitly tells the user what's still
|
|
2493
|
+
// running — never goes silent leaving dispatched work dangling.
|
|
2494
|
+
Stop: [{
|
|
2495
|
+
matcher: '.*',
|
|
2496
|
+
hooks: [async (input) => {
|
|
2497
|
+
const inFlight = Array.isArray(input?.background_tasks) ? input.background_tasks : [];
|
|
2498
|
+
if (input?.stop_hook_active || inFlight.length === 0)
|
|
2499
|
+
return {};
|
|
2500
|
+
const labels = inFlight
|
|
2501
|
+
.map((t) => t?.agent_type || t?.name || t?.command || t?.type || 'task')
|
|
2502
|
+
.slice(0, 6);
|
|
2503
|
+
console.log(`🛑 Stop gate: ${inFlight.length} background task(s) in flight → blocking once for self-review`);
|
|
2504
|
+
return {
|
|
2505
|
+
decision: 'block',
|
|
2506
|
+
reason: `Before you end your turn: ${inFlight.length} background task(s) are still running (${labels.join(', ')}). Do NOT go idle. Either wait for them and synthesize their results into your answer, or explicitly tell the user what is still running and what you will do when it finishes.`,
|
|
2507
|
+
};
|
|
2508
|
+
}]
|
|
2509
|
+
}],
|
|
2504
2510
|
SubagentStart: [{
|
|
2505
2511
|
matcher: '.*',
|
|
2506
2512
|
hooks: [async (input) => {
|
|
@@ -2518,15 +2524,33 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2518
2524
|
const aid = input?.agent_id ?? ('sa-' + Date.now());
|
|
2519
2525
|
statusManager.upsertDispatch(aid, { subagentType: at, dispatchState: 'completed', artifact: msg });
|
|
2520
2526
|
this.#eventEmitter.emit('task_completed', { agent_type: at, agent_id: aid, last_assistant_message: String(msg).slice(0, 400) });
|
|
2521
|
-
// Infinite-loop guard — never re-dispatch
|
|
2527
|
+
// Infinite-loop guard — verifiers never re-dispatch (they carry no
|
|
2528
|
+
// VERIFIER_CHAIN entry anyway; defense-in-depth).
|
|
2522
2529
|
if (at === 'reviewer' || at === 'tester' || at === 'reasoner')
|
|
2523
2530
|
return {};
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2531
|
+
// Verifier chaining — driven by the plain VERIFIER_CHAIN lookup
|
|
2532
|
+
// (replaces the hardcoded writer/researcher branches).
|
|
2533
|
+
const chain = at ? VERIFIER_CHAIN[at] : undefined;
|
|
2534
|
+
if (chain?.then?.length && msg) {
|
|
2535
|
+
const spawn = (role) => {
|
|
2536
|
+
if (role === 'reviewer')
|
|
2537
|
+
return this.#llmRef.spawnReviewer(aid, msg, this.#eventEmitter);
|
|
2538
|
+
if (role === 'tester')
|
|
2539
|
+
return this.#llmRef.spawnTester(aid, msg, this.#eventEmitter);
|
|
2540
|
+
if (role === 'reasoner' || role === 'gate')
|
|
2541
|
+
return this.#llmRef.spawnResearchGate(aid, msg, this.#eventEmitter);
|
|
2542
|
+
console.warn(`[DISPATCH] unknown chain target '${role}' for ${at} — skipped`);
|
|
2543
|
+
return Promise.resolve();
|
|
2544
|
+
};
|
|
2545
|
+
if (chain.mode === 'sequential') {
|
|
2546
|
+
// Await in order WITHOUT blocking the hook return (fire the chain async).
|
|
2547
|
+
void (async () => { for (const r of chain.then)
|
|
2548
|
+
await spawn(r); })();
|
|
2549
|
+
}
|
|
2550
|
+
else {
|
|
2551
|
+
for (const r of chain.then)
|
|
2552
|
+
void spawn(r);
|
|
2553
|
+
}
|
|
2530
2554
|
}
|
|
2531
2555
|
return {};
|
|
2532
2556
|
}]
|
|
@@ -2553,9 +2577,9 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
2553
2577
|
// opts.agents is undefined — the ?? NAMED_AGENTS fallback would skip
|
|
2554
2578
|
// the override entirely. Explicitly apply applyTurbo(NAMED_AGENTS)
|
|
2555
2579
|
// so built-in agents always get FAST_MODEL when turbo is on.
|
|
2556
|
-
agents:
|
|
2580
|
+
agents: applyGrounding(this.#llmRef.turbo
|
|
2557
2581
|
? applyTurbo(this.#opts.agents ?? NAMED_AGENTS, true)
|
|
2558
|
-
: (this.#opts.agents ?? NAMED_AGENTS), this.#sessionId, this.#opts.workingDirectory)
|
|
2582
|
+
: (this.#opts.agents ?? NAMED_AGENTS), this.#sessionId, this.#opts.workingDirectory),
|
|
2559
2583
|
};
|
|
2560
2584
|
// Run Claude Agent SDK query() and stream results
|
|
2561
2585
|
let hasOutput = false;
|