@neotx/core 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +70 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1207,12 +1207,16 @@ function isProcessAlive(pid) {
|
|
|
1207
1207
|
try {
|
|
1208
1208
|
process.kill(pid, 0);
|
|
1209
1209
|
return true;
|
|
1210
|
-
} catch {
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
if (error instanceof Error && "code" in error && error.code === "EPERM") {
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1211
1214
|
return false;
|
|
1212
1215
|
}
|
|
1213
1216
|
}
|
|
1214
1217
|
|
|
1215
1218
|
// src/orchestrator/run-store.ts
|
|
1219
|
+
var ORPHAN_GRACE_PERIOD_MS = 3e4;
|
|
1216
1220
|
var RunStore = class {
|
|
1217
1221
|
runsDir;
|
|
1218
1222
|
createdDirs = /* @__PURE__ */ new Set();
|
|
@@ -1281,7 +1285,10 @@ var RunStore = class {
|
|
|
1281
1285
|
const content = await readFile4(filePath, "utf-8");
|
|
1282
1286
|
const run = JSON.parse(content);
|
|
1283
1287
|
if (run.status !== "running") return null;
|
|
1288
|
+
if (run.pid && run.pid === process.pid) return null;
|
|
1284
1289
|
if (run.pid && isProcessAlive(run.pid)) return null;
|
|
1290
|
+
const ageMs = Date.now() - new Date(run.createdAt).getTime();
|
|
1291
|
+
if (ageMs < ORPHAN_GRACE_PERIOD_MS) return null;
|
|
1285
1292
|
run.status = "failed";
|
|
1286
1293
|
run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1287
1294
|
await writeFile2(filePath, JSON.stringify(run, null, 2), "utf-8");
|
|
@@ -2599,6 +2606,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2599
2606
|
workflow: input.workflow,
|
|
2600
2607
|
repo: input.repo,
|
|
2601
2608
|
prompt: input.prompt,
|
|
2609
|
+
pid: process.pid,
|
|
2602
2610
|
status: "running",
|
|
2603
2611
|
steps: {},
|
|
2604
2612
|
createdAt: activeSession.startedAt,
|
|
@@ -2786,6 +2794,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2786
2794
|
workflow: input.workflow,
|
|
2787
2795
|
repo: input.repo,
|
|
2788
2796
|
prompt: input.prompt,
|
|
2797
|
+
pid: process.pid,
|
|
2789
2798
|
branch: taskResult.branch,
|
|
2790
2799
|
status: taskResult.status === "success" ? "completed" : "failed",
|
|
2791
2800
|
steps: taskResult.steps,
|
|
@@ -3605,13 +3614,16 @@ neo memory list --type fact
|
|
|
3605
3614
|
neo log <type> "<message>" # visible in TUI only
|
|
3606
3615
|
\`\`\``;
|
|
3607
3616
|
var HEARTBEAT_RULES = `### Heartbeat lifecycle
|
|
3608
|
-
1.
|
|
3609
|
-
2.
|
|
3610
|
-
3.
|
|
3611
|
-
4.
|
|
3612
|
-
5.
|
|
3617
|
+
1. **Check work queue FIRST** \u2014 if you have pending tasks, work on the next one before looking for new work
|
|
3618
|
+
2. Process incoming events (messages, run completions)
|
|
3619
|
+
3. Follow up on pending work (CI checks, deferred dispatches) with \`neo runs\` or \`gh pr checks\`
|
|
3620
|
+
4. Make decisions and dispatch agents
|
|
3621
|
+
5. Update task status (\`neo memory update <id> --outcome in_progress|done|blocked\`) and log decisions
|
|
3622
|
+
6. Yield \u2014 each heartbeat should take seconds, not minutes
|
|
3613
3623
|
|
|
3614
|
-
|
|
3624
|
+
**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.
|
|
3625
|
+
|
|
3626
|
+
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
3627
|
Completion events arrive at future heartbeats \u2014 react then.
|
|
3616
3628
|
If you deferred work (e.g. "CI pending"), you MUST check it at the next heartbeat.`;
|
|
3617
3629
|
var REPORTING_RULES = `### Reporting
|
|
@@ -3782,56 +3794,71 @@ var DONE_OUTCOMES = /* @__PURE__ */ new Set(["done", "abandoned"]);
|
|
|
3782
3794
|
var MAX_TASKS = 15;
|
|
3783
3795
|
function buildWorkQueueSection(memories) {
|
|
3784
3796
|
const tasks = memories.filter((m) => m.type === "task" && !DONE_OUTCOMES.has(m.outcome ?? ""));
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3797
|
+
const doneCount = countDoneTasks(memories);
|
|
3798
|
+
if (tasks.length === 0) {
|
|
3799
|
+
if (doneCount > 0) {
|
|
3800
|
+
return `Work queue (0 remaining, ${doneCount} done) \u2014 all tasks complete. Pick up new work or wait for events.`;
|
|
3801
|
+
}
|
|
3802
|
+
return "";
|
|
3803
|
+
}
|
|
3804
|
+
const groups = groupTasksByInitiative(tasks);
|
|
3805
|
+
const lines = renderTaskGroups(groups);
|
|
3806
|
+
if (tasks.length > MAX_TASKS) {
|
|
3807
|
+
lines.push(` ... and ${tasks.length - MAX_TASKS} more pending`);
|
|
3808
|
+
}
|
|
3809
|
+
const header = `Work queue (${tasks.length} remaining, ${doneCount} done) \u2014 dispatch the next eligible task:`;
|
|
3810
|
+
return `${header}
|
|
3811
|
+
${lines.join("\n")}`;
|
|
3812
|
+
}
|
|
3813
|
+
function countDoneTasks(memories) {
|
|
3814
|
+
return memories.filter((m) => m.type === "task" && DONE_OUTCOMES.has(m.outcome ?? "")).length;
|
|
3815
|
+
}
|
|
3816
|
+
function groupTasksByInitiative(tasks) {
|
|
3790
3817
|
const initiativeMap = /* @__PURE__ */ new Map();
|
|
3791
3818
|
const noInitiative = [];
|
|
3792
3819
|
for (const task of tasks) {
|
|
3793
|
-
const
|
|
3794
|
-
if (
|
|
3795
|
-
const
|
|
3796
|
-
const group = initiativeMap.get(
|
|
3820
|
+
const tag = task.tags.find((t) => t.startsWith("initiative:"));
|
|
3821
|
+
if (tag) {
|
|
3822
|
+
const key = tag.slice("initiative:".length);
|
|
3823
|
+
const group = initiativeMap.get(key) ?? [];
|
|
3797
3824
|
group.push(task);
|
|
3798
|
-
initiativeMap.set(
|
|
3825
|
+
initiativeMap.set(key, group);
|
|
3799
3826
|
} else {
|
|
3800
3827
|
noInitiative.push(task);
|
|
3801
3828
|
}
|
|
3802
3829
|
}
|
|
3830
|
+
const groups = [];
|
|
3803
3831
|
for (const [initiative, taskList] of initiativeMap) {
|
|
3804
3832
|
groups.push({ initiative, tasks: taskList });
|
|
3805
3833
|
}
|
|
3806
3834
|
if (noInitiative.length > 0) {
|
|
3807
3835
|
groups.push({ initiative: null, tasks: noInitiative });
|
|
3808
3836
|
}
|
|
3837
|
+
return groups;
|
|
3838
|
+
}
|
|
3839
|
+
function renderTaskGroups(groups) {
|
|
3809
3840
|
const lines = [];
|
|
3810
3841
|
let rendered = 0;
|
|
3811
|
-
const remaining = tasks.length;
|
|
3812
3842
|
for (const group of groups) {
|
|
3813
3843
|
if (rendered >= MAX_TASKS) break;
|
|
3814
|
-
if (group.initiative &&
|
|
3844
|
+
if (group.initiative && groups.length > 1) {
|
|
3815
3845
|
lines.push(` [${group.initiative}]`);
|
|
3816
3846
|
}
|
|
3817
3847
|
for (const task of group.tasks) {
|
|
3818
3848
|
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}`);
|
|
3849
|
+
lines.push(` ${formatTaskLine(task)}`);
|
|
3825
3850
|
rendered++;
|
|
3826
3851
|
}
|
|
3827
3852
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3834
|
-
${
|
|
3853
|
+
return lines;
|
|
3854
|
+
}
|
|
3855
|
+
function formatTaskLine(task) {
|
|
3856
|
+
const marker = formatTaskMarker(task.outcome);
|
|
3857
|
+
const severity = task.severity ? `[${task.severity}] ` : "";
|
|
3858
|
+
const scope = task.scope !== "global" ? ` (${getBasename(task.scope)})` : "";
|
|
3859
|
+
const run = task.runId ? ` [run ${task.runId.slice(0, 8)}]` : "";
|
|
3860
|
+
const cat = task.category ? ` \u2192 ${task.category}` : "";
|
|
3861
|
+
return `${marker} ${severity}${task.content}${scope}${run}${cat}`;
|
|
3835
3862
|
}
|
|
3836
3863
|
function formatTaskMarker(outcome) {
|
|
3837
3864
|
switch (outcome) {
|
|
@@ -3911,16 +3938,16 @@ Heartbeat #${opts.heartbeatCount}
|
|
|
3911
3938
|
${COMMANDS}
|
|
3912
3939
|
</commands>`);
|
|
3913
3940
|
const contextParts = [];
|
|
3914
|
-
|
|
3941
|
+
const workQueue = buildWorkQueueSection(opts.memories);
|
|
3942
|
+
if (workQueue) {
|
|
3943
|
+
contextParts.push(workQueue);
|
|
3944
|
+
}
|
|
3915
3945
|
if (opts.activeRuns.length > 0) {
|
|
3916
3946
|
contextParts.push(`Active runs:
|
|
3917
3947
|
${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
|
|
3918
3948
|
}
|
|
3949
|
+
contextParts.push(...buildContextSections(opts));
|
|
3919
3950
|
contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
|
|
3920
|
-
const workQueue = buildWorkQueueSection(opts.memories);
|
|
3921
|
-
if (workQueue) {
|
|
3922
|
-
contextParts.push(workQueue);
|
|
3923
|
-
}
|
|
3924
3951
|
const recentActions = buildRecentActionsSection(opts.recentActions);
|
|
3925
3952
|
if (recentActions) {
|
|
3926
3953
|
contextParts.push(recentActions);
|
|
@@ -3939,7 +3966,7 @@ ${contextParts.join("\n\n")}
|
|
|
3939
3966
|
${opts.customInstructions}`);
|
|
3940
3967
|
}
|
|
3941
3968
|
instructionParts.push(
|
|
3942
|
-
"This is a standard heartbeat. Focus on processing events and dispatching work."
|
|
3969
|
+
"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
3970
|
);
|
|
3944
3971
|
sections.push(`<instructions>
|
|
3945
3972
|
${instructionParts.join("\n\n")}
|
|
@@ -3956,16 +3983,16 @@ Heartbeat #${opts.heartbeatCount} (CONSOLIDATION)
|
|
|
3956
3983
|
${COMMANDS}
|
|
3957
3984
|
</commands>`);
|
|
3958
3985
|
const contextParts = [];
|
|
3959
|
-
|
|
3986
|
+
const workQueueConsolidation = buildWorkQueueSection(opts.memories);
|
|
3987
|
+
if (workQueueConsolidation) {
|
|
3988
|
+
contextParts.push(workQueueConsolidation);
|
|
3989
|
+
}
|
|
3960
3990
|
if (opts.activeRuns.length > 0) {
|
|
3961
3991
|
contextParts.push(`Active runs:
|
|
3962
3992
|
${opts.activeRuns.map((r) => `- ${r}`).join("\n")}`);
|
|
3963
3993
|
}
|
|
3994
|
+
contextParts.push(...buildContextSections(opts));
|
|
3964
3995
|
contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
|
|
3965
|
-
const workQueueConsolidation = buildWorkQueueSection(opts.memories);
|
|
3966
|
-
if (workQueueConsolidation) {
|
|
3967
|
-
contextParts.push(workQueueConsolidation);
|
|
3968
|
-
}
|
|
3969
3996
|
const recentActions = buildRecentActionsSection(opts.recentActions);
|
|
3970
3997
|
if (recentActions) {
|
|
3971
3998
|
contextParts.push(recentActions);
|