opencode-swarm 5.0.9 → 5.1.0

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.
@@ -9,3 +9,13 @@ export type AgentName = (typeof ALL_AGENT_NAMES)[number];
9
9
  export declare const DEFAULT_MODELS: Record<string, string>;
10
10
  export declare function isQAAgent(name: string): name is QAAgentName;
11
11
  export declare function isSubagent(name: string): boolean;
12
+ import type { ScoringConfig } from './schema';
13
+ export declare const DEFAULT_SCORING_CONFIG: ScoringConfig;
14
+ /**
15
+ * Resolve scoring configuration by deep-merging user config with defaults.
16
+ * Missing scoring block → use defaults; partial weights → merge with defaults.
17
+ *
18
+ * @param userConfig - Optional user-provided scoring configuration
19
+ * @returns The effective scoring configuration with all defaults applied
20
+ */
21
+ export declare function resolveScoringConfig(userConfig?: ScoringConfig): ScoringConfig;
@@ -22,12 +22,93 @@ export declare const HooksConfigSchema: z.ZodObject<{
22
22
  agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
23
23
  }, z.core.$strip>;
24
24
  export type HooksConfig = z.infer<typeof HooksConfigSchema>;
25
+ export declare const ScoringWeightsSchema: z.ZodObject<{
26
+ phase: z.ZodDefault<z.ZodNumber>;
27
+ current_task: z.ZodDefault<z.ZodNumber>;
28
+ blocked_task: z.ZodDefault<z.ZodNumber>;
29
+ recent_failure: z.ZodDefault<z.ZodNumber>;
30
+ recent_success: z.ZodDefault<z.ZodNumber>;
31
+ evidence_presence: z.ZodDefault<z.ZodNumber>;
32
+ decision_recency: z.ZodDefault<z.ZodNumber>;
33
+ dependency_proximity: z.ZodDefault<z.ZodNumber>;
34
+ }, z.core.$strip>;
35
+ export type ScoringWeights = z.infer<typeof ScoringWeightsSchema>;
36
+ export declare const DecisionDecaySchema: z.ZodObject<{
37
+ mode: z.ZodDefault<z.ZodEnum<{
38
+ linear: "linear";
39
+ exponential: "exponential";
40
+ }>>;
41
+ half_life_hours: z.ZodDefault<z.ZodNumber>;
42
+ }, z.core.$strip>;
43
+ export type DecisionDecay = z.infer<typeof DecisionDecaySchema>;
44
+ export declare const TokenRatiosSchema: z.ZodObject<{
45
+ prose: z.ZodDefault<z.ZodNumber>;
46
+ code: z.ZodDefault<z.ZodNumber>;
47
+ markdown: z.ZodDefault<z.ZodNumber>;
48
+ json: z.ZodDefault<z.ZodNumber>;
49
+ }, z.core.$strip>;
50
+ export type TokenRatios = z.infer<typeof TokenRatiosSchema>;
51
+ export declare const ScoringConfigSchema: z.ZodObject<{
52
+ enabled: z.ZodDefault<z.ZodBoolean>;
53
+ max_candidates: z.ZodDefault<z.ZodNumber>;
54
+ weights: z.ZodOptional<z.ZodObject<{
55
+ phase: z.ZodDefault<z.ZodNumber>;
56
+ current_task: z.ZodDefault<z.ZodNumber>;
57
+ blocked_task: z.ZodDefault<z.ZodNumber>;
58
+ recent_failure: z.ZodDefault<z.ZodNumber>;
59
+ recent_success: z.ZodDefault<z.ZodNumber>;
60
+ evidence_presence: z.ZodDefault<z.ZodNumber>;
61
+ decision_recency: z.ZodDefault<z.ZodNumber>;
62
+ dependency_proximity: z.ZodDefault<z.ZodNumber>;
63
+ }, z.core.$strip>>;
64
+ decision_decay: z.ZodOptional<z.ZodObject<{
65
+ mode: z.ZodDefault<z.ZodEnum<{
66
+ linear: "linear";
67
+ exponential: "exponential";
68
+ }>>;
69
+ half_life_hours: z.ZodDefault<z.ZodNumber>;
70
+ }, z.core.$strip>>;
71
+ token_ratios: z.ZodOptional<z.ZodObject<{
72
+ prose: z.ZodDefault<z.ZodNumber>;
73
+ code: z.ZodDefault<z.ZodNumber>;
74
+ markdown: z.ZodDefault<z.ZodNumber>;
75
+ json: z.ZodDefault<z.ZodNumber>;
76
+ }, z.core.$strip>>;
77
+ }, z.core.$strip>;
78
+ export type ScoringConfig = z.infer<typeof ScoringConfigSchema>;
25
79
  export declare const ContextBudgetConfigSchema: z.ZodObject<{
26
80
  enabled: z.ZodDefault<z.ZodBoolean>;
27
81
  warn_threshold: z.ZodDefault<z.ZodNumber>;
28
82
  critical_threshold: z.ZodDefault<z.ZodNumber>;
29
83
  model_limits: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
30
84
  max_injection_tokens: z.ZodDefault<z.ZodNumber>;
85
+ scoring: z.ZodOptional<z.ZodObject<{
86
+ enabled: z.ZodDefault<z.ZodBoolean>;
87
+ max_candidates: z.ZodDefault<z.ZodNumber>;
88
+ weights: z.ZodOptional<z.ZodObject<{
89
+ phase: z.ZodDefault<z.ZodNumber>;
90
+ current_task: z.ZodDefault<z.ZodNumber>;
91
+ blocked_task: z.ZodDefault<z.ZodNumber>;
92
+ recent_failure: z.ZodDefault<z.ZodNumber>;
93
+ recent_success: z.ZodDefault<z.ZodNumber>;
94
+ evidence_presence: z.ZodDefault<z.ZodNumber>;
95
+ decision_recency: z.ZodDefault<z.ZodNumber>;
96
+ dependency_proximity: z.ZodDefault<z.ZodNumber>;
97
+ }, z.core.$strip>>;
98
+ decision_decay: z.ZodOptional<z.ZodObject<{
99
+ mode: z.ZodDefault<z.ZodEnum<{
100
+ linear: "linear";
101
+ exponential: "exponential";
102
+ }>>;
103
+ half_life_hours: z.ZodDefault<z.ZodNumber>;
104
+ }, z.core.$strip>>;
105
+ token_ratios: z.ZodOptional<z.ZodObject<{
106
+ prose: z.ZodDefault<z.ZodNumber>;
107
+ code: z.ZodDefault<z.ZodNumber>;
108
+ markdown: z.ZodDefault<z.ZodNumber>;
109
+ json: z.ZodDefault<z.ZodNumber>;
110
+ }, z.core.$strip>>;
111
+ }, z.core.$strip>>;
31
112
  }, z.core.$strip>;
