@adhdev/daemon-core 1.0.18-rc.20 → 1.0.18-rc.3
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.
- package/dist/cli-adapters/provider-cli-shared.d.ts +0 -33
- package/dist/commands/cli-manager.d.ts +0 -9
- package/dist/commands/upgrade-helper.d.ts +0 -1
- package/dist/commands/windows-atomic-upgrade.d.ts +0 -5
- package/dist/config/mesh-json-config.d.ts +0 -62
- package/dist/index.d.ts +2 -4
- package/dist/index.js +3998 -6112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3855 -5966
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +0 -76
- package/dist/mesh/mesh-active-work.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -28
- package/dist/mesh/mesh-node-identity.d.ts +0 -23
- package/dist/mesh/mesh-refine-gates.d.ts +0 -89
- package/dist/mesh/mesh-remote-event-pull.d.ts +0 -3
- package/dist/mesh/mesh-runtime-store.d.ts +0 -11
- package/dist/mesh/mesh-work-queue.d.ts +1 -24
- package/dist/providers/chat-message-normalization.d.ts +0 -22
- package/dist/providers/cli-provider-instance-types.d.ts +0 -4
- package/dist/providers/cli-provider-instance.d.ts +0 -78
- package/dist/providers/contracts.d.ts +0 -17
- package/dist/providers/provider-schema.d.ts +0 -1
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +0 -14
- package/dist/providers/types/interactive-prompt.d.ts +0 -18
- package/dist/repo-mesh-types.d.ts +6 -38
- package/dist/shared-types.d.ts +2 -4
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +0 -10
- package/src/cli-adapters/cli-state-engine.ts +1 -17
- package/src/cli-adapters/provider-cli-adapter.ts +6 -2
- package/src/cli-adapters/provider-cli-shared.ts +0 -48
- package/src/commands/cli-manager.ts +8 -72
- package/src/commands/high-family/mesh-coordinator-launch.ts +2 -17
- package/src/commands/high-family/mesh-events.ts +2 -13
- package/src/commands/med-family/mesh-crud.ts +0 -155
- package/src/commands/med-family/mesh-queue.ts +1 -74
- package/src/commands/router-refine.ts +4 -71
- package/src/commands/router.ts +0 -8
- package/src/commands/upgrade-helper.ts +15 -78
- package/src/commands/windows-atomic-upgrade.ts +40 -194
- package/src/config/mesh-json-config.ts +0 -111
- package/src/index.ts +1 -21
- package/src/mesh/coordinator-prompt.ts +11 -258
- package/src/mesh/mesh-active-work.ts +1 -11
- package/src/mesh/mesh-completion-synthesis.ts +1 -12
- package/src/mesh/mesh-event-classify.ts +0 -19
- package/src/mesh/mesh-event-forwarding.ts +6 -64
- package/src/mesh/mesh-events-utils.ts +0 -42
- package/src/mesh/mesh-ledger.ts +0 -77
- package/src/mesh/mesh-node-identity.ts +0 -49
- package/src/mesh/mesh-queue-assignment.ts +11 -210
- package/src/mesh/mesh-reconcile-loop.ts +3 -306
- package/src/mesh/mesh-refine-gates.ts +0 -300
- package/src/mesh/mesh-remote-event-pull.ts +47 -68
- package/src/mesh/mesh-runtime-store.ts +0 -21
- package/src/mesh/mesh-work-queue.ts +2 -85
- package/src/providers/chat-message-normalization.ts +0 -53
- package/src/providers/cli-provider-instance-types.ts +0 -53
- package/src/providers/cli-provider-instance.ts +15 -497
- package/src/providers/contracts.ts +1 -25
- package/src/providers/provider-schema.ts +0 -83
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +0 -23
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +0 -20
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +0 -44
- package/src/providers/types/interactive-prompt.ts +0 -77
- package/src/repo-mesh-types.ts +12 -112
- package/src/shared-types.ts +1 -9
- package/src/status/reporter.ts +0 -1
- package/src/status/snapshot.ts +0 -2
- package/dist/mesh/mesh-disk-retention.d.ts +0 -105
- package/dist/providers/auto-approve-modes.d.ts +0 -14
- package/src/mesh/mesh-disk-retention.ts +0 -370
- 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
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
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'
|
|
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
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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.
|
|
3
|
+
"version": "1.0.18-rc.3",
|
|
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.
|
|
51
|
-
"@adhdev/session-host-core": "1.0.18-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "1.0.18-rc.3",
|
|
51
|
+
"@adhdev/session-host-core": "1.0.18-rc.3",
|
|
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
|
-
|
|
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(
|
|
1040
|
+
const modelLaunchArgs = expandModelLaunchArgs(provider?.modelLaunchArgs, initialModel);
|
|
1105
1041
|
const cliArgsWithModel = modelLaunchArgs
|
|
1106
|
-
? [...modelLaunchArgs, ...(
|
|
1107
|
-
:
|
|
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(
|
|
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(
|
|
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 (
|
|
1133
|
-
const resolvedProvider =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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' };
|