@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.d.ts +11 -1
- package/dist/index.js +252 -272
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
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
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
3585
|
-
|
|
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
|
-
|
|
3594
|
-
-
|
|
3595
|
-
-
|
|
3596
|
-
-
|
|
3597
|
-
-
|
|
3598
|
-
-
|
|
3599
|
-
|
|
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\` |
|
|
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.
|
|
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
|
-
<
|
|
3668
|
-
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
-
|
|
3699
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
- \`--
|
|
3751
|
-
- \`--
|
|
3752
|
-
|
|
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
|
|
3767
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3778
|
-
|
|
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
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
-
|
|
3787
|
-
-
|
|
3788
|
-
-
|
|
3789
|
-
|
|
3790
|
-
Use
|
|
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
|
-
|
|
3793
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
lines
|
|
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
|
|
4082
|
+
return countEvents(opts.grouped) === 0 && opts.activeRuns.length === 0 && !hasWork;
|
|
4025
4083
|
}
|
|
4026
4084
|
function buildIdlePrompt(opts) {
|
|
4027
|
-
return
|
|
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
|
|
4043
|
-
|
|
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
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
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
|
|
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
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
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
|
|
4147
|
-
|
|
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
|
|
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
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
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 {
|