@nodeint/migent 0.1.4 → 0.1.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.
Files changed (48) hide show
  1. package/README.md +194 -138
  2. package/dist/commands/agent.d.ts +8 -1
  3. package/dist/commands/agent.js +381 -56
  4. package/dist/commands/config.js +23 -9
  5. package/dist/commands/execute.d.ts +6 -1
  6. package/dist/commands/execute.js +105 -20
  7. package/dist/commands/init.d.ts +1 -0
  8. package/dist/commands/init.js +70 -63
  9. package/dist/commands/prepare.d.ts +1 -0
  10. package/dist/commands/prepare.js +14 -0
  11. package/dist/commands/repair.d.ts +1 -0
  12. package/dist/commands/repair.js +22 -0
  13. package/dist/commands/service.js +5 -4
  14. package/dist/commands/sessions.d.ts +24 -1
  15. package/dist/commands/sessions.js +165 -13
  16. package/dist/commands/skill.js +58 -32
  17. package/dist/commands/watch.d.ts +2 -1
  18. package/dist/commands/watch.js +130 -67
  19. package/dist/commands/workspace-registry.js +10 -1
  20. package/dist/display.d.ts +1 -0
  21. package/dist/display.js +24 -9
  22. package/dist/errors.d.ts +3 -0
  23. package/dist/errors.js +6 -0
  24. package/dist/exits.d.ts +12 -0
  25. package/dist/exits.js +33 -0
  26. package/dist/follow-log.d.ts +29 -0
  27. package/dist/follow-log.js +172 -0
  28. package/dist/fs-utils.d.ts +3 -0
  29. package/dist/fs-utils.js +26 -0
  30. package/dist/index.js +5 -1
  31. package/dist/migrate.js +2 -17
  32. package/dist/program.js +429 -177
  33. package/dist/session-row.d.ts +20 -0
  34. package/dist/session-row.js +21 -0
  35. package/dist/signals.d.ts +3 -0
  36. package/dist/signals.js +56 -0
  37. package/dist/templates/migent/SKILL.md +163 -56
  38. package/dist/templates/migent/references/config.md +52 -0
  39. package/dist/templates/migent/references/inspect.md +32 -0
  40. package/dist/templates/migent/references/schema-config.yaml +35 -0
  41. package/dist/templates/migent/references/schema-workflow.yaml +71 -0
  42. package/dist/templates/migent/references/sessions.md +65 -0
  43. package/dist/text.d.ts +4 -0
  44. package/dist/text.js +13 -0
  45. package/dist/version.d.ts +1 -0
  46. package/dist/version.js +3 -0
  47. package/dist/workspace.js +5 -12
  48. package/package.json +4 -4
package/dist/display.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
+ import { sessionFields } from "./session-row.js";
2
3
  const require = createRequire(import.meta.url);
3
4
  const pc = require("picocolors");
