@adhdev/daemon-core 0.9.82-rc.15 → 0.9.82-rc.152
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/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
- package/dist/commands/handler.d.ts +66 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13153 -3031
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13099 -3023
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +50 -5
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/index.d.ts +11 -0
- package/dist/providers/provider-loader.d.ts +19 -1
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +64 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +6 -2
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +57 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1451 -52
- package/src/commands/cli-manager.ts +184 -3
- package/src/commands/handler.ts +547 -2
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3273 -428
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +72 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +820 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +380 -44
- package/src/providers/contracts.ts +52 -5
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/index.ts +23 -0
- package/src/providers/provider-loader.ts +294 -28
- package/src/providers/provider-schema.ts +31 -13
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +245 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +444 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +119 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +210 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import type { PendingMeshCoordinatorEvent } from './mesh-events.js';
|
|
3
|
+
import type { MeshAsyncJobLifecycle } from '../repo-mesh-types.js';
|
|
4
|
+
|
|
5
|
+
export type MeshAsyncRefineJobStatus = 'accepted' | 'running' | 'completed' | 'failed';
|
|
6
|
+
|
|
7
|
+
export interface MeshAsyncRefineJobSummary extends MeshAsyncJobLifecycle {
|
|
8
|
+
jobId: string;
|
|
9
|
+
interactionId?: string;
|
|
10
|
+
status: MeshAsyncRefineJobStatus;
|
|
11
|
+
meshId?: string;
|
|
12
|
+
nodeId?: string;
|
|
13
|
+
targetNodeId?: string;
|
|
14
|
+
targetDaemonId?: string;
|
|
15
|
+
workspace?: string;
|
|
16
|
+
branch?: string;
|
|
17
|
+
into?: string;
|
|
18
|
+
retryOfJobId?: string;
|
|
19
|
+
lastEvent?: string;
|
|
20
|
+
lastLedgerKind?: string;
|
|
21
|
+
lastUpdatedAt?: string;
|
|
22
|
+
instruction: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readString(value: unknown): string | undefined {
|
|
26
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
30
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
31
|
+
? value as Record<string, unknown>
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function eventStatus(event: string | undefined, fallback?: string): MeshAsyncRefineJobStatus | undefined {
|
|
36
|
+
if (event === 'refine:accepted') return 'accepted';
|
|
37
|
+
if (event === 'refine:completed') return 'completed';
|
|
38
|
+
if (event === 'refine:failed') return 'failed';
|
|
39
|
+
if (fallback === 'completed' || fallback === 'failed' || fallback === 'accepted') return fallback;
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ledgerStatus(kind: string, fallback?: string): MeshAsyncRefineJobStatus {
|
|
44
|
+
if (kind === 'task_completed') return 'completed';
|
|
45
|
+
if (kind === 'task_failed') return 'failed';
|
|
46
|
+
if (fallback === 'accepted') return 'accepted';
|
|
47
|
+
return 'running';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function instructionForStatus(status: MeshAsyncRefineJobStatus): string {
|
|
51
|
+
if (status === 'accepted') return 'Refine job is accepted; wait for asyncRefineJobs or pendingCoordinatorEvents to report running/completed/failed.';
|
|
52
|
+
if (status === 'running') return 'Refine job is running; do not poll the ledger repeatedly. Watch asyncRefineJobs or pendingCoordinatorEvents for the terminal result.';
|
|
53
|
+
if (status === 'completed') return 'Refine job completed; inspect branch convergence and cleanup evidence before reporting final merge state.';
|
|
54
|
+
return 'Refine job failed; inspect result/finalBranchConvergenceState in mesh_task_history, fix the blocker, then rerun mesh_refine_node when ready.';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mergeJob(
|
|
58
|
+
jobs: Map<string, MeshAsyncRefineJobSummary>,
|
|
59
|
+
patch: Partial<MeshAsyncRefineJobSummary> & { jobId?: string },
|
|
60
|
+
): void {
|
|
61
|
+
const jobId = readString(patch.jobId);
|
|
62
|
+
if (!jobId) return;
|
|
63
|
+
const previous = jobs.get(jobId);
|
|
64
|
+
const status = patch.status || previous?.status || 'running';
|
|
65
|
+
const definedPatch = Object.fromEntries(
|
|
66
|
+
Object.entries(patch).filter(([, value]) => value !== undefined),
|
|
67
|
+
) as Partial<MeshAsyncRefineJobSummary>;
|
|
68
|
+
jobs.set(jobId, {
|
|
69
|
+
...previous,
|
|
70
|
+
...definedPatch,
|
|
71
|
+
jobId,
|
|
72
|
+
status,
|
|
73
|
+
instruction: instructionForStatus(status),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildMeshAsyncRefineJobs(args: {
|
|
78
|
+
meshId?: string;
|
|
79
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
80
|
+
pendingEvents?: PendingMeshCoordinatorEvent[];
|
|
81
|
+
}): MeshAsyncRefineJobSummary[] {
|
|
82
|
+
const jobs = new Map<string, MeshAsyncRefineJobSummary>();
|
|
83
|
+
|
|
84
|
+
for (const entry of args.ledgerEntries || []) {
|
|
85
|
+
const payload = readRecord(entry.payload);
|
|
86
|
+
if (payload?.source !== 'refine_mesh_node_async_job') continue;
|
|
87
|
+
const refineJob = readRecord(payload.refineJob);
|
|
88
|
+
const result = readRecord(payload.result);
|
|
89
|
+
const finalState = readRecord(payload.finalBranchConvergenceState) || readRecord(result?.finalBranchConvergenceState);
|
|
90
|
+
const jobId = readString(refineJob?.jobId);
|
|
91
|
+
if (!jobId) continue;
|
|
92
|
+
const status = ledgerStatus(entry.kind, readString(refineJob?.status));
|
|
93
|
+
mergeJob(jobs, {
|
|
94
|
+
jobId,
|
|
95
|
+
interactionId: readString(refineJob?.interactionId),
|
|
96
|
+
status,
|
|
97
|
+
meshId: readString(refineJob?.meshId) || args.meshId,
|
|
98
|
+
nodeId: readString(refineJob?.nodeId) || entry.nodeId,
|
|
99
|
+
targetNodeId: readString(refineJob?.nodeId) || entry.nodeId,
|
|
100
|
+
targetDaemonId: readString(refineJob?.targetDaemonId),
|
|
101
|
+
workspace: readString(refineJob?.workspace),
|
|
102
|
+
branch: readString(result?.branch) || readString(finalState?.branch),
|
|
103
|
+
into: readString(result?.into) || readString(finalState?.baseBranch),
|
|
104
|
+
startedAt: readString(refineJob?.startedAt),
|
|
105
|
+
completedAt: readString(refineJob?.completedAt),
|
|
106
|
+
retryOfJobId: readString(refineJob?.retryOfJobId) || readString(payload.retryOfJobId),
|
|
107
|
+
lastLedgerKind: entry.kind,
|
|
108
|
+
lastUpdatedAt: entry.timestamp,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const event of args.pendingEvents || []) {
|
|
113
|
+
const metadata = readRecord(event.metadataEvent);
|
|
114
|
+
if (metadata?.source !== 'refine_mesh_node_async_job') continue;
|
|
115
|
+
const result = readRecord(metadata.result);
|
|
116
|
+
const finalState = readRecord(result?.finalBranchConvergenceState);
|
|
117
|
+
const jobId = readString(metadata.jobId);
|
|
118
|
+
if (!jobId) continue;
|
|
119
|
+
const status = eventStatus(event.event, readString(metadata.status));
|
|
120
|
+
mergeJob(jobs, {
|
|
121
|
+
jobId,
|
|
122
|
+
interactionId: readString(metadata.interactionId),
|
|
123
|
+
...(status ? { status } : {}),
|
|
124
|
+
meshId: readString(metadata.meshId) || event.meshId || args.meshId,
|
|
125
|
+
nodeId: readString(metadata.nodeId) || event.nodeId,
|
|
126
|
+
targetNodeId: readString(metadata.nodeId) || event.nodeId,
|
|
127
|
+
targetDaemonId: readString(metadata.targetDaemonId),
|
|
128
|
+
workspace: readString(metadata.workspace) || event.workspace,
|
|
129
|
+
branch: readString(result?.branch) || readString(finalState?.branch),
|
|
130
|
+
into: readString(result?.into) || readString(finalState?.baseBranch),
|
|
131
|
+
startedAt: readString(metadata.startedAt),
|
|
132
|
+
completedAt: readString(metadata.completedAt),
|
|
133
|
+
retryOfJobId: readString(metadata.retryOfJobId),
|
|
134
|
+
lastEvent: event.event,
|
|
135
|
+
lastUpdatedAt: new Date(event.queuedAt).toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Array.from(jobs.values()).sort((a, b) => {
|
|
140
|
+
const aTime = new Date(a.lastUpdatedAt || a.startedAt || '').getTime();
|
|
141
|
+
const bTime = new Date(b.lastUpdatedAt || b.startedAt || '').getTime();
|
|
142
|
+
return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -1,20 +1,70 @@
|
|
|
1
|
-
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
1
|
import { randomUUID } from 'crypto';
|
|
4
|
-
import {
|
|
2
|
+
import { requireMeshHostQueueOwner } from './mesh-host-ownership.js';
|
|
3
|
+
import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
|
|
4
|
+
import { BeadsDB } from './beads-db.js';
|
|
5
5
|
|
|
6
6
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
7
7
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
8
8
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
9
|
+
export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
|
|
9
10
|
|
|
10
11
|
export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
|
|
11
12
|
export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
|
|
13
|
+
export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
|
|
14
|
+
|
|
15
|
+
export interface MeshTaskModeValidationResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
taskMode?: MeshTaskMode;
|
|
18
|
+
violations: string[];
|
|
19
|
+
allowedOperations?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
|
|
23
|
+
{ label: 'source_edit', pattern: /\b(edit|modify|patch|apply\s+patch|write\s+(?:to\s+)?(?:file|source)|overwrite|delete\s+file|remove\s+file|create\s+file|touch\s+file)\b/i },
|
|
24
|
+
{ label: 'git_mutation', pattern: /\b(?:git\s+(?:add|commit|push|reset|rebase|clean|checkout|switch|merge|tag|restore|rm|mv|stash|worktree\s+(?:add|remove|move))|push\b)/i },
|
|
25
|
+
{ label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
|
|
26
|
+
{ label: 'deploy_or_version_bump', pattern: /\b(deploy|wrangler\s+deploy|version[-\s]?bump|npm\s+version|release|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i },
|
|
27
|
+
{ label: 'destructive_shell', pattern: /\b(rm\s+-rf|mv\s+\S+\s+\S+|truncate\s|tee\s+\S+|sed\s+-i|shred\b)\b/i },
|
|
28
|
+
{ label: 'package_install', pattern: /\b(npm\s+(?:install|i|add|link|uninstall|remove)|yarn\s+(?:add|remove|link)|pnpm\s+(?:add|remove|link)|pip\s+install|brew\s+install|apt\s+install|cargo\s+install)\b/i },
|
|
29
|
+
{ label: 'container_mutation', pattern: /\b(docker\s+(?:build|run|exec|push|tag|rmi|rm|create|start|stop|kill)|kubectl\s+(?:apply|delete|patch|replace|create|scale))\b/i },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
|
|
33
|
+
if (typeof value !== 'string') return undefined;
|
|
34
|
+
const normalized = value.trim() as MeshTaskMode;
|
|
35
|
+
return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
|
|
39
|
+
const taskMode = normalizeMeshTaskMode(mode);
|
|
40
|
+
if (!taskMode) {
|
|
41
|
+
return { valid: true, violations: [] };
|
|
42
|
+
}
|
|
43
|
+
if (taskMode !== 'live_debug_readonly') {
|
|
44
|
+
return { valid: true, taskMode, violations: [] };
|
|
45
|
+
}
|
|
46
|
+
const violations = LIVE_DEBUG_READONLY_FORBIDDEN
|
|
47
|
+
.filter(rule => rule.pattern.test(message || ''))
|
|
48
|
+
.map(rule => rule.label);
|
|
49
|
+
return {
|
|
50
|
+
valid: violations.length === 0,
|
|
51
|
+
taskMode,
|
|
52
|
+
violations,
|
|
53
|
+
allowedOperations: [
|
|
54
|
+
'process/log/window/port/session inspection',
|
|
55
|
+
'read-only filesystem listing/reading',
|
|
56
|
+
'status probes and keep-running handle reporting',
|
|
57
|
+
'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
12
61
|
|
|
13
62
|
export interface MeshWorkQueueEntry {
|
|
14
63
|
id: string;
|
|
15
64
|
meshId: string;
|
|
16
65
|
message: string;
|
|
17
66
|
status: MeshTaskStatus;
|
|
67
|
+
taskMode?: MeshTaskMode;
|
|
18
68
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
19
69
|
targetNodeId?: string;
|
|
20
70
|
/** If specified, only this runtime session can claim the task */
|
|
@@ -45,25 +95,20 @@ export interface MeshWorkQueueEntry {
|
|
|
45
95
|
updatedAt: string;
|
|
46
96
|
}
|
|
47
97
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
export interface MeshQueueMutationOptions {
|
|
99
|
+
ownerRole?: RepoMeshDaemonRole;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function withQueueLock<T>(_meshId: string, fn: () => T): T {
|
|
103
|
+
return BeadsDB.getInstance().transaction(fn);
|
|
51
104
|
}
|
|
52
105
|
|
|
53
106
|
function readQueue(meshId: string): MeshWorkQueueEntry[] {
|
|
54
|
-
|
|
55
|
-
if (!existsSync(path)) return [];
|
|
56
|
-
try {
|
|
57
|
-
const content = readFileSync(path, 'utf-8');
|
|
58
|
-
return JSON.parse(content) as MeshWorkQueueEntry[];
|
|
59
|
-
} catch {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
107
|
+
return BeadsDB.getInstance().getQueueEntries(meshId);
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
65
|
-
|
|
66
|
-
writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
|
|
111
|
+
BeadsDB.getInstance().replaceQueue(meshId, queue);
|
|
67
112
|
}
|
|
68
113
|
|
|
69
114
|
/**
|
|
@@ -72,21 +117,25 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
|
72
117
|
export function enqueueTask(
|
|
73
118
|
meshId: string,
|
|
74
119
|
message: string,
|
|
75
|
-
opts?: { targetNodeId?: string; targetSessionId?: string }
|
|
120
|
+
opts?: { targetNodeId?: string; targetSessionId?: string; taskMode?: MeshTaskMode | string } & MeshQueueMutationOptions,
|
|
76
121
|
): MeshWorkQueueEntry {
|
|
77
|
-
|
|
122
|
+
requireMeshHostQueueOwner(opts);
|
|
123
|
+
const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
|
|
124
|
+
if (!modeValidation.valid) {
|
|
125
|
+
throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
|
|
126
|
+
}
|
|
78
127
|
const entry: MeshWorkQueueEntry = {
|
|
79
128
|
id: randomUUID(),
|
|
80
129
|
meshId,
|
|
81
130
|
message,
|
|
82
131
|
status: 'pending',
|
|
132
|
+
taskMode: modeValidation.taskMode,
|
|
83
133
|
targetNodeId: opts?.targetNodeId,
|
|
84
134
|
targetSessionId: opts?.targetSessionId,
|
|
85
135
|
createdAt: new Date().toISOString(),
|
|
86
136
|
updatedAt: new Date().toISOString(),
|
|
87
137
|
};
|
|
88
|
-
|
|
89
|
-
writeQueue(meshId, queue);
|
|
138
|
+
BeadsDB.getInstance().insertQueueEntry(entry);
|
|
90
139
|
return entry;
|
|
91
140
|
}
|
|
92
141
|
|
|
@@ -94,51 +143,18 @@ export function enqueueTask(
|
|
|
94
143
|
* Get all tasks in the queue, optionally filtered by status.
|
|
95
144
|
*/
|
|
96
145
|
export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }): MeshWorkQueueEntry[] {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return queue;
|
|
146
|
+
return BeadsDB.getInstance().getQueueEntries(meshId, opts?.status?.length ? opts.status : undefined);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getMeshQueueRevision(meshId: string): string {
|
|
150
|
+
return BeadsDB.getInstance().getQueueRevision(meshId);
|
|
103
151
|
}
|
|
104
152
|
|
|
105
153
|
/**
|
|
106
154
|
* Find the next pending task that this node is allowed to claim, and mark it as assigned.
|
|
107
155
|
*/
|
|
108
156
|
export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// A worker must finish or fail its current queued assignment before it can
|
|
112
|
-
// claim another one. maxParallelTasks limits total mesh concurrency; it is
|
|
113
|
-
// not permission for one node/session to accumulate multiple assigned items.
|
|
114
|
-
const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
|
|
115
|
-
q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
|
|
116
|
-
));
|
|
117
|
-
if (hasActiveAssignment) return null;
|
|
118
|
-
|
|
119
|
-
// Find highest priority task:
|
|
120
|
-
// 1. Pending tasks explicitly targeted at this runtime session
|
|
121
|
-
// 2. Pending tasks explicitly targeted at this node (but not another session)
|
|
122
|
-
// 3. Pending tasks with no target node/session
|
|
123
|
-
let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetSessionId === sessionId);
|
|
124
|
-
if (targetIdx === -1) {
|
|
125
|
-
targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId && !q.targetSessionId);
|
|
126
|
-
}
|
|
127
|
-
if (targetIdx === -1) {
|
|
128
|
-
targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId && !q.targetSessionId);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (targetIdx === -1) return null;
|
|
132
|
-
|
|
133
|
-
const entry = queue[targetIdx];
|
|
134
|
-
entry.status = 'assigned';
|
|
135
|
-
entry.assignedNodeId = nodeId;
|
|
136
|
-
entry.assignedSessionId = sessionId;
|
|
137
|
-
entry.dispatchTimestamp = new Date().toISOString();
|
|
138
|
-
entry.updatedAt = new Date().toISOString();
|
|
139
|
-
|
|
140
|
-
writeQueue(meshId, queue);
|
|
141
|
-
return entry;
|
|
157
|
+
return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
|
|
142
158
|
}
|
|
143
159
|
|
|
144
160
|
/**
|
|
@@ -149,15 +165,16 @@ export function updateTaskStatus(
|
|
|
149
165
|
meshId: string,
|
|
150
166
|
taskId: string,
|
|
151
167
|
status: MeshTaskStatus,
|
|
168
|
+
opts?: MeshQueueMutationOptions,
|
|
152
169
|
): MeshWorkQueueEntry | null {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
requireMeshHostQueueOwner(opts);
|
|
171
|
+
return withQueueLock(meshId, () => {
|
|
172
|
+
const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
|
|
173
|
+
if (!entry) return null;
|
|
174
|
+
entry.status = status;
|
|
175
|
+
BeadsDB.getInstance().updateQueueEntry(entry);
|
|
176
|
+
return entry;
|
|
177
|
+
});
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
export function recordTaskAutoLaunch(
|
|
@@ -165,17 +182,14 @@ export function recordTaskAutoLaunch(
|
|
|
165
182
|
taskId: string,
|
|
166
183
|
autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>,
|
|
167
184
|
): MeshWorkQueueEntry | null {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
};
|
|
176
|
-
queue[idx].updatedAt = now;
|
|
177
|
-
writeQueue(meshId, queue);
|
|
178
|
-
return queue[idx];
|
|
185
|
+
return withQueueLock(meshId, () => {
|
|
186
|
+
const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
|
|
187
|
+
if (!entry) return null;
|
|
188
|
+
const now = new Date().toISOString();
|
|
189
|
+
entry.autoLaunch = { ...autoLaunch, updatedAt: now };
|
|
190
|
+
BeadsDB.getInstance().updateQueueEntry(entry);
|
|
191
|
+
return entry;
|
|
192
|
+
});
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
/**
|
|
@@ -184,19 +198,19 @@ export function recordTaskAutoLaunch(
|
|
|
184
198
|
export function cancelTask(
|
|
185
199
|
meshId: string,
|
|
186
200
|
taskId: string,
|
|
187
|
-
opts?: { reason?: string },
|
|
201
|
+
opts?: { reason?: string } & MeshQueueMutationOptions,
|
|
188
202
|
): MeshWorkQueueEntry | null {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
requireMeshHostQueueOwner(opts);
|
|
204
|
+
return withQueueLock(meshId, () => {
|
|
205
|
+
const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
|
|
206
|
+
if (!entry) return null;
|
|
207
|
+
const now = new Date().toISOString();
|
|
208
|
+
entry.status = 'cancelled';
|
|
209
|
+
entry.cancelledAt = now;
|
|
210
|
+
if (opts?.reason) entry.cancelReason = opts.reason;
|
|
211
|
+
BeadsDB.getInstance().updateQueueEntry(entry);
|
|
212
|
+
return entry;
|
|
213
|
+
});
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
/**
|
|
@@ -212,29 +226,27 @@ export function requeueTask(
|
|
|
212
226
|
targetSessionId?: string;
|
|
213
227
|
clearTargetNode?: boolean;
|
|
214
228
|
clearTargetSession?: boolean;
|
|
215
|
-
},
|
|
229
|
+
} & MeshQueueMutationOptions,
|
|
216
230
|
): MeshWorkQueueEntry | null {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
writeQueue(meshId, queue);
|
|
237
|
-
return entry;
|
|
231
|
+
requireMeshHostQueueOwner(opts);
|
|
232
|
+
return withQueueLock(meshId, () => {
|
|
233
|
+
const entry = BeadsDB.getInstance().findQueueEntryById(meshId, taskId);
|
|
234
|
+
if (!entry) return null;
|
|
235
|
+
entry.status = 'pending';
|
|
236
|
+
delete entry.assignedNodeId;
|
|
237
|
+
delete entry.assignedSessionId;
|
|
238
|
+
delete entry.cancelledAt;
|
|
239
|
+
delete entry.cancelReason;
|
|
240
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
241
|
+
if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
|
|
242
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
243
|
+
if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
|
|
244
|
+
entry.requeuedAt = new Date().toISOString();
|
|
245
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
246
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
247
|
+
BeadsDB.getInstance().updateQueueEntry(entry);
|
|
248
|
+
return entry;
|
|
249
|
+
});
|
|
238
250
|
}
|
|
239
251
|
|
|
240
252
|
/**
|
|
@@ -244,29 +256,16 @@ export function updateSessionTaskStatus(
|
|
|
244
256
|
meshId: string,
|
|
245
257
|
sessionId: string,
|
|
246
258
|
status: MeshTaskStatus,
|
|
259
|
+
opts?: { occurredAt?: string },
|
|
247
260
|
): MeshWorkQueueEntry | null {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (queue[i].assignedSessionId === sessionId && queue[i].status === 'assigned') {
|
|
257
|
-
const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
|
|
258
|
-
if (time > bestTime) {
|
|
259
|
-
bestTime = time;
|
|
260
|
-
bestIdx = i;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (bestIdx === -1) return null;
|
|
265
|
-
|
|
266
|
-
queue[bestIdx].status = status;
|
|
267
|
-
queue[bestIdx].updatedAt = new Date().toISOString();
|
|
268
|
-
writeQueue(meshId, queue);
|
|
269
|
-
return queue[bestIdx];
|
|
261
|
+
return withQueueLock(meshId, () => {
|
|
262
|
+
const occurredAtIso = opts?.occurredAt ? new Date(opts.occurredAt).toISOString() : undefined;
|
|
263
|
+
const entry = BeadsDB.getInstance().findAssignedBySession(meshId, sessionId, occurredAtIso);
|
|
264
|
+
if (!entry) return null;
|
|
265
|
+
entry.status = status;
|
|
266
|
+
BeadsDB.getInstance().updateQueueEntry(entry);
|
|
267
|
+
return entry;
|
|
268
|
+
});
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
export interface MeshWorkQueueStats {
|
|
@@ -294,14 +293,16 @@ export interface MeshWorkQueueStats {
|
|
|
294
293
|
* Return aggregate queue statistics for the given mesh.
|
|
295
294
|
*/
|
|
296
295
|
export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
const
|
|
296
|
+
const rows = BeadsDB.getInstance().getQueueStatsByStatus(meshId);
|
|
297
|
+
const counts: Record<string, number> = {};
|
|
298
|
+
for (const r of rows) counts[r.status] = r.count;
|
|
299
|
+
const pending = counts['pending'] ?? 0;
|
|
300
|
+
const assigned = counts['assigned'] ?? 0;
|
|
301
|
+
const completed = counts['completed'] ?? 0;
|
|
302
|
+
const failed = counts['failed'] ?? 0;
|
|
303
|
+
const cancelled = counts['cancelled'] ?? 0;
|
|
303
304
|
return {
|
|
304
|
-
total:
|
|
305
|
+
total: pending + assigned + completed + failed + cancelled,
|
|
305
306
|
active: pending + assigned,
|
|
306
307
|
historical: completed + failed + cancelled,
|
|
307
308
|
pending,
|
|
@@ -309,22 +310,79 @@ export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
|
309
310
|
completed,
|
|
310
311
|
failed,
|
|
311
312
|
cancelled,
|
|
312
|
-
activeCounts: {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
},
|
|
316
|
-
historicalCounts: {
|
|
317
|
-
completed,
|
|
318
|
-
failed,
|
|
319
|
-
cancelled,
|
|
320
|
-
},
|
|
321
|
-
activeAssignments: queue
|
|
322
|
-
.filter(q => q.status === 'assigned')
|
|
323
|
-
.map(q => ({
|
|
324
|
-
id: q.id,
|
|
325
|
-
nodeId: q.assignedNodeId,
|
|
326
|
-
sessionId: q.assignedSessionId,
|
|
327
|
-
message: q.message,
|
|
328
|
-
})),
|
|
313
|
+
activeCounts: { pending, assigned },
|
|
314
|
+
historicalCounts: { completed, failed, cancelled },
|
|
315
|
+
activeAssignments: BeadsDB.getInstance().getActiveAssignmentDetails(meshId),
|
|
329
316
|
};
|
|
330
317
|
}
|
|
318
|
+
|
|
319
|
+
export function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
320
|
+
BeadsDB.getInstance().transaction(() => {
|
|
321
|
+
BeadsDB.getInstance().replaceQueue(meshId, queue);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function __clearMeshQueueForTests(meshId: string): void {
|
|
326
|
+
BeadsDB.getInstance().deleteQueue(meshId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function __resetBeadsDBForTests(): void {
|
|
330
|
+
BeadsDB.resetForTests();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Direct Dispatch Tracking ─────────────────────────────────────────────────
|
|
334
|
+
// Persists direct (non-queue) task dispatches so buildMeshActiveWork can read
|
|
335
|
+
// active work from BeadsDB instead of scanning ledger JSONL entries.
|
|
336
|
+
|
|
337
|
+
export type DirectDispatchRecord = ReturnType<BeadsDB['getActiveDirectDispatches']>[number];
|
|
338
|
+
|
|
339
|
+
export function insertDirectDispatch(
|
|
340
|
+
meshId: string,
|
|
341
|
+
data: {
|
|
342
|
+
taskId: string;
|
|
343
|
+
nodeId?: string;
|
|
344
|
+
sessionId?: string;
|
|
345
|
+
providerType?: string;
|
|
346
|
+
message: string;
|
|
347
|
+
taskMode?: string;
|
|
348
|
+
via: string;
|
|
349
|
+
dispatchedToIdleSession?: boolean;
|
|
350
|
+
dispatchedAt: string;
|
|
351
|
+
},
|
|
352
|
+
): void {
|
|
353
|
+
try {
|
|
354
|
+
BeadsDB.getInstance().insertDirectDispatch({ ...data, meshId });
|
|
355
|
+
} catch (e: any) {
|
|
356
|
+
process.stderr.write(`[adhdev-mesh] insertDirectDispatch failed for task ${data.taskId}: ${e?.message || e}\n`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function getActiveDirectDispatches(meshId: string): DirectDispatchRecord[] {
|
|
361
|
+
try {
|
|
362
|
+
return BeadsDB.getInstance().getActiveDirectDispatches(meshId);
|
|
363
|
+
} catch {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function updateDirectDispatchStatus(
|
|
369
|
+
meshId: string,
|
|
370
|
+
sessionId: string,
|
|
371
|
+
status: 'acked' | 'completed' | 'failed' | 'stale',
|
|
372
|
+
): void {
|
|
373
|
+
try {
|
|
374
|
+
BeadsDB.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
|
|
375
|
+
} catch { /* best-effort */ }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function cleanupTerminalDirectDispatches(olderThanMs = 7 * 24 * 60 * 60_000): void {
|
|
379
|
+
try {
|
|
380
|
+
BeadsDB.getInstance().cleanupTerminalDirectDispatches(olderThanMs);
|
|
381
|
+
} catch { /* best-effort */ }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function markStaleDirectDispatches(meshId: string, olderThanMs = 60 * 60_000): void {
|
|
385
|
+
try {
|
|
386
|
+
BeadsDB.getInstance().markStaleDirectDispatches(meshId, olderThanMs);
|
|
387
|
+
} catch { /* best-effort */ }
|
|
388
|
+
}
|