opencode-swarm 7.74.3 → 7.76.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,10 +52,17 @@ 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.76.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",
59
+ exports: {
60
+ ".": {
61
+ types: "./dist/index.d.ts",
62
+ default: "./dist/index.js"
63
+ },
64
+ "./package.json": "./package.json"
65
+ },
59
66
  bin: {
60
67
  "opencode-swarm": "./dist/cli/index.js"
61
68
  },
@@ -17493,7 +17500,7 @@ var init_tool_metadata = __esm(() => {
17493
17500
  agents: ["critic_drift_verifier", "critic", "critic_oversight"]
17494
17501
  },
17495
17502
  repo_map: {
17496
- description: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
17503
+ description: "query the repo code graph: importers, dependencies, blast radius, localization, ontology facts, package boundaries, and heuristic preflight packets before refactoring; ontology findings are advisory, not formal proofs",
17497
17504
  agents: [
17498
17505
  "architect",
17499
17506
  "critic_sounding_board",
@@ -18015,6 +18022,7 @@ __export(exports_schema, {
18015
18022
  AutomationModeSchema: () => AutomationModeSchema,
18016
18023
  AutomationConfigSchema: () => AutomationConfigSchema,
18017
18024
  AutomationCapabilitiesSchema: () => AutomationCapabilitiesSchema,
18025
+ AutoReviewConfigSchema: () => AutoReviewConfigSchema,
18018
18026
  AuthorityConfigSchema: () => AuthorityConfigSchema,
18019
18027
  ArchitecturalSupervisionConfigSchema: () => ArchitecturalSupervisionConfigSchema,
18020
18028
  AgentThinkingConfigSchema: () => AgentThinkingConfigSchema,
@@ -18100,7 +18108,7 @@ function resolveExternalSkillsConfig(input) {
18100
18108
  };
18101
18109
  return merged;
18102
18110
  }
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;
18111
+ 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
18112
  var init_schema = __esm(() => {
18105
18113
  init_zod();
18106
18114
  init_constants();
@@ -18288,6 +18296,12 @@ var init_schema = __esm(() => {
18288
18296
  "**/token/**"
18289
18297
  ])
18290
18298
  });
18299
+ AutoReviewConfigSchema = exports_external.object({
18300
+ enabled: exports_external.boolean().default(false),
18301
+ trigger: exports_external.enum(["task_completion", "phase_boundary", "both"]).default("phase_boundary"),
18302
+ timeout_ms: exports_external.number().int().min(1e4).max(1800000).default(300000),
18303
+ max_diff_kb: exports_external.number().int().min(16).max(2048).default(256)
18304
+ });
18291
18305
  AdversarialDetectionConfigSchema = exports_external.object({
18292
18306
  enabled: exports_external.boolean().default(true),
18293
18307
  policy: exports_external.enum(["warn", "gate", "ignore"]).default("warn"),
@@ -18971,6 +18985,7 @@ var init_schema = __esm(() => {
18971
18985
  guardrails: GuardrailsConfigSchema.optional(),
18972
18986
  watchdog: WatchdogConfigSchema.optional(),
18973
18987
  self_review: SelfReviewConfigSchema.optional(),
18988
+ auto_review: AutoReviewConfigSchema.optional(),
18974
18989
  tool_filter: ToolFilterConfigSchema.optional(),
18975
18990
  authority: AuthorityConfigSchema.optional(),
18976
18991
  plan_cursor: PlanCursorConfigSchema.optional(),
@@ -19021,6 +19036,7 @@ var init_schema = __esm(() => {
19021
19036
  version_check: exports_external.boolean().default(true).optional(),
19022
19037
  full_auto: exports_external.object({
19023
19038
  enabled: exports_external.boolean().default(false),
19039
+ locked: exports_external.boolean().default(false),
19024
19040
  critic_model: exports_external.string().optional(),
19025
19041
  max_interactions_per_phase: exports_external.number().int().min(5).max(200).default(50),
19026
19042
  deadlock_threshold: exports_external.number().int().min(2).max(10).default(3),
@@ -19034,6 +19050,7 @@ var init_schema = __esm(() => {
19034
19050
  protected_paths: exports_external.array(exports_external.string()).default([
19035
19051
  ".git",
19036
19052
  ".github/workflows",
19053
+ ".opencode",
19037
19054
  ".swarm",
19038
19055
  "package.json",
19039
19056
  "package-lock.json",
@@ -19061,6 +19078,7 @@ var init_schema = __esm(() => {
19061
19078
  protected_paths: [
19062
19079
  ".git",
19063
19080
  ".github/workflows",
19081
+ ".opencode",
19064
19082
  ".swarm",
19065
19083
  "package.json",
19066
19084
  "package-lock.json",
@@ -19112,6 +19130,7 @@ var init_schema = __esm(() => {
19112
19130
  }))
19113
19131
  }).optional().default(() => ({
19114
19132
  enabled: false,
19133
+ locked: false,
19115
19134
  max_interactions_per_phase: 50,
19116
19135
  deadlock_threshold: 3,
19117
19136
  escalation_mode: "pause",
@@ -19124,6 +19143,7 @@ var init_schema = __esm(() => {
19124
19143
  protected_paths: [
19125
19144
  ".git",
19126
19145
  ".github/workflows",
19146
+ ".opencode",
19127
19147
  ".swarm",
19128
19148
  "package.json",
19129
19149
  "package-lock.json",
@@ -19319,6 +19339,15 @@ function sanitizeExternalSkillsConfig(raw) {
19319
19339
  delete cleaned.external_skills;
19320
19340
  return cleaned;
19321
19341
  }
19342
+ function rawFullAutoLocked(raw) {
19343
+ if (!raw || typeof raw !== "object")
19344
+ return false;
19345
+ const fullAuto = raw.full_auto;
19346
+ if (!fullAuto || typeof fullAuto !== "object" || Array.isArray(fullAuto)) {
19347
+ return false;
19348
+ }
19349
+ return fullAuto.locked === true;
19350
+ }
19322
19351
  function loadPluginConfig(directory) {
19323
19352
  const userConfigPath = path7.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
19324
19353
  const projectConfigPath = path7.join(directory, ".opencode", CONFIG_FILENAME);
@@ -19332,6 +19361,13 @@ function loadPluginConfig(directory) {
19332
19361
  if (rawProjectConfig) {
19333
19362
  mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
19334
19363
  }
19364
+ if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
19365
+ const fullAutoRaw = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
19366
+ mergedRaw = {
19367
+ ...mergedRaw,
19368
+ full_auto: { ...fullAutoRaw, locked: true }
19369
+ };
19370
+ }
19335
19371
  mergedRaw = migratePresetsConfig(mergedRaw);
19336
19372
  mergedRaw = sanitizeExternalSkillsConfig(mergedRaw);
19337
19373
  const result = PluginConfigSchema.safeParse(mergedRaw);
@@ -19364,8 +19400,9 @@ function loadPluginConfigWithMeta(directory) {
19364
19400
  const userResult = loadRawConfigFromPath(userConfigPath);
19365
19401
  const projectResult = loadRawConfigFromPath(projectConfigPath);
19366
19402
  const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
19403
+ const configHadErrors = userResult.hadError || projectResult.hadError;
19367
19404
  const config2 = loadPluginConfig(directory);
19368
- return { config: config2, loadedFromFile };
19405
+ return { config: config2, loadedFromFile, configHadErrors };
19369
19406
  }
19370
19407
  var CONFIG_FILENAME = "opencode-swarm.json", MAX_CONFIG_FILE_BYTES = 102400;
19371
19408
  var init_loader = __esm(() => {
@@ -51242,6 +51279,25 @@ function emptyCounters() {
51242
51279
  consecutiveNoProgressTurns: 0
51243
51280
  };
51244
51281
  }
51282
+ function sanitizeRunState(raw) {
51283
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
51284
+ return null;
51285
+ const r = raw;
51286
+ if (typeof r.sessionID !== "string" || !r.sessionID)
51287
+ return null;
51288
+ const mode = VALID_RUN_MODES.has(r.mode) ? r.mode : "supervised";
51289
+ const status = VALID_RUN_STATUSES.has(r.status) ? r.status : "idle";
51290
+ return { ...raw, mode, status };
51291
+ }
51292
+ function sanitizeSessions(raw) {
51293
+ const result = {};
51294
+ for (const [id, session] of Object.entries(raw)) {
51295
+ const sanitized = sanitizeRunState(session);
51296
+ if (sanitized)
51297
+ result[id] = sanitized;
51298
+ }
51299
+ return result;
51300
+ }
51245
51301
  function emptyState(sessionID, mode = "supervised") {
51246
51302
  const now = nowISO();
51247
51303
  return {
@@ -51306,26 +51362,45 @@ function clearStateUnreadable() {
51306
51362
  stateUnreadable = false;
51307
51363
  stateUnreadableReason = "";
51308
51364
  }
51365
+ function isFullAutoStateUnreadable() {
51366
+ return { unreadable: stateUnreadable, reason: stateUnreadableReason };
51367
+ }
51309
51368
  function readPersisted(directory) {
51310
51369
  try {
51311
51370
  const filePath = validateSwarmPath(directory, STATE_FILE);
51312
- if (!fs15.existsSync(filePath)) {
51371
+ let stats;
51372
+ try {
51373
+ stats = fs15.statSync(filePath);
51374
+ } catch {
51313
51375
  clearStateUnreadable();
51376
+ readCache.delete(filePath);
51314
51377
  return emptyPersisted();
51315
51378
  }
51379
+ const cached3 = readCache.get(filePath);
51380
+ if (cached3 && cached3.mtimeMs === stats.mtimeMs && cached3.size === stats.size) {
51381
+ clearStateUnreadable();
51382
+ return structuredClone(cached3.state);
51383
+ }
51316
51384
  const raw = fs15.readFileSync(filePath, "utf-8");
51317
51385
  const parsed = JSON.parse(raw);
51318
51386
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 2 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
51319
51387
  markStateUnreadable(`malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
51388
+ readCache.delete(filePath);
51320
51389
  return emptyPersisted();
51321
51390
  }
51322
51391
  clearStateUnreadable();
51323
- return {
51392
+ const state = {
51324
51393
  version: 2,
51325
51394
  updatedAt: parsed.updatedAt ?? nowISO(),
51326
51395
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
51327
- sessions: parsed.sessions
51396
+ sessions: sanitizeSessions(parsed.sessions)
51328
51397
  };
51398
+ readCache.set(filePath, {
51399
+ mtimeMs: stats.mtimeMs,
51400
+ size: stats.size,
51401
+ state: structuredClone(state)
51402
+ });
51403
+ return state;
51329
51404
  } catch (error93) {
51330
51405
  const reason = error93 instanceof Error ? error93.message : String(error93);
51331
51406
  error(`[full-auto/state] Failed to read ${STATE_FILE}: ${reason} \u2014 attempting .bak recovery`);
@@ -51341,7 +51416,7 @@ function readPersisted(directory) {
51341
51416
  version: 2,
51342
51417
  updatedAt: parsed.updatedAt ?? nowISO(),
51343
51418
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
51344
- sessions: parsed.sessions
51419
+ sessions: sanitizeSessions(parsed.sessions)
51345
51420
  };
51346
51421
  }
51347
51422
  }
@@ -51349,6 +51424,7 @@ function readPersisted(directory) {
51349
51424
  error(`[full-auto/state] .bak recovery also failed: ${bakError instanceof Error ? bakError.message : String(bakError)}`);
51350
51425
  }
51351
51426
  markStateUnreadable(`canonical=${reason}; .bak=missing-or-corrupt`);
51427
+ readCache.clear();
51352
51428
  return emptyPersisted();
51353
51429
  }
51354
51430
  }
@@ -51386,6 +51462,7 @@ function writePersisted(directory, persisted) {
51386
51462
  }
51387
51463
  } catch {}
51388
51464
  fs15.renameSync(tmpPath, filePath);
51465
+ readCache.delete(filePath);
51389
51466
  const readback = fs15.readFileSync(filePath, "utf-8");
51390
51467
  const parsed = JSON.parse(readback);
51391
51468
  if (parsed?.version !== 2) {
@@ -51397,6 +51474,10 @@ function writePersisted(directory, persisted) {
51397
51474
  throw new Error(`Full-Auto state persistence failed: ${msg}`);
51398
51475
  }
51399
51476
  }
51477
+ function loadFullAutoRunState(directory, sessionID) {
51478
+ const persisted = readPersisted(directory);
51479
+ return persisted.sessions[sessionID];
51480
+ }
51400
51481
  function startFullAutoRun(directory, sessionID, config3, options = {}) {
51401
51482
  return withStateLock(directory, () => {
51402
51483
  const persisted = readPersisted(directory);
@@ -51428,14 +51509,15 @@ function startFullAutoRun(directory, sessionID, config3, options = {}) {
51428
51509
  return state;
51429
51510
  });
51430
51511
  }
51431
- function pauseFullAutoRun(directory, sessionID, reason) {
51512
+ function disarmFullAutoRun(directory, sessionID, reason) {
51432
51513
  return withStateLock(directory, () => {
51433
51514
  const persisted = readPersisted(directory);
51434
51515
  const state = persisted.sessions[sessionID];
51435
51516
  if (!state)
51436
51517
  return;
51437
- state.status = "paused";
51518
+ state.status = "idle";
51438
51519
  state.pauseReason = reason;
51520
+ state.terminateReason = undefined;
51439
51521
  state.updatedAt = nowISO();
51440
51522
  persisted.sessions[sessionID] = state;
51441
51523
  writePersisted(directory, persisted);
@@ -51456,15 +51538,26 @@ function terminateFullAutoRun(directory, sessionID, reason) {
51456
51538
  return state;
51457
51539
  });
51458
51540
  }
51459
- var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", stateUnreadable = false, stateUnreadableReason = "";
51541
+ var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", VALID_RUN_MODES, VALID_RUN_STATUSES, stateUnreadable = false, stateUnreadableReason = "", readCache;
51460
51542
  var init_state2 = __esm(() => {
51461
51543
  init_utils2();
51462
51544
  init_logger();
51463
51545
  import_proper_lockfile8 = __toESM(require_proper_lockfile(), 1);
51464
51546
  lockfile8 = import_proper_lockfile8.default;
51547
+ VALID_RUN_MODES = new Set(["assisted", "supervised", "strict"]);
51548
+ VALID_RUN_STATUSES = new Set([
51549
+ "idle",
51550
+ "running",
51551
+ "paused",
51552
+ "terminated"
51553
+ ]);
51554
+ readCache = new Map;
51465
51555
  });
51466
51556
 
51467
51557
  // src/commands/full-auto.ts
51558
+ function isValidMode(value) {
51559
+ return VALID_MODES.includes(value);
51560
+ }
51468
51561
  async function handleFullAutoCommand(directory, args, sessionID) {
51469
51562
  if (!sessionID || sessionID.trim() === "") {
51470
51563
  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 +51567,63 @@ async function handleFullAutoCommand(directory, args, sessionID) {
51474
51567
  return "Error: No active session. Full-Auto Mode requires an active session to operate.";
51475
51568
  }
51476
51569
  const arg = args[0]?.toLowerCase();
51570
+ if (arg === "status") {
51571
+ return buildStatusReport(directory, sessionID, session.fullAutoMode);
51572
+ }
51477
51573
  let newFullAutoMode;
51574
+ let modeOverride;
51478
51575
  if (arg === "on") {
51479
51576
  newFullAutoMode = true;
51577
+ const modeArg = args[1]?.toLowerCase();
51578
+ if (modeArg) {
51579
+ if (!isValidMode(modeArg)) {
51580
+ return `Error: invalid Full-Auto mode '${args[1]}'. Valid modes: ${VALID_MODES.join(", ")}.`;
51581
+ }
51582
+ modeOverride = modeArg;
51583
+ }
51480
51584
  } else if (arg === "off") {
51481
51585
  newFullAutoMode = false;
51586
+ } else if (arg && isValidMode(arg)) {
51587
+ newFullAutoMode = true;
51588
+ modeOverride = arg;
51482
51589
  } else {
51483
51590
  newFullAutoMode = !session.fullAutoMode;
51484
51591
  }
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
51592
  let v2Status = "unavailable";
51489
51593
  let modeLabel = "supervised";
51490
51594
  let denialMaxConsecutive = 3;
51491
51595
  let denialMaxTotal = 20;
51492
51596
  let failClosed = true;
51493
51597
  let durableError;
51598
+ let criticModelAdvisory = "";
51494
51599
  try {
51495
- const { config: config3 } = loadPluginConfigWithMeta(directory);
51600
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
51496
51601
  const fullAutoConfig = config3.full_auto;
51497
- modeLabel = fullAutoConfig?.mode ?? "supervised";
51602
+ if (newFullAutoMode && configHadErrors) {
51603
+ 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.";
51604
+ }
51605
+ if (newFullAutoMode && fullAutoConfig?.locked === true) {
51606
+ 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.";
51607
+ }
51608
+ const effectiveMode = modeOverride ?? fullAutoConfig?.mode ?? "supervised";
51609
+ modeLabel = effectiveMode;
51498
51610
  denialMaxConsecutive = fullAutoConfig?.denials?.max_consecutive ?? 3;
51499
51611
  denialMaxTotal = fullAutoConfig?.denials?.max_total ?? 20;
51500
51612
  failClosed = fullAutoConfig?.fail_closed !== false;
51501
51613
  if (newFullAutoMode) {
51502
- startFullAutoRun(directory, sessionID, fullAutoConfig);
51614
+ startFullAutoRun(directory, sessionID, fullAutoConfig ? { ...fullAutoConfig, mode: effectiveMode } : undefined);
51503
51615
  v2Status = "running";
51616
+ const criticModel = fullAutoConfig?.critic_model ?? config3.agents?.critic?.model;
51617
+ const architectModel = config3.agents?.architect?.model;
51618
+ if (criticModel && architectModel && criticModel === architectModel) {
51619
+ criticModelAdvisory = " WARNING: critic model matches architect model \u2014 set full_auto.critic_model (or agents.critic.model) to a different model for independent oversight.";
51620
+ }
51504
51621
  } else {
51505
- const paused = pauseFullAutoRun(directory, sessionID, "/swarm full-auto off");
51506
- if (!paused) {
51622
+ const disarmed = disarmFullAutoRun(directory, sessionID, "/swarm full-auto off");
51623
+ if (!disarmed) {
51507
51624
  terminateFullAutoRun(directory, sessionID, "never started");
51508
51625
  }
51509
- v2Status = "paused";
51626
+ v2Status = "idle";
51510
51627
  }
51511
51628
  } catch (error93) {
51512
51629
  durableError = error93 instanceof Error ? error93.message : String(error93);
@@ -51535,13 +51652,54 @@ async function handleFullAutoCommand(directory, args, sessionID) {
51535
51652
  "Full-Auto Mode enabled",
51536
51653
  `(v2 mode=${modeLabel}, fail_closed=${failClosed},`,
51537
51654
  `denials max ${denialMaxConsecutive} consecutive / ${denialMaxTotal} total)`
51538
- ].join(" ");
51655
+ ].join(" ") + criticModelAdvisory;
51656
+ }
51657
+ function buildStatusReport(directory, sessionID, sessionFlag) {
51658
+ const lines = [];
51659
+ lines.push(`Full-Auto session flag: ${sessionFlag ? "on" : "off"}`);
51660
+ try {
51661
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
51662
+ if (configHadErrors) {
51663
+ 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.");
51664
+ }
51665
+ if (config3.full_auto?.locked === true) {
51666
+ lines.push("Config: locked (runtime activation disabled via full_auto.locked)");
51667
+ }
51668
+ } catch {}
51669
+ try {
51670
+ const runState = loadFullAutoRunState(directory, sessionID);
51671
+ const stateHealth = isFullAutoStateUnreadable();
51672
+ if (stateHealth.unreadable) {
51673
+ 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.`);
51674
+ } else if (!runState) {
51675
+ lines.push("Durable run-state: none (no Full-Auto run for this session)");
51676
+ } else {
51677
+ lines.push(`Durable run-state: ${runState.status} (mode=${runState.mode})`);
51678
+ if (runState.pauseReason) {
51679
+ lines.push(`Pause reason: ${runState.pauseReason}`);
51680
+ }
51681
+ if (runState.terminateReason) {
51682
+ lines.push(`Terminate reason: ${runState.terminateReason}`);
51683
+ }
51684
+ lines.push(`Counters: ${runState.counters.toolCalls} tool calls, ${runState.counters.architectTurns} architect turns, ${runState.counters.oversightChecks} oversight checks`);
51685
+ lines.push(`Denials: ${runState.denialCounters.consecutive} consecutive / ${runState.denialCounters.total} total`);
51686
+ if (runState.lastOversightVerdict) {
51687
+ lines.push(`Last oversight verdict: ${runState.lastOversightVerdict}${runState.lastOversightAt ? ` at ${runState.lastOversightAt}` : ""}`);
51688
+ }
51689
+ }
51690
+ } catch (error93) {
51691
+ lines.push(`Durable run-state: unreadable (${error93 instanceof Error ? error93.message : String(error93)})`);
51692
+ }
51693
+ return lines.join(`
51694
+ `);
51539
51695
  }
51696
+ var VALID_MODES;
51540
51697
  var init_full_auto = __esm(() => {
51541
51698
  init_config();
51542
51699
  init_state2();
51543
51700
  init_state();
51544
51701
  init_logger();
51702
+ VALID_MODES = ["assisted", "supervised", "strict"];
51545
51703
  });
51546
51704
 
51547
51705
  // src/services/handoff-service.ts
@@ -57314,7 +57472,18 @@ function containsPathTraversal(str) {
57314
57472
  return false;
57315
57473
  }
57316
57474
  function containsControlChars(str) {
57317
- return /[\0\t\r\n]/.test(str);
57475
+ for (const ch of str) {
57476
+ const code = ch.codePointAt(0);
57477
+ if (code === undefined)
57478
+ continue;
57479
+ if (code <= 31 || code >= 127 && code <= 159)
57480
+ return true;
57481
+ if (code >= 8234 && code <= 8238)
57482
+ return true;
57483
+ if (code >= 8294 && code <= 8297)
57484
+ return true;
57485
+ }
57486
+ return false;
57318
57487
  }
57319
57488
  var init_path_security = () => {};
57320
57489
 
@@ -66676,9 +66845,9 @@ Subcommands:
66676
66845
  },
66677
66846
  "full-auto": {
66678
66847
  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.',
66848
+ description: "Toggle Full-Auto Mode for the active session [on [mode]|off|status]",
66849
+ args: "on [assisted|supervised|strict], off, status",
66850
+ 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
66851
  category: "utility"
66683
66852
  },
66684
66853
  "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;