@adhdev/daemon-core 1.0.18-rc.20 → 1.0.18-rc.4

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 (74) hide show
  1. package/dist/cli-adapters/provider-cli-shared.d.ts +0 -33
  2. package/dist/commands/cli-manager.d.ts +0 -9
  3. package/dist/commands/upgrade-helper.d.ts +0 -1
  4. package/dist/commands/windows-atomic-upgrade.d.ts +0 -5
  5. package/dist/config/mesh-json-config.d.ts +0 -62
  6. package/dist/index.d.ts +2 -4
  7. package/dist/index.js +3998 -6112
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +3855 -5966
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/mesh/coordinator-prompt.d.ts +0 -76
  12. package/dist/mesh/mesh-active-work.d.ts +1 -1
  13. package/dist/mesh/mesh-ledger.d.ts +1 -28
  14. package/dist/mesh/mesh-node-identity.d.ts +0 -23
  15. package/dist/mesh/mesh-refine-gates.d.ts +0 -89
  16. package/dist/mesh/mesh-remote-event-pull.d.ts +0 -3
  17. package/dist/mesh/mesh-runtime-store.d.ts +0 -11
  18. package/dist/mesh/mesh-work-queue.d.ts +1 -24
  19. package/dist/providers/chat-message-normalization.d.ts +0 -22
  20. package/dist/providers/cli-provider-instance-types.d.ts +0 -4
  21. package/dist/providers/cli-provider-instance.d.ts +0 -78
  22. package/dist/providers/contracts.d.ts +0 -17
  23. package/dist/providers/provider-schema.d.ts +0 -1
  24. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +0 -14
  25. package/dist/providers/types/interactive-prompt.d.ts +0 -18
  26. package/dist/repo-mesh-types.d.ts +6 -38
  27. package/dist/shared-types.d.ts +2 -4
  28. package/package.json +3 -3
  29. package/src/boot/daemon-lifecycle.ts +0 -10
  30. package/src/cli-adapters/cli-state-engine.ts +1 -17
  31. package/src/cli-adapters/provider-cli-adapter.ts +6 -2
  32. package/src/cli-adapters/provider-cli-shared.ts +0 -48
  33. package/src/commands/cli-manager.ts +8 -72
  34. package/src/commands/high-family/mesh-coordinator-launch.ts +2 -17
  35. package/src/commands/high-family/mesh-events.ts +2 -13
  36. package/src/commands/med-family/mesh-crud.ts +0 -155
  37. package/src/commands/med-family/mesh-queue.ts +1 -74
  38. package/src/commands/router-refine.ts +4 -71
  39. package/src/commands/router.ts +0 -8
  40. package/src/commands/upgrade-helper.ts +15 -78
  41. package/src/commands/windows-atomic-upgrade.ts +40 -194
  42. package/src/config/mesh-json-config.ts +0 -111
  43. package/src/index.ts +1 -21
  44. package/src/mesh/coordinator-prompt.ts +11 -258
  45. package/src/mesh/mesh-active-work.ts +1 -11
  46. package/src/mesh/mesh-completion-synthesis.ts +1 -12
  47. package/src/mesh/mesh-event-classify.ts +0 -19
  48. package/src/mesh/mesh-event-forwarding.ts +6 -64
  49. package/src/mesh/mesh-events-utils.ts +0 -42
  50. package/src/mesh/mesh-ledger.ts +0 -77
  51. package/src/mesh/mesh-node-identity.ts +0 -49
  52. package/src/mesh/mesh-queue-assignment.ts +11 -210
  53. package/src/mesh/mesh-reconcile-loop.ts +3 -306
  54. package/src/mesh/mesh-refine-gates.ts +0 -300
  55. package/src/mesh/mesh-remote-event-pull.ts +47 -68
  56. package/src/mesh/mesh-runtime-store.ts +0 -21
  57. package/src/mesh/mesh-work-queue.ts +2 -85
  58. package/src/providers/chat-message-normalization.ts +0 -53
  59. package/src/providers/cli-provider-instance-types.ts +0 -53
  60. package/src/providers/cli-provider-instance.ts +15 -497
  61. package/src/providers/contracts.ts +1 -25
  62. package/src/providers/provider-schema.ts +0 -83
  63. package/src/providers/sdk/v1/builders/cli/detect-status.ts +0 -23
  64. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +0 -20
  65. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +0 -44
  66. package/src/providers/types/interactive-prompt.ts +0 -77
  67. package/src/repo-mesh-types.ts +12 -112
  68. package/src/shared-types.ts +1 -9
  69. package/src/status/reporter.ts +0 -1
  70. package/src/status/snapshot.ts +0 -2
  71. package/dist/mesh/mesh-disk-retention.d.ts +0 -105
  72. package/dist/providers/auto-approve-modes.d.ts +0 -14
  73. package/src/mesh/mesh-disk-retention.ts +0 -370
  74. package/src/providers/auto-approve-modes.ts +0 -103
@@ -14,8 +14,6 @@ import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
14
14
  import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
15
15
  import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