4
5
  export function printError(message) {
@@ -11,6 +12,9 @@ export function printNotice(message) {
11
12
  const prefix = tty ? pc.cyan(pc.bold("[migent] ")) : "[migent] ";
12
13
  process.stderr.write(`${prefix}${message}\n`);
13
14
  }
15
+ export function printResumeCommand(sessionId) {
16
+ process.stderr.write(`Resume with:\nmigent session continue ${sessionId}\n`);
17
+ }
14
18
  export function printCreated(paths, nextCommand) {
15
19
  process.stdout.write("Created:\n");
16
20
  for (const filePath of paths)
@@ -45,8 +49,8 @@ export function printSessions(rows) {
45
49
  return;
46
50
  }
47
51
  for (const row of rows) {
48
- const [, sessionId, workflowId, status, executionId, decision, outputs, task] = row;
49
- process.stdout.write(`${sessionId ?? ""} ${workflowId ?? ""} ${status ?? ""} ${executionId ?? ""} ${decision ?? ""} ${outputs ?? ""} ${task ?? ""}\n`);
52
+ const fields = sessionFields(row);
53
+ process.stdout.write(`${fields.session} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
50
54
  }
51
55
  }
52
56
  export function printAgent(result, role, provider, model) {
@@ -74,13 +78,24 @@ export function printWatchSnapshot(snapshot) {
74
78
  }
75
79
  if (typeof snapshot.task === "string" && snapshot.task)
76
80
  lines.push(["task", snapshot.task]);
77
- const executionId = snapshot.execution_id;
78
- const executionStatus = snapshot.execution_status ?? snapshot.latest_execution_status;
79
- if (typeof executionId === "string" && executionId) {
80
- const value = typeof executionStatus === "string" && executionStatus
81
- ? `${executionId} · ${executionStatus}`
82
- : executionId;
83
- lines.push(["execution", value]);
81
+ const running = Array.isArray(snapshot.running_stages) ? snapshot.running_stages.filter(isRecord) : [];
82
+ if (running.length > 0) {
83
+ for (const stage of running) {
84
+ const stageId = typeof stage.stage_id === "string" && stage.stage_id ? stage.stage_id : "stage";
85
+ const executionId = typeof stage.execution_id === "string" ? stage.execution_id : "";
86
+ const summary = typeof stage.summary === "string" && stage.summary ? ` · ${stage.summary}` : "";
87
+ lines.push(["running", `${stageId} · ${executionId}${summary}`]);
88
+ }
89
+ }
90
+ else {
91
+ const executionId = snapshot.execution_id;
92
+ const executionStatus = snapshot.execution_status ?? snapshot.latest_execution_status;
93
+ if (typeof executionId === "string" && executionId) {
94
+ const value = typeof executionStatus === "string" && executionStatus
95
+ ? `${executionId} · ${executionStatus}`
96
+ : executionId;
97
+ lines.push(["execution", value]);
98
+ }
84
99
  }
85
100
  const outcome = snapshot.outcome;
86
101
  if (isRecord(outcome) && typeof outcome.status === "string") {
@@ -0,0 +1,3 @@
1
+ export declare class PromptCancelled extends Error {
2
+ constructor();
3
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,6 @@
1
+ export class PromptCancelled extends Error {
2
+ constructor() {
3
+ super("Prompt cancelled.");
4
+ this.name = "PromptCancelled";
5
+ }
6
+ }
@@ -0,0 +1,12 @@
1
+ export declare const EXIT_OK = 0;
2
+ export declare const EXIT_BLOCKED = 1;
3
+ export declare const EXIT_CONFIG = 2;
4
+ export declare const EXIT_STAGE_FAILED = 3;
5
+ export declare const EXIT_QUOTA = 8;
6
+ export declare const EXIT_REVISIONS = 4;
7
+ export declare const EXIT_DISPATCHES = 5;
8
+ export declare const EXIT_VERIFY = 6;
9
+ export declare const EXIT_VERIFY_FAILED = 7;
10
+ export declare const EXIT_CANCELLED = 130;
11
+ export declare const LOOP_EXITS: string[];
12
+ export declare const STAGE_EXITS: string[];
package/dist/exits.js ADDED
@@ -0,0 +1,33 @@
1
+ export const EXIT_OK = 0;
2
+ export const EXIT_BLOCKED = 1;
3
+ export const EXIT_CONFIG = 2;
4
+ export const EXIT_STAGE_FAILED = 3;
5
+ export const EXIT_QUOTA = 8;
6
+ export const EXIT_REVISIONS = 4;
7
+ export const EXIT_DISPATCHES = 5;
8
+ export const EXIT_VERIFY = 6;
9
+ export const EXIT_VERIFY_FAILED = 7;
10
+ export const EXIT_CANCELLED = 130;
11
+ export const LOOP_EXITS = [
12
+ `Exit ${EXIT_OK} when the session is completed.`,
13
+ `Exit ${EXIT_BLOCKED} when the session is blocked. session continue runs that stage again.`,
14
+ `Exit ${EXIT_CONFIG} on a configuration error.`,
15
+ `Exit ${EXIT_STAGE_FAILED} when a stage fails and the command does not wait. The session stays active.`,
16
+ `Exit ${EXIT_QUOTA} when --once or --stage hits a provider quota and does not wait. The session stays active.`,
17
+ `Exit ${EXIT_REVISIONS} when a route would exceed max_revisions. The session stays active.`,
18
+ `Exit ${EXIT_DISPATCHES} when --max-dispatches is spent. The session stays active.`,
19
+ `Exit ${EXIT_VERIFY} when verify fails max_verify times. The session stays active.`,
20
+ `Exit ${EXIT_VERIFY_FAILED} when a stage with no role fails verify. The session stays active.`,
21
+ `Exit ${EXIT_CANCELLED} when cancelled. session continue resumes that execution.`,
22
+ ];
23
+ export const STAGE_EXITS = [
24
+ `Exit ${EXIT_OK} when the stage outcome is complete, or the session is already completed.`,
25
+ `Exit ${EXIT_BLOCKED} when the stage is not complete, or the session is blocked.`,
26
+ `Exit ${EXIT_CONFIG} on a configuration error.`,
27
+ `Exit ${EXIT_STAGE_FAILED} when a non-continue --once or --stage finds a failed stage with no resume marker.`,
28
+ `Exit ${EXIT_QUOTA} when --once or --stage hits a provider quota and does not wait.`,
29
+ `Exit ${EXIT_REVISIONS} when a route would exceed max_revisions. The session stays active.`,
30
+ `Exit ${EXIT_VERIFY} when verify fails max_verify times. The session stays active.`,
31
+ `Exit ${EXIT_VERIFY_FAILED} when a stage with no role fails verify. The session stays active.`,
32
+ `Exit ${EXIT_CANCELLED} when cancelled. session continue resumes that execution.`,
33
+ ];
@@ -0,0 +1,29 @@
1
+ export interface FollowLine {
2
+ line: string;
3
+ source: "events" | "stream";
4
+ path: string;
5
+ }
6
+ export interface StreamFollowStart {
7
+ offset: number;
8
+ /** Skip stream lines whose timestamp parses earlier than this. The floor stays for later polls. */
9
+ minTimestampMs: number | null;
10
+ }
11
+ /**
12
+ * Where to start reading one execution stream.
13
+ * `--cursor` is a byte offset into events.jsonl. Stream lines are selected by timestamp.
14
+ * A stream created after follow starts is not passed here: the caller reads it from offset 0.
15
+ */
16
+ export declare function streamFollowStart(cursor: number | null, eventsText: string, streamSize: number): StreamFollowStart;
17
+ export declare function includeStreamLine(line: string, minTimestampMs: number | null): boolean;
18
+ export declare function compareFollowLines(left: FollowLine, right: FollowLine): number;
19
+ /** Lines follow would emit for this cursor, including a stream that appeared after the start. */
20
+ export declare function replayFollowLines(input: {
21
+ cursor: number | null;
22
+ eventsText: string;
23
+ streams: Array<{
24
+ path: string;
25
+ text: string;
26
+ appearedAfterStart?: boolean;
27
+ }>;
28
+ }): string[];
29
+ export declare function listStreamFiles(sessionDirectory: string): string[];
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export declare function exists(filePath: string): boolean;
2
+ export declare function isFile(filePath: string): boolean;
3
+ export declare function isDirectory(filePath: string): boolean;
@@ -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 2;
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, statSync, writeFileSync } from "node:fs";
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
- }