plugin-agent-orchestrator 1.0.19 → 1.0.21
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/client/hooks/useRunEventStream.d.ts +22 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-execution-spans.js +24 -0
- package/dist/server/collections/agent-loop-runs.js +36 -0
- package/dist/server/collections/orchestrator-config.js +14 -0
- package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
- package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
- package/dist/server/plugin.js +47 -0
- package/dist/server/resources/agent-loop.js +33 -25
- package/dist/server/resources/tracing.js +5 -8
- package/dist/server/services/AgentHarness.d.ts +2 -0
- package/dist/server/services/AgentHarness.js +56 -90
- package/dist/server/services/AgentLoopController.d.ts +33 -20
- package/dist/server/services/AgentLoopController.js +164 -125
- package/dist/server/services/AgentLoopRepository.js +16 -34
- package/dist/server/services/AgentLoopService.d.ts +28 -18
- package/dist/server/services/AgentLoopService.js +7 -1
- package/dist/server/services/AgentPlannerService.js +5 -25
- package/dist/server/services/AgentRegistryService.d.ts +8 -0
- package/dist/server/services/AgentRegistryService.js +34 -24
- package/dist/server/services/CircuitBreaker.d.ts +40 -0
- package/dist/server/services/CircuitBreaker.js +120 -0
- package/dist/server/services/ContextAggregator.d.ts +45 -0
- package/dist/server/services/ContextAggregator.js +201 -0
- package/dist/server/services/ExecutionSpanService.js +2 -5
- package/dist/server/services/RunEventBus.d.ts +9 -0
- package/dist/server/services/RunEventBus.js +73 -0
- package/dist/server/services/TokenTracker.d.ts +62 -0
- package/dist/server/services/TokenTracker.js +173 -0
- package/dist/server/skill-hub/plugin.js +6 -6
- package/dist/server/skill-hub/tasks/SkillExecutionTask.js +6 -6
- package/dist/server/tools/agent-loop.d.ts +8 -8
- package/dist/server/tools/agent-loop.js +30 -63
- package/dist/server/tools/delegate-task.js +14 -72
- package/dist/server/tools/orchestrator-plan.d.ts +6 -6
- package/dist/server/tools/orchestrator-plan.js +10 -47
- package/dist/server/types.d.ts +47 -0
- package/dist/server/types.js +24 -0
- package/dist/server/utils/ctx-utils.d.ts +30 -0
- package/dist/server/utils/ctx-utils.js +152 -0
- package/dist/server/utils/logging.d.ts +6 -0
- package/dist/server/utils/logging.js +86 -0
- package/package.json +44 -44
- package/src/client/AgentRunsTab.tsx +764 -764
- package/src/client/HarnessProfilesTab.tsx +247 -247
- package/src/client/OrchestratorSettings.tsx +106 -106
- package/src/client/RulesTab.tsx +716 -716
- package/src/client/hooks/useRunEventStream.ts +76 -0
- package/src/client/index.tsx +2 -1
- package/src/client/plugin.tsx +27 -27
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/index.tsx +51 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
- package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
- package/src/client/tools/PlanApprovalCard.tsx +175 -175
- package/src/client/tools/registerOrchestratorCards.ts +7 -7
- package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
- package/src/server/__tests__/circuit-breaker.test.ts +169 -0
- package/src/server/__tests__/context-aggregator.test.ts +222 -0
- package/src/server/__tests__/parallel-execution.test.ts +318 -0
- package/src/server/__tests__/smoke.test.ts +120 -0
- package/src/server/collections/agent-execution-spans.ts +24 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-runs.ts +38 -1
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/orchestrator-config.ts +14 -0
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
- package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
- package/src/server/plugin.ts +53 -0
- package/src/server/resources/agent-loop.ts +21 -12
- package/src/server/resources/tracing.ts +3 -7
- package/src/server/services/AgentHarness.ts +78 -116
- package/src/server/services/AgentLoopController.ts +197 -122
- package/src/server/services/AgentLoopRepository.ts +9 -25
- package/src/server/services/AgentLoopService.ts +13 -1
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/AgentPlannerService.ts +2 -25
- package/src/server/services/AgentRegistryService.ts +40 -31
- package/src/server/services/CircuitBreaker.ts +116 -0
- package/src/server/services/ContextAggregator.ts +239 -0
- package/src/server/services/ExecutionSpanService.ts +2 -4
- package/src/server/services/RunEventBus.ts +45 -0
- package/src/server/services/TokenTracker.ts +209 -0
- package/src/server/skill-hub/plugin.ts +898 -897
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -458
- package/src/server/tools/agent-loop.ts +18 -57
- package/src/server/tools/delegate-task.ts +11 -93
- package/src/server/tools/orchestrator-plan.ts +26 -50
- package/src/server/tools/skill-execute.ts +160 -160
- package/src/server/types.ts +55 -0
- package/src/server/utils/ctx-utils.ts +118 -0
- package/src/server/utils/logging.ts +63 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type EventCallback = (event: any) => void;
|
|
2
|
+
|
|
3
|
+
class RunEventBusImpl {
|
|
4
|
+
private listeners = new Map<string | number, Set<EventCallback>>();
|
|
5
|
+
|
|
6
|
+
subscribe(runId: string | number, callback: EventCallback) {
|
|
7
|
+
let set = this.listeners.get(runId);
|
|
8
|
+
if (!set) {
|
|
9
|
+
set = new Set();
|
|
10
|
+
this.listeners.set(runId, set);
|
|
11
|
+
}
|
|
12
|
+
set.add(callback);
|
|
13
|
+
return () => {
|
|
14
|
+
set?.delete(callback);
|
|
15
|
+
if (set?.size === 0) {
|
|
16
|
+
this.listeners.delete(runId);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
emit(runId: string | number, event: any) {
|
|
22
|
+
const set = this.listeners.get(runId);
|
|
23
|
+
if (!set) return;
|
|
24
|
+
for (const callback of set) {
|
|
25
|
+
try {
|
|
26
|
+
callback(event);
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore per-listener errors
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
listenerCount(runId: string | number): number {
|
|
34
|
+
return this.listeners.get(runId)?.size || 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let instance: RunEventBusImpl | null = null;
|
|
39
|
+
|
|
40
|
+
export function getRunEventBus(): RunEventBusImpl {
|
|
41
|
+
if (!instance) {
|
|
42
|
+
instance = new RunEventBusImpl();
|
|
43
|
+
}
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
export interface TokenUsage {
|
|
2
|
+
inputTokens: number;
|
|
3
|
+
outputTokens: number;
|
|
4
|
+
totalTokens: number;
|
|
5
|
+
cost: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BudgetConfig {
|
|
9
|
+
budgetMaxTokens?: number;
|
|
10
|
+
budgetMaxCost?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface BudgetCheckResult {
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default pricing per 1K tokens (USD).
|
|
20
|
+
* These are conservative estimates; override via env vars:
|
|
21
|
+
* ORCHESTRATOR_PRICE_PER_1K_INPUT
|
|
22
|
+
* ORCHESTRATOR_PRICE_PER_1K_OUTPUT
|
|
23
|
+
*/
|
|
24
|
+
const PRICE_PER_1K_INPUT = Number(process.env.ORCHESTRATOR_PRICE_PER_1K_INPUT || 0.003);
|
|
25
|
+
const PRICE_PER_1K_OUTPUT = Number(process.env.ORCHESTRATOR_PRICE_PER_1K_OUTPUT || 0.015);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Estimate cost based on token counts.
|
|
29
|
+
*/
|
|
30
|
+
function estimateCost(inputTokens: number, outputTokens: number): number {
|
|
31
|
+
return (inputTokens / 1000) * PRICE_PER_1K_INPUT + (outputTokens / 1000) * PRICE_PER_1K_OUTPUT;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract usage_metadata from the final state returned by createReactAgent.invoke().
|
|
36
|
+
*
|
|
37
|
+
* LangChain's createReactAgent returns a final state object with a `messages` array.
|
|
38
|
+
* The last AIMessage in that array carries `usage_metadata` populated by the
|
|
39
|
+
* LLM provider after the final generation step. This is the standard LangChain
|
|
40
|
+
* approach — no private API access needed.
|
|
41
|
+
*
|
|
42
|
+
* Expected shape (from @langchain/core/messages/ai):
|
|
43
|
+
* AIMessage.usage_metadata = {
|
|
44
|
+
* input_tokens: number,
|
|
45
|
+
* output_tokens: number,
|
|
46
|
+
* total_tokens: number,
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
export function extractTokenUsage(finalState: any): TokenUsage | null {
|
|
50
|
+
if (!finalState?.messages || !Array.isArray(finalState.messages)) return null;
|
|
51
|
+
|
|
52
|
+
// Accumulate usage across all AI messages in the run (each LLM call adds usage)
|
|
53
|
+
let totalInput = 0;
|
|
54
|
+
let totalOutput = 0;
|
|
55
|
+
let totalAll = 0;
|
|
56
|
+
|
|
57
|
+
for (const msg of finalState.messages) {
|
|
58
|
+
if (msg?.usage_metadata) {
|
|
59
|
+
const um = msg.usage_metadata;
|
|
60
|
+
totalInput += um.input_tokens || 0;
|
|
61
|
+
totalOutput += um.output_tokens || 0;
|
|
62
|
+
totalAll += um.total_tokens || 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (totalAll === 0) return null;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
inputTokens: totalInput,
|
|
70
|
+
outputTokens: totalOutput,
|
|
71
|
+
totalTokens: totalAll,
|
|
72
|
+
cost: estimateCost(totalInput, totalOutput),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Service for tracking token consumption across agent runs and spans.
|
|
78
|
+
*
|
|
79
|
+
* Responsibilities:
|
|
80
|
+
* 1. Parse token usage from LangGraph execution results
|
|
81
|
+
* 2. Persist token counts to agentExecutionSpans
|
|
82
|
+
* 3. Accumulate totals at the agentLoopRuns level
|
|
83
|
+
* 4. Enforce budget limits (max tokens / max cost per run)
|
|
84
|
+
*/
|
|
85
|
+
export class TokenTracker {
|
|
86
|
+
constructor(private readonly plugin: any) {}
|
|
87
|
+
|
|
88
|
+
get db() {
|
|
89
|
+
return this.plugin.db;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get app() {
|
|
93
|
+
return this.plugin.app;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Track token usage for a single execution span.
|
|
98
|
+
* Updates the span record and accumulates into the parent run.
|
|
99
|
+
*
|
|
100
|
+
* @param spanId - The agentExecutionSpans.id to update
|
|
101
|
+
* @param usage - Parsed token usage from extractTokenUsage()
|
|
102
|
+
* @param runId - Optional agentLoopRuns.id to accumulate totals
|
|
103
|
+
*/
|
|
104
|
+
async trackSpan(spanId: string | number | undefined, usage: TokenUsage, runId?: string | number): Promise<void> {
|
|
105
|
+
if (!spanId) return;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const repo = this.db.getRepository('agentExecutionSpans');
|
|
109
|
+
if (!repo) return;
|
|
110
|
+
|
|
111
|
+
await repo.update({
|
|
112
|
+
filterByTk: spanId,
|
|
113
|
+
values: {
|
|
114
|
+
inputTokens: usage.inputTokens,
|
|
115
|
+
outputTokens: usage.outputTokens,
|
|
116
|
+
totalTokens: usage.totalTokens,
|
|
117
|
+
cost: usage.cost,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (runId != null) {
|
|
122
|
+
await this.accumulateToRun(runId);
|
|
123
|
+
}
|
|
124
|
+
} catch (e: any) {
|
|
125
|
+
this.app.log?.warn?.('[TokenTracker] Failed to track span tokens', e);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Recalculate total token/cost for a run by summing all its spans.
|
|
131
|
+
*/
|
|
132
|
+
async accumulateToRun(runId: string | number): Promise<void> {
|
|
133
|
+
try {
|
|
134
|
+
const spansRepo = this.db.getRepository('agentExecutionSpans');
|
|
135
|
+
if (!spansRepo) return;
|
|
136
|
+
|
|
137
|
+
const spans = await spansRepo.find({
|
|
138
|
+
filter: { 'metadata.agentLoopRunId': String(runId) },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
let totalInput = 0;
|
|
142
|
+
let totalOutput = 0;
|
|
143
|
+
let totalCost = 0;
|
|
144
|
+
|
|
145
|
+
for (const span of spans) {
|
|
146
|
+
totalInput += Number(span.get?.('inputTokens') || span.inputTokens || 0);
|
|
147
|
+
totalOutput += Number(span.get?.('outputTokens') || span.outputTokens || 0);
|
|
148
|
+
totalCost += Number(span.get?.('cost') || span.cost || 0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const runsRepo = this.db.getRepository('agentLoopRuns');
|
|
152
|
+
if (!runsRepo) return;
|
|
153
|
+
|
|
154
|
+
await runsRepo.update({
|
|
155
|
+
filterByTk: runId,
|
|
156
|
+
values: {
|
|
157
|
+
totalInputTokens: totalInput,
|
|
158
|
+
totalOutputTokens: totalOutput,
|
|
159
|
+
totalTokens: totalInput + totalOutput,
|
|
160
|
+
totalCost,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
} catch (e: any) {
|
|
164
|
+
this.app.log?.warn?.('[TokenTracker] Failed to accumulate run totals', e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if a run has exceeded its budget limits.
|
|
170
|
+
*/
|
|
171
|
+
async checkBudget(runId: string | number): Promise<BudgetCheckResult> {
|
|
172
|
+
try {
|
|
173
|
+
const repo = this.db.getRepository('agentLoopRuns');
|
|
174
|
+
if (!repo) return { allowed: true };
|
|
175
|
+
|
|
176
|
+
const run = await repo.findOne({ filter: { id: runId } });
|
|
177
|
+
if (!run) return { allowed: true };
|
|
178
|
+
|
|
179
|
+
const budgetMaxTokens = Number(run.get?.('budgetMaxTokens') ?? 0);
|
|
180
|
+
const budgetMaxCost = Number(run.get?.('budgetMaxCost') ?? 0);
|
|
181
|
+
|
|
182
|
+
if (budgetMaxTokens <= 0 && budgetMaxCost <= 0) return { allowed: true };
|
|
183
|
+
|
|
184
|
+
const totalTokens = Number(run.get?.('totalTokens') || 0);
|
|
185
|
+
const totalCost = Number(run.get?.('totalCost') || 0);
|
|
186
|
+
|
|
187
|
+
if (budgetMaxTokens > 0 && totalTokens >= budgetMaxTokens) {
|
|
188
|
+
return {
|
|
189
|
+
allowed: false,
|
|
190
|
+
reason: `Budget exceeded: ${totalTokens}/${budgetMaxTokens} tokens used. Maximum allowed tokens for this run: ${budgetMaxTokens}.`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (budgetMaxCost > 0 && totalCost >= budgetMaxCost) {
|
|
195
|
+
return {
|
|
196
|
+
allowed: false,
|
|
197
|
+
reason: `Budget exceeded: $${totalCost.toFixed(4)}/$${budgetMaxCost.toFixed(
|
|
198
|
+
4,
|
|
199
|
+
)} spent. Maximum allowed cost for this run: $${budgetMaxCost}.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { allowed: true };
|
|
204
|
+
} catch (e: any) {
|
|
205
|
+
this.app.log?.warn?.('[TokenTracker] Budget check failed, allowing', e);
|
|
206
|
+
return { allowed: true };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|