opencode-swarm 7.74.3 → 7.75.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.
package/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "opencode-swarm",
55
- version: "7.74.3",
55
+ version: "7.75.0",
56
56
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
57
57
  main: "dist/index.js",
58
58
  types: "dist/index.d.ts",
@@ -18015,6 +18015,7 @@ __export(exports_schema, {
18015
18015
  AutomationModeSchema: () => AutomationModeSchema,
18016
18016
  AutomationConfigSchema: () => AutomationConfigSchema,
18017
18017
  AutomationCapabilitiesSchema: () => AutomationCapabilitiesSchema,
18018
+ AutoReviewConfigSchema: () => AutoReviewConfigSchema,
18018
18019
  AuthorityConfigSchema: () => AuthorityConfigSchema,
18019
18020
  ArchitecturalSupervisionConfigSchema: () => ArchitecturalSupervisionConfigSchema,
18020
18021
  AgentThinkingConfigSchema: () => AgentThinkingConfigSchema,
@@ -18100,7 +18101,7 @@ function resolveExternalSkillsConfig(input) {
18100
18101
  };
18101
18102
  return merged;
18102
18103
  }
18103
- var _internals5, SEPARATORS, CANONICAL_ROLES_LONGEST_FIRST, CANONICAL_ROLES_SET, AgentReasoningConfigSchema, AgentThinkingConfigSchema, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, DesignDocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, ContextMapConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, MemoryConfigSchema, CuratorConfigSchema, ArchitecturalSupervisionConfigSchema, KnowledgeApplicationConfigSchema, SkillPropagationConfigSchema, SkillImproverConfigSchema, SpecWriterConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, PrMonitorConfigSchema, ParallelizationConfigSchema, WorktreeIsolationConfigSchema, LeanTurboConfigSchema, StandardTurboConfigSchema, LeanTurboStrategyConfigSchema, TurboConfigSchema, ExternalSkillCandidateSourceTypeSchema, ExternalSkillCandidateEvaluationVerdictSchema, DiscoverySourceSchema, ExternalSkillCandidateSchema, ExternalSkillsConfigSchema, DEFAULT_EXTERNAL_SKILLS_CONFIG, PluginConfigSchema;
18104
+ var _internals5, SEPARATORS, CANONICAL_ROLES_LONGEST_FIRST, CANONICAL_ROLES_SET, AgentReasoningConfigSchema, AgentThinkingConfigSchema, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AutoReviewConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, DesignDocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, ContextMapConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, MemoryConfigSchema, CuratorConfigSchema, ArchitecturalSupervisionConfigSchema, KnowledgeApplicationConfigSchema, SkillPropagationConfigSchema, SkillImproverConfigSchema, SpecWriterConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, PrMonitorConfigSchema, ParallelizationConfigSchema, WorktreeIsolationConfigSchema, LeanTurboConfigSchema, StandardTurboConfigSchema, LeanTurboStrategyConfigSchema, TurboConfigSchema, ExternalSkillCandidateSourceTypeSchema, ExternalSkillCandidateEvaluationVerdictSchema, DiscoverySourceSchema, ExternalSkillCandidateSchema, ExternalSkillsConfigSchema, DEFAULT_EXTERNAL_SKILLS_CONFIG, PluginConfigSchema;
18104
18105
  var init_schema = __esm(() => {
18105
18106
  init_zod();
18106
18107
  init_constants();
@@ -18288,6 +18289,12 @@ var init_schema = __esm(() => {
18288
18289
  "**/token/**"
18289
18290
  ])
18290
18291
  });
18292
+ AutoReviewConfigSchema = exports_external.object({
18293
+ enabled: exports_external.boolean().default(false),
18294
+ trigger: exports_external.enum(["task_completion", "phase_boundary", "both"]).default("phase_boundary"),
18295
+ timeout_ms: exports_external.number().int().min(1e4).max(1800000).default(300000),
18296
+ max_diff_kb: exports_external.number().int().min(16).max(2048).default(256)
18297
+ });
18291
18298
  AdversarialDetectionConfigSchema = exports_external.object({
18292
18299
  enabled: exports_external.boolean().default(true),
18293
18300
  policy: exports_external.enum(["warn", "gate", "ignore"]).default("warn"),
@@ -18971,6 +18978,7 @@ var init_schema = __esm(() => {
18971
18978
  guardrails: GuardrailsConfigSchema.optional(),
18972
18979
  watchdog: WatchdogConfigSchema.optional(),
18973
18980
  self_review: SelfReviewConfigSchema.optional(),
18981
+ auto_review: AutoReviewConfigSchema.optional(),
18974
18982
  tool_filter: ToolFilterConfigSchema.optional(),
18975
18983
  authority: AuthorityConfigSchema.optional(),
18976
18984
  plan_cursor: PlanCursorConfigSchema.optional(),
@@ -19021,6 +19029,7 @@ var init_schema = __esm(() => {
19021
19029
  version_check: exports_external.boolean().default(true).optional(),
19022
19030
  full_auto: exports_external.object({
19023
19031
  enabled: exports_external.boolean().default(false),
19032
+ locked: exports_external.boolean().default(false),
19024
19033
  critic_model: exports_external.string().optional(),
19025
19034
  max_interactions_per_phase: exports_external.number().int().min(5).max(200).default(50),
19026
19035
  deadlock_threshold: exports_external.number().int().min(2).max(10).default(3),
@@ -19034,6 +19043,7 @@ var init_schema = __esm(() => {
19034
19043
  protected_paths: exports_external.array(exports_external.string()).default([
19035
19044
  ".git",
19036
19045
  ".github/workflows",
19046
+ ".opencode",
19037
19047
  ".swarm",
19038
19048
  "package.json",
19039
19049
  "package-lock.json",
@@ -19061,6 +19071,7 @@ var init_schema = __esm(() => {
19061
19071
  protected_paths: [
19062
19072
  ".git",
19063
19073
  ".github/workflows",
19074
+ ".opencode",
19064
19075
  ".swarm",
19065
19076
  "package.json",
19066
19077
  "package-lock.json",
@@ -19112,6 +19123,7 @@ var init_schema = __esm(() => {
19112
19123
  }))
19113
19124
  }).optional().default(() => ({
19114
19125
  enabled: false,
19126
+ locked: false,
19115
19127
  max_interactions_per_phase: 50,
19116
19128
  deadlock_threshold: 3,
19117
19129
  escalation_mode: "pause",
@@ -19124,6 +19136,7 @@ var init_schema = __esm(() => {
19124
19136
  protected_paths: [
19125
19137
  ".git",
19126
19138
  ".github/workflows",
19139
+ ".opencode",
19127
19140
  ".swarm",
19128
19141
  "package.json",
19129
19142
  "package-lock.json",
@@ -19319,6 +19332,15 @@ function sanitizeExternalSkillsConfig(raw) {
19319
19332
  delete cleaned.external_skills;
19320
19333
  return cleaned;
19321
19334
  }
19335
+ function rawFullAutoLocked(raw) {
19336
+ if (!raw || typeof raw !== "object")
19337
+ return false;
19338
+ const fullAuto = raw.full_auto;
19339
+ if (!fullAuto || typeof fullAuto !== "object" || Array.isArray(fullAuto)) {
19340
+ return false;
19341
+ }
19342
+ return fullAuto.locked === true;
19343
+ }
19322
19344
  function loadPluginConfig(directory) {
19323
19345
  const userConfigPath = path7.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
19324
19346
  const projectConfigPath = path7.join(directory, ".opencode", CONFIG_FILENAME);
@@ -19332,6 +19354,13 @@ function loadPluginConfig(directory) {
19332
19354
  if (rawProjectConfig) {
19333
19355
  mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
19334
19356
  }
19357
+ if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
19358
+ const fullAutoRaw = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
19359
+ mergedRaw = {
19360
+ ...mergedRaw,
19361
+ full_auto: { ...fullAutoRaw, locked: true }
19362
+ };
19363
+ }
19335
19364
  mergedRaw = migratePresetsConfig(mergedRaw);
19336
19365
  mergedRaw = sanitizeExternalSkillsConfig(mergedRaw);
19337
19366
  const result = PluginConfigSchema.safeParse(mergedRaw);
@@ -19364,8 +19393,9 @@ function loadPluginConfigWithMeta(directory) {
19364
19393
  const userResult = loadRawConfigFromPath(userConfigPath);
19365
19394
  const projectResult = loadRawConfigFromPath(projectConfigPath);
19366
19395
  const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
19396
+ const configHadErrors = userResult.hadError || projectResult.hadError;
19367
19397
  const config2 = loadPluginConfig(directory);
19368
- return { config: config2, loadedFromFile };
19398
+ return { config: config2, loadedFromFile, configHadErrors };
19369
19399
  }
19370
19400
  var CONFIG_FILENAME = "opencode-swarm.json", MAX_CONFIG_FILE_BYTES = 102400;
19371
19401
  var init_loader = __esm(() => {
@@ -51242,6 +51272,25 @@ function emptyCounters() {
51242
51272
  consecutiveNoProgressTurns: 0
51243
51273
  };
51244
51274
  }
51275
+ function sanitizeRunState(raw) {
51276
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
51277
+ return null;
51278
+ const r = raw;
51279
+ if (typeof r.sessionID !== "string" || !r.sessionID)
51280
+ return null;
51281
+ const mode = VALID_RUN_MODES.has(r.mode) ? r.mode : "supervised";
51282
+ const status = VALID_RUN_STATUSES.has(r.status) ? r.status : "idle";
51283
+ return { ...raw, mode, status };
51284
+ }
51285
+ function sanitizeSessions(raw) {
51286
+ const result = {};
51287
+ for (const [id, session] of Object.entries(raw)) {
51288
+ const sanitized = sanitizeRunState(session);
51289
+ if (sanitized)
51290
+ result[id] = sanitized;
51291
+ }
51292
+ return result;
51293
+ }
51245
51294
  function emptyState(sessionID, mode = "supervised") {
51246
51295
  const now = nowISO();
51247
51296
  return {
@@ -51306,26 +51355,45 @@ function clearStateUnreadable() {
51306
51355
  stateUnreadable = false;
51307
51356
  stateUnreadableReason = "";
51308
51357
  }
51358
+ function isFullAutoStateUnreadable() {
51359
+ return { unreadable: stateUnreadable, reason: stateUnreadableReason };
51360
+ }
51309
51361
  function readPersisted(directory) {
51310
51362
  try {
51311
51363
  const filePath = validateSwarmPath(directory, STATE_FILE);
51312
- if (!fs15.existsSync(filePath)) {
51364
+ let stats;
51365
+ try {
51366
+ stats = fs15.statSync(filePath);
51367
+ } catch {
51313
51368
  clearStateUnreadable();
51369
+ readCache.delete(filePath);
51314
51370
  return emptyPersisted();
51315
51371
  }
51372
+ const cached3 = readCache.get(filePath);
51373
+ if (cached3 && cached3.mtimeMs === stats.mtimeMs && cached3.size === stats.size) {
51374
+ clearStateUnreadable();
51375
+ return structuredClone(cached3.state);
51376
+ }
51316
51377
  const raw = fs15.readFileSync(filePath, "utf-8");
51317
51378
  const parsed = JSON.parse(raw);
51318
51379
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 2 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
51319
51380
  markStateUnreadable(`malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
51381
+ readCache.delete(filePath);
51320
51382
  return emptyPersisted();
51321
51383
  }
51322
51384
  clearStateUnreadable();
51323
- return {
51385
+ const state = {
51324
51386
  version: 2,
51325
51387
  updatedAt: parsed.updatedAt ?? nowISO(),
51326
51388
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
51327
- sessions: parsed.sessions
51389
+ sessions: sanitizeSessions(parsed.sessions)
51328
51390
  };
51391
+ readCache.set(filePath, {
51392
+ mtimeMs: stats.mtimeMs,
51393
+ size: stats.size,
51394
+ state: structuredClone(state)
51395
+ });
51396
+ return state;
51329
51397
  } catch (error93) {
51330
51398
  const reason = error93 instanceof Error ? error93.message : String(error93);
51331
51399
  error(`[full-auto/state] Failed to read ${STATE_FILE}: ${reason} \u2014 attempting .bak recovery`);
@@ -51341,7 +51409,7 @@ function readPersisted(directory) {
51341
51409
  version: 2,
51342
51410
  updatedAt: parsed.updatedAt ?? nowISO(),
51343
51411
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
51344
- sessions: parsed.sessions
51412
+ sessions: sanitizeSessions(parsed.sessions)
51345
51413
  };
51346
51414
  }
51347
51415
  }
@@ -51349,6 +51417,7 @@ function readPersisted(directory) {
51349
51417
  error(`[full-auto/state] .bak recovery also failed: ${bakError instanceof Error ? bakError.message : String(bakError)}`);
51350
51418
  }
51351
51419
  markStateUnreadable(`canonical=${reason}; .bak=missing-or-corrupt`);
51420
+ readCache.clear();
51352
51421
  return emptyPersisted();
51353
51422
  }
51354
51423
  }
@@ -51386,6 +51455,7 @@ function writePersisted(directory, persisted) {
51386
51455
  }
51387
51456
  } catch {}
51388
51457
  fs15.renameSync(tmpPath, filePath);
51458
+ readCache.delete(filePath);
51389
51459
  const readback = fs15.readFileSync(filePath, "utf-8");
51390
51460
  const parsed = JSON.parse(readback);
51391
51461
  if (parsed?.version !== 2) {
@@ -51397,6 +51467,10 @@ function writePersisted(directory, persisted) {
51397
51467
  throw new Error(`Full-Auto state persistence failed: ${msg}`);
51398
51468
  }
51399
51469
  }
51470
+ function loadFullAutoRunState(directory, sessionID) {
51471
+ const persisted = readPersisted(directory);
51472
+ return persisted.sessions[sessionID];
51473
+ }
51400
51474
  function startFullAutoRun(directory, sessionID, config3, options = {}) {
51401
51475
  return withStateLock(directory, () => {
51402
51476
  const persisted = readPersisted(directory);
@@ -51428,14 +51502,15 @@ function startFullAutoRun(directory, sessionID, config3, options = {}) {
51428
51502
  return state;
51429
51503
  });
51430
51504
  }
51431
- function pauseFullAutoRun(directory, sessionID, reason) {
51505
+ function disarmFullAutoRun(directory, sessionID, reason) {
51432
51506
  return withStateLock(directory, () => {
51433
51507
  const persisted = readPersisted(directory);
51434
51508
  const state = persisted.sessions[sessionID];
51435
51509
  if (!state)
51436
51510
  return;
51437
- state.status = "paused";
51511
+ state.status = "idle";
51438
51512
  state.pauseReason = reason;
51513
+ state.terminateReason = undefined;
51439
51514
  state.updatedAt = nowISO();
51440
51515
  persisted.sessions[sessionID] = state;
51441
51516
  writePersisted(directory, persisted);
@@ -51456,15 +51531,26 @@ function terminateFullAutoRun(directory, sessionID, reason) {
51456
51531
  return state;
51457
51532
  });
51458
51533
  }
51459
- var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", stateUnreadable = false, stateUnreadableReason = "";
51534
+ var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", VALID_RUN_MODES, VALID_RUN_STATUSES, stateUnreadable = false, stateUnreadableReason = "", readCache;
51460
51535
  var init_state2 = __esm(() => {
51461
51536
  init_utils2();
51462
51537
  init_logger();
51463
51538
  import_proper_lockfile8 = __toESM(require_proper_lockfile(), 1);
51464
51539
  lockfile8 = import_proper_lockfile8.default;
51540
+ VALID_RUN_MODES = new Set(["assisted", "supervised", "strict"]);
51541
+ VALID_RUN_STATUSES = new Set([
51542
+ "idle",
51543
+ "running",
51544
+ "paused",
51545
+ "terminated"
51546
+ ]);
51547
+ readCache = new Map;
51465
51548
  });
51466
51549
 
51467
51550
  // src/commands/full-auto.ts
51551
+ function isValidMode(value) {
51552
+ return VALID_MODES.includes(value);
51553
+ }
51468
51554
  async function handleFullAutoCommand(directory, args, sessionID) {
51469
51555
  if (!sessionID || sessionID.trim() === "") {
51470
51556
  return "Error: No active session context. Full-Auto Mode requires an active session. Use /swarm-full-auto from within an OpenCode session, or start a session first.";
@@ -51474,39 +51560,63 @@ async function handleFullAutoCommand(directory, args, sessionID) {
51474
51560
  return "Error: No active session. Full-Auto Mode requires an active session to operate.";
51475
51561
  }
51476
51562
  const arg = args[0]?.toLowerCase();
51563
+ if (arg === "status") {
51564
+ return buildStatusReport(directory, sessionID, session.fullAutoMode);
51565
+ }
51477
51566
  let newFullAutoMode;
51567
+ let modeOverride;
51478
51568
  if (arg === "on") {
51479
51569
  newFullAutoMode = true;
51570
+ const modeArg = args[1]?.toLowerCase();
51571
+ if (modeArg) {
51572
+ if (!isValidMode(modeArg)) {
51573
+ return `Error: invalid Full-Auto mode '${args[1]}'. Valid modes: ${VALID_MODES.join(", ")}.`;
51574
+ }
51575
+ modeOverride = modeArg;
51576
+ }
51480
51577
  } else if (arg === "off") {
51481
51578
  newFullAutoMode = false;
51579
+ } else if (arg && isValidMode(arg)) {
51580
+ newFullAutoMode = true;
51581
+ modeOverride = arg;
51482
51582
  } else {
51483
51583
  newFullAutoMode = !session.fullAutoMode;
51484
51584
  }
51485
- if (newFullAutoMode && !swarmState.fullAutoEnabledInConfig) {
51486
- return "Error: Full-Auto Mode cannot be enabled because full_auto.enabled is not set to true in the swarm plugin config. The autonomous oversight hook is inactive without config-level enablement. Set full_auto.enabled = true in your opencode-swarm config and restart.";
51487
- }
51488
51585
  let v2Status = "unavailable";
51489
51586
  let modeLabel = "supervised";
51490
51587
  let denialMaxConsecutive = 3;
51491
51588
  let denialMaxTotal = 20;
51492
51589
  let failClosed = true;
51493
51590
  let durableError;
51591
+ let criticModelAdvisory = "";
51494
51592
  try {
51495
- const { config: config3 } = loadPluginConfigWithMeta(directory);
51593
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
51496
51594
  const fullAutoConfig = config3.full_auto;
51497
- modeLabel = fullAutoConfig?.mode ?? "supervised";
51595
+ if (newFullAutoMode && configHadErrors) {
51596
+ return "Error: Full-Auto Mode cannot be enabled \u2014 a swarm plugin config file exists but could not be loaded, so full_auto.locked cannot be verified. Fix the config file (see warnings above) and retry.";
51597
+ }
51598
+ if (newFullAutoMode && fullAutoConfig?.locked === true) {
51599
+ return "Error: Full-Auto Mode is locked for this project (full_auto.locked is true in the swarm plugin config). Runtime activation is disabled by configuration; remove the lock to use /swarm full-auto on.";
51600
+ }
51601
+ const effectiveMode = modeOverride ?? fullAutoConfig?.mode ?? "supervised";
51602
+ modeLabel = effectiveMode;
51498
51603
  denialMaxConsecutive = fullAutoConfig?.denials?.max_consecutive ?? 3;
51499
51604
  denialMaxTotal = fullAutoConfig?.denials?.max_total ?? 20;
51500
51605
  failClosed = fullAutoConfig?.fail_closed !== false;
51501
51606
  if (newFullAutoMode) {
51502
- startFullAutoRun(directory, sessionID, fullAutoConfig);
51607
+ startFullAutoRun(directory, sessionID, fullAutoConfig ? { ...fullAutoConfig, mode: effectiveMode } : undefined);
51503
51608
  v2Status = "running";
51609
+ const criticModel = fullAutoConfig?.critic_model ?? config3.agents?.critic?.model;
51610
+ const architectModel = config3.agents?.architect?.model;
51611
+ if (criticModel && architectModel && criticModel === architectModel) {
51612
+ criticModelAdvisory = " WARNING: critic model matches architect model \u2014 set full_auto.critic_model (or agents.critic.model) to a different model for independent oversight.";
51613
+ }
51504
51614
  } else {
51505
- const paused = pauseFullAutoRun(directory, sessionID, "/swarm full-auto off");
51506
- if (!paused) {
51615
+ const disarmed = disarmFullAutoRun(directory, sessionID, "/swarm full-auto off");
51616
+ if (!disarmed) {
51507
51617
  terminateFullAutoRun(directory, sessionID, "never started");
51508
51618
  }
51509
- v2Status = "paused";
51619
+ v2Status = "idle";
51510
51620
  }
51511
51621
  } catch (error93) {
51512
51622
  durableError = error93 instanceof Error ? error93.message : String(error93);
@@ -51535,13 +51645,54 @@ async function handleFullAutoCommand(directory, args, sessionID) {
51535
51645
  "Full-Auto Mode enabled",
51536
51646
  `(v2 mode=${modeLabel}, fail_closed=${failClosed},`,
51537
51647
  `denials max ${denialMaxConsecutive} consecutive / ${denialMaxTotal} total)`
51538
- ].join(" ");
51648
+ ].join(" ") + criticModelAdvisory;
51649
+ }
51650
+ function buildStatusReport(directory, sessionID, sessionFlag) {
51651
+ const lines = [];
51652
+ lines.push(`Full-Auto session flag: ${sessionFlag ? "on" : "off"}`);
51653
+ try {
51654
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
51655
+ if (configHadErrors) {
51656
+ lines.push("Config: UNREADABLE (a config file exists but could not be loaded; `full_auto.locked` cannot be verified, so runtime activation refuses by fail-closed default). Fix the config file to restore normal status.");
51657
+ }
51658
+ if (config3.full_auto?.locked === true) {
51659
+ lines.push("Config: locked (runtime activation disabled via full_auto.locked)");
51660
+ }
51661
+ } catch {}
51662
+ try {
51663
+ const runState = loadFullAutoRunState(directory, sessionID);
51664
+ const stateHealth = isFullAutoStateUnreadable();
51665
+ if (stateHealth.unreadable) {
51666
+ lines.push(`Durable run-state: UNREADABLE (${stateHealth.reason}). Non-read-only tools are blocked fail-closed until .swarm/full-auto-state.json (or .bak) is restored or deleted.`);
51667
+ } else if (!runState) {
51668
+ lines.push("Durable run-state: none (no Full-Auto run for this session)");
51669
+ } else {
51670
+ lines.push(`Durable run-state: ${runState.status} (mode=${runState.mode})`);
51671
+ if (runState.pauseReason) {
51672
+ lines.push(`Pause reason: ${runState.pauseReason}`);
51673
+ }
51674
+ if (runState.terminateReason) {
51675
+ lines.push(`Terminate reason: ${runState.terminateReason}`);
51676
+ }
51677
+ lines.push(`Counters: ${runState.counters.toolCalls} tool calls, ${runState.counters.architectTurns} architect turns, ${runState.counters.oversightChecks} oversight checks`);
51678
+ lines.push(`Denials: ${runState.denialCounters.consecutive} consecutive / ${runState.denialCounters.total} total`);
51679
+ if (runState.lastOversightVerdict) {
51680
+ lines.push(`Last oversight verdict: ${runState.lastOversightVerdict}${runState.lastOversightAt ? ` at ${runState.lastOversightAt}` : ""}`);
51681
+ }
51682
+ }
51683
+ } catch (error93) {
51684
+ lines.push(`Durable run-state: unreadable (${error93 instanceof Error ? error93.message : String(error93)})`);
51685
+ }
51686
+ return lines.join(`
51687
+ `);
51539
51688
  }
51689
+ var VALID_MODES;
51540
51690
  var init_full_auto = __esm(() => {
51541
51691
  init_config();
51542
51692
  init_state2();
51543
51693
  init_state();
51544
51694
  init_logger();
51695
+ VALID_MODES = ["assisted", "supervised", "strict"];
51545
51696
  });
51546
51697
 
51547
51698
  // src/services/handoff-service.ts
@@ -66676,9 +66827,9 @@ Subcommands:
66676
66827
  },
66677
66828
  "full-auto": {
66678
66829
  handler: (ctx) => handleFullAutoCommand(ctx.directory, ctx.args, ctx.sessionID),
66679
- description: "Toggle Full-Auto Mode for the active session [on|off]",
66680
- args: "on, off",
66681
- details: 'Toggles Full-Auto Mode which enables autonomous execution without confirmation prompts. When enabled, the architect proceeds through implementation steps automatically. Session-scoped \u2014 resets on new session. Use "on" or "off" to set explicitly, or toggle with no argument.',
66830
+ description: "Toggle Full-Auto Mode for the active session [on [mode]|off|status]",
66831
+ args: "on [assisted|supervised|strict], off, status",
66832
+ details: 'First-class toggle for Full-Auto Mode \u2014 autonomous execution with the critic reviewing escalations on your behalf. No config-level enablement is required: "on" activates immediately (unless full_auto.locked is true in config), "off" disarms the run and returns the session to normal interactive operation, "status" reports the durable run state. ' + 'An optional mode after "on" overrides full_auto.mode for this run: assisted (critic consulted only on policy escalations), supervised (default \u2014 risky/high-impact actions reviewed by the critic), strict (ALL plan mutations reviewed by the critic). ' + "While active, the critic answers architect questions and reviews phase boundaries, delegations, and risky actions on your behalf; only ESCALATE_TO_HUMAN verdicts halt the run for your input. The run state is durable (.swarm/full-auto-state.json) and survives restarts; toggle with no argument flips the current state.",
66682
66833
  category: "utility"
66683
66834
  },
66684
66835
  "auto-proceed": {
@@ -1,12 +1,24 @@
1
1
  /**
2
2
  * Handles the /swarm full-auto command.
3
- * Toggles Full-Auto Mode on or off for the active session.
3
+ * First-class session toggle for Full-Auto Mode: on / off / status / bare toggle.
4
+ *
5
+ * Full-Auto no longer requires `full_auto.enabled: true` in the plugin config —
6
+ * the v2 hooks are always armed and gated at runtime by the durable per-session
7
+ * run state, so activation is a pure runtime decision (like switching permission
8
+ * modes in other agent CLIs). Administrators can set `full_auto.locked: true`
9
+ * to refuse runtime activation entirely.
10
+ *
11
+ * `on` accepts an optional mode argument (`assisted` | `supervised` | `strict`)
12
+ * that overrides `full_auto.mode` for this run. In every mode the critic
13
+ * reviews escalations on the user's behalf; `strict` routes ALL plan mutations
14
+ * through the critic, `supervised` (default) routes risky/high-impact actions,
15
+ * `assisted` only consults the critic when the deterministic policy escalates.
4
16
  *
5
17
  * In Full-Auto v2 this also creates a durable run-state record under
6
18
  * .swarm/full-auto-state.json so the permission/oversight infrastructure can
7
19
  * fail-closed across hooks and across process restarts.
8
20
  *
9
- * H2 fix: durable write happens BEFORE flipping the legacy
21
+ * H2 fix (preserved): durable write happens BEFORE flipping the legacy
10
22
  * `session.fullAutoMode` flag. If the durable write fails, the command
11
23
  * surfaces the error in its return string and does NOT enable the legacy
12
24
  * reactive intercept — preventing a silent fail-open where reactive checks
@@ -14,7 +26,7 @@
14
26
  * durable run.
15
27
  *
16
28
  * @param directory - Project directory (used to persist Full-Auto run state)
17
- * @param args - Optional argument: "on" | "off" | undefined (toggle behavior)
29
+ * @param args - "on [mode]" | "off" | "status" | undefined (toggle behavior)
18
30
  * @param sessionID - Session ID for accessing active session state
19
31
  * @returns Feedback message about Full-Auto Mode state
20
32
  */
@@ -501,9 +501,9 @@ export declare const COMMAND_REGISTRY: {
501
501
  };
502
502
  readonly 'full-auto': {
503
503
  readonly handler: (ctx: CommandContext) => Promise<string>;
504
- readonly description: "Toggle Full-Auto Mode for the active session [on|off]";
505
- readonly args: "on, off";
506
- readonly details: "Toggles Full-Auto Mode which enables autonomous execution without confirmation prompts. When enabled, the architect proceeds through implementation steps automatically. Session-scoped — resets on new session. Use \"on\" or \"off\" to set explicitly, or toggle with no argument.";
504
+ readonly description: "Toggle Full-Auto Mode for the active session [on [mode]|off|status]";
505
+ readonly args: "on [assisted|supervised|strict], off, status";
506
+ readonly details: string;
507
507
  readonly category: "utility";
508
508
  };
509
509
  readonly 'auto-proceed': {
@@ -1,17 +1,6 @@
1
1
  import { type PluginConfig } from './schema';
2
2
  export declare const MAX_CONFIG_FILE_BYTES = 102400;
3
3
  export { deepMerge, MAX_MERGE_DEPTH } from '../utils/merge';
4
- /**
5
- * Load plugin configuration from user and project config files.
6
- *
7
- * Config locations:
8
- * 1. User config: ~/.config/opencode/opencode-swarm.json
9
- * 2. Project config: <directory>/.opencode/opencode-swarm.json
10
- *
11
- * Project config takes precedence. Nested objects are deep-merged.
12
- * IMPORTANT: Raw configs are merged BEFORE Zod parsing so that
13
- * Zod defaults don't override explicit user values.
14
- */
15
4
  export declare function loadPluginConfig(directory: string): PluginConfig;
16
5
  /**
17
6
  * Internal variant of loadPluginConfig that also returns loader metadata.
@@ -21,6 +10,11 @@ export declare function loadPluginConfig(directory: string): PluginConfig;
21
10
  export declare function loadPluginConfigWithMeta(directory: string): {
22
11
  config: PluginConfig;
23
12
  loadedFromFile: boolean;
13
+ /** True when a config file existed but could not be loaded (corrupt JSON,
14
+ * oversized, permission error). Consumers with fail-closed semantics —
15
+ * e.g. the Full-Auto `locked` activation guard — must treat this as
16
+ * "config unknown", not "config defaults". */
17
+ configHadErrors: boolean;
24
18
  };
25
19
  /**
26
20
  * Async variant of `loadPluginConfigWithMeta`. Used by the plugin entry
@@ -29,6 +23,7 @@ export declare function loadPluginConfigWithMeta(directory: string): {
29
23
  export declare function loadPluginConfigWithMetaAsync(directory: string): Promise<{
30
24
  config: PluginConfig;
31
25
  loadedFromFile: boolean;
26
+ configHadErrors: boolean;
32
27
  }>;
33
28
  /**
34
29
  * Load custom prompt for an agent from the prompts directory.
@@ -333,6 +333,17 @@ export declare const ReviewPassesConfigSchema: z.ZodObject<{
333
333
  security_globs: z.ZodDefault<z.ZodArray<z.ZodString>>;
334
334
  }, z.core.$strip>;
335
335
  export type ReviewPassesConfig = z.infer<typeof ReviewPassesConfigSchema>;
336
+ export declare const AutoReviewConfigSchema: z.ZodObject<{
337
+ enabled: z.ZodDefault<z.ZodBoolean>;
338
+ trigger: z.ZodDefault<z.ZodEnum<{
339
+ task_completion: "task_completion";
340
+ phase_boundary: "phase_boundary";
341
+ both: "both";
342
+ }>>;
343
+ timeout_ms: z.ZodDefault<z.ZodNumber>;
344
+ max_diff_kb: z.ZodDefault<z.ZodNumber>;
345
+ }, z.core.$strip>;
346
+ export type AutoReviewConfig = z.infer<typeof AutoReviewConfigSchema>;
336
347
  export declare const AdversarialDetectionConfigSchema: z.ZodObject<{
337
348
  enabled: z.ZodDefault<z.ZodBoolean>;
338
349
  policy: z.ZodDefault<z.ZodEnum<{
@@ -1392,6 +1403,16 @@ export declare const PluginConfigSchema: z.ZodObject<{
1392
1403
  enabled: z.ZodDefault<z.ZodBoolean>;
1393
1404
  skip_in_turbo: z.ZodDefault<z.ZodBoolean>;
1394
1405
  }, z.core.$strip>>;
1406
+ auto_review: z.ZodOptional<z.ZodObject<{
1407
+ enabled: z.ZodDefault<z.ZodBoolean>;
1408
+ trigger: z.ZodDefault<z.ZodEnum<{
1409
+ task_completion: "task_completion";
1410
+ phase_boundary: "phase_boundary";
1411
+ both: "both";
1412
+ }>>;
1413
+ timeout_ms: z.ZodDefault<z.ZodNumber>;
1414
+ max_diff_kb: z.ZodDefault<z.ZodNumber>;
1415
+ }, z.core.$strip>>;
1395
1416
  tool_filter: z.ZodOptional<z.ZodObject<{
1396
1417
  enabled: z.ZodDefault<z.ZodBoolean>;
1397
1418
  overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
@@ -1858,6 +1879,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
1858
1879
  version_check: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
1859
1880
  full_auto: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1860
1881
  enabled: z.ZodDefault<z.ZodBoolean>;
1882
+ locked: z.ZodDefault<z.ZodBoolean>;
1861
1883
  critic_model: z.ZodOptional<z.ZodString>;
1862
1884
  max_interactions_per_phase: z.ZodDefault<z.ZodNumber>;
1863
1885
  deadlock_threshold: z.ZodDefault<z.ZodNumber>;
@@ -88,6 +88,18 @@ export declare function startFullAutoRun(directory: string, sessionID: string, c
88
88
  taskID?: string;
89
89
  }): FullAutoRunState;
90
90
  export declare function pauseFullAutoRun(directory: string, sessionID: string, reason: string): FullAutoRunState | undefined;
91
+ /**
92
+ * Disarm a Full-Auto run in response to an explicit user `off`.
93
+ *
94
+ * Unlike `pauseFullAutoRun` / `terminateFullAutoRun` (system-initiated halts
95
+ * that fail-closed-block non-read-only tools until the user re-enables),
96
+ * disarming returns the session to normal interactive operation: the record
97
+ * transitions to `'idle'`, which every enforcement path treats as
98
+ * "no active Full-Auto run". Counters and denial history are preserved for
99
+ * audit. (Adversarial review F3: `off` must not be a one-way door into a
100
+ * write-blocked session.)
101
+ */
102
+ export declare function disarmFullAutoRun(directory: string, sessionID: string, reason: string): FullAutoRunState | undefined;
91
103
  export declare function terminateFullAutoRun(directory: string, sessionID: string, reason: string): FullAutoRunState | undefined;
92
104
  export declare function isFullAutoRunActive(directory: string, sessionID: string): boolean;
93
105
  export type FullAutoCounterKey = keyof FullAutoCounters;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Auto-review hook (auto-review machinery, piece B) — opt-in.
3
+ *
4
+ * When `auto_review.enabled` is true, completing a task
5
+ * (`update_task_status` → status 'completed') and/or a phase
6
+ * (`phase_complete`) automatically dispatches the registered reviewer agent
7
+ * over a fresh ephemeral session to review the current execution diff —
8
+ * the same "second model reviews the work in a clean context" pattern used
9
+ * by Claude Code's auto-review and Codex's review model. The reviewer agent
10
+ * carries its own configured model (`agents.reviewer.model`), so the review
11
+ * model is independently configurable from the coder/architect models.
12
+ *
13
+ * The pass is ADVISORY and fully fail-open:
14
+ * - fire-and-forget from `tool.execute.after` (never blocks the tool)
15
+ * - verdicts are persisted as durable review receipts
16
+ * (`.swarm/review-receipts/`, scope-fingerprinted over the diff) and an
17
+ * `auto_review` event is appended to `.swarm/events.jsonl`
18
+ * - a REJECTED or unparseable verdict injects a `[AUTO-REVIEW]` advisory
19
+ * into the architect's next prompt; APPROVED stays silent
20
+ *
21
+ * Bounds (AGENTS.md invariants 3 and 8): the diff subprocess uses execFile
22
+ * with cwd/timeout/maxBuffer and ignored stdin; dispatches are guarded by a
23
+ * per-session in-flight set plus a 60s cooldown in a bounded FIFO map.
24
+ */
25
+ import { type AutoReviewConfig } from '../config/schema.js';
26
+ /** Test-only: clear module-level dispatch tracking. */
27
+ export declare function resetAutoReviewTracking(): void;
28
+ export type ExecutionDiffResult = {
29
+ status: 'ok';
30
+ diff: string;
31
+ } | {
32
+ status: 'clean';
33
+ } | {
34
+ status: 'error';
35
+ reason: string;
36
+ };
37
+ /**
38
+ * Collect the execution diff for review: `git diff HEAD` (tracked changes)
39
+ * plus a porcelain summary of untracked files. Distinguishes a clean working
40
+ * tree from collection failures (git missing, timeout, diff exceeding the
41
+ * 2× maxBuffer cap) so events report honestly. Output is truncated to
42
+ * `maxBytes`.
43
+ */
44
+ declare function computeExecutionDiff(directory: string, maxBytes: number): Promise<ExecutionDiffResult>;
45
+ declare function dispatchReviewer(directory: string, prompt: string, agentName: string, timeoutMs: number): Promise<string>;
46
+ export interface AutoReviewRunInput {
47
+ directory: string;
48
+ sessionID: string;
49
+ trigger: 'task_completion' | 'phase_boundary';
50
+ taskId?: string;
51
+ phase?: number;
52
+ config: Required<Pick<AutoReviewConfig, 'timeout_ms' | 'max_diff_kb'>>;
53
+ injectAdvisory: (sessionId: string, message: string) => void;
54
+ }
55
+ /**
56
+ * Execute one auto-review pass: collect diff → dispatch reviewer over an
57
+ * ephemeral session → persist receipt + event → advisory on REJECTED or
58
+ * unparseable output. Fully fail-open; never throws.
59
+ */
60
+ export declare function runAutoReview(input: AutoReviewRunInput): Promise<void>;
61
+ export interface AutoReviewHookOptions {
62
+ config: AutoReviewConfig;
63
+ directory: string;
64
+ injectAdvisory: (sessionId: string, message: string) => void;
65
+ }
66
+ export declare function createAutoReviewHook(options: AutoReviewHookOptions): {
67
+ toolAfter: (input: {
68
+ tool: string;
69
+ sessionID: string;
70
+ callID?: string;
71
+ }, output: {
72
+ args?: unknown;
73
+ output?: unknown;
74
+ }) => Promise<void>;
75
+ };
76
+ export declare const _internals: {
77
+ computeExecutionDiff: typeof computeExecutionDiff;
78
+ dispatchReviewer: typeof dispatchReviewer;
79
+ runAutoReview: typeof runAutoReview;
80
+ now: () => number;
81
+ };
82
+ export {};