@vigolium/piolium 0.0.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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/agents/access-auditor.md +300 -0
  4. package/agents/assumption-breaker.md +154 -0
  5. package/agents/attack-designer.md +116 -0
  6. package/agents/code-scanner.md +139 -0
  7. package/agents/concurrency-auditor.md +238 -0
  8. package/agents/confirm-writer.md +257 -0
  9. package/agents/context-reviewer.md +274 -0
  10. package/agents/cross-verifier.md +165 -0
  11. package/agents/cve-scout.md +381 -0
  12. package/agents/env-builder.md +282 -0
  13. package/agents/env-profiler.md +205 -0
  14. package/agents/evidence-collector.md +140 -0
  15. package/agents/finding-grader.md +142 -0
  16. package/agents/finding-writer.md +148 -0
  17. package/agents/flow-tracer.md +106 -0
  18. package/agents/goal-backtracer.md +146 -0
  19. package/agents/history-miner.md +467 -0
  20. package/agents/independent-verifier.md +118 -0
  21. package/agents/intent-mapper.md +183 -0
  22. package/agents/longshot-collector.md +128 -0
  23. package/agents/longshot-prober.md +126 -0
  24. package/agents/patch-auditor.md +73 -0
  25. package/agents/poc-author.md +124 -0
  26. package/agents/poc-runner.md +194 -0
  27. package/agents/probe-lead.md +269 -0
  28. package/agents/red-challenger.md +101 -0
  29. package/agents/report-composer.md +208 -0
  30. package/agents/review-adjudicator.md +216 -0
  31. package/agents/spec-auditor.md +155 -0
  32. package/agents/taint-tracer.md +265 -0
  33. package/agents/test-locator.md +209 -0
  34. package/agents/threat-modeler.md +132 -0
  35. package/agents/variant-scanner.md +108 -0
  36. package/agents/variant-spotter.md +110 -0
  37. package/bin/piolium.mjs +376 -0
  38. package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
  39. package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
  40. package/extensions/piolium/agent-runner.ts +322 -0
  41. package/extensions/piolium/agents.ts +266 -0
  42. package/extensions/piolium/audit-state.ts +522 -0
  43. package/extensions/piolium/bundled-resources.ts +97 -0
  44. package/extensions/piolium/candidate-scan.ts +966 -0
  45. package/extensions/piolium/command-target.ts +177 -0
  46. package/extensions/piolium/console-stream.ts +57 -0
  47. package/extensions/piolium/export-results.ts +380 -0
  48. package/extensions/piolium/findings.ts +448 -0
  49. package/extensions/piolium/heartbeat.ts +182 -0
  50. package/extensions/piolium/help.ts +234 -0
  51. package/extensions/piolium/index.ts +1865 -0
  52. package/extensions/piolium/longshot.ts +530 -0
  53. package/extensions/piolium/matcher-suggestions.ts +196 -0
  54. package/extensions/piolium/matcher-utils.ts +83 -0
  55. package/extensions/piolium/modes/balanced.ts +750 -0
  56. package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
  57. package/extensions/piolium/modes/confirm.ts +697 -0
  58. package/extensions/piolium/modes/deep.ts +917 -0
  59. package/extensions/piolium/modes/diff.ts +177 -0
  60. package/extensions/piolium/modes/lite.ts +540 -0
  61. package/extensions/piolium/modes/longshot.ts +595 -0
  62. package/extensions/piolium/modes/merge.ts +204 -0
  63. package/extensions/piolium/modes/phase-runner.ts +267 -0
  64. package/extensions/piolium/modes/reinvest.ts +546 -0
  65. package/extensions/piolium/modes/revisit.ts +279 -0
  66. package/extensions/piolium/modes.ts +48 -0
  67. package/extensions/piolium/phase-labels.ts +123 -0
  68. package/extensions/piolium/phase-status-strip.ts +92 -0
  69. package/extensions/piolium/prompt-prefix-editor.ts +39 -0
  70. package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
  71. package/extensions/piolium/recon.ts +409 -0
  72. package/extensions/piolium/result-stats.ts +105 -0
  73. package/extensions/piolium/retry.ts +120 -0
  74. package/extensions/piolium/scheduler.ts +212 -0
  75. package/extensions/piolium/secrets.ts +368 -0
  76. package/extensions/piolium/tools/web-tools.ts +148 -0
  77. package/package.json +77 -0
  78. package/skills/agentic-actions-auditor/SKILL.md +327 -0
  79. package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
  80. package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
  81. package/skills/agentic-actions-auditor/references/foundations.md +94 -0
  82. package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
  83. package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
  84. package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
  85. package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
  86. package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
  87. package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
  88. package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
  89. package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
  90. package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
  91. package/skills/audit/SKILL.md +562 -0
  92. package/skills/audit/assets/icon.svg +7 -0
  93. package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
  94. package/skills/audit/references/adversarial-review.md +148 -0
  95. package/skills/audit/references/architecture-aware-sast.md +306 -0
  96. package/skills/audit/references/audit-workflow.md +737 -0
  97. package/skills/audit/references/chamber-protocol.md +384 -0
  98. package/skills/audit/references/creative-attack-modes.md +221 -0
  99. package/skills/audit/references/deep-analysis.md +273 -0
  100. package/skills/audit/references/domain-attack-playbooks.md +1129 -0
  101. package/skills/audit/references/knowledge-base-template.md +513 -0
  102. package/skills/audit/references/real-env-validation.md +191 -0
  103. package/skills/audit/references/report-templates.md +417 -0
  104. package/skills/audit/references/triage-and-prereqs.md +134 -0
  105. package/skills/audit/scripts/consolidate_drafts.py +554 -0
  106. package/skills/audit/scripts/partition_findings.py +152 -0
  107. package/skills/audit/scripts/rg-hotspots.sh +121 -0
  108. package/skills/audit/scripts/stamp_file_state.py +349 -0
  109. package/skills/code-reviewer/SKILL.md +65 -0
  110. package/skills/codeql/SKILL.md +281 -0
  111. package/skills/codeql/references/build-fixes.md +90 -0
  112. package/skills/codeql/references/diagnostic-query-templates.md +339 -0
  113. package/skills/codeql/references/extension-yaml-format.md +209 -0
  114. package/skills/codeql/references/important-only-suite.md +153 -0
  115. package/skills/codeql/references/language-details.md +207 -0
  116. package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
  117. package/skills/codeql/references/performance-tuning.md +111 -0
  118. package/skills/codeql/references/quality-assessment.md +172 -0
  119. package/skills/codeql/references/ruleset-catalog.md +63 -0
  120. package/skills/codeql/references/run-all-suite.md +92 -0
  121. package/skills/codeql/references/sarif-processing.md +79 -0
  122. package/skills/codeql/references/threat-models.md +51 -0
  123. package/skills/codeql/workflows/build-database.md +280 -0
  124. package/skills/codeql/workflows/create-data-extensions.md +261 -0
  125. package/skills/codeql/workflows/run-analysis.md +301 -0
  126. package/skills/differential-review/SKILL.md +220 -0
  127. package/skills/differential-review/adversarial.md +203 -0
  128. package/skills/differential-review/methodology.md +234 -0
  129. package/skills/differential-review/patterns.md +300 -0
  130. package/skills/differential-review/reporting.md +369 -0
  131. package/skills/fp-check/SKILL.md +125 -0
  132. package/skills/fp-check/references/bug-class-verification.md +114 -0
  133. package/skills/fp-check/references/deep-verification.md +143 -0
  134. package/skills/fp-check/references/evidence-templates.md +91 -0
  135. package/skills/fp-check/references/false-positive-patterns.md +115 -0
  136. package/skills/fp-check/references/gate-reviews.md +27 -0
  137. package/skills/fp-check/references/standard-verification.md +78 -0
  138. package/skills/insecure-defaults/SKILL.md +117 -0
  139. package/skills/insecure-defaults/references/examples.md +409 -0
  140. package/skills/last30days/SKILL.md +444 -0
  141. package/skills/sarif-parsing/SKILL.md +483 -0
  142. package/skills/sarif-parsing/resources/jq-queries.md +162 -0
  143. package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
  144. package/skills/security-threat-model/LICENSE.txt +201 -0
  145. package/skills/security-threat-model/SKILL.md +81 -0
  146. package/skills/security-threat-model/agents/openai.yaml +4 -0
  147. package/skills/security-threat-model/references/prompt-template.md +255 -0
  148. package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
  149. package/skills/semgrep/SKILL.md +212 -0
  150. package/skills/semgrep/references/rulesets.md +162 -0
  151. package/skills/semgrep/references/scan-modes.md +110 -0
  152. package/skills/semgrep/references/scanner-task-prompt.md +140 -0
  153. package/skills/semgrep/scripts/merge_sarif.py +203 -0
  154. package/skills/semgrep/workflows/scan-workflow.md +311 -0
  155. package/skills/semgrep-rule-creator/SKILL.md +168 -0
  156. package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
  157. package/skills/semgrep-rule-creator/references/workflow.md +240 -0
  158. package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
  159. package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
  160. package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
  161. package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
  162. package/skills/sharp-edges/SKILL.md +292 -0
  163. package/skills/sharp-edges/references/auth-patterns.md +252 -0
  164. package/skills/sharp-edges/references/case-studies.md +274 -0
  165. package/skills/sharp-edges/references/config-patterns.md +333 -0
  166. package/skills/sharp-edges/references/crypto-apis.md +190 -0
  167. package/skills/sharp-edges/references/lang-c.md +205 -0
  168. package/skills/sharp-edges/references/lang-csharp.md +285 -0
  169. package/skills/sharp-edges/references/lang-go.md +270 -0
  170. package/skills/sharp-edges/references/lang-java.md +263 -0
  171. package/skills/sharp-edges/references/lang-javascript.md +269 -0
  172. package/skills/sharp-edges/references/lang-kotlin.md +265 -0
  173. package/skills/sharp-edges/references/lang-php.md +245 -0
  174. package/skills/sharp-edges/references/lang-python.md +274 -0
  175. package/skills/sharp-edges/references/lang-ruby.md +273 -0
  176. package/skills/sharp-edges/references/lang-rust.md +272 -0
  177. package/skills/sharp-edges/references/lang-swift.md +287 -0
  178. package/skills/sharp-edges/references/language-specific.md +588 -0
  179. package/skills/spec-to-code-compliance/SKILL.md +357 -0
  180. package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
  181. package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
  182. package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
  183. package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
  184. package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
  185. package/skills/variant-analysis/METHODOLOGY.md +327 -0
  186. package/skills/variant-analysis/SKILL.md +142 -0
  187. package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
  188. package/skills/variant-analysis/resources/codeql/go.ql +69 -0
  189. package/skills/variant-analysis/resources/codeql/java.ql +71 -0
  190. package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
  191. package/skills/variant-analysis/resources/codeql/python.ql +80 -0
  192. package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
  193. package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
  194. package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
  195. package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
  196. package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
  197. package/skills/variant-analysis/resources/variant-report-template.md +75 -0
  198. package/skills/vuln-report/SKILL.md +137 -0
  199. package/skills/vuln-report/agents/openai.yaml +4 -0
  200. package/skills/vuln-report/references/report-template.md +135 -0
  201. package/skills/wooyun-legacy/SKILL.md +367 -0
  202. package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
  203. package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
  204. package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
  205. package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
  206. package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
  207. package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
  208. package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
  209. package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
  210. package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
  211. package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
  212. package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
  213. package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
  214. package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
  215. package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
  216. package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
  217. package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
  218. package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
  219. package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
  220. package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
  221. package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
  222. package/skills/wooyun-legacy/references/xss.md +746 -0
  223. package/skills/zeroize-audit/SKILL.md +371 -0
  224. package/skills/zeroize-audit/configs/c.yaml +21 -0
  225. package/skills/zeroize-audit/configs/default.yaml +128 -0
  226. package/skills/zeroize-audit/configs/rust.yaml +83 -0
  227. package/skills/zeroize-audit/prompts/report_template.md +238 -0
  228. package/skills/zeroize-audit/prompts/system.md +163 -0
  229. package/skills/zeroize-audit/prompts/task.md +97 -0
  230. package/skills/zeroize-audit/references/compile-commands.md +231 -0
  231. package/skills/zeroize-audit/references/detection-strategy.md +191 -0
  232. package/skills/zeroize-audit/references/ir-analysis.md +252 -0
  233. package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
  234. package/skills/zeroize-audit/references/poc-generation.md +470 -0
  235. package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
  236. package/skills/zeroize-audit/schemas/input.json +83 -0
  237. package/skills/zeroize-audit/schemas/output.json +140 -0
  238. package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
  239. package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
  240. package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
  241. package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
  242. package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
  243. package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
  244. package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
  245. package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
  246. package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
  247. package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
  248. package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
  249. package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
  250. package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
  251. package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
  252. package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
  253. package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
  254. package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
  255. package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
  256. package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
  257. package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
  258. package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
  259. package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
  260. package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
  261. package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
  262. package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
  263. package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
  264. package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
  265. package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
  266. package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
  267. package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
  268. package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
  269. package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
  270. package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
  271. package/themes/piolium-srcery.json +94 -0
