@neotx/core 0.1.0-alpha.22 → 0.1.0-alpha.24

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/index.d.ts CHANGED
@@ -38,6 +38,7 @@ declare const agentConfigSchema: z.ZodObject<{
38
38
  name: z.ZodString;
39
39
  extends: z.ZodOptional<z.ZodString>;
40
40
  description: z.ZodOptional<z.ZodString>;
41
+ version: z.ZodOptional<z.ZodString>;
41
42
  model: z.ZodOptional<z.ZodEnum<{
42
43
  opus: "opus";
43
44
  sonnet: "sonnet";
@@ -62,7 +63,29 @@ declare const agentConfigSchema: z.ZodObject<{
62
63
  writable: "writable";
63
64
  }>>;
64
65
  maxTurns: z.ZodOptional<z.ZodNumber>;
66
+ maxCost: z.ZodOptional<z.ZodNumber>;
65
67
  mcpServers: z.ZodOptional<z.ZodArray<z.ZodString>>;
68
+ agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
69
+ description: z.ZodString;
70
+ prompt: z.ZodString;
71
+ tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
72
+ Read: "Read";
73
+ Write: "Write";
74
+ Edit: "Edit";
75
+ Bash: "Bash";
76
+ Glob: "Glob";
77
+ Grep: "Grep";
78
+ Agent: "Agent";
79
+ WebSearch: "WebSearch";
80
+ WebFetch: "WebFetch";
81
+ NotebookEdit: "NotebookEdit";
82
+ }>>>;
83
+ model: z.ZodOptional<z.ZodEnum<{
84
+ opus: "opus";
85
+ sonnet: "sonnet";
86
+ haiku: "haiku";
87
+ }>>;
88
+ }, z.core.$strip>>>;
66
89
  }, z.core.$strip>;
67
90
  type AgentConfig = z.infer<typeof agentConfigSchema>;
68
91
  type AgentModel = z.infer<typeof agentModelSchema>;
@@ -368,18 +391,31 @@ declare function removeRepoFromGlobalConfig(pathOrName: string): Promise<boolean
368
391
  */
369
392
  declare function listReposFromGlobalConfig(): Promise<RepoConfig[]>;
370
393
 
394
+ interface SubagentDefinition {
395
+ description: string;
396
+ prompt: string;
397
+ tools?: string[] | undefined;
398
+ model?: string | undefined;
399
+ }
371
400
  interface AgentDefinition {
372
401
  description: string;
373
402
  prompt: string;
374
403
  tools: string[];
375
404
  model: string;
376
405
  mcpServers?: string[] | undefined;
406
+ agents?: Record<string, SubagentDefinition> | undefined;
377
407
  }
378
408
  interface ResolvedAgent {
379
409
  name: string;
380
410
  definition: AgentDefinition;
381
411
  sandbox: "writable" | "readonly";
382
412
  maxTurns?: number | undefined;
413
+ /**
414
+ * Maximum cost in USD for this agent session.
415
+ * Checked post-session; if session cost >= maxCost, budget_exceeded error is thrown.
416
+ */
417
+ maxCost?: number | undefined;
418
+ version?: string | undefined;
383
419
  source: "built-in" | "custom" | "extended";
384
420
  }
