@adhdev/daemon-core 0.9.82-rc.16 → 0.9.82-rc.160

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 (182) hide show
  1. package/dist/boot/process-hardening.d.ts +50 -0
  2. package/dist/chat/source-machine.d.ts +166 -0
  3. package/dist/chat/source-resolver.d.ts +104 -0
  4. package/dist/chat/subscription-updates.d.ts +1 -0
  5. package/dist/cli-adapter-types.d.ts +5 -1
  6. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  7. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  8. package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
  9. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  10. package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
  11. package/dist/commands/handler.d.ts +66 -0
  12. package/dist/commands/router.d.ts +22 -0
  13. package/dist/config/chat-history.d.ts +5 -0
  14. package/dist/config/config.d.ts +5 -0
  15. package/dist/config/mesh-config.d.ts +68 -1
  16. package/dist/git/git-commands.d.ts +5 -1
  17. package/dist/index.d.ts +20 -6
  18. package/dist/index.js +13154 -3031
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +13100 -3023
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/installer.d.ts +1 -4
  23. package/dist/ipc/local-ipc-server.d.ts +91 -0
  24. package/dist/launch.d.ts +1 -1
  25. package/dist/logging/async-batch-writer.d.ts +10 -0
  26. package/dist/mesh/beads-db.d.ts +72 -0
  27. package/dist/mesh/contracts.d.ts +164 -0
  28. package/dist/mesh/coordinator-registry.d.ts +25 -0
  29. package/dist/mesh/mesh-active-work.d.ts +90 -0
  30. package/dist/mesh/mesh-events.d.ts +77 -5
  31. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  32. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  33. package/dist/mesh/mesh-ledger.d.ts +58 -1
  34. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  35. package/dist/mesh/mesh-work-queue.d.ts +44 -5
  36. package/dist/mesh/preview-freshness.d.ts +18 -0
  37. package/dist/mesh/refine-config.d.ts +193 -0
  38. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  39. package/dist/providers/approval-utils.d.ts +9 -0
  40. package/dist/providers/cli-provider-instance.d.ts +6 -1
  41. package/dist/providers/contracts.d.ts +50 -5
  42. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  43. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  44. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  45. package/dist/providers/native-history/index.d.ts +11 -0
  46. package/dist/providers/provider-loader.d.ts +19 -1
  47. package/dist/providers/read-chat-contract.d.ts +29 -0
  48. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  49. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  50. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  51. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +64 -0
  52. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  53. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  54. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  55. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  56. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  57. package/dist/providers/sdk/v1/index.d.ts +30 -0
  58. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  59. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  60. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  61. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  62. package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
  63. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  64. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  65. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  66. package/dist/providers/transcript-v2.d.ts +176 -0
  67. package/dist/repo-mesh-types.d.ts +67 -0
  68. package/dist/shared-types.d.ts +12 -0
  69. package/dist/status/reporter.d.ts +2 -0
  70. package/dist/status/snapshot.d.ts +1 -0
  71. package/dist/types.d.ts +5 -0
  72. package/package.json +6 -2
  73. package/src/boot/daemon-lifecycle.ts +17 -10
  74. package/src/boot/process-hardening.ts +89 -0
  75. package/src/chat/source-machine.ts +534 -0
  76. package/src/chat/source-resolver.ts +0 -0
  77. package/src/chat/subscription-updates.ts +14 -1
  78. package/src/cli-adapter-types.d.ts +1 -0
  79. package/src/cli-adapter-types.ts +3 -1
  80. package/src/cli-adapters/cli-script-runner.ts +421 -0
  81. package/src/cli-adapters/cli-state-engine.ts +1086 -0
  82. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  83. package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
  84. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  85. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  86. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  87. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  88. package/src/cli-adapters/provider-cli-shared.ts +57 -11
  89. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  90. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  91. package/src/commands/chat-commands.ts +1451 -52
  92. package/src/commands/cli-manager.ts +184 -3
  93. package/src/commands/handler.ts +547 -2
  94. package/src/commands/mesh-coordinator.ts +13 -143
  95. package/src/commands/router.ts +3285 -428
  96. package/src/config/chat-history.ts +79 -24
  97. package/src/config/config.ts +12 -0
  98. package/src/config/mesh-config.ts +249 -2
  99. package/src/config/recent-activity.ts +8 -2
  100. package/src/daemon/dev-cli-debug.ts +10 -1
  101. package/src/detection/ide-detector.ts +26 -16
  102. package/src/git/git-commands.ts +17 -5
  103. package/src/git/git-worktree.ts +8 -1
  104. package/src/index.ts +72 -5
  105. package/src/installer.d.ts +1 -1
  106. package/src/installer.ts +8 -6
  107. package/src/ipc/local-ipc-server.ts +278 -0
  108. package/src/launch.d.ts +1 -1
  109. package/src/launch.ts +37 -28
  110. package/src/logging/async-batch-writer.ts +55 -0
  111. package/src/logging/logger.ts +2 -1
  112. package/src/mesh/beads-db.ts +479 -0
  113. package/src/mesh/contracts.ts +329 -0
  114. package/src/mesh/coordinator-prompt.ts +40 -22
  115. package/src/mesh/coordinator-registry.ts +75 -0
  116. package/src/mesh/mesh-active-work.ts +437 -0
  117. package/src/mesh/mesh-events.ts +820 -61
  118. package/src/mesh/mesh-fast-forward.ts +430 -0
  119. package/src/mesh/mesh-host-ownership.ts +73 -0
  120. package/src/mesh/mesh-ledger.ts +457 -104
  121. package/src/mesh/mesh-refine-status.ts +144 -0
  122. package/src/mesh/mesh-work-queue.ts +216 -158
  123. package/src/mesh/preview-freshness.ts +118 -0
  124. package/src/mesh/refine-config.ts +366 -0
  125. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  126. package/src/providers/approval-utils.ts +39 -5
  127. package/src/providers/chat-message-normalization.ts +4 -11
  128. package/src/providers/cli-provider-instance.ts +380 -44
  129. package/src/providers/contracts.ts +52 -5
  130. package/src/providers/ide-provider-instance.ts +17 -3
  131. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  132. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  133. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  134. package/src/providers/native-history/index.ts +23 -0
  135. package/src/providers/provider-loader.ts +294 -28
  136. package/src/providers/provider-schema.ts +31 -13
  137. package/src/providers/read-chat-contract.ts +76 -16
  138. package/src/providers/sdk/README.md +49 -0
  139. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  140. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  141. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  142. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +245 -0
  143. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  144. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  145. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  146. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  147. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  148. package/src/providers/sdk/v1/index.ts +152 -0
  149. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  150. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  151. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  152. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +444 -0
  153. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  154. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  155. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  156. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  157. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  158. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  159. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  160. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  161. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +119 -0
  162. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  163. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  164. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  165. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  166. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  167. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  168. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  169. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  170. package/src/providers/sdk/v1/types/common/index.ts +210 -0
  171. package/src/providers/sdk/v1/validators/index.ts +19 -0
  172. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  173. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  174. package/src/providers/transcript-v2.ts +567 -0
  175. package/src/providers/version-archive.ts +38 -20
  176. package/src/repo-mesh-types.ts +77 -0
  177. package/src/shared-types.ts +9 -0
  178. package/src/status/builders.ts +23 -6
  179. package/src/status/reporter.ts +15 -0
  180. package/src/status/snapshot.ts +35 -11
  181. package/src/system/host-memory.ts +29 -12
  182. package/src/types.ts +5 -0