@@ -0,0 +1,322 @@
1
+ /**
2
+ * In-process agent runner.
3
+ *
4
+ * Spawns a child Pi session via the SDK (`createAgentSession`) instead of
5
+ * forking a `pi --mode json` subprocess. Rationale:
6
+ * - Auth, model registry, and resource discovery already live in the parent
7
+ * process; in-process spawn inherits them automatically.
8
+ * - Subagent transcripts stream as plain `AgentSessionEvent` objects, no
9
+ * line-oriented JSON parsing needed.
10
+ * - Cancellation is one `agent.abort()` call instead of POSIX signal soup.
11
+ *
12
+ * Each run gets its own directory under
13
+ * `piolium/tmp/piolium/runs/<runId>/` containing:
14
+ * - prompt.md — task text + injected runtime header (for replay/debugging)
15
+ * - transcript.jsonl — every AgentSessionEvent, one per line
16
+ * - result.md — the child's final assistant text
17
+ * - error.txt — present iff the run errored
18
+ *
19
+ * The runner does not enforce concurrency by itself — call sites schedule
20
+ * runs through the `Scheduler` to honour the global burst cap.
21
+ */
22
+
23
+ import { type WriteStream, createWriteStream } from "node:fs";
24
+ import { writeFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
27
+ import type { ImageContent, Model, TextContent } from "@earendil-works/pi-ai";
28
+ import {
29
+ type AgentSessionEvent,
30
+ DefaultResourceLoader,
31
+ type ModelRegistry,
32
+ SessionManager,
33
+ type ToolDefinition,
34
+ createAgentSession,
35
+ getAgentDir,
36
+ } from "@earendil-works/pi-coding-agent";
37
+ import type { AgentDefinition } from "./agents.ts";
38
+ import type { AuditMode } from "./audit-state.ts";
39
+ import { getBundledSkillsDir } from "./bundled-resources.ts";
40
+ import { ensureRunDir } from "./scheduler.ts";
41
+ import { WEB_TOOLS } from "./tools/web-tools.ts";
42
+
43
+ export interface RuntimeContext {
44
+ /** Working directory the child should treat as the audited repository root. */
45
+ cwd: string;
46
+ mode: AuditMode;
47
+ /** Phase identifier (Q0/L1/P5/V3/...) — used for the runtime header. */
48
+ phase?: string;
49
+ /**
50
+ * Filesystem paths the child is allowed to write. Sub-agents are told to
51
+ * stay within these; the orchestrator merges fragments afterwards.
52
+ */
53
+ outputPaths?: string[];
54
+ /** Free-form notes appended to the runtime header (e.g. "git unavailable"). */
55
+ notes?: string[];
56
+ }
57
+
58
+ export interface RunAgentOptions {
59
+ agent: AgentDefinition;
60
+ /** The concrete task — becomes the first user message to the child. */
61
+ task: string;
62
+ /** Stable, per-task id used to name the transcript directory. */
63
+ runId: string;
64
+ runtime: RuntimeContext;
65
+ /**
66
+ * Optional model override. When omitted the child boots with the
67
+ * settings-derived default, matching the parent.
68
+ */
69
+ // biome-ignore lint/suspicious/noExplicitAny: pi-ai Model is generic over provider api
70
+ model?: Model<any>;
71
+ /**
72
+ * Optional parent registry. This preserves extension-registered providers
73
+ * and their request headers for child sessions without loading extensions
74
+ * inside the child agent.
75
+ */
76
+ modelRegistry?: ModelRegistry;
77
+ /**
78
+ * Thinking/reasoning level the child should use. Wire the parent's current
79
+ * level here so child phases reason at the same depth — without it the
80
+ * SDK falls back to "medium" (or "off"), regardless of how the parent
81
+ * was configured.
82
+ */
83
+ thinkingLevel?: ThinkingLevel;
84
+ /** External cancellation. Abort kills the child session. */
85
+ signal?: AbortSignal;
86
+ /** Forwarded copy of every child event — surface for UI integration. */
87
+ onEvent?: (event: AgentSessionEvent) => void;
88
+ /**
89
+ * When false (default), the child has no extensions loaded. Setting this
90
+ * is a footgun — extensions can re-register tools and recurse — so
91
+ * leave it off unless you know what you're doing.
92
+ */
93
+ noExtensions?: boolean;
94
+ }
95
+
96
+ export type AgentRuntimeModel = Pick<RunAgentOptions, "model" | "modelRegistry" | "thinkingLevel">;
97
+
98
+ export interface RunAgentResult {
99
+ /** The child's final assistant text (trimmed). Empty string if none was produced. */
100
+ text: string;
101
+ transcriptPath: string;
102
+ resultPath: string;
103
+ promptPath: string;
104
+ durationMs: number;
105
+ /** Last assistant `stopReason`, if any (e.g. "end_turn", "error"). */
106
+ stopReason?: string;
107
+ /** Populated when the child errored or aborted. */
108
+ errorMessage?: string;
109
+ }
110
+
111
+ export class AgentRunError extends Error {
112
+ constructor(
113
+ message: string,
114
+ public readonly result: RunAgentResult,
115
+ ) {
116
+ super(message);
117
+ this.name = "AgentRunError";
118
+ }
119
+ }
120
+
121
+ const TRANSCRIPT_STRING_LIMIT = 8_000;
122
+
123
+ export function buildRuntimeHeader(runtime: RuntimeContext): string {
124
+ const lines: string[] = ["# piolium Runtime", ""];
125
+ lines.push(`- Target repository: ${runtime.cwd}`);
126
+ lines.push("- Audit directory: piolium/");
127
+ lines.push("- Audit state: piolium/audit-state.json");
128
+ lines.push(`- Mode: ${runtime.mode}`);
129
+ if (runtime.phase) lines.push(`- Phase: ${runtime.phase}`);
130
+ if (runtime.outputPaths && runtime.outputPaths.length > 0) {
131
+ lines.push(`- Assigned output paths: ${runtime.outputPaths.join(", ")}`);
132
+ lines.push("- Do not write outside the assigned paths unless this prompt explicitly says to.");
133
+ }
134
+ lines.push("- Keep findings on disk; do not keep important state only in conversation memory.");
135
+ lines.push(
136
+ "- If blocked, write a short failure note to your assigned output path and exit cleanly.",
137
+ );
138
+ if (runtime.notes && runtime.notes.length > 0) {
139
+ lines.push("");
140
+ lines.push("Operator notes:");
141
+ for (const note of runtime.notes) lines.push(`- ${note}`);
142
+ }
143
+ return lines.join("\n");
144
+ }
145
+
146
+ function truncateTranscriptString(value: string): string {
147
+ if (value.length <= TRANSCRIPT_STRING_LIMIT) return value;
148
+ return `${value.slice(0, TRANSCRIPT_STRING_LIMIT)}\n...[truncated ${value.length - TRANSCRIPT_STRING_LIMIT} chars]`;
149
+ }
150
+
151
+ function compactTranscriptValue(value: unknown, depth = 0): unknown {
152
+ if (typeof value === "string") return truncateTranscriptString(value);
153
+ if (value === null || typeof value !== "object") return value;
154
+ if (depth > 8) return "[max depth]";
155
+ if (Array.isArray(value)) return value.map((item) => compactTranscriptValue(item, depth + 1));
156
+
157
+ const out: Record<string, unknown> = {};
158
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
159
+ if (key === "thinkingSignature" || key === "encrypted_content" || key === "partial") continue;
160
+ out[key] = compactTranscriptValue(child, depth + 1);
161
+ }
162
+ return out;
163
+ }
164
+
165
+ export function compactTranscriptEvent(event: AgentSessionEvent): unknown {
166
+ if (event.type !== "message_update") return compactTranscriptValue(event);
167
+
168
+ const updateEvent = event as AgentSessionEvent & {
169
+ assistantMessageEvent?: Record<string, unknown>;
170
+ };
171
+ return {
172
+ type: event.type,
173
+ assistantMessageEvent: compactTranscriptValue(updateEvent.assistantMessageEvent ?? {}),
174
+ };
175
+ }
176
+
177
+ function extractAssistantText(content: unknown): string {
178
+ if (typeof content === "string") return content;
179
+ if (!Array.isArray(content)) return "";
180
+ return content
181
+ .filter(
182
+ (c): c is TextContent | ImageContent =>
183
+ !!c && typeof c === "object" && (c as { type?: string }).type === "text",
184
+ )
185
+ .map((c) => (c as TextContent).text ?? "")
186
+ .join("");
187
+ }
188
+
189
+ export async function runAgent(options: RunAgentOptions): Promise<RunAgentResult> {
190
+ const start = Date.now();
191
+ const runDir = ensureRunDir(options.runtime.cwd, options.runId);
192
+ const transcriptPath = join(runDir, "transcript.jsonl");
193
+ const resultPath = join(runDir, "result.md");
194
+ const promptPath = join(runDir, "prompt.md");
195
+ const errorPath = join(runDir, "error.txt");
196
+
197
+ const header = buildRuntimeHeader(options.runtime);
198
+ const composedSystemPrompt = `${header}\n\n${options.agent.systemPrompt}`;
199
+
200
+ writeFileSync(
201
+ promptPath,
202
+ [
203
+ `# Run ${options.runId}`,
204
+ `Agent: ${options.agent.name}`,
205
+ `Source: ${options.agent.sourcePath}`,
206
+ "",
207
+ "## Task",
208
+ "",
209
+ options.task,
210
+ "",
211
+ "## System prompt (header + agent body)",
212
+ "",
213
+ composedSystemPrompt,
214
+ ].join("\n"),
215
+ );
216
+
217
+ const transcript: WriteStream = createWriteStream(transcriptPath, { flags: "a" });
218
+ const writeEvent = (event: AgentSessionEvent) => {
219
+ try {
220
+ transcript.write(`${JSON.stringify(compactTranscriptEvent(event))}\n`);
221
+ } catch {
222
+ // transcript loss is non-fatal; final result still surfaces via return value
223
+ }
224
+ options.onEvent?.(event);
225
+ };
226
+
227
+ const resourceLoader = new DefaultResourceLoader({
228
+ cwd: options.runtime.cwd,
229
+ agentDir: getAgentDir(),
230
+ systemPrompt: composedSystemPrompt,
231
+ additionalSkillPaths: [getBundledSkillsDir()],
232
+ noExtensions: options.noExtensions ?? true,
233
+ noSkills: false,
234
+ noPromptTemplates: true,
235
+ noThemes: true,
236
+ noContextFiles: true,
237
+ });
238
+ await resourceLoader.reload();
239
+
240
+ const childCustomTools: ToolDefinition[] = [...WEB_TOOLS];
241
+
242
+ const allowedTools = options.agent.allowedTools;
243
+ const { session } = await createAgentSession({
244
+ cwd: options.runtime.cwd,
245
+ agentDir: getAgentDir(),
246
+ ...(options.model ? { model: options.model } : {}),
247
+ ...(options.modelRegistry ? { modelRegistry: options.modelRegistry } : {}),
248
+ ...(options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),
249
+ tools: allowedTools,
250
+ sessionManager: SessionManager.inMemory(),
251
+ resourceLoader,
252
+ customTools: childCustomTools,
253
+ ...(allowedTools.length === 0 ? { noTools: "all" as const } : {}),
254
+ });
255
+
256
+ let finalText = "";
257
+ let stopReason: string | undefined;
258
+ let errorMessage: string | undefined;
259
+
260
+ const unsubscribe = session.subscribe((event) => {
261
+ writeEvent(event);
262
+ if (event.type === "message_end") {
263
+ const message = event.message as {
264
+ role?: string;
265
+ content?: unknown;
266
+ stopReason?: string;
267
+ errorMessage?: string;
268
+ };
269
+ if (message.role !== "assistant") return;
270
+ if (message.stopReason) stopReason = message.stopReason;
271
+ if (message.errorMessage) errorMessage = message.errorMessage;
272
+ const text = extractAssistantText(message.content);
273
+ if (text) finalText = text;
274
+ }
275
+ });
276
+
277
+ const abortListener = () => session.agent.abort();
278
+ options.signal?.addEventListener("abort", abortListener, { once: true });
279
+
280
+ let runError: unknown;
281
+ try {
282
+ await session.prompt(options.task);
283
+ await session.agent.waitForIdle();
284
+ } catch (err) {
285
+ runError = err;
286
+ } finally {
287
+ options.signal?.removeEventListener("abort", abortListener);
288
+ unsubscribe();
289
+ session.dispose();
290
+ await new Promise<void>((resolve) => transcript.end(() => resolve()));
291
+ }
292
+
293
+ if (!errorMessage && runError) {
294
+ errorMessage = runError instanceof Error ? runError.message : String(runError);
295
+ }
296
+ if (!errorMessage && !finalText && stopReason === "error") {
297
+ errorMessage = "Child session ended in error with no message.";
298
+ }
299
+
300
+ const trimmed = finalText.trim();
301
+ if (trimmed) {
302
+ writeFileSync(resultPath, `${trimmed}\n`);
303
+ }
304
+ if (errorMessage) {
305
+ writeFileSync(errorPath, `${errorMessage}\n`);
306
+ }
307
+
308
+ const result: RunAgentResult = {
309
+ text: trimmed,
310
+ transcriptPath,
311
+ resultPath,
312
+ promptPath,
313
+ durationMs: Date.now() - start,
314
+ ...(stopReason ? { stopReason } : {}),
315
+ ...(errorMessage ? { errorMessage } : {}),
316
+ };
317
+
318
+ if (errorMessage) {
319
+ throw new AgentRunError(errorMessage, result);
320
+ }
321
+ return result;
322
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Sub-agent loader.
3
+ *
4
+ * Reads Claude Code-style agent manifests (`agents/<name>.md` with YAML
5
+ * frontmatter) and produces `AgentDefinition` records the runner can hand
6
+ * straight to `createAgentSession`. Tool names from Claude's vocabulary
7
+ * (Read, Bash, Glob, Agent, SendMessage…) are translated to Pi names at
8
+ * load time so individual agent files stay faithful to the upstream
9
+ * archon-audit source.
10
+ *
11
+ * Adapted from piolium's `src/subagents/loader.ts`. Differences:
12
+ * - We don't use the same diagnostic taxonomy; collisions just keep the
13
+ * first-seen entry (project > user > bundled) and the warning surfaces
14
+ * in `LoadResult.warnings`.
15
+ * - Skill name list is captured as `skills` (string[]) so the runner can
16
+ * pre-load them through the resource loader.
17
+ */
18
+
19
+ import { readFileSync, readdirSync, statSync } from "node:fs";
20
+ import { basename, extname, join } from "node:path";
21
+ // Vendored bundle so a fresh `pi install <repo-path>` works without
22
+ // running `bun install` first (the npm-resolved "yaml" package would
23
+ // fail to load on a clone with no node_modules). Refresh with:
24
+ // bun build node_modules/yaml/dist/index.js \
25
+ // --outfile extensions/piolium/_vendor/yaml.bundle.mjs \
26
+ // --target node --format esm --minify
27
+ import { parse as parseYaml } from "./_vendor/yaml.bundle.mjs";
28
+ import { getAgentSearchPaths } from "./bundled-resources.ts";
29
+
30
+ export interface AgentDefinition {
31
+ /** Stable identifier — derived from filename, e.g. `code-scanner`. */
32
+ name: string;
33
+ description: string;
34
+ systemPrompt: string;
35
+ /** Pi-style tool names (already translated from Claude vocabulary). */
36
+ allowedTools: string[];
37
+ /** Optional model alias — `opus`, `sonnet`, `haiku`, or a concrete id. */
38
+ model?: string;
39
+ /** Skills the runner should preload for the child session. */
40
+ skills: string[];
41
+ sourcePath: string;
42
+ }
43
+
44
+ export interface LoaderDiagnostic {
45
+ level: "warn" | "error";
46
+ path: string;
47
+ message: string;
48
+ }
49
+
50
+ export interface LoadResult {
51
+ agents: Map<string, AgentDefinition>;
52
+ diagnostics: LoaderDiagnostic[];
53
+ }
54
+
55
+ const TOOL_TRANSLATION: Record<string, string | null> = {
56
+ Read: "read",
57
+ Write: "write",
58
+ Edit: "edit",
59
+ Bash: "bash",
60
+ Grep: "grep",
61
+ Glob: "find",
62
+ LS: "ls",
63
+ Ls: "ls",
64
+ // Claude's `Agent` tool == "spawn another sub-agent". For piolium we
65
+ // expose this only to a small set of orchestrator-style agents (KB
66
+ // builder, static analyzer, etc.); the actual spawning is handled by the
67
+ // extension runner, not by the child session itself. Translating to a
68
+ // stable Pi tool name keeps the surface predictable.
69
+ Agent: "spawn_agent",
70
+ WebFetch: "WebFetch",
71
+ WebSearch: "WebSearch",
72
+ // SendMessage is how chamber agents coordinate in upstream Archon. Pi has
73
+ // no inter-agent messaging primitive; the orchestrator replaces it with
74
+ // shared-file rounds. Drop it from the child's allowlist.
75
+ SendMessage: null,
76
+ };
77
+
78
+ export interface TranslateResult {
79
+ tools: string[];
80
+ dropped: string[];
81
+ }
82
+
83
+ export function translateTools(input: unknown): TranslateResult {
84
+ const raw = normalizeToolList(input);
85
+ const tools: string[] = [];
86
+ const seen = new Set<string>();
87
+ const dropped: string[] = [];
88
+ for (const name of raw) {
89
+ const trimmed = name.trim();
90
+ if (!trimmed) continue;
91
+ // Already a lowercase pi tool? Keep verbatim.
92
+ if (/^[a-z][a-z0-9_]*$/.test(trimmed)) {
93
+ if (!seen.has(trimmed)) {
94
+ seen.add(trimmed);
95
+ tools.push(trimmed);
96
+ }
97
+ continue;
98
+ }
99
+ const mapped = TOOL_TRANSLATION[trimmed];
100
+ if (mapped === null || mapped === undefined) {
101
+ dropped.push(trimmed);
102
+ continue;
103
+ }
104
+ if (!seen.has(mapped)) {
105
+ seen.add(mapped);
106
+ tools.push(mapped);
107
+ }
108
+ }
109
+ return { tools, dropped };
110
+ }
111
+
112
+ function normalizeToolList(input: unknown): string[] {
113
+ if (Array.isArray(input)) return input.map((x) => String(x));
114
+ if (typeof input !== "string") return [];
115
+ return input.split(/[,\s]+/).filter(Boolean);
116
+ }
117
+
118
+ const AGENT_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
119
+
120
+ export function isValidAgentName(name: string): boolean {
121
+ return name.length > 0 && name.length <= 64 && AGENT_NAME_RE.test(name) && !name.includes("--");
122
+ }
123
+
124
+ interface FrontmatterParsed {
125
+ frontmatter: Record<string, unknown>;
126
+ body: string;
127
+ }
128
+
129
+ export function splitFrontmatter(content: string): FrontmatterParsed {
130
+ if (!content.startsWith("---")) return { frontmatter: {}, body: content };
131
+ const rest = content.slice(3);
132
+ const endIdx = rest.indexOf("\n---");
133
+ if (endIdx < 0) return { frontmatter: {}, body: content };
134
+ const yamlBlock = rest.slice(0, endIdx);
135
+ const afterClose = rest.slice(endIdx + "\n---".length);
136
+ const body = afterClose.replace(/^[\r\n]+/, "");
137
+ const frontmatter = (parseYaml(yamlBlock) as Record<string, unknown> | null) ?? {};
138
+ return { frontmatter, body };
139
+ }
140
+
141
+ export function parseAgentFile(
142
+ filePath: string,
143
+ content: string,
144
+ ): { agent?: AgentDefinition; diagnostics: LoaderDiagnostic[] } {
145
+ const diagnostics: LoaderDiagnostic[] = [];
146
+ const name = basename(filePath, extname(filePath));
147
+
148
+ if (!isValidAgentName(name)) {
149
+ diagnostics.push({
150
+ level: "error",
151
+ path: filePath,
152
+ message: `Invalid agent name "${name}" — must match /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, no consecutive hyphens, ≤64 chars.`,
153
+ });
154
+ return { diagnostics };
155
+ }
156
+
157
+ const { frontmatter, body } = splitFrontmatter(content);
158
+ const description =
159
+ typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
160
+ if (!description) {
161
+ diagnostics.push({
162
+ level: "error",
163
+ path: filePath,
164
+ message: "Frontmatter missing required `description` field.",
165
+ });
166
+ return { diagnostics };
167
+ }
168
+ if (!body.trim()) {
169
+ diagnostics.push({ level: "error", path: filePath, message: "Body is empty." });
170
+ return { diagnostics };
171
+ }
172
+
173
+ const { tools: allowedTools, dropped } = translateTools(frontmatter.tools);
174
+ if (dropped.length > 0) {
175
+ diagnostics.push({
176
+ level: "warn",
177
+ path: filePath,
178
+ message: `Dropped unsupported tools: ${dropped.join(", ")}.`,
179
+ });
180
+ }
181
+
182
+ const model =
183
+ typeof frontmatter.model === "string" && frontmatter.model.trim()
184
+ ? frontmatter.model.trim()
185
+ : undefined;
186
+
187
+ const skills = normalizeSkillList(frontmatter.skills);
188
+
189
+ return {
190
+ agent: {
191
+ name,
192
+ description,
193
+ systemPrompt: body.trim(),
194
+ allowedTools,
195
+ ...(model ? { model } : {}),
196
+ skills,
197
+ sourcePath: filePath,
198
+ },
199
+ diagnostics,
200
+ };
201
+ }
202
+
203
+ function normalizeSkillList(input: unknown): string[] {
204
+ if (Array.isArray(input)) return input.map((s) => String(s).trim()).filter(Boolean);
205
+ if (typeof input === "string") {
206
+ return input
207
+ .split(/[,\s]+/)
208
+ .map((s) => s.trim())
209
+ .filter(Boolean);
210
+ }
211
+ return [];
212
+ }
213
+
214
+ export function loadAgentsFromDirs(dirs: string[]): LoadResult {
215
+ const agents = new Map<string, AgentDefinition>();
216
+ const diagnostics: LoaderDiagnostic[] = [];
217
+
218
+ for (const dir of dirs) {
219
+ let entries: string[];
220
+ try {
221
+ const stat = statSync(dir);
222
+ if (!stat.isDirectory()) continue;
223
+ entries = readdirSync(dir);
224
+ } catch {
225
+ continue; // missing dir — fine, just skip
226
+ }
227
+ for (const entry of entries) {
228
+ if (!entry.endsWith(".md")) continue;
229
+ const filePath = join(dir, entry);
230
+ let content: string;
231
+ try {
232
+ content = readFileSync(filePath, "utf8");
233
+ } catch (err) {
234
+ diagnostics.push({
235
+ level: "error",
236
+ path: filePath,
237
+ message: `Failed to read: ${(err as Error).message}`,
238
+ });
239
+ continue;
240
+ }
241
+ const { agent, diagnostics: fileDiags } = parseAgentFile(filePath, content);
242
+ diagnostics.push(...fileDiags);
243
+ if (!agent) continue;
244
+ if (agents.has(agent.name)) {
245
+ diagnostics.push({
246
+ level: "warn",
247
+ path: filePath,
248
+ message: `Duplicate agent name "${agent.name}" — keeping ${agents.get(agent.name)?.sourcePath}, ignoring this file.`,
249
+ });
250
+ continue;
251
+ }
252
+ agents.set(agent.name, agent);
253
+ }
254
+ }
255
+
256
+ return { agents, diagnostics };
257
+ }
258
+
259
+ export interface LoadOptions {
260
+ cwd: string;
261
+ allowProjectAgents?: boolean;
262
+ }
263
+
264
+ export function loadAgents(options: LoadOptions): LoadResult {
265
+ return loadAgentsFromDirs(getAgentSearchPaths(options.cwd, options.allowProjectAgents ?? false));
266
+ }