@neotx/core 0.1.0-alpha.4 → 0.1.0-alpha.5
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 +63 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1213,6 +1213,7 @@ function isProcessAlive(pid) {
|
|
|
1213
1213
|
}
|
|
1214
1214
|
|
|
1215
1215
|
// src/orchestrator/run-store.ts
|
|
1216
|
+
var ORPHAN_GRACE_PERIOD_MS = 3e4;
|
|
1216
1217
|
var RunStore = class {
|
|
1217
1218
|
runsDir;
|
|
1218
1219
|
createdDirs = /* @__PURE__ */ new Set();
|
|
@@ -1282,6 +1283,8 @@ var RunStore = class {
|
|
|
1282
1283
|
const run = JSON.parse(content);
|
|
1283
1284
|
if (run.status !== "running") return null;
|
|
1284
1285
|
if (run.pid && isProcessAlive(run.pid)) return null;
|
|
1286
|
+
const ageMs = Date.now() - new Date(run.createdAt).getTime();
|
|
1287
|
+
if (ageMs < ORPHAN_GRACE_PERIOD_MS) return null;
|
|
1285
1288
|
run.status = "failed";
|
|
1286
1289
|
run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
1290
|
await writeFile2(filePath, JSON.stringify(run, null, 2), "utf-8");
|
|
@@ -3605,13 +3608,16 @@ neo memory list --type fact
|
|
|
3605
3608
|
neo log <type> "<message>" # visible in TUI only
|
|
3606
3609
|
\`\`\``;
|
|
3607
3610
|
var HEARTBEAT_RULES = `### Heartbeat lifecycle
|
|
3608
|
-
1.
|
|
3609
|
-
2.
|
|
3610
|
-
3.
|
|
3611
|
-
4.
|
|
3612
|
-
5.
|
|
3611
|
+
1. **Check work queue FIRST** \u2014 if you have pending tasks, work on the next one before looking for new work
|
|
3612
|
+
2. Process incoming events (messages, run completions)
|
|
3613
|
+
3. Follow up on pending work (CI checks, deferred dispatches) with \`neo runs\` or \`gh pr checks\`
|
|
3614
|
+
4. Make decisions and dispatch agents
|
|
3615
|
+
5. Update task status (\`neo memory update <id> --outcome in_progress|done|blocked\`) and log decisions
|
|
3616
|
+
6. Yield \u2014 each heartbeat should take seconds, not minutes
|
|
3613
3617
|
|
|
3614
|
-
|
|
3618
|
+
**CRITICAL**: Your work queue IS your plan. Do not re-plan work that is already in the queue. When an planner agent produces tasks, create them with \`neo memory write --type task\`, then dispatch them one by one in subsequent heartbeats. Mark each task \`in_progress\` when dispatching, \`done\` when the run completes, \`blocked\` if stuck.
|
|
3619
|
+
|
|
3620
|
+
After dispatching with \`neo run\`, mark the task \`in_progress\`, note the runId in your focus, and yield. Do NOT poll in a loop.
|
|
3615
3621
|
Completion events arrive at future heartbeats \u2014 react then.
|
|
3616
3622
|
If you deferred work (e.g. "CI pending"), you MUST check it at the next heartbeat.`;
|
|
3617
3623
|
var REPORTING_RULES = `### Reporting
|
|
@@ -3782,56 +3788,71 @@ var DONE_OUTCOMES = /* @__PURE__ */ new Set(["done", "abandoned"]);
|
|
|
3782
3788
|
var MAX_TASKS = 15;
|
|
3783
3789
|
function buildWorkQueueSection(memories) {
|
|
3784
3790
|
const tasks = memories.filter((m) => m.type === "task" && !DONE_OUTCOMES.has(m.outcome ?? ""));
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3791
|
+
const doneCount = countDoneTasks(memories);
|
|
3792
|
+
if (tasks.length === 0) {
|
|
3793
|
+
if (doneCount > 0) {
|
|
3794
|
+
return `Work queue (0 remaining, ${doneCount} done) \u2014 all tasks complete. Pick up new work or wait for events.`;
|
|
3795
|
+
}
|
|
3796
|
+
return "";
|
|
3797
|
+
}
|
|
3798
|
+
const groups = groupTasksByInitiative(tasks);
|
|
3799
|
+
const lines = renderTaskGroups(groups);
|
|
3800
|
+
if (tasks.length > MAX_TASKS) {
|
|
3801
|
+
lines.push(` ... and ${tasks.length - MAX_TASKS} more pending`);
|
|
3802
|
+
}
|
|
3803
|
+
const header = `Work queue (${tasks.length} remaining, ${doneCount} done) \u2014 dispatch the next eligible task:`;
|
|
3804
|
+
return `${header}
|
|
3805
|
+
${lines.join("\n")}`;
|
|
3806
|
+
}
|
|
3807
|
+
function countDoneTasks(memories) {
|
|
3808
|
+
return memories.filter((m) => m.type === "task" && DONE_OUTCOMES.has(m.outcome ?? "")).length;
|
|
3809
|
+
}
|
|
3810
|
+
function groupTasksByInitiative(tasks) {
|
|
3790
3811
|
const initiativeMap = /* @__PURE__ */ new Map();
|
|
3791
3812
|
const noInitiative = [];
|
|
3792
3813
|
for (const task of tasks) {
|
|
3793
|
-
const
|
|
3794
|
-
if (
|
|
3795
|
-
const
|
|
3796
|
-
const group = initiativeMap.get(
|
|
3814
|
+
const tag = task.tags.find((t) => t.startsWith("initiative:"));
|
|
3815
|
+
if (tag) {
|
|
3816
|
+
const key = tag.slice("initiative:".length);
|
|
3817
|
+
const group = initiativeMap.get(key) ?? [];
|
|
3797
3818
|
group.push(task);
|
|
3798
|
-
initiativeMap.set(
|
|
3819
|
+
initiativeMap.set(key, group);
|
|
3799
3820
|
} else {
|
|
3800
3821
|
noInitiative.push(task);
|
|
3801
3822
|
}
|
|
3802
3823
|
}
|
|
3824
|
+
const groups = [];
|
|
3803
3825
|
for (const [initiative, taskList] of initiativeMap) {
|
|
3804
3826
|
groups.push({ initiative, tasks: taskList });
|
|
3805
3827
|
}
|
|
3806
3828
|
if (noInitiative.length > 0) {
|
|
3807
3829
|
groups.push({ initiative: null, tasks: noInitiative });
|
|
3808
3830
|
}
|
|
3831
|
+
return groups;
|
|
3832
|
+
}
|
|
3833
|
+
function renderTaskGroups(groups) {
|
|
3809
3834
|
const lines = [];
|
|
3810
3835
|
let rendered = 0;
|
|
3811
|
-
const remaining = tasks.length;
|
|
3812
3836
|
for (const group of groups) {
|
|
3813
3837
|
if (rendered >= MAX_TASKS) break;
|
|
3814
|
-
if (group.initiative &&
|
|
3838
|
+
if (group.initiative && groups.length > 1) {
|
|
3815
3839
|
lines.push(` [${group.initiative}]`);
|
|
3816
3840
|
}
|
|
3817
3841
|
for (const task of group.tasks) {
|
|
3818
3842
|
if (rendered >= MAX_TASKS) break;
|
|
3819
|
-
|
|
3820
|
-
const severity = task.severity ? `[${task.severity}] ` : "";
|
|
3821
|
-
const scopeBasename = task.scope !== "global" ? ` (${getBasename(task.scope)})` : "";
|
|
3822
|
-
const runRef = task.runId ? ` [run ${task.runId.slice(0, 8)}]` : "";
|
|
3823
|
-
const categoryRef = task.category ? ` \u2192 ${task.category}` : "";
|
|
3824
|
-
lines.push(` ${marker} ${severity}${task.content}${scopeBasename}${runRef}${categoryRef}`);
|
|
3843
|
+
lines.push(` ${formatTaskLine(task)}`);
|
|
3825
3844
|
rendered++;
|
|
3826
3845
|
}
|
|
3827
3846
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3834
|
-
${
|
|
3847
|
+
return lines;
|
|
3848
|
+
}
|
|
3849
|
+
function formatTaskLine(task) {
|
|
3850
|
+
const marker = formatTaskMarker(task.outcome);
|
|
3851
|
+
const severity = task.severity ? `[${task.severity}] ` : "";
|
|
3852
|
+
const scope = task.scope !== "global" ? ` (${getBasename(task.scope)})` : "";
|
|
3853
|
+
const run = task.runId ? ` [run ${task.runId.slice(0, 8)}]` : "";
|
|
3854
|
+
const cat = task.category ? ` \u2192 ${task.category}` : "";
|
|
3855
|
+
return `${marker} ${severity}${task.content}${scope}${run}${cat}`;
|
|
3835
3856
|
}
|
|
3836
3857
|
function formatTaskMarker(outcome) {
|
|
3837
3858
|
switch (outcome) {
|
|
@@ -3911,16 +3932,16 @@ Heartbeat #${opts.heartbeatCount}
|
|
|
3911
3932
|
${COMMANDS}
|
|
3912
3933
|
</commands>`);
|
|
3913
3934
|
const contextParts = [];
|
|
3914
|
-
|
|
3935
|
+
const workQueue = buildWorkQueueSection(opts.memories);
|
|
3936
|
+
if (workQueue) {
|
|
3937
|
+
contextParts.push(workQueue);
|
|
3938
|
+
}
|
|
3915
3939
|
if (opts.activeRuns.length > 0) {
|
|
3916
3940
|
contextParts.push(`Active runs:
|
|
3917
3941
|
${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
|
|
3918
3942
|
}
|
|
3943
|
+
contextParts.push(...buildContextSections(opts));
|
|
3919
3944
|
contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
|
|
3920
|
-
const workQueue = buildWorkQueueSection(opts.memories);
|
|
3921
|
-
if (workQueue) {
|
|
3922
|
-
contextParts.push(workQueue);
|
|
3923
|
-
}
|
|
3924
3945
|
const recentActions = buildRecentActionsSection(opts.recentActions);
|
|
3925
3946
|
if (recentActions) {
|
|
3926
3947
|
contextParts.push(recentActions);
|
|
@@ -3939,7 +3960,7 @@ ${contextParts.join("\n\n")}
|
|
|
3939
3960
|
${opts.customInstructions}`);
|
|
3940
3961
|
}
|
|
3941
3962
|
instructionParts.push(
|
|
3942
|
-
"This is a standard heartbeat. Focus on processing events and dispatching work."
|
|
3963
|
+
"This is a standard heartbeat. Focus on processing events and dispatching work. If you have tasks in your work queue, dispatch the next eligible one."
|
|
3943
3964
|
);
|
|
3944
3965
|
sections.push(`<instructions>
|
|
3945
3966
|
${instructionParts.join("\n\n")}
|
|
@@ -3956,16 +3977,16 @@ Heartbeat #${opts.heartbeatCount} (CONSOLIDATION)
|
|
|
3956
3977
|
${COMMANDS}
|
|
3957
3978
|
</commands>`);
|
|
3958
3979
|
const contextParts = [];
|
|
3959
|
-
|
|
3980
|
+
const workQueueConsolidation = buildWorkQueueSection(opts.memories);
|
|
3981
|
+
if (workQueueConsolidation) {
|
|
3982
|
+
contextParts.push(workQueueConsolidation);
|
|
3983
|
+
}
|
|
3960
3984
|
if (opts.activeRuns.length > 0) {
|
|
3961
3985
|
contextParts.push(`Active runs:
|
|
3962
3986
|
${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
|
|
3963
3987
|
}
|
|
3988
|
+
contextParts.push(...buildContextSections(opts));
|
|
3964
3989
|
contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
|
|
3965
|
-
const workQueueConsolidation = buildWorkQueueSection(opts.memories);
|
|
3966
|
-
if (workQueueConsolidation) {
|
|
3967
|
-
contextParts.push(workQueueConsolidation);
|
|
3968
|
-
}
|
|
3969
3990
|
const recentActions = buildRecentActionsSection(opts.recentActions);
|
|
3970
3991
|
if (recentActions) {
|
|
3971
3992
|
contextParts.push(recentActions);
|