@@ -33,6 +33,26 @@ import { validateProviderDefinition } from './provider-schema.js';
33
33
  import type { ProviderSourceMode } from '../config/config.js';
34
34
  import type { ProviderSourceConfigSnapshot, ProviderUserDirSource } from '../config/provider-source-config.js';
35
35
 
36
+ /**
37
+ * Adds a provider-script root to the require whitelist. Wrapped in a
38
+ * try/catch + null check so a loader hot-path can't crash on a path
39
+ * that doesn't exist yet or one the whitelist hook rejects.
40
+ *
41
+ * The require-whitelist module is loaded lazily on first call. Eagerly
42
+ * top-level importing it pulls `node:fs.realpathSync.native` into
43
+ * module evaluation, which breaks unit tests that partially mock `fs`
44
+ * (e.g. test/commands/get-logs-incremental.test.ts mocks only
45
+ * existsSync + readFileSync). Lazy load keeps that mock surface valid.
46
+ */
47
+ function registerProviderScriptRootSafely(root: string | null | undefined): void {
48
+ if (!root || typeof root !== 'string') return;
49
+ try {
50
+ const { registerProviderScriptRoot } =
51
+ require('./sdk/v1/sandbox/require-whitelist.js') as typeof import('./sdk/v1/sandbox/require-whitelist.js');
52
+ registerProviderScriptRoot(root);
53
+ } catch { /* boot-time only — swallow */ }
54
+ }
55
+
36
56
  interface ProviderAvailabilityState {
37
57
  installed: boolean;
38
58
  detectedPath: string | null;
@@ -92,7 +112,9 @@ export class ProviderLoader {
92
112
  }
93
113
 
94
114
  private static readonly GITHUB_TARBALL_URL = 'https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz';
115
+ private static readonly REGISTRY_BASE_URL = 'https://api.adhf.dev/api/v1/registry';
95
116
  private static readonly META_FILE = '.meta.json';
117
+ private static readonly REGISTRY_META_FILE = '.registry-meta.json';
96
118
  private static readonly REPO_PROVIDER_DIRNAME = 'adhdev-providers';
97
119
  private static readonly SIBLING_MARKER_FILE = '.adhdev-provider-root';
98
120
  private static readonly SIBLING_ENV_VAR = 'ADHDEV_USE_SIBLING_PROVIDERS';
@@ -224,7 +246,12 @@ export class ProviderLoader {
224
246
  * Highest-priority editable overrides come first.
225
247
  */
226
248
  getProviderRoots(): string[] {
227
- return [this.userDir, this.upstreamDir];
249
+ // Order matters: user customs > marketplace installs > upstream auto-sync.
250
+ // findProviderDirInternal walks this list in order to locate the provider
251
+ // dir containing the scripts/, so marketplace must be included here even
252
+ // though loadAll() also reads it directly.
253
+ const marketplaceDir = path.join(os.homedir(), '.adhdev', 'marketplace');
254
+ return [this.userDir, marketplaceDir, this.upstreamDir];
228
255
  }
229
256
 
230
257
  getSourceConfig(): ProviderSourceConfigSnapshot {
@@ -336,7 +363,19 @@ export class ProviderLoader {
336
363
  this.log('Upstream loading disabled (sourceMode=no-upstream)');
337
364
  }
338
365
 
339
- // 2. Load user custom (excluding .upstream — highest priority, never auto-updated)
366
+ // 2. Load marketplace installs from ~/.adhdev/marketplace/ (overrides upstream,
367
+ // but is itself overridden by user customs in step 3). These are providers the
368
+ // user explicitly installed via the Marketplace UI. They are NOT touched by
369
+ // upstream sync.
370
+ const marketplaceDir = path.join(os.homedir(), '.adhdev', 'marketplace');
371
+ if (fs.existsSync(marketplaceDir)) {
372
+ const marketplaceCount = this.loadDir(marketplaceDir);
373
+ if (marketplaceCount > 0) {
374
+ this.log(`Loaded ${marketplaceCount} marketplace-installed providers`);
375
+ }
376
+ }
377
+
378
+ // 3. Load user custom (excluding .upstream — highest priority, never auto-updated)
340
379
  if (fs.existsSync(this.userDir)) {
341
380
  const userCount = this.loadDir(this.userDir, ['.upstream']);
342
381
  if (userCount > 0) {
@@ -988,7 +1027,12 @@ export class ProviderLoader {
988
1027
  }
989
1028
 
990
1029
  // 3. Composite override (OS + version)
991
- if (base.overrides) {
1030
+ // Legacy shape: base.overrides is an Array<{ when: {os,version}, scripts }>.
1031
+ // v1 manifests (Phase 3-4) repurposed `overrides` as an object map of
1032
+ // capability overrides (e.g. { detectStatus: { path, schema } }), which is
1033
+ // consumed by the SDK builders, not by this resolver. Only iterate when
1034
+ // the field is in the legacy array shape.
1035
+ if (Array.isArray(base.overrides)) {
992
1036
  for (const override of base.overrides) {
993
1037
  const osMatch = !override.when.os || override.when.os === currentOs;
994
1038
  const verMatch = !override.when.version || (currentVersion && this.matchesVersion(currentVersion, override.when.version));
@@ -996,6 +1040,40 @@ export class ProviderLoader {
996
1040
  resolved.scripts = { ...resolved.scripts, ...override.scripts };
997
1041
  }
998
1042
  }
1043
+ } else if (base.overrides && typeof base.overrides === 'object') {
1044
+ // v1 manifest shape: { detectStatus: { path }, parseSession: { path }, ... }
1045
+ // Each script name maps to a path inside the provider directory. We load
1046
+ // the file and merge its export(s) into resolved.scripts. Lets a
1047
+ // provider override a single primitive (e.g. just detectStatus) while
1048
+ // letting the SDK synthesize the rest from the tui block.
1049
+ const providerDir = this.findProviderDirInternal(base.type);
1050
+ if (providerDir) {
1051
+ for (const [scriptName, override] of Object.entries(base.overrides as Record<string, any>)) {
1052
+ if (!override || typeof override.path !== 'string') continue;
1053
+ const fullPath = path.join(providerDir, override.path);
1054
+ if (!fs.existsSync(fullPath)) {
1055
+ this.log(` [overrides] ${base.type}: ${scriptName} path not found: ${fullPath}`);
1056
+ continue;
1057
+ }
1058
+ try {
1059
+ // Override scripts go through the same whitelist gate as the
1060
+ // main scripts dir. Use the provider parent root so a v1
1061
+ // override can still require ../_shared helpers.
1062
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1063
+ delete require.cache[require.resolve(fullPath)];
1064
+ const fn = require(fullPath);
1065
+ const target = typeof fn === 'function' ? fn : (fn && fn[scriptName]);
1066
+ if (typeof target === 'function') {
1067
+ resolved.scripts = { ...resolved.scripts, [scriptName]: target } as any;
1068
+ this.log(` [overrides] ${base.type}: ${scriptName} loaded from ${override.path}`);
1069
+ } else {
1070
+ this.log(` [overrides] ${base.type}: ${scriptName} export missing in ${override.path}`);
1071
+ }
1072
+ } catch (e: any) {
1073
+ this.log(` [overrides] ${base.type}: ${scriptName} require failed: ${e?.message || e}`);
1074
+ }
1075
+ }
1076
+ }
999
1077
  }
1000
1078
 
1001
1079
  if ((resolved.category === 'cli' || resolved.category === 'acp') && resolved.spawn?.command) {
@@ -1026,6 +1104,13 @@ export class ProviderLoader {
1026
1104
  return null;
1027
1105
  }
1028
1106
 
1107
+ // Register the provider's *parent root* (e.g. .../adhdev-providers/) so
1108
+ // the require whitelist gates every script + every _shared helper this
1109
+ // provider may reach. Picking the grandparent (one above the category
1110
+ // dir `cli/`) lets sibling helpers in `_shared` resolve while still
1111
+ // blocking `../../etc/...` escapes. Idempotent.
1112
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1113
+
1029
1114
  // Return cached scripts if available (cleared on reload/watch)
1030
1115
  const cached = this.scriptsCache.get(dir);
1031
1116
  if (cached) return cached;
@@ -1067,13 +1152,17 @@ export class ProviderLoader {
1067
1152
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
1068
1153
  });
1069
1154
 
1155
+ let reloadTimer: ReturnType<typeof setTimeout> | null = null;
1070
1156
  const handleChange = (filePath: string) => {
1071
1157
  if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
1072
1158
  return;
1073
1159
  }
1074
1160
  if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
1075
- this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1076
- this.reload();
1161
+ if (reloadTimer) clearTimeout(reloadTimer);
1162
+ reloadTimer = setTimeout(() => {
1163
+ this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1164
+ this.reload();
1165
+ }, 300);
1077
1166
  }
1078
1167
  };
1079
1168
 
@@ -1124,13 +1213,118 @@ export class ProviderLoader {
1124
1213
  *
1125
1214
  * @returns Whether an update occurred
1126
1215
  */
1216
+ /**
1217
+ * Sync providers from the ADHDev registry (registry.adhf.dev).
1218
+ *
1219
+ * Downloads only providers whose server checksum differs from the locally
1220
+ * cached checksum. Falls back gracefully to the GitHub tarball path if the
1221
+ * registry is unreachable or returns an unexpected response.
1222
+ *
1223
+ * Returns `{ updated: true }` when at least one provider file changed on disk,
1224
+ * `{ updated: false }` when everything is already current, or
1225
+ * `{ updated: false, error }` when the registry couldn't be reached and we
1226
+ * should proceed to the GitHub tarball fallback.
1227
+ */
1228
+ async fetchFromRegistry(): Promise<{ updated: boolean; error?: string }> {
1229
+ if (this.disableUpstream) {
1230
+ this.log('Registry sync skipped (sourceMode=no-upstream)');
1231
+ return { updated: false };
1232
+ }
1233
+ this.log(`Registry sync starting (${ProviderLoader.REGISTRY_BASE_URL})...`);
1234
+
1235
+ const https = require('https') as typeof import('https');
1236
+ const regMetaPath = path.join(this.upstreamDir, ProviderLoader.REGISTRY_META_FILE);
1237
+
1238
+ // Load cached checksums
1239
+ let cachedChecksums: Record<string, string> = {};
1240
+ try {
1241
+ if (fs.existsSync(regMetaPath)) {
1242
+ cachedChecksums = JSON.parse(fs.readFileSync(regMetaPath, 'utf-8')).checksums ?? {};
1243
+ }
1244
+ } catch { }
1245
+
1246
+ try {
1247
+ // 1. Fetch provider list
1248
+ const listUrl = `${ProviderLoader.REGISTRY_BASE_URL}/providers`;
1249
+ const listBody = await new Promise<string>((resolve, reject) => {
1250
+ const req = https.get(listUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
1251
+ if (res.statusCode !== 200) { reject(new Error(`registry list HTTP ${res.statusCode}`)); return; }
1252
+ const chunks: Buffer[] = [];
1253
+ res.on('data', (c: Buffer) => chunks.push(c));
1254
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
1255
+ });
1256
+ req.on('error', reject);
1257
+ req.on('timeout', () => { req.destroy(); reject(new Error('registry list timeout')); });
1258
+ });
1259
+
1260
+ const list = JSON.parse(listBody) as { providers: Array<{ type: string; category: string; checksum: string; version: string }> };
1261
+ if (!Array.isArray(list.providers)) throw new Error('unexpected registry response shape');
1262
+
1263
+ let updatedCount = 0;
1264
+
1265
+ for (const entry of list.providers) {
1266
+ const { type, category, checksum, version } = entry;
1267
+ const cacheKey = `${category}/${type}`;
1268
+ if (cachedChecksums[cacheKey] === checksum) continue; // already current
1269
+
1270
+ // Download this provider's manifest
1271
+ const dlUrl = `${ProviderLoader.REGISTRY_BASE_URL}/providers/${type}/${version}/download`;
1272
+ const manifestBody = await new Promise<string>((resolve, reject) => {
1273
+ const req = https.get(dlUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 30000 }, (res) => {
1274
+ if (res.statusCode !== 200) { reject(new Error(`registry download HTTP ${res.statusCode} for ${type}@${version}`)); return; }
1275
+ const chunks: Buffer[] = [];
1276
+ res.on('data', (c: Buffer) => chunks.push(c));
1277
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
1278
+ });
1279
+ req.on('error', reject);
1280
+ req.on('timeout', () => { req.destroy(); reject(new Error(`download timeout for ${type}`)); });
1281
+ });
1282
+
1283
+ // Verify checksum
1284
+ const actualChecksum = await new Promise<string>((resolve) => {
1285
+ const crypto = require('crypto') as typeof import('crypto');
1286
+ resolve(crypto.createHash('sha256').update(manifestBody, 'utf-8').digest('hex'));
1287
+ });
1288
+ if (actualChecksum !== checksum) {
1289
+ this.log(`⚠ Registry checksum mismatch for ${type}@${version} — skipping`);
1290
+ continue;
1291
+ }
1292
+
1293
+ // Write to upstream dir
1294
+ const providerDir = path.join(this.upstreamDir, category, type);
1295
+ fs.mkdirSync(providerDir, { recursive: true });
1296
+ fs.writeFileSync(path.join(providerDir, 'provider.json'), manifestBody, 'utf-8');
1297
+
1298
+ cachedChecksums[cacheKey] = checksum;
1299
+ updatedCount++;
1300
+ this.log(`✓ Registry updated: ${category}/${type}@${version}`);
1301
+ }
1302
+
1303
+ // Persist updated checksums
1304
+ fs.mkdirSync(this.upstreamDir, { recursive: true });
1305
+ fs.writeFileSync(regMetaPath, JSON.stringify({
1306
+ checksums: cachedChecksums,
1307
+ syncedAt: new Date().toISOString(),
1308
+ providerCount: list.providers.length,
1309
+ }, null, 2));
1310
+
1311
+ this.log(`Registry sync complete: ${list.providers.length} providers, ${updatedCount} updated`);
1312
+ return { updated: updatedCount > 0 };
1313
+ } catch (e: any) {
1314
+ this.log(`⚠ Registry sync failed (falling back to GitHub tarball): ${e?.message}`);
1315
+ return { updated: false, error: e?.message };
1316
+ }
1317
+ }
1318
+
1127
1319
  async fetchLatest(): Promise<{ updated: boolean; error?: string }> {
1128
1320
  if (this.disableUpstream) {
1129
1321
  this.log('Upstream fetch skipped (sourceMode=no-upstream)');
1130
1322
  return { updated: false };
1131
1323
  }
1132
1324
  const https = require('https') as typeof import('https');
1133
- const { execSync } = require('child_process') as typeof import('child_process');
1325
+ const { exec } = require('child_process') as typeof import('child_process');
1326
+ const { promisify } = require('util');
1327
+ const execAsync = promisify(exec);
1134
1328
 
1135
1329
  const metaPath = path.join(this.upstreamDir, ProviderLoader.META_FILE);
1136
1330
  let prevEtag = '';
@@ -1207,7 +1401,7 @@ export class ProviderLoader {
1207
1401
 
1208
1402
  // Extract
1209
1403
  fs.mkdirSync(tmpExtract, { recursive: true });
1210
- execSync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1404
+ await execAsync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1211
1405
 
1212
1406
  // Tarball internal structure: adhdev-providers-main/ide/... → strip 1 level
1213
1407
  const extracted = fs.readdirSync(tmpExtract);
@@ -1315,15 +1509,17 @@ export class ProviderLoader {
1315
1509
  } catch { }
1316
1510
  }
1317
1511
 
1318
- /** Count provider files (provider.js or provider.json) */
1512
+ /** Count provider files (provider.v1.json or provider.json — at most one per dir). */
1319
1513
  private countProviders(dir: string): number {
1320
1514
  if (!fs.existsSync(dir)) return 0;
1321
1515
  let count = 0;
1322
1516
  const scan = (d: string) => {
1323
1517
  try {
1324
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
1518
+ const entries = fs.readdirSync(d, { withFileTypes: true });
1519
+ const hasManifest = entries.some(e => e.name === 'provider.v1.json' || e.name === 'provider.json');
1520
+ if (hasManifest) count++;
1521
+ for (const entry of entries) {
1325
1522
  if (entry.isDirectory()) scan(path.join(d, entry.name));
1326
- else if (entry.name === 'provider.json') count++;
1327
1523
  }
1328
1524
  } catch { }
1329
1525
  };
@@ -1372,11 +1568,15 @@ export class ProviderLoader {
1372
1568
  return args ? args.map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(' ') : '';
1373
1569
  }
1374
1570
  const schemaDef = this.getSettingsSchema(providerType)[key];
1375
- const defaultVal = schemaDef
1376
- ? (key === 'autoApprove' && schemaDef.type === 'boolean'
1377
- ? true
1378
- : schemaDef.default)
1379
- : undefined;
1571
+ // (fix) Previously this hard-coded `autoApprove` boolean default to `true`,
1572
+ // overriding whatever schemaDef.default the provider.json declared. That
1573
+ // surfaced as soon as a provider added an `autoApprove` schema entry with
1574
+ // default=false: the user had never opted in but the daemon treated the
1575
+ // session as auto-approve, which then triggered recordAutoApproval every
1576
+ // time the CLI showed an approval modal — producing a flood of system
1577
+ // "Auto-approved: ..." messages and keeping the session pinned to
1578
+ // generating while modals cycled in and out. Trust the schemaDef.default.
1579
+ const defaultVal = schemaDef ? schemaDef.default : undefined;
1380
1580
 
1381
1581
  const config = this.readConfig();
1382
1582
  const userVal = config?.providerSettings?.[providerType]?.[key];
@@ -1490,10 +1690,17 @@ export class ProviderLoader {
1490
1690
  ...this.getSyntheticSettings(type, provider),
1491
1691
  ...(provider.settings || {}),
1492
1692
  };
1693
+ // (fix) Previously this clause forced `autoApprove.default = true` for any
1694
+ // boolean autoApprove schema, even when the provider.json explicitly set
1695
+ // `default: false`. Combined with the synthetic-settings fallback at
1696
+ // getSyntheticSettings (which also defaults autoApprove to true when the
1697
+ // provider doesn't supply one), that meant CLI providers silently turned on
1698
+ // auto-approval, producing a flood of "Auto-approved: ..." system messages
1699
+ // every time an approval modal appeared and pinning the session to
1700
+ // generating while modals cycled. Trust the provider's declared default.
1493
1701
  if (result.autoApprove?.type === 'boolean') {
1494
1702
  result.autoApprove = {
1495
1703
  ...result.autoApprove,
1496
- default: true,
1497
1704
  public: true,
1498
1705
  label: result.autoApprove.label || 'Auto Approve',
1499
1706
  description: result.autoApprove.description || 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1518,7 +1725,10 @@ export class ProviderLoader {
1518
1725
  if (!provider.settings?.autoApprove) {
1519
1726
  result.autoApprove = {
1520
1727
  type: 'boolean',
1521
- default: true,
1728
+ // (fix) Safe default is *off*. Auto-approving every modal without the
1729
+ // user opting in produced silent-bash-execution surprises and the
1730
+ // "Auto-approved: ..." system-message flood seen on AGY/Codex.
1731
+ default: false,
1522
1732
  public: true,
1523
1733
  label: 'Auto Approve',
1524
1734
  description: 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1581,23 +1791,32 @@ export class ProviderLoader {
1581
1791
  const cat = provider.category;
1582
1792
 
1583
1793
  const searchRoots = this.getProviderRoots();
1794
+ const hasManifest = (dir: string) =>
1795
+ fs.existsSync(path.join(dir, 'provider.v1.json')) || fs.existsSync(path.join(dir, 'provider.json'));
1796
+ const readManifestType = (dir: string): string | null => {
1797
+ for (const file of ['provider.v1.json', 'provider.json']) {
1798
+ const p = path.join(dir, file);
1799
+ if (!fs.existsSync(p)) continue;
1800
+ try {
1801
+ const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
1802
+ if (typeof data?.type === 'string') return data.type;
1803
+ } catch { /* skip */ }
1804
+ }
1805
+ return null;
1806
+ };
1584
1807
  for (const root of searchRoots) {
1585
1808
  if (!fs.existsSync(root)) continue;
1586
1809
  const candidate = this.getProviderDir(root, cat, type);
1587
- if (fs.existsSync(path.join(candidate, 'provider.json'))) return candidate;
1810
+ if (hasManifest(candidate)) return candidate;
1588
1811
  // Scan category dir for type match
1589
1812
  const catDir = path.join(root, cat);
1590
1813
  if (fs.existsSync(catDir)) {
1591
1814
  try {
1592
1815
  for (const entry of fs.readdirSync(catDir, { withFileTypes: true })) {
1593
1816
  if (!entry.isDirectory()) continue;
1594
- const jsonPath = path.join(catDir, entry.name, 'provider.json');
1595
- if (fs.existsSync(jsonPath)) {
1596
- try {
1597
- const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
1598
- if (data.type === type) return path.join(catDir, entry.name);
1599
- } catch { /* skip */ }
1600
- }
1817
+ const entryDir = path.join(catDir, entry.name);
1818
+ const manifestType = readManifestType(entryDir);
1819
+ if (manifestType === type) return entryDir;
1601
1820
  }
1602
1821
  } catch { /* skip */ }
1603
1822
  }
@@ -1690,17 +1909,44 @@ export class ProviderLoader {
1690
1909
  return;
1691
1910
  }
1692
1911
 
1693
- // Check if this directory has provider.json
1912
+ // v1-first manifest selection. provider.v1.json (the SDK-shape
1913
+ // manifest with `overrides`, `tui`, `source`, `canonicalHistory`)
1914
+ // wins over provider.json (legacy). Without this branch the v1
1915
+ // file is silently ignored — that's how the codex-cli `overrides`
1916
+ // path and the tui-block builders went un-honored for the first
1917
+ // pass of SDK rollout.
1918
+ const hasV1 = entries.some(e => e.name === 'provider.v1.json');
1694
1919
  const hasJson = entries.some(e => e.name === 'provider.json');
1695
1920
 
1696
- if (hasJson) {
1697
- const jsonPath = path.join(d, 'provider.json');
1921
+ if (hasV1 || hasJson) {
1922
+ const manifestFile = hasV1 ? 'provider.v1.json' : 'provider.json';
1923
+ const jsonPath = path.join(d, manifestFile);
1698
1924
  try {
1699
1925
  const raw = fs.readFileSync(jsonPath, 'utf-8');
1700
1926
  const mod = JSON.parse(raw) as Omit<ProviderModule, 'extensionIdPattern'> & {
1701
1927
  extensionIdPattern?: RegExp | string;
1702
1928
  };
1703
1929
 
1930
+ // Validate v1 manifests against the SDK schema. Failures are
1931
+ // surfaced as a single warning line with all issues attached
1932
+ // so manifest authors don't need to guess which field is wrong.
1933
+ // Loading still proceeds — bricking the daemon on a single
1934
+ // bad field would be worse than running with a known warning.
1935
+ if (hasV1 && mod?.category === 'cli') {
1936
+ try {
1937
+ const { validateCliProviderManifest, formatManifestValidationIssues } =
1938
+ require('./sdk/v1/validators/manifest.js') as typeof import('./sdk/v1/validators/manifest.js');
1939
+ const validation = validateCliProviderManifest(mod);
1940
+ if (!validation.ok) {
1941
+ this.log(`⚠ ${jsonPath}: schema validation failed:\n${formatManifestValidationIssues(validation.issues)}`);
1942
+ }
1943
+ } catch (e: any) {
1944
+ // Validator load failed — log once and continue so a
1945
+ // broken validator can't take down provider loading.
1946
+ this.log(`⚠ ${jsonPath}: validator unavailable: ${e?.message || e}`);
1947
+ }
1948
+ }
1949
+
1704
1950
  // Restore RegExp fields from JSON (extensionIdPattern)
1705
1951
  if (typeof mod.extensionIdPattern === 'string') {
1706
1952
  const flags = mod.extensionIdPattern_flags || '';
@@ -1712,6 +1958,22 @@ export class ProviderLoader {
1712
1958
  ...(extensionIdPattern instanceof RegExp ? { extensionIdPattern } : {}),
1713
1959
  };
1714
1960
 
1961
+ // v1 manifests use `nativeHistory` as the canonical field name.
1962
+ // Legacy v0 manifests use `canonicalHistory`. The daemon's
1963
+ // runtime + downstream code reads `provider.nativeHistory`, so
1964
+ // for legacy manifests we copy `canonicalHistory` into
1965
+ // `nativeHistory` here. We also keep `canonicalHistory`
1966
+ // populated in both directions (deprecated alias) so any
1967
+ // external consumers still reading the old name keep working
1968
+ // during the one-release deprecation window.
1969
+ const nh = (normalizedProvider as any).nativeHistory;
1970
+ const ch = (normalizedProvider as any).canonicalHistory;
1971
+ if (nh && !ch) {
1972
+ (normalizedProvider as any).canonicalHistory = nh;
1973
+ } else if (ch && !nh) {
1974
+ (normalizedProvider as any).nativeHistory = ch;
1975
+ }
1976
+
1715
1977
  const validation = validateProviderDefinition(normalizedProvider);
1716
1978
  for (const warning of validation.warnings) {
1717
1979
  this.log(`⚠ ${jsonPath}: ${warning}`);
@@ -1725,6 +1987,10 @@ export class ProviderLoader {
1725
1987
  const scriptsPath = path.join(d, 'scripts.js');
1726
1988
  if (!hasCompatibility && fs.existsSync(scriptsPath)) {
1727
1989
  try {
1990
+ // Gate the IDE/extension scripts.js (legacy single-file
1991
+ // format) under the same whitelist. `d` here is the
1992
+ // provider dir; its grandparent contains _shared.
1993
+ registerProviderScriptRootSafely(path.dirname(path.dirname(d)));
1728
1994
  delete require.cache[require.resolve(scriptsPath)];
1729
1995
  const scripts = require(scriptsPath) as Partial<ProviderScripts>;
1730
1996
  normalizedProvider.scripts = scripts;
@@ -37,6 +37,12 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
37
37
  'approvalPositiveHints',
38
38
  'sessionIdPattern',
39
39
  'historyBehavior',
40
+ // v1 canonical field name. The loader fills this in for legacy v0
41
+ // manifests that ship the old `canonicalHistory` spelling.
42
+ 'nativeHistory',
43
+ // Deprecated v0 alias for `nativeHistory`. Kept in the allowed-keys
44
+ // list so legacy provider.json manifests don't trip the unknown-key
45
+ // warning during the one-release deprecation window.
40
46
  'canonicalHistory',
41
47
  'autoFixProfile',
42
48
  'ideLevelScripts',
@@ -64,6 +70,8 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
64
70
  'sendKey',
65
71
  'submitStrategy',
66
72
  'requirePromptEchoBeforeSubmit',
73
+ 'requiresFinalAssistantBeforeIdle',
74
+ 'augmentStaleSnapshot',
67
75
  'timeouts',
68
76
  'disableUpstream',
69
77
  ])
@@ -128,7 +136,7 @@ export function validateProviderDefinition(raw: unknown): ProviderValidationResu
128
136
  }
129
137
 
130
138
  validateCapabilities(provider as unknown as ProviderModule, controls, errors)
131
- validateCanonicalHistory(provider.canonicalHistory, errors)
139
+ validateNativeHistory(provider.nativeHistory, errors)
132
140
  validateMeshCoordinator(provider.meshCoordinator, errors)
133
141
 
134
142
  for (const control of controls) {
@@ -228,33 +236,43 @@ function validateCapabilities(provider: ProviderModule, controls: ProviderContro
228
236
  }
229
237
  }
230
238
 
231
- function validateCanonicalHistory(raw: unknown, errors: string[]): void {
239
+ function validateNativeHistory(raw: unknown, errors: string[]): void {
232
240
  if (raw === undefined) return
233
241
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
234
- errors.push('canonicalHistory must be an object')
242
+ errors.push('nativeHistory must be an object')
235
243
  return
236
244
  }
237
245
 
238
- const canonicalHistory = raw as Record<string, unknown>
239
- const format = canonicalHistory.format
246
+ const nativeHistory = raw as Record<string, unknown>
247
+ const format = nativeHistory.format
240
248
  if (format !== undefined && (typeof format !== 'string' || !format.trim())) {
241
- errors.push('canonicalHistory.format must be a non-empty string when provided')
249
+ errors.push('nativeHistory.format must be a non-empty string when provided')
242
250
  }
243
251
 
244
- const watchPath = canonicalHistory.watchPath
252
+ const watchPath = nativeHistory.watchPath
245
253
  if (watchPath !== undefined && (typeof watchPath !== 'string' || !watchPath.trim())) {
246
- errors.push('canonicalHistory.watchPath must be a non-empty string when provided')
254
+ errors.push('nativeHistory.watchPath must be a non-empty string when provided')
247
255
  }
248
256
 
249
- const mode = canonicalHistory.mode
257
+ const mode = nativeHistory.mode
250
258
  if (mode !== undefined && !['native-source', 'materialized-mirror', 'disabled'].includes(String(mode))) {
251
- errors.push('canonicalHistory.mode must be one of: native-source, materialized-mirror, disabled')
259
+ errors.push('nativeHistory.mode must be one of: native-source, materialized-mirror, disabled')
252
260
  }
253
261
 
254
- const scripts = canonicalHistory.scripts
262
+ // Chat transcript contract version (transcript-v2.ts). Absent → treated as
263
+ // v1 by readDeclaredChatContractVersion(). A2 will reject unrecognised
264
+ // values at load time; A1 only validates the field shape.
265
+ const chatContractVersion = nativeHistory.contractVersion
266
+ if (chatContractVersion !== undefined
267
+ && chatContractVersion !== '1.0'
268
+ && chatContractVersion !== '2.0') {
269
+ errors.push(`nativeHistory.contractVersion must be '1.0' or '2.0' when provided (got ${JSON.stringify(chatContractVersion)})`)
270
+ }
271
+
272
+ const scripts = nativeHistory.scripts
255
273
  if (scripts === undefined) return
256
274
  if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
257
- errors.push('canonicalHistory.scripts must be an object')
275
+ errors.push('nativeHistory.scripts must be an object')
258
276
  return
259
277
  }
260
278
 
@@ -262,7 +280,7 @@ function validateCanonicalHistory(raw: unknown, errors: string[]): void {
262
280
  for (const key of ['readSession', 'listSessions']) {
263
281
  const value = scriptConfig[key]
264
282
  if (typeof value !== 'string' || !value.trim()) {
265
- errors.push(`canonicalHistory.scripts.${key} must be a non-empty string`)
283
+ errors.push(`nativeHistory.scripts.${key} must be a non-empty string`)
266
284
  }
267
285
  }
268
286
  }