@gr8ful/spf 0.1.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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +321 -0
  3. package/assets/defaults/spf.config.yaml +141 -0
  4. package/assets/prompts/builder/system.md +13 -0
  5. package/assets/prompts/builder/user.md +34 -0
  6. package/assets/prompts/documenter/system.md +17 -0
  7. package/assets/prompts/documenter/user.md +48 -0
  8. package/assets/prompts/planner/system.md +21 -0
  9. package/assets/prompts/planner/user.md +45 -0
  10. package/assets/prompts/reviewer/system.md +16 -0
  11. package/assets/prompts/reviewer/user.md +44 -0
  12. package/assets/prompts/scout/system.md +20 -0
  13. package/assets/prompts/scout/user.md +34 -0
  14. package/assets/skill/SKILL.md +80 -0
  15. package/assets/skill/cookbooks/authoring_chains.md +193 -0
  16. package/assets/skill/cookbooks/how_to_prompt_for_the_eng.md +109 -0
  17. package/assets/skill/cookbooks/roster.md +197 -0
  18. package/assets/skill/cookbooks/run_adw.md +92 -0
  19. package/assets/skill/cookbooks/spf_overview.md +111 -0
  20. package/assets/skill/references/config.md +188 -0
  21. package/assets/skill/references/handoff.md +162 -0
  22. package/assets/skill/references/observability.md +184 -0
  23. package/dist/chains/adw_build.d.ts +12 -0
  24. package/dist/chains/adw_build.js +27 -0
  25. package/dist/chains/adw_build_review.d.ts +21 -0
  26. package/dist/chains/adw_build_review.js +55 -0
  27. package/dist/chains/adw_build_test.d.ts +21 -0
  28. package/dist/chains/adw_build_test.js +67 -0
  29. package/dist/chains/adw_document.d.ts +23 -0
  30. package/dist/chains/adw_document.js +59 -0
  31. package/dist/chains/adw_plan.d.ts +12 -0
  32. package/dist/chains/adw_plan.js +27 -0
  33. package/dist/chains/adw_plan_build.d.ts +12 -0
  34. package/dist/chains/adw_plan_build.js +30 -0
  35. package/dist/chains/adw_plan_build_test.d.ts +16 -0
  36. package/dist/chains/adw_plan_build_test.js +65 -0
  37. package/dist/chains/adw_plan_build_test_quality.d.ts +18 -0
  38. package/dist/chains/adw_plan_build_test_quality.js +66 -0
  39. package/dist/chains/adw_prompt.d.ts +12 -0
  40. package/dist/chains/adw_prompt.js +25 -0
  41. package/dist/chains/adw_quality.d.ts +12 -0
  42. package/dist/chains/adw_quality.js +32 -0
  43. package/dist/chains/adw_scout.d.ts +12 -0
  44. package/dist/chains/adw_scout.js +27 -0
  45. package/dist/chains/adw_simple_sdlc.d.ts +43 -0
  46. package/dist/chains/adw_simple_sdlc.js +147 -0
  47. package/dist/chains/context.d.ts +19 -0
  48. package/dist/chains/context.js +1 -0
  49. package/dist/chains/index.d.ts +19 -0
  50. package/dist/chains/index.js +116 -0
  51. package/dist/cli/bin.d.ts +15 -0
  52. package/dist/cli/bin.js +24 -0
  53. package/dist/cli/commands/abort.d.ts +1 -0
  54. package/dist/cli/commands/abort.js +42 -0
  55. package/dist/cli/commands/doctor.d.ts +1 -0
  56. package/dist/cli/commands/doctor.js +165 -0
  57. package/dist/cli/commands/eject.d.ts +1 -0
  58. package/dist/cli/commands/eject.js +55 -0
  59. package/dist/cli/commands/events.d.ts +1 -0
  60. package/dist/cli/commands/events.js +49 -0
  61. package/dist/cli/commands/init.d.ts +1 -0
  62. package/dist/cli/commands/init.js +62 -0
  63. package/dist/cli/commands/install-skill.d.ts +1 -0
  64. package/dist/cli/commands/install-skill.js +122 -0
  65. package/dist/cli/commands/list.d.ts +1 -0
  66. package/dist/cli/commands/list.js +13 -0
  67. package/dist/cli/commands/migrate.d.ts +1 -0
  68. package/dist/cli/commands/migrate.js +167 -0
  69. package/dist/cli/commands/phases.d.ts +1 -0
  70. package/dist/cli/commands/phases.js +25 -0
  71. package/dist/cli/commands/run.d.ts +3 -0
  72. package/dist/cli/commands/run.js +27 -0
  73. package/dist/cli/commands/sessions.d.ts +1 -0
  74. package/dist/cli/commands/sessions.js +20 -0
  75. package/dist/cli/commands/trace.d.ts +8 -0
  76. package/dist/cli/commands/trace.js +10 -0
  77. package/dist/cli/commands/ui.d.ts +1 -0
  78. package/dist/cli/commands/ui.js +35 -0
  79. package/dist/cli/commands/version.d.ts +1 -0
  80. package/dist/cli/commands/version.js +8 -0
  81. package/dist/cli/commands/watch.d.ts +8 -0
  82. package/dist/cli/commands/watch.js +210 -0
  83. package/dist/cli/gitignore.d.ts +1 -0
  84. package/dist/cli/gitignore.js +14 -0
  85. package/dist/cli/index.d.ts +1 -0
  86. package/dist/cli/index.js +156 -0
  87. package/dist/core/agent_cc.d.ts +75 -0
  88. package/dist/core/agent_cc.js +322 -0
  89. package/dist/core/agent_flue.d.ts +66 -0
  90. package/dist/core/agent_flue.js +321 -0
  91. package/dist/core/agents.d.ts +64 -0
  92. package/dist/core/agents.js +456 -0
  93. package/dist/core/changes.d.ts +35 -0
  94. package/dist/core/changes.js +98 -0
  95. package/dist/core/console.d.ts +36 -0
  96. package/dist/core/console.js +156 -0
  97. package/dist/core/data_types.d.ts +562 -0
  98. package/dist/core/data_types.js +382 -0
  99. package/dist/core/gates.d.ts +33 -0
  100. package/dist/core/gates.js +144 -0
  101. package/dist/core/git_helper.d.ts +59 -0
  102. package/dist/core/git_helper.js +115 -0
  103. package/dist/core/issues/github_provider.d.ts +52 -0
  104. package/dist/core/issues/github_provider.js +211 -0
  105. package/dist/core/issues/provider.d.ts +90 -0
  106. package/dist/core/issues/provider.js +14 -0
  107. package/dist/core/paths.d.ts +78 -0
  108. package/dist/core/paths.js +108 -0
  109. package/dist/core/permissions.d.ts +78 -0
  110. package/dist/core/permissions.js +187 -0
  111. package/dist/core/prompts.d.ts +4 -0
  112. package/dist/core/prompts.js +17 -0
  113. package/dist/core/quality.d.ts +65 -0
  114. package/dist/core/quality.js +194 -0
  115. package/dist/core/runner.d.ts +72 -0
  116. package/dist/core/runner.js +168 -0
  117. package/dist/core/session.d.ts +16 -0
  118. package/dist/core/session.js +60 -0
  119. package/dist/core/sqlite.d.ts +55 -0
  120. package/dist/core/sqlite.js +106 -0
  121. package/dist/core/tracer.d.ts +56 -0
  122. package/dist/core/tracer.js +246 -0
  123. package/dist/core/utils.d.ts +40 -0
  124. package/dist/core/utils.js +108 -0
  125. package/dist/core/watch.d.ts +44 -0
  126. package/dist/core/watch.js +194 -0
  127. package/dist/test/agent_cc.test.d.ts +1 -0
  128. package/dist/test/agent_cc.test.js +95 -0
  129. package/dist/test/agent_flue.test.d.ts +1 -0
  130. package/dist/test/agent_flue.test.js +83 -0
  131. package/dist/test/data_types.test.d.ts +10 -0
  132. package/dist/test/data_types.test.js +49 -0
  133. package/dist/test/ui_server.test.d.ts +1 -0
  134. package/dist/test/ui_server.test.js +119 -0
  135. package/dist/test/watch.test.d.ts +1 -0
  136. package/dist/test/watch.test.js +227 -0
  137. package/dist/ui/server/app.d.ts +3 -0
  138. package/dist/ui/server/app.js +98 -0
  139. package/dist/ui/server/db.d.ts +82 -0
  140. package/dist/ui/server/db.js +333 -0
  141. package/dist/ui/server/serve.d.ts +13 -0
  142. package/dist/ui/server/serve.js +80 -0
  143. package/dist/ui/server/static.d.ts +5 -0
  144. package/dist/ui/server/static.js +55 -0
  145. package/dist/ui/shared/types.d.ts +263 -0
  146. package/dist/ui/shared/types.js +8 -0
  147. package/package.json +52 -0
  148. package/web/assets/index-C7nF068F.css +1 -0
  149. package/web/assets/index-mzSArcnQ.js +11 -0
  150. package/web/assets/play-latin-400-normal-GKW-4YV7.woff2 +0 -0
  151. package/web/assets/play-latin-700-normal-DyPlLDbb.woff2 +0 -0
  152. package/web/index.html +14 -0
  153. package/web/logo.svg +6 -0
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Flue coding agent interface — replaces agent_pi.ts.
3
+ *
4
+ * One generic Flue agent function (`SfAgent`), driven entirely by YAML: a
5
+ * module-level registry maps a Flue conversation id (== SPF's session id) to
6
+ * the model/thinking/sandbox/tools/instruction that call should use, set by
7
+ * `run()` just before dispatching. SPF's roster stays data; Flue never learns
8
+ * there are five different "kinds" of agent.
9
+ *
10
+ * Structured output has no Flue-native "constrain this agent's final
11
+ * result" mechanism (that exists only for a nested `harness.prompt()` call
12
+ * inside a tool) — so every call gets one injected tool, `sf_report`, whose
13
+ * `input` schema IS the envelope's Valibot schema. Flue validates the
14
+ * model's arguments against it before `run()` fires, and `run()` returns
15
+ * `{terminate: true}` — the same loop-ending contract Flue's own built-in
16
+ * `finish`/`give_up` tools use — so a report ends the turn structurally,
17
+ * not just by instruction. The captured data rides back via
18
+ * `useDataWriter`, which is what `AgentReply.data` surfaces to the caller.
19
+ * `agents.ts`'s existing JSON-extraction fallback stays as a safety net for
20
+ * a model that never calls the tool at all.
21
+ *
22
+ * `run()`'s signature deliberately mirrors the old agent_pi.ts `run()` so
23
+ * agents.ts's `send()` closure changes only its imports and field names.
24
+ */
25
+ import { AgentRunError, createBashTool, createEditTool, createGlobTool, createGrepTool, createReadTool, createWriteTool, init, observe, useDataWriter, useModel, useSandbox, useTool, } from "@flue/runtime";
26
+ import { local, sqlite, start } from "@flue/runtime/node";
27
+ import { UsageBreakdown, makeAgentResult } from "./data_types.js";
28
+ import { nowIso, operatorEnv } from "./utils.js";
29
+ const RESULT_SNIPPET_CHARS = 20_000; // tool output rides along whole; clip only guards pathological cases
30
+ const ARG_VALUE_CHARS = 20_000; // args too — the UI scrolls, it must not be handed cut-off data
31
+ const LABEL_CHARS = 80; // "bash: <command>" shown as the event name
32
+ // The arg that identifies a call at a glance, in the order tools tend to use.
33
+ // Unverified against Flue's own built-in tool argument names beyond what
34
+ // their published schemas show (path/command/pattern) — kept from pi's list
35
+ // since the overlap is exact where it matters; a mismatch here only means a
36
+ // less specific label, never a wrong one.
37
+ const PRIMARY_ARGS = ["command", "path", "file_path", "pattern", "query", "url"];
38
+ function hasMessage(value) {
39
+ return typeof value === "object" && value !== null && typeof value.message === "string";
40
+ }
41
+ function clipText(text, limit) {
42
+ return text.length <= limit ? text : text.slice(0, limit).trimEnd() + "…";
43
+ }
44
+ /** One-line human name for a tool call: `bash: ls -la src`. */
45
+ function labelFor(tool, args) {
46
+ let value = "";
47
+ for (const key of PRIMARY_ARGS) {
48
+ if (typeof args[key] === "string" && args[key].trim()) {
49
+ value = args[key];
50
+ break;
51
+ }
52
+ }
53
+ if (!value) {
54
+ for (const v of Object.values(args)) {
55
+ if (typeof v === "string" && v.trim()) {
56
+ value = v;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ value = String(value).split(/\s+/).filter(Boolean).join(" ");
62
+ return value ? `${tool}: ${clipText(value, LABEL_CHARS)}` : tool;
63
+ }
64
+ /** `tool-output`'s `output` is typed `unknown` — normalize whatever shape it turns out to be. */
65
+ function snippetOf(output) {
66
+ if (output == null)
67
+ return "";
68
+ if (typeof output === "string")
69
+ return output;
70
+ try {
71
+ return JSON.stringify(output);
72
+ }
73
+ catch {
74
+ return String(output);
75
+ }
76
+ }
77
+ /**
78
+ * Folds Flue's `tool-input` + `tool-output`/`tool-output-error` chunk pair
79
+ * into ONE normalized record per completed call — the same shape
80
+ * agent_pi.ts's tracker produced, so agents.ts's eventForwarder (and the
81
+ * tracer/visualizer downstream of it) need no changes. `tool-output` never
82
+ * carries the tool's name, which is exactly why folding is still required.
83
+ */
84
+ export class ToolCallTracker {
85
+ open = new Map();
86
+ /** Returns the record for a finished tool call, else null. */
87
+ observe(chunk) {
88
+ if (chunk.type === "tool-input") {
89
+ this.open.set(chunk.toolCallId, {
90
+ tool: chunk.toolName,
91
+ args: chunk.input || {},
92
+ started_at: chunk.timestamp || nowIso(),
93
+ });
94
+ return null;
95
+ }
96
+ if (chunk.type === "tool-output") {
97
+ return this.finish(chunk.toolCallId, chunk.output, true, chunk.durationMs);
98
+ }
99
+ if (chunk.type === "tool-output-error") {
100
+ return this.finish(chunk.toolCallId, chunk.errorText, false, chunk.durationMs);
101
+ }
102
+ return null;
103
+ }
104
+ finish(callId, output, ok, durationMs) {
105
+ const opened = this.open.get(callId);
106
+ this.open.delete(callId);
107
+ const tool = opened?.tool || "tool";
108
+ const args = opened?.args || {};
109
+ const record = {
110
+ tool,
111
+ tool_call_id: callId,
112
+ args: Object.fromEntries(Object.entries(args).map(([key, value]) => [key, typeof value === "string" ? clipText(value, ARG_VALUE_CHARS) : value])),
113
+ ok,
114
+ label: labelFor(tool, args),
115
+ };
116
+ const snippet = snippetOf(output);
117
+ if (snippet)
118
+ record.result_snippet = clipText(snippet, RESULT_SNIPPET_CHARS);
119
+ record.ended_at = nowIso();
120
+ if (durationMs !== undefined)
121
+ record.duration_ms = Math.round(durationMs);
122
+ if (opened?.started_at)
123
+ record.started_at = opened.started_at;
124
+ return record;
125
+ }
126
+ }
127
+ // ── tool-name resolution ─────────────────────────────────────────────────────
128
+ const BUILTIN_TOOLS = {
129
+ read: createReadTool,
130
+ write: createWriteTool,
131
+ edit: createEditTool,
132
+ bash: createBashTool,
133
+ grep: createGrepTool,
134
+ glob: createGlobTool,
135
+ };
136
+ // pi's vocabulary -> Flue's. "ls" has no Flue built-in (bash/glob cover it);
137
+ // it is a KNOWN name that resolves to nothing, not an unknown one.
138
+ const TOOL_ALIASES = { find: "glob" };
139
+ const DROPPED_TOOLS = new Set(["ls"]);
140
+ /** Used by agents.validate() so a typo'd tool name fails before anything spawns. */
141
+ export function isKnownToolName(name) {
142
+ return DROPPED_TOOLS.has(name) || (TOOL_ALIASES[name] ?? name) in BUILTIN_TOOLS;
143
+ }
144
+ function resolveBuiltinTools(names) {
145
+ const factories = [];
146
+ for (const name of names) {
147
+ if (DROPPED_TOOLS.has(name))
148
+ continue;
149
+ const factory = BUILTIN_TOOLS[TOOL_ALIASES[name] ?? name];
150
+ if (factory)
151
+ factories.push(factory);
152
+ }
153
+ return factories;
154
+ }
155
+ /**
156
+ * `undefined`/`null` toolNames = every builtin (Flue's own default when no
157
+ * `tools` override is given — matches SPF's "unset = all tools usable").
158
+ * `local()`'s env does NOT inherit process.env by default (only PATH/HOME/
159
+ * USER/LANG/TERM/TMPDIR) — operatorEnv() restores today's actual behavior.
160
+ */
161
+ function sandboxFor(toolNames, cwd) {
162
+ const base = local({ cwd, env: operatorEnv() });
163
+ if (!toolNames)
164
+ return base;
165
+ const factories = resolveBuiltinTools(toolNames);
166
+ return { ...base, tools: (env) => factories.map((f) => f(env)) };
167
+ }
168
+ // ── model pattern validation ─────────────────────────────────────────────────
169
+ /**
170
+ * Flue exposes no public catalog of registered models (only pi's --list-models
171
+ * CLI did, and that command is gone with pi). This checks only the STATIC
172
+ * `provider/model-id` shape — the same shape SPF's config docs already ask
173
+ * for — not that the provider is reachable or the id exists. An actually
174
+ * wrong model now surfaces at the first real dispatch instead of at
175
+ * validate() time; that trade is made explicit here rather than pretending
176
+ * a catalog check still happens.
177
+ */
178
+ export function resolveModel(pattern) {
179
+ const slash = pattern.indexOf("/");
180
+ if (slash <= 0 || slash === pattern.length - 1) {
181
+ throw new Error(`model ${JSON.stringify(pattern)} must be written as provider/model-id`);
182
+ }
183
+ return [pattern.slice(0, slash), pattern.slice(slash + 1)];
184
+ }
185
+ const REGISTRY = new Map();
186
+ function sfAgentRender({ id }) {
187
+ const spec = REGISTRY.get(id);
188
+ if (!spec)
189
+ throw new Error(`agent_flue: no render spec registered for conversation ${id} — run() must set it before dispatching`);
190
+ useModel(spec.model, { thinkingLevel: spec.thinking });
191
+ useSandbox(sandboxFor(spec.toolNames, spec.cwd));
192
+ const writeReport = useDataWriter("sf_report");
193
+ useTool({
194
+ name: "sf_report",
195
+ description: `Emit your final ${spec.outputTypeName} report as your last action. Call this exactly once — it ends your turn.`,
196
+ input: spec.outputSchema,
197
+ run: ({ data }) => {
198
+ writeReport(data);
199
+ return { terminate: true };
200
+ },
201
+ });
202
+ return spec.systemText;
203
+ }
204
+ const SfAgent = Object.assign(sfAgentRender, {
205
+ agentName: "spf-agent",
206
+ });
207
+ const pendingUsage = new Map();
208
+ let runtimePromise = null;
209
+ function ensureRuntime(flueDbPath) {
210
+ if (!runtimePromise) {
211
+ runtimePromise = start({ agents: [SfAgent], db: sqlite(flueDbPath) }).then((flue) => {
212
+ observe((event) => {
213
+ if (event.type !== "turn" || !event.submissionId)
214
+ return;
215
+ const slot = pendingUsage.get(event.submissionId);
216
+ if (!slot)
217
+ return; // not one of ours (or already collected) — ignore
218
+ const usage = event.response.usage;
219
+ if (!usage)
220
+ return;
221
+ const turnTokens = contextTokensOf(usage);
222
+ slot.usage.add_turn(usage, turnTokens);
223
+ // Occupancy is read off the last valid AGENT-purpose turn: compaction
224
+ // turns cost real money (folded into usage above) but say nothing
225
+ // about the agent's own conversation occupancy, and an errored turn's
226
+ // reading can't be trusted either way.
227
+ if (event.purpose === "agent" && turnTokens && !event.isError) {
228
+ slot.context_tokens = turnTokens;
229
+ }
230
+ });
231
+ return flue;
232
+ });
233
+ }
234
+ return runtimePromise;
235
+ }
236
+ /** Graceful shutdown — call once, at process exit. Safe to call if never started. */
237
+ export async function shutdown() {
238
+ if (!runtimePromise)
239
+ return;
240
+ const flue = await runtimePromise;
241
+ await flue.stop();
242
+ runtimePromise = null;
243
+ }
244
+ /**
245
+ * Mirrors pi's own `calculateContextTokens`: prefer the provider's
246
+ * totalTokens, else sum the parts. Cache reads count — cached prompt is
247
+ * still prompt. Verbatim from agent_pi.ts (Flue's PromptUsage shape matches
248
+ * pi-ai's field names one-for-one).
249
+ */
250
+ function contextTokensOf(usage) {
251
+ if (usage.totalTokens)
252
+ return Math.trunc(usage.totalTokens);
253
+ return Math.trunc(usage.input + usage.output + usage.cacheRead + usage.cacheWrite);
254
+ }
255
+ /**
256
+ * Run one Flue dispatch+read turn against the given conversation id,
257
+ * creating it on first contact and continuing it on every later call with
258
+ * the same `session_id` — the same continuity `pi --session-id` gave.
259
+ *
260
+ * `onSpawn`/`onExit` bracket this call the way they bracketed pi's child
261
+ * process, but there is no child process here: Flue runs in-process, so
262
+ * both fire with this process's own pid. A future `spf abort` can still find
263
+ * and stop the right OS process; distinguishing WHICH in-flight submission
264
+ * that pid is running is a `processes.submission_id` column left for later,
265
+ * not attempted here.
266
+ */
267
+ export async function run(request, onEvent, onSpawn, onExit) {
268
+ await ensureRuntime(request.flue_db_path);
269
+ REGISTRY.set(request.session_id, {
270
+ model: request.model,
271
+ thinking: request.thinking,
272
+ cwd: request.cwd,
273
+ toolNames: request.tools,
274
+ systemText: request.system_prompt,
275
+ outputSchema: request.output_schema,
276
+ outputTypeName: request.output_type_name,
277
+ });
278
+ const pid = process.pid ?? -1;
279
+ onSpawn?.(pid);
280
+ const handle = init(SfAgent, { id: request.session_id });
281
+ const receipt = await handle.dispatch(request.prompt);
282
+ const slot = { usage: new UsageBreakdown(), context_tokens: 0 };
283
+ pendingUsage.set(receipt.submissionId, slot);
284
+ try {
285
+ // Raw chunks pass straight through, exactly as agent_pi.ts's onEvent
286
+ // carried pi's raw JSONL events — the folding into one record per tool
287
+ // call is eventForwarder's job (agents.ts), via this module's own
288
+ // ToolCallTracker. Folding here too would fold twice.
289
+ const reply = await handle.read(receipt, { onEvent });
290
+ const reportEntries = reply.data["sf_report"];
291
+ const report = reportEntries && reportEntries.length > 0 ? reportEntries[reportEntries.length - 1] : null;
292
+ return makeAgentResult({
293
+ session_id: request.session_id,
294
+ text: reply.text,
295
+ report,
296
+ tokens: slot.usage.total_tokens,
297
+ cost: slot.usage.total_cost,
298
+ usage: slot.usage,
299
+ context_tokens: slot.context_tokens,
300
+ context_window: 0,
301
+ });
302
+ }
303
+ catch (error) {
304
+ if (error instanceof AgentRunError) {
305
+ // Verified by reproduction: a settled failure's `cause` is a plain
306
+ // durably-recorded error record — {name, message, type, details} —
307
+ // NOT a live Error instance (a durable settlement is read back from
308
+ // storage, not rethrown from the original throw site). Duck-type the
309
+ // message rather than checking `instanceof Error`, or every failure
310
+ // prints the unhelpful "[object Object]".
311
+ const cause = error.cause;
312
+ const detail = hasMessage(cause) ? cause.message : cause instanceof Error ? cause.message : cause ? JSON.stringify(cause) : error.message;
313
+ throw new Error(`flue submission ${error.outcome}: ${detail}`);
314
+ }
315
+ throw error;
316
+ }
317
+ finally {
318
+ pendingUsage.delete(receipt.submissionId);
319
+ onExit?.(pid);
320
+ }
321
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Config loading/validation and agent execution.
3
+ *
4
+ * Every ADW validates its agents before running (fail fast, nothing spawns
5
+ * against a half-valid config). Every agent call parses against a concrete
6
+ * output type; parse failures and gate violations re-prompt the SAME session
7
+ * with a correction — context intact, bounded retries. Agent proposes, code
8
+ * disposes.
9
+ */
10
+ import { GateReport, makeEventRecord, type AgentCall, type AgentConfig, type EnvelopeBase, type Phase, type SFConfig } from "./data_types.ts";
11
+ export declare class GateFailure extends Error {
12
+ }
13
+ /**
14
+ * Load and merge every existing path in `configPaths`, in order — later
15
+ * paths override earlier ones. Built-in defaults first, an optional `.spf/`
16
+ * override second is the normal case; a single explicit `--config` path
17
+ * (the caller passes just that one path, no built-in) is used standalone.
18
+ * A path that doesn't exist is silently skipped, EXCEPT that if none of them
19
+ * exist the result is an all-defaults config — every field has a schema
20
+ * default, so this degrades to "zero agents defined" rather than a crash,
21
+ * and agents.validate() reports that plainly.
22
+ */
23
+ export declare function loadConfig(configPaths: string[]): SFConfig;
24
+ export declare function resolve(cfg: SFConfig, name: string): AgentConfig;
25
+ /** Fail fast: every required name must resolve to a usable agent. */
26
+ export declare function validate(cfg: SFConfig, required: string[], requiredSuites?: string[], cwd?: string): void;
27
+ interface RunForAgents {
28
+ cfg: SFConfig;
29
+ adw_id: string;
30
+ repo_root: string;
31
+ spf_dir: string | null;
32
+ data_dir: string;
33
+ session_dir: string;
34
+ context_handoff_dir: string;
35
+ agent_map: Record<string, {
36
+ session_id: string;
37
+ model: string;
38
+ coding_agent: string;
39
+ }>;
40
+ tracer: {
41
+ event: (record: ReturnType<typeof makeEventRecord>) => string;
42
+ processStart: (adwId: string, kind: string, name: string, pid: number, command: string) => void;
43
+ processEnd: (adwId: string, pid: number) => void;
44
+ envelopeRow: (phase: Phase, agent: string, outputType: string, payloadJson: string, valid: boolean, attempt: number) => void;
45
+ gateRow: (phase: Phase, gate: string, report: GateReport, attempt: number) => void;
46
+ agentSessionRow: (adwId: string, agent: AgentConfig, sessionId: string, contextTokens?: number, contextWindow?: number) => void;
47
+ };
48
+ console: {
49
+ agentStarted: (name: string, model: string, sessionId: string) => void;
50
+ gateResult: (name: string, report: GateReport) => void;
51
+ retry: (name: string, attempt: number, limit: number, reason: string) => void;
52
+ envelopeSummary: (envelope: EnvelopeBase, typeName: string) => void;
53
+ agentFinished: (name: string, tokens: number, cost: number) => void;
54
+ };
55
+ addUsage: (tokens: number, cost: number) => void;
56
+ saveAgentMap: (agent: string, entry: {
57
+ session_id: string;
58
+ model: string;
59
+ coding_agent: string;
60
+ }) => void;
61
+ }
62
+ /** One agent call: render prompts -> pi run -> typed parse -> gates -> envelope. */
63
+ export declare function execute(run: RunForAgents, phase: Phase, call: AgentCall): Promise<EnvelopeBase>;
64
+ export {};