@nodeint/migent 0.1.5 → 0.1.7

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.
@@ -0,0 +1,77 @@
1
+ import { whichSync } from "@nodeint/migent-kernel";
2
+ import { printProviders } from "../display.js";
3
+ import { cliHooks } from "../hooks.js";
4
+ import { providerMap, loadCatalog, runProviderCommand, } from "../provider.js";
5
+ function mapProviderOption(opt) {
6
+ return {
7
+ name: opt.name,
8
+ prompt: opt.prompt === true,
9
+ allow_default: opt.allowDefault === true,
10
+ overridable: opt.overridable === true,
11
+ };
12
+ }
13
+ function getProviderOptions(provider) {
14
+ const candidate = provider;
15
+ if (typeof candidate.providerOptions === "function") {
16
+ return candidate.providerOptions().map(mapProviderOption);
17
+ }
18
+ return [];
19
+ }
20
+ export async function collectProviders(providers, which, runCommand) {
21
+ // A provider without a default client has no CLI of its own to probe.
22
+ const names = Object.keys(providers).filter((name) => providers[name].requiresClient !== true).sort();
23
+ const rows = [];
24
+ for (const name of names) {
25
+ const provider = providers[name];
26
+ const path = which(provider.command);
27
+ const options = getProviderOptions(provider);
28
+ const problems = [];
29
+ let models = [];
30
+ let default_model = null;
31
+ if (path === null) {
32
+ problems.push(`${provider.command} is not on PATH.`);
33
+ }
34
+ else {
35
+ try {
36
+ const catalog = await loadCatalog(provider, which, runCommand);
37
+ models = [...catalog.ids];
38
+ default_model = catalog.defaultId;
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ problems.push(message);
43
+ }
44
+ }
45
+ rows.push({
46
+ name,
47
+ command: provider.command,
48
+ path,
49
+ available: path !== null,
50
+ models,
51
+ default_model,
52
+ options,
53
+ problems,
54
+ });
55
+ }
56
+ return rows;
57
+ }
58
+ function providerPayload(rows) {
59
+ return {
60
+ ok: rows.every((row) => row.problems.length === 0),
61
+ providers: rows.map((row) => ({ ...row })),
62
+ };
63
+ }
64
+ export async function runProviderList(options = {}) {
65
+ const providers = options.providers ?? providerMap();
66
+ const which = options.which ?? cliHooks.which ?? whichSync;
67
+ const runCommand = options.runCommand ?? cliHooks.runCommand ?? runProviderCommand;
68
+ const rows = await collectProviders(providers, which, runCommand);
69
+ if (options.asJson) {
70
+ process.stdout.write(`${JSON.stringify(providerPayload(rows))}\n`);
71
+ }
72
+ else {
73
+ printProviders(rows);
74
+ }
75
+ const hasAnyModels = rows.some((row) => row.models.length > 0);
76
+ return hasAnyModels ? 0 : 1;
77
+ }
@@ -1,8 +1,5 @@
1
- import { existsSync } from "node:fs";
2
- import path from "node:path";
3
- import { listSessionMetrics, listSessionRows, sessionMetrics, ConfigurationError } from "@nodeint/migent-kernel";
1
+ import { listSealedMetrics, listSessionRows, readSealedMetrics, ConfigurationError } from "@nodeint/migent-kernel";
4
2
  import { printError, printSessions } from "../display.js";
5
- import { adapterMap } from "../provider.js";
6
3
  import { SESSION_COLUMN, sessionFields } from "../session-row.js";
7
4
  import { optionalText } from "../text.js";
8
5
  import { followSession } from "./watch.js";
@@ -36,9 +33,8 @@ export function runSessionMetrics(workspace, session, options) {
36
33
  printError("List filters cannot be combined with a session.");
37
34
  return 2;
38
35
  }
