@limno/core 0.1.14 → 0.1.16
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.cjs +46 -8
- package/dist/index.js +49 -11
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -458,6 +458,31 @@ function createRunner(root, folder, onState) {
|
|
|
458
458
|
return null;
|
|
459
459
|
}
|
|
460
460
|
};
|
|
461
|
+
const settle = (ids, edited, summary) => {
|
|
462
|
+
const set = readSet();
|
|
463
|
+
if (!set) return;
|
|
464
|
+
const now = Date.now();
|
|
465
|
+
const why = state.status === "stopped" ? "Stopped before it finished." : state.status === "failed" ? state.error ?? `${state.agent ? NAMES[state.agent] : "The agent"} stopped with an error.` : summary ?? "Finished without editing any files.";
|
|
466
|
+
let touched = false;
|
|
467
|
+
for (const c of set.changes) {
|
|
468
|
+
if (!ids.includes(c.id) || c.status !== "in_progress" && c.status !== "queued") continue;
|
|
469
|
+
touched = true;
|
|
470
|
+
c.statusAt = now;
|
|
471
|
+
if (state.status === "done" && edited) c.status = "implemented";
|
|
472
|
+
else {
|
|
473
|
+
c.status = "failed";
|
|
474
|
+
c.agentNote = why;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!touched) return;
|
|
478
|
+
set.updatedAt = now;
|
|
479
|
+
try {
|
|
480
|
+
const tmp = `${changeSetFile}.${process.pid}.tmp`;
|
|
481
|
+
(0, import_node_fs2.writeFileSync)(tmp, JSON.stringify(set, null, 2));
|
|
482
|
+
(0, import_node_fs2.renameSync)(tmp, changeSetFile);
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
};
|
|
461
486
|
let run2 = null;
|
|
462
487
|
let committing = false;
|
|
463
488
|
const say = (line) => console.log(`\x1B[36m[limno]\x1B[0m ${line}`);
|
|
@@ -480,6 +505,7 @@ function createRunner(root, folder, onState) {
|
|
|
480
505
|
message: (0, import_spec.commitMessage)(changes)
|
|
481
506
|
};
|
|
482
507
|
run2 = rec;
|
|
508
|
+
settle(ids, files.length > 0, rec.summary);
|
|
483
509
|
try {
|
|
484
510
|
saveRun(folder, rec);
|
|
485
511
|
} catch {
|
|
@@ -596,7 +622,7 @@ function createRunner(root, folder, onState) {
|
|
|
596
622
|
}
|
|
597
623
|
}
|
|
598
624
|
function args(agent, resume) {
|
|
599
|
-
const server = { command: "npx", args: ["-y", "@limno/mcp", "--root", root] };
|
|
625
|
+
const server = { command: "npx", args: ["-y", "@limno/mcp", "--root", root, "--background"] };
|
|
600
626
|
if (agent === "claude") {
|
|
601
627
|
return [
|
|
602
628
|
"-p",
|
|
@@ -875,6 +901,7 @@ function createUpdater(root) {
|
|
|
875
901
|
var ROUTE = "/__limno";
|
|
876
902
|
var require3 = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
877
903
|
var STALE_MS = 3e4;
|
|
904
|
+
var isPresenceFile = (name) => /^agent(-\d+)?\.json$/.test(name);
|
|
878
905
|
function readJson(file) {
|
|
879
906
|
try {
|
|
880
907
|
return JSON.parse((0, import_node_fs4.readFileSync)(file, "utf8"));
|
|
@@ -909,7 +936,6 @@ function overlayFile() {
|
|
|
909
936
|
function createLimnoService({ root, dir = ".limno" }) {
|
|
910
937
|
const folder = import_node_path5.default.join(root, dir);
|
|
911
938
|
const file = import_node_path5.default.join(folder, "changeset.json");
|
|
912
|
-
const presenceFile = import_node_path5.default.join(folder, "agent.json");
|
|
913
939
|
let lastWrittenHash = "";
|
|
914
940
|
const clients = /* @__PURE__ */ new Set();
|
|
915
941
|
function broadcast(event, data) {
|
|
@@ -925,17 +951,29 @@ data: ${JSON.stringify(data)}
|
|
|
925
951
|
return { command: "npx", args: ["-y", "@limno/mcp", "--root", root], root };
|
|
926
952
|
}
|
|
927
953
|
function agentStatus() {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
954
|
+
let names = [];
|
|
955
|
+
try {
|
|
956
|
+
names = (0, import_node_fs4.readdirSync)(folder).filter(isPresenceFile);
|
|
957
|
+
} catch {
|
|
958
|
+
}
|
|
959
|
+
let best = null;
|
|
960
|
+
for (const name of names) {
|
|
961
|
+
const presence = readJson(import_node_path5.default.join(folder, name));
|
|
962
|
+
if (!presence?.pid) continue;
|
|
963
|
+
let alive = true;
|
|
931
964
|
try {
|
|
932
965
|
process.kill(presence.pid, 0);
|
|
933
966
|
} catch {
|
|
934
967
|
alive = false;
|
|
935
968
|
}
|
|
969
|
+
if (!alive) {
|
|
970
|
+
if (name !== "agent.json") (0, import_node_fs4.rmSync)(import_node_path5.default.join(folder, name), { force: true });
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (Date.now() - (presence.lastSeen ?? 0) >= STALE_MS) continue;
|
|
974
|
+
if (!best || (presence.lastSeen ?? 0) > (best.lastSeen ?? 0)) best = presence;
|
|
936
975
|
}
|
|
937
|
-
|
|
938
|
-
return { connected, client: connected ? presence?.client ?? null : null, lastSeen: presence?.lastSeen ?? null, setup: setupInfo() };
|
|
976
|
+
return { connected: !!best, client: best?.client ?? null, lastSeen: best?.lastSeen ?? null, setup: setupInfo() };
|
|
939
977
|
}
|
|
940
978
|
function installConfig(target) {
|
|
941
979
|
const dest = target === "claude" ? import_node_path5.default.join(root, ".mcp.json") : import_node_path5.default.join(root, ".cursor", "mcp.json");
|
|
@@ -989,7 +1027,7 @@ data: ${JSON.stringify(data)}
|
|
|
989
1027
|
const names = new Set(pending);
|
|
990
1028
|
pending.clear();
|
|
991
1029
|
if (names.has("changeset.json")) onChangeSetFile();
|
|
992
|
-
if (names.
|
|
1030
|
+
if ([...names].some(isPresenceFile)) pushAgent();
|
|
993
1031
|
}, 40);
|
|
994
1032
|
});
|
|
995
1033
|
watcher.unref?.();
|
package/dist/index.js
CHANGED
|
@@ -174,14 +174,14 @@ async function stampTemplate(code, id, relFile, root) {
|
|
|
174
174
|
|
|
175
175
|
// src/service.ts
|
|
176
176
|
import { createHash } from "crypto";
|
|
177
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as
|
|
177
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync, statSync, watch, writeFileSync as writeFileSync4 } from "fs";
|
|
178
178
|
import { createRequire as createRequire3 } from "module";
|
|
179
179
|
import path5 from "path";
|
|
180
180
|
import { emptyChangeSet, mergeStatuses, renderPrompt } from "@limno/spec";
|
|
181
181
|
|
|
182
182
|
// src/runner.ts
|
|
183
183
|
import { spawn as spawn2 } from "child_process";
|
|
184
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
184
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
185
185
|
import os from "os";
|
|
186
186
|
import path3 from "path";
|
|
187
187
|
import { commitMessage, describeChanges } from "@limno/spec";
|
|
@@ -408,6 +408,31 @@ function createRunner(root, folder, onState) {
|
|
|
408
408
|
return null;
|
|
409
409
|
}
|
|
410
410
|
};
|
|
411
|
+
const settle = (ids, edited, summary) => {
|
|
412
|
+
const set = readSet();
|
|
413
|
+
if (!set) return;
|
|
414
|
+
const now = Date.now();
|
|
415
|
+
const why = state.status === "stopped" ? "Stopped before it finished." : state.status === "failed" ? state.error ?? `${state.agent ? NAMES[state.agent] : "The agent"} stopped with an error.` : summary ?? "Finished without editing any files.";
|
|
416
|
+
let touched = false;
|
|
417
|
+
for (const c of set.changes) {
|
|
418
|
+
if (!ids.includes(c.id) || c.status !== "in_progress" && c.status !== "queued") continue;
|
|
419
|
+
touched = true;
|
|
420
|
+
c.statusAt = now;
|
|
421
|
+
if (state.status === "done" && edited) c.status = "implemented";
|
|
422
|
+
else {
|
|
423
|
+
c.status = "failed";
|
|
424
|
+
c.agentNote = why;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (!touched) return;
|
|
428
|
+
set.updatedAt = now;
|
|
429
|
+
try {
|
|
430
|
+
const tmp = `${changeSetFile}.${process.pid}.tmp`;
|
|
431
|
+
writeFileSync2(tmp, JSON.stringify(set, null, 2));
|
|
432
|
+
renameSync2(tmp, changeSetFile);
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
};
|
|
411
436
|
let run2 = null;
|
|
412
437
|
let committing = false;
|
|
413
438
|
const say = (line) => console.log(`\x1B[36m[limno]\x1B[0m ${line}`);
|
|
@@ -430,6 +455,7 @@ function createRunner(root, folder, onState) {
|
|
|
430
455
|
message: commitMessage(changes)
|
|
431
456
|
};
|
|
432
457
|
run2 = rec;
|
|
458
|
+
settle(ids, files.length > 0, rec.summary);
|
|
433
459
|
try {
|
|
434
460
|
saveRun(folder, rec);
|
|
435
461
|
} catch {
|
|
@@ -546,7 +572,7 @@ function createRunner(root, folder, onState) {
|
|
|
546
572
|
}
|
|
547
573
|
}
|
|
548
574
|
function args(agent, resume) {
|
|
549
|
-
const server = { command: "npx", args: ["-y", "@limno/mcp", "--root", root] };
|
|
575
|
+
const server = { command: "npx", args: ["-y", "@limno/mcp", "--root", root, "--background"] };
|
|
550
576
|
if (agent === "claude") {
|
|
551
577
|
return [
|
|
552
578
|
"-p",
|
|
@@ -825,6 +851,7 @@ function createUpdater(root) {
|
|
|
825
851
|
var ROUTE = "/__limno";
|
|
826
852
|
var require3 = createRequire3(import.meta.url);
|
|
827
853
|
var STALE_MS = 3e4;
|
|
854
|
+
var isPresenceFile = (name) => /^agent(-\d+)?\.json$/.test(name);
|
|
828
855
|
function readJson(file) {
|
|
829
856
|
try {
|
|
830
857
|
return JSON.parse(readFileSync4(file, "utf8"));
|
|
@@ -836,7 +863,7 @@ function writeAtomic(file, data) {
|
|
|
836
863
|
mkdirSync4(path5.dirname(file), { recursive: true });
|
|
837
864
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
838
865
|
writeFileSync4(tmp, data);
|
|
839
|
-
|
|
866
|
+
renameSync3(tmp, file);
|
|
840
867
|
}
|
|
841
868
|
function body(req) {
|
|
842
869
|
return new Promise((resolve, reject) => {
|
|
@@ -859,7 +886,6 @@ function overlayFile() {
|
|
|
859
886
|
function createLimnoService({ root, dir = ".limno" }) {
|
|
860
887
|
const folder = path5.join(root, dir);
|
|
861
888
|
const file = path5.join(folder, "changeset.json");
|
|
862
|
-
const presenceFile = path5.join(folder, "agent.json");
|
|
863
889
|
let lastWrittenHash = "";
|
|
864
890
|
const clients = /* @__PURE__ */ new Set();
|
|
865
891
|
function broadcast(event, data) {
|
|
@@ -875,17 +901,29 @@ data: ${JSON.stringify(data)}
|
|
|
875
901
|
return { command: "npx", args: ["-y", "@limno/mcp", "--root", root], root };
|
|
876
902
|
}
|
|
877
903
|
function agentStatus() {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
904
|
+
let names = [];
|
|
905
|
+
try {
|
|
906
|
+
names = readdirSync(folder).filter(isPresenceFile);
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
let best = null;
|
|
910
|
+
for (const name of names) {
|
|
911
|
+
const presence = readJson(path5.join(folder, name));
|
|
912
|
+
if (!presence?.pid) continue;
|
|
913
|
+
let alive = true;
|
|
881
914
|
try {
|
|
882
915
|
process.kill(presence.pid, 0);
|
|
883
916
|
} catch {
|
|
884
917
|
alive = false;
|
|
885
918
|
}
|
|
919
|
+
if (!alive) {
|
|
920
|
+
if (name !== "agent.json") rmSync(path5.join(folder, name), { force: true });
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (Date.now() - (presence.lastSeen ?? 0) >= STALE_MS) continue;
|
|
924
|
+
if (!best || (presence.lastSeen ?? 0) > (best.lastSeen ?? 0)) best = presence;
|
|
886
925
|
}
|
|
887
|
-
|
|
888
|
-
return { connected, client: connected ? presence?.client ?? null : null, lastSeen: presence?.lastSeen ?? null, setup: setupInfo() };
|
|
926
|
+
return { connected: !!best, client: best?.client ?? null, lastSeen: best?.lastSeen ?? null, setup: setupInfo() };
|
|
889
927
|
}
|
|
890
928
|
function installConfig(target) {
|
|
891
929
|
const dest = target === "claude" ? path5.join(root, ".mcp.json") : path5.join(root, ".cursor", "mcp.json");
|
|
@@ -939,7 +977,7 @@ data: ${JSON.stringify(data)}
|
|
|
939
977
|
const names = new Set(pending);
|
|
940
978
|
pending.clear();
|
|
941
979
|
if (names.has("changeset.json")) onChangeSetFile();
|
|
942
|
-
if (names.
|
|
980
|
+
if ([...names].some(isPresenceFile)) pushAgent();
|
|
943
981
|
}, 40);
|
|
944
982
|
});
|
|
945
983
|
watcher.unref?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@limno/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Limno's framework-neutral dev tooling: JSX source stamping, the change-set store and the endpoints the editor talks to.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@babel/parser": "^7.29.0",
|
|
39
39
|
"@babel/traverse": "^7.29.0",
|
|
40
|
-
"@limno/mcp": "0.1.
|
|
41
|
-
"@limno/overlay": "0.1.
|
|
42
|
-
"@limno/spec": "0.1.
|
|
40
|
+
"@limno/mcp": "0.1.9",
|
|
41
|
+
"@limno/overlay": "0.1.16",
|
|
42
|
+
"@limno/spec": "0.1.9",
|
|
43
43
|
"launch-editor": "^2.14.1",
|
|
44
44
|
"magic-string": "^0.30.17"
|
|
45
45
|
},
|