opencode-swarm 5.0.10 → 5.1.1
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/config/schema.d.ts +4 -0
- package/dist/hooks/context-scoring.d.ts +70 -0
- package/dist/hooks/delegation-gate.d.ts +29 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.js +289 -26
- package/dist/state.d.ts +2 -0
- package/package.json +1 -1
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<{
|
|
@@ -200,6 +202,8 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
200
202
|
agent_activity: z.ZodDefault<z.ZodBoolean>;
|
|
201
203
|
delegation_tracker: z.ZodDefault<z.ZodBoolean>;
|
|
202
204
|
agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
205
|
+
delegation_gate: z.ZodDefault<z.ZodBoolean>;
|
|
206
|
+
delegation_max_chars: z.ZodDefault<z.ZodNumber>;
|
|
203
207
|
}, z.core.$strip>>;
|
|
204
208
|
context_budget: z.ZodOptional<z.ZodObject<{
|
|
205
209
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Scoring Utility
|
|
3
|
+
*
|
|
4
|
+
* Pure scoring/ranking helpers for context injection budget.
|
|
5
|
+
* Implements deterministic, reproducible candidate ranking based on configurable weights.
|
|
6
|
+
*/
|
|
7
|
+
export type ContentType = 'prose' | 'code' | 'markdown' | 'json';
|
|
8
|
+
export type CandidateKind = 'phase' | 'task' | 'decision' | 'evidence' | 'agent_context';
|
|
9
|
+
export interface ContextCandidate {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: CandidateKind;
|
|
12
|
+
text: string;
|
|
13
|
+
tokens: number;
|
|
14
|
+
priority: number;
|
|
15
|
+
metadata: {
|
|
16
|
+
contentType: ContentType;
|
|
17
|
+
dependencyDepth?: number;
|
|
18
|
+
decisionAgeHours?: number;
|
|
19
|
+
isCurrentTask?: boolean;
|
|
20
|
+
isBlockedTask?: boolean;
|
|
21
|
+
hasFailure?: boolean;
|
|
22
|
+
hasSuccess?: boolean;
|
|
23
|
+
hasEvidence?: boolean;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface RankedCandidate extends ContextCandidate {
|
|
27
|
+
score: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ScoringWeights {
|
|
30
|
+
phase: number;
|
|
31
|
+
current_task: number;
|
|
32
|
+
blocked_task: number;
|
|
33
|
+
recent_failure: number;
|
|
34
|
+
recent_success: number;
|
|
35
|
+
evidence_presence: number;
|
|
36
|
+
decision_recency: number;
|
|
37
|
+
dependency_proximity: number;
|
|
38
|
+
}
|
|
39
|
+
export interface DecisionDecayConfig {
|
|
40
|
+
mode: 'linear' | 'exponential';
|
|
41
|
+
half_life_hours: number;
|
|
42
|
+
}
|
|
43
|
+
export interface TokenRatios {
|
|
44
|
+
prose: number;
|
|
45
|
+
code: number;
|
|
46
|
+
markdown: number;
|
|
47
|
+
json: number;
|
|
48
|
+
}
|
|
49
|
+
export interface ScoringConfig {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
max_candidates: number;
|
|
52
|
+
weights: ScoringWeights;
|
|
53
|
+
decision_decay: DecisionDecayConfig;
|
|
54
|
+
token_ratios: TokenRatios;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Rank context candidates by importance score.
|
|
58
|
+
*
|
|
59
|
+
* Scoring formula:
|
|
60
|
+
* - base_score = sum of (weight * feature_flag)
|
|
61
|
+
* - For items with dependency depth: adjusted_score = base_score / (1 + depth)
|
|
62
|
+
* - For decisions with age: age_factor = 2^(-age_hours / half_life_hours), score = decision_recency * age_factor
|
|
63
|
+
*
|
|
64
|
+
* Tie-breaker: score desc → priority desc → id asc (stable sort)
|
|
65
|
+
*
|
|
66
|
+
* @param candidates - Array of context candidates
|
|
67
|
+
* @param config - Scoring configuration
|
|
68
|
+
* @returns Ranked candidates (truncated to max_candidates, original order if disabled)
|
|
69
|
+
*/
|
|
70
|
+
export declare function rankCandidates(candidates: ContextCandidate[], config: ScoringConfig): RankedCandidate[];
|
|
@@ -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
|
@@ -2,6 +2,7 @@ export { createAgentActivityHooks } from './agent-activity';
|
|
|
2
2
|
export { createCompactionCustomizerHook } from './compaction-customizer';
|
|
3
3
|
export { createContextBudgetHandler } from './context-budget';
|
|
4
4
|
export { createDelegationTrackerHook } from './delegation-tracker';
|
|
5
|
+
export { createDelegationGateHook } from './delegation-gate';
|
|
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';
|
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),
|
|
@@ -13696,7 +13698,8 @@ function resolveGuardrailsConfig(base, agentName) {
|
|
|
13696
13698
|
return base;
|
|
13697
13699
|
}
|
|
13698
13700
|
const baseName = stripKnownSwarmPrefix(agentName);
|
|
13699
|
-
const
|
|
13701
|
+
const builtInLookup = DEFAULT_AGENT_PROFILES[baseName];
|
|
13702
|
+
const effectiveName = builtInLookup ? baseName : ORCHESTRATOR_NAME;
|
|
13700
13703
|
const builtIn = DEFAULT_AGENT_PROFILES[effectiveName];
|
|
13701
13704
|
const userProfile = base.profiles?.[effectiveName] ?? base.profiles?.[baseName] ?? base.profiles?.[agentName];
|
|
13702
13705
|
if (!builtIn && !userProfile) {
|
|
@@ -13833,6 +13836,30 @@ var DEFAULT_MODELS = {
|
|
|
13833
13836
|
critic: "google/gemini-2.0-flash",
|
|
13834
13837
|
default: "google/gemini-2.0-flash"
|
|
13835
13838
|
};
|
|
13839
|
+
var DEFAULT_SCORING_CONFIG = {
|
|
13840
|
+
enabled: false,
|
|
13841
|
+
max_candidates: 100,
|
|
13842
|
+
weights: {
|
|
13843
|
+
phase: 1,
|
|
13844
|
+
current_task: 2,
|
|
13845
|
+
blocked_task: 1.5,
|
|
13846
|
+
recent_failure: 2.5,
|
|
13847
|
+
recent_success: 0.5,
|
|
13848
|
+
evidence_presence: 1,
|
|
13849
|
+
decision_recency: 1.5,
|
|
13850
|
+
dependency_proximity: 1
|
|
13851
|
+
},
|
|
13852
|
+
decision_decay: {
|
|
13853
|
+
mode: "exponential",
|
|
13854
|
+
half_life_hours: 24
|
|
13855
|
+
},
|
|
13856
|
+
token_ratios: {
|
|
13857
|
+
prose: 0.25,
|
|
13858
|
+
code: 0.4,
|
|
13859
|
+
markdown: 0.3,
|
|
13860
|
+
json: 0.35
|
|
13861
|
+
}
|
|
13862
|
+
};
|
|
13836
13863
|
// src/config/plan-schema.ts
|
|
13837
13864
|
var TaskStatusSchema = exports_external.enum([
|
|
13838
13865
|
"pending",
|
|
@@ -16041,7 +16068,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
|
16041
16068
|
warningIssued: false,
|
|
16042
16069
|
warningReason: "",
|
|
16043
16070
|
hardLimitHit: false,
|
|
16044
|
-
lastSuccessTime: now
|
|
16071
|
+
lastSuccessTime: now,
|
|
16072
|
+
delegationActive: false
|
|
16045
16073
|
};
|
|
16046
16074
|
swarmState.agentSessions.set(sessionId, sessionState);
|
|
16047
16075
|
}
|
|
@@ -16062,6 +16090,7 @@ function ensureAgentSession(sessionId, agentName) {
|
|
|
16062
16090
|
session.warningReason = "";
|
|
16063
16091
|
session.hardLimitHit = false;
|
|
16064
16092
|
session.lastSuccessTime = now;
|
|
16093
|
+
session.delegationActive = false;
|
|
16065
16094
|
}
|
|
16066
16095
|
session.lastToolCallTime = now;
|
|
16067
16096
|
return session;
|
|
@@ -16297,15 +16326,21 @@ function createContextBudgetHandler(config2) {
|
|
|
16297
16326
|
function createDelegationTrackerHook(config2) {
|
|
16298
16327
|
return async (input, _output) => {
|
|
16299
16328
|
if (!input.agent || input.agent === "") {
|
|
16329
|
+
const session2 = swarmState.agentSessions.get(input.sessionID);
|
|
16330
|
+
if (session2) {
|
|
16331
|
+
session2.delegationActive = false;
|
|
16332
|
+
}
|
|
16300
16333
|
return;
|
|
16301
16334
|
}
|
|
16335
|
+
const agentName = input.agent;
|
|
16302
16336
|
const previousAgent = swarmState.activeAgent.get(input.sessionID);
|
|
16303
|
-
swarmState.activeAgent.set(input.sessionID,
|
|
16304
|
-
ensureAgentSession(input.sessionID,
|
|
16305
|
-
|
|
16337
|
+
swarmState.activeAgent.set(input.sessionID, agentName);
|
|
16338
|
+
const session = ensureAgentSession(input.sessionID, agentName);
|
|
16339
|
+
session.delegationActive = true;
|
|
16340
|
+
if (config2.hooks?.delegation_tracker === true && previousAgent && previousAgent !== agentName) {
|
|
16306
16341
|
const entry = {
|
|
16307
16342
|
from: previousAgent,
|
|
16308
|
-
to:
|
|
16343
|
+
to: agentName,
|
|
16309
16344
|
timestamp: Date.now()
|
|
16310
16345
|
};
|
|
16311
16346
|
if (!swarmState.delegationChains.has(input.sessionID)) {
|
|
@@ -16317,6 +16352,70 @@ function createDelegationTrackerHook(config2) {
|
|
|
16317
16352
|
}
|
|
16318
16353
|
};
|
|
16319
16354
|
}
|
|
16355
|
+
// src/hooks/delegation-gate.ts
|
|
16356
|
+
function createDelegationGateHook(config2) {
|
|
16357
|
+
const enabled = config2.hooks?.delegation_gate !== false;
|
|
16358
|
+
const delegationMaxChars = config2.hooks?.delegation_max_chars ?? 4000;
|
|
16359
|
+
if (!enabled) {
|
|
16360
|
+
return async (_input, _output) => {};
|
|
16361
|
+
}
|
|
16362
|
+
return async (_input, output) => {
|
|
16363
|
+
const messages = output?.messages;
|
|
16364
|
+
if (!messages || messages.length === 0)
|
|
16365
|
+
return;
|
|
16366
|
+
let lastUserMessageIndex = -1;
|
|
16367
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
16368
|
+
if (messages[i]?.info?.role === "user") {
|
|
16369
|
+
lastUserMessageIndex = i;
|
|
16370
|
+
break;
|
|
16371
|
+
}
|
|
16372
|
+
}
|
|
16373
|
+
if (lastUserMessageIndex === -1)
|
|
16374
|
+
return;
|
|
16375
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
16376
|
+
if (!lastUserMessage?.parts)
|
|
16377
|
+
return;
|
|
16378
|
+
const agent = lastUserMessage.info?.agent;
|
|
16379
|
+
const strippedAgent = agent ? stripKnownSwarmPrefix(agent) : undefined;
|
|
16380
|
+
if (strippedAgent && strippedAgent !== "architect")
|
|
16381
|
+
return;
|
|
16382
|
+
const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
|
|
16383
|
+
if (textPartIndex === -1)
|
|
16384
|
+
return;
|
|
16385
|
+
const textPart = lastUserMessage.parts[textPartIndex];
|
|
16386
|
+
const text = textPart.text ?? "";
|
|
16387
|
+
const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
|
|
16388
|
+
if (!coderDelegationPattern.test(text))
|
|
16389
|
+
return;
|
|
16390
|
+
const warnings = [];
|
|
16391
|
+
if (text.length > delegationMaxChars) {
|
|
16392
|
+
warnings.push(`Delegation exceeds recommended size (${text.length} chars, limit ${delegationMaxChars}). Consider splitting into smaller tasks.`);
|
|
16393
|
+
}
|
|
16394
|
+
const fileMatches = text.match(/^FILE:/gm);
|
|
16395
|
+
if (fileMatches && fileMatches.length > 1) {
|
|
16396
|
+
warnings.push(`Multiple FILE: directives detected (${fileMatches.length}). Each coder task should target ONE file.`);
|
|
16397
|
+
}
|
|
16398
|
+
const taskMatches = text.match(/^TASK:/gm);
|
|
16399
|
+
if (taskMatches && taskMatches.length > 1) {
|
|
16400
|
+
warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
|
|
16401
|
+
}
|
|
16402
|
+
const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with)\b/gi;
|
|
16403
|
+
const batchingMatches = text.match(batchingPattern);
|
|
16404
|
+
if (batchingMatches && batchingMatches.length > 0) {
|
|
16405
|
+
warnings.push("Batching language detected. Break compound objectives into separate coder calls.");
|
|
16406
|
+
}
|
|
16407
|
+
if (warnings.length === 0)
|
|
16408
|
+
return;
|
|
16409
|
+
const warningText = `[\u26A0\uFE0F DELEGATION GATE: Your coder delegation may be too complex. Issues:
|
|
16410
|
+
${warnings.join(`
|
|
16411
|
+
`)}
|
|
16412
|
+
Split into smaller, atomic tasks for better results.]`;
|
|
16413
|
+
const originalText = textPart.text ?? "";
|
|
16414
|
+
textPart.text = `${warningText}
|
|
16415
|
+
|
|
16416
|
+
${originalText}`;
|
|
16417
|
+
};
|
|
16418
|
+
}
|
|
16320
16419
|
// src/hooks/guardrails.ts
|
|
16321
16420
|
function createGuardrailsHooks(config2) {
|
|
16322
16421
|
if (config2.enabled === false) {
|
|
@@ -16328,6 +16427,11 @@ function createGuardrailsHooks(config2) {
|
|
|
16328
16427
|
}
|
|
16329
16428
|
return {
|
|
16330
16429
|
toolBefore: async (input, output) => {
|
|
16430
|
+
const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
|
|
16431
|
+
const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
|
|
16432
|
+
if (strippedAgent === ORCHESTRATOR_NAME) {
|
|
16433
|
+
return;
|
|
16434
|
+
}
|
|
16331
16435
|
const agentName = swarmState.activeAgent.get(input.sessionID);
|
|
16332
16436
|
const session = ensureAgentSession(input.sessionID, agentName);
|
|
16333
16437
|
const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
|
|
@@ -16536,7 +16640,69 @@ ${originalText}`;
|
|
|
16536
16640
|
})
|
|
16537
16641
|
};
|
|
16538
16642
|
}
|
|
16643
|
+
// src/hooks/context-scoring.ts
|
|
16644
|
+
function calculateAgeFactor(ageHours, config2) {
|
|
16645
|
+
if (ageHours <= 0) {
|
|
16646
|
+
return 1;
|
|
16647
|
+
}
|
|
16648
|
+
if (config2.mode === "exponential") {
|
|
16649
|
+
return 2 ** (-ageHours / config2.half_life_hours);
|
|
16650
|
+
} else {
|
|
16651
|
+
const linearFactor = 1 - ageHours / (config2.half_life_hours * 2);
|
|
16652
|
+
return Math.max(0, linearFactor);
|
|
16653
|
+
}
|
|
16654
|
+
}
|
|
16655
|
+
function calculateBaseScore(candidate, weights, decayConfig) {
|
|
16656
|
+
const { kind, metadata } = candidate;
|
|
16657
|
+
const phase = kind === "phase" ? 1 : 0;
|
|
16658
|
+
const currentTask = metadata.isCurrentTask ? 1 : 0;
|
|
16659
|
+
const blockedTask = metadata.isBlockedTask ? 1 : 0;
|
|
16660
|
+
const recentFailure = metadata.hasFailure ? 1 : 0;
|
|
16661
|
+
const recentSuccess = metadata.hasSuccess ? 1 : 0;
|
|
16662
|
+
const evidencePresence = metadata.hasEvidence ? 1 : 0;
|
|
16663
|
+
let decisionRecency = 0;
|
|
16664
|
+
if (kind === "decision" && metadata.decisionAgeHours !== undefined) {
|
|
16665
|
+
decisionRecency = calculateAgeFactor(metadata.decisionAgeHours, decayConfig);
|
|
16666
|
+
}
|
|
16667
|
+
const dependencyProximity = 1 / (1 + (metadata.dependencyDepth ?? 0));
|
|
16668
|
+
return weights.phase * phase + weights.current_task * currentTask + weights.blocked_task * blockedTask + weights.recent_failure * recentFailure + weights.recent_success * recentSuccess + weights.evidence_presence * evidencePresence + weights.decision_recency * decisionRecency + weights.dependency_proximity * dependencyProximity;
|
|
16669
|
+
}
|
|
16670
|
+
function rankCandidates(candidates, config2) {
|
|
16671
|
+
if (!config2.enabled) {
|
|
16672
|
+
return candidates.map((c) => ({ ...c, score: 0 }));
|
|
16673
|
+
}
|
|
16674
|
+
if (candidates.length === 0) {
|
|
16675
|
+
return [];
|
|
16676
|
+
}
|
|
16677
|
+
const scored = candidates.map((candidate) => {
|
|
16678
|
+
const score = calculateBaseScore(candidate, config2.weights, config2.decision_decay);
|
|
16679
|
+
return { ...candidate, score };
|
|
16680
|
+
});
|
|
16681
|
+
scored.sort((a, b) => {
|
|
16682
|
+
if (b.score !== a.score) {
|
|
16683
|
+
return b.score - a.score;
|
|
16684
|
+
}
|
|
16685
|
+
if (b.priority !== a.priority) {
|
|
16686
|
+
return b.priority - a.priority;
|
|
16687
|
+
}
|
|
16688
|
+
return a.id.localeCompare(b.id);
|
|
16689
|
+
});
|
|
16690
|
+
return scored.slice(0, config2.max_candidates);
|
|
16691
|
+
}
|
|
16692
|
+
|
|
16539
16693
|
// src/hooks/system-enhancer.ts
|
|
16694
|
+
function estimateContentType(text) {
|
|
16695
|
+
if (text.includes("```") || text.includes("function ") || text.includes("const ")) {
|
|
16696
|
+
return "code";
|
|
16697
|
+
}
|
|
16698
|
+
if (text.startsWith("{") || text.startsWith("[")) {
|
|
16699
|
+
return "json";
|
|
16700
|
+
}
|
|
16701
|
+
if (text.includes("#") || text.includes("*") || text.includes("- ")) {
|
|
16702
|
+
return "markdown";
|
|
16703
|
+
}
|
|
16704
|
+
return "prose";
|
|
16705
|
+
}
|
|
16540
16706
|
function createSystemEnhancerHook(config2, directory) {
|
|
16541
16707
|
const enabled = config2.hooks?.system_enhancer !== false;
|
|
16542
16708
|
if (!enabled) {
|
|
@@ -16556,44 +16722,133 @@ function createSystemEnhancerHook(config2, directory) {
|
|
|
16556
16722
|
const maxInjectionTokens = config2.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
|
|
16557
16723
|
let injectedTokens = 0;
|
|
16558
16724
|
const contextContent = await readSwarmFileAsync(directory, "context.md");
|
|
16559
|
-
const
|
|
16560
|
-
if (
|
|
16561
|
-
const
|
|
16562
|
-
if (
|
|
16563
|
-
|
|
16725
|
+
const scoringEnabled = config2.context_budget?.scoring?.enabled === true;
|
|
16726
|
+
if (!scoringEnabled) {
|
|
16727
|
+
const plan2 = await loadPlan(directory);
|
|
16728
|
+
if (plan2 && plan2.migration_status !== "migration_failed") {
|
|
16729
|
+
const currentPhase2 = extractCurrentPhaseFromPlan(plan2);
|
|
16730
|
+
if (currentPhase2) {
|
|
16731
|
+
tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
|
|
16732
|
+
}
|
|
16733
|
+
const currentTask2 = extractCurrentTaskFromPlan(plan2);
|
|
16734
|
+
if (currentTask2) {
|
|
16735
|
+
tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
|
|
16736
|
+
}
|
|
16737
|
+
} else {
|
|
16738
|
+
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
16739
|
+
if (planContent) {
|
|
16740
|
+
const currentPhase2 = extractCurrentPhase(planContent);
|
|
16741
|
+
if (currentPhase2) {
|
|
16742
|
+
tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
|
|
16743
|
+
}
|
|
16744
|
+
const currentTask2 = extractCurrentTask(planContent);
|
|
16745
|
+
if (currentTask2) {
|
|
16746
|
+
tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
|
|
16747
|
+
}
|
|
16748
|
+
}
|
|
16564
16749
|
}
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16750
|
+
if (contextContent) {
|
|
16751
|
+
const decisions = extractDecisions(contextContent, 200);
|
|
16752
|
+
if (decisions) {
|
|
16753
|
+
tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
|
|
16754
|
+
}
|
|
16755
|
+
if (config2.hooks?.agent_activity !== false && _input.sessionID) {
|
|
16756
|
+
const activeAgent = swarmState.activeAgent.get(_input.sessionID);
|
|
16757
|
+
if (activeAgent) {
|
|
16758
|
+
const agentContext = extractAgentContext(contextContent, activeAgent, config2.hooks?.agent_awareness_max_chars ?? 300);
|
|
16759
|
+
if (agentContext) {
|
|
16760
|
+
tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16568
16764
|
}
|
|
16765
|
+
return;
|
|
16766
|
+
}
|
|
16767
|
+
const userScoringConfig = config2.context_budget?.scoring;
|
|
16768
|
+
const candidates = [];
|
|
16769
|
+
let idCounter = 0;
|
|
16770
|
+
const effectiveConfig = userScoringConfig?.weights ? {
|
|
16771
|
+
...DEFAULT_SCORING_CONFIG,
|
|
16772
|
+
...userScoringConfig,
|
|
16773
|
+
weights: userScoringConfig.weights
|
|
16774
|
+
} : DEFAULT_SCORING_CONFIG;
|
|
16775
|
+
const plan = await loadPlan(directory);
|
|
16776
|
+
let currentPhase = null;
|
|
16777
|
+
let currentTask = null;
|
|
16778
|
+
if (plan && plan.migration_status !== "migration_failed") {
|
|
16779
|
+
currentPhase = extractCurrentPhaseFromPlan(plan);
|
|
16780
|
+
currentTask = extractCurrentTaskFromPlan(plan);
|
|
16569
16781
|
} else {
|
|
16570
16782
|
const planContent = await readSwarmFileAsync(directory, "plan.md");
|
|
16571
16783
|
if (planContent) {
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
|
|
16575
|
-
}
|
|
16576
|
-
const currentTask = extractCurrentTask(planContent);
|
|
16577
|
-
if (currentTask) {
|
|
16578
|
-
tryInject(`[SWARM CONTEXT] Current task: ${currentTask}`);
|
|
16579
|
-
}
|
|
16784
|
+
currentPhase = extractCurrentPhase(planContent);
|
|
16785
|
+
currentTask = extractCurrentTask(planContent);
|
|
16580
16786
|
}
|
|
16581
16787
|
}
|
|
16788
|
+
if (currentPhase) {
|
|
16789
|
+
const text = `[SWARM CONTEXT] Current phase: ${currentPhase}`;
|
|
16790
|
+
candidates.push({
|
|
16791
|
+
id: `candidate-${idCounter++}`,
|
|
16792
|
+
kind: "phase",
|
|
16793
|
+
text,
|
|
16794
|
+
tokens: estimateTokens(text),
|
|
16795
|
+
priority: 1,
|
|
16796
|
+
metadata: { contentType: estimateContentType(text) }
|
|
16797
|
+
});
|
|
16798
|
+
}
|
|
16799
|
+
if (currentTask) {
|
|
16800
|
+
const text = `[SWARM CONTEXT] Current task: ${currentTask}`;
|
|
16801
|
+
candidates.push({
|
|
16802
|
+
id: `candidate-${idCounter++}`,
|
|
16803
|
+
kind: "task",
|
|
16804
|
+
text,
|
|
16805
|
+
tokens: estimateTokens(text),
|
|
16806
|
+
priority: 2,
|
|
16807
|
+
metadata: {
|
|
16808
|
+
contentType: estimateContentType(text),
|
|
16809
|
+
isCurrentTask: true
|
|
16810
|
+
}
|
|
16811
|
+
});
|
|
16812
|
+
}
|
|
16582
16813
|
if (contextContent) {
|
|
16583
16814
|
const decisions = extractDecisions(contextContent, 200);
|
|
16584
16815
|
if (decisions) {
|
|
16585
|
-
|
|
16816
|
+
const text = `[SWARM CONTEXT] Key decisions: ${decisions}`;
|
|
16817
|
+
candidates.push({
|
|
16818
|
+
id: `candidate-${idCounter++}`,
|
|
16819
|
+
kind: "decision",
|
|
16820
|
+
text,
|
|
16821
|
+
tokens: estimateTokens(text),
|
|
16822
|
+
priority: 3,
|
|
16823
|
+
metadata: { contentType: estimateContentType(text) }
|
|
16824
|
+
});
|
|
16586
16825
|
}
|
|
16587
16826
|
if (config2.hooks?.agent_activity !== false && _input.sessionID) {
|
|
16588
16827
|
const activeAgent = swarmState.activeAgent.get(_input.sessionID);
|
|
16589
16828
|
if (activeAgent) {
|
|
16590
16829
|
const agentContext = extractAgentContext(contextContent, activeAgent, config2.hooks?.agent_awareness_max_chars ?? 300);
|
|
16591
16830
|
if (agentContext) {
|
|
16592
|
-
|
|
16831
|
+
const text = `[SWARM AGENT CONTEXT] ${agentContext}`;
|
|
16832
|
+
candidates.push({
|
|
16833
|
+
id: `candidate-${idCounter++}`,
|
|
16834
|
+
kind: "agent_context",
|
|
16835
|
+
text,
|
|
16836
|
+
tokens: estimateTokens(text),
|
|
16837
|
+
priority: 4,
|
|
16838
|
+
metadata: { contentType: estimateContentType(text) }
|
|
16839
|
+
});
|
|
16593
16840
|
}
|
|
16594
16841
|
}
|
|
16595
16842
|
}
|
|
16596
16843
|
}
|
|
16844
|
+
const ranked = rankCandidates(candidates, effectiveConfig);
|
|
16845
|
+
for (const candidate of ranked) {
|
|
16846
|
+
if (injectedTokens + candidate.tokens > maxInjectionTokens) {
|
|
16847
|
+
continue;
|
|
16848
|
+
}
|
|
16849
|
+
output.system.push(candidate.text);
|
|
16850
|
+
injectedTokens += candidate.tokens;
|
|
16851
|
+
}
|
|
16597
16852
|
} catch (error49) {
|
|
16598
16853
|
warn("System enhancer failed:", error49);
|
|
16599
16854
|
}
|
|
@@ -29338,6 +29593,7 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29338
29593
|
const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
|
|
29339
29594
|
const activityHooks = createAgentActivityHooks(config3, ctx.directory);
|
|
29340
29595
|
const delegationHandler = createDelegationTrackerHook(config3);
|
|
29596
|
+
const delegationGateHandler = createDelegationGateHook(config3);
|
|
29341
29597
|
const guardrailsConfig = GuardrailsConfigSchema.parse(config3.guardrails ?? {});
|
|
29342
29598
|
const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
|
|
29343
29599
|
log("Plugin initialized", {
|
|
@@ -29385,7 +29641,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29385
29641
|
"experimental.chat.messages.transform": composeHandlers(...[
|
|
29386
29642
|
pipelineHook["experimental.chat.messages.transform"],
|
|
29387
29643
|
contextBudgetHandler,
|
|
29388
|
-
guardrailsHooks.messagesTransform
|
|
29644
|
+
guardrailsHooks.messagesTransform,
|
|
29645
|
+
delegationGateHandler
|
|
29389
29646
|
].filter((fn) => Boolean(fn))),
|
|
29390
29647
|
"experimental.chat.system.transform": systemEnhancerHook["experimental.chat.system.transform"],
|
|
29391
29648
|
"experimental.session.compacting": compactionHook["experimental.session.compacting"],
|
|
@@ -29394,6 +29651,12 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
29394
29651
|
if (!swarmState.activeAgent.has(input.sessionID)) {
|
|
29395
29652
|
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
29396
29653
|
}
|
|
29654
|
+
const session = swarmState.agentSessions.get(input.sessionID);
|
|
29655
|
+
const activeAgent = swarmState.activeAgent.get(input.sessionID);
|
|
29656
|
+
if (session && activeAgent && activeAgent !== ORCHESTRATOR_NAME && session.delegationActive === false) {
|
|
29657
|
+
swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
|
|
29658
|
+
ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
|
|
29659
|
+
}
|
|
29397
29660
|
await guardrailsHooks.toolBefore(input, output);
|
|
29398
29661
|
await safeHook(activityHooks.toolBefore)(input, output);
|
|
29399
29662
|
},
|
package/dist/state.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface AgentSessionState {
|
|
|
61
61
|
hardLimitHit: boolean;
|
|
62
62
|
/** Timestamp of most recent SUCCESSFUL tool call (for idle timeout) */
|
|
63
63
|
lastSuccessTime: number;
|
|
64
|
+
/** Whether active delegation is in progress for this session */
|
|
65
|
+
delegationActive: boolean;
|
|
64
66
|
}
|
|
65
67
|
/**
|
|
66
68
|
* Singleton state object for sharing data across hooks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
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",
|