39
- const adapters = adapterMap();
40
36
  if (sessionId === null) {
41
- const rows = listSessionMetrics(workspace, adapters, {
37
+ const rows = listSealedMetrics(workspace, {
42
38
  workflow: selection.workflow ?? null,
43
39
  status: selection.status ?? null,
44
40
  limit: selection.limit ?? null,
@@ -47,12 +43,16 @@ export function runSessionMetrics(workspace, session, options) {
47
43
  process.stdout.write(`${JSON.stringify(row)}\n`);
48
44
  return 0;
49
45
  }
50
- const eventsPath = path.join(workspace, ".migent", "sessions", sessionId, "events.jsonl");
51
- if (!existsSync(eventsPath)) {
52
- printError(`No events found for session: ${sessionId}`);
46
+ const sealed = readSealedMetrics(workspace, sessionId);
47
+ if (sealed.state === "missing") {
48
+ printError(`No metrics for session: ${sessionId}. Metrics are recorded when a workflow session completes.`);
53
49
  return 2;
54
50
  }
55
- process.stdout.write(`${JSON.stringify(sessionMetrics(workspace, sessionId, adapters))}\n`);
51
+ if (sealed.state === "corrupt") {
52
+ printError(`metrics.json for session ${sessionId} is not valid JSON.`);
53
+ return 2;
54
+ }
55
+ process.stdout.write(`${JSON.stringify(sealed.metrics)}\n`);
56
56
  return 0;
57
57
  }
58
58
  export async function runSessions(workspace, session, options) {
@@ -130,6 +130,7 @@ function sessionJson(row) {
130
130
  const fields = sessionFields(row);
131
131
  return {
132
132
  session: fields.session,
133
+ title: fields.title,
133
134
  workflow: fields.workflow,
134
135
  status: fields.status,
135
136
  execution: fields.execution,
@@ -7,7 +7,14 @@ import { isDirectory, isFile } from "../fs-utils.js";
7
7
  const SKILL_RELATIVE = path.join(".agents", "skills", "migent");
8
8
  const STATE_FILENAME = ".migent-template.json";
9
9
  const TEMPLATE_VERSION = 1;
10
- const MANAGED = ["SKILL.md", "references/schema-config.yaml", "references/schema-workflow.yaml"];
10
+ const MANAGED = [
11
+ "SKILL.md",
12
+ "references/schema-config.yaml",
13
+ "references/schema-workflow.yaml",
14
+ "references/sessions.md",
15
+ "references/inspect.md",
16
+ "references/config.md",
17
+ ];
11
18
  export function installSkill(workspace) {
12
19
  const skillDirectory = path.join(workspace, SKILL_RELATIVE);
13
20
  if (existsSync(skillDirectory)) {
package/dist/display.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Check } from "@nodeint/migent-kernel";
2
2
  export declare function printError(message: string): void;
3
3
  export declare function printNotice(message: string): void;
4
+ export declare function printResumeCommand(sessionId: string): void;
4
5
  export declare function printCreated(paths: readonly string[], nextCommand: string): void;
5
6
  export declare function printAlreadyInitialized(): void;
6
7
  export declare function printInitConflicts(paths: readonly string[]): void;
@@ -10,3 +11,19 @@ export declare function printAgent(result: Record<string, unknown>, role: string
10
11
  export declare function printWatchSnapshot(snapshot: Record<string, unknown>): void;
11
12
  export declare function printWatchEvent(event: Record<string, unknown>): void;
12
13
  export declare function formatWatchTimestamp(timestamp: unknown): string;
14
+ export interface ProviderRow {
15
+ name: string;
16
+ command: string;
17
+ path: string | null;
18
+ available: boolean;
19
+ models: string[];
20
+ default_model: string | null;
21
+ options: Array<{
22
+ name: string;
23
+ prompt: boolean;
24
+ allow_default: boolean;
25
+ overridable: boolean;
26
+ }>;
27
+ problems: string[];
28
+ }
29
+ export declare function printProviders(rows: readonly ProviderRow[]): void;
package/dist/display.js CHANGED
@@ -12,6 +12,9 @@ export function printNotice(message) {
12
12
  const prefix = tty ? pc.cyan(pc.bold("[migent] ")) : "[migent] ";
13
13
  process.stderr.write(`${prefix}${message}\n`);
14
14
  }
15
+ export function printResumeCommand(sessionId) {
16
+ process.stderr.write(`Resume with:\nmigent session continue ${sessionId}\n`);
17
+ }
15
18
  export function printCreated(paths, nextCommand) {
16
19
  process.stdout.write("Created:\n");
17
20
  for (const filePath of paths)
@@ -47,7 +50,8 @@ export function printSessions(rows) {
47
50
  }
48
51
  for (const row of rows) {
49
52
  const fields = sessionFields(row);
50
- process.stdout.write(`${fields.session} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
53
+ const identity = fields.title !== "" && fields.title !== fields.session ? `${fields.title} ${fields.session}` : fields.session;
54
+ process.stdout.write(`${identity} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
51
55
  }
52
56
  }
53
57
  export function printAgent(result, role, provider, model) {
@@ -70,6 +74,10 @@ export function printWatchSnapshot(snapshot) {
70
74
  ["session_id", String(snapshot.session_id ?? "--")],
71
75
  ["status", String(snapshot.status ?? "unknown")],
72
76
  ];
77
+ const title = snapshot.title;
78
+ if (typeof title === "string" && title !== "" && title !== lines[0]?.[1]) {
79
+ lines.splice(1, 0, ["title", title]);
80
+ }
73
81
  if (typeof snapshot.workflow_id === "string" && snapshot.workflow_id) {
74
82
  lines.push(["workflow", snapshot.workflow_id]);
75
83
  }
@@ -130,6 +138,40 @@ export function formatWatchTimestamp(timestamp) {
130
138
  return timestamp.trim();
131
139
  return parsed.toISOString().replace("T", " ").replace(/\.\d+Z$/, "+00:00").replace("Z", "+00:00");
132
140
  }
141
+ export function printProviders(rows) {
142
+ const tty = Boolean(process.stdout.isTTY);
143
+ for (const row of rows) {
144
+ const ok = row.problems.length === 0 && row.models.length > 0;
145
+ const status = ok ? (tty ? pc.green("ok") : "ok") : tty ? pc.red("failed") : "failed";
146
+ process.stdout.write(`${row.name.padEnd(12)}${status}\n`);
147
+ if (row.path !== null) {
148
+ const line = ` path: ${row.path}`;
149
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
150
+ }
151
+ else {
152
+ const line = ` path: not found`;
153
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
154
+ }
155
+ if (row.models.length > 0) {
156
+ process.stdout.write(` models:\n`);
157
+ for (const model of row.models) {
158
+ const marked = model === row.default_model ? `${model} (default)` : model;
159
+ const line = ` ${marked}`;
160
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
161
+ }
162
+ }
163
+ if (row.options.length > 0) {
164
+ process.stdout.write(` options:\n`);
165
+ for (const opt of row.options) {
166
+ const line = ` ${opt.name}`;
167
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
168
+ }
169
+ }
170
+ for (const problem of row.problems) {
171
+ process.stdout.write(` ${problem}\n`);
172
+ }
173
+ }
174
+ }
133
175
  function isRecord(value) {
134
176
  return typeof value === "object" && value !== null && !Array.isArray(value);
135
177
  }
package/dist/exits.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export declare const EXIT_OK = 0;
2
2
  export declare const EXIT_BLOCKED = 1;
3
3
  export declare const EXIT_CONFIG = 2;
4
- export declare const EXIT_ATTEMPTS = 3;
4
+ export declare const EXIT_STAGE_FAILED = 3;
5
+ export declare const EXIT_QUOTA = 8;
5
6
  export declare const EXIT_REVISIONS = 4;
6
7
  export declare const EXIT_DISPATCHES = 5;
7
8
  export declare const EXIT_VERIFY = 6;
9
+ export declare const EXIT_VERIFY_FAILED = 7;
8
10
  export declare const EXIT_CANCELLED = 130;
9
11
  export declare const LOOP_EXITS: string[];
10
12
  export declare const STAGE_EXITS: string[];
package/dist/exits.js CHANGED
@@ -1,26 +1,33 @@
1
1
  export const EXIT_OK = 0;
2
2
  export const EXIT_BLOCKED = 1;
3
3
  export const EXIT_CONFIG = 2;
4
- export const EXIT_ATTEMPTS = 3;
4
+ export const EXIT_STAGE_FAILED = 3;
5
+ export const EXIT_QUOTA = 8;
5
6
  export const EXIT_REVISIONS = 4;
6
7
  export const EXIT_DISPATCHES = 5;
7
8
  export const EXIT_VERIFY = 6;
9
+ export const EXIT_VERIFY_FAILED = 7;
8
10
  export const EXIT_CANCELLED = 130;
9
11
  export const LOOP_EXITS = [
10
12
  `Exit ${EXIT_OK} when the session is completed.`,
11
13
  `Exit ${EXIT_BLOCKED} when the session is blocked. session continue runs that stage again.`,
12
14
  `Exit ${EXIT_CONFIG} on a configuration error.`,
13
- `Exit ${EXIT_ATTEMPTS} when --attempts is spent. The session stays active.`,
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.`,
14
17
  `Exit ${EXIT_REVISIONS} when a route would exceed max_revisions. The session stays active.`,
15
18
  `Exit ${EXIT_DISPATCHES} when --max-dispatches is spent. The session stays active.`,
16
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.`,
17
21
  `Exit ${EXIT_CANCELLED} when cancelled. session continue resumes that execution.`,
18
22
  ];
19
23
  export const STAGE_EXITS = [
20
24
  `Exit ${EXIT_OK} when the stage outcome is complete, or the session is already completed.`,
21
25
  `Exit ${EXIT_BLOCKED} when the stage is not complete, or the session is blocked.`,
22
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.`,
23
29
  `Exit ${EXIT_REVISIONS} when a route would exceed max_revisions. The session stays active.`,
24
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.`,
25
32
  `Exit ${EXIT_CANCELLED} when cancelled. session continue resumes that execution.`,
26
33
  ];
package/dist/hooks.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { AgentToolRunner } from "@nodeint/migent-kernel";
2
+ import type { RunCommand } from "./provider.js";
2
3
  /** Test seams. Production leaves both null. */
3
4
  export declare const cliHooks: {
4
5
  which: ((command: string) => string | null) | null;
5
6
  runner: AgentToolRunner | null;
7
+ runCommand: RunCommand | null;
6
8
  };
package/dist/hooks.js CHANGED
@@ -2,4 +2,5 @@
2
2
  export const cliHooks = {
3
3
  which: null,
4
4
  runner: null,
5
+ runCommand: null,
5
6
  };
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export async function main(argv = process.argv.slice(2), cwd) {
36
36
  setInvocationCwd(undefined);
37
37
  cliHooks.which = null;
38
38
  cliHooks.runner = null;
39
+ cliHooks.runCommand = null;
39
40
  }
40
41
  }
41
42
  function isDirectRun() {
package/dist/migrate.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { type MigrationAdapter } from "@nodeint/migent-kernel";
1
+ import { type MigrationProvider } from "@nodeint/migent-kernel";
2
2
  export declare function requireCurrentSchema(workspace: string): void;
3
3
  export declare function runMigrate(workspace: string, options?: {
4
4
  assumeYes?: boolean;
5
5
  confirm?: ((message: string) => boolean | Promise<boolean>) | null;
6
- adapters?: Record<string, MigrationAdapter> | null;
6
+ providers?: Record<string, MigrationProvider> | null;
7
7
  }): Promise<number>;
8
- export declare function migrationAdapters(adapters?: Record<string, unknown>): Record<string, MigrationAdapter>;
8
+ export declare function migrationProviders(providers?: Record<string, unknown>): Record<string, MigrationProvider>;
package/dist/migrate.js CHANGED
@@ -4,29 +4,31 @@ 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
6
  import { isDirectory, isFile } from "./fs-utils.js";
7
- import { adapterMap } from "./provider.js";
8
- const OUTDATED = "Schema is outdated. Run `migent migrate` to update.";
7
+ import { providerMap } from "./provider.js";
8
+ function versionList(entries) {
9
+ const width = Math.max(...entries.map(([label]) => label.length));
10
+ return entries.map(([label, version]) => ` ${label.padEnd(width)} v${version}`).join("\n");
11
+ }
9
12
  export function requireCurrentSchema(workspace) {
10
13
  const newer = [];
11
- let outdated = false;
14
+ const older = [];
12
15
  for (const [label, filePath] of schemaFiles(workspace)) {
13
16
  const version = storedVersion(filePath);
14
17
  if (version === null || version === SCHEMA_VERSION)
15
18
  continue;
16
- if (version > SCHEMA_VERSION) {
17
- newer.push(`${label} schema_version ${version} is newer than this Migent (${SCHEMA_VERSION}).`);
18
- continue;
19
- }
20
- outdated = true;
19
+ (version > SCHEMA_VERSION ? newer : older).push([label, version]);
20
+ }
21
+ if (older.length > 0) {
22
+ throw new ConfigurationError(`Schema is outdated (this Migent uses v${SCHEMA_VERSION}).\n${versionList(older)}\n\n` +
23
+ "Run `migent migrate` to update.");
24
+ }
25
+ if (newer.length > 0) {
26
+ throw new ConfigurationError(`Schema is newer than this Migent supports (v${SCHEMA_VERSION}).\n${versionList(newer)}`);
21
27
  }
22
- if (outdated)
23
- throw new ConfigurationError(OUTDATED);
24
- if (newer.length > 0)
25
- throw new ConfigurationError(newer.join(" "));
26
28
  }
27
29
  export async function runMigrate(workspace, options = {}) {
28
- const adapters = options.adapters ?? migrationAdapters();
29
- const rewrites = pendingRewrites(workspace, adapters);
30
+ const providers = options.providers ?? migrationProviders();
31
+ const rewrites = pendingRewrites(workspace, providers);
30
32
  if (rewrites.length === 0) {
31
33
  process.stdout.write("Schema is current.\n");
32
34
  return 0;
@@ -43,10 +45,10 @@ export async function runMigrate(workspace, options = {}) {
43
45
  process.stdout.write(`Upgraded schema to version ${SCHEMA_VERSION}.\n`);
44
46
  return 0;
45
47
  }
46
- export function migrationAdapters(adapters = adapterMap()) {
48
+ export function migrationProviders(providers = providerMap()) {
47
49
  const mapped = {};
48
- for (const [name, adapter] of Object.entries(adapters)) {
49
- const candidate = adapter;
50
+ for (const [name, provider] of Object.entries(providers)) {
51
+ const candidate = provider;
50
52
  mapped[name] = {
51
53
  modelMigrations: candidate.modelMigrations ?? {},
52
54
  roleMigrations: candidate.roleMigrations ?? {},
@@ -74,17 +76,17 @@ async function accepted(confirm) {
74
76
  }
75
77
  return true;
76
78
  }
77
- function pendingRewrites(workspace, adapters) {
79
+ function pendingRewrites(workspace, providers) {
78
80
  const configPath = path.join(workspace, ".migent", "config.yaml");
79
81
  if (!isFile(configPath))
80
82
  return [];
81
- const [config, configRewrite] = configRewriteOf(configPath, adapters);
83
+ const [config, configRewrite] = configRewriteOf(configPath, providers);
82
84
  if (config === null)
83
85
  return [];
84
86
  const rewrites = [];
85
87
  if (configRewrite !== null)
86
88
  rewrites.push(configRewrite);
87
- rewrites.push(...workflowRewrites(workspace, config, adapters));
89
+ rewrites.push(...workflowRewrites(workspace, config, providers));
88
90
  return rewrites;
89
91
  }
90
92
  function schemaFiles(workspace) {
@@ -109,7 +111,7 @@ function storedVersion(filePath) {
109
111
  return null;
110
112
  }
111
113
  }
112
- function configRewriteOf(filePath, adapters) {
114
+ function configRewriteOf(filePath, providers) {
113
115
  const original = readFileSync(filePath, "utf8");
114
116
  let parsed;
115
117
  try {
@@ -121,7 +123,7 @@ function configRewriteOf(filePath, adapters) {
121
123
  const updated = structuredClone(parsed);
122
124
  let notes;
123
125
  try {
124
- notes = migrateConfig(updated, adapters);
126
+ notes = migrateConfig(updated, providers);
125
127
  }
126
128
  catch (error) {
127
129
  if (error instanceof ConfigurationError)
@@ -136,7 +138,7 @@ function configRewriteOf(filePath, adapters) {
136
138
  const labeled = notes.map((note) => note.startsWith("schema_version") ? `Migrated .migent/config.yaml ${note}` : note);
137
139
  return [updated, { path: filePath, text: rewritten, notes: labeled }];
138
140
  }
139
- function workflowRewrites(workspace, config, adapters) {
141
+ function workflowRewrites(workspace, config, providers) {
140
142
  const directory = workflowsDirectory(workspace);
141
143
  if (!isDirectory(directory))
142
144
  return [];
@@ -156,7 +158,7 @@ function workflowRewrites(workspace, config, adapters) {
156
158
  const updated = structuredClone(parsed);
157
159
  let changed;
158
160
  try {
159
- changed = migrateWorkflow(updated, config, adapters);
161
+ changed = migrateWorkflow(updated, config, providers);
160
162
  }
161
163
  catch (error) {
162
164
  if (error instanceof ConfigurationError)