@neotx/core 0.1.0-alpha.14 → 0.1.0-alpha.15

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.js CHANGED
@@ -2082,7 +2082,6 @@ var MemoryStore = class {
2082
2082
  case "createdAt":
2083
2083
  orderBy = "ORDER BY created_at DESC";
2084
2084
  break;
2085
- case "relevance":
2086
2085
  default:
2087
2086
  orderBy = "ORDER BY (access_count * MAX(0, 1.0 - (julianday('now') - julianday(last_accessed_at)) / 60.0)) DESC";
2088
2087
  break;
@@ -3025,12 +3024,12 @@ var Orchestrator = class extends NeoEventEmitter {
3025
3024
  // ─── Private: Supervisor discovery ─────────────────────
3026
3025
  /** Discover running supervisor daemons and return webhook configs for their endpoints. */
3027
3026
  async discoverSupervisorWebhooks() {
3028
- const { readdir: readdir6 } = await import("fs/promises");
3027
+ const { readdir: readdir7 } = await import("fs/promises");
3029
3028
  const supervisorsDir = getSupervisorsDir();
3030
3029
  if (!existsSync6(supervisorsDir)) return [];
3031
3030
  const webhooks = [];
3032
3031
  try {
3033
- const entries = await readdir6(supervisorsDir, { withFileTypes: true });
3032
+ const entries = await readdir7(supervisorsDir, { withFileTypes: true });
3034
3033
  for (const entry of entries) {
3035
3034
  if (!entry.isDirectory()) continue;
3036
3035
  try {
@@ -3282,13 +3281,14 @@ var EventQueue = class {
3282
3281
  /**
3283
3282
  * Drain and group events: deduplicates messages by content,
3284
3283
  * keeps webhooks and run completions separate.
3284
+ * Returns both grouped events AND original raw events for later marking as processed.
3285
3285
  */
3286
3286
  drainAndGroup() {
3287
- const events = this.drain();
3287
+ const rawEvents = this.drain();
3288
3288
  const messageMap = /* @__PURE__ */ new Map();
3289
3289
  const webhooks = [];
3290
3290
  const runCompletions = [];
3291
- for (const event of events) {
3291
+ for (const event of rawEvents) {
3292
3292
  if (event.kind === "message") {
3293
3293
  const key = event.data.text.trim().toLowerCase();
3294
3294
  const existing = messageMap.get(key);
@@ -3304,9 +3304,12 @@ var EventQueue = class {
3304
3304
  }
3305
3305
  }
3306
3306
  return {
3307
- messages: [...messageMap.values()],
3308
- webhooks,
3309
- runCompletions
3307
+ grouped: {
3308
+ messages: [...messageMap.values()],
3309
+ webhooks,
3310
+ runCompletions
3311
+ },
3312
+ rawEvents
3310
3313
  };
3311
3314
  }
3312
3315
  size() {
@@ -3320,8 +3323,7 @@ var EventQueue = class {
3320
3323
  for (const p of [inboxPath, eventsPath]) {
3321
3324
  try {
3322
3325
  await writeFile3(p, "", { flag: "a" });
3323
- } catch (err) {
3324
- console.error(`[EventQueue] Failed to ensure file exists: ${p}`, err);
3326
+ } catch {
3325
3327
  }
3326
3328
  }
3327
3329
  this.watchJsonlFile(inboxPath, "message");
@@ -3371,13 +3373,11 @@ var EventQueue = class {
3371
3373
  watchJsonlFile(filePath, kind) {
3372
3374
  try {
3373
3375
  const watcher = watch(filePath, () => {
3374
- this.readNewLines(filePath, kind).catch((err) => {
3375
- console.error(`[EventQueue] Failed to read new lines from ${filePath}:`, err);
3376
+ this.readNewLines(filePath, kind).catch(() => {
3376
3377
  });
3377
3378
  });
3378
3379
  this.watchers.push(watcher);
3379
- } catch (err) {
3380
- console.error(`[EventQueue] Cannot watch file (may not exist yet): ${filePath}`, err);
3380
+ } catch {
3381
3381
  }
3382
3382
  }
3383
3383
  async readNewLines(filePath, kind) {
@@ -3464,8 +3464,7 @@ var EventQueue = class {
3464
3464
  await writeFile3(filePath, updated.join("\n"), "utf-8");
3465
3465
  this.fileOffsets.set(filePath, updated.join("\n").length);
3466
3466
  }
3467
- } catch (err) {
3468
- console.error(`[EventQueue] Failed to mark events as processed in ${filePath}:`, err);
3467
+ } catch {
3469
3468
  }
3470
3469
  }
3471
3470
  };
@@ -3577,26 +3576,19 @@ async function appendLogBuffer(dir, entry) {
3577
3576
  }
3578
3577
 
3579
3578
  // src/supervisor/prompt-builder.ts
3580
- var ROLE = `You are the neo autonomous supervisor \u2014 the engineering manager your agents deserve.
3581
-
3582
- You don't write code. You make sure the right work happens, at the right time, by the right agent \u2014 and you follow through until it's done.
3579
+ var ROLE = `You are the neo autonomous supervisor \u2014 accountable for delivery across parallel initiatives.
3583
3580
 
3584
- <mindset>
3585
- - You are accountable for delivery. A task in the queue that nobody is working on is YOUR problem.
3586
- - Be the manager you'd want: give agents clear context, check their output, unblock them when they're stuck.
3587
- - Think before dispatching. Read the task context, understand what's needed, craft a prompt that sets the agent up to succeed on the first try.
3588
- - When a run completes, ALWAYS read its output. Verify the result meets the acceptance criteria. If it doesn't, figure out why and act \u2014 re-dispatch with better instructions, file a follow-up, or escalate.
3589
- - When a run fails, diagnose before retrying. Read the output, check if the prompt was unclear, if the branch had conflicts, if the agent hit a known issue. Fix the root cause.
3590
- - Never let work stall silently. If a run has been active too long, check on it. If a task is blocked, find what unblocks it. If nothing is happening, ask why.
3591
- </mindset>
3581
+ You do not write code directly; you ensure the right work is assigned, executed, reviewed, and completed by the right agent.`;
3582
+ var OPERATING_PRINCIPLES = `### Operating principles
3592
3583
 
3593
- <behavioral-contract>
3594
- - Your ONLY visible output is \`neo log\` commands. The TUI shows these and nothing else.
3595
- - Your text output is NEVER shown to anyone \u2014 every token of text is wasted cost.
3596
- - Produce tool calls, not explanations. Do not narrate your reasoning.
3597
- - You NEVER modify code \u2014 that is the agents' job.
3598
- - You can read code in the available repos (path in \`neo repos\` command)
3599
- </behavioral-contract>`;
3584
+ - Own delivery end-to-end: any queued task without an active owner is your responsibility.
3585
+ - Operate like a strong engineering lead: provide clear context, dispatch deliberately, validate outcomes, and remove blockers quickly.
3586
+ - On run completion: read \`neo runs <runId>\`, verify acceptance criteria, then decide next action (done, follow-up, redispatch, escalate).
3587
+ - On run failure: diagnose root cause before retrying (prompt quality, branch conflict, known issue, environment/tooling), then fix the cause.
3588
+ - Prevent silent stalls: monitor long-running jobs, detect blocked work early, and actively unblock.
3589
+ - Keep initiative boundaries strict: decisions for initiative A must not be influenced by unrelated state from B.
3590
+ - Your user-visible channel is \`neo log\` only; produce concise tool calls (not reasoning/explanations) and avoid wasted tokens.
3591
+ - You may inspect repositories available via \`neo repos\`, read-only to launch agents.`;
3600
3592
  var COMMANDS = `### Dispatching agents
3601
3593
  \`\`\`bash
3602
3594
  neo run <agent> --prompt "..." --repo <path> --branch <name> [--priority critical|high|medium|low] [--meta '<json>']
@@ -3607,7 +3599,7 @@ neo run <agent> --prompt "..." --repo <path> --branch <name> [--priority critica
3607
3599
  | \`--prompt\` | always | Task description for the agent |
3608
3600
  | \`--repo\` | always | Target repository path |
3609
3601
  | \`--branch\` | always | Branch name for the isolated clone |
3610
- | \`--priority\` | no | \`critical\`, \`high\`, \`medium\`, \`low\` |
3602
+ | \`--priority\` | optional | \`critical\`, \`high\`, \`medium\`, \`low\` |
3611
3603
  | \`--meta\` | **always** | JSON with \`"label"\` for identification + \`"ticketId"\`, \`"stage"\`, etc. |
3612
3604
 
3613
3605
  All agents require \`--branch\`. Each agent session runs in an isolated clone on that branch.
@@ -3642,8 +3634,7 @@ neo log <type> "<message>" # visible in TUI only
3642
3634
  var COMMANDS_COMPACT = `### Commands (reference)
3643
3635
  \`neo run <agent> --prompt "..." --repo <path> --branch <name> --meta '{"label":"T1-auth",...}'\`
3644
3636
  \`neo runs [--short | <runId>]\` \xB7 \`neo runs --short --status running\` \xB7 \`neo cost --short\`
3645
- \`neo memory write|update|forget|search|list\` \xB7 \`neo log <type> "<msg>"\`
3646
- ALWAYS read run output on completion: \`neo runs <runId>\` \u2014 it contains the agent's structured result.`;
3637
+ \`neo memory write|update|forget|search|list\` \xB7 \`neo log <type> "<msg>"\``;
3647
3638
  var HEARTBEAT_RULES = `### Heartbeat lifecycle
3648
3639
 
3649
3640
  <decision-tree>
@@ -3653,7 +3644,7 @@ var HEARTBEAT_RULES = `### Heartbeat lifecycle
3653
3644
  4. EVENTS? \u2014 process run completions, messages, webhooks. Parse agent JSON output.
3654
3645
  5. FOLLOW-UPS? \u2014 check CI (\`gh pr checks\`), deferred dispatches.
3655
3646
  6. DISPATCH \u2014 route work to agents. Mark tasks \`in_progress\`, add ACTIVE to focus.
3656
- 7. YIELD \u2014 log your decisions and yield. Do not poll. Completions arrive at future heartbeats.
3647
+ 7. SERIALIZE & YIELD \u2014 rewrite focus (see <focus>), log your decisions, and yield. Do not poll.
3657
3648
  </decision-tree>
3658
3649
 
3659
3650
  <run-monitoring>
@@ -3664,43 +3655,15 @@ Runs are your agents in the field. You MUST actively track them:
3664
3655
  - **Active runs**: check \`neo runs --short --status running\` to verify your runs are still alive. If a run disappeared, investigate.
3665
3656
  </run-monitoring>
3666
3657
 
3667
- <orchestration>
3668
- When managing a multi-task initiative (architect decomposition, feature with milestones):
3669
-
3670
- **Branch strategy:**
3671
- - Use ONE branch per initiative: \`feat/YC-2670-kanban-improvements\` \u2014 all tasks in the initiative push commits to this same branch
3672
- - Each agent inherits the previous task's work without needing merges
3673
- - The first task creates the branch. Subsequent tasks reuse it with the same \`--branch\` flag
3674
- - Open the PR after the first task completes. Later tasks push additional commits to the same PR
3675
- - Tasks within an initiative MUST be dispatched sequentially (not in parallel) since they share a branch
3676
- - Independent initiatives CAN run in parallel on different branches
3677
-
3678
- **Before dispatching a task:**
3679
- 1. Run the task's \`--category\` command to retrieve context (architect plan, previous run output)
3680
- 2. Write a detailed \`--prompt\` with: task description, acceptance criteria, files to modify, and context from previous tasks in the initiative
3681
- 3. Include results from completed sibling tasks: what was built, which files were changed, which APIs were added
3682
- 4. Always pass the same \`--branch\` as previous tasks in the initiative
3658
+ <multi-task-initiatives>
3659
+ **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.
3683
3660
 
3684
- **After a run completes:**
3685
- 1. \`neo runs <runId>\` \u2014 read the FULL output, not just status
3686
- 2. Extract: PR URL/number, files changed, test results, any issues
3687
- 3. Verify the output matches the task's acceptance criteria
3688
- 4. If the agent opened a PR: dispatch \`reviewer\` in parallel with CI (do not wait for CI)
3689
- 5. Update the task outcome and log the result with concrete details (PR#, branch, what was done)
3690
- 6. Update the initiative note with the completed milestone
3661
+ **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.
3691
3662
 
3692
- **Cross-task context:**
3693
- - Each task builds on previous ones. When dispatching T6, tell the agent what T1-T5 produced (commits, APIs added, files changed)
3694
- - Store key outputs as facts if they affect future tasks: "T5 added dateRange param to fetchAllFstRecords"
3695
- - Use notes for initiative-level plans: \`cat notes/plan-<initiative>.md\` \u2014 update as tasks complete
3696
- </orchestration>
3663
+ **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.
3697
3664
 
3698
- <rules>
3699
- - Work queue IS your plan. Never re-plan existing tasks.
3700
- - Maximize parallelism: dispatch independent tasks in the same heartbeat.
3701
- - After dispatch: update focus, yield immediately. Do NOT wait for results.
3702
- - Deferred work (CI pending): MUST check at next heartbeat.
3703
- </rules>`;
3665
+ **Memory:** store key outputs as facts if they affect future tasks (e.g. "T5 added dateRange param to fetchAllFstRecords").
3666
+ </multi-task-initiatives>`;
3704
3667
  var REPORTING_RULES = `### Reporting
3705
3668
 
3706
3669
  \`neo log\` is your ONLY visible output. Use telegraphic format.
@@ -3711,19 +3674,14 @@ neo log action "<agent> <repo>:<branch> run:<runId> | <context>"
3711
3674
  neo log discovery "<what> in <where>"
3712
3675
  </log-format>
3713
3676
 
3714
- <examples>
3715
- <example type="good">
3677
+ <examples type="good">
3716
3678
  neo log decision "YC-42 \u2192 developer | clear spec, complexity 3"
3717
3679
  neo log action "developer standards:feat/YC-42-auth run:5900a64a | task T1"
3718
3680
  neo log discovery "CI requires node 20 in api-service"
3719
- </example>
3720
- <example type="bad">
3721
- neo log plan "Good! Now let me check the status and update things accordingly."
3722
- neo log decision "Heartbeat #309: Idle cycle - no action required. All 4 repositories stable."
3723
- neo log action "I've dispatched a developer agent to work on the authentication feature."
3724
- </example>
3725
3681
  </examples>`;
3726
- var MEMORY_RULES_CORE = `### Memory
3682
+ function buildMemoryRulesCore(supervisorDir) {
3683
+ const notesDir = `${supervisorDir}/notes`;
3684
+ return `### Memory
3727
3685
 
3728
3686
  <memory-types>
3729
3687
  | Type | Store when | TTL |
@@ -3736,7 +3694,7 @@ var MEMORY_RULES_CORE = `### Memory
3736
3694
  </memory-types>
3737
3695
 
3738
3696
  <memory-rules>
3739
- - Focus MUST use structured format: ACTIVE/PENDING/WAITING/PROCESSED lines only.
3697
+ - Focus is free-form working memory \u2014 rewrite at end of EVERY heartbeat (see <focus>).
3740
3698
  - NEVER store: file counts, line numbers, completed work details, data available via \`neo runs <id>\`.
3741
3699
  - After PR merge: forget related facts unless they are reusable architectural truths.
3742
3700
  - Pattern escalation: same failure 3+ times \u2192 write a \`procedure\`.
@@ -3744,53 +3702,36 @@ var MEMORY_RULES_CORE = `### Memory
3744
3702
  </memory-rules>
3745
3703
 
3746
3704
  <task-workflow>
3747
- Tasks are your work queue. The work queue section above shows them with markers (\`\u25CB\` pending, \`[ACTIVE]\` in_progress, \`[BLOCKED]\` blocked).
3748
-
3749
- Create a task for any planned work: incoming tickets, architect decompositions, refiner sub-tickets, follow-up actions, CI fixes.
3750
- - \`--severity critical|high|medium|low\` \u2014 dispatch highest severity first
3751
- - \`--tags "initiative:<name>"\` \u2014 groups related tasks (shown as [initiative] headers in queue)
3752
- - \`--tags "depends:mem_<id>"\` \u2014 task cannot start until dependency is done
3753
- - \`--category\` \u2014 **MANDATORY** \u2014 the command to retrieve context for this task (shown as \`\u2192 <command>\` in queue)
3754
-
3755
- **Context retrieval rule**: every task and relevant memory MUST include a way for you to access its source context at a future heartbeat. You are stateless \u2014 without this, you lose the context.
3756
- - Agent output: \`--category "neo runs <runId>"\`
3757
- - Note/plan: \`--category "cat notes/plan-feature.md"\`
3758
- - Notion ticket: \`--category "API-retrieve-a-page <notionPageId>"\`
3759
- - Architect decomposition: \`--category "neo runs <architectRunId>"\` (contains milestones + tasks)
3760
-
3761
- Lifecycle: create \u2192 \`neo memory update <id> --outcome in_progress\` (on dispatch) \u2192 \`done\` (on success) / \`blocked\` (on failure, will retry) / \`abandoned\` (terminal, won't retry)
3762
-
3763
- Dispatch rule: pick the highest-severity task with no unmet dependencies. Dispatch independent tasks in parallel. Before dispatching, run the \`--category\` command to retrieve task context.
3705
+ Queue markers: \u25CB pending \xB7 [ACTIVE] in_progress \xB7 [BLOCKED] blocked.
3706
+ Create tasks for: incoming tickets, architect decompositions, sub-tickets, follow-ups, CI fixes.
3707
+ - \`--tags "initiative:<name>"\` \u2014 groups related tasks
3708
+ - \`--tags "depends:mem_<id>"\` \u2014 blocks until dependency is done
3709
+ - \`--category\` \u2014 retrieval command (MANDATORY). Examples: \`"neo runs <runId>"\` \xB7 \`"cat ${notesDir}/plan-feature.md"\` \xB7 \`"API-retrieve-a-page <notionPageId>"\`
3710
+ Lifecycle: create \u2192 in_progress (on dispatch) \u2192 done | blocked | abandoned
3764
3711
  </task-workflow>
3765
3712
 
3766
- <focus-format>
3767
- ACTIVE: <runId> <agent> "<task>" branch:<name>
3768
- PENDING: <taskId> "<description>" depends:<taskId>
3769
- WAITING: <what> since:HB<N>
3770
- PROCESSED: <runId> \u2192 <outcome> PR#<N>
3771
- </focus-format>
3713
+ <focus>
3714
+ You are stateless between heartbeats. Focus is your scratchpad \u2014 the only thing future-you will read before acting.
3772
3715
 
3773
- <notes>
3774
-
3775
- You have a notes/ directory for rich markdown documents that persist across heartbeats.
3716
+ Write it like a handoff note to yourself: what's happening, what you decided, what to do next, what to watch for. Free-form. No format imposed. The only rule: if you don't write it down, you lose it.
3776
3717
 
3777
- When to use notes:
3778
- - Architect decompositions: save the full plan with milestones, tasks, acceptance criteria, dependency graph
3779
- - Initiative tracking: progress log with completed/pending tasks, PRs merged, blockers
3780
- - Complex debugging: accumulate findings across multiple heartbeats
3781
- - Review checklists: aggregate reviewer feedback across fix/review cycles
3718
+ Rewrite focus at the END of every heartbeat. Never leave it empty after a heartbeat with activity.
3719
+ </focus>
3782
3720
 
3783
- How to use:
3784
- - Write: \`cat > notes/plan-YC-2670-kanban.md << 'EOF' ... EOF\` \u2014 include milestones checklist, acceptance criteria, file paths
3785
- - Read: \`cat notes/plan-YC-2670-kanban.md\` \u2014 retrieve full context at any heartbeat
3786
- - Link to tasks: \`neo memory write --type task --category "cat notes/plan-YC-2670-kanban.md" "M3: UI"\`
3787
- - Update: check off completed milestones, add PR numbers, note blockers after each task completes
3788
- - Cleanup: \`rm notes/plan-*.md\` when the initiative is done
3789
-
3790
- Use notes for every initiative with 3+ tasks. They are your project management tool.
3721
+ <notes>
3722
+ Notes directory: \`${notesDir}/\`
3723
+ Use notes for any initiative with 3+ tasks (persists across heartbeats).
3724
+ - Write: \`cat > ${notesDir}/plan-<initiative>.md << 'EOF' ... EOF\`
3725
+ - Link to tasks: \`--category "cat ${notesDir}/plan-<initiative>.md"\`
3726
+ - Update after each task: check off milestones, add PR numbers, note blockers
3727
+ - Delete when initiative is done
3728
+ Use cases: architect decompositions, initiative tracking, debugging across heartbeats, review checklists.
3791
3729
  </notes>`;
3792
- var MEMORY_RULES_EXAMPLES = `<memory-examples>
3793
- neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1' branch:feat/x (cat notes/plan-YC-2670-kanban.md)"
3730
+ }
3731
+ function buildMemoryRulesExamples(supervisorDir) {
3732
+ const notesDir = `${supervisorDir}/notes`;
3733
+ return `<memory-examples>
3734
+ neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1' branch:feat/x (cat ${notesDir}/plan-YC-2670-kanban.md)"
3794
3735
  neo memory write --type fact --scope /repo "main branch uses protected merges \u2014 agents must create PRs, never push directly"
3795
3736
  neo memory write --type fact --scope /repo "pnpm build must pass before push \u2014 CI does not rebuild, run 2g589f34a5a failed without it"
3796
3737
  neo memory write --type procedure --scope /repo "After architect run: parse milestones from JSON output, create one task per milestone with --tags initiative:<name>"
@@ -3800,10 +3741,89 @@ neo memory write --type task --scope /repo --severity high --category "neo runs
3800
3741
  neo memory update <id> --outcome in_progress|done|blocked|abandoned
3801
3742
  neo memory forget <id>
3802
3743
  </memory-examples>`;
3744
+ }
3745
+ function buildRoleSection(heartbeatCount, label) {
3746
+ const suffix = label ? ` (${label})` : "";
3747
+ return `<role>
3748
+ ${ROLE}
3749
+ Heartbeat #${heartbeatCount}${suffix}
3750
+ </role>`;
3751
+ }
3803
3752
  function getCommandsSection(heartbeatCount) {
3804
3753
  return heartbeatCount <= 3 ? COMMANDS : COMMANDS_COMPACT;
3805
3754
  }
3806
- function buildContextSections(opts) {
3755
+ function buildReferenceSection(heartbeatCount) {
3756
+ return `<reference>
3757
+ ${getCommandsSection(heartbeatCount)}
3758
+ </reference>`;
3759
+ }
3760
+ function buildFocusSection(memories) {
3761
+ const focusEntries = memories.filter((m) => m.type === "focus");
3762
+ if (focusEntries.length > 0) {
3763
+ const lines = focusEntries.map((m) => `- ${m.content}`).join("\n");
3764
+ return `<focus>
3765
+ ${lines}
3766
+ </focus>`;
3767
+ }
3768
+ return "<focus>\n(empty \u2014 use neo memory write --type focus to set working context)\n</focus>";
3769
+ }
3770
+ function buildFullContext(opts) {
3771
+ const parts = [];
3772
+ parts.push(buildFocusSection(opts.memories));
3773
+ const workQueue = buildWorkQueueSection(opts.memories);
3774
+ if (workQueue) {
3775
+ parts.push(workQueue);
3776
+ }
3777
+ if (opts.activeRuns.length > 0) {
3778
+ parts.push(`Active runs:
3779
+ ${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
3780
+ }
3781
+ const recentActions = buildRecentActionsSection(opts.recentActions);
3782
+ if (recentActions) {
3783
+ parts.push(recentActions);
3784
+ }
3785
+ parts.push(buildKnowledgeSection(opts.memories));
3786
+ parts.push(...buildEnvironmentSections(opts));
3787
+ parts.push(`Events:
3788
+ ${buildEventsSection(opts.grouped)}`);
3789
+ return `<context>
3790
+ ${parts.join("\n\n")}
3791
+ </context>`;
3792
+ }
3793
+ function buildCompactionContext(opts) {
3794
+ const parts = [];
3795
+ parts.push(buildFocusSection(opts.memories));
3796
+ parts.push(buildKnowledgeSection(opts.memories));
3797
+ const workQueue = buildWorkQueueSection(opts.memories);
3798
+ if (workQueue) {
3799
+ parts.push(workQueue);
3800
+ }
3801
+ parts.push(...buildEnvironmentSections(opts));
3802
+ return `<context>
3803
+ ${parts.join("\n\n")}
3804
+ </context>`;
3805
+ }
3806
+ function buildBaseInstructions(opts, options) {
3807
+ const parts = [];
3808
+ parts.push(OPERATING_PRINCIPLES);
3809
+ parts.push(HEARTBEAT_RULES);
3810
+ parts.push(REPORTING_RULES);
3811
+ parts.push(buildMemoryRulesCore(opts.supervisorDir));
3812
+ if (options.includeExamples) {
3813
+ parts.push(buildMemoryRulesExamples(opts.supervisorDir));
3814
+ }
3815
+ if (opts.customInstructions) {
3816
+ parts.push(`### Custom instructions
3817
+ ${opts.customInstructions}`);
3818
+ }
3819
+ return parts;
3820
+ }
3821
+ function wrapInstructions(parts) {
3822
+ return `<instructions>
3823
+ ${parts.join("\n\n")}
3824
+ </instructions>`;
3825
+ }
3826
+ function buildEnvironmentSections(opts) {
3807
3827
  const parts = [];
3808
3828
  if (opts.repos.length > 0) {
3809
3829
  const repoList = opts.repos.map((r) => `- ${r.path} (branch: ${r.defaultBranch})`).join("\n");
@@ -3820,22 +3840,11 @@ ${mcpList}`);
3820
3840
  );
3821
3841
  return parts;
3822
3842
  }
3823
- function buildMemorySection(memories, supervisorDir) {
3824
- const focusEntries = memories.filter((m) => m.type === "focus");
3843
+ function buildKnowledgeSection(memories) {
3825
3844
  const factEntries = memories.filter((m) => m.type === "fact");
3826
3845
  const procedureEntries = memories.filter((m) => m.type === "procedure");
3827
3846
  const feedbackEntries = memories.filter((m) => m.type === "feedback");
3828
3847
  const parts = [];
3829
- if (focusEntries.length > 0) {
3830
- const lines = focusEntries.map((m) => `- ${m.content}`).join("\n");
3831
- parts.push(`<focus>
3832
- ${lines}
3833
- </focus>`);
3834
- } else {
3835
- parts.push(
3836
- "<focus>\n(empty \u2014 use neo memory write --type focus to set working context)\n</focus>"
3837
- );
3838
- }
3839
3848
  if (factEntries.length > 0) {
3840
3849
  const byScope = /* @__PURE__ */ new Map();
3841
3850
  for (const m of factEntries) {
@@ -3871,12 +3880,6 @@ ${lines}`);
3871
3880
  parts.push(`Recurring review issues:
3872
3881
  ${lines}`);
3873
3882
  }
3874
- parts.push(`For detailed plans and checklists, use notes:
3875
- \`\`\`bash
3876
- cat > ${supervisorDir}/notes/plan-feature.md << 'EOF'
3877
- <your detailed plan here>
3878
- EOF
3879
- \`\`\``);
3880
3883
  return parts.join("\n\n");
3881
3884
  }
3882
3885
  var DONE_OUTCOMES = /* @__PURE__ */ new Set(["done", "abandoned"]);
@@ -3925,18 +3928,72 @@ function groupTasksByInitiative(tasks) {
3925
3928
  }
3926
3929
  return groups;
3927
3930
  }
3931
+ var SEVERITY_ORDER = {
3932
+ critical: 0,
3933
+ high: 1,
3934
+ medium: 2,
3935
+ low: 3
3936
+ };
3937
+ function bySeverity(a, b) {
3938
+ const aOrder = SEVERITY_ORDER[a.severity ?? "medium"] ?? 2;
3939
+ const bOrder = SEVERITY_ORDER[b.severity ?? "medium"] ?? 2;
3940
+ return aOrder - bOrder;
3941
+ }
3942
+ function partitionTasks(tasks) {
3943
+ const active = [];
3944
+ const blocked = [];
3945
+ const pending = [];
3946
+ for (const t of tasks) {
3947
+ if (t.outcome === "in_progress") active.push(t);
3948
+ else if (t.outcome === "blocked") blocked.push(t);
3949
+ else pending.push(t);
3950
+ }
3951
+ return { active, blocked, pending };
3952
+ }
3953
+ function renderInitiativeSummary(group) {
3954
+ const { active, pending } = partitionTasks(group.tasks);
3955
+ const nextEligible = [...pending].sort(bySeverity)[0];
3956
+ const cat = nextEligible?.category ? ` -> ${nextEligible.category}` : "";
3957
+ const nextLabel = nextEligible ? ` (next: ${nextEligible.content.slice(0, 30)}${nextEligible.content.length > 30 ? "..." : ""} [${nextEligible.severity ?? "medium"}])` : "";
3958
+ return `[${group.initiative}] ${active.length} active, ${pending.length} pending${nextLabel}${cat}`;
3959
+ }
3960
+ function renderCompactInitiative(group, lines, rendered) {
3961
+ lines.push(` ${renderInitiativeSummary(group)}`);
3962
+ const { active, blocked, pending } = partitionTasks(group.tasks);
3963
+ const nextEligible = [...pending].sort(bySeverity)[0];
3964
+ for (const task of [...active, ...blocked]) {
3965
+ if (rendered >= MAX_TASKS) break;
3966
+ lines.push(` ${formatTaskLine(task)}`);
3967
+ rendered++;
3968
+ }
3969
+ if (nextEligible && active.length === 0 && blocked.length === 0 && rendered < MAX_TASKS) {
3970
+ lines.push(` ${formatTaskLine(nextEligible)}`);
3971
+ rendered++;
3972
+ }
3973
+ return rendered;
3974
+ }
3975
+ function renderFlatGroup(group, showHeader, lines, rendered) {
3976
+ if (showHeader && group.initiative) {
3977
+ lines.push(` [${group.initiative}]`);
3978
+ }
3979
+ for (const task of group.tasks) {
3980
+ if (rendered >= MAX_TASKS) break;
3981
+ lines.push(` ${formatTaskLine(task)}`);
3982
+ rendered++;
3983
+ }
3984
+ return rendered;
3985
+ }
3928
3986
  function renderTaskGroups(groups) {
3929
3987
  const lines = [];
3930
3988
  let rendered = 0;
3931
3989
  for (const group of groups) {
3932
3990
  if (rendered >= MAX_TASKS) break;
3933
- if (group.initiative && groups.length > 1) {
3934
- lines.push(` [${group.initiative}]`);
3935
- }
3936
- for (const task of group.tasks) {
3937
- if (rendered >= MAX_TASKS) break;
3938
- lines.push(` ${formatTaskLine(task)}`);
3939
- rendered++;
3991
+ const useCompactMode = group.initiative && group.tasks.length >= 3;
3992
+ if (useCompactMode) {
3993
+ rendered = renderCompactInitiative(group, lines, rendered);
3994
+ } else {
3995
+ const showHeader = group.initiative !== null && groups.length > 1;
3996
+ rendered = renderFlatGroup(group, showHeader, lines, rendered);
3940
3997
  }
3941
3998
  }
3942
3999
  return lines;
@@ -4017,17 +4074,15 @@ ${JSON.stringify(event.data.payload ?? {}, null, 2)}
4017
4074
  return `Internal event: ${event.eventKind}`;
4018
4075
  }
4019
4076
  }
4077
+ function countEvents(grouped) {
4078
+ return grouped.messages.length + grouped.webhooks.length + grouped.runCompletions.length;
4079
+ }
4020
4080
  function isIdleHeartbeat(opts) {
4021
- const { messages, webhooks, runCompletions } = opts.grouped;
4022
- const totalEvents = messages.length + webhooks.length + runCompletions.length;
4023
4081
  const hasWork = buildWorkQueueSection(opts.memories) !== "";
4024
- return totalEvents === 0 && opts.activeRuns.length === 0 && !hasWork;
4082
+ return countEvents(opts.grouped) === 0 && opts.activeRuns.length === 0 && !hasWork;
4025
4083
  }
4026
4084
  function buildIdlePrompt(opts) {
4027
- return `<role>
4028
- ${ROLE}
4029
- Heartbeat #${opts.heartbeatCount}
4030
- </role>
4085
+ return `${buildRoleSection(opts.heartbeatCount)}
4031
4086
 
4032
4087
  <context>
4033
4088
  No events. No active runs. No pending tasks.
@@ -4039,90 +4094,20 @@ Nothing to do. Run \`neo log discovery "idle"\` and yield. Do not produce any ot
4039
4094
  </directive>`;
4040
4095
  }
4041
4096
  function buildStandardPrompt(opts) {
4042
- const sections = [];
4043
- sections.push(`<role>
4044
- ${ROLE}
4045
- Heartbeat #${opts.heartbeatCount}
4046
- </role>`);
4047
- const contextParts = [];
4048
- const workQueue = buildWorkQueueSection(opts.memories);
4049
- if (workQueue) {
4050
- contextParts.push(workQueue);
4051
- }
4052
- if (opts.activeRuns.length > 0) {
4053
- contextParts.push(`Active runs:
4054
- ${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
4055
- }
4056
- contextParts.push(...buildContextSections(opts));
4057
- contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
4058
- const recentActions = buildRecentActionsSection(opts.recentActions);
4059
- if (recentActions) {
4060
- contextParts.push(recentActions);
4061
- }
4062
- contextParts.push(`Events:
4063
- ${buildEventsSection(opts.grouped)}`);
4064
- sections.push(`<context>
4065
- ${contextParts.join("\n\n")}
4066
- </context>`);
4067
- sections.push(`<reference>
4068
- ${getCommandsSection(opts.heartbeatCount)}
4069
- </reference>`);
4070
- const instructionParts = [];
4071
- instructionParts.push(HEARTBEAT_RULES);
4072
- instructionParts.push(REPORTING_RULES);
4073
- instructionParts.push(MEMORY_RULES_CORE);
4074
- if (opts.customInstructions) {
4075
- instructionParts.push(`### Custom instructions
4076
- ${opts.customInstructions}`);
4077
- }
4078
- const { messages, webhooks, runCompletions } = opts.grouped;
4079
- const hasEvents = messages.length + webhooks.length + runCompletions.length > 0;
4097
+ const instructionParts = buildBaseInstructions(opts, { includeExamples: false });
4098
+ const hasEvents = countEvents(opts.grouped) > 0;
4080
4099
  instructionParts.push(
4081
4100
  hasEvents ? "Process events, dispatch eligible work, yield. Each heartbeat costs ~$0.10 \u2014 be efficient." : "No events. If pending work exists, dispatch it. Otherwise yield immediately."
4082
4101
  );
4083
- sections.push(`<instructions>
4084
- ${instructionParts.join("\n\n")}
4085
- </instructions>`);
4086
- return sections.join("\n\n");
4102
+ return [
4103
+ buildRoleSection(opts.heartbeatCount),
4104
+ buildFullContext(opts),
4105
+ buildReferenceSection(opts.heartbeatCount),
4106
+ wrapInstructions(instructionParts)
4107
+ ].join("\n\n");
4087
4108
  }
4088
4109
  function buildConsolidationPrompt(opts) {
4089
- const sections = [];
4090
- sections.push(`<role>
4091
- ${ROLE}
4092
- Heartbeat #${opts.heartbeatCount} (CONSOLIDATION)
4093
- </role>`);
4094
- const contextParts = [];
4095
- const workQueueConsolidation = buildWorkQueueSection(opts.memories);
4096
- if (workQueueConsolidation) {
4097
- contextParts.push(workQueueConsolidation);
4098
- }
4099
- if (opts.activeRuns.length > 0) {
4100
- contextParts.push(`Active runs:
4101
- ${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
4102
- }
4103
- contextParts.push(...buildContextSections(opts));
4104
- contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
4105
- const recentActions = buildRecentActionsSection(opts.recentActions);
4106
- if (recentActions) {
4107
- contextParts.push(recentActions);
4108
- }
4109
- contextParts.push(`Events:
4110
- ${buildEventsSection(opts.grouped)}`);
4111
- sections.push(`<context>
4112
- ${contextParts.join("\n\n")}
4113
- </context>`);
4114
- sections.push(`<reference>
4115
- ${getCommandsSection(opts.heartbeatCount)}
4116
- </reference>`);
4117
- const instructionParts = [];
4118
- instructionParts.push(HEARTBEAT_RULES);
4119
- instructionParts.push(REPORTING_RULES);
4120
- instructionParts.push(MEMORY_RULES_CORE);
4121
- instructionParts.push(MEMORY_RULES_EXAMPLES);
4122
- if (opts.customInstructions) {
4123
- instructionParts.push(`### Custom instructions
4124
- ${opts.customInstructions}`);
4125
- }
4110
+ const instructionParts = buildBaseInstructions(opts, { includeExamples: true });
4126
4111
  instructionParts.push(
4127
4112
  `### Consolidation
4128
4113
  This is a CONSOLIDATION heartbeat.
@@ -4137,39 +4122,16 @@ If there IS active work, your job:
4137
4122
  4. **Prune completed work** \u2014 if a PR is merged or an initiative is done, forget related facts that are no longer actionable. Keep only reusable architectural truths.
4138
4123
  5. **Prune done tasks** \u2014 forget tasks with outcome \`done\` or \`abandoned\` older than 7 days.`
4139
4124
  );
4140
- sections.push(`<instructions>
4141
- ${instructionParts.join("\n\n")}
4142
- </instructions>`);
4143
- return sections.join("\n\n");
4125
+ return [
4126
+ buildRoleSection(opts.heartbeatCount, "CONSOLIDATION"),
4127
+ buildFullContext(opts),
4128
+ buildReferenceSection(opts.heartbeatCount),
4129
+ wrapInstructions(instructionParts)
4130
+ ].join("\n\n");
4144
4131
  }
4145
4132
  function buildCompactionPrompt(opts) {
4146
- const sections = [];
4147
- sections.push(`<role>
4148
- ${ROLE}
4149
- Heartbeat #${opts.heartbeatCount} (COMPACTION)
4150
- </role>`);
4151
- const contextParts = [];
4152
- contextParts.push(...buildContextSections(opts));
4153
- contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
4154
- const workQueueCompaction = buildWorkQueueSection(opts.memories);
4155
- if (workQueueCompaction) {
4156
- contextParts.push(workQueueCompaction);
4157
- }
4158
- sections.push(`<context>
4159
- ${contextParts.join("\n\n")}
4160
- </context>`);
4161
- sections.push(`<reference>
4162
- ${getCommandsSection(opts.heartbeatCount)}
4163
- </reference>`);
4164
- const instructionParts = [];
4165
- instructionParts.push(HEARTBEAT_RULES);
4166
- instructionParts.push(REPORTING_RULES);
4167
- instructionParts.push(MEMORY_RULES_CORE);
4168
- instructionParts.push(MEMORY_RULES_EXAMPLES);
4169
- if (opts.customInstructions) {
4170
- instructionParts.push(`### Custom instructions
4171
- ${opts.customInstructions}`);
4172
- }
4133
+ const notesDir = `${opts.supervisorDir}/notes`;
4134
+ const instructionParts = buildBaseInstructions(opts, { includeExamples: true });
4173
4135
  instructionParts.push(`### Compaction
4174
4136
  This is a COMPACTION heartbeat. Deep-clean your ENTIRE memory.
4175
4137
 
@@ -4179,7 +4141,7 @@ This is a COMPACTION heartbeat. Deep-clean your ENTIRE memory.
4179
4141
  4. **Merge duplicates** \u2014 combine similar facts within the same scope into one.
4180
4142
  5. **Clean up focus** \u2014 forget resolved items, rewrite remaining in structured format.
4181
4143
  6. **Prune done tasks** \u2014 forget tasks with outcome \`done\` or \`abandoned\` older than 7 days.
4182
- 7. **Delete completed notes** from notes/ directory.
4144
+ 7. **Delete completed notes** from \`${notesDir}/\` directory.
4183
4145
  8. **Stay under 15 facts per scope** \u2014 prioritize facts that affect dispatch decisions.
4184
4146
 
4185
4147
  Flag contradictions: if two facts contradict, keep the newer one.
@@ -4188,10 +4150,12 @@ Flag contradictions: if two facts contradict, keep the newer one.
4188
4150
  neo memory list --type fact
4189
4151
  neo memory forget <stale-id>
4190
4152
  \`\`\``);
4191
- sections.push(`<instructions>
4192
- ${instructionParts.join("\n\n")}
4193
- </instructions>`);
4194
- return sections.join("\n\n");
4153
+ return [
4154
+ buildRoleSection(opts.heartbeatCount, "COMPACTION"),
4155
+ buildCompactionContext(opts),
4156
+ buildReferenceSection(opts.heartbeatCount),
4157
+ wrapInstructions(instructionParts)
4158
+ ].join("\n\n");
4195
4159
  }
4196
4160
 
4197
4161
  // src/supervisor/heartbeat.ts
@@ -4218,6 +4182,7 @@ var HeartbeatLoop = class {
4218
4182
  sessionId;
4219
4183
  eventQueue;
4220
4184
  activityLog;
4185
+ _eventsPath;
4221
4186
  customInstructions;
4222
4187
  defaultInstructionsPath;
4223
4188
  memoryStore = null;
@@ -4229,9 +4194,14 @@ var HeartbeatLoop = class {
4229
4194
  this.sessionId = options.sessionId;
4230
4195
  this.eventQueue = options.eventQueue;
4231
4196
  this.activityLog = options.activityLog;
4197
+ this._eventsPath = options.eventsPath;
4232
4198
  this.defaultInstructionsPath = options.defaultInstructionsPath;
4233
4199
  this.memoryDbPath = options.memoryDbPath;
4234
4200
  }
4201
+ /** Path to the inbox/events directory for markProcessed() calls */
4202
+ get eventsPath() {
4203
+ return this._eventsPath;
4204
+ }
4235
4205
  getMemoryStore() {
4236
4206
  if (!this.memoryStore && this.memoryDbPath) {
4237
4207
  try {
@@ -4283,7 +4253,7 @@ var HeartbeatLoop = class {
4283
4253
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4284
4254
  const budgetCheck = await this.checkBudgetExceeded(state, today);
4285
4255
  if (budgetCheck.exceeded) return;
4286
- const grouped = this.eventQueue.drainAndGroup();
4256
+ const { grouped, rawEvents } = this.eventQueue.drainAndGroup();
4287
4257
  const totalEventCount = grouped.messages.length + grouped.webhooks.length + grouped.runCompletions.length;
4288
4258
  const activeRuns = await this.getActiveRuns();
4289
4259
  const skipResult = await this.handleSkipLogic({
@@ -4320,6 +4290,10 @@ var HeartbeatLoop = class {
4320
4290
  }
4321
4291
  );
4322
4292
  const { costUsd, turnCount } = await this.callSdk(prompt, heartbeatId);
4293
+ if (rawEvents.length > 0) {
4294
+ const inboxPath = path14.join(this.supervisorDir, "inbox.jsonl");
4295
+ await this.eventQueue.markProcessed(inboxPath, this.eventsPath, rawEvents);
4296
+ }
4323
4297
  if (modeResult.isConsolidation) {
4324
4298
  const allIds = modeResult.unconsolidated.map((e) => e.id);
4325
4299
  if (allIds.length > 0) {
@@ -4894,6 +4868,7 @@ var SupervisorDaemon = class {
4894
4868
  sessionId: this.sessionId,
4895
4869
  eventQueue: this.eventQueue,
4896
4870
  activityLog: this.activityLog,
4871
+ eventsPath,
4897
4872
  defaultInstructionsPath: this.defaultInstructionsPath
4898
4873
  });
4899
4874
  await this.heartbeatLoop.start();
@@ -4949,6 +4924,11 @@ var SupervisorDaemon = class {
4949
4924
  }
4950
4925
  };
4951
4926
 
4927
+ // src/supervisor/shutdown.ts
4928
+ import { existsSync as existsSync9 } from "fs";
4929
+ import { readdir as readdir6, readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
4930
+ import path16 from "path";
4931
+
4952
4932
  // src/index.ts
4953
4933
  var VERSION = "0.1.0";
4954
4934
  export {