@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,267 @@
1
+ /**
2
+ * `@agenteer/node-repair-loop` — meta-node that drives a validate →
3
+ * repair → validate cycle via ReplaceMe (sub-plan 03 §14).
4
+ *
5
+ * **ReplaceMe-first.** Per master plan ratified resolutions, the retry
6
+ * mechanism is data-driven convergence: each repair pass returns
7
+ * `replace_me` with a successor carrying the narrowed target. A bounded
8
+ * `max_iterations` ceiling exists only as a runaway backstop.
9
+ *
10
+ * Shape of one iteration:
11
+ * 1. Run the target node (via `SpawnChildren`).
12
+ * 2. Run the validator against the target's output.
13
+ * 3. If validator.verdict === "pass" → Output(final).
14
+ * If validator.verdict === "fail" and iter < ceiling →
15
+ * ReplaceMe(self with target_input augmented by the failing issues).
16
+ * Otherwise → Failed("repair_exhausted").
17
+ *
18
+ * This is the node that earns the framework's "self-obsolescence"
19
+ * pillar — it's where ReplaceMe pays rent in day-to-day workflows.
20
+ */
21
+
22
+ import { z } from "zod";
23
+ import {
24
+ asArtifact,
25
+ makeManifest,
26
+ type Node,
27
+ type NodeInput,
28
+ type NodeManifest,
29
+ type NodeResult,
30
+ type NodeSpawn,
31
+ } from "@agenteer/core";
32
+ import { ValidatorOutputSchema, type ValidatorOutput } from "../shared/index.js";
33
+
34
+ const MANIFEST: NodeManifest = makeManifest({
35
+ id: "@agenteer/node-repair-loop",
36
+ name: "repair_loop",
37
+ description:
38
+ "ReplaceMe-first validate/repair loop. Max-iterations is a runaway backstop; progress is data-driven.",
39
+ determinism: "stochastic",
40
+ required_actions: [],
41
+ dynamic_actions: true,
42
+ // We spawn whichever target + validator the caller names.
43
+ dynamic_action_spec: "spawn:${input.target_manifest_id}, spawn:${input.validator_manifest_id}",
44
+ tags: ["meta"],
45
+ side_effects: {
46
+ writes_fs: false,
47
+ network: false,
48
+ mutates_ctx: true,
49
+ emits_ctx_variants: ["artifact.repair_loop.result"],
50
+ },
51
+ });
52
+
53
+ const InputSchema = z.object({
54
+ target_manifest_id: z.string().min(1),
55
+ target_input: z.unknown(),
56
+ validator_manifest_id: z.string().min(1),
57
+ validator_input: z.unknown().optional(),
58
+ max_iterations: z.number().int().min(1).max(32).default(4),
59
+ iteration: z.number().int().min(0).default(0),
60
+ /**
61
+ * Optional ctx tag: final result is emitted as an Artifact under this
62
+ * name so downstream nodes can read it via ctx.
63
+ */
64
+ emit_as: z.string().optional(),
65
+ });
66
+
67
+ const OutputSchema = z.object({
68
+ verdict: z.enum(["pass", "fail"]),
69
+ iterations: z.number().int().nonnegative(),
70
+ final_output: z.unknown(),
71
+ last_issues: z
72
+ .array(
73
+ z.object({
74
+ path: z.string().optional(),
75
+ message: z.string(),
76
+ code: z.string().optional(),
77
+ severity: z.enum(["error", "warning"]).optional(),
78
+ }),
79
+ )
80
+ .optional(),
81
+ });
82
+
83
+ type Input = z.input<typeof InputSchema>;
84
+ type Output = z.infer<typeof OutputSchema>;
85
+
86
+ const TARGET_CORRELATION = "repair.target";
87
+ const VALIDATOR_CORRELATION = "repair.validator";
88
+
89
+ export function repairLoopFactory(): Node<Input, Output> {
90
+ return {
91
+ manifest: MANIFEST,
92
+ inputSchema: InputSchema,
93
+ outputSchema: OutputSchema,
94
+ ctx: [],
95
+ model: null,
96
+ async execute(input: NodeInput<Input>): Promise<NodeResult<Output>> {
97
+ const i = input.original;
98
+ const iter = i.iteration ?? 0;
99
+ const maxIter = i.max_iterations ?? 4;
100
+
101
+ if (!input.children) {
102
+ // No children yet → we're on entry; spawn the target.
103
+ return {
104
+ kind: "spawn_children",
105
+ join: { mode: "all" },
106
+ children: [
107
+ {
108
+ manifest_id: i.target_manifest_id,
109
+ input: i.target_input,
110
+ correlation: TARGET_CORRELATION,
111
+ },
112
+ ],
113
+ };
114
+ }
115
+
116
+ // Re-entry. Two shapes to handle:
117
+ // (a) only target-result present → spawn validator
118
+ // (b) target + validator present → decide
119
+ const target = input.children.find((c) => c.correlation === TARGET_CORRELATION);
120
+ const validator = input.children.find((c) => c.correlation === VALIDATOR_CORRELATION);
121
+
122
+ if (!target) {
123
+ return {
124
+ kind: "failed",
125
+ reason: "repair_loop: no target child on re-entry",
126
+ retryable: false,
127
+ evidence: { verdict: "fail" },
128
+ };
129
+ }
130
+ if (target.result.kind !== "output") {
131
+ return {
132
+ kind: "failed",
133
+ reason: `repair_loop: target failed (${(target.result as { reason?: string }).reason ?? target.result.kind})`,
134
+ retryable: false,
135
+ details: { target: target.result },
136
+ evidence: { verdict: "fail" },
137
+ };
138
+ }
139
+
140
+ const targetOutput = (target.result as { value: unknown }).value;
141
+
142
+ if (!validator) {
143
+ // Spawn the validator with the target's output merged in.
144
+ const validatorInput = buildValidatorInput(i.validator_input, targetOutput);
145
+ return {
146
+ kind: "spawn_children",
147
+ join: { mode: "all" },
148
+ children: [
149
+ {
150
+ manifest_id: i.target_manifest_id,
151
+ input: i.target_input,
152
+ correlation: TARGET_CORRELATION,
153
+ },
154
+ {
155
+ manifest_id: i.validator_manifest_id,
156
+ input: validatorInput,
157
+ correlation: VALIDATOR_CORRELATION,
158
+ },
159
+ ],
160
+ };
161
+ }
162
+
163
+ // Validator present — interpret.
164
+ if (validator.result.kind !== "output") {
165
+ return {
166
+ kind: "failed",
167
+ reason: `repair_loop: validator failed (${(validator.result as { reason?: string }).reason ?? validator.result.kind})`,
168
+ retryable: false,
169
+ evidence: { verdict: "fail" },
170
+ };
171
+ }
172
+ const vParsed = ValidatorOutputSchema.safeParse((validator.result as { value: unknown }).value);
173
+ if (!vParsed.success) {
174
+ return {
175
+ kind: "failed",
176
+ reason: `repair_loop: validator output not a ValidatorOutput shape`,
177
+ retryable: false,
178
+ details: { issues: vParsed.error.issues },
179
+ evidence: { verdict: "fail" },
180
+ };
181
+ }
182
+ const vout: ValidatorOutput = vParsed.data;
183
+
184
+ if (vout.verdict === "pass") {
185
+ const base: Output = {
186
+ verdict: "pass",
187
+ iterations: iter,
188
+ final_output: targetOutput,
189
+ };
190
+ const patch = i.emit_as
191
+ ? {
192
+ set: {
193
+ [i.emit_as]: asArtifact(
194
+ { verdict: "pass", iterations: iter, output: targetOutput },
195
+ { media_type: "application/json" },
196
+ ),
197
+ },
198
+ }
199
+ : undefined;
200
+ return {
201
+ kind: "output",
202
+ value: base,
203
+ ...(patch ? { ctx_patch: patch } : {}),
204
+ evidence: { verdict: "pass" },
205
+ };
206
+ }
207
+
208
+ // Validator failed.
209
+ if (iter + 1 >= maxIter) {
210
+ return {
211
+ kind: "output",
212
+ value: {
213
+ verdict: "fail",
214
+ iterations: iter + 1,
215
+ final_output: targetOutput,
216
+ last_issues: vout.issues,
217
+ },
218
+ evidence: { verdict: "fail" },
219
+ };
220
+ }
221
+
222
+ // Repair step: ReplaceMe self with `iteration + 1`, feeding the
223
+ // validator's issues back into target_input as a hint.
224
+ const successor: NodeSpawn = {
225
+ manifest_id: MANIFEST.id,
226
+ // Runtime preserves the frame's correlation on ReplaceMe; this
227
+ // value is only used for lineage tracking on the successor's
228
+ // own spawn event.
229
+ correlation: `repair-iter-${iter + 1}`,
230
+ input: {
231
+ ...i,
232
+ iteration: iter + 1,
233
+ target_input: {
234
+ ...(typeof i.target_input === "object" && i.target_input !== null
235
+ ? (i.target_input as Record<string, unknown>)
236
+ : { original: i.target_input }),
237
+ prior_issues: vout.issues,
238
+ prior_iteration: iter,
239
+ },
240
+ },
241
+ };
242
+
243
+ return {
244
+ kind: "replace_me",
245
+ reason: `repair_iteration:${iter + 1}:${vout.issues.length}_issues`,
246
+ successor,
247
+ };
248
+ },
249
+ };
250
+ }
251
+
252
+ export const repairLoopManifest = MANIFEST;
253
+
254
+ function buildValidatorInput(
255
+ baseValidatorInput: unknown,
256
+ targetOutput: unknown,
257
+ ): unknown {
258
+ // If the caller didn't specify, pass the target output through as
259
+ // `candidate` — a reasonable default for validators that inspect output.
260
+ if (baseValidatorInput === undefined || baseValidatorInput === null) {
261
+ return { candidate: targetOutput };
262
+ }
263
+ if (typeof baseValidatorInput === "object") {
264
+ return { ...(baseValidatorInput as Record<string, unknown>), candidate: targetOutput };
265
+ }
266
+ return baseValidatorInput;
267
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * `@agenteer/node-default-planner` — LLM-driven plan composer (sub-plan 03 §17).
3
+ *
4
+ * **Returns a plan as DATA**, not as `SpawnChildren` (master plan ratified
5
+ * resolution). This enables:
6
+ * - Replay: a plan is a value you can serialize, review, and diff.
7
+ * - Human approval: pair with `approval_gate` to require sign-off before exec.
8
+ * - Judge review: `judge_with_stripped_ctx` can score the plan.
9
+ * - Swapping planners: another node consumes the plan and spawns.
10
+ *
11
+ * The node emits the plan as an Artifact via `ctx.set(asArtifact(...))`
12
+ * (uses R3-A) so downstream nodes see it via their context slice.
13
+ */
14
+
15
+ import { z } from "zod";
16
+ import {
17
+ asArtifact,
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-default-planner",
28
+ name: "default_planner",
29
+ description:
30
+ "LLM-driven planner. Returns a plan (as data + ctx artifact); does NOT spawn the plan itself.",
31
+ determinism: "stochastic",
32
+ required_actions: [],
33
+ dynamic_actions: true,
34
+ dynamic_action_spec: "model:${input.model_id}",
35
+ tags: ["meta", "planner"],
36
+ side_effects: {
37
+ writes_fs: false,
38
+ network: true,
39
+ mutates_ctx: true,
40
+ emits_ctx_variants: ["artifact.plan"],
41
+ },
42
+ });
43
+
44
+ const PlanStepSchema = z.object({
45
+ id: z.string().min(1),
46
+ manifest_id: z.string().min(1),
47
+ input: z.unknown(),
48
+ rationale: z.string().optional(),
49
+ depends_on: z.array(z.string()).default([]),
50
+ attenuate: z.array(z.string()).optional(),
51
+ });
52
+
53
+ const PlanSchema = z.object({
54
+ goal: z.string(),
55
+ steps: z.array(PlanStepSchema).min(1),
56
+ notes: z.string().optional(),
57
+ });
58
+
59
+ const InputSchema = z.object({
60
+ goal: z.string().min(1),
61
+ /**
62
+ * Manifests the planner is allowed to include. Structural filter —
63
+ * the planner picks from this set and only this set. No semantic
64
+ * "free-for-all"; matches the "planner filters structurally first"
65
+ * invariant.
66
+ */
67
+ available_manifests: z
68
+ .array(
69
+ z.object({
70
+ id: z.string().min(1),
71
+ name: z.string(),
72
+ description: z.string(),
73
+ required_actions: z.array(z.string()).default([]),
74
+ }),
75
+ )
76
+ .min(1),
77
+ model_id: z.string().min(1),
78
+ /** Tag under which the plan is emitted to ctx (default: "plan"). */
79
+ emit_as: z.string().default("plan"),
80
+ });
81
+
82
+ const OutputSchema = z.object({
83
+ plan: PlanSchema,
84
+ model: z.string(),
85
+ method: z.enum(["native", "text_parse", "mock"]),
86
+ });
87
+
88
+ type Input = z.input<typeof InputSchema>;
89
+ type Output = z.infer<typeof OutputSchema>;
90
+
91
+ export function defaultPlannerFactory(): Node<Input, Output> {
92
+ return {
93
+ manifest: MANIFEST,
94
+ inputSchema: InputSchema,
95
+ outputSchema: OutputSchema,
96
+ ctx: [],
97
+ model: null,
98
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
99
+ const i = input.original;
100
+ const emitAs = i.emit_as ?? "plan";
101
+
102
+ const systemPrompt = buildSystemPrompt();
103
+ const userPrompt = buildUserPrompt(i);
104
+
105
+ try {
106
+ const res = await handle.callModel<z.infer<typeof PlanSchema>>({
107
+ model_id: i.model_id,
108
+ prompt: userPrompt,
109
+ system: systemPrompt,
110
+ schema: PlanSchema,
111
+ });
112
+ const plan = res.value;
113
+
114
+ // Defensive: reject plans whose steps reference manifests outside
115
+ // the allowed set. The kernel would refuse at spawn anyway, but
116
+ // we fail fast here so the planner sees feedback as a `Failed`
117
+ // that a repair_loop can digest.
118
+ const allowed = new Set(i.available_manifests.map((m) => m.id));
119
+ const illegal = plan.steps.filter((s) => !allowed.has(s.manifest_id));
120
+ if (illegal.length > 0) {
121
+ return {
122
+ kind: "failed",
123
+ reason: `planner chose manifests outside available_manifests: ${illegal.map((s) => s.manifest_id).join(", ")}`,
124
+ retryable: true,
125
+ evidence: { verdict: "fail" },
126
+ };
127
+ }
128
+
129
+ return {
130
+ kind: "output",
131
+ value: { plan, model: res.model, method: res.method },
132
+ ctx_patch: {
133
+ set: {
134
+ [emitAs]: asArtifact(plan, {
135
+ media_type: "application/json",
136
+ schema_ref: "@agenteer/default_planner.plan.v1",
137
+ }),
138
+ },
139
+ },
140
+ evidence: { verdict: "pass" },
141
+ };
142
+ } catch (err) {
143
+ const reason = err instanceof Error ? err.message : String(err);
144
+ return {
145
+ kind: "failed",
146
+ reason,
147
+ retryable: /timeout|rate.?limit|5\d\d|structured_output_exhausted/i.test(reason),
148
+ evidence: { verdict: "fail" },
149
+ };
150
+ }
151
+ },
152
+ };
153
+ }
154
+
155
+ export const defaultPlannerManifest = MANIFEST;
156
+
157
+ function buildSystemPrompt(): string {
158
+ return [
159
+ "You are a software-engineering planner.",
160
+ "You produce a JSON plan whose steps can be executed by the given manifests.",
161
+ "Constraints:",
162
+ "- Every step.manifest_id MUST be drawn from the provided `available_manifests` list.",
163
+ "- Every step.id is unique within the plan.",
164
+ "- `depends_on` references earlier step ids only; no cycles.",
165
+ "- `input` must match what the named manifest expects.",
166
+ "- Keep plans minimal — no speculative steps.",
167
+ ].join("\n");
168
+ }
169
+
170
+ function buildUserPrompt(input: Input): string {
171
+ const catalog = input.available_manifests
172
+ .map((m) => `- ${m.id} (${m.name}): ${m.description}`)
173
+ .join("\n");
174
+ return [
175
+ `Goal: ${input.goal}`,
176
+ ``,
177
+ `Available manifests:`,
178
+ catalog,
179
+ ``,
180
+ `Return JSON matching: { goal, steps: [{ id, manifest_id, input, rationale?, depends_on[] }], notes? }`,
181
+ ].join("\n");
182
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `@agenteer/node-file-read` — permissioned filesystem read (sub-plan 03 §3).
3
+ *
4
+ * Deterministic. Declares `fs.read:*`; actual scope attenuation happens at
5
+ * spawn time. At dispatch the permission kernel synthesizes the concrete
6
+ * `fs.read:<path>` capability and checks against `granted`.
7
+ */
8
+
9
+ import { z } from "zod";
10
+ import {
11
+ makeManifest,
12
+ type Node,
13
+ type NodeInput,
14
+ type NodeManifest,
15
+ type NodeResult,
16
+ type NodeRuntimeHandle,
17
+ } from "@agenteer/core";
18
+
19
+ const MANIFEST: NodeManifest = makeManifest({
20
+ id: "@agenteer/node-file-read",
21
+ name: "file_read",
22
+ description: "Read a file from disk into a string. Capability-gated at dispatch.",
23
+ determinism: "deterministic",
24
+ // Static caps: none. Dynamic augmentation derives the concrete fs.read
25
+ // scope from input.path at spawn time (master plan §R4).
26
+ required_actions: [],
27
+ dynamic_actions: true,
28
+ dynamic_action_spec: "fs.read:${input.path}",
29
+ tags: ["primitive", "fs"],
30
+ side_effects: {
31
+ writes_fs: false,
32
+ network: false,
33
+ mutates_ctx: true,
34
+ emits_ctx_variants: ["artifact.file_read"],
35
+ },
36
+ });
37
+
38
+ const InputSchema = z.object({
39
+ path: z.string().min(1),
40
+ /** Optional ctx tag to emit the content under via ctx_patch.set. */
41
+ emit_as: z.string().optional(),
42
+ });
43
+
44
+ const OutputSchema = z.object({
45
+ path: z.string(),
46
+ content: z.string(),
47
+ bytes: z.number().int().nonnegative(),
48
+ });
49
+
50
+ type Input = z.infer<typeof InputSchema>;
51
+ type Output = z.infer<typeof OutputSchema>;
52
+
53
+ export function fileReadFactory(): Node<Input, Output> {
54
+ return {
55
+ manifest: MANIFEST,
56
+ inputSchema: InputSchema,
57
+ outputSchema: OutputSchema,
58
+ ctx: [],
59
+ model: null,
60
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
61
+ const { path, emit_as } = input.original;
62
+ try {
63
+ const content = await handle.callAction<string>("fs.read", { path });
64
+ return {
65
+ kind: "output",
66
+ value: { path, content, bytes: Buffer.byteLength(content, "utf8") },
67
+ ...(emit_as ? { ctx_patch: { set: { [emit_as]: content } } } : {}),
68
+ evidence: { verdict: "pass" },
69
+ };
70
+ } catch (err) {
71
+ return {
72
+ kind: "failed",
73
+ reason: err instanceof Error ? err.message : String(err),
74
+ retryable: false,
75
+ evidence: { verdict: "fail" },
76
+ };
77
+ }
78
+ },
79
+ };
80
+ }
81
+
82
+ export const fileReadManifest = MANIFEST;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `@agenteer/node-file-write` — permissioned filesystem write (sub-plan 03 §4).
3
+ *
4
+ * Deterministic w.r.t. (path, content), though authors often want to
5
+ * mark it stochastic if the upstream producer was an LLM — the wrapper
6
+ * node is the right place for that. `fs.write:<path>` is checked at
7
+ * dispatch; the hard denylist applies unconditionally.
8
+ */
9
+
10
+ import { z } from "zod";
11
+ import {
12
+ makeManifest,
13
+ type Node,
14
+ type NodeInput,
15
+ type NodeManifest,
16
+ type NodeResult,
17
+ type NodeRuntimeHandle,
18
+ } from "@agenteer/core";
19
+
20
+ const MANIFEST: NodeManifest = makeManifest({
21
+ id: "@agenteer/node-file-write",
22
+ name: "file_write",
23
+ description: "Write a file to disk. Capability-gated; denylist enforced.",
24
+ determinism: "deterministic",
25
+ required_actions: [],
26
+ dynamic_actions: true,
27
+ dynamic_action_spec: "fs.write:${input.path}",
28
+ tags: ["primitive", "fs"],
29
+ side_effects: {
30
+ writes_fs: true,
31
+ network: false,
32
+ mutates_ctx: false,
33
+ },
34
+ });
35
+
36
+ const InputSchema = z.object({
37
+ path: z.string().min(1),
38
+ content: z.string(),
39
+ });
40
+
41
+ const OutputSchema = z.object({
42
+ path: z.string(),
43
+ bytes: z.number().int().nonnegative(),
44
+ });
45
+
46
+ type Input = z.infer<typeof InputSchema>;
47
+ type Output = z.infer<typeof OutputSchema>;
48
+
49
+ export function fileWriteFactory(): Node<Input, Output> {
50
+ return {
51
+ manifest: MANIFEST,
52
+ inputSchema: InputSchema,
53
+ outputSchema: OutputSchema,
54
+ ctx: [],
55
+ model: null,
56
+ async execute(input: NodeInput<Input>, handle: NodeRuntimeHandle): Promise<NodeResult<Output>> {
57
+ const { path, content } = input.original;
58
+ try {
59
+ const { bytes } = await handle.callAction<{ bytes: number }>("fs.write", {
60
+ path,
61
+ content,
62
+ });
63
+ return {
64
+ kind: "output",
65
+ value: { path, bytes },
66
+ evidence: { verdict: "pass" },
67
+ };
68
+ } catch (err) {
69
+ return {
70
+ kind: "failed",
71
+ reason: err instanceof Error ? err.message : String(err),
72
+ retryable: false,
73
+ evidence: { verdict: "fail" },
74
+ };
75
+ }
76
+ },
77
+ };
78
+ }
79
+
80
+ export const fileWriteManifest = MANIFEST;