@procedures/node 0.0.0-stage → 0.2.0

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 (55) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +342 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/index.d.ts +11 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +14 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/module.d.ts +19 -0
  10. package/dist/module.d.ts.map +1 -0
  11. package/dist/module.js +97 -0
  12. package/dist/module.js.map +1 -0
  13. package/dist/procedures/node/index.d.ts +5 -0
  14. package/dist/procedures/node/index.d.ts.map +1 -0
  15. package/dist/procedures/node/index.js +5 -0
  16. package/dist/procedures/node/index.js.map +1 -0
  17. package/dist/procedures/node/kill.d.ts +11 -0
  18. package/dist/procedures/node/kill.d.ts.map +1 -0
  19. package/dist/procedures/node/kill.js +25 -0
  20. package/dist/procedures/node/kill.js.map +1 -0
  21. package/dist/procedures/node/run.d.ts +18 -0
  22. package/dist/procedures/node/run.d.ts.map +1 -0
  23. package/dist/procedures/node/run.js +48 -0
  24. package/dist/procedures/node/run.js.map +1 -0
  25. package/dist/procedures/node/spawn.d.ts +14 -0
  26. package/dist/procedures/node/spawn.d.ts.map +1 -0
  27. package/dist/procedures/node/spawn.js +35 -0
  28. package/dist/procedures/node/spawn.js.map +1 -0
  29. package/dist/procedures/node/status.d.ts +11 -0
  30. package/dist/procedures/node/status.d.ts.map +1 -0
  31. package/dist/procedures/node/status.js +17 -0
  32. package/dist/procedures/node/status.js.map +1 -0
  33. package/dist/process-manager.d.ts +50 -0
  34. package/dist/process-manager.d.ts.map +1 -0
  35. package/dist/process-manager.js +92 -0
  36. package/dist/process-manager.js.map +1 -0
  37. package/dist/register.d.ts +7 -0
  38. package/dist/register.d.ts.map +1 -0
  39. package/dist/register.js +9 -0
  40. package/dist/register.js.map +1 -0
  41. package/dist/types.d.ts +83 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +90 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +77 -3
  46. package/src/index.ts +17 -0
  47. package/src/module.ts +121 -0
  48. package/src/procedures/node/index.ts +4 -0
  49. package/src/procedures/node/kill.ts +30 -0
  50. package/src/procedures/node/run.ts +56 -0
  51. package/src/procedures/node/spawn.ts +39 -0
  52. package/src/procedures/node/status.ts +23 -0
  53. package/src/process-manager.ts +102 -0
  54. package/src/register.ts +11 -0
  55. package/src/types.ts +184 -0
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * client-node - Node.js process management procedures
3
+ */
4
+
5
+ export * from "./module.js";
6
+
7
+ // Export types
8
+ export * from "./types.js";
9
+
10
+ // Export procedures
11
+ export { nodeRun } from "./procedures/node/run.js";
12
+ export { nodeSpawn } from "./procedures/node/spawn.js";
13
+ export { nodeKill } from "./procedures/node/kill.js";
14
+ export { nodeStatus } from "./procedures/node/status.js";
15
+
16
+ // Export process manager
17
+ export { processManager } from "./process-manager.js";
package/src/module.ts ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Module (no side effects): the procedures of this package as data, for `createRegistry()`.
3
+ *
4
+ * Procedure Registration for Node.js process management
5
+ */
6
+
7
+ import { createProcedure, zodAdapter, defineModule, type ProcedureModule, PROCEDURE_REGISTRY, registerModules, type ProcedureRegistry } from "@procedures/client";
8
+ import { nodeRun } from "./procedures/node/run.js";
9
+ import { nodeSpawn } from "./procedures/node/spawn.js";
10
+ import { nodeKill } from "./procedures/node/kill.js";
11
+ import { nodeStatus } from "./procedures/node/status.js";
12
+ import {
13
+ NodeRunInputSchema,
14
+ NodeSpawnInputSchema,
15
+ NodeKillInputSchema,
16
+ NodeStatusInputSchema,
17
+ type NodeRunInput,
18
+ type NodeRunOutput,
19
+ type NodeSpawnInput,
20
+ type NodeSpawnOutput,
21
+ type NodeKillInput,
22
+ type NodeKillOutput,
23
+ type NodeStatusInput,
24
+ type NodeStatusOutput,
25
+ NodeRunOutputSchema,
26
+ NodeSpawnOutputSchema,
27
+ NodeKillOutputSchema,
28
+ NodeStatusOutputSchema,
29
+ } from "./types.js";
30
+
31
+ // Procedure definitions
32
+ const nodeRunProcedure = createProcedure()
33
+ .path(["node", "run"])
34
+ .input(zodAdapter<NodeRunInput>(NodeRunInputSchema))
35
+ .output(zodAdapter<NodeRunOutput>(NodeRunOutputSchema))
36
+ .meta({
37
+ description: "Run a Node.js script and wait for completion",
38
+ args: ["script"],
39
+ shorts: { cwd: "C", timeout: "t" },
40
+ output: "json",
41
+ // The caller chooses the script, and the script can do anything
42
+ effects: { reads: ["process"], writes: ["process"] },
43
+ })
44
+ .handler(async (input: NodeRunInput, ctx): Promise<NodeRunOutput> => {
45
+ return nodeRun(input, ctx);
46
+ })
47
+ .build();
48
+
49
+ const nodeSpawnProcedure = createProcedure()
50
+ .path(["node", "spawn"])
51
+ .input(zodAdapter<NodeSpawnInput>(NodeSpawnInputSchema))
52
+ .output(zodAdapter<NodeSpawnOutput>(NodeSpawnOutputSchema))
53
+ .meta({
54
+ description: "Spawn a long-running Node.js process",
55
+ args: ["script"],
56
+ shorts: { cwd: "C" },
57
+ output: "json",
58
+ // It adds the process to the jobs of the host
59
+ effects: { reads: ["process"], writes: ["process", "jobs"] },
60
+ })
61
+ .handler(async (input: NodeSpawnInput): Promise<NodeSpawnOutput> => {
62
+ return nodeSpawn(input);
63
+ })
64
+ .build();
65
+
66
+ const nodeKillProcedure = createProcedure()
67
+ .path(["node", "kill"])
68
+ .input(zodAdapter<NodeKillInput>(NodeKillInputSchema))
69
+ .output(zodAdapter<NodeKillOutput>(NodeKillOutputSchema))
70
+ .meta({
71
+ description: "Kill a spawned Node.js process",
72
+ args: ["processId"],
73
+ shorts: { signal: "s" },
74
+ output: "json",
75
+ // It ends a process of node.spawn. A second call finds the process already ended.
76
+ // It stops a job of the host: a process that node.spawn started
77
+ effects: { reads: ["jobs"], writes: ["jobs"], idempotent: true },
78
+ })
79
+ .handler(async (input: NodeKillInput): Promise<NodeKillOutput> => {
80
+ return nodeKill(input);
81
+ })
82
+ .build();
83
+
84
+ const nodeStatusProcedure = createProcedure()
85
+ .path(["node", "status"])
86
+ .input(zodAdapter<NodeStatusInput>(NodeStatusInputSchema))
87
+ .output(zodAdapter<NodeStatusOutput>(NodeStatusOutputSchema))
88
+ .meta({
89
+ description: "Get status of running Node.js processes",
90
+ args: [],
91
+ shorts: {},
92
+ output: "json",
93
+ // It reads the records of the processes of node.spawn
94
+ effects: { reads: ["jobs"], writes: [] },
95
+ })
96
+ .handler(async (input: NodeStatusInput): Promise<NodeStatusOutput> => {
97
+ return nodeStatus(input);
98
+ })
99
+ .build();
100
+
101
+ /**
102
+ * The module of this package, for `createRegistry()`: its procedures as data. Importing this
103
+ * file registers nothing. (`register.ts` registers the same procedures in the global registry.)
104
+ */
105
+ export const nodeModule: ProcedureModule = defineModule("@procedures/node", [
106
+ nodeRunProcedure,
107
+ nodeSpawnProcedure,
108
+ nodeKillProcedure,
109
+ nodeStatusProcedure,
110
+ ]);
111
+
112
+ export default nodeModule;
113
+
114
+ /**
115
+ * This function registers the procedures of @procedures/node, and of the modules that it requires, in
116
+ * a registry. The default is the global registry. Importing this file registers nothing: the
117
+ * `./register` entry starts this function (ARCHITECTURE-PROPOSALS-2026-10, P9).
118
+ */
119
+ export function registerNodeProcedures(registry: ProcedureRegistry = PROCEDURE_REGISTRY): void {
120
+ registerModules(registry, [nodeModule]);
121
+ }
@@ -0,0 +1,4 @@
1
+ export { nodeRun } from "./run.js";
2
+ export { nodeSpawn } from "./spawn.js";
3
+ export { nodeKill } from "./kill.js";
4
+ export { nodeStatus } from "./status.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * node.kill procedure
3
+ *
4
+ * Kills a spawned Node.js process and the processes that it started.
5
+ */
6
+
7
+ import type { NodeKillInput, NodeKillOutput } from "../../types.js";
8
+ import { processManager } from "../../process-manager.js";
9
+
10
+ /**
11
+ * Kill a spawned process by ID, and wait for its end
12
+ */
13
+ export async function nodeKill(input: NodeKillInput): Promise<NodeKillOutput> {
14
+ const { processId, signal = "SIGTERM" } = input;
15
+
16
+ if (!processManager.get(processId)) {
17
+ return {
18
+ success: false,
19
+ };
20
+ }
21
+
22
+ const success = await processManager.kill(processId, signal as NodeJS.Signals);
23
+ const processInfo = processManager.getStatus(processId)[0];
24
+
25
+ const result: NodeKillOutput = { success };
26
+ if (processInfo?.exitCode !== undefined) {
27
+ result.exitCode = processInfo.exitCode;
28
+ }
29
+ return result;
30
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * node.run procedure
3
+ *
4
+ * Runs a Node.js script and waits for completion.
5
+ *
6
+ * The script runs with the Node.js of the host (`process.execPath`), through `runCommand` of
7
+ * client-shell: the output has a limit, and the timeout and the signal kill the script and the
8
+ * processes that it started (deep dive WRP-10). Before, the output had no limit, and the timeout
9
+ * killed only the direct child.
10
+ */
11
+
12
+ import { runCommand } from "@procedures/shell/command";
13
+ import type { NodeRunInput, NodeRunOutput } from "../../types.js";
14
+
15
+ /** The end of the output that a timeout error shows. */
16
+ const ERROR_OUTPUT_CHARS = 2000;
17
+
18
+ /**
19
+ * Run a Node.js script and wait for completion
20
+ */
21
+ export async function nodeRun(
22
+ input: NodeRunInput,
23
+ ctx?: { signal?: AbortSignal | undefined }
24
+ ): Promise<NodeRunOutput> {
25
+ const { script, args = [], cwd, env, timeout, maxOutputBytes } = input;
26
+
27
+ const result = await runCommand(process.execPath, {
28
+ args: [script, ...args],
29
+ cwd,
30
+ env,
31
+ timeout,
32
+ signal: ctx?.signal,
33
+ maxOutputBytes,
34
+ });
35
+
36
+ if (result.error) {
37
+ throw new Error(`node did not start: ${result.error}`);
38
+ }
39
+ if (result.timedOut) {
40
+ const output = `${result.stdout}${result.stderr}`.slice(-ERROR_OUTPUT_CHARS);
41
+ throw new Error(`Process timed out after ${timeout}ms${output ? `. Output:\n${output}` : ""}`);
42
+ }
43
+ if (result.aborted) {
44
+ const error = new Error("node.run was aborted");
45
+ error.name = "AbortError";
46
+ throw error;
47
+ }
48
+
49
+ const output: NodeRunOutput = {
50
+ exitCode: result.exitCode,
51
+ stdout: result.stdout,
52
+ stderr: result.stderr,
53
+ };
54
+ if (result.truncated) output.truncated = true;
55
+ return output;
56
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * node.spawn procedure
3
+ *
4
+ * Spawns a long-running Node.js process.
5
+ *
6
+ * The process is a record of the process registry of client-shell: node.status shows it,
7
+ * node.kill ends its whole tree, and it ends with the host (deep dive WRP-4, WRP-10).
8
+ */
9
+
10
+ import type { NodeSpawnInput, NodeSpawnOutput } from "../../types.js";
11
+ import { processManager } from "../../process-manager.js";
12
+
13
+ /**
14
+ * Spawn a long-running Node.js process
15
+ */
16
+ export async function nodeSpawn(input: NodeSpawnInput): Promise<NodeSpawnOutput> {
17
+ const { script, args = [], cwd, env, ready } = input;
18
+
19
+ const started = processManager.start(script, { args, cwd, env });
20
+ if (started.status === "error") {
21
+ throw new Error(`node did not start for ${script}`);
22
+ }
23
+
24
+ // Wait for ready pattern if specified
25
+ if (ready) {
26
+ try {
27
+ await processManager.waitForReady(started.processId, ready.pattern, ready.timeout ?? 30000);
28
+ } catch (error) {
29
+ // A process that is not ready is of no use to the caller: stop it
30
+ await processManager.kill(started.processId);
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ return {
36
+ processId: started.processId,
37
+ pid: started.pid,
38
+ };
39
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * node.status procedure
3
+ *
4
+ * Gets status of spawned Node.js processes.
5
+ */
6
+
7
+ import type { NodeStatusInput, NodeStatusOutput } from "../../types.js";
8
+ import { processManager } from "../../process-manager.js";
9
+
10
+ /**
11
+ * Get status of running processes (and the recent exits)
12
+ */
13
+ export async function nodeStatus(input: NodeStatusInput): Promise<NodeStatusOutput> {
14
+ const { processId } = input;
15
+
16
+ const processes = processManager.getStatus(processId).map((info) =>
17
+ input.output ? { ...info, output: processManager.output(info.processId) } : info
18
+ );
19
+
20
+ return {
21
+ processes,
22
+ };
23
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Process Manager - Manages spawned Node.js processes
3
+ *
4
+ * The manager is a view of the process registry of client-shell, for the group "node"
5
+ * (deep dive WRP-4, WRP-10):
6
+ * - `kill` ends the whole process tree, not only the direct child;
7
+ * - the registry reads the output of each process, so a full pipe does not stop it;
8
+ * - the processes end when the host ends (before, they were detached and kept running);
9
+ * - the registry removes old exit records, and `cleanup` removes the rest.
10
+ */
11
+
12
+ import { processes, type ManagedProcessInfo } from "@procedures/shell/command";
13
+ import type { ProcessInfo } from "./types.js";
14
+
15
+ /** The group of the node processes in the process registry. */
16
+ export const NODE_GROUP = "node";
17
+
18
+ function toProcessInfo(info: ManagedProcessInfo): ProcessInfo {
19
+ const result: ProcessInfo = {
20
+ processId: info.id,
21
+ pid: info.pid,
22
+ script: info.label ?? info.args[0] ?? "",
23
+ status: info.status,
24
+ startedAt: info.startedAt,
25
+ };
26
+ if (info.exitCode !== undefined) result.exitCode = info.exitCode;
27
+ if (info.exitedAt !== undefined) result.exitedAt = info.exitedAt;
28
+ return result;
29
+ }
30
+
31
+ class ProcessManager {
32
+ /**
33
+ * Start a Node.js script and record it
34
+ */
35
+ start(
36
+ script: string,
37
+ options: { args?: string[] | undefined; cwd?: string | undefined; env?: Record<string, string> | undefined }
38
+ ): ProcessInfo {
39
+ const info = processes.start(process.execPath, {
40
+ args: [script, ...(options.args ?? [])],
41
+ cwd: options.cwd,
42
+ env: options.env,
43
+ group: NODE_GROUP,
44
+ label: script,
45
+ });
46
+ return toProcessInfo(info);
47
+ }
48
+
49
+ /**
50
+ * Get a managed process by ID
51
+ */
52
+ get(processId: string): ProcessInfo | undefined {
53
+ const info = processes.get(processId);
54
+ return info && info.group === NODE_GROUP ? toProcessInfo(info) : undefined;
55
+ }
56
+
57
+ /**
58
+ * Kill a process (and the processes that it started) by ID, and wait for its end
59
+ */
60
+ async kill(processId: string, signal: NodeJS.Signals = "SIGTERM"): Promise<boolean> {
61
+ if (!this.get(processId)) return false;
62
+ return (await processes.stop(processId, signal)).stopped;
63
+ }
64
+
65
+ /**
66
+ * Wait for a ready pattern in the output
67
+ */
68
+ async waitForReady(processId: string, pattern: string, timeout: number = 30000): Promise<void> {
69
+ await processes.waitFor(processId, new RegExp(pattern), timeout);
70
+ }
71
+
72
+ /**
73
+ * The end of the output of a process (the last 64 KiB)
74
+ */
75
+ output(processId: string): string {
76
+ return this.get(processId) ? processes.output(processId) : "";
77
+ }
78
+
79
+ /**
80
+ * Get status of all or specific processes
81
+ */
82
+ getStatus(processId?: string): ProcessInfo[] {
83
+ return processes
84
+ .list({ group: NODE_GROUP })
85
+ .filter((info) => processId === undefined || info.id === processId)
86
+ .map(toProcessInfo);
87
+ }
88
+
89
+ /**
90
+ * Cleanup exited processes
91
+ */
92
+ cleanup(): number {
93
+ let cleaned = 0;
94
+ for (const info of processes.list({ group: NODE_GROUP })) {
95
+ if (info.exitedAt && processes.forget(info.id)) cleaned++;
96
+ }
97
+ return cleaned;
98
+ }
99
+ }
100
+
101
+ // Singleton instance
102
+ export const processManager: ProcessManager = new ProcessManager();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/node, and of the modules that it
3
+ * requires, in the global registry: `import "@procedures/node/register"`. The package root and the
4
+ * `./module` entry register nothing (ARCHITECTURE-PROPOSALS-2026-10, P9).
5
+ */
6
+
7
+ import { registerNodeProcedures } from "./module.js";
8
+
9
+ export * from "./module.js";
10
+
11
+ registerNodeProcedures();
package/src/types.ts ADDED
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Type definitions for client-node procedures
3
+ */
4
+
5
+ import { z } from "zod";
6
+
7
+ // ============================================================================
8
+ // node.run - Run script and wait for completion
9
+ // ============================================================================
10
+
11
+ export const NodeRunInputSchema: z.ZodObject<{
12
+ script: z.ZodString;
13
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
+ cwd: z.ZodOptional<z.ZodString>;
15
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
16
+ timeout: z.ZodOptional<z.ZodNumber>;
17
+ maxOutputBytes: z.ZodOptional<z.ZodNumber>;
18
+ }> = z.object({
19
+ /** Script path to run */
20
+ script: z.string().describe("Script path to run"),
21
+ /** Arguments to pass to the script */
22
+ args: z.array(z.string()).optional().describe("Arguments to pass to the script"),
23
+ /** Working directory */
24
+ cwd: z.string().optional().describe("Working directory"),
25
+ /** Environment variables */
26
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
27
+ /** Timeout in milliseconds. At the timeout, the script and its child processes are killed. */
28
+ timeout: z.number().optional().describe("Timeout in milliseconds. At the timeout, the script and its child processes are killed."),
29
+ /** The most bytes kept of each output stream (default: 64 MiB). The procedure keeps the first bytes. */
30
+ maxOutputBytes: z.number().int().positive().optional().describe("The most bytes kept of each output stream (default: 64 MiB). The procedure keeps the first bytes."),
31
+ });
32
+
33
+ export type NodeRunInput = z.infer<typeof NodeRunInputSchema>;
34
+
35
+ export interface NodeRunOutput {
36
+ /** Exit code of the process */
37
+ exitCode: number;
38
+ /** Standard output */
39
+ stdout: string;
40
+ /** Standard error */
41
+ stderr: string;
42
+ /** True when an output stream was longer than `maxOutputBytes` */
43
+ truncated?: boolean | undefined;
44
+ }
45
+
46
+ // ============================================================================
47
+ // node.spawn - Spawn long-running process
48
+ // ============================================================================
49
+
50
+ export const NodeSpawnInputSchema: z.ZodObject<{
51
+ script: z.ZodString;
52
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
53
+ cwd: z.ZodOptional<z.ZodString>;
54
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
55
+ ready: z.ZodOptional<z.ZodObject<{
56
+ pattern: z.ZodString;
57
+ timeout: z.ZodOptional<z.ZodNumber>;
58
+ }>>;
59
+ }> = z.object({
60
+ /** Script path to run */
61
+ script: z.string().describe("Script path to run"),
62
+ /** Arguments to pass to the script */
63
+ args: z.array(z.string()).optional().describe("Arguments to pass to the script"),
64
+ /** Working directory */
65
+ cwd: z.string().optional().describe("Working directory"),
66
+ /** Environment variables */
67
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables"),
68
+ /** Ready pattern - wait for this in stdout before returning */
69
+ ready: z
70
+ .object({
71
+ pattern: z.string(),
72
+ timeout: z.number().optional(),
73
+ })
74
+ .optional().describe("Ready pattern - wait for this in stdout before returning"),
75
+ });
76
+
77
+ export type NodeSpawnInput = z.infer<typeof NodeSpawnInputSchema>;
78
+
79
+ export interface NodeSpawnOutput {
80
+ /** Unique process identifier for management */
81
+ processId: string;
82
+ /** OS process ID */
83
+ pid: number;
84
+ }
85
+
86
+ // ============================================================================
87
+ // node.kill - Kill a spawned process
88
+ // ============================================================================
89
+
90
+ export const NodeKillInputSchema: z.ZodObject<{
91
+ processId: z.ZodString;
92
+ signal: z.ZodOptional<z.ZodString>;
93
+ }> = z.object({
94
+ /** Process ID returned from node.spawn */
95
+ processId: z.string().describe("Process ID returned from node.spawn"),
96
+ /** Signal to send (default: SIGTERM) */
97
+ signal: z.string().optional().describe("Signal to send (default: SIGTERM)"),
98
+ });
99
+
100
+ export type NodeKillInput = z.infer<typeof NodeKillInputSchema>;
101
+
102
+ export interface NodeKillOutput {
103
+ /** Whether the kill was successful */
104
+ success: boolean;
105
+ /** Exit code if process exited */
106
+ exitCode?: number | undefined;
107
+ }
108
+
109
+ // ============================================================================
110
+ // node.status - Get status of running processes
111
+ // ============================================================================
112
+
113
+ export const NodeStatusInputSchema: z.ZodObject<{
114
+ processId: z.ZodOptional<z.ZodString>;
115
+ output: z.ZodOptional<z.ZodBoolean>;
116
+ }> = z.object({
117
+ /** Optional process ID to filter by */
118
+ processId: z.string().optional().describe("Optional process ID to filter by"),
119
+ /** Also give the end of the output of each process (the last 64 KiB) */
120
+ output: z.boolean().optional().describe("Also give the end of the output of each process (the last 64 KiB)"),
121
+ });
122
+
123
+ export type NodeStatusInput = z.infer<typeof NodeStatusInputSchema>;
124
+
125
+ export interface ProcessInfo {
126
+ /** Process identifier */
127
+ processId: string;
128
+ /** OS process ID */
129
+ pid: number;
130
+ /** Script path */
131
+ script: string;
132
+ /** Current status */
133
+ status: "running" | "exited" | "error";
134
+ /** Exit code if exited */
135
+ exitCode?: number | undefined;
136
+ /** Start time */
137
+ startedAt: string;
138
+ /** Exit time if exited */
139
+ exitedAt?: string | undefined;
140
+ /** With `output: true`, the end of the output */
141
+ output?: string | undefined;
142
+ }
143
+
144
+ export interface NodeStatusOutput {
145
+ /** List of processes */
146
+ processes: ProcessInfo[];
147
+ }
148
+
149
+ // Output schemas (roadmap 2.1). Loose objects: a field that a schema does not name stays in the
150
+ // result (a strict schema would remove it when the output is validated).
151
+
152
+ export const NodeRunOutputSchema: z.ZodType<NodeRunOutput> = z.looseObject({
153
+ exitCode: z.number().describe("The exit code of the script"),
154
+ stdout: z.string().describe("The standard output"),
155
+ stderr: z.string().describe("The standard error"),
156
+ truncated: z.boolean().optional().describe("True when an output stream was longer than maxOutputBytes"),
157
+ });
158
+
159
+ export const NodeSpawnOutputSchema: z.ZodType<NodeSpawnOutput> = z.looseObject({
160
+ processId: z.string().describe("The id of the process: node.kill and node.status take it"),
161
+ pid: z.number().describe("The process id of the operating system"),
162
+ });
163
+
164
+ export const NodeKillOutputSchema: z.ZodType<NodeKillOutput> = z.looseObject({
165
+ success: z.boolean().describe("True when the process ended"),
166
+ exitCode: z.number().optional().describe("The exit code, when the process exited"),
167
+ });
168
+
169
+ export const NodeStatusOutputSchema: z.ZodType<NodeStatusOutput> = z.looseObject({
170
+ processes: z
171
+ .array(
172
+ z.looseObject({
173
+ processId: z.string().describe("The id of the process"),
174
+ pid: z.number().describe("The process id of the operating system"),
175
+ script: z.string().describe("The script"),
176
+ status: z.enum(["running", "exited", "error"]).describe("The state of the process"),
177
+ exitCode: z.number().optional().describe("The exit code, when the process exited"),
178
+ startedAt: z.string().describe("The start time (ISO 8601)"),
179
+ exitedAt: z.string().optional().describe("The end time (ISO 8601), when the process ended"),
180
+ output: z.string().optional().describe("With output: true, the end of the output"),
181
+ })
182
+ )
183
+ .describe("The processes of this host"),
184
+ });