@agenteer/stdlib 1.0.0-rc.1

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 (118) hide show
  1. package/README.md +98 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/context/context_curator.d.ts +115 -0
  4. package/dist/context/context_curator.d.ts.map +1 -0
  5. package/dist/context/context_curator.js +132 -0
  6. package/dist/context/context_curator.js.map +1 -0
  7. package/dist/humans/approval_gate.d.ts +82 -0
  8. package/dist/humans/approval_gate.d.ts.map +1 -0
  9. package/dist/humans/approval_gate.js +76 -0
  10. package/dist/humans/approval_gate.js.map +1 -0
  11. package/dist/humans/ask_user.d.ts +79 -0
  12. package/dist/humans/ask_user.d.ts.map +1 -0
  13. package/dist/humans/ask_user.js +101 -0
  14. package/dist/humans/ask_user.js.map +1 -0
  15. package/dist/index.d.ts +49 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +93 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/meta/cross_check.d.ts +96 -0
  20. package/dist/meta/cross_check.d.ts.map +1 -0
  21. package/dist/meta/cross_check.js +165 -0
  22. package/dist/meta/cross_check.js.map +1 -0
  23. package/dist/meta/judge_with_stripped_ctx.d.ts +75 -0
  24. package/dist/meta/judge_with_stripped_ctx.d.ts.map +1 -0
  25. package/dist/meta/judge_with_stripped_ctx.js +151 -0
  26. package/dist/meta/judge_with_stripped_ctx.js.map +1 -0
  27. package/dist/meta/parallel_fanout.d.ts +83 -0
  28. package/dist/meta/parallel_fanout.d.ts.map +1 -0
  29. package/dist/meta/parallel_fanout.js +120 -0
  30. package/dist/meta/parallel_fanout.js.map +1 -0
  31. package/dist/meta/repair_loop.d.ts +90 -0
  32. package/dist/meta/repair_loop.d.ts.map +1 -0
  33. package/dist/meta/repair_loop.js +230 -0
  34. package/dist/meta/repair_loop.js.map +1 -0
  35. package/dist/planner/default_planner.d.ts +88 -0
  36. package/dist/planner/default_planner.d.ts.map +1 -0
  37. package/dist/planner/default_planner.js +156 -0
  38. package/dist/planner/default_planner.js.map +1 -0
  39. package/dist/primitives/file_read.d.ts +60 -0
  40. package/dist/primitives/file_read.d.ts.map +1 -0
  41. package/dist/primitives/file_read.js +68 -0
  42. package/dist/primitives/file_read.js.map +1 -0
  43. package/dist/primitives/file_write.d.ts +60 -0
  44. package/dist/primitives/file_write.d.ts.map +1 -0
  45. package/dist/primitives/file_write.js +66 -0
  46. package/dist/primitives/file_write.js.map +1 -0
  47. package/dist/primitives/llm_call.d.ts +85 -0
  48. package/dist/primitives/llm_call.d.ts.map +1 -0
  49. package/dist/primitives/llm_call.js +99 -0
  50. package/dist/primitives/llm_call.js.map +1 -0
  51. package/dist/primitives/shell_exec.d.ts +66 -0
  52. package/dist/primitives/shell_exec.d.ts.map +1 -0
  53. package/dist/primitives/shell_exec.js +106 -0
  54. package/dist/primitives/shell_exec.js.map +1 -0
  55. package/dist/primitives/tool_call.d.ts +62 -0
  56. package/dist/primitives/tool_call.d.ts.map +1 -0
  57. package/dist/primitives/tool_call.js +69 -0
  58. package/dist/primitives/tool_call.js.map +1 -0
  59. package/dist/shared/capture.d.ts +34 -0
  60. package/dist/shared/capture.d.ts.map +1 -0
  61. package/dist/shared/capture.js +124 -0
  62. package/dist/shared/capture.js.map +1 -0
  63. package/dist/shared/ctx.d.ts +14 -0
  64. package/dist/shared/ctx.d.ts.map +1 -0
  65. package/dist/shared/ctx.js +48 -0
  66. package/dist/shared/ctx.js.map +1 -0
  67. package/dist/shared/index.d.ts +4 -0
  68. package/dist/shared/index.d.ts.map +1 -0
  69. package/dist/shared/index.js +4 -0
  70. package/dist/shared/index.js.map +1 -0
  71. package/dist/shared/validator.d.ts +47 -0
  72. package/dist/shared/validator.d.ts.map +1 -0
  73. package/dist/shared/validator.js +41 -0
  74. package/dist/shared/validator.js.map +1 -0
  75. package/dist/validators/compile.d.ts +83 -0
  76. package/dist/validators/compile.d.ts.map +1 -0
  77. package/dist/validators/compile.js +126 -0
  78. package/dist/validators/compile.js.map +1 -0
  79. package/dist/validators/json_schema_validate.d.ts +72 -0
  80. package/dist/validators/json_schema_validate.d.ts.map +1 -0
  81. package/dist/validators/json_schema_validate.js +85 -0
  82. package/dist/validators/json_schema_validate.js.map +1 -0
  83. package/dist/validators/regex_check.d.ts +64 -0
  84. package/dist/validators/regex_check.d.ts.map +1 -0
  85. package/dist/validators/regex_check.js +85 -0
  86. package/dist/validators/regex_check.js.map +1 -0
  87. package/dist/validators/test_run.d.ts +74 -0
  88. package/dist/validators/test_run.d.ts.map +1 -0
  89. package/dist/validators/test_run.js +149 -0
  90. package/dist/validators/test_run.js.map +1 -0
  91. package/dist/validators/typecheck.d.ts +61 -0
  92. package/dist/validators/typecheck.d.ts.map +1 -0
  93. package/dist/validators/typecheck.js +89 -0
  94. package/dist/validators/typecheck.js.map +1 -0
  95. package/package.json +61 -0
  96. package/src/context/context_curator.ts +154 -0
  97. package/src/humans/approval_gate.ts +101 -0
  98. package/src/humans/ask_user.ts +137 -0
  99. package/src/index.ts +149 -0
  100. package/src/meta/cross_check.ts +219 -0
  101. package/src/meta/judge_with_stripped_ctx.ts +171 -0
  102. package/src/meta/parallel_fanout.ts +142 -0
  103. package/src/meta/repair_loop.ts +267 -0
  104. package/src/planner/default_planner.ts +182 -0
  105. package/src/primitives/file_read.ts +82 -0
  106. package/src/primitives/file_write.ts +80 -0
  107. package/src/primitives/llm_call.ts +113 -0
  108. package/src/primitives/shell_exec.ts +122 -0
  109. package/src/primitives/tool_call.ts +84 -0
  110. package/src/shared/capture.ts +155 -0
  111. package/src/shared/ctx.ts +45 -0
  112. package/src/shared/index.ts +17 -0
  113. package/src/shared/validator.ts +51 -0
  114. package/src/validators/compile.ts +175 -0
  115. package/src/validators/json_schema_validate.ts +103 -0
  116. package/src/validators/regex_check.ts +107 -0
  117. package/src/validators/test_run.ts +199 -0
  118. package/src/validators/typecheck.ts +121 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `@agenteer/node-llm-call` — typed prompt + typed output via a model (sub-plan 03 §1).