16
16
  import type { MagiKindPanelMap, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
17
- import type { ProviderModule } from './providers/contracts.js';
18
- import type { RepoMeshDeclarativeConfig } from './config/mesh-json-config.js';
19
17
  export interface RepoMesh {
20
18
  id: string;
21
19
  name: string;
@@ -257,8 +255,6 @@ export interface RepoMeshPolicy {
257
255
  * A node policy may override this per-node (RepoMeshNodePolicy.delegatedWorkerAutoApprove).
258
256
  */
259
257
  delegatedWorkerAutoApprove?: boolean;
260
- /** Explicit opt-in required before delegated workers may use a dangerous provider mode. */
261
- delegatedWorkerDangerousModeAllow?: boolean;
262
258
  /**
263
259
  * MESH-SEND-KEYS (feature 3): opt-in to allow the coordinator to inject
264
260
  * DESTRUCTIVE keys (CTRL_C / ESC) into a worker PTY via mesh_send_keys. These
@@ -410,8 +406,6 @@ export interface RepoMeshNodePolicy {
410
406
  * precedence over the mesh-level policy for worker sessions launched onto this node.
411
407
  */
412
408
  delegatedWorkerAutoApprove?: boolean;
413
- /** Per-node override for dangerous delegated worker mode authorization. */
414
- delegatedWorkerDangerousModeAllow?: boolean;
415
409
  /**
416
410
  * MESH-SEND-KEYS (feature 3): per-node override for
417
411
  * RepoMeshPolicy.allowSendKeysDestructive.
@@ -530,39 +524,13 @@ export declare function normalizeAutoFastForwardPolicy(value: unknown): NonNulla
530
524
  */
531
525
  export declare function mergeAndNormalizePolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy;
532
526
  /**
533
- * Resolve delegated worker auto-approve. Legacy providers return a boolean. Providers
534
- * with modes return a mode id, except a dangerous mode is downgraded to a
535
- * non-dangerous PTY mode unless mesh/node policy explicitly opts in.
536
- *
537
- * THREE EXPLICIT STAGES do not collapse them; the ordering is a hard MAGI
538
- * invariant:
539
- *
540
- * ① ENABLE gate (machine-local policy only): node boolean > mesh boolean.
541
- * `enabled=false` returns `false` IMMEDIATELY, BEFORE any mode selection.
542
- * The repo `mesh.json` providerDefaults has ZERO influence here — a
543
- * node/mesh opt-out is never overridden by a repo-declared requested mode.
544
- *
545
- * ② MODE selection (only when enabled === true):
546
- * task override [FUTURE — param reserved below, not yet wired] >
547
- * repo mesh.json providerDefaults.autoApproveModes[providerType] >
548
- * provider spec autoApproveModes.default.
549
- * A repo-requested mode ID is adopted ONLY when it exists in the provider's
550
- * own `autoApproveModes.modes`; an unknown/stale/typo'd ID is IGNORED and we
551
- * fall back to the provider default (fail-closed: never coerce into a
552
- * dangerous mode via a bad ID).
553
- *
554
- * ③ DANGEROUS gate: whichever mode stage ② picked, if it is dangerous and the
555
- * machine-local delegatedWorkerDangerousModeAllow is not set, downgrade to a
556
- * non-dangerous PTY-parse mode (or `false` if none exists).
527
+ * Resolve whether a delegated worker session launched onto `nodePolicy` (within a mesh
528
+ * governed by `meshPolicy`) should auto-approve. Precedence: node override mesh policy
529
+ * default true. The result is stamped into the worker launch settings envelope as
530
+ * `autoApprove`; it wins over the global per-provider-type autoApprove config because the
531
+ * launch path merges the envelope as a settingsOverride on top of the provider defaults.
557
532
  */
558
- export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, provider?: Pick<ProviderModule, 'autoApproveModes'> | null, repoConfig?: RepoMeshDeclarativeConfig | null, providerType?: string | null): boolean | string;
559
- export declare function resolveDelegatedWorkerDangerousModeAllow(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerDangerousModeAllow'> | null): boolean;
560
- /** Shape a boolean-or-mode resolution for the settings precedence contract. */
561
- export declare function delegatedWorkerAutoApproveSettings(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, provider?: Pick<ProviderModule, 'autoApproveModes'> | null, repoConfig?: RepoMeshDeclarativeConfig | null, providerType?: string | null): {
562
- autoApprove: boolean | undefined;
563
- autoApproveMode: string | undefined;
564
- delegatedWorkerDangerousModeAllow: boolean;
565
- };
533
+ export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove'> | null): boolean;
566
534
  /**
567
535
  * MESH-SEND-KEYS (feature 3): resolve whether DESTRUCTIVE key injection
568
536
  * (CTRL_C/ESC via mesh_send_keys) is permitted for a node. Node policy overrides
@@ -13,7 +13,7 @@ export type { ProviderState, ProviderStatus, ActiveChatData, IdeProviderState, C
13
13
  export type { ProviderErrorReason } from './providers/provider-instance.js';
14
14
  import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _ProviderErrorReason } from './providers/provider-instance.js';
15
15
  import type { WorkspaceEntry } from './config/workspaces.js';
16
- import type { AutoApproveModesConfig, ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
16
+ import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
17
17
  import type { GitCompactSummary, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams } from './git/git-types.js';
18
18
  import type { InteractivePrompt } from './providers/types/interactive-prompt.js';
19
19
  export type { GitCommandName, GitCompactSummary, GitDiffSummary, GitFailureReason, GitFileChange, GitFileChangeStatus, GitRepoIdentity, GitRepoStatus, GitSnapshot, GitSnapshotCompareSummary, GitSnapshotReason, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams, } from './git/git-types.js';
@@ -428,8 +428,6 @@ export interface AvailableProviderInfo {
428
428
  lastVerification?: MachineProviderCheckResult;
429
429
  /** Provider-declared Repo Mesh coordinator/MCP behavior. */
430
430
  meshCoordinator?: ProviderMeshCoordinatorConfig;
431
- /** Provider-declared auto-approve choices shown by session launch UIs. */
432
- autoApproveModes?: AutoApproveModesConfig;
433
431
  /** BRAIN-ROUTING: suggested model values for the new-session model dropdown. */
434
432
  modelOptions?: string[];
435
433
  /** BRAIN-ROUTING: reasoning-effort values for the new-session thinking dropdown. */
@@ -626,7 +624,7 @@ export interface DashboardBootstrapDaemonEntry extends Partial<CloudDaemonSummar
626
624
  p2p?: StatusReportPayload['p2p'];
627
625
  timestamp?: number;
628
626
  }
629
- export type DaemonStatusEventName = 'agent:generating_started' | 'agent:waiting_approval' | 'agent:waiting_choice' | 'agent:generating_completed' | 'agent:stopped' | 'monitor:no_progress' | 'monitor:long_generating';
627
+ export type DaemonStatusEventName = 'agent:generating_started' | 'agent:waiting_approval' | 'agent:generating_completed' | 'agent:stopped' | 'monitor:no_progress' | 'monitor:long_generating';
630
628
  /** Minimal daemon-originated event payload relayed through the server. */
631
629
  export interface DaemonStatusEventPayload {
632
630
  event: DaemonStatusEventName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "1.0.18-rc.20",
3
+ "version": "1.0.18-rc.4",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,8 +47,8 @@
47
47
  "author": "vilmire",
48
48
  "license": "AGPL-3.0-or-later",
49
49
  "dependencies": {
50
- "@adhdev/mesh-shared": "1.0.18-rc.20",
51
- "@adhdev/session-host-core": "1.0.18-rc.20",
50
+ "@adhdev/mesh-shared": "1.0.18-rc.4",
51
+ "@adhdev/session-host-core": "1.0.18-rc.4",
52
52
  "@agentclientprotocol/sdk": "^0.16.1",
53
53
  "ajv": "^8.20.0",
54
54
  "ajv-formats": "^3.0.1",
@@ -39,7 +39,6 @@ import type { IdeProviderInstance } from '../providers/ide-provider-instance.js'
39
39
  import { createDefaultGitCommandServices } from '../git/git-commands.js';
40
40
  import { setupMeshEventForwarding } from '../mesh/mesh-events.js';
41
41
  import { setupMeshReconcileLoop } from '../mesh/mesh-reconcile-loop.js';
42
- import { MeshRuntimeStore } from '../mesh/mesh-runtime-store.js';
43
42
  import { loadMeshCoordinatorRegistry } from '../mesh/coordinator-registry.js';
44
43
  import { applyProcessHardening } from './process-hardening.js';
45
44
  import { installProviderProcessShim } from '../providers/sdk/v1/sandbox/require-whitelist.js';
@@ -504,13 +503,4 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
504
503
  try { m.disconnect(); } catch { /* noop */ }
505
504
  }
506
505
  cdpManagers.clear();
507
-
508
- // 7. VACUUM the mesh runtime DB. Retention prunes rows with DELETE (frees pages
509
- // inside the file but never shrinks it); the mesh-runtime.db grew to hundreds of
510
- // MB (mission 86def38d disk-accumulation bootstrap failure) because it was never
511
- // compacted. Do this LAST, after all writers (reconcile loop, CLIs, instances)
512
- // are stopped, so nothing contends for the exclusive VACUUM lock. Best-effort —
513
- // vacuum() swallows its own errors; the try/catch guards the getInstance() throw
514
- // when the store never opened (degraded JSONL-only mode).
515
- try { MeshRuntimeStore.getInstance().vacuum(); } catch { /* store unavailable — nothing to vacuum */ }
516
506
  }
@@ -15,7 +15,6 @@ import {
15
15
  import {
16
16
  buildCliScreenSnapshot,
17
17
  compactPromptText,
18
- isPurePtyTranscriptProvider,
19
18
  normalizePromptText,
20
19
  promptLikelyVisible,
21
20
  type CliChatMessage,
@@ -362,22 +361,7 @@ export class CliStateEngine {
362
361
  // idle transition, unchanged. Scoped to transcriptAuthority:'provider'
363
362
  // only, so PTY-authoritative providers (whose spinner/settled parsing
364
363
  // already drives generating promptly) are unaffected.
365
- //
366
- // (fix: kimi pure-PTY completion-emit) The pure-PTY full-buffer class
367
- // (kimi and kin — see isPurePtyTranscriptProvider) is NOT
368
- // transcriptAuthority:'provider', so without this it stays idle when a
369
- // prompt is submitted from idle: the FSM never crosses generating→idle,
370
- // detectStatusTransition's generating|waiting_approval→idle arm never
371
- // runs, and agent:generating_completed is never emitted — the mesh
372
- // coordinator leaves the task 'assigned' and the status-agnostic stall
373
- // watchdog then false-fires task_stalled on the finished-but-idle
374
- // session. Promoting this class to generating on turn-start makes the
375
- // existing PTY-parsed final-assistant idle gate produce a real
376
- // generating→idle completion edge. Same idle safety as above: applyIdle
377
- // / finishResponse still own the actual idle transition.
378
- const promoteOnTurnStart = this.provider.transcriptAuthority === 'provider'
379
- || isPurePtyTranscriptProvider(this.provider);
380
- if (promoteOnTurnStart && this.currentStatus !== 'waiting_approval') {
364
+ if (this.provider.transcriptAuthority === 'provider' && this.currentStatus !== 'waiting_approval') {
381
365
  this.setStatus('generating', 'turn_started');
382
366
  this.callbacks.onStatusChange();
383
367
  }
@@ -39,7 +39,6 @@ import {
39
39
  compactPromptText,
40
40
  estimatePromptDisplayLines,
41
41
  extractPromptRetrySnippet,
42
- isPurePtyTranscriptProvider,
43
42
  listCliScriptNames,
44
43
  normalizePromptText,
45
44
  normalizeScreenSnapshot,
@@ -457,7 +456,12 @@ export class ProviderCliAdapter implements CliAdapter {
457
456
  * provider-owned) so no other provider's turn-scoped parse changes.
458
457
  */
459
458
  private parsesFullPtyTranscriptFromBuffer(): boolean {
460
- return isPurePtyTranscriptProvider(this.provider);
459
+ if (this.providerOwnsTranscript()) return false;
460
+ // nativeHistory is a top-level provider field not surfaced on
461
+ // CliProviderModule; read it via the same structural cast used for `tui`.
462
+ if ((this.provider as { nativeHistory?: unknown }).nativeHistory) return false;
463
+ const transcriptPty = (this.provider.tui as { transcriptPty?: { scope?: unknown } } | undefined)?.transcriptPty;
464
+ return transcriptPty?.scope === 'buffer';
461
465
  }
462
466
 
463
467
  /**
@@ -254,54 +254,6 @@ export interface CliProviderModule {
254
254
  _versionWarning?: string | null;
255
255
  }
256
256
 
257
- /**
258
- * PURE-PTY TRANSCRIPT CLASS predicate (kimi and kin).
259
- *
260
- * A provider is "pure-PTY full-buffer" when it reconstructs its ENTIRE transcript
261
- * from the rendered PTY buffer on every read and has NO alternate transcript
262
- * source:
263
- * - transcriptAuthority !== 'provider' (the daemon PTY parser owns the transcript,
264
- * not a provider-side canonical source)
265
- * - NO nativeHistory (no on-disk / native-source history to fall back to)
266
- * - tui.transcriptPty.scope === 'buffer' (the parser walks the full rendered buffer)
267
- *
268
- * This class is invisible to two provider-authority-keyed code paths that other
269
- * providers rely on for mesh completion semantics:
270
- * 1. CliStateEngine.onTurnStarted only promotes to 'generating' for
271
- * transcriptAuthority==='provider' providers — so a pure-PTY session that
272
- * submits a prompt while already idle collapses idle→idle, the
273
- * generating→idle edge never occurs, and agent:generating_completed is never
274
- * emitted (the coordinator ledger leaves the task 'assigned' forever).
275
- * 2. checkMeshWorkerStall's native-transcript completion reconcile is gated on
276
- * the native-source shape, so a finished pure-PTY worker's static idle is
277
- * misread as monitor:no_progress (a false task_stalled).
278
- *
279
- * The runtime capability, NOT any single spec field value, is authoritative: a
280
- * given kimi manifest checkout may declare nativeHistory/transcriptAuthority, but
281
- * the live-loaded pure-PTY session has none. Callers that only hold a
282
- * CliProviderModule (the engine) share this exact predicate with the adapter
283
- * (parsesFullPtyTranscriptFromBuffer) so the two never drift.
284
- */
285
- export function isPurePtyTranscriptProvider(provider: {
286
- transcriptAuthority?: 'provider' | 'daemon';
287
- nativeHistory?: unknown;
288
- tui?: Record<string, unknown>;
289
- } | null | undefined): boolean {
290
- // No provider (e.g. an instance whose module isn't set when the stall watchdog
291
- // ticks) ⇒ not the pure-PTY class. Guarding here protects every caller — the
292
- // stall-reconcile and turn-start paths pass this.provider unguarded, and
293
- // this.provider is genuinely nullable at runtime (see this.provider?.nativeHistory
294
- // in cli-provider-instance.ts). Matches resolveLocalSessionPurePty's own
295
- // no-provider ⇒ not-pure-PTY contract.
296
- if (!provider) return false;
297
- if (provider.transcriptAuthority === 'provider') return false;
298
- // nativeHistory is a top-level provider field not surfaced on
299
- // CliProviderModule; read it via the same structural shape the adapter uses.
300
- if (provider.nativeHistory) return false;
301
- const transcriptPty = (provider.tui as { transcriptPty?: { scope?: unknown } } | undefined)?.transcriptPty;
302
- return transcriptPty?.scope === 'buffer';
303
- }
304
-
305
257
  function stripAnsi(str: string): string {
306
258
  // eslint-disable-next-line no-control-regex
307
259
  return str
@@ -34,7 +34,6 @@ import type { SessionRegistry } from '../sessions/registry.js';
34
34
  import type { ProviderInstance } from '../providers/provider-instance.js';
35
35
  import { LOG } from '../logging/logger.js';
36
36
  import { shouldRestoreHostedRuntime } from './hosted-runtime-restore.js';
37
- import { findProviderAutoApproveMode, resolveProviderAutoApproveMode } from '../providers/auto-approve-modes.js';
38
37
 
39
38
  // ─── external dependency interface ──────────────────────────
40
39
 
@@ -453,40 +452,6 @@ export function expandThinkingLaunchArgs(
453
452
  return template.map((part) => part.includes('{{level}}') ? part.replace('{{level}}', mapped) : part);
454
453
  }
455
454
 
456
- function matchesRemovedLaunchArg(arg: string, removeArg: string): boolean {
457
- return arg === removeArg || (removeArg.startsWith('--') && arg.startsWith(`${removeArg}=`));
458
- }
459
-
460
- /**
461
- * Apply a selected launch-args auto-approve mode without mutating provider metadata.
462
- * removeArgs only targets provider-owned base spawn.args; launchArgs are prepended to
463
- * per-launch args beside model/thinking args, making conflict removal order-independent.
464
- */
465
- export function applyAutoApproveModeLaunchArgs(
466
- provider: ProviderModule | undefined,
467
- cliArgs: string[] | undefined,
468
- settings: Record<string, unknown> | undefined,
469
- ): { provider: ProviderModule | undefined; cliArgs: string[] | undefined } {
470
- if (!provider) return { provider, cliArgs };
471
- const resolved = resolveProviderAutoApproveMode(provider, settings);
472
- if (!resolved.active || resolved.strategy !== 'launch-args') return { provider, cliArgs };
473
- const mode = findProviderAutoApproveMode(provider, resolved.modeId);
474
- if (!mode || !Array.isArray(mode.launchArgs) || mode.launchArgs.length === 0) return { provider, cliArgs };
475
-
476
- const removeArgs = Array.isArray(mode.removeArgs) ? mode.removeArgs : [];
477
- const baseArgs = provider.spawn?.args;
478
- const filteredBaseArgs = Array.isArray(baseArgs) && removeArgs.length > 0
479
- ? baseArgs.filter((arg) => !removeArgs.some((removeArg) => matchesRemovedLaunchArg(arg, removeArg)))
480
- : baseArgs;
481
- const launchProvider = filteredBaseArgs === baseArgs
482
- ? provider
483
- : { ...provider, spawn: { ...provider.spawn!, args: filteredBaseArgs } };
484
- return {
485
- provider: launchProvider,
486
- cliArgs: [...mode.launchArgs, ...(cliArgs || [])],
487
- };
488
- }
489
-
490
455
  function readSubcommandSessionId(args: string[], subcommands: string[]): string | undefined {
491
456
  const resumeIndex = args.findIndex((arg) => subcommands.includes(arg));
492
457
  if (resumeIndex < 0) return undefined;
@@ -742,24 +707,6 @@ export class DaemonCliManager {
742
707
  clearInterval(checkStopped);
743
708
  setTimeout(() => {
744
709
  if (this.adapters.has(key)) {
745
- // KIMI-MESH-COMPLETION-EMIT (axis 2): before removeInstance closes the
746
- // event-emit window, give a mesh DELEGATED worker one last chance to emit
747
- // its completion. A native-source worker (e.g. kimi) can have its PTY
748
- // killed by a false stall AFTER it finished the task (transcript written)
749
- // but BEFORE the FSM's idle→completed event fired — the instance is the
750
- // only thing that can emit that event, and it is about to be removed. The
751
- // instance-side method is a no-op for a non-mesh session or when the
752
- // turn's completion already fired (double-emit guard) or when there is no
753
- // transcript evidence of a finished turn. Best-effort — never blocks cleanup.
754
- try {
755
- const inst = instanceManager?.getInstance(key) as (ProviderInstance & { flushMeshCompletionBeforeCleanup?: () => boolean }) | undefined;
756
- if (typeof inst?.flushMeshCompletionBeforeCleanup === 'function') {
757
- const emitted = inst.flushMeshCompletionBeforeCleanup();
758
- if (emitted) LOG.info('CLI', `Emitted pre-cleanup mesh completion for ${cliType} session ${key} before auto-clean`);
759
- }
760
- } catch (e) {
761
- LOG.warn('CLI', `pre-cleanup mesh completion flush failed for ${key}: ${(e as Error)?.message || e}`);
762
- }
763
710
  this.adapters.delete(key);
764
711
  this.deps.removeAgentTracking(key);
765
712
  sessionRegistry?.unregisterByInstanceKey(key);
@@ -1083,17 +1030,6 @@ export class DaemonCliManager {
1083
1030
  console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
1084
1031
  }
1085
1032
 
1086
- const launchSettings = {
1087
- ...this.providerLoader.getSettings(normalizedType),
1088
- ...(options?.settingsOverride || {}),
1089
- };
1090
- const versionResolvedProvider = provider
1091
- ? (this.providerLoader.resolve(cliType, { version: cliInfo.version }) || provider)
1092
- : undefined;
1093
- const autoApproveLaunch = applyAutoApproveModeLaunchArgs(versionResolvedProvider, cliArgs, launchSettings);
1094
- const launchProvider = autoApproveLaunch.provider || provider;
1095
- const cliArgsWithAutoApprove = autoApproveLaunch.cliArgs;
1096
-
1097
1033
  // ─── Model axis (MAGI kind-panel): expand initialModel → launch args ───
1098
1034
  // For a plain CLI provider the model is selected at spawn time via the manifest's
1099
1035
  // modelLaunchArgs template ('{{model}}' → the requested model). ACP providers took
@@ -1101,10 +1037,10 @@ export class DaemonCliManager {
1101
1037
  // or no requested model, is a no-op — model selection is best-effort and must never
1102
1038
  // fail a launch. The model args are prepended so a caller's explicit cliArgs (e.g. a
1103
1039
  // resume flag) still win positionally where order matters.
1104
- const modelLaunchArgs = expandModelLaunchArgs(launchProvider?.modelLaunchArgs, initialModel);
1040
+ const modelLaunchArgs = expandModelLaunchArgs(provider?.modelLaunchArgs, initialModel);
1105
1041
  const cliArgsWithModel = modelLaunchArgs
1106
- ? [...modelLaunchArgs, ...(cliArgsWithAutoApprove || [])]
1107
- : cliArgsWithAutoApprove;
1042
+ ? [...modelLaunchArgs, ...(cliArgs || [])]
1043
+ : cliArgs;
1108
1044
  if (initialModel && !modelLaunchArgs) {
1109
1045
  LOG.warn('CLI', `[${normalizedType}] initialModel='${initialModel}' requested but provider declares no modelLaunchArgs template — launching without model selection.`);
1110
1046
  }
@@ -1115,7 +1051,7 @@ export class DaemonCliManager {
1115
1051
  // Best-effort; a provider with no template (or no requested level) is a no-op. ACP
1116
1052
  // providers route thinking through setConfigOption('thought_level') above.
1117
1053
  const initialThinkingLevel = options?.initialThinkingLevel;
1118
- const thinkingLaunchArgs = expandThinkingLaunchArgs(launchProvider?.thinkingLaunchArgs, initialThinkingLevel, launchProvider?.thinkingLevelMap);
1054
+ const thinkingLaunchArgs = expandThinkingLaunchArgs(provider?.thinkingLaunchArgs, initialThinkingLevel, provider?.thinkingLevelMap);
1119
1055
  const cliArgsWithBrain = thinkingLaunchArgs
1120
1056
  ? [...thinkingLaunchArgs, ...(cliArgsWithModel || [])]
1121
1057
  : cliArgsWithModel;
@@ -1124,13 +1060,13 @@ export class DaemonCliManager {
1124
1060
  }
1125
1061
 
1126
1062
  // ─── Resolve launch options → provider session binding ───
1127
- const sessionBinding = resolveCliSessionBinding(launchProvider, normalizedType, cliArgsWithBrain, options?.resumeSessionId);
1063
+ const sessionBinding = resolveCliSessionBinding(provider, normalizedType, cliArgsWithBrain, options?.resumeSessionId);
1128
1064
  const resolvedCliArgs = sessionBinding.cliArgs;
1129
1065
 
1130
1066
  // If InstanceManager exists, manage as CliProviderInstance unified
1131
1067
  const instanceManager = this.deps.getInstanceManager();
1132
- if (launchProvider && instanceManager) {
1133
- const resolvedProvider = launchProvider;
1068
+ if (provider && instanceManager) {
1069
+ const resolvedProvider = this.providerLoader.resolve(cliType, { version: cliInfo.version }) || provider;
1134
1070
  await this.registerCliInstance(
1135
1071
  key,
1136
1072
  normalizedType,
@@ -1138,7 +1074,7 @@ export class DaemonCliManager {
1138
1074
  resolvedDir,
1139
1075
  resolvedCliArgs,
1140
1076
  resolvedProvider,
1141
- launchSettings,
1077
+ { ...this.providerLoader.getSettings(normalizedType), ...(options?.settingsOverride || {}) },
1142
1078
  false,
1143
1079
  {
1144
1080
  providerSessionId: sessionBinding.providerSessionId,
@@ -115,12 +115,8 @@ export const meshCoordinatorLaunchHandlers: Record<string, HighFamilyHandler> =
115
115
  try {
116
116
  const { readOperatingNotes } = await import('../../mesh/mesh-ledger.js');
117
117
  // readOperatingNotes filters out tombstoned (forgotten) notes so a
118
- // retracted lesson never rides into the prompt. Newest last.
119
- // Phase 2 (d): the byte-budget/count cap now bounds the injected list
120
- // (selectOperatingNotesForPrompt), so read a larger candidate tail than
121
- // the old fixed 20 and let injection-side ranking + budget do the
122
- // bounding. Keep a sane store-read ceiling to avoid unbounded arrays.
123
- const noteEntries = readOperatingNotes(id, { tail: 100 });
118
+ // retracted lesson never rides into the prompt. Newest last; tail 20.
119
+ const noteEntries = readOperatingNotes(id, { tail: 20 });
124
120
  const notes = noteEntries
125
121
  .map((e) => {
126
122
  const p = (e.payload || {}) as Record<string, unknown>;
@@ -135,17 +131,6 @@ export const meshCoordinatorLaunchHandlers: Record<string, HighFamilyHandler> =
135
131
  category,
136
132
  createdAt: typeof p.createdAt === 'string' ? p.createdAt : e.timestamp,
137
133
  sourceCoordinator: typeof p.sourceCoordinator === 'string' ? p.sourceCoordinator : undefined,
138
- // Operating-notes lifecycle: thread pinned/expiresAt so the
139
- // injection-side selection can honor them. Legacy notes lack
140
- // these → pinned defaults false, expiry governed by category TTL.
141
- pinned: p.pinned === true,
142
- ...(typeof p.expiresAt === 'string' ? { expiresAt: p.expiresAt } : {}),
143
- // Phase 2 (b)/(c): thread the ledger id + supersedes/subjectKey
144
- // so version-supersede targeting and same-class folding work.
145
- // Legacy notes lack them → no supersede, fold by leading [tag].
146
- noteId: e.id,
147
- ...(typeof p.supersedes === 'string' ? { supersedes: p.supersedes } : {}),
148
- ...(typeof p.subjectKey === 'string' ? { subjectKey: p.subjectKey } : {}),
149
134
  };
150
135
  })
151
136
  .filter((n): n is NonNullable<typeof n> => n !== null);
@@ -93,21 +93,10 @@ export const meshEventsHandlers: Record<string, HighFamilyHandler> = {
93
93
  ? args.sessionId.trim()
94
94
  : '';
95
95
  if (!sessionId) return { success: false, error: 'targetSessionId required' };
96
- const rawResponse = args?.response ?? args;
96
+ const response = normalizeInteractivePromptResponse(args?.response ?? args);
97
97
  const instance = ctx.deps.instanceManager.getInstance(sessionId);
98
98
  if (!instance) return { success: false, error: `No running instance for session ${sessionId}` };
99
- // mesh_answer_question (mission f1d25e11) sends a coordinator-friendly answer array
100
- // that is resolved against the AUTHORITATIVE active prompt inside the instance
101
- // (resolveInteractivePromptResponse). Forward that shape RAW. The legacy strict
102
- // (questionId-keyed) form is still validated here so a malformed dashboard-local
103
- // answer is rejected before it reaches the instance.
104
- const isFriendlyArrayForm = rawResponse
105
- && typeof rawResponse === 'object'
106
- && Array.isArray((rawResponse as { answers?: unknown }).answers);
107
- const payload = isFriendlyArrayForm
108
- ? rawResponse
109
- : normalizeInteractivePromptResponse(rawResponse);
110
- ctx.deps.instanceManager.sendEvent(sessionId, 'interactive_prompt_response', payload);
99
+ ctx.deps.instanceManager.sendEvent(sessionId, 'interactive_prompt_response', response);
111
100
  return { success: true };
112
101
  },
113
102
  };
@@ -399,161 +399,6 @@ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
399
399
  }
400
400
  },
401
401
 
402
- // READ path for `.adhdev/mesh.json` — returns the currently-committed repo
403
- // config (parsed + normalized) for a workspace so a UI can render/edit the
404
- // existing declarative zones (notably `providerDefaults.autoApproveModes`)
405
- // WITHOUT re-deriving them from the machine-local scaffold. Never writes.
406
- // `config` is undefined when no repo file exists (sourceType 'unavailable') or
407
- // it is unparseable (sourceType 'invalid', with the parse error surfaced).
408
- read_mesh_json_config: async (_ctx: MedFamilyContext, args: any) => {
409
- const workspace = typeof args?.workspace === 'string' && args.workspace.trim() ? args.workspace.trim() : process.cwd();
410
- try {
411
- const { loadRepoMeshJsonConfig } = await import('../../config/mesh-json-config.js');
412
- const loaded = loadRepoMeshJsonConfig(workspace);
413
- return {
414
- success: true,
415
- workspace,
416
- sourceType: loaded.sourceType,
417
- source: loaded.source,
418
- ...(loaded.path ? { path: loaded.path } : {}),
419
- ...(loaded.error ? { error: loaded.error } : {}),
420
- config: loaded.config,
421
- // Convenience projection so the UI does not have to reach into config.
422
- providerDefaults: loaded.config?.providerDefaults,
423
- };
424
- } catch (e: any) {
425
- return { success: false, error: e.message };
426
- }
427
- },
428
-
429
- // Partial-edit WRITE path for `.adhdev/mesh.json` `providerDefaults` — a
430
- // READ-MODIFY-WRITE that preserves operator hand-edits. Unlike
431
- // write_mesh_json_config (which rebuilds the WHOLE file from the machine-local
432
- // scaffold and can silently drop hand-edited zones), this parses the existing
433
- // repo file, merges ONLY the providerDefaults.autoApproveModes zone, and
434
- // re-serializes — coordinator prompt, operating notes and limits authored in
435
- // the repo are carried through untouched. Defaults to dry-run.
436
- //
437
- // args: { workspace?, autoApproveModes: Record<providerType,modeId>, write?, merge? }
438
- // merge=true (default): per-provider merge into the existing map; a modeId of
439
- // '' | null removes that provider's entry. merge=false: REPLACE the whole
440
- // autoApproveModes map with the supplied one.
441
- set_mesh_provider_defaults: async (_ctx: MedFamilyContext, args: any) => {
442
- const workspace = typeof args?.workspace === 'string' && args.workspace.trim() ? args.workspace.trim() : process.cwd();
443
- const write = args?.write === true;
444
- const merge = args?.merge !== false; // default true
445
- const inputModes = args?.autoApproveModes;
446
- if (inputModes !== undefined && (typeof inputModes !== 'object' || inputModes === null || Array.isArray(inputModes))) {
447
- return { success: false, error: 'autoApproveModes must be an object (providerType → modeId) when provided' };
448
- }
449
- try {
450
- const {
451
- loadRepoMeshJsonConfig,
452
- normalizeRepoMeshDeclarativeConfig,
453
- MESH_JSON_CONFIG_LOCATIONS,
454
- } = await import('../../config/mesh-json-config.js');
455
- const { existsSync, readFileSync, mkdirSync, writeFileSync } = await import('fs');
456
- const { dirname, join } = await import('path');
457
- const yaml = await import('js-yaml');
458
-
459
- const relativePath = MESH_JSON_CONFIG_LOCATIONS[0];
460
-
461
- // Read-modify-write: parse the EXISTING on-disk document (preferring the
462
- // first existing json/yaml variant) so unrelated zones survive verbatim.
463
- let baseDoc: Record<string, any> = { version: 1 };
464
- let existingPath = join(workspace, relativePath);
465
- let existedAsYaml = false;
466
- for (const relative of MESH_JSON_CONFIG_LOCATIONS) {
467
- const candidate = join(workspace, relative);
468
- if (!existsSync(candidate)) continue;
469
- try {
470
- const text = readFileSync(candidate, 'utf-8');
471
- const parsed = /\.json$/i.test(candidate) ? JSON.parse(text) : yaml.load(text);
472
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
473
- baseDoc = parsed as Record<string, any>;
474
- existingPath = candidate;
475
- existedAsYaml = !/\.json$/i.test(candidate);
476
- }
477
- } catch (e: any) {
478
- return { success: false, error: `existing ${relative} is unparseable, refusing to overwrite: ${e?.message || e}` };
479
- }
480
- break;
481
- }
482
-
483
- // Merge ONLY the providerDefaults.autoApproveModes zone.
484
- const existingPd = baseDoc.providerDefaults && typeof baseDoc.providerDefaults === 'object' && !Array.isArray(baseDoc.providerDefaults)
485
- ? baseDoc.providerDefaults as Record<string, any>
486
- : {};
487
- const existingModes = existingPd.autoApproveModes && typeof existingPd.autoApproveModes === 'object' && !Array.isArray(existingPd.autoApproveModes)
488
- ? { ...existingPd.autoApproveModes as Record<string, string> }
489
- : {};
490
-
491
- const nextModes: Record<string, string> = merge ? existingModes : {};
492
- if (inputModes) {
493
- for (const [providerType, modeId] of Object.entries(inputModes as Record<string, unknown>)) {
494
- const type = typeof providerType === 'string' ? providerType.trim() : '';
495
- if (!type) continue;
496
- const id = typeof modeId === 'string' ? modeId.trim() : '';
497
- if (id) nextModes[type] = id;
498
- else delete nextModes[type]; // '' / null → remove this provider's entry
499
- }
500
- }
501
-
502
- const nextDoc: Record<string, any> = { ...baseDoc, version: 1 };
503
- if (Object.keys(nextModes).length) {
504
- nextDoc.providerDefaults = { ...existingPd, autoApproveModes: nextModes };
505
- } else {
506
- // No entries left → drop the zone entirely so we don't leave an empty stub.
507
- if (nextDoc.providerDefaults) {
508
- const { autoApproveModes, ...restPd } = nextDoc.providerDefaults;
509
- if (Object.keys(restPd).length) nextDoc.providerDefaults = restPd;
510
- else delete nextDoc.providerDefaults;
511
- }
512
- }
513
-
514
- // Validate the merged document before it ever touches disk.
515
- const validation = normalizeRepoMeshDeclarativeConfig(nextDoc);
516
- if (!validation.valid) {
517
- return { success: false, error: `merged mesh.json is invalid: ${validation.errors.join('; ')}` };
518
- }
519
-
520
- // Serialize in the on-disk format (JSON unless the existing file was YAML).
521
- const absolutePath = existingPath;
522
- const serialized = existedAsYaml
523
- ? yaml.dump(nextDoc, { indent: 2 })
524
- : `${JSON.stringify(nextDoc, null, 2)}\n`;
525
-
526
- if (!write) {
527
- return {
528
- success: true,
529
- written: false,
530
- dryRun: true,
531
- path: absolutePath,
532
- relativePath,
533
- merge,
534
- providerDefaults: nextDoc.providerDefaults,
535
- preview: serialized,
536
- note: 'Dry-run: nothing written. Re-run with write=true to persist. Only the providerDefaults zone is merged; other repo zones are preserved.',
537
- };
538
- }
539
-
540
- mkdirSync(dirname(absolutePath), { recursive: true });
541
- writeFileSync(absolutePath, serialized, 'utf-8');
542
- return {
543
- success: true,
544
- written: true,
545
- dryRun: false,
546
- path: absolutePath,
547
- relativePath,
548
- merge,
549
- providerDefaults: nextDoc.providerDefaults,
550
- note: 'Wrote providerDefaults into .adhdev/mesh.json (read-modify-write; other zones preserved). Commit it to the repo.',
551
- };
552
- } catch (e: any) {
553
- return { success: false, error: e.message };
554
- }
555
- },
556
-
557
402
  delete_mesh: async (_ctx: MedFamilyContext, args: any) => {
558
403
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
559
404
  if (!meshId) return { success: false, error: 'meshId required' };