385
421
  interface PersistedRun {
@@ -1058,7 +1094,9 @@ interface SessionOptions {
1058
1094
  env?: Record<string, string>;
1059
1095
  initTimeoutMs: number;
1060
1096
  maxDurationMs: number;
1097
+ maxTurns?: number | undefined;
1061
1098
  resumeSessionId?: string | undefined;
1099
+ agents?: Record<string, unknown> | undefined;
1062
1100
  onEvent?: ((event: SessionEvent) => void) | undefined;
1063
1101
  }
1064
1102
  interface SessionResult {
@@ -1208,6 +1246,16 @@ type DecisionInput = Omit<Decision, "id" | "createdAt" | "answeredAt" | "answer"
1208
1246
  * JSONL-backed store for decisions.
1209
1247
  * Append-only with in-place updates for answers and expiration.
1210
1248
  * Uses an in-memory mutex to serialize write operations.
1249
+ *
1250
+ * Compaction Strategy:
1251
+ * - Threshold-based compaction triggers on either:
1252
+ * 1. Tombstone ratio exceeds 30% of total valid entries (tombstones / (valid decisions + tombstones), excluding malformed lines)
1253
+ * 2. File size exceeds 10MB (prevents unbounded growth)
1254
+ * - Compaction rebuilds the file, filtering out tombstoned entries and preserving active decisions
1255
+ * - In-memory index maintains O(1) lookup without full file scans
1256
+ * - Compaction runs synchronously within the write lock to maintain consistency
1257
+ * - During compaction: all write operations are blocked until compaction completes, preventing race conditions
1258
+ * - Concurrent reads during compaction may return stale data from before compaction started
1211
1259
  */
1212
1260
  declare class DecisionStore {
1213
1261
  private readonly filePath;
@@ -1215,6 +1263,8 @@ declare class DecisionStore {
1215
1263
  private readonly dirCache;
1216
1264
  /** Promise-based mutex to serialize write operations */
1217
1265
  private writeLock;
1266
+ /** Maximum file size in bytes before validation fails (default: 10MB) */
1267
+ private readonly maxFileSizeBytes;
1218
1268
  constructor(filePath: string);
1219
1269
  /**
1220
1270
  * Acquire the write lock and execute a callback.
@@ -1314,9 +1364,9 @@ declare const activityEntrySchema: z.ZodObject<{
1314
1364
  message: "message";
1315
1365
  decision: "decision";
1316
1366
  tool_use: "tool_use";
1367
+ action: "action";
1317
1368
  event: "event";
1318
1369
  heartbeat: "heartbeat";
1319
- action: "action";
1320
1370
  warning: "warning";
1321
1371
  thinking: "thinking";
1322
1372
  plan: "plan";
@@ -1379,8 +1429,8 @@ declare const activityQueryOptionsSchema: z.ZodObject<{
1379
1429
  error: "error";
1380
1430
  message: "message";
1381
1431
  decision: "decision";
1382
- event: "event";
1383
1432
  action: "action";
1433
+ event: "event";
1384
1434
  plan: "plan";
1385
1435
  dispatch: "dispatch";
1386
1436
  }>>;
@@ -1988,4 +2038,4 @@ declare function testWebhooks(): Promise<WebhookTestResult[]>;
1988
2038
 
1989
2039
  declare const VERSION = "0.1.0";
1990
2040
 
1991
- export { type ActiveSession, type ActivityEntry, ActivityLog, type ActivityQueryOptions, type AgentConfig, type AgentDefinition, type AgentMessageEvent, type AgentModel, AgentRegistry, type AgentTool, type AgentToolEntry, type AgentToolUseEvent, type AuditLogMiddleware, type BudgetAlertEvent, ConfigStore, type CostEntry, CostJournal, type CostUpdateEvent, type Decision, type DecisionInput, type DecisionOption, DecisionStore, type DispatchInput, type Embedder, EventJournal, EventQueue, type GateWaitingEvent, type GitStrategy, type GlobalConfig, HeartbeatLoop, type HeartbeatLoopOptions, type HookEvent, type InboxMessage, LocalEmbedder, type LoopDetectionMiddleware, type McpServerConfig, type MemoryEntry, type MemoryQuery, type MemoryStats, MemoryStore, type MemoryType, type MemoryWriteInput, type Middleware, type MiddlewareChain, type MiddlewareContext, type MiddlewareContextMap, type MiddlewareEvent, type MiddlewareHandler, type MiddlewareResult, type NeoConfig, type NeoEvent, NeoEventEmitter, Orchestrator, type OrchestratorOptions, type OrchestratorShutdownEvent, type OrchestratorStatus, type ParsedOutput, type PersistedRun, type Priority, type QueueDequeueEvent, type QueueEnqueueEvent, type QueuedEvent, type RecoveryOptions, type RepoConfig, type RepoConfigInput, type ResolvedAgent, type RunContext, type SDKHooks, type SandboxConfig, Semaphore, type SemaphoreCallbacks, type SemaphoreConfig, type SessionCloneInfo, type SessionCompleteEvent, SessionError, type SessionEvent, type SessionExecutionConfig, type SessionExecutionDeps, type SessionExecutionInput, type SessionExecutionResult, SessionExecutor, type SessionFailEvent, type SessionOptions, type SessionResult, type SessionStartEvent, StatusReader, type StepCompleteEvent, type StepResult, type StepStartEvent, SupervisorDaemon, type SupervisorDaemonOptions, type SupervisorDaemonState, type SupervisorDaemonState as SupervisorState, type SupervisorStatus, type TaskResult, VERSION, WebhookDispatcher, type WebhookEntry, type WebhookEntryInput, type WebhookIncomingEvent, WebhookServer, type WebhookTestPayload, type WebhookTestResult, activityEntrySchema, addRepoToGlobalConfig, addWebhook, agentConfigSchema, agentModelSchema, agentSandboxSchema, agentToolEntrySchema, agentToolSchema, appendLogBuffer, auditLog, budgetGuard, buildFullPrompt, buildGitStrategyInstructions, buildMiddlewareChain, buildReportingInstructions, buildSDKHooks, buildSandboxConfig, createBranch, createSessionClone, decisionOptionSchema, decisionSchema, deleteBranch, fetchRemote, getBranchName, getCurrentBranch, getDataDir, getJournalsDir, getRepoRunsDir, getRunDispatchPath, getRunLogPath, getRunsDir, getSupervisorActivityPath, getSupervisorDecisionsPath, getSupervisorDir, getSupervisorEventsPath, getSupervisorInboxPath, getSupervisorLockPath, getSupervisorStatePath, getSupervisorsDir, globalConfigSchema, inboxMessageSchema, isProcessAlive, listReposFromGlobalConfig, listSessionClones, listWebhooks, loadAgentFile, loadConfig, loadGlobalConfig, loadRepoInstructions, loopDetection, matchesFilter, mcpServerConfigSchema, neoConfigSchema, parseOutput, pushBranch, pushSessionBranch, removeRepoFromGlobalConfig, removeSessionClone, removeWebhook, repoConfigSchema, repoOverrideConfigSchema, resolveAgent, runSession, runWithRecovery, supervisorDaemonStateSchema, supervisorDaemonStateSchema as supervisorStateSchema, supervisorStatusSchema, testWebhooks, toRepoSlug, webhookEntrySchema, webhookIncomingEventSchema };
2041
+ export { type ActiveSession, type ActivityEntry, ActivityLog, type ActivityQueryOptions, type AgentConfig, type AgentDefinition, type AgentMessageEvent, type AgentModel, AgentRegistry, type AgentTool, type AgentToolEntry, type AgentToolUseEvent, type AuditLogMiddleware, type BudgetAlertEvent, ConfigStore, type CostEntry, CostJournal, type CostUpdateEvent, type Decision, type DecisionInput, type DecisionOption, DecisionStore, type DispatchInput, type Embedder, EventJournal, EventQueue, type GateWaitingEvent, type GitStrategy, type GlobalConfig, HeartbeatLoop, type HeartbeatLoopOptions, type HookEvent, type InboxMessage, LocalEmbedder, type LoopDetectionMiddleware, type McpServerConfig, type MemoryEntry, type MemoryQuery, type MemoryStats, MemoryStore, type MemoryType, type MemoryWriteInput, type Middleware, type MiddlewareChain, type MiddlewareContext, type MiddlewareContextMap, type MiddlewareEvent, type MiddlewareHandler, type MiddlewareResult, type NeoConfig, type NeoEvent, NeoEventEmitter, Orchestrator, type OrchestratorOptions, type OrchestratorShutdownEvent, type OrchestratorStatus, type ParsedOutput, type PersistedRun, type Priority, type QueueDequeueEvent, type QueueEnqueueEvent, type QueuedEvent, type RecoveryOptions, type RepoConfig, type RepoConfigInput, type ResolvedAgent, type RunContext, type SDKHooks, type SandboxConfig, Semaphore, type SemaphoreCallbacks, type SemaphoreConfig, type SessionCloneInfo, type SessionCompleteEvent, SessionError, type SessionEvent, type SessionExecutionConfig, type SessionExecutionDeps, type SessionExecutionInput, type SessionExecutionResult, SessionExecutor, type SessionFailEvent, type SessionOptions, type SessionResult, type SessionStartEvent, StatusReader, type StepCompleteEvent, type StepResult, type StepStartEvent, type SubagentDefinition, SupervisorDaemon, type SupervisorDaemonOptions, type SupervisorDaemonState, type SupervisorDaemonState as SupervisorState, type SupervisorStatus, type TaskResult, VERSION, WebhookDispatcher, type WebhookEntry, type WebhookEntryInput, type WebhookIncomingEvent, WebhookServer, type WebhookTestPayload, type WebhookTestResult, activityEntrySchema, addRepoToGlobalConfig, addWebhook, agentConfigSchema, agentModelSchema, agentSandboxSchema, agentToolEntrySchema, agentToolSchema, appendLogBuffer, auditLog, budgetGuard, buildFullPrompt, buildGitStrategyInstructions, buildMiddlewareChain, buildReportingInstructions, buildSDKHooks, buildSandboxConfig, createBranch, createSessionClone, decisionOptionSchema, decisionSchema, deleteBranch, fetchRemote, getBranchName, getCurrentBranch, getDataDir, getJournalsDir, getRepoRunsDir, getRunDispatchPath, getRunLogPath, getRunsDir, getSupervisorActivityPath, getSupervisorDecisionsPath, getSupervisorDir, getSupervisorEventsPath, getSupervisorInboxPath, getSupervisorLockPath, getSupervisorStatePath, getSupervisorsDir, globalConfigSchema, inboxMessageSchema, isProcessAlive, listReposFromGlobalConfig, listSessionClones, listWebhooks, loadAgentFile, loadConfig, loadGlobalConfig, loadRepoInstructions, loopDetection, matchesFilter, mcpServerConfigSchema, neoConfigSchema, parseOutput, pushBranch, pushSessionBranch, removeRepoFromGlobalConfig, removeSessionClone, removeWebhook, repoConfigSchema, repoOverrideConfigSchema, resolveAgent, runSession, runWithRecovery, supervisorDaemonStateSchema, supervisorDaemonStateSchema as supervisorStateSchema, supervisorStatusSchema, testWebhooks, toRepoSlug, webhookEntrySchema, webhookIncomingEventSchema };
package/dist/index.js CHANGED
@@ -20,17 +20,32 @@ var agentToolSchema = z.enum([
20
20
  ]);
21
21
  var agentToolEntrySchema = z.union([agentToolSchema, z.literal("$inherited")]);
22
22
  var agentSandboxSchema = z.enum(["writable", "readonly"]);
23
+ var subagentDefinitionSchema = z.object({
24
+ description: z.string(),
25
+ prompt: z.string(),
26
+ tools: z.array(agentToolSchema).optional(),
27
+ model: agentModelSchema.optional()
28
+ });
23
29
  var agentConfigSchema = z.object({
24
30
  name: z.string(),
25
31
  extends: z.string().optional(),
26
32
  description: z.string().optional(),
33
+ version: z.string().optional(),
27
34
  model: agentModelSchema.optional(),
28
35
  tools: z.array(agentToolEntrySchema).optional(),
29
36
  prompt: z.string().optional(),
30
37
  promptAppend: z.string().optional(),
31
38
  sandbox: agentSandboxSchema.optional(),
32
39
  maxTurns: z.number().optional(),
33
- mcpServers: z.array(z.string()).optional()
40
+ /**
41
+ * Maximum cost in USD for this agent session.
42
+ * Checked post-session (SDK provides cost only after session ends).
43
+ * If session cost >= maxCost, a budget_exceeded error is thrown.
44
+ * Child agents can override the parent's maxCost.
45
+ */
46
+ maxCost: z.number().min(0).optional(),
47
+ mcpServers: z.array(z.string()).optional(),
48
+ agents: z.record(z.string(), subagentDefinitionSchema).optional()
34
49
  });
35
50
 
36
51
  // src/agents/loader.ts
@@ -66,6 +81,20 @@ ${issues}`);
66
81
  );
67
82
  }
68
83
  }
84
+ if (config.agents) {
85
+ for (const [name, subagent] of Object.entries(config.agents)) {
86
+ if (subagent.prompt.endsWith(".md")) {
87
+ const subagentPromptPath = path.resolve(path.dirname(filePath), subagent.prompt);
88
+ try {
89
+ subagent.prompt = await readFile(subagentPromptPath, "utf-8");
90
+ } catch (err) {
91
+ throw new Error(
92
+ `Subagent "${name}" prompt file not found: ${subagentPromptPath} (referenced in ${filePath}). Error: ${err instanceof Error ? err.message : String(err)}`
93
+ );
94
+ }
95
+ }
96
+ }
97
+ }
69
98
  return config;
70
99
  }
71
100
 
@@ -91,18 +120,25 @@ function resolveExtendedAgent(config, extendsName, builtIns) {
91
120
  const tools = mergeTools(config.tools, base.tools);
92
121
  const prompt = mergePrompt(config.prompt, config.promptAppend, base.prompt);
93
122
  const mcpServers = mergeMcpServerNames(base.mcpServers, config.mcpServers);
123
+ const agents = mergeAgents(
124
+ base.agents,
125
+ config.agents
126
+ );
94
127
  const definition = {
95
128
  description: config.description ?? base.description ?? "",
96
129
  prompt,
97
130
  tools,
98
131
  model: config.model ?? base.model ?? "sonnet",
99
- ...mcpServers.length > 0 ? { mcpServers } : {}
132
+ ...mcpServers.length > 0 ? { mcpServers } : {},
133
+ ...agents ? { agents } : {}
100
134
  };
101
135
  return {
102
136
  name: config.name,
103
137
  definition,
104
138
  sandbox: config.sandbox ?? base.sandbox ?? "readonly",
105
139
  ...config.maxTurns !== void 0 ? { maxTurns: config.maxTurns } : base.maxTurns !== void 0 ? { maxTurns: base.maxTurns } : {},
140
+ ...config.maxCost !== void 0 ? { maxCost: config.maxCost } : base.maxCost !== void 0 ? { maxCost: base.maxCost } : {},
141
+ ...config.version !== void 0 ? { version: config.version } : base.version !== void 0 ? { version: base.version } : {},
106
142
  source: config.name === extendsName && !config.extends ? "built-in" : "extended"
107
143
  };
108
144
  }
@@ -144,13 +180,16 @@ ${config.promptAppend}`;
144
180
  prompt,
145
181
  tools,
146
182
  model: config.model,
147
- ...config.mcpServers?.length ? { mcpServers: config.mcpServers } : {}
183
+ ...config.mcpServers?.length ? { mcpServers: config.mcpServers } : {},
184
+ ...config.agents ? { agents: config.agents } : {}
148
185
  };
149
186
  return {
150
187
  name: config.name,
151
188
  definition,
152
189
  sandbox: config.sandbox,
153
190
  ...config.maxTurns !== void 0 ? { maxTurns: config.maxTurns } : {},
191
+ ...config.maxCost !== void 0 ? { maxCost: config.maxCost } : {},
192
+ ...config.version !== void 0 ? { version: config.version } : {},
154
193
  source: "custom"
155
194
  };
156
195
  }
