@nodeint/migent 0.1.3 → 0.1.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/README.md +57 -41
- package/dist/commands/agent.js +111 -59
- package/dist/commands/config.js +20 -8
- package/dist/commands/execute.d.ts +6 -0
- package/dist/commands/execute.js +30 -14
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +91 -87
- package/dist/commands/prepare.d.ts +1 -0
- package/dist/commands/prepare.js +14 -0
- package/dist/commands/repair.d.ts +1 -0
- package/dist/commands/repair.js +22 -0
- package/dist/commands/service.js +5 -4
- package/dist/commands/sessions.d.ts +24 -1
- package/dist/commands/sessions.js +165 -13
- package/dist/commands/skill.js +51 -32
- package/dist/commands/watch.d.ts +2 -1
- package/dist/commands/watch.js +130 -67
- package/dist/commands/workspace-registry.js +10 -1
- package/dist/display.js +21 -9
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +6 -0
- package/dist/exits.d.ts +10 -0
- package/dist/exits.js +26 -0
- package/dist/follow-log.d.ts +29 -0
- package/dist/follow-log.js +172 -0
- package/dist/fs-utils.d.ts +3 -0
- package/dist/fs-utils.js +26 -0
- package/dist/index.js +5 -1
- package/dist/migrate.js +2 -17
- package/dist/program.js +388 -168
- package/dist/session-row.d.ts +20 -0
- package/dist/session-row.js +21 -0
- package/dist/signals.d.ts +3 -0
- package/dist/signals.js +56 -0
- package/dist/templates/migent/SKILL.md +159 -50
- package/dist/templates/migent/references/schema-config.yaml +35 -0
- package/dist/templates/migent/references/schema-workflow.yaml +61 -0
- package/dist/text.d.ts +4 -0
- package/dist/text.js +13 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/workspace.js +5 -12
- package/package.json +4 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Where to start reading one execution stream.
|
|
5
|
+
* `--cursor` is a byte offset into events.jsonl. Stream lines are selected by timestamp.
|
|
6
|
+
* A stream created after follow starts is not passed here: the caller reads it from offset 0.
|
|
7
|
+
*/
|
|
8
|
+
export function streamFollowStart(cursor, eventsText, streamSize) {
|
|
9
|
+
if (cursor === null)
|
|
10
|
+
return { offset: streamSize, minTimestampMs: null };
|
|
11
|
+
if (cursor === 0)
|
|
12
|
+
return { offset: 0, minTimestampMs: null };
|
|
13
|
+
const anchor = firstCompleteEvent(eventsText, cursor);
|
|
14
|
+
if (anchor === null)
|
|
15
|
+
return { offset: streamSize, minTimestampMs: null };
|
|
16
|
+
const ms = Date.parse(anchor.timestamp);
|
|
17
|
+
return { offset: 0, minTimestampMs: Number.isNaN(ms) ? null : ms };
|
|
18
|
+
}
|
|
19
|
+
export function includeStreamLine(line, minTimestampMs) {
|
|
20
|
+
if (minTimestampMs === null)
|
|
21
|
+
return true;
|
|
22
|
+
const timestamp = eventTimestamp(line);
|
|
23
|
+
if (timestamp === null)
|
|
24
|
+
return true;
|
|
25
|
+
const ms = Date.parse(timestamp);
|
|
26
|
+
if (Number.isNaN(ms))
|
|
27
|
+
return true;
|
|
28
|
+
return ms >= minTimestampMs;
|
|
29
|
+
}
|
|
30
|
+
export function compareFollowLines(left, right) {
|
|
31
|
+
const a = sortKey(left);
|
|
32
|
+
const b = sortKey(right);
|
|
33
|
+
if (a.time !== b.time)
|
|
34
|
+
return a.time - b.time;
|
|
35
|
+
if (a.seq !== b.seq)
|
|
36
|
+
return a.seq - b.seq;
|
|
37
|
+
if (a.source !== b.source)
|
|
38
|
+
return a.source - b.source;
|
|
39
|
+
if (a.path < b.path)
|
|
40
|
+
return -1;
|
|
41
|
+
if (a.path > b.path)
|
|
42
|
+
return 1;
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
/** Lines follow would emit for this cursor, including a stream that appeared after the start. */
|
|
46
|
+
export function replayFollowLines(input) {
|
|
47
|
+
const eventsSize = Buffer.byteLength(input.eventsText);
|
|
48
|
+
const eventOffset = input.cursor === null ? eventsSize : input.cursor;
|
|
49
|
+
const lines = linesFromByte(input.eventsText, eventOffset).map((line) => ({
|
|
50
|
+
line,
|
|
51
|
+
source: "events",
|
|
52
|
+
path: "events.jsonl",
|
|
53
|
+
}));
|
|
54
|
+
for (const stream of input.streams) {
|
|
55
|
+
const size = Buffer.byteLength(stream.text);
|
|
56
|
+
const start = stream.appearedAfterStart
|
|
57
|
+
? { offset: 0, minTimestampMs: null }
|
|
58
|
+
: streamFollowStart(input.cursor, input.eventsText, size);
|
|
59
|
+
for (const line of linesFromByte(stream.text, start.offset)) {
|
|
60
|
+
if (!includeStreamLine(line, start.minTimestampMs))
|
|
61
|
+
continue;
|
|
62
|
+
lines.push({ line, source: "stream", path: stream.path });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
lines.sort(compareFollowLines);
|
|
66
|
+
return lines.map((item) => item.line);
|
|
67
|
+
}
|
|
68
|
+
export function listStreamFiles(sessionDirectory) {
|
|
69
|
+
const root = path.join(sessionDirectory, "executions");
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const files = [];
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
if (!entry.isDirectory() || (entry.name.startsWith(".") && entry.name.endsWith(".tmp")))
|
|
80
|
+
continue;
|
|
81
|
+
const stream = path.join(root, entry.name, "stream.jsonl");
|
|
82
|
+
try {
|
|
83
|
+
if (statSync(stream).isFile())
|
|
84
|
+
files.push(stream);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// This execution has no stream yet.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
files.sort();
|
|
91
|
+
return files;
|
|
92
|
+
}
|
|
93
|
+
function firstCompleteEvent(eventsText, cursor) {
|
|
94
|
+
for (const entry of lineSpans(eventsText)) {
|
|
95
|
+
if (entry.start < cursor)
|
|
96
|
+
continue;
|
|
97
|
+
const timestamp = eventTimestamp(entry.line);
|
|
98
|
+
if (timestamp === null && !isEventLine(entry.line))
|
|
99
|
+
continue;
|
|
100
|
+
if (!isEventLine(entry.line))
|
|
101
|
+
continue;
|
|
102
|
+
return { timestamp: timestamp ?? "" };
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
function isEventLine(line) {
|
|
107
|
+
try {
|
|
108
|
+
const value = JSON.parse(line);
|
|
109
|
+
return isRecord(value) && typeof value.event === "string" && value.event !== "";
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function eventTimestamp(line) {
|
|
116
|
+
try {
|
|
117
|
+
const value = JSON.parse(line);
|
|
118
|
+
if (!isRecord(value) || typeof value.event !== "string")
|
|
119
|
+
return null;
|
|
120
|
+
return typeof value.timestamp === "string" ? value.timestamp : "";
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function sortKey(item) {
|
|
127
|
+
let time = Number.POSITIVE_INFINITY;
|
|
128
|
+
let seq = Number.POSITIVE_INFINITY;
|
|
129
|
+
try {
|
|
130
|
+
const value = JSON.parse(item.line);
|
|
131
|
+
if (isRecord(value)) {
|
|
132
|
+
if (typeof value.timestamp === "string") {
|
|
133
|
+
const parsed = Date.parse(value.timestamp);
|
|
134
|
+
if (!Number.isNaN(parsed))
|
|
135
|
+
time = parsed;
|
|
136
|
+
}
|
|
137
|
+
if (typeof value.seq === "number" && Number.isFinite(value.seq))
|
|
138
|
+
seq = value.seq;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Unparseable lines sort after timed events and stay in source order.
|
|
143
|
+
}
|
|
144
|
+
return { time, seq, source: item.source === "events" ? 0 : 1, path: item.path };
|
|
145
|
+
}
|
|
146
|
+
function linesFromByte(text, offset) {
|
|
147
|
+
const buffer = Buffer.from(text);
|
|
148
|
+
if (offset >= buffer.length || offset < 0)
|
|
149
|
+
return offset < 0 ? linesFromByte(text, 0) : [];
|
|
150
|
+
const slice = buffer.subarray(offset).toString("utf8");
|
|
151
|
+
const parts = slice.split("\n");
|
|
152
|
+
if (!slice.endsWith("\n"))
|
|
153
|
+
parts.pop();
|
|
154
|
+
return parts.filter((line) => line !== "");
|
|
155
|
+
}
|
|
156
|
+
function lineSpans(text) {
|
|
157
|
+
const buffer = Buffer.from(text);
|
|
158
|
+
const spans = [];
|
|
159
|
+
let lineStart = 0;
|
|
160
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
161
|
+
if (buffer[index] !== 0x0a)
|
|
162
|
+
continue;
|
|
163
|
+
const line = buffer.subarray(lineStart, index).toString("utf8");
|
|
164
|
+
if (line !== "")
|
|
165
|
+
spans.push({ start: lineStart, line });
|
|
166
|
+
lineStart = index + 1;
|
|
167
|
+
}
|
|
168
|
+
return spans;
|
|
169
|
+
}
|
|
170
|
+
function isRecord(value) {
|
|
171
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
172
|
+
}
|
package/dist/fs-utils.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
export function exists(filePath) {
|
|
3
|
+
try {
|
|
4
|
+
statSync(filePath);
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function isFile(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
return statSync(filePath).isFile();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function isDirectory(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
return statSync(filePath).isDirectory();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { CommanderError } from "commander";
|
|
5
5
|
import { ConfigurationError } from "@nodeint/migent-kernel";
|
|
6
6
|
import { printError } from "./display.js";
|
|
7
|
+
import { PromptCancelled } from "./errors.js";
|
|
8
|
+
import { EXIT_CANCELLED, EXIT_CONFIG } from "./exits.js";
|
|
7
9
|
import { cliHooks } from "./hooks.js";
|
|
8
10
|
import { createProgram } from "./program.js";
|
|
9
11
|
import { setInvocationCwd } from "./workspace.js";
|
|
@@ -20,9 +22,11 @@ export async function main(argv = process.argv.slice(2), cwd) {
|
|
|
20
22
|
return status.code;
|
|
21
23
|
}
|
|
22
24
|
catch (error) {
|
|
25
|
+
if (error instanceof PromptCancelled)
|
|
26
|
+
return EXIT_CANCELLED;
|
|
23
27
|
if (error instanceof ConfigurationError) {
|
|
24
28
|
printError(error.message);
|
|
25
|
-
return
|
|
29
|
+
return EXIT_CONFIG;
|
|
26
30
|
}
|
|
27
31
|
if (error instanceof CommanderError)
|
|
28
32
|
return error.exitCode === 0 ? 0 : 2;
|
package/dist/migrate.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync, readdirSync,
|
|
1
|
+
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { confirm as clackConfirm, isCancel } from "@clack/prompts";
|
|
4
4
|
import { SCHEMA_VERSION, documentVersion, loadYamlMapping, migrateConfig, migrateWorkflow, patchConfigText, patchWorkflowText, workflowsDirectory, ConfigurationError, } from "@nodeint/migent-kernel";
|
|
5
5
|
import { printError, printNotice } from "./display.js";
|
|
6
|
+
import { isDirectory, isFile } from "./fs-utils.js";
|
|
6
7
|
import { adapterMap } from "./provider.js";
|
|
7
8
|
const OUTDATED = "Schema is outdated. Run `migent migrate` to update.";
|
|
8
9
|
export function requireCurrentSchema(workspace) {
|
|
@@ -173,19 +174,3 @@ function workflowRewrites(workspace, config, adapters) {
|
|
|
173
174
|
}
|
|
174
175
|
return rewrites;
|
|
175
176
|
}
|
|
176
|
-
function isFile(filePath) {
|
|
177
|
-
try {
|
|
178
|
-
return statSync(filePath).isFile();
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function isDirectory(filePath) {
|
|
185
|
-
try {
|
|
186
|
-
return statSync(filePath).isDirectory();
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|