opencode-swarm 5.1.0 → 5.1.3
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/commands/benchmark.d.ts +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/retrieve.d.ts +5 -0
- package/dist/config/schema.d.ts +19 -0
- package/dist/hooks/delegation-gate.d.ts +29 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/tool-summarizer.d.ts +28 -0
- package/dist/index.js +669 -90
- package/dist/summaries/index.d.ts +2 -0
- package/dist/summaries/manager.d.ts +29 -0
- package/dist/summaries/summarizer.d.ts +38 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleBenchmarkCommand(directory: string, args: string[]): Promise<string>;
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentDefinition } from '../agents';
|
|
2
2
|
export { handleAgentsCommand } from './agents';
|
|
3
3
|
export { handleArchiveCommand } from './archive';
|
|
4
|
+
export { handleBenchmarkCommand } from './benchmark';
|
|
4
5
|
export { handleConfigCommand } from './config';
|
|
5
6
|
export { handleDiagnoseCommand } from './diagnose';
|
|
6
7
|
export { handleEvidenceCommand } from './evidence';
|
|
@@ -8,6 +9,7 @@ export { handleExportCommand } from './export';
|
|
|
8
9
|
export { handleHistoryCommand } from './history';
|
|
9
10
|
export { handlePlanCommand } from './plan';
|
|
10
11
|
export { handleResetCommand } from './reset';
|
|
12
|
+
export { handleRetrieveCommand } from './retrieve';
|
|
11
13
|
export { handleStatusCommand } from './status';
|
|
12
14
|
/**
|
|
13
15
|
* Creates a command.execute.before handler for /swarm commands.
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare const HooksConfigSchema: z.ZodObject<{
|
|
|
20
20
|
agent_activity: z.ZodDefault<z.ZodBoolean>;
|
|
21
21
|
delegation_tracker: z.ZodDefault<z.ZodBoolean>;
|
|
22
22
|
agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
delegation_gate: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
delegation_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
23
25
|
}, z.core.$strip>;
|
|
24
26
|
export type HooksConfig = z.infer<typeof HooksConfigSchema>;
|
|
25
27
|
export declare const ScoringWeightsSchema: z.ZodObject<{
|
|
@@ -118,6 +120,14 @@ export declare const EvidenceConfigSchema: z.ZodObject<{
|
|
|
118
120
|
auto_archive: z.ZodDefault<z.ZodBoolean>;
|
|
119
121
|
}, z.core.$strip>;
|
|
120
122
|
export type EvidenceConfig = z.infer<typeof EvidenceConfigSchema>;
|
|
123
|
+
export declare const SummaryConfigSchema: z.ZodObject<{
|
|
124
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
125
|
+
threshold_bytes: z.ZodDefault<z.ZodNumber>;
|
|
126
|
+
max_summary_chars: z.ZodDefault<z.ZodNumber>;
|
|
127
|
+
max_stored_bytes: z.ZodDefault<z.ZodNumber>;
|
|
128
|
+
retention_days: z.ZodDefault<z.ZodNumber>;
|
|
129
|
+
}, z.core.$strip>;
|
|
130
|
+
export type SummaryConfig = z.infer<typeof SummaryConfigSchema>;
|
|
121
131
|
export declare const GuardrailsProfileSchema: z.ZodObject<{
|
|
122
132
|
max_tool_calls: z.ZodOptional<z.ZodNumber>;
|
|
123
133
|
max_duration_minutes: z.ZodOptional<z.ZodNumber>;
|
|
@@ -200,6 +210,8 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
200
210
|
agent_activity: z.ZodDefault<z.ZodBoolean>;
|
|
201
211
|
delegation_tracker: z.ZodDefault<z.ZodBoolean>;
|
|
202
212
|
agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
213
|
+
delegation_gate: z.ZodDefault<z.ZodBoolean>;
|
|
214
|
+
delegation_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
203
215
|
}, z.core.$strip>>;
|
|
204
216
|
context_budget: z.ZodOptional<z.ZodObject<{
|
|
205
217
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -258,6 +270,13 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
258
270
|
max_bundles: z.ZodDefault<z.ZodNumber>;
|
|
259
271
|
auto_archive: z.ZodDefault<z.ZodBoolean>;
|
|
260
272
|
}, z.core.$strip>>;
|
|
273
|
+
summaries: z.ZodOptional<z.ZodObject<{
|
|
274
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
275
|
+
threshold_bytes: z.ZodDefault<z.ZodNumber>;
|
|
276
|
+
max_summary_chars: z.ZodDefault<z.ZodNumber>;
|
|
277
|
+
max_stored_bytes: z.ZodDefault<z.ZodNumber>;
|
|
278
|
+
retention_days: z.ZodDefault<z.ZodNumber>;
|
|
279
|
+
}, z.core.$strip>>;
|
|
261
280
|
}, z.core.$strip>;
|
|
262
281
|
export type PluginConfig = z.infer<typeof PluginConfigSchema>;
|
|
263
282
|
export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Gate Hook
|
|
3
|
+
*
|
|
4
|
+
* Warns the architect when coder delegations are too large or batched.
|
|
5
|
+
* Uses experimental.chat.messages.transform to provide non-blocking guidance.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginConfig } from '../config';
|
|
8
|
+
interface MessageInfo {
|
|
9
|
+
role: string;
|
|
10
|
+
agent?: string;
|
|
11
|
+
sessionID?: string;
|
|
12
|
+
}
|
|
13
|
+
interface MessagePart {
|
|
14
|
+
type: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
interface MessageWithParts {
|
|
19
|
+
info: MessageInfo;
|
|
20
|
+
parts: MessagePart[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Creates the experimental.chat.messages.transform hook for delegation gating.
|
|
24
|
+
* Inspects coder delegations and warns when tasks are oversized or batched.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDelegationGateHook(config: PluginConfig): (input: Record<string, never>, output: {
|
|
27
|
+
messages?: MessageWithParts[];
|
|
28
|
+
}) => Promise<void>;
|
|
29
|
+
export {};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { createAgentActivityHooks } from './agent-activity';
|
|
2
2
|
export { createCompactionCustomizerHook } from './compaction-customizer';
|
|
3
3
|
export { createContextBudgetHandler } from './context-budget';
|
|
4
|
+
export { createDelegationGateHook } from './delegation-gate';
|
|
4
5
|
export { createDelegationTrackerHook } from './delegation-tracker';
|
|
5
6
|
export { extractCurrentPhase, extractCurrentPhaseFromPlan, extractCurrentTask, extractCurrentTaskFromPlan, extractDecisions, extractIncompleteTasks, extractIncompleteTasksFromPlan, extractPatterns, } from './extractors';
|
|
6
7
|
export { createGuardrailsHooks } from './guardrails';
|
|
7
8
|
export { createPipelineTrackerHook } from './pipeline-tracker';
|
|
8
9
|
export { createSystemEnhancerHook } from './system-enhancer';
|
|
10
|
+
export { createToolSummarizerHook, resetSummaryIdCounter, } from './tool-summarizer';
|
|
9
11
|
export { composeHandlers, estimateTokens, readSwarmFileAsync, safeHook, validateSwarmPath, } from './utils';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Output Summarizer Hook
|
|
3
|
+
*
|
|
4
|
+
* Intercepts oversized tool outputs in tool.execute.after,
|
|
5
|
+
* stores the full content to .swarm/summaries/, and replaces
|
|
6
|
+
* the output with a compact summary containing a retrieval ID.
|
|
7
|
+
*/
|
|
8
|
+
import type { SummaryConfig } from '../config/schema';
|
|
9
|
+
/**
|
|
10
|
+
* Reset the summary ID counter. Used for testing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resetSummaryIdCounter(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a tool.execute.after hook that summarizes oversized tool outputs.
|
|
15
|
+
*
|
|
16
|
+
* @param config - Summary configuration including enabled, thresholds, and limits
|
|
17
|
+
* @param directory - Base directory for storing full outputs
|
|
18
|
+
* @returns Async hook function for tool.execute.after
|
|
19
|
+
*/
|
|
20
|
+
export declare function createToolSummarizerHook(config: SummaryConfig, directory: string): (input: {
|
|
21
|
+
tool: string;
|
|
22
|
+
sessionID: string;
|
|
23
|
+
callID: string;
|
|
24
|
+
}, output: {
|
|
25
|
+
title: string;
|
|
26
|
+
output: string;
|
|
27
|
+
metadata: unknown;
|
|
28
|
+
}) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -13578,7 +13578,9 @@ var HooksConfigSchema = exports_external.object({
|
|
|
13578
13578
|
compaction: exports_external.boolean().default(true),
|
|
13579
13579
|
agent_activity: exports_external.boolean().default(true),
|
|
13580
13580
|
delegation_tracker: exports_external.boolean().default(false),
|
|
13581
|
-
agent_awareness_max_chars: exports_external.number().min(50).max(2000).default(300)
|
|
13581
|
+
agent_awareness_max_chars: exports_external.number().min(50).max(2000).default(300),
|
|
13582
|
+
delegation_gate: exports_external.boolean().default(true),
|
|
13583
|
+
delegation_max_chars: exports_external.number().min(500).max(20000).default(4000)
|
|
13582
13584
|
});
|
|
13583
13585
|
var ScoringWeightsSchema = exports_external.object({
|
|
13584
13586
|
phase: exports_external.number().min(0).max(5).default(1),
|
|
@@ -13621,6 +13623,13 @@ var EvidenceConfigSchema = exports_external.object({
|
|
|
13621
13623
|
max_bundles: exports_external.number().min(10).max(1e4).default(1000),
|
|
13622
13624
|
auto_archive: exports_external.boolean().default(false)
|
|
13623
13625
|
});
|
|
13626
|
+
var SummaryConfigSchema = exports_external.object({
|
|
13627
|
+
enabled: exports_external.boolean().default(true),
|
|
13628
|
+
threshold_bytes: exports_external.number().min(1024).max(1048576).default(20480),
|
|
13629
|
+
max_summary_chars: exports_external.number().min(100).max(5000).default(1000),
|
|
13630
|
+
max_stored_bytes: exports_external.number().min(10240).max(104857600).default(10485760),
|
|
13631
|
+
retention_days: exports_external.number().min(1).max(365).default(7)
|
|
13632
|
+
});
|
|
13624
13633
|
var GuardrailsProfileSchema = exports_external.object({
|
|
13625
13634
|
max_tool_calls: exports_external.number().min(0).max(1000).optional(),
|
|
13626
13635
|
max_duration_minutes: exports_external.number().min(0).max(480).optional(),
|
|
@@ -13714,7 +13723,8 @@ var PluginConfigSchema = exports_external.object({
|
|
|
13714
13723
|
hooks: HooksConfigSchema.optional(),
|
|
13715
13724
|
context_budget: ContextBudgetConfigSchema.optional(),
|
|
13716
13725
|
guardrails: GuardrailsConfigSchema.optional(),
|
|
13717
|
-
evidence: EvidenceConfigSchema.optional()
|
|
13726
|
+
evidence: EvidenceConfigSchema.optional(),
|
|
13727
|
+
summaries: SummaryConfigSchema.optional()
|
|
13718
13728
|
});
|
|
13719
13729
|
|
|
13720
13730
|
// src/config/loader.ts
|
|
@@ -15012,6 +15022,282 @@ async function handleArchiveCommand(directory, args) {
|
|
|
15012
15022
|
`);
|
|
15013
15023
|
}
|
|
15014
15024
|
|
|
15025
|
+
// src/state.ts
|
|
15026
|
+
var swarmState = {
|
|
15027
|
+
activeToolCalls: new Map,
|
|
15028
|
+
toolAggregates: new Map,
|
|
15029
|
+
activeAgent: new Map,
|
|
15030
|
+
delegationChains: new Map,
|
|
15031
|
+
pendingEvents: 0,
|
|
15032
|
+
agentSessions: new Map
|
|
15033
|
+
};
|
|
15034
|
+
function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
15035
|
+
const now = Date.now();
|
|
15036
|
+
const staleIds = [];
|
|
15037
|
+
for (const [id, session] of swarmState.agentSessions) {
|
|
15038
|
+
if (now - session.lastToolCallTime > staleDurationMs) {
|
|
15039
|
+
staleIds.push(id);
|
|
15040
|
+
}
|
|
15041
|
+
}
|
|
15042
|
+
for (const id of staleIds) {
|
|
15043
|
+
swarmState.agentSessions.delete(id);
|
|
15044
|
+
}
|
|
15045
|
+
const sessionState = {
|
|
15046
|
+
agentName,
|
|
15047
|
+
startTime: now,
|
|
15048
|
+
lastToolCallTime: now,
|
|
15049
|
+
toolCallCount: 0,
|
|
15050
|
+
consecutiveErrors: 0,
|
|
15051
|
+
recentToolCalls: [],
|
|
15052
|
+
warningIssued: false,
|
|
15053
|
+
warningReason: "",
|
|
15054
|
+
hardLimitHit: false,
|
|
15055
|
+
lastSuccessTime: now,
|
|
15056
|
+
delegationActive: false
|
|
15057
|
+
};
|
|
15058
|
+
swarmState.agentSessions.set(sessionId, sessionState);
|
|
15059
|
+
}
|
|
15060
|
+
function getAgentSession(sessionId) {
|
|
15061
|
+
return swarmState.agentSessions.get(sessionId);
|
|
15062
|
+
}
|
|
15063
|
+
function ensureAgentSession(sessionId, agentName) {
|
|
15064
|
+
const now = Date.now();
|
|
15065
|
+
let session = swarmState.agentSessions.get(sessionId);
|
|
15066
|
+
if (session) {
|
|
15067
|
+
if (agentName && agentName !== session.agentName) {
|
|
15068
|
+
session.agentName = agentName;
|
|
15069
|
+
session.startTime = now;
|
|
15070
|
+
session.toolCallCount = 0;
|
|
15071
|
+
session.consecutiveErrors = 0;
|
|
15072
|
+
session.recentToolCalls = [];
|
|
15073
|
+
session.warningIssued = false;
|
|
15074
|
+
session.warningReason = "";
|
|
15075
|
+
session.hardLimitHit = false;
|
|
15076
|
+
session.lastSuccessTime = now;
|
|
15077
|
+
session.delegationActive = false;
|
|
15078
|
+
}
|
|
15079
|
+
session.lastToolCallTime = now;
|
|
15080
|
+
return session;
|
|
15081
|
+
}
|
|
15082
|
+
startAgentSession(sessionId, agentName ?? "unknown");
|
|
15083
|
+
session = swarmState.agentSessions.get(sessionId);
|
|
15084
|
+
if (!session) {
|
|
15085
|
+
throw new Error(`Failed to create guardrail session for ${sessionId}`);
|
|
15086
|
+
}
|
|
15087
|
+
return session;
|
|
15088
|
+
}
|
|
15089
|
+
|
|
15090
|
+
// src/commands/benchmark.ts
|
|
15091
|
+
var CI = {
|
|
15092
|
+
review_pass_rate: 70,
|
|
15093
|
+
test_pass_rate: 80,
|
|
15094
|
+
max_agent_error_rate: 20,
|
|
15095
|
+
max_hard_limit_hits: 1
|
|
15096
|
+
};
|
|
15097
|
+
async function handleBenchmarkCommand(directory, args) {
|
|
15098
|
+
let cumulative = args.includes("--cumulative");
|
|
15099
|
+
if (args.includes("--ci-gate"))
|
|
15100
|
+
cumulative = true;
|
|
15101
|
+
const mode = cumulative ? "cumulative" : "in-memory";
|
|
15102
|
+
const agentMap = new Map;
|
|
15103
|
+
for (const [, s] of swarmState.agentSessions) {
|
|
15104
|
+
const e = agentMap.get(s.agentName) || {
|
|
15105
|
+
toolCalls: 0,
|
|
15106
|
+
hardLimits: 0,
|
|
15107
|
+
warnings: 0
|
|
15108
|
+
};
|
|
15109
|
+
e.toolCalls += s.toolCallCount;
|
|
15110
|
+
if (s.hardLimitHit)
|
|
15111
|
+
e.hardLimits++;
|
|
15112
|
+
if (s.warningIssued)
|
|
15113
|
+
e.warnings++;
|
|
15114
|
+
agentMap.set(s.agentName, e);
|
|
15115
|
+
}
|
|
15116
|
+
const agentHealth = Array.from(agentMap.entries()).map(([a, v]) => ({
|
|
15117
|
+
agent: a,
|
|
15118
|
+
...v
|
|
15119
|
+
}));
|
|
15120
|
+
const toolPerf = [];
|
|
15121
|
+
for (const [, a] of swarmState.toolAggregates) {
|
|
15122
|
+
const successRate = a.count ? a.successCount / a.count * 100 : 0;
|
|
15123
|
+
toolPerf.push({
|
|
15124
|
+
tool: a.tool,
|
|
15125
|
+
calls: a.count,
|
|
15126
|
+
successRate: Math.round(successRate * 10) / 10,
|
|
15127
|
+
avg: a.count ? Math.round(a.totalDuration / a.count) : 0
|
|
15128
|
+
});
|
|
15129
|
+
}
|
|
15130
|
+
toolPerf.sort((a, b) => b.calls - a.calls);
|
|
15131
|
+
let delegationCount = 0;
|
|
15132
|
+
for (const c of swarmState.delegationChains.values())
|
|
15133
|
+
delegationCount += c.length;
|
|
15134
|
+
let quality;
|
|
15135
|
+
if (cumulative) {
|
|
15136
|
+
let reviewPasses = 0, reviewFails = 0, testPasses = 0, testFails = 0, additions = 0, deletions = 0;
|
|
15137
|
+
for (const tid of await listEvidenceTaskIds(directory)) {
|
|
15138
|
+
const b = await loadEvidence(directory, tid);
|
|
15139
|
+
if (!b)
|
|
15140
|
+
continue;
|
|
15141
|
+
for (const e of b.entries) {
|
|
15142
|
+
if (e.type === "review") {
|
|
15143
|
+
if (e.verdict === "approved")
|
|
15144
|
+
reviewPasses++;
|
|
15145
|
+
else if (e.verdict === "rejected")
|
|
15146
|
+
reviewFails++;
|
|
15147
|
+
} else if (e.type === "test") {
|
|
15148
|
+
testPasses += e.tests_passed;
|
|
15149
|
+
testFails += e.tests_failed;
|
|
15150
|
+
} else if (e.type === "diff") {
|
|
15151
|
+
additions += e.additions;
|
|
15152
|
+
deletions += e.deletions;
|
|
15153
|
+
}
|
|
15154
|
+
}
|
|
15155
|
+
}
|
|
15156
|
+
const totalReviews = reviewPasses + reviewFails, totalTests = testPasses + testFails;
|
|
15157
|
+
quality = {
|
|
15158
|
+
reviewPassRate: totalReviews ? Math.round(reviewPasses / totalReviews * 1000) / 10 : null,
|
|
15159
|
+
testPassRate: totalTests ? Math.round(testPasses / totalTests * 1000) / 10 : null,
|
|
15160
|
+
totalReviews,
|
|
15161
|
+
testsPassed: testPasses,
|
|
15162
|
+
testsFailed: testFails,
|
|
15163
|
+
additions,
|
|
15164
|
+
deletions
|
|
15165
|
+
};
|
|
15166
|
+
}
|
|
15167
|
+
let ciGate;
|
|
15168
|
+
if (args.includes("--ci-gate")) {
|
|
15169
|
+
let totalCalls = 0, totalFailures = 0;
|
|
15170
|
+
for (const [, a] of swarmState.toolAggregates) {
|
|
15171
|
+
totalCalls += a.count;
|
|
15172
|
+
totalFailures += a.failureCount;
|
|
15173
|
+
}
|
|
15174
|
+
const agentErrorRate = totalCalls ? totalFailures / totalCalls * 100 : 0;
|
|
15175
|
+
let maxHardLimits = 0;
|
|
15176
|
+
for (const v of agentMap.values())
|
|
15177
|
+
if (v.hardLimits > maxHardLimits)
|
|
15178
|
+
maxHardLimits = v.hardLimits;
|
|
15179
|
+
const checks3 = [
|
|
15180
|
+
{
|
|
15181
|
+
name: "Review pass rate",
|
|
15182
|
+
value: quality?.reviewPassRate ?? 0,
|
|
15183
|
+
threshold: CI.review_pass_rate,
|
|
15184
|
+
operator: ">=",
|
|
15185
|
+
passed: (quality?.reviewPassRate ?? 0) >= CI.review_pass_rate
|
|
15186
|
+
},
|
|
15187
|
+
{
|
|
15188
|
+
name: "Test pass rate",
|
|
15189
|
+
value: quality?.testPassRate ?? 0,
|
|
15190
|
+
threshold: CI.test_pass_rate,
|
|
15191
|
+
operator: ">=",
|
|
15192
|
+
passed: (quality?.testPassRate ?? 0) >= CI.test_pass_rate
|
|
15193
|
+
},
|
|
15194
|
+
{
|
|
15195
|
+
name: "Agent error rate",
|
|
15196
|
+
value: Math.round(agentErrorRate * 10) / 10,
|
|
15197
|
+
threshold: CI.max_agent_error_rate,
|
|
15198
|
+
operator: "<=",
|
|
15199
|
+
passed: agentErrorRate <= CI.max_agent_error_rate
|
|
15200
|
+
},
|
|
15201
|
+
{
|
|
15202
|
+
name: "Hard limit hits",
|
|
15203
|
+
value: maxHardLimits,
|
|
15204
|
+
threshold: CI.max_hard_limit_hits,
|
|
15205
|
+
operator: "<=",
|
|
15206
|
+
passed: maxHardLimits <= CI.max_hard_limit_hits
|
|
15207
|
+
}
|
|
15208
|
+
];
|
|
15209
|
+
ciGate = { passed: checks3.every((c) => c.passed), checks: checks3 };
|
|
15210
|
+
}
|
|
15211
|
+
const lines = [
|
|
15212
|
+
`## Swarm Benchmark (mode: ${mode})`,
|
|
15213
|
+
"",
|
|
15214
|
+
"### Agent Health"
|
|
15215
|
+
];
|
|
15216
|
+
if (!agentHealth.length)
|
|
15217
|
+
lines.push("No agent sessions recorded");
|
|
15218
|
+
else
|
|
15219
|
+
for (const { agent, toolCalls, hardLimits, warnings } of agentHealth) {
|
|
15220
|
+
const parts = [`${toolCalls} tool calls`];
|
|
15221
|
+
if (warnings > 0)
|
|
15222
|
+
parts.push(`${warnings} warning${warnings > 1 ? "s" : ""}`);
|
|
15223
|
+
parts.push(hardLimits ? `${hardLimits} hard limit hit${hardLimits > 1 ? "s" : ""}` : "0 hard limits");
|
|
15224
|
+
lines.push(`- ${hardLimits ? "\u26A0\uFE0F" : "\u2705"} **${agent}**: ${parts.join(", ")}`);
|
|
15225
|
+
}
|
|
15226
|
+
lines.push("", "### Tool Performance");
|
|
15227
|
+
if (!toolPerf.length)
|
|
15228
|
+
lines.push("No tool data recorded");
|
|
15229
|
+
else {
|
|
15230
|
+
lines.push("| Tool | Calls | Success Rate | Avg Duration |", "|------|-------|-------------|-------------|");
|
|
15231
|
+
for (const { tool, calls, successRate, avg } of toolPerf)
|
|
15232
|
+
lines.push(`| ${tool} | ${calls} | ${successRate}% | ${avg}ms |`);
|
|
15233
|
+
}
|
|
15234
|
+
lines.push("", "### Delegations", delegationCount ? `Total: ${delegationCount} delegations` : "No delegations recorded", "");
|
|
15235
|
+
if (quality) {
|
|
15236
|
+
lines.push("### Quality Signals");
|
|
15237
|
+
if (!quality.totalReviews && !quality.testsPassed && !quality.additions)
|
|
15238
|
+
lines.push("No evidence data found");
|
|
15239
|
+
else {
|
|
15240
|
+
if (quality.reviewPassRate !== null)
|
|
15241
|
+
lines.push(`- Review pass rate: ${quality.reviewPassRate}% (${quality.totalReviews}) ${quality.reviewPassRate >= 70 ? "\u2705" : "\u274C"}`);
|
|
15242
|
+
else
|
|
15243
|
+
lines.push("- Review pass rate: N/A (no reviews)");
|
|
15244
|
+
if (quality.testPassRate !== null)
|
|
15245
|
+
lines.push(`- Test pass rate: ${quality.testPassRate}% (${quality.testsPassed}/${quality.testsPassed + quality.testsFailed}) ${quality.testPassRate >= 80 ? "\u2705" : "\u274C"}`);
|
|
15246
|
+
else
|
|
15247
|
+
lines.push("- Test pass rate: N/A (no tests)");
|
|
15248
|
+
lines.push(`- Code churn: +${quality.additions} / -${quality.deletions} lines`);
|
|
15249
|
+
}
|
|
15250
|
+
lines.push("");
|
|
15251
|
+
}
|
|
15252
|
+
if (ciGate) {
|
|
15253
|
+
lines.push("### CI Gate", ciGate.passed ? "\u2705 PASSED" : "\u274C FAILED");
|
|
15254
|
+
for (const c of ciGate.checks)
|
|
15255
|
+
lines.push(`- ${c.name}: ${c.value}% ${c.operator} ${c.threshold}% ${c.passed ? "\u2705" : "\u274C"}`);
|
|
15256
|
+
lines.push("");
|
|
15257
|
+
}
|
|
15258
|
+
const json2 = {
|
|
15259
|
+
mode,
|
|
15260
|
+
timestamp: new Date().toISOString(),
|
|
15261
|
+
agent_health: agentHealth.map((a) => ({
|
|
15262
|
+
agent: a.agent,
|
|
15263
|
+
tool_calls: a.toolCalls,
|
|
15264
|
+
hard_limit_hits: a.hardLimits,
|
|
15265
|
+
warnings: a.warnings
|
|
15266
|
+
})),
|
|
15267
|
+
tool_performance: toolPerf.map((t) => ({
|
|
15268
|
+
tool: t.tool,
|
|
15269
|
+
calls: t.calls,
|
|
15270
|
+
success_rate: t.successRate,
|
|
15271
|
+
avg_duration_ms: t.avg
|
|
15272
|
+
})),
|
|
15273
|
+
delegations: delegationCount
|
|
15274
|
+
};
|
|
15275
|
+
if (quality)
|
|
15276
|
+
json2.quality = {
|
|
15277
|
+
review_pass_rate: quality.reviewPassRate,
|
|
15278
|
+
test_pass_rate: quality.testPassRate,
|
|
15279
|
+
total_reviews: quality.totalReviews,
|
|
15280
|
+
total_tests_passed: quality.testsPassed,
|
|
15281
|
+
total_tests_failed: quality.testsFailed,
|
|
15282
|
+
additions: quality.additions,
|
|
15283
|
+
deletions: quality.deletions
|
|
15284
|
+
};
|
|
15285
|
+
if (ciGate)
|
|
15286
|
+
json2.ci_gate = {
|
|
15287
|
+
passed: ciGate.passed,
|
|
15288
|
+
checks: ciGate.checks.map((c) => ({
|
|
15289
|
+
name: c.name,
|
|
15290
|
+
value: c.value,
|
|
15291
|
+
threshold: c.threshold,
|
|
15292
|
+
operator: c.operator,
|
|
15293
|
+
passed: c.passed
|
|
15294
|
+
}))
|
|
15295
|
+
};
|
|
15296
|
+
lines.push("[BENCHMARK_JSON]", JSON.stringify(json2, null, 2), "[/BENCHMARK_JSON]");
|
|
15297
|
+
return lines.join(`
|
|
15298
|
+
`);
|
|
15299
|
+
}
|
|
15300
|
+
|
|
15015
15301
|
// src/commands/config.ts
|
|
15016
15302
|
import * as os2 from "os";
|
|
15017
15303
|
import * as path4 from "path";
|
|
@@ -15722,6 +16008,17 @@ async function handleResetCommand(directory, args) {
|
|
|
15722
16008
|
results.push(`- \u274C Failed to delete ${filename}`);
|
|
15723
16009
|
}
|
|
15724
16010
|
}
|
|
16011
|
+
try {
|
|
16012
|
+
const summariesPath = validateSwarmPath(directory, "summaries");
|
|
16013
|
+
if (fs2.existsSync(summariesPath)) {
|
|
16014
|
+
fs2.rmSync(summariesPath, { recursive: true, force: true });
|
|
16015
|
+
results.push("- \u2705 Deleted summaries/ directory");
|
|
16016
|
+
} else {
|
|
16017
|
+
results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
|
|
16018
|
+
}
|
|
16019
|
+
} catch {
|
|
16020
|
+
results.push("- \u274C Failed to delete summaries/");
|
|
16021
|
+
}
|
|
15725
16022
|
return [
|
|
15726
16023
|
"## Swarm Reset Complete",
|
|
15727
16024
|
"",
|
|
@@ -15732,6 +16029,112 @@ async function handleResetCommand(directory, args) {
|
|
|
15732
16029
|
`);
|
|
15733
16030
|
}
|
|
15734
16031
|
|
|
16032
|
+
// src/summaries/manager.ts
|
|
16033
|
+
import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync3 } from "fs";
|
|
16034
|
+
import * as path6 from "path";
|
|
16035
|
+
var SUMMARY_ID_REGEX = /^S\d+$/;
|
|
16036
|
+
function sanitizeSummaryId(id) {
|
|
16037
|
+
if (!id || id.length === 0) {
|
|
16038
|
+
throw new Error("Invalid summary ID: empty string");
|
|
16039
|
+
}
|
|
16040
|
+
if (/\0/.test(id)) {
|
|
16041
|
+
throw new Error("Invalid summary ID: contains null bytes");
|
|
16042
|
+
}
|
|
16043
|
+
for (let i = 0;i < id.length; i++) {
|
|
16044
|
+
if (id.charCodeAt(i) < 32) {
|
|
16045
|
+
throw new Error("Invalid summary ID: contains control characters");
|
|
16046
|
+
}
|
|
16047
|
+
}
|
|
16048
|
+
if (id.includes("..") || id.includes("../") || id.includes("..\\")) {
|
|
16049
|
+
throw new Error("Invalid summary ID: path traversal detected");
|
|
16050
|
+
}
|
|
16051
|
+
if (!SUMMARY_ID_REGEX.test(id)) {
|
|
16052
|
+
throw new Error(`Invalid summary ID: must match pattern ^S\\d+$, got "${id}"`);
|
|
16053
|
+
}
|
|
16054
|
+
return id;
|
|
16055
|
+
}
|
|
16056
|
+
async function storeSummary(directory, id, fullOutput, summaryText, maxStoredBytes) {
|
|
16057
|
+
const sanitizedId = sanitizeSummaryId(id);
|
|
16058
|
+
const outputBytes = Buffer.byteLength(fullOutput, "utf8");
|
|
16059
|
+
if (outputBytes > maxStoredBytes) {
|
|
16060
|
+
throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
|
|
16061
|
+
}
|
|
16062
|
+
const relativePath = path6.join("summaries", `${sanitizedId}.json`);
|
|
16063
|
+
const summaryPath = validateSwarmPath(directory, relativePath);
|
|
16064
|
+
const summaryDir = path6.dirname(summaryPath);
|
|
16065
|
+
const entry = {
|
|
16066
|
+
id: sanitizedId,
|
|
16067
|
+
summaryText,
|
|
16068
|
+
fullOutput,
|
|
16069
|
+
timestamp: Date.now(),
|
|
16070
|
+
originalBytes: outputBytes
|
|
16071
|
+
};
|
|
16072
|
+
const entryJson = JSON.stringify(entry);
|
|
16073
|
+
mkdirSync2(summaryDir, { recursive: true });
|
|
16074
|
+
const tempPath = path6.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
|
|
16075
|
+
try {
|
|
16076
|
+
await Bun.write(tempPath, entryJson);
|
|
16077
|
+
renameSync2(tempPath, summaryPath);
|
|
16078
|
+
} catch (error49) {
|
|
16079
|
+
try {
|
|
16080
|
+
rmSync3(tempPath, { force: true });
|
|
16081
|
+
} catch {}
|
|
16082
|
+
throw error49;
|
|
16083
|
+
}
|
|
16084
|
+
}
|
|
16085
|
+
async function loadFullOutput(directory, id) {
|
|
16086
|
+
const sanitizedId = sanitizeSummaryId(id);
|
|
16087
|
+
const relativePath = path6.join("summaries", `${sanitizedId}.json`);
|
|
16088
|
+
validateSwarmPath(directory, relativePath);
|
|
16089
|
+
const content = await readSwarmFileAsync(directory, relativePath);
|
|
16090
|
+
if (content === null) {
|
|
16091
|
+
return null;
|
|
16092
|
+
}
|
|
16093
|
+
try {
|
|
16094
|
+
const parsed = JSON.parse(content);
|
|
16095
|
+
if (typeof parsed.fullOutput === "string") {
|
|
16096
|
+
return parsed.fullOutput;
|
|
16097
|
+
}
|
|
16098
|
+
warn(`Summary entry ${sanitizedId} missing valid fullOutput field`);
|
|
16099
|
+
return null;
|
|
16100
|
+
} catch (error49) {
|
|
16101
|
+
warn(`Summary entry validation failed for ${sanitizedId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
16102
|
+
return null;
|
|
16103
|
+
}
|
|
16104
|
+
}
|
|
16105
|
+
|
|
16106
|
+
// src/commands/retrieve.ts
|
|
16107
|
+
async function handleRetrieveCommand(directory, args) {
|
|
16108
|
+
const summaryId = args[0];
|
|
16109
|
+
if (!summaryId) {
|
|
16110
|
+
return [
|
|
16111
|
+
"## Swarm Retrieve",
|
|
16112
|
+
"",
|
|
16113
|
+
"Usage: `/swarm retrieve <id>`",
|
|
16114
|
+
"",
|
|
16115
|
+
"Example: `/swarm retrieve S1`",
|
|
16116
|
+
"",
|
|
16117
|
+
"Retrieves the full output that was replaced by a summary."
|
|
16118
|
+
].join(`
|
|
16119
|
+
`);
|
|
16120
|
+
}
|
|
16121
|
+
try {
|
|
16122
|
+
const fullOutput = await loadFullOutput(directory, summaryId);
|
|
16123
|
+
if (fullOutput === null) {
|
|
16124
|
+
return `## Summary Not Found
|
|
16125
|
+
|
|
16126
|
+
No stored output found for ID \`${summaryId}\`.
|
|
16127
|
+
|
|
16128
|
+
Use a valid summary ID (e.g., S1, S2, S3).`;
|
|
16129
|
+
}
|
|
16130
|
+
return fullOutput;
|
|
16131
|
+
} catch (error49) {
|
|
16132
|
+
return `## Retrieve Failed
|
|
16133
|
+
|
|
16134
|
+
${error49 instanceof Error ? error49.message : String(error49)}`;
|
|
16135
|
+
}
|
|
16136
|
+
}
|
|
16137
|
+
|
|
15735
16138
|
// src/hooks/extractors.ts
|
|
15736
16139
|
function extractCurrentPhase(planContent) {
|
|
15737
16140
|
if (!planContent) {
|
|
@@ -15980,8 +16383,10 @@ var HELP_TEXT = [
|
|
|
15980
16383
|
"- `/swarm evidence [taskId]` \u2014 Show evidence bundles",
|
|
15981
16384
|
"- `/swarm archive [--dry-run]` \u2014 Archive old evidence bundles",
|
|
15982
16385
|
"- `/swarm diagnose` \u2014 Run health check on swarm state",
|
|
16386
|
+
"- `/swarm benchmark [--cumulative] [--ci-gate]` \u2014 Show performance metrics",
|
|
15983
16387
|
"- `/swarm export` \u2014 Export plan and context as JSON",
|
|
15984
|
-
"- `/swarm reset --confirm` \u2014 Clear swarm state files"
|
|
16388
|
+
"- `/swarm reset --confirm` \u2014 Clear swarm state files",
|
|
16389
|
+
"- `/swarm retrieve <id>` \u2014 Retrieve full output from a summary"
|
|
15985
16390
|
].join(`
|
|
15986
16391
|
`);
|
|
15987
16392
|
function createSwarmCommandHandler(directory, agents) {
|
|
@@ -16020,12 +16425,18 @@ function createSwarmCommandHandler(directory, agents) {
|
|
|
16020
16425
|
case "diagnose":
|
|
16021
16426
|
text = await handleDiagnoseCommand(directory, args);
|
|
16022
16427
|
break;
|
|
16428
|
+
case "benchmark":
|
|
16429
|
+
text = await handleBenchmarkCommand(directory, args);
|
|
16430
|
+
break;
|
|
16023
16431
|
case "export":
|
|
16024
16432
|
text = await handleExportCommand(directory, args);
|
|
16025
16433
|
break;
|
|
16026
16434
|
case "reset":
|
|
16027
16435
|
text = await handleResetCommand(directory, args);
|
|
16028
16436
|
break;
|
|
16437
|
+
case "retrieve":
|
|
16438
|
+
text = await handleRetrieveCommand(directory, args);
|
|
16439
|
+
break;
|
|
16029
16440
|
default:
|
|
16030
16441
|
text = HELP_TEXT;
|
|
16031
16442
|
break;
|
|
@@ -16036,71 +16447,6 @@ function createSwarmCommandHandler(directory, agents) {
|
|
|
16036
16447
|
};
|
|
16037
16448
|
}
|
|
16038
16449
|
|
|
16039
|
-
// src/state.ts
|
|
16040
|
-
var swarmState = {
|
|
16041
|
-
activeToolCalls: new Map,
|
|
16042
|
-
toolAggregates: new Map,
|
|
16043
|
-
activeAgent: new Map,
|
|
16044
|
-
delegationChains: new Map,
|
|
16045
|
-
pendingEvents: 0,
|
|
16046
|
-
agentSessions: new Map
|
|
16047
|
-
};
|
|
16048
|
-
function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
16049
|
-
const now = Date.now();
|
|
16050
|
-
const staleIds = [];
|
|
16051
|
-
for (const [id, session] of swarmState.agentSessions) {
|
|
16052
|
-
if (now - session.lastToolCallTime > staleDurationMs) {
|
|
16053
|
-
staleIds.push(id);
|
|
16054
|
-
}
|
|
16055
|
-
}
|
|
16056
|
-
for (const id of staleIds) {
|
|
16057
|
-
swarmState.agentSessions.delete(id);
|
|
16058
|
-
}
|
|
16059
|
-
const sessionState = {
|
|
16060
|
-
agentName,
|
|
16061
|
-
startTime: now,
|
|
16062
|
-
lastToolCallTime: now,
|
|
16063
|
-
toolCallCount: 0,
|
|
16064
|
-
consecutiveErrors: 0,
|
|
16065
|
-
recentToolCalls: [],
|
|
16066
|
-
warningIssued: false,
|
|
16067
|
-
warningReason: "",
|
|
16068
|
-
hardLimitHit: false,
|
|
16069
|
-
lastSuccessTime: now,
|
|
16070
|
-
delegationActive: false
|
|
16071
|
-
};
|
|
16072
|
-
swarmState.agentSessions.set(sessionId, sessionState);
|
|
16073
|
-
}
|
|
16074
|
-
function getAgentSession(sessionId) {
|
|
16075
|
-
return swarmState.agentSessions.get(sessionId);
|
|
16076
|
-
}
|
|
16077
|
-
function ensureAgentSession(sessionId, agentName) {
|
|
16078
|
-
const now = Date.now();
|
|
16079
|
-
let session = swarmState.agentSessions.get(sessionId);
|
|
16080
|
-
if (session) {
|
|
16081
|
-
if (agentName && agentName !== session.agentName) {
|
|
16082
|
-
session.agentName = agentName;
|
|
16083
|
-
session.startTime = now;
|
|
16084
|
-
session.toolCallCount = 0;
|
|
16085
|
-
session.consecutiveErrors = 0;
|
|
16086
|
-
session.recentToolCalls = [];
|
|
16087
|
-
session.warningIssued = false;
|
|
16088
|
-
session.warningReason = "";
|
|
16089
|
-
session.hardLimitHit = false;
|
|
16090
|
-
session.lastSuccessTime = now;
|
|
16091
|
-
session.delegationActive = false;
|
|
16092
|
-
}
|
|
16093
|
-
session.lastToolCallTime = now;
|
|
16094
|
-
return session;
|
|
16095
|
-
}
|
|
16096
|
-
startAgentSession(sessionId, agentName ?? "unknown");
|
|
16097
|
-
session = swarmState.agentSessions.get(sessionId);
|
|
16098
|
-
if (!session) {
|
|
16099
|
-
throw new Error(`Failed to create guardrail session for ${sessionId}`);
|
|
16100
|
-
}
|
|
16101
|
-
return session;
|
|
16102
|
-
}
|
|
16103
|
-
|
|
16104
16450
|
// src/hooks/agent-activity.ts
|
|
16105
16451
|
function createAgentActivityHooks(config2, directory) {
|
|
16106
16452
|
if (config2.hooks?.agent_activity === false) {
|
|
@@ -16169,8 +16515,8 @@ async function doFlush(directory) {
|
|
|
16169
16515
|
const activitySection = renderActivitySection();
|
|
16170
16516
|
const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
|
|
16171
16517
|
const flushedCount = swarmState.pendingEvents;
|
|
16172
|
-
const
|
|
16173
|
-
await Bun.write(
|
|
16518
|
+
const path7 = `${directory}/.swarm/context.md`;
|
|
16519
|
+
await Bun.write(path7, updated);
|
|
16174
16520
|
swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
|
|
16175
16521
|
} catch (error49) {
|
|
16176
16522
|
warn("Agent activity flush failed:", error49);
|
|
@@ -16320,6 +16666,70 @@ function createContextBudgetHandler(config2) {
|
|
|
16320
16666
|
}
|
|
16321
16667
|
};
|
|
16322
16668
|
}
|
|
16669
|
+
// src/hooks/delegation-gate.ts
|
|
16670
|
+
function createDelegationGateHook(config2) {
|
|
16671
|
+
const enabled = config2.hooks?.delegation_gate !== false;
|
|
16672
|
+
const delegationMaxChars = config2.hooks?.delegation_max_chars ?? 4000;
|
|
16673
|
+
if (!enabled) {
|
|
16674
|
+
return async (_input, _output) => {};
|
|
16675
|
+
}
|
|
16676
|
+
return async (_input, output) => {
|
|
16677
|
+
const messages = output?.messages;
|
|
16678
|
+
if (!messages || messages.length === 0)
|
|
16679
|
+
return;
|
|
16680
|
+
let lastUserMessageIndex = -1;
|
|
16681
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
16682
|
+
if (messages[i]?.info?.role === "user") {
|
|
16683
|
+
lastUserMessageIndex = i;
|
|
16684
|
+
break;
|
|
16685
|
+
}
|
|
16686
|
+
}
|
|
16687
|
+
if (lastUserMessageIndex === -1)
|
|
16688
|
+
return;
|
|
16689
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
16690
|
+
if (!lastUserMessage?.parts)
|
|
16691
|
+
return;
|
|
16692
|
+
const agent = lastUserMessage.info?.agent;
|
|
16693
|
+
const strippedAgent = agent ? stripKnownSwarmPrefix(agent) : undefined;
|
|
16694
|
+
if (strippedAgent && strippedAgent !== "architect")
|
|
16695
|
+
return;
|
|
16696
|
+
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
16697
|
+
if (textPartIndex === -1)
|
|
16698
|
+
return;
|
|
16699
|
+
const textPart = lastUserMessage.parts[textPartIndex];
|
|
16700
|
+
const text = textPart.text ?? "";
|
|
16701
|
+
const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
|
|
16702
|
+
if (!coderDelegationPattern.test(text))
|
|
16703
|
+
return;
|
|
16704
|
+
const warnings = [];
|
|
16705
|
+
if (text.length > delegationMaxChars) {
|
|
16706
|
+
warnings.push(`Delegation exceeds recommended size (${text.length} chars, limit ${delegationMaxChars}). Consider splitting into smaller tasks.`);
|
|
16707
|
+
}
|
|
16708
|
+
const fileMatches = text.match(/^FILE:/gm);
|
|
16709
|
+
if (fileMatches && fileMatches.length > 1) {
|
|
16710
|
+
warnings.push(`Multiple FILE: directives detected (${fileMatches.length}). Each coder task should target ONE file.`);
|
|
16711
|
+
}
|
|
16712
|
+
const taskMatches = text.match(/^TASK:/gm);
|
|
16713
|
+
if (taskMatches && taskMatches.length > 1) {
|
|
16714
|
+
warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
|
|
16715
|
+
}
|
|
16716
|
+
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with)\b/gi;
|
|
16717
|
+
const batchingMatches = text.match(batchingPattern);
|
|
16718
|
+
if (batchingMatches && batchingMatches.length > 0) {
|
|
16719
|
+
warnings.push("Batching language detected. Break compound objectives into separate coder calls.");
|
|
16720
|
+
}
|
|
16721
|
+
if (warnings.length === 0)
|
|
16722
|
+
return;
|
|
16723
|
+
const warningText = `[\u26A0\uFE0F DELEGATION GATE: Your coder delegation may be too complex. Issues:
|
|
16724
|
+
${warnings.join(`
|
|
16725
|
+
`)}
|
|
16726
|
+
Split into smaller, atomic tasks for better results.]`;
|
|
16727
|
+
const originalText = textPart.text ?? "";
|
|
16728
|
+
textPart.text = `${warningText}
|
|
16729
|
+
|
|
16730
|
+
${originalText}`;
|
|
16731
|
+
};
|
|
16732
|
+
}
|
|
16323
16733
|
// src/hooks/delegation-tracker.ts
|
|
16324
16734
|
function createDelegationTrackerHook(config2) {
|
|
16325
16735
|
return async (input, _output) => {
|
|
@@ -16361,8 +16771,17 @@ function createGuardrailsHooks(config2) {
|
|
|
16361
16771
|
}
|
|
16362
16772
|
return {
|
|
16363
16773
|
toolBefore: async (input, output) => {
|
|
16774
|
+
const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
|
|
16775
|
+
const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
|
|
16776
|
+
if (strippedAgent === ORCHESTRATOR_NAME) {
|
|
16777
|
+
return;
|
|
16778
|
+
}
|
|
16364
16779
|
const agentName = swarmState.activeAgent.get(input.sessionID);
|
|
16365
16780
|
const session = ensureAgentSession(input.sessionID, agentName);
|
|
16781
|
+
const resolvedName = stripKnownSwarmPrefix(session.agentName);
|
|
16782
|
+
if (resolvedName === ORCHESTRATOR_NAME) {
|
|
16783
|
+
return;
|
|
16784
|
+
}
|
|
16366
16785
|
const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
|
|
16367
16786
|
if (session.hardLimitHit) {
|
|
16368
16787
|
throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
|
|
@@ -16691,6 +17110,7 @@ function createSystemEnhancerHook(config2, directory) {
|
|
|
16691
17110
|
}
|
|
16692
17111
|
}
|
|
16693
17112
|
}
|
|
17113
|
+
tryInject("[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.");
|
|
16694
17114
|
return;
|
|
16695
17115
|
}
|
|
16696
17116
|
const userScoringConfig = config2.context_budget?.scoring;
|
|
@@ -16816,6 +17236,154 @@ ${activitySection}`;
|
|
|
16816
17236
|
}
|
|
16817
17237
|
return contextSummary;
|
|
16818
17238
|
}
|
|
17239
|
+
// src/summaries/summarizer.ts
|
|
17240
|
+
var HYSTERESIS_FACTOR = 1.25;
|
|
17241
|
+
function detectContentType(output, toolName) {
|
|
17242
|
+
const trimmed = output.trim();
|
|
17243
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
17244
|
+
try {
|
|
17245
|
+
JSON.parse(trimmed);
|
|
17246
|
+
return "json";
|
|
17247
|
+
} catch {}
|
|
17248
|
+
}
|
|
17249
|
+
const codeToolNames = ["read", "cat", "grep", "bash"];
|
|
17250
|
+
const lowerToolName = toolName.toLowerCase();
|
|
17251
|
+
const toolSegments = lowerToolName.split(/[.\-_/]/);
|
|
17252
|
+
if (codeToolNames.some((name) => toolSegments.includes(name))) {
|
|
17253
|
+
return "code";
|
|
17254
|
+
}
|
|
17255
|
+
const codePatterns = [
|
|
17256
|
+
"function ",
|
|
17257
|
+
"const ",
|
|
17258
|
+
"import ",
|
|
17259
|
+
"export ",
|
|
17260
|
+
"class ",
|
|
17261
|
+
"def ",
|
|
17262
|
+
"return ",
|
|
17263
|
+
"=>"
|
|
17264
|
+
];
|
|
17265
|
+
const startsWithShebang = trimmed.startsWith("#!");
|
|
17266
|
+
if (codePatterns.some((pattern) => output.includes(pattern)) || startsWithShebang) {
|
|
17267
|
+
return "code";
|
|
17268
|
+
}
|
|
17269
|
+
const sampleSize = Math.min(1000, output.length);
|
|
17270
|
+
let nonPrintableCount = 0;
|
|
17271
|
+
for (let i = 0;i < sampleSize; i++) {
|
|
17272
|
+
const charCode = output.charCodeAt(i);
|
|
17273
|
+
if (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) {
|
|
17274
|
+
nonPrintableCount++;
|
|
17275
|
+
}
|
|
17276
|
+
}
|
|
17277
|
+
if (sampleSize > 0 && nonPrintableCount / sampleSize > 0.1) {
|
|
17278
|
+
return "binary";
|
|
17279
|
+
}
|
|
17280
|
+
return "text";
|
|
17281
|
+
}
|
|
17282
|
+
function shouldSummarize(output, thresholdBytes) {
|
|
17283
|
+
const byteLength = Buffer.byteLength(output, "utf8");
|
|
17284
|
+
return byteLength >= thresholdBytes * HYSTERESIS_FACTOR;
|
|
17285
|
+
}
|
|
17286
|
+
function formatBytes(bytes) {
|
|
17287
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
17288
|
+
let unitIndex = 0;
|
|
17289
|
+
let size = bytes;
|
|
17290
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
17291
|
+
size /= 1024;
|
|
17292
|
+
unitIndex++;
|
|
17293
|
+
}
|
|
17294
|
+
const formatted = unitIndex === 0 ? size.toString() : size.toFixed(1);
|
|
17295
|
+
return `${formatted} ${units[unitIndex]}`;
|
|
17296
|
+
}
|
|
17297
|
+
function createSummary(output, toolName, summaryId, maxSummaryChars) {
|
|
17298
|
+
const contentType = detectContentType(output, toolName);
|
|
17299
|
+
const lineCount = output.split(`
|
|
17300
|
+
`).length;
|
|
17301
|
+
const byteSize = Buffer.byteLength(output, "utf8");
|
|
17302
|
+
const formattedSize = formatBytes(byteSize);
|
|
17303
|
+
const headerLine = `[SUMMARY ${summaryId}] ${formattedSize} | ${contentType} | ${lineCount} lines`;
|
|
17304
|
+
const footerLine = `\u2192 Use /swarm retrieve ${summaryId} for full content`;
|
|
17305
|
+
const overhead = headerLine.length + 1 + footerLine.length + 1;
|
|
17306
|
+
const maxPreviewChars = maxSummaryChars - overhead;
|
|
17307
|
+
let preview;
|
|
17308
|
+
switch (contentType) {
|
|
17309
|
+
case "json": {
|
|
17310
|
+
try {
|
|
17311
|
+
const parsed = JSON.parse(output.trim());
|
|
17312
|
+
if (Array.isArray(parsed)) {
|
|
17313
|
+
preview = `[ ${parsed.length} items ]`;
|
|
17314
|
+
} else if (typeof parsed === "object" && parsed !== null) {
|
|
17315
|
+
const keys = Object.keys(parsed).slice(0, 3);
|
|
17316
|
+
preview = `{ ${keys.join(", ")}${Object.keys(parsed).length > 3 ? ", ..." : ""} }`;
|
|
17317
|
+
} else {
|
|
17318
|
+
const lines = output.split(`
|
|
17319
|
+
`).filter((line) => line.trim().length > 0).slice(0, 3);
|
|
17320
|
+
preview = lines.join(`
|
|
17321
|
+
`);
|
|
17322
|
+
}
|
|
17323
|
+
} catch {
|
|
17324
|
+
const lines = output.split(`
|
|
17325
|
+
`).filter((line) => line.trim().length > 0).slice(0, 3);
|
|
17326
|
+
preview = lines.join(`
|
|
17327
|
+
`);
|
|
17328
|
+
}
|
|
17329
|
+
break;
|
|
17330
|
+
}
|
|
17331
|
+
case "code": {
|
|
17332
|
+
const lines = output.split(`
|
|
17333
|
+
`).filter((line) => line.trim().length > 0).slice(0, 5);
|
|
17334
|
+
preview = lines.join(`
|
|
17335
|
+
`);
|
|
17336
|
+
break;
|
|
17337
|
+
}
|
|
17338
|
+
case "text": {
|
|
17339
|
+
const lines = output.split(`
|
|
17340
|
+
`).filter((line) => line.trim().length > 0).slice(0, 5);
|
|
17341
|
+
preview = lines.join(`
|
|
17342
|
+
`);
|
|
17343
|
+
break;
|
|
17344
|
+
}
|
|
17345
|
+
case "binary": {
|
|
17346
|
+
preview = `[Binary content - ${formattedSize}]`;
|
|
17347
|
+
break;
|
|
17348
|
+
}
|
|
17349
|
+
default: {
|
|
17350
|
+
const lines = output.split(`
|
|
17351
|
+
`).filter((line) => line.trim().length > 0).slice(0, 5);
|
|
17352
|
+
preview = lines.join(`
|
|
17353
|
+
`);
|
|
17354
|
+
}
|
|
17355
|
+
}
|
|
17356
|
+
if (preview.length > maxPreviewChars) {
|
|
17357
|
+
preview = preview.substring(0, maxPreviewChars - 3) + "...";
|
|
17358
|
+
}
|
|
17359
|
+
return `${headerLine}
|
|
17360
|
+
${preview}
|
|
17361
|
+
${footerLine}`;
|
|
17362
|
+
}
|
|
17363
|
+
|
|
17364
|
+
// src/hooks/tool-summarizer.ts
|
|
17365
|
+
var nextSummaryId = 1;
|
|
17366
|
+
function createToolSummarizerHook(config2, directory) {
|
|
17367
|
+
if (config2.enabled === false) {
|
|
17368
|
+
return async () => {};
|
|
17369
|
+
}
|
|
17370
|
+
return async (input, output) => {
|
|
17371
|
+
if (typeof output.output !== "string" || output.output.length === 0) {
|
|
17372
|
+
return;
|
|
17373
|
+
}
|
|
17374
|
+
if (!shouldSummarize(output.output, config2.threshold_bytes)) {
|
|
17375
|
+
return;
|
|
17376
|
+
}
|
|
17377
|
+
const summaryId = `S${nextSummaryId++}`;
|
|
17378
|
+
const summaryText = createSummary(output.output, input.tool, summaryId, config2.max_summary_chars);
|
|
17379
|
+
try {
|
|
17380
|
+
await storeSummary(directory, summaryId, output.output, summaryText, config2.max_stored_bytes);
|
|
17381
|
+
output.output = summaryText;
|
|
17382
|
+
} catch (error49) {
|
|
17383
|
+
warn(`Tool output summarization failed for ${summaryId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
17384
|
+
}
|
|
17385
|
+
};
|
|
17386
|
+
}
|
|
16819
17387
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
16820
17388
|
var exports_external2 = {};
|
|
16821
17389
|
__export(exports_external2, {
|
|
@@ -17545,10 +18113,10 @@ function mergeDefs2(...defs) {
|
|
|
17545
18113
|
function cloneDef2(schema) {
|
|
17546
18114
|
return mergeDefs2(schema._zod.def);
|
|
17547
18115
|
}
|
|
17548
|
-
function getElementAtPath2(obj,
|
|
17549
|
-
if (!
|
|
18116
|
+
function getElementAtPath2(obj, path7) {
|
|
18117
|
+
if (!path7)
|
|
17550
18118
|
return obj;
|
|
17551
|
-
return
|
|
18119
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
17552
18120
|
}
|
|
17553
18121
|
function promiseAllObject2(promisesObj) {
|
|
17554
18122
|
const keys = Object.keys(promisesObj);
|
|
@@ -17907,11 +18475,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
17907
18475
|
}
|
|
17908
18476
|
return false;
|
|
17909
18477
|
}
|
|
17910
|
-
function prefixIssues2(
|
|
18478
|
+
function prefixIssues2(path7, issues) {
|
|
17911
18479
|
return issues.map((iss) => {
|
|
17912
18480
|
var _a2;
|
|
17913
18481
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
17914
|
-
iss.path.unshift(
|
|
18482
|
+
iss.path.unshift(path7);
|
|
17915
18483
|
return iss;
|
|
17916
18484
|
});
|
|
17917
18485
|
}
|
|
@@ -18079,7 +18647,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
18079
18647
|
return issue3.message;
|
|
18080
18648
|
};
|
|
18081
18649
|
const result = { errors: [] };
|
|
18082
|
-
const processError = (error50,
|
|
18650
|
+
const processError = (error50, path7 = []) => {
|
|
18083
18651
|
var _a2, _b;
|
|
18084
18652
|
for (const issue3 of error50.issues) {
|
|
18085
18653
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -18089,7 +18657,7 @@ function treeifyError2(error49, _mapper) {
|
|
|
18089
18657
|
} else if (issue3.code === "invalid_element") {
|
|
18090
18658
|
processError({ issues: issue3.issues }, issue3.path);
|
|
18091
18659
|
} else {
|
|
18092
|
-
const fullpath = [...
|
|
18660
|
+
const fullpath = [...path7, ...issue3.path];
|
|
18093
18661
|
if (fullpath.length === 0) {
|
|
18094
18662
|
result.errors.push(mapper(issue3));
|
|
18095
18663
|
continue;
|
|
@@ -18121,8 +18689,8 @@ function treeifyError2(error49, _mapper) {
|
|
|
18121
18689
|
}
|
|
18122
18690
|
function toDotPath2(_path) {
|
|
18123
18691
|
const segs = [];
|
|
18124
|
-
const
|
|
18125
|
-
for (const seg of
|
|
18692
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
18693
|
+
for (const seg of path7) {
|
|
18126
18694
|
if (typeof seg === "number")
|
|
18127
18695
|
segs.push(`[${seg}]`);
|
|
18128
18696
|
else if (typeof seg === "symbol")
|
|
@@ -29318,7 +29886,7 @@ Use these as DOMAIN values when delegating to @sme.`;
|
|
|
29318
29886
|
});
|
|
29319
29887
|
// src/tools/file-extractor.ts
|
|
29320
29888
|
import * as fs3 from "fs";
|
|
29321
|
-
import * as
|
|
29889
|
+
import * as path7 from "path";
|
|
29322
29890
|
var EXT_MAP = {
|
|
29323
29891
|
python: ".py",
|
|
29324
29892
|
py: ".py",
|
|
@@ -29396,12 +29964,12 @@ var extract_code_blocks = tool({
|
|
|
29396
29964
|
if (prefix) {
|
|
29397
29965
|
filename = `${prefix}_${filename}`;
|
|
29398
29966
|
}
|
|
29399
|
-
let filepath =
|
|
29400
|
-
const base =
|
|
29401
|
-
const ext =
|
|
29967
|
+
let filepath = path7.join(targetDir, filename);
|
|
29968
|
+
const base = path7.basename(filepath, path7.extname(filepath));
|
|
29969
|
+
const ext = path7.extname(filepath);
|
|
29402
29970
|
let counter = 1;
|
|
29403
29971
|
while (fs3.existsSync(filepath)) {
|
|
29404
|
-
filepath =
|
|
29972
|
+
filepath = path7.join(targetDir, `${base}_${counter}${ext}`);
|
|
29405
29973
|
counter++;
|
|
29406
29974
|
}
|
|
29407
29975
|
try {
|
|
@@ -29522,8 +30090,11 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29522
30090
|
const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
|
|
29523
30091
|
const activityHooks = createAgentActivityHooks(config3, ctx.directory);
|
|
29524
30092
|
const delegationHandler = createDelegationTrackerHook(config3);
|
|
30093
|
+
const delegationGateHandler = createDelegationGateHook(config3);
|
|
29525
30094
|
const guardrailsConfig = GuardrailsConfigSchema.parse(config3.guardrails ?? {});
|
|
29526
30095
|
const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
|
|
30096
|
+
const summaryConfig = SummaryConfigSchema.parse(config3.summaries ?? {});
|
|
30097
|
+
const toolSummarizerHook = createToolSummarizerHook(summaryConfig, ctx.directory);
|
|
29527
30098
|
log("Plugin initialized", {
|
|
29528
30099
|
directory: ctx.directory,
|
|
29529
30100
|
maxIterations: config3.max_iterations,
|
|
@@ -29537,7 +30108,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29537
30108
|
commands: true,
|
|
29538
30109
|
agentActivity: config3.hooks?.agent_activity !== false,
|
|
29539
30110
|
delegationTracker: config3.hooks?.delegation_tracker === true,
|
|
29540
|
-
guardrails: guardrailsConfig.enabled
|
|
30111
|
+
guardrails: guardrailsConfig.enabled,
|
|
30112
|
+
toolSummarizer: summaryConfig.enabled
|
|
29541
30113
|
}
|
|
29542
30114
|
});
|
|
29543
30115
|
return {
|
|
@@ -29569,7 +30141,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29569
30141
|
"experimental.chat.messages.transform": composeHandlers(...[
|
|
29570
30142
|
pipelineHook["experimental.chat.messages.transform"],
|
|
29571
30143
|
contextBudgetHandler,
|
|
29572
|
-
guardrailsHooks.messagesTransform
|
|
30144
|
+
guardrailsHooks.messagesTransform,
|
|
30145
|
+
delegationGateHandler
|
|
29573
30146
|
].filter((fn) => Boolean(fn))),
|
|
29574
30147
|
"experimental.chat.system.transform": systemEnhancerHook["experimental.chat.system.transform"],
|
|
29575
30148
|
"experimental.session.compacting": compactionHook["experimental.session.compacting"],
|
|
@@ -29580,14 +30153,20 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29580
30153
|
}
|
|
29581
30154
|
const session = swarmState.agentSessions.get(input.sessionID);
|
|
29582
30155
|
const activeAgent = swarmState.activeAgent.get(input.sessionID);
|
|
29583
|
-
if (session && activeAgent && activeAgent !== ORCHESTRATOR_NAME
|
|
29584
|
-
|
|
29585
|
-
|
|
30156
|
+
if (session && activeAgent && activeAgent !== ORCHESTRATOR_NAME) {
|
|
30157
|
+
const stripActive = stripKnownSwarmPrefix(activeAgent);
|
|
30158
|
+
if (stripActive !== ORCHESTRATOR_NAME) {
|
|
30159
|
+
const staleDelegation = !session.delegationActive || Date.now() - session.lastToolCallTime > 60000;
|
|
30160
|
+
if (staleDelegation) {
|
|
30161
|
+
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
30162
|
+
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
30163
|
+
}
|
|
30164
|
+
}
|
|
29586
30165
|
}
|
|
29587
30166
|
await guardrailsHooks.toolBefore(input, output);
|
|
29588
30167
|
await safeHook(activityHooks.toolBefore)(input, output);
|
|
29589
30168
|
},
|
|
29590
|
-
"tool.execute.after": composeHandlers(activityHooks.toolAfter, guardrailsHooks.toolAfter),
|
|
30169
|
+
"tool.execute.after": composeHandlers(activityHooks.toolAfter, guardrailsHooks.toolAfter, toolSummarizerHook),
|
|
29591
30170
|
"chat.message": safeHook(delegationHandler)
|
|
29592
30171
|
};
|
|
29593
30172
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate and sanitize summary ID.
|
|
3
|
+
* Must match regex ^S\d+$ (e.g., S1, S2, S99)
|
|
4
|
+
* Rejects: empty string, null bytes, control characters, path traversal, non-matching patterns
|
|
5
|
+
* @throws Error with descriptive message on failure
|
|
6
|
+
*/
|
|
7
|
+
export declare function sanitizeSummaryId(id: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Store a summary entry to .swarm/summaries/{id}.json.
|
|
10
|
+
* Performs atomic write via temp file + rename.
|
|
11
|
+
* @throws Error if summary ID is invalid or size limit would be exceeded
|
|
12
|
+
*/
|
|
13
|
+
export declare function storeSummary(directory: string, id: string, fullOutput: string, summaryText: string, maxStoredBytes: number): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Load fullOutput from a summary entry.
|
|
16
|
+
* Returns null if file doesn't exist or validation fails.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadFullOutput(directory: string, id: string): Promise<string | null>;
|
|
19
|
+
/**
|
|
20
|
+
* List all summary IDs that have summary entries.
|
|
21
|
+
* Returns sorted array of valid summary IDs.
|
|
22
|
+
* Returns empty array if summaries directory doesn't exist.
|
|
23
|
+
*/
|
|
24
|
+
export declare function listSummaries(directory: string): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Delete summaries older than retentionDays.
|
|
27
|
+
* Returns array of deleted summary IDs.
|
|
28
|
+
*/
|
|
29
|
+
export declare function cleanupSummaries(directory: string, retentionDays: number): Promise<string[]>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summarization engine for tool outputs.
|
|
3
|
+
* Provides content type detection, summarization decision logic, and structured summary creation.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Hysteresis factor to prevent churn for outputs near the threshold.
|
|
7
|
+
* An output must be 25% larger than the threshold to be summarized.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HYSTERESIS_FACTOR = 1.25;
|
|
10
|
+
/**
|
|
11
|
+
* Content type classification for tool outputs.
|
|
12
|
+
*/
|
|
13
|
+
type ContentType = 'json' | 'code' | 'text' | 'binary';
|
|
14
|
+
/**
|
|
15
|
+
* Heuristic-based content type detection.
|
|
16
|
+
* @param output - The tool output string to analyze
|
|
17
|
+
* @param toolName - The name of the tool that produced the output
|
|
18
|
+
* @returns The detected content type: 'json', 'code', 'text', or 'binary'
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectContentType(output: string, toolName: string): ContentType;
|
|
21
|
+
/**
|
|
22
|
+
* Determines whether output should be summarized based on size and hysteresis.
|
|
23
|
+
* Uses hysteresis to prevent repeated summarization decisions for outputs near the threshold.
|
|
24
|
+
* @param output - The tool output string to check
|
|
25
|
+
* @param thresholdBytes - The threshold in bytes
|
|
26
|
+
* @returns true if the output should be summarized
|
|
27
|
+
*/
|
|
28
|
+
export declare function shouldSummarize(output: string, thresholdBytes: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a structured summary string from tool output.
|
|
31
|
+
* @param output - The full tool output string
|
|
32
|
+
* @param toolName - The name of the tool that produced the output
|
|
33
|
+
* @param summaryId - Unique identifier for this summary
|
|
34
|
+
* @param maxSummaryChars - Maximum characters allowed for the preview
|
|
35
|
+
* @returns Formatted summary string
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSummary(output: string, toolName: string, summaryId: string, maxSummaryChars: number): string;
|
|
38
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.3",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|