3
+ *
4
+ * M2 scope: the structured-output wrapper with native-first / text-parse
5
+ * fallback lives in `@agenteer/trust/structured` (M3). Here we:
6
+ * - validate the input shape,
7
+ * - call `handle.callModel` which the runtime routes through the
8
+ * permission kernel + ModelProvider,
9
+ * - emit an Artifact ctx item with the validated value.
10
+ *
11
+ * Response schema is carried on `input.schema` as a Zod type. For
12
+ * third-party wire delivery (JSON-only), the M6 registry serializes
13
+ * schemas; that path is still future work.
14
+ */
15
+
16
+ import { z } from "zod";
17
+ import {
18
+ makeManifest,
19
+ type Node,
20
+ type NodeInput,
21
+ type NodeManifest,
22
+ type NodeResult,
23
+ type NodeRuntimeHandle,
24
+ } from "@agenteer/core";
25
+
26
+ const MANIFEST: NodeManifest = makeManifest({
27
+ id: "@agenteer/node-llm-call",
28
+ name: "llm_call",
29
+ description: "Call a configured model with a structured schema; return the validated value.",
30
+ determinism: "stochastic",
31
+ required_actions: [],
32
+ dynamic_actions: true,
33
+ dynamic_action_spec: "model:${input.model_id}",
34
+ tags: ["primitive", "llm"],
35
+ side_effects: {
36
+ writes_fs: false,
37
+ network: true,
38
+ mutates_ctx: true,
39
+ emits_ctx_variants: ["artifact.llm_call"],
40
+ },
41
+ });
42
+
43
+ const InputSchema = z.object({
44
+ model_id: z.string().min(1),
45
+ prompt: z.string().min(1),
46
+ system: z.string().optional(),
47
+ temperature: z.number().min(0).max(2).optional(),
48
+ max_tokens: z.number().int().min(1).optional(),
49
+ /** Tag under which the validated value is appended to context. */
50
+ emit_as: z.string().optional(),
51
+ });
52
+
53
+ const OutputSchema = z.object({
54
+ value: z.unknown(),
55
+ model: z.string(),
56
+ tokens: z.object({ prompt: z.number().int(), completion: z.number().int() }),
57
+ method: z.enum(["native", "text_parse", "mock"]),
58
+ });
59
+
60
+ type Input = z.infer<typeof InputSchema>;
61
+ type Output = z.infer<typeof OutputSchema>;
62
+
63
+ /**
64
+ * Factory. Accepts an optional response schema — nodes authored on top of
65
+ * `llm_call` typically pass a concrete Zod here. When absent, the return
66
+ * value is passed through unchecked (the manifest's `output_schema` still
67
+ * enforces the outer wrapper shape).
68
+ */
69
+ export function llmCallFactory(responseSchema?: z.ZodTypeAny): () => Node<Input, Output> {
70
+ return () => ({
71
+ manifest: MANIFEST,
72
+ inputSchema: InputSchema,
73
+ outputSchema: OutputSchema,
74
+ ctx: [],
75
+ model: null, // resolved dynamically via input.model_id
76
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
77
+ const { model_id, prompt, system, temperature, max_tokens, emit_as } = input.original;
78
+ try {
79
+ const res = await handle.callModel({
80
+ model_id,
81
+ prompt,
82
+ ...(system !== undefined ? { system } : {}),
83
+ ...(temperature !== undefined ? { temperature } : {}),
84
+ ...(max_tokens !== undefined ? { max_tokens } : {}),
85
+ ...(responseSchema ? { schema: responseSchema } : {}),
86
+ });
87
+ const value: Output = {
88
+ value: res.value,
89
+ model: res.model,
90
+ tokens: res.tokens,
91
+ method: res.method,
92
+ };
93
+ return {
94
+ kind: "output",
95
+ value,
96
+ ...(emit_as ? { ctx_patch: { set: { [emit_as]: res.value } } } : {}),
97
+ evidence: { verdict: "pass" },
98
+ };
99
+ } catch (err) {
100
+ const reason = err instanceof Error ? err.message : String(err);
101
+ const retryable = /timeout|rate.?limit|5\d\d/i.test(reason);
102
+ return {
103
+ kind: "failed",
104
+ reason,
105
+ retryable,
106
+ evidence: { verdict: "fail" },
107
+ };
108
+ }
109
+ },
110
+ });
111
+ }
112
+
113
+ export const llmCallManifest = MANIFEST;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `@agenteer/node-shell-exec` — permissioned subprocess (sub-plan 03 §5).
3
+ *
4
+ * `shell.exec` is intentionally coarse (sub-plan 02 §1.1.4). Dispatch
5
+ * checks capability + denylist on cwd; the runtime's §R1 snapshot wrap
6
+ * catches writes that escape the declared `fs.write` scope. Streaming
7
+ * stdout/stderr uses the shared capture utility so `compile` / `test_run`
8
+ * behave identically.
9
+ */
10
+
11
+ import { z } from "zod";
12
+ import {
13
+ authorizeOperation,
14
+ assertNotDenied,
15
+ makeManifest,
16
+ type Node,
17
+ type NodeInput,
18
+ type NodeManifest,
19
+ type NodeResult,
20
+ type NodeRuntimeHandle,
21
+ } from "@agenteer/core";
22
+ import { runCommand, tailOf } from "../shared/index.js";
23
+
24
+ const MANIFEST: NodeManifest = makeManifest({
25
+ id: "@agenteer/node-shell-exec",
26
+ name: "shell_exec",
27
+ description: "Execute a shell command. Captures stdout/stderr/exit_code. Coarse-grained.",
28
+ determinism: "stochastic",
29
+ required_actions: ["shell.exec:"],
30
+ tags: ["primitive", "shell"],
31
+ });
32
+
33
+ const InputSchema = z.object({
34
+ command: z.string().min(1),
35
+ cwd: z.string().optional(),
36
+ timeout_ms: z.number().int().min(1).max(600_000).default(120_000),
37
+ stdin: z.string().optional(),
38
+ });
39
+
40
+ const OutputSchema = z.object({
41
+ exit_code: z.number().int(),
42
+ stdout: z.string(),
43
+ stderr: z.string(),
44
+ duration_ms: z.number().int().nonnegative(),
45
+ timed_out: z.boolean(),
46
+ });
47
+
48
+ type Input = z.input<typeof InputSchema>;
49
+ type Output = z.infer<typeof OutputSchema>;
50
+
51
+ export function shellExecFactory(): Node<Input, Output> {
52
+ return {
53
+ manifest: MANIFEST,
54
+ inputSchema: InputSchema,
55
+ outputSchema: OutputSchema,
56
+ ctx: [],
57
+ model: null,
58
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
59
+ const { command, cwd, stdin } = input.original;
60
+ const timeout_ms = input.original.timeout_ms ?? 120_000;
61
+
62
+ try {
63
+ authorizeOperation(handle.granted, { kind: "shell.exec" });
64
+ } catch (err) {
65
+ return {
66
+ kind: "failed",
67
+ reason: `permission_denied: ${err instanceof Error ? err.message : String(err)}`,
68
+ retryable: false,
69
+ evidence: { verdict: "fail" },
70
+ };
71
+ }
72
+ if (cwd) {
73
+ try {
74
+ assertNotDenied(cwd);
75
+ } catch (err) {
76
+ return {
77
+ kind: "failed",
78
+ reason: err instanceof Error ? err.message : String(err),
79
+ retryable: false,
80
+ evidence: { verdict: "fail" },
81
+ };
82
+ }
83
+ }
84
+
85
+ try {
86
+ const r = await runCommand(command, {
87
+ ...(cwd !== undefined ? { cwd } : {}),
88
+ ...(stdin !== undefined ? { stdin } : {}),
89
+ timeout_ms,
90
+ signal: handle.signal,
91
+ });
92
+ return {
93
+ kind: "output",
94
+ value: {
95
+ exit_code: r.exit_code,
96
+ stdout: r.stdout,
97
+ stderr: r.stderr,
98
+ duration_ms: r.duration_ms,
99
+ timed_out: r.timed_out,
100
+ },
101
+ evidence: {
102
+ verdict: r.exit_code === 0 && !r.timed_out ? "pass" : "fail",
103
+ tool_output: {
104
+ command,
105
+ exit_code: r.exit_code,
106
+ stdout_tail: tailOf(r.stdout),
107
+ },
108
+ },
109
+ };
110
+ } catch (err) {
111
+ return {
112
+ kind: "failed",
113
+ reason: err instanceof Error ? err.message : String(err),
114
+ retryable: true,
115
+ evidence: { verdict: "fail" },
116
+ };
117
+ }
118
+ },
119
+ };
120
+ }
121
+
122
+ export const shellExecManifest = MANIFEST;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `@agenteer/node-tool-call` — primitive for invoking a registered tool
3
+ * (sub-plan 03 §2). §R4 honest dynamic-action disclosure:
4
+ * dynamic_actions: true
5
+ * dynamic_action_spec: "tool:${input.tool_name}"
6
+ *
7
+ * The permission kernel synthesizes `tool:<concrete_name>` at spawn time
8
+ * and checks against the parent's granted set. Tools themselves are
9
+ * adapters injected via `Runtime({ toolRegistry })`.
10
+ */
11
+
12
+ import { z } from "zod";
13
+ import {
14
+ makeManifest,
15
+ type Node,
16
+ type NodeInput,
17
+ type NodeManifest,
18
+ type NodeResult,
19
+ type NodeRuntimeHandle,
20
+ } from "@agenteer/core";
21
+
22
+ const MANIFEST: NodeManifest = makeManifest({
23
+ id: "@agenteer/node-tool-call",
24
+ name: "tool_call",
25
+ description:
26
+ "Invoke a registered tool by name. §R4-compliant: declared tool capability is synthesized from input at dispatch.",
27
+ determinism: "stochastic",
28
+ required_actions: [],
29
+ dynamic_actions: true,
30
+ dynamic_action_spec: "tool:${input.tool_name}",
31
+ tags: ["primitive"],
32
+ side_effects: {
33
+ writes_fs: false,
34
+ network: true,
35
+ mutates_ctx: false,
36
+ },
37
+ });
38
+
39
+ const InputSchema = z.object({
40
+ tool_name: z.string().min(1),
41
+ args: z.unknown(),
42
+ });
43
+
44
+ const OutputSchema = z.object({
45
+ tool_name: z.string(),
46
+ value: z.unknown(),
47
+ });
48
+
49
+ type Input = z.input<typeof InputSchema>;
50
+ type Output = z.infer<typeof OutputSchema>;
51
+
52
+ export function toolCallFactory(): Node<Input, Output> {
53
+ return {
54
+ manifest: MANIFEST,
55
+ inputSchema: InputSchema,
56
+ outputSchema: OutputSchema,
57
+ ctx: [],
58
+ model: null,
59
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
60
+ const { tool_name, args } = input.original;
61
+ try {
62
+ const value = await handle.callAction<unknown>("tool.invoke", {
63
+ tool_name,
64
+ args,
65
+ });
66
+ return {
67
+ kind: "output",
68
+ value: { tool_name, value },
69
+ evidence: { verdict: "pass", tool_output: { command: `tool:${tool_name}` } },
70
+ };
71
+ } catch (err) {
72
+ const reason = err instanceof Error ? err.message : String(err);
73
+ return {
74
+ kind: "failed",
75
+ reason,
76
+ retryable: /rate.?limit|timeout|5\d\d/i.test(reason),
77
+ evidence: { verdict: "fail" },
78
+ };
79
+ }
80
+ },
81
+ };
82
+ }
83
+
84
+ export const toolCallManifest = MANIFEST;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Streaming stdout/stderr capture with ring-buffer truncation — sub-plan
3
+ * 03 §S.2. Shared by `shell_exec`, `compile`, `test_run`. One module,
4
+ * not three, so truncation + timeout + binary-detection behavior stays
5
+ * consistent across validators.
6
+ */
7
+
8
+ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
9
+
10
+ export interface RunCommandOptions {
11
+ readonly cwd?: string;
12
+ readonly timeout_ms: number;
13
+ readonly stdin?: string;
14
+ readonly signal: AbortSignal;
15
+ readonly env?: NodeJS.ProcessEnv;
16
+ /** Per-stream ring buffer cap (default 256 KiB). */
17
+ readonly capture_limit?: number;
18
+ /** SIGTERM → SIGKILL grace (default 5s). */
19
+ readonly kill_grace_ms?: number;
20
+ readonly onStdoutLine?: (line: string) => void;
21
+ readonly onStderrLine?: (line: string) => void;
22
+ }
23
+
24
+ export interface RunCommandResult {
25
+ exit_code: number;
26
+ stdout: string;
27
+ stderr: string;
28
+ duration_ms: number;
29
+ timed_out: boolean;
30
+ truncated: { stdout: boolean; stderr: boolean };
31
+ }
32
+
33
+ const DEFAULT_CAPTURE_LIMIT = 256 * 1024;
34
+ const DEFAULT_KILL_GRACE_MS = 5_000;
35
+
36
+ export async function runCommand(
37
+ command: string,
38
+ opts: RunCommandOptions,
39
+ ): Promise<RunCommandResult> {
40
+ const startedAt = Date.now();
41
+ return await new Promise<RunCommandResult>((resolve, reject) => {
42
+ const child: ChildProcessWithoutNullStreams = spawn(command, {
43
+ shell: true,
44
+ ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
45
+ ...(opts.env ? { env: opts.env } : {}),
46
+ }) as ChildProcessWithoutNullStreams;
47
+
48
+ const limit = opts.capture_limit ?? DEFAULT_CAPTURE_LIMIT;
49
+ let stdout = "";
50
+ let stderr = "";
51
+ let stdoutTruncated = false;
52
+ let stderrTruncated = false;
53
+ let timedOut = false;
54
+ let settled = false;
55
+ let stdoutTail = "";
56
+ let stderrTail = "";
57
+
58
+ const feed = (
59
+ chunk: Buffer | string,
60
+ which: "stdout" | "stderr",
61
+ ): void => {
62
+ const s = chunk.toString();
63
+ const appendTo = (buf: string, truncated: boolean): [string, boolean] => {
64
+ if (truncated) return [buf, true];
65
+ if (buf.length + s.length > limit) {
66
+ const room = Math.max(0, limit - buf.length);
67
+ return [buf + s.slice(0, room), true];
68
+ }
69
+ return [buf + s, false];
70
+ };
71
+ if (which === "stdout") {
72
+ [stdout, stdoutTruncated] = appendTo(stdout, stdoutTruncated);
73
+ if (opts.onStdoutLine) {
74
+ stdoutTail += s;
75
+ const idx = stdoutTail.lastIndexOf("\n");
76
+ if (idx >= 0) {
77
+ for (const line of stdoutTail.slice(0, idx).split("\n")) {
78
+ if (line.length) opts.onStdoutLine(line);
79
+ }
80
+ stdoutTail = stdoutTail.slice(idx + 1);
81
+ }
82
+ }
83
+ } else {
84
+ [stderr, stderrTruncated] = appendTo(stderr, stderrTruncated);
85
+ if (opts.onStderrLine) {
86
+ stderrTail += s;
87
+ const idx = stderrTail.lastIndexOf("\n");
88
+ if (idx >= 0) {
89
+ for (const line of stderrTail.slice(0, idx).split("\n")) {
90
+ if (line.length) opts.onStderrLine(line);
91
+ }
92
+ stderrTail = stderrTail.slice(idx + 1);
93
+ }
94
+ }
95
+ }
96
+ };
97
+
98
+ child.stdout.on("data", (c) => feed(c, "stdout"));
99
+ child.stderr.on("data", (c) => feed(c, "stderr"));
100
+
101
+ const grace = opts.kill_grace_ms ?? DEFAULT_KILL_GRACE_MS;
102
+ const timeoutTimer = setTimeout(() => {
103
+ timedOut = true;
104
+ child.kill("SIGTERM");
105
+ const hard = setTimeout(() => child.kill("SIGKILL"), grace);
106
+ hard.unref();
107
+ }, opts.timeout_ms);
108
+
109
+ const onAbort = (): void => {
110
+ child.kill("SIGTERM");
111
+ };
112
+ opts.signal.addEventListener("abort", onAbort, { once: true });
113
+
114
+ child.on("error", (err) => {
115
+ if (settled) return;
116
+ settled = true;
117
+ clearTimeout(timeoutTimer);
118
+ opts.signal.removeEventListener("abort", onAbort);
119
+ reject(err);
120
+ });
121
+
122
+ child.on("close", (code) => {
123
+ if (settled) return;
124
+ settled = true;
125
+ clearTimeout(timeoutTimer);
126
+ opts.signal.removeEventListener("abort", onAbort);
127
+ // Flush any remaining partial lines.
128
+ if (stdoutTail.length && opts.onStdoutLine) opts.onStdoutLine(stdoutTail);
129
+ if (stderrTail.length && opts.onStderrLine) opts.onStderrLine(stderrTail);
130
+ resolve({
131
+ exit_code: code ?? -1,
132
+ stdout: stdoutTruncated
133
+ ? `${stdout}\n[...stdout truncated at ${limit} bytes...]`
134
+ : stdout,
135
+ stderr: stderrTruncated
136
+ ? `${stderr}\n[...stderr truncated at ${limit} bytes...]`
137
+ : stderr,
138
+ duration_ms: Date.now() - startedAt,
139
+ timed_out: timedOut,
140
+ truncated: { stdout: stdoutTruncated, stderr: stderrTruncated },
141
+ });
142
+ });
143
+
144
+ if (opts.stdin !== undefined) {
145
+ child.stdin.write(opts.stdin);
146
+ }
147
+ child.stdin.end();
148
+ });
149
+ }
150
+
151
+ /** Convenience: tail of a stream for evidence `stdout_tail` / summary. */
152
+ export function tailOf(text: string, maxChars = 4096): string {
153
+ if (text.length <= maxChars) return text;
154
+ return `[...]${text.slice(-maxChars)}`;
155
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ctx_patch builders for stdlib authors (sub-plan 03 §S.4).
3
+ *
4
+ * Keeps stdlib code terse: `mergeSetArtifacts({ "plan.head": plan })`
5
+ * instead of spelling out the `asArtifact` wrapper per entry.
6
+ */
7
+
8
+ import { asArtifact, type CtxArtifactMarker, type CtxPatch } from "@agenteer/core";
9
+
10
+ export function setArtifacts(
11
+ entries: Record<string, { body: unknown; media_type?: string; schema_ref?: string }>,
12
+ ): Pick<CtxPatch, "set"> {
13
+ const set: Record<string, CtxArtifactMarker> = {};
14
+ for (const [key, spec] of Object.entries(entries)) {
15
+ set[key] = asArtifact(spec.body, {
16
+ ...(spec.media_type !== undefined ? { media_type: spec.media_type } : {}),
17
+ ...(spec.schema_ref !== undefined ? { schema_ref: spec.schema_ref } : {}),
18
+ });
19
+ }
20
+ return { set };
21
+ }
22
+
23
+ export function mergePatches(...patches: (CtxPatch | undefined)[]): CtxPatch | undefined {
24
+ const set: Record<string, unknown> = {};
25
+ const del: string[] = [];
26
+ const append: Record<string, unknown[]> = {};
27
+ let any = false;
28
+ for (const p of patches) {
29
+ if (!p) continue;
30
+ any = true;
31
+ if (p.set) Object.assign(set, p.set);
32
+ if (p.delete) del.push(...p.delete);
33
+ if (p.append) {
34
+ for (const [k, vs] of Object.entries(p.append)) {
35
+ append[k] = [...(append[k] ?? []), ...vs];
36
+ }
37
+ }
38
+ }
39
+ if (!any) return undefined;
40
+ const out: CtxPatch = {};
41
+ if (Object.keys(set).length) out.set = set;
42
+ if (del.length) out.delete = del;
43
+ if (Object.keys(append).length) out.append = append;
44
+ return out;
45
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ runCommand,
3
+ tailOf,
4
+ type RunCommandOptions,
5
+ type RunCommandResult,
6
+ } from "./capture.js";
7
+
8
+ export {
9
+ ValidatorIssueSchema,
10
+ ValidatorOutputSchema,
11
+ type ValidatorIssue,
12
+ type ValidatorOutput,
13
+ passOutput,
14
+ failOutput,
15
+ } from "./validator.js";
16
+
17
+ export { setArtifacts, mergePatches } from "./ctx.js";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Validator shape (sub-plan 03 §Part B; master plan ratified resolution).
3
+ *
4
+ * Validators emit `verdict: "fail"` as DATA on `Output`, not via `Failed`.
5
+ * `Failed` is reserved for "couldn't run at all" (binary missing,
6
+ * unreadable input, capability denied). This helper normalizes the
7
+ * output shape so downstream meta-nodes (repair_loop, judge) can switch
8
+ * on a single field.
9
+ */
10
+
11
+ import { z } from "zod";
12
+
13
+ export const ValidatorIssueSchema = z.object({
14
+ path: z.string().optional(),
15
+ message: z.string(),
16
+ code: z.string().optional(),
17
+ severity: z.enum(["error", "warning"]).default("error"),
18
+ });
19
+ export type ValidatorIssue = z.infer<typeof ValidatorIssueSchema>;
20
+
21
+ export const ValidatorOutputSchema = z.object({
22
+ verdict: z.enum(["pass", "fail"]),
23
+ issues: z.array(ValidatorIssueSchema),
24
+ /** Short human-readable summary for logs / evidence. */
25
+ summary: z.string(),
26
+ /** Tool-level exit code if applicable; 0 for pass, non-zero for fail. */
27
+ exit_code: z.number().int().optional(),
28
+ /** Tail of raw tool output, bounded. */
29
+ stdout_tail: z.string().optional(),
30
+ stderr_tail: z.string().optional(),
31
+ });
32
+ export type ValidatorOutput = z.infer<typeof ValidatorOutputSchema>;
33
+
34
+ export function passOutput(summary: string): ValidatorOutput {
35
+ return { verdict: "pass", issues: [], summary, exit_code: 0 };
36
+ }
37
+
38
+ export function failOutput(
39
+ issues: ValidatorIssue[],
40
+ summary: string,
41
+ extra?: { exit_code?: number; stdout_tail?: string; stderr_tail?: string },
42
+ ): ValidatorOutput {
43
+ return {
44
+ verdict: "fail",
45
+ issues,
46
+ summary,
47
+ ...(extra?.exit_code !== undefined ? { exit_code: extra.exit_code } : {}),
48
+ ...(extra?.stdout_tail ? { stdout_tail: extra.stdout_tail } : {}),
49
+ ...(extra?.stderr_tail ? { stderr_tail: extra.stderr_tail } : {}),
50
+ };
51
+ }