32
113
  export type ContextBudgetConfig = z.infer<typeof ContextBudgetConfigSchema>;
33
114
  export declare const EvidenceConfigSchema: z.ZodObject<{
@@ -126,6 +207,33 @@ export declare const PluginConfigSchema: z.ZodObject<{
126
207
  critical_threshold: z.ZodDefault<z.ZodNumber>;
127
208
  model_limits: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
128
209
  max_injection_tokens: z.ZodDefault<z.ZodNumber>;
210
+ scoring: z.ZodOptional<z.ZodObject<{
211
+ enabled: z.ZodDefault<z.ZodBoolean>;
212
+ max_candidates: z.ZodDefault<z.ZodNumber>;
213
+ weights: z.ZodOptional<z.ZodObject<{
214
+ phase: z.ZodDefault<z.ZodNumber>;
215
+ current_task: z.ZodDefault<z.ZodNumber>;
216
+ blocked_task: z.ZodDefault<z.ZodNumber>;
217
+ recent_failure: z.ZodDefault<z.ZodNumber>;
218
+ recent_success: z.ZodDefault<z.ZodNumber>;
219
+ evidence_presence: z.ZodDefault<z.ZodNumber>;
220
+ decision_recency: z.ZodDefault<z.ZodNumber>;
221
+ dependency_proximity: z.ZodDefault<z.ZodNumber>;
222
+ }, z.core.$strip>>;
223
+ decision_decay: z.ZodOptional<z.ZodObject<{
224
+ mode: z.ZodDefault<z.ZodEnum<{
225
+ linear: "linear";
226
+ exponential: "exponential";
227
+ }>>;
228
+ half_life_hours: z.ZodDefault<z.ZodNumber>;
229
+ }, z.core.$strip>>;
230
+ token_ratios: z.ZodOptional<z.ZodObject<{
231
+ prose: z.ZodDefault<z.ZodNumber>;
232
+ code: z.ZodDefault<z.ZodNumber>;
233
+ markdown: z.ZodDefault<z.ZodNumber>;
234
+ json: z.ZodDefault<z.ZodNumber>;
235
+ }, z.core.$strip>>;
236
+ }, z.core.$strip>>;
129
237
  }, z.core.$strip>>;
130
238
  guardrails: z.ZodOptional<z.ZodObject<{
131
239
  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[];
package/dist/index.js CHANGED
@@ -26,29 +26,6 @@ var __export = (target, all) => {
26
26
  };
27
27
  var __require = import.meta.require;
28
28
 
29
- // src/config/constants.ts
30
- var QA_AGENTS = ["reviewer", "critic"];
31
- var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
32
- var ORCHESTRATOR_NAME = "architect";
33
- var ALL_SUBAGENT_NAMES = [
34
- "sme",
35
- ...QA_AGENTS,
36
- ...PIPELINE_AGENTS
37
- ];
38
- var ALL_AGENT_NAMES = [
39
- ORCHESTRATOR_NAME,
40
- ...ALL_SUBAGENT_NAMES
41
- ];
42
- var DEFAULT_MODELS = {
43
- architect: "anthropic/claude-sonnet-4-5",
44
- explorer: "google/gemini-2.0-flash",
45
- coder: "anthropic/claude-sonnet-4-5",
46
- test_engineer: "google/gemini-2.0-flash",
47
- sme: "google/gemini-2.0-flash",
48
- reviewer: "google/gemini-2.0-flash",
49
- critic: "google/gemini-2.0-flash",
50
- default: "google/gemini-2.0-flash"
51
- };
52
29
  // src/config/loader.ts
53
30
  import * as fs from "fs";
54
31
  import * as os from "os";
@@ -13603,12 +13580,40 @@ var HooksConfigSchema = exports_external.object({
13603
13580
  delegation_tracker: exports_external.boolean().default(false),
13604
13581
  agent_awareness_max_chars: exports_external.number().min(50).max(2000).default(300)
13605
13582
  });
13583
+ var ScoringWeightsSchema = exports_external.object({
13584
+ phase: exports_external.number().min(0).max(5).default(1),
13585
+ current_task: exports_external.number().min(0).max(5).default(2),
13586
+ blocked_task: exports_external.number().min(0).max(5).default(1.5),
13587
+ recent_failure: exports_external.number().min(0).max(5).default(2.5),
13588
+ recent_success: exports_external.number().min(0).max(5).default(0.5),
13589
+ evidence_presence: exports_external.number().min(0).max(5).default(1),
13590
+ decision_recency: exports_external.number().min(0).max(5).default(1.5),
13591
+ dependency_proximity: exports_external.number().min(0).max(5).default(1)
13592
+ });
13593
+ var DecisionDecaySchema = exports_external.object({
13594
+ mode: exports_external.enum(["linear", "exponential"]).default("exponential"),
13595
+ half_life_hours: exports_external.number().min(1).max(168).default(24)
13596
+ });
13597
+ var TokenRatiosSchema = exports_external.object({
13598
+ prose: exports_external.number().min(0.1).max(1).default(0.25),
13599
+ code: exports_external.number().min(0.1).max(1).default(0.4),
13600
+ markdown: exports_external.number().min(0.1).max(1).default(0.3),
13601
+ json: exports_external.number().min(0.1).max(1).default(0.35)
13602
+ });
13603
+ var ScoringConfigSchema = exports_external.object({
13604
+ enabled: exports_external.boolean().default(false),
13605
+ max_candidates: exports_external.number().min(10).max(500).default(100),
13606
+ weights: ScoringWeightsSchema.optional(),
13607
+ decision_decay: DecisionDecaySchema.optional(),
13608
+ token_ratios: TokenRatiosSchema.optional()
13609
+ });
13606
13610
  var ContextBudgetConfigSchema = exports_external.object({
13607
13611
  enabled: exports_external.boolean().default(true),
13608
13612
  warn_threshold: exports_external.number().min(0).max(1).default(0.7),
13609
13613
  critical_threshold: exports_external.number().min(0).max(1).default(0.9),
13610
13614
  model_limits: exports_external.record(exports_external.string(), exports_external.number().min(1000)).default({ default: 128000 }),
13611
- max_injection_tokens: exports_external.number().min(100).max(50000).default(4000)
13615
+ max_injection_tokens: exports_external.number().min(100).max(50000).default(4000),
13616
+ scoring: ScoringConfigSchema.optional()
13612
13617
  });
13613
13618
  var EvidenceConfigSchema = exports_external.object({
13614
13619
  enabled: exports_external.boolean().default(true),
@@ -13691,8 +13696,10 @@ function resolveGuardrailsConfig(base, agentName) {
13691
13696
  return base;
13692
13697
  }
13693
13698
  const baseName = stripKnownSwarmPrefix(agentName);
13694
- const builtIn = DEFAULT_AGENT_PROFILES[baseName];
13695
- const userProfile = base.profiles?.[baseName] ?? base.profiles?.[agentName];
13699
+ const builtInLookup = DEFAULT_AGENT_PROFILES[baseName];
13700
+ const effectiveName = builtInLookup ? baseName : ORCHESTRATOR_NAME;
13701
+ const builtIn = DEFAULT_AGENT_PROFILES[effectiveName];
13702
+ const userProfile = base.profiles?.[effectiveName] ?? base.profiles?.[baseName] ?? base.profiles?.[agentName];
13696
13703
  if (!builtIn && !userProfile) {
13697
13704
  return base;
13698
13705
  }
@@ -13803,6 +13810,54 @@ function loadAgentPrompt(agentName) {
13803
13810
  }
13804
13811
  return result;
13805
13812
  }
13813
+
13814
+ // src/config/constants.ts
13815
+ var QA_AGENTS = ["reviewer", "critic"];
13816
+ var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
13817
+ var ORCHESTRATOR_NAME = "architect";
13818
+ var ALL_SUBAGENT_NAMES = [
13819
+ "sme",
13820
+ ...QA_AGENTS,
13821
+ ...PIPELINE_AGENTS
13822
+ ];
13823
+ var ALL_AGENT_NAMES = [
13824
+ ORCHESTRATOR_NAME,
13825
+ ...ALL_SUBAGENT_NAMES
13826
+ ];
13827
+ var DEFAULT_MODELS = {
13828
+ architect: "anthropic/claude-sonnet-4-5",
13829
+ explorer: "google/gemini-2.0-flash",
13830
+ coder: "anthropic/claude-sonnet-4-5",
13831
+ test_engineer: "google/gemini-2.0-flash",
13832
+ sme: "google/gemini-2.0-flash",
13833
+ reviewer: "google/gemini-2.0-flash",
13834
+ critic: "google/gemini-2.0-flash",
13835
+ default: "google/gemini-2.0-flash"
13836
+ };
13837
+ var DEFAULT_SCORING_CONFIG = {
13838
+ enabled: false,
13839
+ max_candidates: 100,
13840
+ weights: {
13841
+ phase: 1,
13842
+ current_task: 2,
13843
+ blocked_task: 1.5,
13844
+ recent_failure: 2.5,
13845
+ recent_success: 0.5,
13846
+ evidence_presence: 1,
13847
+ decision_recency: 1.5,
13848
+ dependency_proximity: 1
13849
+ },
13850
+ decision_decay: {
13851
+ mode: "exponential",
13852
+ half_life_hours: 24
13853
+ },
13854
+ token_ratios: {
13855
+ prose: 0.25,
13856
+ code: 0.4,
13857
+ markdown: 0.3,
13858
+ json: 0.35
13859
+ }
13860
+ };
13806
13861
  // src/config/plan-schema.ts
13807
13862
  var TaskStatusSchema = exports_external.enum([
13808
13863
  "pending",
@@ -16011,7 +16066,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
16011
16066
  warningIssued: false,
16012
16067
  warningReason: "",
16013
16068
  hardLimitHit: false,
16014
- lastSuccessTime: now
16069
+ lastSuccessTime: now,
16070
+ delegationActive: false
16015
16071
  };
16016
16072
  swarmState.agentSessions.set(sessionId, sessionState);
16017
16073
  }
@@ -16032,6 +16088,7 @@ function ensureAgentSession(sessionId, agentName) {
16032
16088
  session.warningReason = "";
16033
16089
  session.hardLimitHit = false;
16034
16090
  session.lastSuccessTime = now;
16091
+ session.delegationActive = false;
16035
16092
  }
16036
16093
  session.lastToolCallTime = now;
16037
16094
  return session;
@@ -16267,15 +16324,21 @@ function createContextBudgetHandler(config2) {
16267
16324
  function createDelegationTrackerHook(config2) {
16268
16325
  return async (input, _output) => {
16269
16326
  if (!input.agent || input.agent === "") {
16327
+ const session2 = swarmState.agentSessions.get(input.sessionID);
16328
+ if (session2) {
16329
+ session2.delegationActive = false;
16330
+ }
16270
16331
  return;
16271
16332
  }
16333
+ const agentName = input.agent;
16272
16334
  const previousAgent = swarmState.activeAgent.get(input.sessionID);
16273
- swarmState.activeAgent.set(input.sessionID, input.agent);
16274
- ensureAgentSession(input.sessionID, input.agent);
16275
- if (config2.hooks?.delegation_tracker === true && previousAgent && previousAgent !== input.agent) {
16335
+ swarmState.activeAgent.set(input.sessionID, agentName);
16336
+ const session = ensureAgentSession(input.sessionID, agentName);
16337
+ session.delegationActive = true;
16338
+ if (config2.hooks?.delegation_tracker === true && previousAgent && previousAgent !== agentName) {
16276
16339
  const entry = {
16277
16340
  from: previousAgent,
16278
- to: input.agent,
16341
+ to: agentName,
16279
16342
  timestamp: Date.now()
16280
16343
  };
16281
16344
  if (!swarmState.delegationChains.has(input.sessionID)) {
@@ -16506,7 +16569,69 @@ ${originalText}`;
16506
16569
  })
16507
16570
  };
16508
16571
  }
16572
+ // src/hooks/context-scoring.ts
16573
+ function calculateAgeFactor(ageHours, config2) {
16574
+ if (ageHours <= 0) {
16575
+ return 1;
16576
+ }
16577
+ if (config2.mode === "exponential") {
16578
+ return 2 ** (-ageHours / config2.half_life_hours);
16579
+ } else {
16580
+ const linearFactor = 1 - ageHours / (config2.half_life_hours * 2);
16581
+ return Math.max(0, linearFactor);
16582
+ }
16583
+ }
16584
+ function calculateBaseScore(candidate, weights, decayConfig) {
16585
+ const { kind, metadata } = candidate;
16586
+ const phase = kind === "phase" ? 1 : 0;
16587
+ const currentTask = metadata.isCurrentTask ? 1 : 0;
16588
+ const blockedTask = metadata.isBlockedTask ? 1 : 0;
16589
+ const recentFailure = metadata.hasFailure ? 1 : 0;
16590
+ const recentSuccess = metadata.hasSuccess ? 1 : 0;
16591
+ const evidencePresence = metadata.hasEvidence ? 1 : 0;
16592
+ let decisionRecency = 0;
16593
+ if (kind === "decision" && metadata.decisionAgeHours !== undefined) {
16594
+ decisionRecency = calculateAgeFactor(metadata.decisionAgeHours, decayConfig);
16595
+ }
16596
+ const dependencyProximity = 1 / (1 + (metadata.dependencyDepth ?? 0));
16597
+ 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;
16598
+ }
16599
+ function rankCandidates(candidates, config2) {
16600
+ if (!config2.enabled) {
16601
+ return candidates.map((c) => ({ ...c, score: 0 }));
16602
+ }
16603
+ if (candidates.length === 0) {
16604
+ return [];
16605
+ }
16606
+ const scored = candidates.map((candidate) => {
16607
+ const score = calculateBaseScore(candidate, config2.weights, config2.decision_decay);
16608
+ return { ...candidate, score };
16609
+ });
16610
+ scored.sort((a, b) => {
16611
+ if (b.score !== a.score) {
16612
+ return b.score - a.score;
16613
+ }
16614
+ if (b.priority !== a.priority) {
16615
+ return b.priority - a.priority;
16616
+ }
16617
+ return a.id.localeCompare(b.id);
16618
+ });
16619
+ return scored.slice(0, config2.max_candidates);
16620
+ }
16621
+
16509
16622
  // src/hooks/system-enhancer.ts
16623
+ function estimateContentType(text) {
16624
+ if (text.includes("```") || text.includes("function ") || text.includes("const ")) {
16625
+ return "code";
16626
+ }
16627
+ if (text.startsWith("{") || text.startsWith("[")) {
16628
+ return "json";
16629
+ }
16630
+ if (text.includes("#") || text.includes("*") || text.includes("- ")) {
16631
+ return "markdown";
16632
+ }
16633
+ return "prose";
16634
+ }
16510
16635
  function createSystemEnhancerHook(config2, directory) {
16511
16636
  const enabled = config2.hooks?.system_enhancer !== false;
16512
16637
  if (!enabled) {
@@ -16526,44 +16651,133 @@ function createSystemEnhancerHook(config2, directory) {
16526
16651
  const maxInjectionTokens = config2.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
16527
16652
  let injectedTokens = 0;
16528
16653
  const contextContent = await readSwarmFileAsync(directory, "context.md");
16529
- const plan = await loadPlan(directory);
16530
- if (plan && plan.migration_status !== "migration_failed") {
16531
- const currentPhase = extractCurrentPhaseFromPlan(plan);
16532
- if (currentPhase) {
16533
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
16654
+ const scoringEnabled = config2.context_budget?.scoring?.enabled === true;
16655
+ if (!scoringEnabled) {
16656
+ const plan2 = await loadPlan(directory);
16657
+ if (plan2 && plan2.migration_status !== "migration_failed") {
16658
+ const currentPhase2 = extractCurrentPhaseFromPlan(plan2);
16659
+ if (currentPhase2) {
16660
+ tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
16661
+ }
16662
+ const currentTask2 = extractCurrentTaskFromPlan(plan2);
16663
+ if (currentTask2) {
16664
+ tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
16665
+ }
16666
+ } else {
16667
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
16668
+ if (planContent) {
16669
+ const currentPhase2 = extractCurrentPhase(planContent);
16670
+ if (currentPhase2) {
16671
+ tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
16672
+ }
16673
+ const currentTask2 = extractCurrentTask(planContent);
16674
+ if (currentTask2) {
16675
+ tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
16676
+ }
16677
+ }
16534
16678
  }
16535
- const currentTask = extractCurrentTaskFromPlan(plan);
16536
- if (currentTask) {
16537
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask}`);
16679
+ if (contextContent) {
16680
+ const decisions = extractDecisions(contextContent, 200);
16681
+ if (decisions) {
16682
+ tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
16683
+ }
16684
+ if (config2.hooks?.agent_activity !== false && _input.sessionID) {
16685
+ const activeAgent = swarmState.activeAgent.get(_input.sessionID);
16686
+ if (activeAgent) {
16687
+ const agentContext = extractAgentContext(contextContent, activeAgent, config2.hooks?.agent_awareness_max_chars ?? 300);
16688
+ if (agentContext) {
16689
+ tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
16690
+ }
16691
+ }
16692
+ }
16538
16693
  }
16694
+ return;
16695
+ }
16696
+ const userScoringConfig = config2.context_budget?.scoring;
16697
+ const candidates = [];
16698
+ let idCounter = 0;
16699
+ const effectiveConfig = userScoringConfig?.weights ? {
16700
+ ...DEFAULT_SCORING_CONFIG,
16701
+ ...userScoringConfig,
16702
+ weights: userScoringConfig.weights
16703
+ } : DEFAULT_SCORING_CONFIG;
16704
+ const plan = await loadPlan(directory);
16705
+ let currentPhase = null;
16706
+ let currentTask = null;
16707
+ if (plan && plan.migration_status !== "migration_failed") {
16708
+ currentPhase = extractCurrentPhaseFromPlan(plan);
16709
+ currentTask = extractCurrentTaskFromPlan(plan);
16539
16710
  } else {
16540
16711
  const planContent = await readSwarmFileAsync(directory, "plan.md");
16541
16712
  if (planContent) {
16542
- const currentPhase = extractCurrentPhase(planContent);
16543
- if (currentPhase) {
16544
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
16545
- }
16546
- const currentTask = extractCurrentTask(planContent);
16547
- if (currentTask) {
16548
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask}`);
16549
- }
16713
+ currentPhase = extractCurrentPhase(planContent);
16714
+ currentTask = extractCurrentTask(planContent);
16550
16715
  }
16551
16716
  }
16717
+ if (currentPhase) {
16718
+ const text = `[SWARM CONTEXT] Current phase: ${currentPhase}`;
16719
+ candidates.push({
16720
+ id: `candidate-${idCounter++}`,
16721
+ kind: "phase",
16722
+ text,
16723
+ tokens: estimateTokens(text),
16724
+ priority: 1,
16725
+ metadata: { contentType: estimateContentType(text) }
16726
+ });
16727
+ }
16728
+ if (currentTask) {
16729
+ const text = `[SWARM CONTEXT] Current task: ${currentTask}`;
16730
+ candidates.push({
16731
+ id: `candidate-${idCounter++}`,
16732
+ kind: "task",
16733
+ text,
16734
+ tokens: estimateTokens(text),
16735
+ priority: 2,
16736
+ metadata: {
16737
+ contentType: estimateContentType(text),
16738
+ isCurrentTask: true
16739
+ }
16740
+ });
16741
+ }
16552
16742
  if (contextContent) {
16553
16743
  const decisions = extractDecisions(contextContent, 200);
16554
16744
  if (decisions) {
16555
- tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
16745
+ const text = `[SWARM CONTEXT] Key decisions: ${decisions}`;
16746
+ candidates.push({
16747
+ id: `candidate-${idCounter++}`,
16748
+ kind: "decision",
16749
+ text,
16750
+ tokens: estimateTokens(text),
16751
+ priority: 3,
16752
+ metadata: { contentType: estimateContentType(text) }
16753
+ });
16556
16754
  }
16557
16755
  if (config2.hooks?.agent_activity !== false && _input.sessionID) {
16558
16756
  const activeAgent = swarmState.activeAgent.get(_input.sessionID);
16559
16757
  if (activeAgent) {
16560
16758
  const agentContext = extractAgentContext(contextContent, activeAgent, config2.hooks?.agent_awareness_max_chars ?? 300);
16561
16759
  if (agentContext) {
16562
- tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
16760
+ const text = `[SWARM AGENT CONTEXT] ${agentContext}`;
16761
+ candidates.push({
16762
+ id: `candidate-${idCounter++}`,
16763
+ kind: "agent_context",
16764
+ text,
16765
+ tokens: estimateTokens(text),
16766
+ priority: 4,
16767
+ metadata: { contentType: estimateContentType(text) }
16768
+ });
16563
16769
  }
16564
16770
  }
16565
16771
  }
16566
16772
  }
16773
+ const ranked = rankCandidates(candidates, effectiveConfig);
16774
+ for (const candidate of ranked) {
16775
+ if (injectedTokens + candidate.tokens > maxInjectionTokens) {
16776
+ continue;
16777
+ }
16778
+ output.system.push(candidate.text);
16779
+ injectedTokens += candidate.tokens;
16780
+ }
16567
16781
  } catch (error49) {
16568
16782
  warn("System enhancer failed:", error49);
16569
16783
  }
@@ -29361,6 +29575,15 @@ var OpenCodeSwarm = async (ctx) => {
29361
29575
  "experimental.session.compacting": compactionHook["experimental.session.compacting"],
29362
29576
  "command.execute.before": safeHook(commandHandler),
29363
29577
  "tool.execute.before": async (input, output) => {
29578
+ if (!swarmState.activeAgent.has(input.sessionID)) {
29579
+ swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
29580
+ }
29581
+ const session = swarmState.agentSessions.get(input.sessionID);
29582
+ const activeAgent = swarmState.activeAgent.get(input.sessionID);
29583
+ if (session && activeAgent && activeAgent !== ORCHESTRATOR_NAME && session.delegationActive === false) {
29584
+ swarmState.activeAgent.set(input.sessionID, ORCHESTRATOR_NAME);
29585
+ ensureAgentSession(input.sessionID, ORCHESTRATOR_NAME);
29586
+ }
29364
29587
  await guardrailsHooks.toolBefore(input, output);
29365
29588
  await safeHook(activityHooks.toolBefore)(input, output);
29366
29589
  },
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.0.9",
3
+ "version": "5.1.0",
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",