opencode-swarm 6.32.4 → 6.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -142,6 +142,30 @@ You only need to specify the agents you want to override. The `architect` inheri
142
142
 
143
143
  See the [full configuration reference](#configuration-reference) and the [free-tier model setup](#free-tier-opencode-zen-models) for more options.
144
144
 
145
+ ### Step 7 — Performance modes (optional)
146
+
147
+ Swarm runs optional quality hooks (slop detection, incremental verification, compaction) on every tool call. For faster iteration, you can skip these:
148
+
149
+ **Via slash command** (session-wide):
150
+ ```
151
+ /swarm turbo
152
+ ```
153
+
154
+ **Via config** (persistent):
155
+ ```json
156
+ {
157
+ "execution_mode": "fast"
158
+ }
159
+ ```
160
+
161
+ | Mode | Behavior |
162
+ |------|----------|
163
+ | `strict` | Runs all quality hooks (slop detector, incremental verify, compaction). Maximum safety, highest overhead. |
164
+ | `balanced` (default) | Skips optional quality hooks. Recommended for most workflows. |
165
+ | `fast` | Same as balanced. Reserved for future more aggressive optimizations. |
166
+
167
+ Use `strict` mode for critical security-sensitive changes. Switch to `balanced` for routine development.
168
+
145
169
  ---
146
170
 
147
171
  ## Common First-Run Questions
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli/index.js CHANGED
@@ -17497,7 +17497,7 @@ var init_evidence_summary_service = __esm(() => {
17497
17497
  });
17498
17498
 
17499
17499
  // src/cli/index.ts
17500
- import * as fs11 from "fs";
17500
+ import * as fs12 from "fs";
17501
17501
  import * as os5 from "os";
17502
17502
  import * as path23 from "path";
17503
17503
 
@@ -18063,6 +18063,7 @@ var GuardrailsConfigSchema = exports_external.object({
18063
18063
  max_consecutive_errors: exports_external.number().min(2).max(20).default(5),
18064
18064
  warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.75),
18065
18065
  idle_timeout_minutes: exports_external.number().min(5).max(240).default(60),
18066
+ no_op_warning_threshold: exports_external.number().min(1).max(100).default(15),
18066
18067
  qa_gates: exports_external.object({
18067
18068
  required_tools: exports_external.array(exports_external.string().min(1)).default([
18068
18069
  "diff",
@@ -18178,6 +18179,7 @@ var PluginConfigSchema = exports_external.object({
18178
18179
  pipeline: PipelineConfigSchema.optional(),
18179
18180
  phase_complete: PhaseCompleteConfigSchema.optional(),
18180
18181
  qa_retry_limit: exports_external.number().min(1).max(10).default(3),
18182
+ execution_mode: exports_external.enum(["strict", "balanced", "fast"]).default("balanced"),
18181
18183
  inject_phase_reminders: exports_external.boolean().default(true),
18182
18184
  hooks: HooksConfigSchema.optional(),
18183
18185
  gates: GateConfigSchema.optional(),
@@ -33931,6 +33933,8 @@ function formatHandoffMarkdown(data) {
33931
33933
  init_utils2();
33932
33934
  import { mkdirSync as mkdirSync4, renameSync as renameSync4 } from "fs";
33933
33935
  import * as path14 from "path";
33936
+ var pendingWrite = null;
33937
+ var lastWritePromise = Promise.resolve();
33934
33938
  function serializeAgentSession(s) {
33935
33939
  const gateLog = {};
33936
33940
  const rawGateLog = s.gateLog ?? new Map;
@@ -34015,7 +34019,18 @@ async function writeSnapshot(directory, state) {
34015
34019
  const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
34016
34020
  await Bun.write(tempPath, content);
34017
34021
  renameSync4(tempPath, resolvedPath);
34018
- } catch {}
34022
+ } catch (error93) {
34023
+ console.warn("[snapshot-writer] write failed:", error93 instanceof Error ? error93.message : String(error93));
34024
+ }
34025
+ }
34026
+ async function flushPendingSnapshot(directory) {
34027
+ if (pendingWrite) {
34028
+ clearTimeout(pendingWrite);
34029
+ pendingWrite = null;
34030
+ await writeSnapshot(directory, swarmState).catch(() => {});
34031
+ } else {
34032
+ await lastWritePromise;
34033
+ }
34019
34034
  }
34020
34035
 
34021
34036
  // src/commands/handoff.ts
@@ -34027,6 +34042,7 @@ async function handleHandoffCommand(directory, _args) {
34027
34042
  await Bun.write(tempPath, markdown);
34028
34043
  renameSync5(tempPath, resolvedPath);
34029
34044
  await writeSnapshot(directory, swarmState);
34045
+ await flushPendingSnapshot(directory);
34030
34046
  return `## Handoff Brief Written
34031
34047
 
34032
34048
  Brief written to \`.swarm/handoff.md\`.
@@ -39322,6 +39338,37 @@ async function handleResetCommand(directory, args) {
39322
39338
  `);
39323
39339
  }
39324
39340
 
39341
+ // src/commands/reset-session.ts
39342
+ init_utils2();
39343
+ import * as fs10 from "fs";
39344
+ async function handleResetSessionCommand(directory, _args) {
39345
+ const results = [];
39346
+ try {
39347
+ const statePath = validateSwarmPath(directory, "session/state.json");
39348
+ if (fs10.existsSync(statePath)) {
39349
+ fs10.unlinkSync(statePath);
39350
+ results.push("\u2705 Deleted .swarm/session/state.json");
39351
+ } else {
39352
+ results.push("\u23ED\uFE0F state.json not found (already clean)");
39353
+ }
39354
+ } catch {
39355
+ results.push("\u274C Failed to delete state.json");
39356
+ }
39357
+ const sessionCount = swarmState.agentSessions.size;
39358
+ swarmState.agentSessions.clear();
39359
+ results.push(`\u2705 Cleared ${sessionCount} in-memory agent session(s)`);
39360
+ return [
39361
+ "## Session State Reset",
39362
+ "",
39363
+ ...results,
39364
+ "",
39365
+ "Session state cleared. Plan, evidence, and knowledge preserved.",
39366
+ "",
39367
+ "**Next step:** Start a new OpenCode session. The plugin will initialize fresh session state on startup."
39368
+ ].join(`
39369
+ `);
39370
+ }
39371
+
39325
39372
  // src/summaries/manager.ts
39326
39373
  init_utils2();
39327
39374
  init_utils();
@@ -39402,18 +39449,18 @@ ${error93 instanceof Error ? error93.message : String(error93)}`;
39402
39449
 
39403
39450
  // src/commands/rollback.ts
39404
39451
  init_utils2();
39405
- import * as fs10 from "fs";
39452
+ import * as fs11 from "fs";
39406
39453
  import * as path22 from "path";
39407
39454
  async function handleRollbackCommand(directory, args) {
39408
39455
  const phaseArg = args[0];
39409
39456
  if (!phaseArg) {
39410
39457
  const manifestPath2 = validateSwarmPath(directory, "checkpoints/manifest.json");
39411
- if (!fs10.existsSync(manifestPath2)) {
39458
+ if (!fs11.existsSync(manifestPath2)) {
39412
39459
  return "No checkpoints found. Use `/swarm checkpoint` to create checkpoints.";
39413
39460
  }
39414
39461
  let manifest2;
39415
39462
  try {
39416
- manifest2 = JSON.parse(fs10.readFileSync(manifestPath2, "utf-8"));
39463
+ manifest2 = JSON.parse(fs11.readFileSync(manifestPath2, "utf-8"));
39417
39464
  } catch {
39418
39465
  return "Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.";
39419
39466
  }
@@ -39435,12 +39482,12 @@ async function handleRollbackCommand(directory, args) {
39435
39482
  return "Error: Phase number must be a positive integer.";
39436
39483
  }
39437
39484
  const manifestPath = validateSwarmPath(directory, "checkpoints/manifest.json");
39438
- if (!fs10.existsSync(manifestPath)) {
39485
+ if (!fs11.existsSync(manifestPath)) {
39439
39486
  return `Error: No checkpoints found. Cannot rollback to phase ${targetPhase}.`;
39440
39487
  }
39441
39488
  let manifest;
39442
39489
  try {
39443
- manifest = JSON.parse(fs10.readFileSync(manifestPath, "utf-8"));
39490
+ manifest = JSON.parse(fs11.readFileSync(manifestPath, "utf-8"));
39444
39491
  } catch {
39445
39492
  return `Error: Checkpoint manifest is corrupted. Delete .swarm/checkpoints/manifest.json and re-checkpoint.`;
39446
39493
  }
@@ -39450,10 +39497,10 @@ async function handleRollbackCommand(directory, args) {
39450
39497
  return `Error: Checkpoint for phase ${targetPhase} not found. Available phases: ${available}`;
39451
39498
  }
39452
39499
  const checkpointDir = validateSwarmPath(directory, `checkpoints/phase-${targetPhase}`);
39453
- if (!fs10.existsSync(checkpointDir)) {
39500
+ if (!fs11.existsSync(checkpointDir)) {
39454
39501
  return `Error: Checkpoint directory for phase ${targetPhase} does not exist.`;
39455
39502
  }
39456
- const checkpointFiles = fs10.readdirSync(checkpointDir);
39503
+ const checkpointFiles = fs11.readdirSync(checkpointDir);
39457
39504
  if (checkpointFiles.length === 0) {
39458
39505
  return `Error: Checkpoint for phase ${targetPhase} is empty. Cannot rollback.`;
39459
39506
  }
@@ -39464,7 +39511,7 @@ async function handleRollbackCommand(directory, args) {
39464
39511
  const src = path22.join(checkpointDir, file3);
39465
39512
  const dest = path22.join(swarmDir, file3);
39466
39513
  try {
39467
- fs10.cpSync(src, dest, { recursive: true, force: true });
39514
+ fs11.cpSync(src, dest, { recursive: true, force: true });
39468
39515
  successes.push(file3);
39469
39516
  } catch (error93) {
39470
39517
  failures.push({ file: file3, error: error93.message });
@@ -39481,7 +39528,7 @@ async function handleRollbackCommand(directory, args) {
39481
39528
  timestamp: new Date().toISOString()
39482
39529
  };
39483
39530
  try {
39484
- fs10.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
39531
+ fs11.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
39485
39532
  `);
39486
39533
  } catch (error93) {
39487
39534
  console.error("Failed to write rollback event:", error93);
@@ -39524,11 +39571,11 @@ async function handleSimulateCommand(directory, args) {
39524
39571
  ];
39525
39572
  const report = reportLines.filter(Boolean).join(`
39526
39573
  `);
39527
- const fs11 = await import("fs/promises");
39574
+ const fs12 = await import("fs/promises");
39528
39575
  const path23 = await import("path");
39529
39576
  const reportPath = path23.join(directory, ".swarm", "simulate-report.md");
39530
- await fs11.mkdir(path23.dirname(reportPath), { recursive: true });
39531
- await fs11.writeFile(reportPath, report, "utf-8");
39577
+ await fs12.mkdir(path23.dirname(reportPath), { recursive: true });
39578
+ await fs12.writeFile(reportPath, report, "utf-8");
39532
39579
  return `${darkMatterPairs.length} hidden coupling pairs detected`;
39533
39580
  }
39534
39581
 
@@ -39929,6 +39976,47 @@ async function executeWriteRetro(args, directory) {
39929
39976
  message: "Invalid summary: must be a non-empty string"
39930
39977
  }, null, 2);
39931
39978
  }
39979
+ if (args.task_id !== undefined) {
39980
+ const tid = args.task_id;
39981
+ if (!tid || tid.length === 0 || tid.length > 200) {
39982
+ return JSON.stringify({
39983
+ success: false,
39984
+ phase,
39985
+ message: "Invalid task ID: must match pattern"
39986
+ }, null, 2);
39987
+ }
39988
+ if (/\0/.test(tid)) {
39989
+ return JSON.stringify({
39990
+ success: false,
39991
+ phase,
39992
+ message: "Invalid task ID: contains null bytes"
39993
+ }, null, 2);
39994
+ }
39995
+ for (let i = 0;i < tid.length; i++) {
39996
+ if (tid.charCodeAt(i) < 32) {
39997
+ return JSON.stringify({
39998
+ success: false,
39999
+ phase,
40000
+ message: "Invalid task ID: contains control characters"
40001
+ }, null, 2);
40002
+ }
40003
+ }
40004
+ if (tid.includes("..") || tid.includes("/") || tid.includes("\\")) {
40005
+ return JSON.stringify({
40006
+ success: false,
40007
+ phase,
40008
+ message: "Invalid task ID: path traversal detected"
40009
+ }, null, 2);
40010
+ }
40011
+ const VALID_TASK_ID = /^(retro-\d+|\d+\.\d+(\.\d+)*)$/;
40012
+ if (!VALID_TASK_ID.test(tid)) {
40013
+ return JSON.stringify({
40014
+ success: false,
40015
+ phase,
40016
+ message: "Invalid task ID: must match pattern"
40017
+ }, null, 2);
40018
+ }
40019
+ }
39932
40020
  const taskId = args.task_id ?? `retro-${phase}`;
39933
40021
  const retroEntry = {
39934
40022
  task_id: taskId,
@@ -40168,6 +40256,10 @@ var COMMAND_REGISTRY = {
40168
40256
  handler: (ctx) => handleResetCommand(ctx.directory, ctx.args),
40169
40257
  description: "Clear swarm state files [--confirm]"
40170
40258
  },
40259
+ "reset-session": {
40260
+ handler: (ctx) => handleResetSessionCommand(ctx.directory, ctx.args),
40261
+ description: "Clear session state while preserving plan, evidence, and knowledge"
40262
+ },
40171
40263
  rollback: {
40172
40264
  handler: (ctx) => handleRollbackCommand(ctx.directory, ctx.args),
40173
40265
  description: "Restore swarm state to a checkpoint <phase>"
@@ -40241,13 +40333,13 @@ var OPENCODE_CONFIG_PATH = path23.join(CONFIG_DIR, "opencode.json");
40241
40333
  var PLUGIN_CONFIG_PATH = path23.join(CONFIG_DIR, "opencode-swarm.json");
40242
40334
  var PROMPTS_DIR = path23.join(CONFIG_DIR, "opencode-swarm");
40243
40335
  function ensureDir(dir) {
40244
- if (!fs11.existsSync(dir)) {
40245
- fs11.mkdirSync(dir, { recursive: true });
40336
+ if (!fs12.existsSync(dir)) {
40337
+ fs12.mkdirSync(dir, { recursive: true });
40246
40338
  }
40247
40339
  }
40248
40340
  function loadJson(filepath) {
40249
40341
  try {
40250
- const content = fs11.readFileSync(filepath, "utf-8");
40342
+ const content = fs12.readFileSync(filepath, "utf-8");
40251
40343
  const stripped = content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, comment) => comment ? "" : match).replace(/,(\s*[}\]])/g, "$1");
40252
40344
  return JSON.parse(stripped);
40253
40345
  } catch {
@@ -40255,7 +40347,7 @@ function loadJson(filepath) {
40255
40347
  }
40256
40348
  }
40257
40349
  function saveJson(filepath, data) {
40258
- fs11.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
40350
+ fs12.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
40259
40351
  `, "utf-8");
40260
40352
  }
40261
40353
  async function install() {
@@ -40288,7 +40380,7 @@ async function install() {
40288
40380
  saveJson(OPENCODE_CONFIG_PATH, opencodeConfig);
40289
40381
  console.log("\u2713 Added opencode-swarm to OpenCode plugins");
40290
40382
  console.log("\u2713 Disabled default OpenCode agents (explore, general)");
40291
- if (!fs11.existsSync(PLUGIN_CONFIG_PATH)) {
40383
+ if (!fs12.existsSync(PLUGIN_CONFIG_PATH)) {
40292
40384
  const defaultConfig = {
40293
40385
  agents: {
40294
40386
  coder: { model: "opencode/minimax-m2.5-free" },
@@ -40331,7 +40423,7 @@ async function uninstall() {
40331
40423
  `);
40332
40424
  const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
40333
40425
  if (!opencodeConfig) {
40334
- if (fs11.existsSync(OPENCODE_CONFIG_PATH)) {
40426
+ if (fs12.existsSync(OPENCODE_CONFIG_PATH)) {
40335
40427
  console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
40336
40428
  return 1;
40337
40429
  } else {
@@ -40363,13 +40455,13 @@ async function uninstall() {
40363
40455
  console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
40364
40456
  if (process.argv.includes("--clean")) {
40365
40457
  let cleaned = false;
40366
- if (fs11.existsSync(PLUGIN_CONFIG_PATH)) {
40367
- fs11.unlinkSync(PLUGIN_CONFIG_PATH);
40458
+ if (fs12.existsSync(PLUGIN_CONFIG_PATH)) {
40459
+ fs12.unlinkSync(PLUGIN_CONFIG_PATH);
40368
40460
  console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
40369
40461
  cleaned = true;
40370
40462
  }
40371
- if (fs11.existsSync(PROMPTS_DIR)) {
40372
- fs11.rmSync(PROMPTS_DIR, { recursive: true });
40463
+ if (fs12.existsSync(PROMPTS_DIR)) {
40464
+ fs12.rmSync(PROMPTS_DIR, { recursive: true });
40373
40465
  console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
40374
40466
  cleaned = true;
40375
40467
  }
@@ -21,6 +21,7 @@ export { handlePromoteCommand } from './promote';
21
21
  export type { CommandContext, CommandEntry, RegisteredCommand, } from './registry.js';
22
22
  export { COMMAND_REGISTRY, resolveCommand, VALID_COMMANDS, } from './registry.js';
23
23
  export { handleResetCommand } from './reset';
24
+ export { handleResetSessionCommand } from './reset-session';
24
25
  export { handleRetrieveCommand } from './retrieve';
25
26
  export { handleRollbackCommand } from './rollback';
26
27
  export { handleSimulateCommand } from './simulate';
@@ -104,6 +104,10 @@ export declare const COMMAND_REGISTRY: {
104
104
  readonly handler: (ctx: CommandContext) => Promise<string>;
105
105
  readonly description: "Clear swarm state files [--confirm]";
106
106
  };
107
+ readonly 'reset-session': {
108
+ readonly handler: (ctx: CommandContext) => Promise<string>;
109
+ readonly description: "Clear session state while preserving plan, evidence, and knowledge";
110
+ };
107
111
  readonly rollback: {
108
112
  readonly handler: (ctx: CommandContext) => Promise<string>;
109
113
  readonly description: "Restore swarm state to a checkpoint <phase>";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Handles the /swarm reset-session command.
3
+ * Deletes only the session state file (.swarm/session/state.json)
4
+ * and clears in-memory agent sessions. Preserves plan, evidence,
5
+ * and knowledge for continuity across sessions.
6
+ */
7
+ export declare function handleResetSessionCommand(directory: string, _args: string[]): Promise<string>;
@@ -313,6 +313,7 @@ export declare const GuardrailsConfigSchema: z.ZodObject<{
313
313
  max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
314
314
  warning_threshold: z.ZodDefault<z.ZodNumber>;
315
315
  idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
316
+ no_op_warning_threshold: z.ZodDefault<z.ZodNumber>;
316
317
  qa_gates: z.ZodOptional<z.ZodObject<{
317
318
  required_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
318
319
  require_reviewer_test_engineer: z.ZodDefault<z.ZodBoolean>;
@@ -497,6 +498,11 @@ export declare const PluginConfigSchema: z.ZodObject<{
497
498
  }, z.core.$strip>>;
498
499
  }, z.core.$strip>>;
499
500
  qa_retry_limit: z.ZodDefault<z.ZodNumber>;
501
+ execution_mode: z.ZodDefault<z.ZodEnum<{
502
+ strict: "strict";
503
+ balanced: "balanced";
504
+ fast: "fast";
505
+ }>>;
500
506
  inject_phase_reminders: z.ZodDefault<z.ZodBoolean>;
501
507
  hooks: z.ZodOptional<z.ZodObject<{
502
508
  system_enhancer: z.ZodDefault<z.ZodBoolean>;
@@ -585,6 +591,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
585
591
  max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
586
592
  warning_threshold: z.ZodDefault<z.ZodNumber>;
587
593
  idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
594
+ no_op_warning_threshold: z.ZodDefault<z.ZodNumber>;
588
595
  qa_gates: z.ZodOptional<z.ZodObject<{
589
596
  required_tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
590
597
  require_reviewer_test_engineer: z.ZodDefault<z.ZodBoolean>;
@@ -6,6 +6,13 @@
6
6
  */
7
7
  import type { PluginConfig } from '../config';
8
8
  import type { DelegationEnvelope, EnvelopeValidationResult } from '../types/delegation.js';
9
+ /**
10
+ * v6.33.1 CRIT-1: Fallback map for declared coder scope by taskId.
11
+ * When messagesTransform sets declaredCoderScope on the architect session,
12
+ * the coder session may not exist yet. This map allows scope-guard to look up
13
+ * the scope by taskId when the session's declaredCoderScope is null.
14
+ */
15
+ export declare const pendingCoderScopeByTaskId: Map<string, string[]>;
9
16
  /**
10
17
  * Parses a string to extract a DelegationEnvelope.
11
18
  * Returns null if no valid envelope is found.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Verification tests for callID→evidenceTaskId map in delegation-gate.ts
3
+ *
4
+ * Tests verify the map behavior indirectly through evidence recording outcomes:
5
+ * 1. Map stores callID→evidenceTaskId after determining the taskId
6
+ * 2. Map is checked first before getEvidenceTaskId fallback
7
+ * 3. Map entry is cleaned up after successful evidence recording
8
+ * 4. Map entry is cleaned up even when evidence recording errors
9
+ * 5. Fallback works when storedTaskId is not in map
10
+ */
11
+ export {};
@@ -23,7 +23,7 @@ export interface ScopeGuardConfig {
23
23
  * @param _directory - The workspace directory (reserved for future use)
24
24
  * @param injectAdvisory - Optional callback to push advisory to architect session
25
25
  */
26
- export declare function createScopeGuardHook(config: Partial<ScopeGuardConfig>, _directory: string, injectAdvisory?: (sessionId: string, message: string) => void): {
26
+ export declare function createScopeGuardHook(config: Partial<ScopeGuardConfig>, directory: string, injectAdvisory?: (sessionId: string, message: string) => void): {
27
27
  toolBefore: (input: {
28
28
  tool: string;
29
29
  sessionID: string;
@@ -40,4 +40,4 @@ export declare function createScopeGuardHook(config: Partial<ScopeGuardConfig>,
40
40
  * @param scopeEntries - Array of declared scope entries (files or directories)
41
41
  * @returns true if the file is within scope, false otherwise
42
42
  */
43
- export declare function isFileInScope(filePath: string, scopeEntries: string[]): boolean;
43
+ export declare function isFileInScope(filePath: string, scopeEntries: string[], directory?: string): boolean;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TRAJECTORY LOGGER (v6.31 Task 3.2)
3
+ *
4
+ * tool.execute.after hook that appends per-task tool call trajectories to a
5
+ * .swarm/evidence/{taskId}/trajectory.jsonl file. Only logs INSIDE delegation
6
+ * scope (when delegationActive is true on the session).
7
+ *
8
+ * Trajectories are used for post-hoc analysis, audit trails, and replay.
9
+ */
10
+ export interface TrajectoryConfig {
11
+ enabled: boolean;
12
+ max_lines: number;
13
+ }
14
+ export interface TrajectoryEntry {
15
+ tool: string;
16
+ args_summary: string;
17
+ verdict: string;
18
+ timestamp: string;
19
+ agent: string;
20
+ elapsed_ms: number;
21
+ }
22
+ /**
23
+ * Truncates a trajectory file to the newest half when maxLines is exceeded.
24
+ * Reads all lines, keeps the newest half, rewrites the file.
25
+ *
26
+ * @param filePath - Absolute path to the trajectory.jsonl file
27
+ * @param maxLines - Maximum number of lines to retain
28
+ */
29
+ export declare function truncateTrajectoryFile(filePath: string, maxLines: number): Promise<void>;
30
+ /**
31
+ * Creates the trajectory logger hook pair.
32
+ *
33
+ * @param config - TrajectoryConfig { enabled: boolean (default true), max_lines: number (default 500) }
34
+ * @param _directory - Reserved for future use (evidence path derived from session taskId)
35
+ * @returns Object with toolAfter handler
36
+ */
37
+ export declare function createTrajectoryLoggerHook(config: Partial<TrajectoryConfig>, _directory: string): {
38
+ toolAfter: (input: {
39
+ tool: string;
40
+ sessionID: string;
41
+ callID: string;
42
+ args?: Record<string, unknown>;
43
+ }, output: {
44
+ title: string;
45
+ output: string;
46
+ metadata: unknown;
47
+ }) => Promise<void>;
48
+ };
49
+ /**
50
+ * Records the start time for a tool call (called from toolBefore).
51
+ * Stored in a module-level Map for correlation with toolAfter.
52
+ *
53
+ * @param sessionId - Session identifier
54
+ * @param callID - Tool call identifier
55
+ * @param startTime - Start timestamp in milliseconds
56
+ */
57
+ export declare function recordToolCallStart(sessionId: string, callID: string, startTime: number): void;