@adhdev/daemon-core 0.9.82-rc.2 → 0.9.82-rc.200

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 (244) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +3 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/chat/source-machine.d.ts +166 -0
  4. package/dist/chat/source-resolver.d.ts +104 -0
  5. package/dist/chat/subscription-updates.d.ts +1 -0
  6. package/dist/cli-adapter-types.d.ts +23 -2
  7. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  8. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  9. package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
  10. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  11. package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
  12. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  13. package/dist/commands/cli-manager.d.ts +2 -1
  14. package/dist/commands/handler.d.ts +110 -0
  15. package/dist/commands/mesh-coordinator.d.ts +85 -1
  16. package/dist/commands/router.d.ts +29 -1
  17. package/dist/config/chat-history.d.ts +9 -0
  18. package/dist/config/config.d.ts +5 -0
  19. package/dist/config/mesh-config.d.ts +72 -1
  20. package/dist/git/git-commands.d.ts +8 -1
  21. package/dist/git/git-status.d.ts +5 -0
  22. package/dist/git/git-types.d.ts +12 -0
  23. package/dist/index.d.ts +39 -10
  24. package/dist/index.js +29425 -12026
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +25700 -8378
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/installer.d.ts +1 -4
  29. package/dist/ipc/local-ipc-server.d.ts +91 -0
  30. package/dist/launch.d.ts +1 -1
  31. package/dist/logging/async-batch-writer.d.ts +10 -0
  32. package/dist/mesh/contracts.d.ts +164 -0
  33. package/dist/mesh/coordinator-prompt.d.ts +30 -0
  34. package/dist/mesh/coordinator-registry.d.ts +59 -0
  35. package/dist/mesh/mesh-active-work.d.ts +90 -0
  36. package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
  37. package/dist/mesh/mesh-events.d.ts +167 -6
  38. package/dist/mesh/mesh-fast-forward.d.ts +41 -0
  39. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  40. package/dist/mesh/mesh-ledger.d.ts +58 -1
  41. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  42. package/dist/mesh/mesh-runtime-store.d.ts +149 -0
  43. package/dist/mesh/mesh-work-queue.d.ts +55 -6
  44. package/dist/mesh/preview-freshness.d.ts +18 -0
  45. package/dist/mesh/refine-config.d.ts +193 -0
  46. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  47. package/dist/providers/approval-utils.d.ts +13 -0
  48. package/dist/providers/chat-message-normalization.d.ts +1 -0
  49. package/dist/providers/cli-provider-instance.d.ts +39 -3
  50. package/dist/providers/contracts.d.ts +130 -6
  51. package/dist/providers/external-sources.d.ts +71 -0
  52. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  53. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  54. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  55. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  56. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  57. package/dist/providers/native-history/index.d.ts +13 -0
  58. package/dist/providers/provider-instance-manager.d.ts +12 -0
  59. package/dist/providers/provider-instance.d.ts +13 -1
  60. package/dist/providers/provider-loader.d.ts +26 -4
  61. package/dist/providers/provider-trust.d.ts +31 -0
  62. package/dist/providers/read-chat-contract.d.ts +29 -0
  63. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  64. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  65. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  66. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  67. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  68. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  69. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  70. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  71. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  72. package/dist/providers/sdk/v1/index.d.ts +30 -0
  73. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  74. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  75. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  76. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  77. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  78. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  79. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  80. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  81. package/dist/providers/spec/adapter.d.ts +60 -0
  82. package/dist/providers/spec/cli-adapter.d.ts +98 -0
  83. package/dist/providers/spec/driver.d.ts +190 -0
  84. package/dist/providers/spec/evaluator.d.ts +55 -0
  85. package/dist/providers/spec/loader.d.ts +14 -0
  86. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  87. package/dist/providers/spec/route.d.ts +4 -0
  88. package/dist/providers/spec/schema.gen.d.ts +599 -0
  89. package/dist/providers/spec/types.d.ts +282 -0
  90. package/dist/providers/transcript-v2.d.ts +176 -0
  91. package/dist/providers/types/interactive-prompt.d.ts +48 -0
  92. package/dist/repo-mesh-types.d.ts +230 -1
  93. package/dist/sessions/registry.d.ts +3 -0
  94. package/dist/shared-types-extra.d.ts +1 -1
  95. package/dist/shared-types.d.ts +42 -1
  96. package/dist/status/normalize.d.ts +1 -1
  97. package/dist/status/normalize.js +1 -0
  98. package/dist/status/normalize.js.map +1 -1
  99. package/dist/status/normalize.mjs +1 -0
  100. package/dist/status/normalize.mjs.map +1 -1
  101. package/dist/status/reporter.d.ts +2 -0
  102. package/dist/status/snapshot.d.ts +1 -0
  103. package/dist/types.d.ts +5 -0
  104. package/package.json +7 -2
  105. package/src/agent-stream/poller.ts +2 -3
  106. package/src/boot/daemon-lifecycle.ts +22 -10
  107. package/src/boot/process-hardening.ts +89 -0
  108. package/src/chat/source-machine.ts +534 -0
  109. package/src/chat/source-resolver.ts +0 -0
  110. package/src/chat/subscription-updates.ts +20 -1
  111. package/src/cli-adapter-types.d.ts +1 -0
  112. package/src/cli-adapter-types.ts +22 -2
  113. package/src/cli-adapters/cli-script-runner.ts +421 -0
  114. package/src/cli-adapters/cli-state-engine.ts +1138 -0
  115. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  116. package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
  117. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  118. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  119. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  120. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  121. package/src/cli-adapters/provider-cli-shared.ts +59 -11
  122. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  123. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  124. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  125. package/src/commands/chat-commands.ts +1787 -60
  126. package/src/commands/cli-manager.ts +283 -14
  127. package/src/commands/handler.ts +809 -2
  128. package/src/commands/mesh-coordinator.ts +331 -122
  129. package/src/commands/router.ts +3915 -546
  130. package/src/config/chat-history.ts +95 -24
  131. package/src/config/config.ts +12 -0
  132. package/src/config/mesh-config.ts +280 -2
  133. package/src/config/recent-activity.ts +8 -2
  134. package/src/daemon/dev-cli-debug.ts +10 -1
  135. package/src/detection/ide-detector.ts +26 -16
  136. package/src/git/git-commands.ts +38 -8
  137. package/src/git/git-status.ts +132 -12
  138. package/src/git/git-summary.ts +3 -0
  139. package/src/git/git-types.ts +13 -0
  140. package/src/git/git-worktree.ts +8 -1
  141. package/src/index.ts +127 -9
  142. package/src/installer.d.ts +1 -1
  143. package/src/installer.ts +8 -6
  144. package/src/ipc/local-ipc-server.ts +278 -0
  145. package/src/launch.d.ts +1 -1
  146. package/src/launch.ts +37 -28
  147. package/src/logging/async-batch-writer.ts +55 -0
  148. package/src/logging/logger.ts +2 -1
  149. package/src/mesh/contracts.ts +329 -0
  150. package/src/mesh/coordinator-prompt.ts +204 -30
  151. package/src/mesh/coordinator-registry.ts +121 -0
  152. package/src/mesh/mesh-active-work.ts +437 -0
  153. package/src/mesh/mesh-delivery-policy.ts +298 -0
  154. package/src/mesh/mesh-events.ts +1352 -109
  155. package/src/mesh/mesh-fast-forward.ts +438 -0
  156. package/src/mesh/mesh-host-ownership.ts +73 -0
  157. package/src/mesh/mesh-ledger.ts +457 -104
  158. package/src/mesh/mesh-refine-status.ts +144 -0
  159. package/src/mesh/mesh-runtime-store.ts +769 -0
  160. package/src/mesh/mesh-work-queue.ts +267 -159
  161. package/src/mesh/preview-freshness.ts +118 -0
  162. package/src/mesh/refine-config.ts +366 -0
  163. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  164. package/src/providers/approval-utils.d.ts +4 -0
  165. package/src/providers/approval-utils.ts +47 -5
  166. package/src/providers/chat-message-normalization.ts +7 -12
  167. package/src/providers/cli-provider-instance.ts +806 -64
  168. package/src/providers/contracts.d.ts +55 -0
  169. package/src/providers/contracts.ts +141 -6
  170. package/src/providers/external-sources.ts +218 -0
  171. package/src/providers/ide-provider-instance.ts +19 -5
  172. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  173. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  174. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  175. package/src/providers/native-history/dispatcher.ts +340 -0
  176. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  177. package/src/providers/native-history/index.ts +30 -0
  178. package/src/providers/provider-instance-manager.ts +30 -0
  179. package/src/providers/provider-instance.ts +10 -1
  180. package/src/providers/provider-loader.ts +582 -50
  181. package/src/providers/provider-schema.ts +87 -14
  182. package/src/providers/provider-trust.ts +114 -0
  183. package/src/providers/read-chat-contract.ts +76 -16
  184. package/src/providers/sdk/README.md +49 -0
  185. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  186. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  187. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  188. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  189. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  190. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  191. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  192. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  193. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  194. package/src/providers/sdk/v1/index.ts +152 -0
  195. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  196. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  197. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  198. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  199. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  200. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  201. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  202. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  203. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  204. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  205. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  206. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  207. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  208. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  209. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  210. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  211. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  212. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  213. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  214. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  215. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  216. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  217. package/src/providers/sdk/v1/validators/index.ts +19 -0
  218. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  219. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  220. package/src/providers/spec/adapter.ts +176 -0
  221. package/src/providers/spec/cli-adapter.ts +538 -0
  222. package/src/providers/spec/driver.ts +725 -0
  223. package/src/providers/spec/evaluator.ts +373 -0
  224. package/src/providers/spec/loader.ts +130 -0
  225. package/src/providers/spec/native-history-executor.ts +939 -0
  226. package/src/providers/spec/route.ts +51 -0
  227. package/src/providers/spec/schema.gen.ts +559 -0
  228. package/src/providers/spec/schema.json +237 -0
  229. package/src/providers/spec/types.ts +301 -0
  230. package/src/providers/transcript-v2.ts +567 -0
  231. package/src/providers/types/interactive-prompt.ts +425 -0
  232. package/src/providers/version-archive.ts +38 -20
  233. package/src/repo-mesh-types.ts +250 -1
  234. package/src/sessions/registry.ts +3 -0
  235. package/src/shared-types-extra.ts +1 -1
  236. package/src/shared-types.ts +45 -1
  237. package/src/status/builders.ts +24 -6
  238. package/src/status/normalize.ts +2 -0
  239. package/src/status/reporter.ts +15 -0
  240. package/src/status/snapshot.ts +84 -25
  241. package/src/system/host-memory.ts +29 -12
  242. package/src/types.ts +5 -0
  243. package/dist/mesh/mesh-sync.d.ts +0 -53
  244. package/src/mesh/mesh-sync.ts +0 -111
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Manifest validators — runtime JSON Schema check for v1 provider manifests.
3
+ *
4
+ * The schemas live under `schemas/{category}/provider.schema.json` and are
5
+ * the authoritative contract for what a v1 manifest may contain. Daemon
6
+ * code that loads a manifest at install or boot time runs it through
7
+ * `validateCliProviderManifest()` first; failures are reported back to
8
+ * the caller with structured paths so manifest authors can locate the
9
+ * offending field without guessing.
10
+ *
11
+ * Validation lives in the SDK layer (not in provider-loader) so dashboards,
12
+ * registry workers, and the provider publish flow can all reuse the
13
+ * same code path and produce identical error messages.
14
+ */
15
+
16
+ import Ajv2020, { type ErrorObject, type ValidateFunction } from 'ajv/dist/2020.js';
17
+ import addFormats from 'ajv-formats';
18
+ import cliProviderSchema from '../schemas/cli/provider.schema.json';
19
+
20
+ export interface ManifestValidationIssue {
21
+ /** JSON Pointer-style path into the manifest. Empty string for the root. */
22
+ path: string;
23
+ /** Schema-keyword that failed. */
24
+ keyword: string;
25
+ /** Human-readable explanation. */
26
+ message: string;
27
+ /** Allowed values when the failure is an `enum` / `const` / `additionalProperties`. */
28
+ allowed?: unknown;
29
+ }
30
+
31
+ export interface ManifestValidationResult {
32
+ ok: boolean;
33
+ issues: ManifestValidationIssue[];
34
+ }
35
+
36
+ let _ajv: Ajv2020 | null = null;
37
+ let _cliValidator: ValidateFunction | null = null;
38
+
39
+ function getAjv(): Ajv2020 {
40
+ if (_ajv) return _ajv;
41
+ // strict=false because the schema authors freely use schema-level
42
+ // `description`/`examples` fields that ajv would otherwise warn about.
43
+ // allErrors=true so a single validation run reports every problem at
44
+ // once instead of bailing on the first — that's what makes the
45
+ // structured-error output usable for manifest authors.
46
+ _ajv = new Ajv2020({
47
+ strict: false,
48
+ allErrors: true,
49
+ allowUnionTypes: true,
50
+ });
51
+ addFormats(_ajv);
52
+ return _ajv;
53
+ }
54
+
55
+ function getCliValidator(): ValidateFunction {
56
+ if (_cliValidator) return _cliValidator;
57
+ _cliValidator = getAjv().compile(cliProviderSchema as unknown as object);
58
+ return _cliValidator;
59
+ }
60
+
61
+ function formatIssue(err: ErrorObject): ManifestValidationIssue {
62
+ const path = err.instancePath || '';
63
+ const params = err.params as Record<string, unknown>;
64
+ let message = err.message || 'validation failed';
65
+ let allowed: unknown;
66
+
67
+ if (err.keyword === 'additionalProperties') {
68
+ const extra = params.additionalProperty;
69
+ message = `unexpected property "${extra}"`;
70
+ } else if (err.keyword === 'required') {
71
+ message = `missing required property "${params.missingProperty}"`;
72
+ } else if (err.keyword === 'enum') {
73
+ allowed = params.allowedValues;
74
+ message = `must be one of ${JSON.stringify(allowed)}`;
75
+ } else if (err.keyword === 'const') {
76
+ allowed = params.allowedValue;
77
+ message = `must equal ${JSON.stringify(allowed)}`;
78
+ } else if (err.keyword === 'type') {
79
+ message = `must be ${params.type}`;
80
+ }
81
+
82
+ return { path, keyword: err.keyword, message, ...(allowed !== undefined ? { allowed } : {}) };
83
+ }
84
+
85
+ /**
86
+ * Validate a CLI provider.v1.json manifest. Returns `{ ok: true, issues: [] }`
87
+ * on success and `{ ok: false, issues: [...] }` on failure with each issue
88
+ * pointing at the offending field. Never throws — manifest parse errors
89
+ * should be caught by the caller before this is called.
90
+ */
91
+ export function validateCliProviderManifest(manifest: unknown): ManifestValidationResult {
92
+ const validator = getCliValidator();
93
+ const ok = validator(manifest) as boolean;
94
+ if (ok) return { ok: true, issues: [] };
95
+ const issues = (validator.errors || []).map(formatIssue);
96
+ return { ok: false, issues };
97
+ }
98
+
99
+ /**
100
+ * Render a list of validation issues as a single multi-line string,
101
+ * suitable for log lines or daemon command error fields.
102
+ */
103
+ export function formatManifestValidationIssues(issues: ManifestValidationIssue[]): string {
104
+ return issues
105
+ .map((issue) => {
106
+ const where = issue.path || '<root>';
107
+ return ` - ${where}: ${issue.message} (${issue.keyword})`;
108
+ })
109
+ .join('\n');
110
+ }
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Static taint analyzer for extended-tier override JS.
3
+ *
4
+ * Goal: classify the override JS shipped with an extended-tier provider into
5
+ * one of three risk tiers, so the dashboard + daemon trust prompt can
6
+ * surface accurate language to the operator instead of a single generic
7
+ * "this provider contains JS" warning.
8
+ *
9
+ * - clean : no flagged APIs at all. The override looks like pure string
10
+ * manipulation. Still extended-tier, but the warning copy is
11
+ * "this provider contains JS; nothing dangerous was detected".
12
+ * - elevated : touches process / dynamic codegen / dynamic require / eval
13
+ * / Function constructor / child_process. The warning copy is
14
+ * explicit ("this provider can run shell commands / dynamically
15
+ * load code").
16
+ * - hostile : combines elevated APIs with network egress, AND uses obvious
17
+ * obfuscation (base64-decoded strings fed into Function / eval).
18
+ * The publish endpoint should reject these.
19
+ *
20
+ * Implementation note: this is a *static* analyzer — string + light AST scan
21
+ * via Node's built-in parser. It is intentionally fail-loud: anything that
22
+ * looks suspicious is flagged. False positives are fine; this exists to gate
23
+ * the trust prompt, not to be a real sandbox. The real sandbox lives in
24
+ * Phase 5 (isolated-vm).
25
+ *
26
+ * Used by:
27
+ * - `adhdev provider validate` (CLI tier classification)
28
+ * - registry publish endpoint static-check
29
+ * - daemon at provider-load time, to size the warning
30
+ */
31
+
32
+ import { readFileSync, existsSync } from 'node:fs';
33
+ import { resolve, dirname, join } from 'node:path';
34
+
35
+ // ─── Findings ──────────────────────────────────────────────────────────
36
+
37
+ export type TaintLevel = 'clean' | 'elevated' | 'hostile';
38
+
39
+ export interface TaintFinding {
40
+ /** What was found, e.g. "child_process.exec". */
41
+ api: string;
42
+ /** Coarse category. */
43
+ category: TaintCategory;
44
+ /** File the finding came from. */
45
+ file: string;
46
+ /** Line number (1-indexed) of the first character of the matched token. */
47
+ line: number;
48
+ /** The surrounding source excerpt, trimmed. */
49
+ excerpt: string;
50
+ }
51
+
52
+ export type TaintCategory =
53
+ | 'process-control' // process.exit, process.kill
54
+ | 'shell-exec' // child_process.{exec,execSync,spawn,spawnSync,fork}
55
+ | 'fs-write' // fs.writeFile / writeFileSync outside override dir
56
+ | 'fs-read' // fs.readFile / readFileSync
57
+ | 'network' // http(s).request, fetch, net.connect, dgram
58
+ | 'dynamic-codegen' // eval, new Function(), Function('...')
59
+ | 'dynamic-require' // require(variable), import(variable)
60
+ | 'obfuscation' // Buffer.from(...,'base64').toString + (eval|Function)
61
+ | 'global-mutation'; // mutating Object.prototype, process.env
62
+
63
+ export interface TaintResult {
64
+ level: TaintLevel;
65
+ findings: TaintFinding[];
66
+ files: string[];
67
+ }
68
+
69
+ // ─── Patterns ──────────────────────────────────────────────────────────
70
+
71
+ interface PatternRule {
72
+ category: TaintCategory;
73
+ /** Display label. */
74
+ label: string;
75
+ /** Regex applied per-line. */
76
+ pattern: RegExp;
77
+ }
78
+
79
+ const PATTERNS: PatternRule[] = [
80
+ {
81
+ category: 'shell-exec',
82
+ label: 'child_process.exec',
83
+ pattern: /\bchild_process\b[\s\S]{0,40}?\b(exec|execSync|spawn|spawnSync|fork|execFile)\b/,
84
+ },
85
+ {
86
+ category: 'shell-exec',
87
+ label: 'require("child_process")',
88
+ pattern: /\brequire\s*\(\s*['"]child_process['"]\s*\)/,
89
+ },
90
+ {
91
+ category: 'shell-exec',
92
+ label: 'import child_process',
93
+ pattern: /\bfrom\s+['"](?:node:)?child_process['"]/,
94
+ },
95
+ {
96
+ category: 'process-control',
97
+ label: 'process.exit / kill',
98
+ pattern: /\bprocess\.(exit|kill|abort)\s*\(/,
99
+ },
100
+ {
101
+ category: 'global-mutation',
102
+ label: 'process.env mutation',
103
+ pattern: /\bprocess\.env\.[A-Z_][A-Z0-9_]*\s*=/,
104
+ },
105
+ {
106
+ category: 'global-mutation',
107
+ label: 'prototype pollution',
108
+ pattern: /\bObject\.prototype\b.*=/,
109
+ },
110
+ {
111
+ category: 'dynamic-codegen',
112
+ label: 'eval()',
113
+ pattern: /(?<![A-Za-z0-9_$])eval\s*\(/,
114
+ },
115
+ {
116
+ category: 'dynamic-codegen',
117
+ label: 'new Function()',
118
+ pattern: /\bnew\s+Function\s*\(/,
119
+ },
120
+ {
121
+ category: 'dynamic-require',
122
+ label: 'require(variable)',
123
+ pattern: /\brequire\s*\(\s*(?!['"`])/,
124
+ },
125
+ {
126
+ category: 'dynamic-require',
127
+ label: 'import(variable)',
128
+ pattern: /(?<![.\w])import\s*\(\s*(?!['"`])/,
129
+ },
130
+ {
131
+ category: 'network',
132
+ label: 'fetch()',
133
+ pattern: /(?<![A-Za-z0-9_$])fetch\s*\(/,
134
+ },
135
+ {
136
+ category: 'network',
137
+ label: 'http(s).request',
138
+ pattern: /\b(https?)\.(request|get)\s*\(/,
139
+ },
140
+ {
141
+ category: 'network',
142
+ label: 'net.connect / Socket',
143
+ pattern: /\b(net\.connect|tls\.connect|new\s+net\.Socket)\b/,
144
+ },
145
+ {
146
+ category: 'network',
147
+ label: 'dgram',
148
+ pattern: /\brequire\s*\(\s*['"](?:node:)?dgram['"]/,
149
+ },
150
+ {
151
+ category: 'fs-write',
152
+ label: 'fs.writeFile / writeFileSync',
153
+ pattern: /\bfs\.(writeFile|writeFileSync|appendFile|appendFileSync|unlink|unlinkSync|rm|rmSync)\b/,
154
+ },
155
+ {
156
+ category: 'fs-read',
157
+ label: 'fs.readFile / readFileSync',
158
+ pattern: /\bfs\.(readFile|readFileSync|readdir|readdirSync|stat|statSync)\b/,
159
+ },
160
+ ];
161
+
162
+ // Obfuscation heuristic: base64 / hex decode feeding into eval or Function.
163
+ const OBFUSCATION_PATTERN =
164
+ /Buffer\.from\s*\([^)]*['"](?:base64|hex)['"]\s*\)\s*\.toString\s*\([^)]*\)[\s\S]{0,80}?(eval|Function)\s*\(/;
165
+
166
+ // ─── File walker — picks up everything reachable from overrides ────────
167
+
168
+ function listOverrideJsFiles(manifestPath: string): string[] {
169
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as { overrides?: Record<string, unknown> };
170
+ if (!manifest.overrides) return [];
171
+ const base = dirname(manifestPath);
172
+ const out = new Set<string>();
173
+ for (const value of Object.values(manifest.overrides)) {
174
+ let rel: string | null = null;
175
+ if (typeof value === 'string') rel = value;
176
+ else if (value && typeof value === 'object' && 'path' in value && typeof (value as { path: unknown }).path === 'string') {
177
+ rel = (value as { path: string }).path;
178
+ }
179
+ if (!rel) continue;
180
+ const abs = resolve(base, rel);
181
+ if (existsSync(abs)) out.add(abs);
182
+ }
183
+ // Best-effort: also scan sibling scripts/ tree, since overrides commonly
184
+ // require() helpers from there.
185
+ for (const candidate of ['scripts']) {
186
+ const dir = join(base, candidate);
187
+ if (existsSync(dir)) {
188
+ try {
189
+ walk(dir, out);
190
+ } catch {
191
+ // Ignore individual unreadable files.
192
+ }
193
+ }
194
+ }
195
+ return [...out];
196
+ }
197
+
198
+ function walk(dir: string, into: Set<string>): void {
199
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
200
+ const { readdirSync, statSync } = require('node:fs') as typeof import('node:fs');
201
+ for (const entry of readdirSync(dir)) {
202
+ const abs = join(dir, entry);
203
+ let st;
204
+ try { st = statSync(abs); } catch { continue; }
205
+ if (st.isDirectory()) walk(abs, into);
206
+ else if (st.isFile() && /\.(?:m|c)?js$/.test(entry)) into.add(abs);
207
+ }
208
+ }
209
+
210
+ // ─── Scanner ───────────────────────────────────────────────────────────
211
+
212
+ function scanFile(file: string): TaintFinding[] {
213
+ const source = readFileSync(file, 'utf-8');
214
+ const findings: TaintFinding[] = [];
215
+ // Per-line scan keeps line numbers honest.
216
+ const lines = source.split('\n');
217
+ for (let i = 0; i < lines.length; i += 1) {
218
+ const line = lines[i];
219
+ for (const rule of PATTERNS) {
220
+ if (rule.pattern.test(line)) {
221
+ findings.push({
222
+ api: rule.label,
223
+ category: rule.category,
224
+ file,
225
+ line: i + 1,
226
+ excerpt: line.trim().slice(0, 200),
227
+ });
228
+ }
229
+ }
230
+ }
231
+ // Whole-file scan for obfuscation pattern (may span lines).
232
+ if (OBFUSCATION_PATTERN.test(source)) {
233
+ findings.push({
234
+ api: 'base64/hex → eval|Function',
235
+ category: 'obfuscation',
236
+ file,
237
+ line: 1,
238
+ excerpt: '<obfuscation pattern across multiple lines>',
239
+ });
240
+ }
241
+ return findings;
242
+ }
243
+
244
+ // ─── Classification ────────────────────────────────────────────────────
245
+
246
+ const ELEVATED_CATEGORIES = new Set<TaintCategory>([
247
+ 'shell-exec',
248
+ 'process-control',
249
+ 'dynamic-codegen',
250
+ 'dynamic-require',
251
+ 'fs-write',
252
+ 'global-mutation',
253
+ ]);
254
+
255
+ const HOSTILE_REQUIRES = new Set<TaintCategory>([
256
+ 'obfuscation',
257
+ ]);
258
+
259
+ function classify(findings: TaintFinding[]): TaintLevel {
260
+ if (findings.length === 0) return 'clean';
261
+ const cats = new Set(findings.map((f) => f.category));
262
+ if ([...cats].some((c) => HOSTILE_REQUIRES.has(c))) return 'hostile';
263
+ // Hostile also: elevated + network combined.
264
+ const hasElevated = [...cats].some((c) => ELEVATED_CATEGORIES.has(c));
265
+ const hasNetwork = cats.has('network');
266
+ if (hasElevated && hasNetwork) return 'hostile';
267
+ if (hasElevated || hasNetwork) return 'elevated';
268
+ return 'clean'; // only fs-read on its own is fine.
269
+ }
270
+
271
+ // ─── Public API ────────────────────────────────────────────────────────
272
+
273
+ export function analyzeOverrideTaint(manifestPath: string): TaintResult {
274
+ const files = listOverrideJsFiles(manifestPath);
275
+ const findings: TaintFinding[] = [];
276
+ for (const f of files) findings.push(...scanFile(f));
277
+ return {
278
+ level: classify(findings),
279
+ findings,
280
+ files,
281
+ };
282
+ }
283
+
284
+ /** Format a result as a single CLI-friendly block. */
285
+ export function formatTaintResult(result: TaintResult): string {
286
+ const lines: string[] = [];
287
+ lines.push(`Tier: extended (override JS present)`);
288
+ lines.push(`Risk level: ${result.level}`);
289
+ lines.push(`Files scanned: ${result.files.length}`);
290
+ if (result.findings.length === 0) {
291
+ lines.push('No flagged APIs detected.');
292
+ return lines.join('\n');
293
+ }
294
+ // Group by category for a tighter report.
295
+ const byCat = new Map<TaintCategory, TaintFinding[]>();
296
+ for (const f of result.findings) {
297
+ const arr = byCat.get(f.category) ?? [];
298
+ arr.push(f);
299
+ byCat.set(f.category, arr);
300
+ }
301
+ for (const [cat, items] of byCat) {
302
+ lines.push(` ${cat}: ${items.length} occurrence(s)`);
303
+ for (const f of items.slice(0, 5)) {
304
+ lines.push(` ${f.file}:${f.line} ${f.api}`);
305
+ }
306
+ if (items.length > 5) lines.push(` …and ${items.length - 5} more`);
307
+ }
308
+ return lines.join('\n');
309
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Thin generic terminal adapter.
3
+ *
4
+ * Spawns a PTY, feeds bytes into a headless xterm so a visible screen
5
+ * snapshot is always available, and exposes a small hook surface
6
+ * (init / tick / on_screen_changed / on_exit). Nothing in this module
7
+ * knows about agents, modals, or specs — it's just a terminal host.
8
+ *
9
+ * The Spec Driver is built on top of this adapter and consumes the
10
+ * hooks to drive its declarative state machine.
11
+ *
12
+ * Inbound from driver:
13
+ * send_keys(str) — raw bytes to PTY
14
+ * resize(c, r) — resize the PTY and the xterm
15
+ * kill() — terminate the child
16
+ *
17
+ * Outbound to driver:
18
+ * init({ pid })
19
+ * on_pty_data(chunk) — raw bytes for dashboard's terminal pane
20
+ * on_screen_changed(snapshot) — coalesced visible-screen change
21
+ * tick() — periodic timer (configurable interval)
22
+ * on_exit({ exitCode })
23
+ */
24
+ 'use strict';
25
+
26
+ // @xterm/headless ships as CJS only but stamps __esModule = true on its
27
+ // export object, so tsup's __toESM helper neither synthesizes a `default`
28
+ // property (it sees the __esModule marker and trusts it) nor preserves
29
+ // the named `Terminal` export through a plain ESM-style
30
+ // `import { Terminal } from '@xterm/headless'`. The first symptom we saw
31
+ // from this was the published `adhdev` CLI exploding with
32
+ // "Named export 'Terminal' not found" inside the mcp ipc bootstrap.
33
+ //
34
+ // Pull Terminal off the namespace import (which tsup compiles into a
35
+ // shim that *does* see the package's own Terminal property) instead.
36
+ // Type-only import keeps the .d.ts surface honest.
37
+ import type { Terminal as TerminalType } from '@xterm/headless';
38
+ import * as xtermHeadlessNs from '@xterm/headless';
39
+ const TerminalCtor: { new (...args: unknown[]): TerminalType } =
40
+ (xtermHeadlessNs as any).Terminal
41
+ ?? (xtermHeadlessNs as any).default?.Terminal;
42
+ import { NodePtyTransportFactory, type PtyRuntimeTransport, type PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
43
+
44
+ export interface TerminalAdapterOpts {
45
+ binary: string;
46
+ args?: string[];
47
+ cwd: string;
48
+ env?: Record<string, string>;
49
+ cols?: number;
50
+ rows?: number;
51
+ /** Coalesce screen snapshots: emit on_screen_changed at most this often. */
52
+ screenChangeDebounceMs?: number;
53
+ /** tick() period. 0 disables ticks. */
54
+ tickIntervalMs?: number;
55
+ /**
56
+ * Optional PTY transport factory. When the daemon supplies a
57
+ * SessionHostPtyTransportFactory (standalone runs the PTY inside the
58
+ * session-host so the runtime/<sid>/snapshot endpoint can serve it),
59
+ * forward it here. Without it the PTY spawns locally inside the
60
+ * daemon process and the dashboard's terminal pane reports
61
+ * "Runtime terminal unavailable: Unknown session".
62
+ */
63
+ transportFactory?: PtyTransportFactory;
64
+ }
65
+
66
+ export interface TerminalAdapterHandlers {
67
+ init?(info: { pid: number }): void;
68
+ on_pty_data?(chunk: string): void;
69
+ on_screen_changed?(snapshot: string): void;
70
+ tick?(): void;
71
+ on_exit?(info: { exitCode: number }): void;
72
+ }
73
+
74
+ export class TerminalAdapter {
75
+ private term: TerminalType;
76
+ private pty: PtyRuntimeTransport | null = null;
77
+ private factory: PtyTransportFactory;
78
+ private cols: number;
79
+ private rows: number;
80
+ private screenDebounceMs: number;
81
+ private tickIntervalMs: number;
82
+ private screenTimer: ReturnType<typeof setTimeout> | null = null;
83
+ private tickTimer: ReturnType<typeof setInterval> | null = null;
84
+ private lastScreen = '';
85
+
86
+ constructor(
87
+ private readonly opts: TerminalAdapterOpts,
88
+ private readonly handlers: TerminalAdapterHandlers,
89
+ ) {
90
+ this.cols = opts.cols ?? 100;
91
+ this.rows = opts.rows ?? 30;
92
+ this.screenDebounceMs = opts.screenChangeDebounceMs ?? 80;
93
+ this.tickIntervalMs = opts.tickIntervalMs ?? 0;
94
+ this.factory = opts.transportFactory ?? new NodePtyTransportFactory();
95
+ this.term = new TerminalCtor({ cols: this.cols, rows: this.rows, allowProposedApi: true, scrollback: 1000 });
96
+ }
97
+
98
+ start(): void {
99
+ this.pty = this.factory.spawn(this.opts.binary, this.opts.args ?? [], {
100
+ cwd: this.opts.cwd,
101
+ env: { ...process.env, ...(this.opts.env ?? {}) } as Record<string, string>,
102
+ cols: this.cols,
103
+ rows: this.rows,
104
+ });
105
+ this.handlers.init?.({ pid: this.pty.pid });
106
+ this.pty.onData((chunk) => this.onChunk(chunk));
107
+ this.pty.onExit((info) => {
108
+ this.stopTimers();
109
+ this.handlers.on_exit?.({ exitCode: typeof info.exitCode === 'number' ? info.exitCode : 0 });
110
+ this.pty = null;
111
+ });
112
+ if (this.tickIntervalMs > 0) {
113
+ this.tickTimer = setInterval(() => this.handlers.tick?.(), this.tickIntervalMs);
114
+ }
115
+ }
116
+
117
+ send_keys(s: string): void {
118
+ this.pty?.write(s);
119
+ }
120
+
121
+ resize(cols: number, rows: number): void {
122
+ this.cols = cols; this.rows = rows;
123
+ this.pty?.resize(cols, rows);
124
+ this.term.resize(cols, rows);
125
+ }
126
+
127
+ snapshot(): string {
128
+ return this.lastScreen || this.computeScreen();
129
+ }
130
+
131
+ getCursorPosition(): { row: number; col: number } {
132
+ const buf = this.term.buffer.active;
133
+ return {
134
+ row: Math.max(0, (buf as any).cursorY ?? 0),
135
+ col: Math.max(0, (buf as any).cursorX ?? 0),
136
+ };
137
+ }
138
+
139
+ kill(): void {
140
+ this.stopTimers();
141
+ try { this.pty?.kill(); } catch { /* ignore */ }
142
+ this.pty = null;
143
+ this.term.dispose();
144
+ }
145
+
146
+ private onChunk(chunk: string): void {
147
+ try { this.handlers.on_pty_data?.(chunk); } catch { /* user side */ }
148
+ this.term.write(chunk);
149
+ // Coalesce snapshot emission — rapid bursts shouldn't fire 200x.
150
+ if (this.screenTimer) return;
151
+ this.screenTimer = setTimeout(() => {
152
+ this.screenTimer = null;
153
+ const snap = this.computeScreen();
154
+ if (snap === this.lastScreen) return;
155
+ this.lastScreen = snap;
156
+ try { this.handlers.on_screen_changed?.(snap); } catch { /* user side */ }
157
+ }, this.screenDebounceMs);
158
+ }
159
+
160
+ private computeScreen(): string {
161
+ const buf = this.term.buffer.active;
162
+ const out: string[] = [];
163
+ for (let y = 0; y < buf.length; y += 1) {
164
+ const line = buf.getLine(y);
165
+ if (!line) continue;
166
+ out.push(line.translateToString(true));
167
+ }
168
+ while (out.length > 0 && out[out.length - 1].trim() === '') out.pop();
169
+ return out.join('\n');
170
+ }
171
+
172
+ private stopTimers(): void {
173
+ if (this.screenTimer) { clearTimeout(this.screenTimer); this.screenTimer = null; }
174
+ if (this.tickTimer) { clearInterval(this.tickTimer); this.tickTimer = null; }
175
+ }
176
+ }