@@ -175,6 +214,12 @@ function mergeMcpServerNames(base, override) {
175
214
  if (!base?.length && !override?.length) return [];
176
215
  return [.../* @__PURE__ */ new Set([...base ?? [], ...override ?? []])];
177
216
  }
217
+ function mergeAgents(base, override) {
218
+ if (!base && !override) return void 0;
219
+ if (!base) return override;
220
+ if (!override) return base;
221
+ return { ...base, ...override };
222
+ }
178
223
 
179
224
  // src/agents/registry.ts
180
225
  var AgentRegistry = class {
@@ -1996,7 +2041,7 @@ function buildQueryOptions(options) {
1996
2041
  // Always pass cwd: session clone for writable agents, repo root for readonly.
1997
2042
  // Without this, readonly agents default to process.cwd() and may write to main tree.
1998
2043
  cwd: sessionPath ?? options.repoPath,
1999
- // maxTurns: agent.maxTurns,
2044
+ ...options.maxTurns ? { maxTurns: options.maxTurns } : {},
2000
2045
  allowedTools: sandboxConfig.allowedTools,
2001
2046
  // Workers run detached without a TTY — bypass interactive permission prompts.
2002
2047
  // Required pair: permissionMode alone is not enough, SDK also needs the flag.
@@ -2013,6 +2058,9 @@ function buildQueryOptions(options) {
2013
2058
  if (options.mcpServers && Object.keys(options.mcpServers).length > 0) {
2014
2059
  queryOptions.mcpServers = options.mcpServers;
2015
2060
  }
2061
+ if (options.agents && Object.keys(options.agents).length > 0) {
2062
+ queryOptions.agents = options.agents;
2063
+ }
2016
2064
  if (options.env && Object.keys(options.env).length > 0) {
2017
2065
  queryOptions.env = { ...process.env, ...options.env };
2018
2066
  }
@@ -2241,14 +2289,23 @@ ALWAYS run commands from this directory. NEVER cd to or operate on any other rep
2241
2289
  sandboxConfig,
2242
2290
  hooks,
2243
2291
  env: agentEnv,
2292
+ agents: agent.definition.agents,
2244
2293
  initTimeoutMs: this.config.initTimeoutMs,
2245
2294
  maxDurationMs: this.config.maxDurationMs,
2246
2295
  maxRetries: this.config.maxRetries,
2247
2296
  backoffBaseMs: this.config.backoffBaseMs,
2248
2297
  ...sessionPath ? { sessionPath } : {},
2249
2298
  ...mcpServers ? { mcpServers } : {},
2250
- ...onAttempt ? { onAttempt } : {}
2299
+ ...onAttempt ? { onAttempt } : {},
2300
+ ...agent.maxTurns ? { maxTurns: agent.maxTurns } : {}
2251
2301
  });
2302
+ if (agent.maxCost !== void 0 && sessionResult.costUsd >= agent.maxCost) {
2303
+ throw new SessionError(
2304
+ `Agent session exceeded budget: $${sessionResult.costUsd.toFixed(4)} >= $${agent.maxCost.toFixed(4)} limit`,
2305
+ "budget_exceeded",
2306
+ sessionResult.sessionId
2307
+ );
2308
+ }
2252
2309
  const parsed = parseOutput(sessionResult.output);
2253
2310
  const result = {
2254
2311
  status: "success",
@@ -3456,9 +3513,20 @@ import { z as z5 } from "zod";
3456
3513
 
3457
3514
  // src/supervisor/decisions.ts
3458
3515
  import { randomUUID as randomUUID4 } from "crypto";
3459
- import { appendFile as appendFile4, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
3516
+ import { appendFile as appendFile4, readFile as readFile6, stat as stat3, writeFile as writeFile3 } from "fs/promises";
3460
3517
  import path11 from "path";
3461
3518
  import { z as z4 } from "zod";
3519
+ var DecisionFileSizeError = class extends Error {
3520
+ constructor(filePath, fileSizeBytes, maxSizeBytes) {
3521
+ super(
3522
+ `Decision file exceeds maximum size: ${filePath} (${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB > ${(maxSizeBytes / 1024 / 1024).toFixed(2)}MB)`
3523
+ );
3524
+ this.filePath = filePath;
3525
+ this.fileSizeBytes = fileSizeBytes;
3526
+ this.maxSizeBytes = maxSizeBytes;
3527
+ this.name = "DecisionFileSizeError";
3528
+ }
3529
+ };
3462
3530
  var decisionOptionSchema = z4.object({
3463
3531
  key: z4.string(),
3464
3532
  label: z4.string(),
@@ -3479,12 +3547,20 @@ var decisionSchema = z4.object({
3479
3547
  answer: z4.string().optional(),
3480
3548
  expiredAt: z4.coerce.string().optional()
3481
3549
  });
3550
+ var tombstoneSchema = z4.object({
3551
+ action: z4.literal("tombstone"),
3552
+ id: z4.string(),
3553
+ createdAt: z4.coerce.string(),
3554
+ reason: z4.enum(["deleted", "expired", "purged"])
3555
+ });
3482
3556
  var DecisionStore = class {
3483
3557
  filePath;
3484
3558
  dir;
3485
3559
  dirCache = /* @__PURE__ */ new Set();
3486
3560
  /** Promise-based mutex to serialize write operations */
3487
3561
  writeLock = Promise.resolve();
3562
+ /** Maximum file size in bytes before validation fails (default: 10MB) */
3563
+ maxFileSizeBytes = 10 * 1024 * 1024;
3488
3564
  constructor(filePath) {
3489
3565
  this.filePath = filePath;
3490
3566
  this.dir = path11.dirname(filePath);
@@ -3607,6 +3683,10 @@ var DecisionStore = class {
3607
3683
  async readAll() {
3608
3684
  let content;
3609
3685
  try {
3686
+ const stats = await stat3(this.filePath);
3687
+ if (stats.size > this.maxFileSizeBytes) {
3688
+ throw new DecisionFileSizeError(this.filePath, stats.size, this.maxFileSizeBytes);
3689
+ }
3610
3690
  content = await readFile6(this.filePath, "utf-8");
3611
3691
  } catch (error) {
3612
3692
  if (error.code === "ENOENT") {
@@ -3733,7 +3813,7 @@ var activityQueryOptionsSchema = z5.object({
3733
3813
 
3734
3814
  // src/supervisor/activity-log.ts
3735
3815
  import { randomUUID as randomUUID5 } from "crypto";
3736
- import { appendFile as appendFile5, readFile as readFile7, rename, stat as stat3 } from "fs/promises";
3816
+ import { appendFile as appendFile5, readFile as readFile7, rename, stat as stat4 } from "fs/promises";
3737
3817
  import path12 from "path";
3738
3818
  var ACTIVITY_FILE = "activity.jsonl";
3739
3819
  var MAX_SIZE_BYTES = 10 * 1024 * 1024;
@@ -3795,7 +3875,7 @@ var ActivityLog = class {
3795
3875
  }
3796
3876
  async checkRotation() {
3797
3877
  try {
3798
- const stats = await stat3(this.filePath);
3878
+ const stats = await stat4(this.filePath);
3799
3879
  if (stats.size > MAX_SIZE_BYTES) {
3800
3880
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3801
3881
  const rotatedPath = path12.join(this.dir, `activity-${timestamp}.jsonl`);
@@ -4113,7 +4193,7 @@ var IdleDetector = class {
4113
4193
  };
4114
4194
 
4115
4195
  // src/supervisor/log-buffer.ts
4116
- import { appendFile as appendFile6, readFile as readFile9, stat as stat4, writeFile as writeFile5 } from "fs/promises";
4196
+ import { appendFile as appendFile6, readFile as readFile9, stat as stat5, writeFile as writeFile5 } from "fs/promises";
4117
4197
  import path13 from "path";
4118
4198
  var LOG_BUFFER_FILE = "log-buffer.jsonl";
4119
4199
  var MAX_FILE_BYTES = 1024 * 1024;
@@ -4247,7 +4327,9 @@ var OPERATING_PRINCIPLES = `### Operating principles
4247
4327
  - Your user-visible channel is \`neo log\` only; produce concise tool calls (not reasoning/explanations) and avoid wasted tokens.
4248
4328
  - You may inspect repositories available via \`neo repos\`, read-only to launch agents.
4249
4329
  - Task hygiene is non-negotiable: update task outcomes EVERY heartbeat. A task without a current outcome is a blind spot.
4250
- - **No duplicate dispatches**: before dispatching a \`developer\` for any finding, ALWAYS check for open or recently merged PRs on the same topic: \`gh pr list --repo <repo> --search "<keywords>" --state open\` and \`--state merged --limit 5\`. If a similar PR exists \u2192 skip and log with \`neo log discovery\`. Dispatching duplicate agents wastes budget and pollutes the PR list.`;
4330
+ - **No duplicate dispatches**: before dispatching a \`developer\` for any finding, ALWAYS check for open or recently merged PRs on the same topic: \`gh pr list --repo <repo> --search "<keywords>" --state open\` and \`--state merged --limit 5\`. If a similar PR exists \u2192 skip and log with \`neo log discovery\`. Dispatching duplicate agents wastes budget and pollutes the PR list.
4331
+ - **Decision routing**: when a pending decision arrives from an agent, answer within 1-2 heartbeats. Route: (1) answer directly if strategic/scope/priority, (2) dispatch scout to investigate if codebase context needed, (3) wait for human if autoDecide is off or genuinely uncertain. Agents are BLOCKED waiting \u2014 stale decisions waste session budget.
4332
+ - **Verify agent output**: always read agent output with \`neo runs <runId>\` before dispatching follow-up work. Route based on agent output contracts documented in SUPERVISOR.md.`;
4251
4333
  var COMMANDS = `### Dispatching agents
4252
4334
  \`\`\`bash
4253
4335
  neo run <agent> --prompt "..." --repo <path> --branch <name> [--priority critical|high|medium|low] [--meta '<json>']
@@ -4272,7 +4354,7 @@ neo runs <runId> # full run details + agent output (MUST READ
4272
4354
  neo cost --short [--all] # check budget
4273
4355
  \`\`\`
4274
4356
 
4275
- \`neo runs <runId>\` returns the agent's full output. **ALWAYS read it when a run completes** \u2014 it contains structured JSON (PR URLs, issues, plans, milestones) that you need to decide next steps.
4357
+ \`neo runs <runId>\` returns the agent's full output. **ALWAYS read it when a run completes** \u2014 it contains the agent's results that you need to decide next steps per SUPERVISOR.md routing rules.
4276
4358
 
4277
4359
  ### Memory
4278
4360
  \`\`\`bash
@@ -4323,8 +4405,9 @@ var HEARTBEAT_RULES = `### Heartbeat lifecycle
4323
4405
  1. DEDUP FIRST \u2014 check focus for PROCESSED entries. Skip any runId already processed.
4324
4406
  2. MONITOR RUNS \u2014 \`neo runs --short\` to check active run status. If a run completed since last HB, read its output with \`neo runs <runId>\` BEFORE doing anything else.
4325
4407
  3. PENDING TASKS? \u2014 dispatch the next eligible task from work queue. Do not re-plan.
4326
- 4. EVENTS? \u2014 process run completions, messages, webhooks. Parse agent JSON output.
4408
+ 4. EVENTS? \u2014 process run completions, messages, webhooks. Read agent output and route per SUPERVISOR.md contracts.
4327
4409
  5. FOLLOW-UPS? \u2014 check CI (\`gh pr checks\`), deferred dispatches.
4410
+ 5b. DECISIONS? \u2014 check \`neo decision list\` for pending decisions from agents. Route each: answer directly, dispatch scout to investigate, or wait for human. Agents are blocked waiting \u2014 prioritize these.
4328
4411
  6. DISPATCH \u2014 route work to agents. Mark tasks \`in_progress\`, add ACTIVE to focus.
4329
4412
  7. UPDATE TASKS \u2014 review ALL in_progress/blocked tasks. For each: confirm status matches reality (run still active? PR merged? blocked resolved?). Update outcomes immediately \u2014 do not defer to next heartbeat.
4330
4413
  8. SERIALIZE & YIELD \u2014 rewrite focus (see <focus>), log your decisions, and yield. Do not poll.
@@ -4333,15 +4416,15 @@ var HEARTBEAT_RULES = `### Heartbeat lifecycle
4333
4416
  <run-monitoring>
4334
4417
  Runs are your agents in the field. You MUST actively track them:
4335
4418
  - **On dispatch**: include a label in \`--meta\` for identification: \`--meta '{"label":"T6-csv-export","ticketId":"YC-42",...}'\`
4336
- - **On completion**: ALWAYS run \`neo runs <runId>\` to read the full output. Parse structured JSON (PR URLs, issues, plans). This is NOT optional \u2014 you cannot decide next steps without reading the output.
4419
+ - **On completion**: ALWAYS run \`neo runs <runId>\` to read the full output. This is NOT optional \u2014 you cannot decide next steps without reading the output.
4337
4420
  - **On failure**: read the output to understand why. Decide: retry (blocked), abandon, or escalate.
4338
4421
  - **Active runs**: check \`neo runs --short --status running\` to verify your runs are still alive. If a run disappeared, investigate.
4339
4422
  </run-monitoring>
4340
4423
 
4341
4424
  <multi-task-initiatives>
4342
- **Branch strategy:** one branch per initiative \u2014 all tasks push to the same branch sequentially (never in parallel). First task creates the branch; open PR after it completes. Later tasks add commits to the same PR. Independent initiatives CAN run in parallel on different branches.
4425
+ **Branch strategy:** one branch per initiative. Architect produces a plan; developer executes all tasks on that branch. Independent initiatives CAN run in parallel on different branches.
4343
4426
 
4344
- **Dispatch quality:** write a detailed \`--prompt\` with acceptance criteria, files to modify, and context from completed sibling tasks (commits, APIs added, files changed). When dispatching task N, summarize what tasks 1..N-1 produced.
4427
+ **Dispatch quality:** when dispatching developer with a plan, include the plan path and any context from completed prior work (PR numbers, APIs added). For direct tasks (no plan), write a detailed \`--prompt\` with acceptance criteria.
4345
4428
 
4346
4429
  **Post-completion:** if agent opened a PR, dispatch \`reviewer\` in parallel with CI (do not wait). Update task outcome with concrete details (PR#, what was done) and update the initiative note.
4347
4430
 
@@ -4430,7 +4513,7 @@ function buildMemoryRulesExamples(supervisorDir) {
4430
4513
  neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1' branch:feat/x (cat ${notesDir}/plan-YC-2670-kanban.md)"
4431
4514
  neo memory write --type fact --scope /repo "main branch uses protected merges \u2014 agents must create PRs, never push directly"
4432
4515
  neo memory write --type fact --scope /repo "pnpm build must pass before push \u2014 CI does not rebuild, run 2g589f34a5a failed without it"
4433
- neo memory write --type procedure --scope /repo "After architect run: parse milestones from JSON output, create one task per milestone with --tags initiative:<name>"
4516
+ neo memory write --type procedure --scope /repo "After architect run: read plan path from output, dispatch developer with plan per SUPERVISOR.md routing"
4434
4517
  neo memory write --type procedure --scope /repo "When developer run fails with ENOSPC: the repo has large fixtures \u2014 use --branch with shallow clone flag"
4435
4518
  neo memory write --type feedback --scope /repo "User wants PR descriptions in French even though code is in English"
4436
4519
  neo memory write --type task --scope /repo --severity high --category "neo runs 2g589f34a5a" --tags "initiative:auth-v2,depends:mem_xyz" "T1: Auth middleware"