@adhdev/daemon-core 0.9.82-rc.21 → 0.9.82-rc.211
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/daemon-lifecycle.d.ts +1 -0
- 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 +23 -2
- 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 +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +3 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +1 -2
- package/dist/cli-adapters/terminal-screen.d.ts +2 -11
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +32 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +9 -1
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-types.d.ts +2 -0
- package/dist/index.d.ts +46 -11
- package/dist/index.js +35173 -15756
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34880 -15556
- 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/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +36 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +151 -0
- package/dist/mesh/mesh-events-pending.d.ts +33 -0
- package/dist/mesh/mesh-events-stale.d.ts +40 -0
- package/dist/mesh/mesh-events-utils.d.ts +14 -0
- package/dist/mesh/mesh-events.d.ts +5 -40
- package/dist/mesh/mesh-fast-forward.d.ts +41 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
- package/dist/mesh/mesh-ledger.d.ts +77 -1
- package/dist/mesh/mesh-missions.d.ts +58 -0
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-runtime-store.d.ts +324 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-work-queue.d.ts +137 -6
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +215 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
- package/dist/providers/acp-provider-instance.d.ts +2 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/cli-provider-instance.d.ts +39 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- 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/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +12 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- 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 +65 -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 +203 -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/spec/adapter.d.ts +52 -0
- package/dist/providers/spec/cli-adapter.d.ts +101 -0
- package/dist/providers/spec/driver.d.ts +225 -0
- package/dist/providers/spec/evaluator.d.ts +54 -0
- package/dist/providers/spec/loader.d.ts +15 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +2025 -0
- package/dist/providers/spec/types.d.ts +302 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +109 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- 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 -3
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +22 -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 +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1138 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +673 -1139
- 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 +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +35 -29
- package/src/cli-adapters/terminal-backends/types.ts +1 -3
- package/src/cli-adapters/terminal-screen.ts +11 -81
- package/src/commands/chat-commands.ts +1787 -60
- package/src/commands/cli-manager.ts +283 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +332 -122
- package/src/commands/router.ts +4198 -557
- package/src/config/chat-history.ts +95 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +42 -12
- package/src/git/git-diff.ts +53 -0
- package/src/git/git-status.ts +35 -6
- package/src/git/git-types.ts +2 -0
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +126 -10
- 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/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +218 -31
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-delivery-policy.ts +315 -0
- package/src/mesh/mesh-events-coordinator.ts +1404 -0
- package/src/mesh/mesh-events-pending.ts +371 -0
- package/src/mesh/mesh-events-stale.ts +283 -0
- package/src/mesh/mesh-events-utils.ts +161 -0
- package/src/mesh/mesh-events.ts +24 -908
- package/src/mesh/mesh-fast-forward.ts +438 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
- package/src/mesh/mesh-ledger.ts +587 -102
- package/src/mesh/mesh-missions.ts +151 -0
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-review-inbox.ts +307 -0
- package/src/mesh/mesh-runtime-store.ts +1305 -0
- package/src/mesh/mesh-task-stats.ts +154 -0
- package/src/mesh/mesh-work-queue.ts +490 -166
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +403 -0
- package/src/mesh/worktree-bootstrap-config.ts +326 -0
- package/src/providers/acp-provider-instance.ts +15 -1
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +833 -64
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- 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/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +582 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- 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 +250 -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 +504 -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 +125 -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 +229 -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/spec/adapter.ts +138 -0
- package/src/providers/spec/cli-adapter.ts +626 -0
- package/src/providers/spec/driver.ts +922 -0
- package/src/providers/spec/evaluator.ts +597 -0
- package/src/providers/spec/loader.ts +324 -0
- package/src/providers/spec/native-history-executor.ts +939 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +691 -0
- package/src/providers/spec/schema.json +341 -0
- package/src/providers/spec/types.ts +356 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +425 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +119 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -1,24 +1,91 @@
|
|
|
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 { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
5
|
+
import { getMesh } from '../config/mesh-config.js';
|
|
5
6
|
|
|
6
7
|
export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
|
|
7
8
|
export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
|
|
8
9
|
export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
|
|
10
|
+
export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
|
|
9
11
|
|
|
10
12
|
export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
|
|
11
13
|
export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
|
|
14
|
+
export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
|
|
15
|
+
|
|
16
|
+
export interface MeshTaskModeValidationResult {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
taskMode?: MeshTaskMode;
|
|
19
|
+
violations: string[];
|
|
20
|
+
allowedOperations?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
|
|
24
|
+
{ 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 },
|
|
25
|
+
{ 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 },
|
|
26
|
+
{ label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
|
|
27
|
+
{ 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 },
|
|
28
|
+
{ 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 },
|
|
29
|
+
{ 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 },
|
|
30
|
+
{ 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 },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
|
|
34
|
+
if (typeof value !== 'string') return undefined;
|
|
35
|
+
const normalized = value.trim() as MeshTaskMode;
|
|
36
|
+
return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
|
|
40
|
+
const taskMode = normalizeMeshTaskMode(mode);
|
|
41
|
+
if (!taskMode) {
|
|
42
|
+
return { valid: true, violations: [] };
|
|
43
|
+
}
|
|
44
|
+
if (taskMode !== 'live_debug_readonly') {
|
|
45
|
+
return { valid: true, taskMode, violations: [] };
|
|
46
|
+
}
|
|
47
|
+
const violations = LIVE_DEBUG_READONLY_FORBIDDEN
|
|
48
|
+
.filter(rule => rule.pattern.test(message || ''))
|
|
49
|
+
.map(rule => rule.label);
|
|
50
|
+
return {
|
|
51
|
+
valid: violations.length === 0,
|
|
52
|
+
taskMode,
|
|
53
|
+
violations,
|
|
54
|
+
allowedOperations: [
|
|
55
|
+
'process/log/window/port/session inspection',
|
|
56
|
+
'read-only filesystem listing/reading',
|
|
57
|
+
'status probes and keep-running handle reporting',
|
|
58
|
+
'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
12
62
|
|
|
13
63
|
export interface MeshWorkQueueEntry {
|
|
14
64
|
id: string;
|
|
15
65
|
meshId: string;
|
|
16
66
|
message: string;
|
|
17
67
|
status: MeshTaskStatus;
|
|
68
|
+
taskMode?: MeshTaskMode;
|
|
18
69
|
/** If specified, only this node can claim the task (used by legacy mesh_send_task) */
|
|
19
70
|
targetNodeId?: string;
|
|
20
71
|
/** If specified, only this runtime session can claim the task */
|
|
21
72
|
targetSessionId?: string;
|
|
73
|
+
/** If specified, a node must expose all tags before it can claim the task. */
|
|
74
|
+
requiredTags?: string[];
|
|
75
|
+
/**
|
|
76
|
+
* M1: ids of tasks that must reach 'completed' before this task is claimable.
|
|
77
|
+
* Forward references (ids not yet enqueued) are allowed for batch flows and
|
|
78
|
+
* simply keep the task waiting until the referenced task exists and completes.
|
|
79
|
+
*/
|
|
80
|
+
dependsOn?: string[];
|
|
81
|
+
/** M1/M3: mission this task belongs to (joins mesh_missions). */
|
|
82
|
+
missionId?: string;
|
|
83
|
+
/**
|
|
84
|
+
* M1: why this task is held back (e.g. "dependency_failed:<taskId>").
|
|
85
|
+
* Only set by the system on dependency failure under the 'block' policy;
|
|
86
|
+
* waiting-on-dependency state is computed at view time, not stored.
|
|
87
|
+
*/
|
|
88
|
+
blockedReason?: string;
|
|
22
89
|
/** The node that actually claimed and is executing the task */
|
|
23
90
|
assignedNodeId?: string;
|
|
24
91
|
/** The session currently executing the task */
|
|
@@ -30,6 +97,8 @@ export interface MeshWorkQueueEntry {
|
|
|
30
97
|
requeueReason?: string;
|
|
31
98
|
requeuedAt?: string;
|
|
32
99
|
requeueCount?: number;
|
|
100
|
+
/** Max automatic requeue attempts. When requeueCount reaches this, task is auto-failed. */
|
|
101
|
+
maxRetries?: number;
|
|
33
102
|
/** Last automatic queue session spin-up attempt, for mesh_view_queue/debug visibility. */
|
|
34
103
|
autoLaunch?: {
|
|
35
104
|
status: 'skipped' | 'started' | 'failed' | 'completed';
|
|
@@ -45,25 +114,112 @@ export interface MeshWorkQueueEntry {
|
|
|
45
114
|
updatedAt: string;
|
|
46
115
|
}
|
|
47
116
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
117
|
+
export interface MeshQueueMutationOptions {
|
|
118
|
+
ownerRole?: RepoMeshDaemonRole;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizeMeshCapabilityTags(value: unknown): string[] {
|
|
122
|
+
if (!Array.isArray(value)) return [];
|
|
123
|
+
const seen = new Set<string>();
|
|
124
|
+
return value
|
|
125
|
+
.map(tag => typeof tag === 'string' ? tag.trim() : '')
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.filter(tag => {
|
|
128
|
+
if (seen.has(tag)) return false;
|
|
129
|
+
seen.add(tag);
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function firstProviderPriority(policy: unknown): string | undefined {
|
|
135
|
+
const raw = policy && typeof policy === 'object' && !Array.isArray(policy)
|
|
136
|
+
? (policy as Record<string, unknown>).providerPriority
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!Array.isArray(raw)) return undefined;
|
|
139
|
+
return raw.find(type => typeof type === 'string' && type.trim())?.trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function buildMeshNodeCapabilityTags(
|
|
143
|
+
node: { capabilities?: unknown; policy?: unknown; isLocalWorktree?: unknown; worktreeBranch?: unknown } | undefined,
|
|
144
|
+
providerType?: string,
|
|
145
|
+
): string[] {
|
|
146
|
+
const provider = typeof providerType === 'string' && providerType.trim()
|
|
147
|
+
? providerType.trim()
|
|
148
|
+
: firstProviderPriority(node?.policy);
|
|
149
|
+
const worktreeBranch = typeof node?.worktreeBranch === 'string' && node.worktreeBranch.trim()
|
|
150
|
+
? node.worktreeBranch.trim()
|
|
151
|
+
: null;
|
|
152
|
+
return normalizeMeshCapabilityTags([
|
|
153
|
+
...(Array.isArray(node?.capabilities) ? node.capabilities : []),
|
|
154
|
+
`os=${process.platform}`,
|
|
155
|
+
`arch=${process.arch}`,
|
|
156
|
+
...(provider ? [`provider=${provider}`] : []),
|
|
157
|
+
// Worktree nodes automatically expose a "worktree=<branch>" tag so that
|
|
158
|
+
// mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
|
|
159
|
+
// only to the matching worktree node.
|
|
160
|
+
...(node?.isLocalWorktree === true && worktreeBranch ? [`worktree=${worktreeBranch}`] : []),
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function nodeSatisfiesRequiredTags(requiredTags: unknown, capabilityTags: unknown): boolean {
|
|
165
|
+
const required = normalizeMeshCapabilityTags(requiredTags);
|
|
166
|
+
if (required.length === 0) return true;
|
|
167
|
+
const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
|
|
168
|
+
return required.every(tag => available.has(tag));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function withQueueLock<T>(_meshId: string, fn: () => T): T {
|
|
172
|
+
return MeshRuntimeStore.getInstance().transaction(fn);
|
|
51
173
|
}
|
|
52
174
|
|
|
53
175
|
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
|
-
}
|
|
176
|
+
return MeshRuntimeStore.getInstance().getQueueEntries(meshId);
|
|
62
177
|
}
|
|
63
178
|
|
|
64
179
|
function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
65
|
-
|
|
66
|
-
|
|
180
|
+
MeshRuntimeStore.getInstance().replaceQueue(meshId, queue);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeDependsOn(value: unknown): string[] {
|
|
184
|
+
if (!Array.isArray(value)) return [];
|
|
185
|
+
const seen = new Set<string>();
|
|
186
|
+
return value
|
|
187
|
+
.map(id => typeof id === 'string' ? id.trim() : '')
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.filter(id => {
|
|
190
|
+
if (seen.has(id)) return false;
|
|
191
|
+
seen.add(id);
|
|
192
|
+
return true;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* M1: detect dependency cycles before enqueue. Walks the dependency graph of
|
|
198
|
+
* existing queue entries plus the new task's edges. Fail-closed: a cycle
|
|
199
|
+
* rejects the enqueue entirely. Synchronous and bounded by queue size.
|
|
200
|
+
*/
|
|
201
|
+
export function assertNoDependencyCycle(meshId: string, newTaskId: string, dependsOn: string[]): void {
|
|
202
|
+
if (dependsOn.length === 0) return;
|
|
203
|
+
if (dependsOn.includes(newTaskId)) {
|
|
204
|
+
throw new Error(`dependency_cycle_detected: task '${newTaskId}' cannot depend on itself`);
|
|
205
|
+
}
|
|
206
|
+
const adjacency = new Map<string, string[]>();
|
|
207
|
+
for (const entry of readQueue(meshId)) {
|
|
208
|
+
adjacency.set(entry.id, normalizeDependsOn(entry.dependsOn));
|
|
209
|
+
}
|
|
210
|
+
adjacency.set(newTaskId, dependsOn);
|
|
211
|
+
// DFS from the new task: if we can reach newTaskId again, the edges form a cycle.
|
|
212
|
+
const stack = [...dependsOn];
|
|
213
|
+
const visited = new Set<string>();
|
|
214
|
+
while (stack.length > 0) {
|
|
215
|
+
const current = stack.pop()!;
|
|
216
|
+
if (current === newTaskId) {
|
|
217
|
+
throw new Error(`dependency_cycle_detected: task '${newTaskId}' is part of a dependency cycle via '${dependsOn.join(', ')}'`);
|
|
218
|
+
}
|
|
219
|
+
if (visited.has(current)) continue;
|
|
220
|
+
visited.add(current);
|
|
221
|
+
stack.push(...(adjacency.get(current) ?? []));
|
|
222
|
+
}
|
|
67
223
|
}
|
|
68
224
|
|
|
69
225
|
/**
|
|
@@ -72,75 +228,119 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
|
72
228
|
export function enqueueTask(
|
|
73
229
|
meshId: string,
|
|
74
230
|
message: string,
|
|
75
|
-
opts?: {
|
|
231
|
+
opts?: {
|
|
232
|
+
targetNodeId?: string;
|
|
233
|
+
targetSessionId?: string;
|
|
234
|
+
taskMode?: MeshTaskMode | string;
|
|
235
|
+
requiredTags?: string[];
|
|
236
|
+
/** M1: tasks that must complete before this one is claimable. */
|
|
237
|
+
dependsOn?: string[];
|
|
238
|
+
/** M1/M3: mission this task belongs to. */
|
|
239
|
+
missionId?: string;
|
|
240
|
+
/** Explicit task id for batch/template flows (M5). Random UUID when omitted. */
|
|
241
|
+
id?: string;
|
|
242
|
+
} & MeshQueueMutationOptions,
|
|
76
243
|
): MeshWorkQueueEntry {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
244
|
+
requireMeshHostQueueOwner(opts);
|
|
245
|
+
const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
|
|
246
|
+
if (!modeValidation.valid) {
|
|
247
|
+
throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
|
|
248
|
+
}
|
|
249
|
+
const id = typeof opts?.id === 'string' && opts.id.trim() ? opts.id.trim() : randomUUID();
|
|
250
|
+
const dependsOn = normalizeDependsOn(opts?.dependsOn);
|
|
251
|
+
return withQueueLock(meshId, () => {
|
|
252
|
+
if (MeshRuntimeStore.getInstance().findQueueEntryById(meshId, id)) {
|
|
253
|
+
throw new Error(`duplicate_task_id: task '${id}' already exists in mesh '${meshId}'`);
|
|
254
|
+
}
|
|
255
|
+
assertNoDependencyCycle(meshId, id, dependsOn);
|
|
256
|
+
const entry: MeshWorkQueueEntry = {
|
|
257
|
+
id,
|
|
258
|
+
meshId,
|
|
259
|
+
message,
|
|
260
|
+
status: 'pending',
|
|
261
|
+
taskMode: modeValidation.taskMode,
|
|
262
|
+
targetNodeId: opts?.targetNodeId,
|
|
263
|
+
targetSessionId: opts?.targetSessionId,
|
|
264
|
+
requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
|
|
265
|
+
...(dependsOn.length > 0 ? { dependsOn } : {}),
|
|
266
|
+
...(typeof opts?.missionId === 'string' && opts.missionId.trim() ? { missionId: opts.missionId.trim() } : {}),
|
|
267
|
+
createdAt: new Date().toISOString(),
|
|
268
|
+
updatedAt: new Date().toISOString(),
|
|
269
|
+
};
|
|
270
|
+
MeshRuntimeStore.getInstance().insertQueueEntry(entry);
|
|
271
|
+
return entry;
|
|
272
|
+
});
|
|
91
273
|
}
|
|
92
274
|
|
|
93
275
|
/**
|
|
94
276
|
* Get all tasks in the queue, optionally filtered by status.
|
|
95
277
|
*/
|
|
96
278
|
export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }): MeshWorkQueueEntry[] {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return queue;
|
|
279
|
+
return MeshRuntimeStore.getInstance().getQueueEntries(meshId, opts?.status?.length ? opts.status : undefined);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function getMeshQueueRevision(meshId: string): string {
|
|
283
|
+
return MeshRuntimeStore.getInstance().getQueueRevision(meshId);
|
|
103
284
|
}
|
|
104
285
|
|
|
105
286
|
/**
|
|
106
287
|
* Find the next pending task that this node is allowed to claim, and mark it as assigned.
|
|
107
288
|
*/
|
|
108
|
-
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
|
-
}
|
|
289
|
+
export function claimNextTask(meshId: string, nodeId: string, sessionId: string, capabilityTags?: string[]): MeshWorkQueueEntry | null {
|
|
290
|
+
return MeshRuntimeStore.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
|
|
291
|
+
}
|
|
130
292
|
|
|
131
|
-
|
|
293
|
+
// ─── M1: Dependency Failure Propagation ─────────
|
|
132
294
|
|
|
133
|
-
|
|
134
|
-
entry.status = 'assigned';
|
|
135
|
-
entry.assignedNodeId = nodeId;
|
|
136
|
-
entry.assignedSessionId = sessionId;
|
|
137
|
-
entry.dispatchTimestamp = new Date().toISOString();
|
|
138
|
-
entry.updatedAt = new Date().toISOString();
|
|
295
|
+
export type DependencyFailurePolicy = 'block' | 'cancel';
|
|
139
296
|
|
|
140
|
-
|
|
141
|
-
|
|
297
|
+
function resolveDependencyFailurePolicy(meshId: string): DependencyFailurePolicy {
|
|
298
|
+
try {
|
|
299
|
+
const policy = (getMesh(meshId)?.policy ?? {}) as Record<string, unknown>;
|
|
300
|
+
return policy.onDependencyFailure === 'cancel' ? 'cancel' : 'block';
|
|
301
|
+
} catch {
|
|
302
|
+
return 'block';
|
|
303
|
+
}
|
|
142
304
|
}
|
|
143
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Apply the mesh's onDependencyFailure policy to pending dependents of a task
|
|
308
|
+
* that just reached a failed/cancelled terminal state.
|
|
309
|
+
*
|
|
310
|
+
* - 'block' (default): dependents stay pending with blockedReason
|
|
311
|
+
* "dependency_failed:<taskId>" so an operator can requeue/cancel them.
|
|
312
|
+
* - 'cancel': dependents are cancelled (cascading to their own dependents).
|
|
313
|
+
*
|
|
314
|
+
* Must be called inside the queue lock of the triggering transition.
|
|
315
|
+
*/
|
|
316
|
+
function propagateDependencyFailure(meshId: string, failedTaskId: string): void {
|
|
317
|
+
const policy = resolveDependencyFailurePolicy(meshId);
|
|
318
|
+
const store = MeshRuntimeStore.getInstance();
|
|
319
|
+
const frontier = [failedTaskId];
|
|
320
|
+
const seen = new Set<string>(frontier);
|
|
321
|
+
while (frontier.length > 0) {
|
|
322
|
+
const currentId = frontier.pop()!;
|
|
323
|
+
const dependents = store.getQueueEntries(meshId, ['pending'])
|
|
324
|
+
.filter(entry => Array.isArray(entry.dependsOn) && entry.dependsOn.includes(currentId));
|
|
325
|
+
for (const dependent of dependents) {
|
|
326
|
+
if (seen.has(dependent.id)) continue;
|
|
327
|
+
seen.add(dependent.id);
|
|
328
|
+
if (policy === 'cancel') {
|
|
329
|
+
dependent.status = 'cancelled';
|
|
330
|
+
dependent.cancelledAt = new Date().toISOString();
|
|
331
|
+
dependent.cancelReason = `dependency_failed:${currentId}`;
|
|
332
|
+
store.updateQueueEntry(dependent);
|
|
333
|
+
frontier.push(dependent.id); // cascade to transitive dependents
|
|
334
|
+
} else {
|
|
335
|
+
dependent.blockedReason = `dependency_failed:${currentId}`;
|
|
336
|
+
store.updateQueueEntry(dependent);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const DEPENDENCY_FAILURE_TERMINALS = new Set<MeshTaskStatus>(['failed', 'cancelled']);
|
|
343
|
+
|
|
144
344
|
/**
|
|
145
345
|
* Update the status of a specific task.
|
|
146
346
|
* Used when a session completes, fails, or stalls.
|
|
@@ -149,15 +349,17 @@ export function updateTaskStatus(
|
|
|
149
349
|
meshId: string,
|
|
150
350
|
taskId: string,
|
|
151
351
|
status: MeshTaskStatus,
|
|
352
|
+
opts?: MeshQueueMutationOptions,
|
|
152
353
|
): MeshWorkQueueEntry | null {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
354
|
+
requireMeshHostQueueOwner(opts);
|
|
355
|
+
return withQueueLock(meshId, () => {
|
|
356
|
+
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
357
|
+
if (!entry) return null;
|
|
358
|
+
entry.status = status;
|
|
359
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
360
|
+
if (DEPENDENCY_FAILURE_TERMINALS.has(status)) propagateDependencyFailure(meshId, taskId);
|
|
361
|
+
return entry;
|
|
362
|
+
});
|
|
161
363
|
}
|
|
162
364
|
|
|
163
365
|
export function recordTaskAutoLaunch(
|
|
@@ -165,17 +367,14 @@ export function recordTaskAutoLaunch(
|
|
|
165
367
|
taskId: string,
|
|
166
368
|
autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>,
|
|
167
369
|
): MeshWorkQueueEntry | null {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
};
|
|
176
|
-
queue[idx].updatedAt = now;
|
|
177
|
-
writeQueue(meshId, queue);
|
|
178
|
-
return queue[idx];
|
|
370
|
+
return withQueueLock(meshId, () => {
|
|
371
|
+
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
372
|
+
if (!entry) return null;
|
|
373
|
+
const now = new Date().toISOString();
|
|
374
|
+
entry.autoLaunch = { ...autoLaunch, updatedAt: now };
|
|
375
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
376
|
+
return entry;
|
|
377
|
+
});
|
|
179
378
|
}
|
|
180
379
|
|
|
181
380
|
/**
|
|
@@ -184,25 +383,31 @@ export function recordTaskAutoLaunch(
|
|
|
184
383
|
export function cancelTask(
|
|
185
384
|
meshId: string,
|
|
186
385
|
taskId: string,
|
|
187
|
-
opts?: { reason?: string },
|
|
386
|
+
opts?: { reason?: string } & MeshQueueMutationOptions,
|
|
188
387
|
): MeshWorkQueueEntry | null {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
388
|
+
requireMeshHostQueueOwner(opts);
|
|
389
|
+
return withQueueLock(meshId, () => {
|
|
390
|
+
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
391
|
+
if (!entry) return null;
|
|
392
|
+
const now = new Date().toISOString();
|
|
393
|
+
entry.status = 'cancelled';
|
|
394
|
+
entry.cancelledAt = now;
|
|
395
|
+
if (opts?.reason) entry.cancelReason = opts.reason;
|
|
396
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
397
|
+
propagateDependencyFailure(meshId, taskId);
|
|
398
|
+
return entry;
|
|
399
|
+
});
|
|
200
400
|
}
|
|
201
401
|
|
|
202
402
|
/**
|
|
203
403
|
* Return a queue task to pending for retry. By default, dead session targeting
|
|
204
404
|
* and assigned ownership are cleared so stale assignments do not strand again.
|
|
205
405
|
*/
|
|
406
|
+
export type RequeueResult =
|
|
407
|
+
| { status: 'requeued'; entry: MeshWorkQueueEntry }
|
|
408
|
+
| { status: 'failed_max_retries'; entry: MeshWorkQueueEntry; maxRetries: number; requeueCount: number }
|
|
409
|
+
| { status: 'not_found' };
|
|
410
|
+
|
|
206
411
|
export function requeueTask(
|
|
207
412
|
meshId: string,
|
|
208
413
|
taskId: string,
|
|
@@ -212,29 +417,48 @@ export function requeueTask(
|
|
|
212
417
|
targetSessionId?: string;
|
|
213
418
|
clearTargetNode?: boolean;
|
|
214
419
|
clearTargetSession?: boolean;
|
|
215
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Override the retry cap for this call. Use only for explicit operator actions.
|
|
422
|
+
* If true, the task is requeued even when requeueCount >= maxRetries.
|
|
423
|
+
*/
|
|
424
|
+
force?: boolean;
|
|
425
|
+
/** Per-task retry cap override. Falls back to mesh policy maxTaskRetries (default 1). */
|
|
426
|
+
maxRetries?: number;
|
|
427
|
+
} & MeshQueueMutationOptions,
|
|
216
428
|
): MeshWorkQueueEntry | null {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
429
|
+
requireMeshHostQueueOwner(opts);
|
|
430
|
+
return withQueueLock(meshId, () => {
|
|
431
|
+
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
432
|
+
if (!entry) return null;
|
|
433
|
+
const currentCount = entry.requeueCount || 0;
|
|
434
|
+
const maxRetries = opts?.maxRetries ?? entry.maxRetries ?? 1;
|
|
435
|
+
if (!opts?.force && currentCount >= maxRetries) {
|
|
436
|
+
// Auto-fail: cap exceeded without explicit force override.
|
|
437
|
+
entry.status = 'failed';
|
|
438
|
+
entry.cancelReason = `max_retries_exceeded: requeued ${currentCount} time(s), limit is ${maxRetries}`;
|
|
439
|
+
entry.updatedAt = new Date().toISOString();
|
|
440
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
441
|
+
propagateDependencyFailure(meshId, taskId);
|
|
442
|
+
return entry;
|
|
443
|
+
}
|
|
444
|
+
entry.status = 'pending';
|
|
445
|
+
// Operator requeue clears a dependency-failure block — the operator is
|
|
446
|
+
// explicitly overriding the held-back state.
|
|
447
|
+
delete entry.blockedReason;
|
|
448
|
+
delete entry.assignedNodeId;
|
|
449
|
+
delete entry.assignedSessionId;
|
|
450
|
+
delete entry.cancelledAt;
|
|
451
|
+
delete entry.cancelReason;
|
|
452
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
453
|
+
if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
|
|
454
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
455
|
+
if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
|
|
456
|
+
entry.requeuedAt = new Date().toISOString();
|
|
457
|
+
entry.requeueCount = currentCount + 1;
|
|
458
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
459
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
460
|
+
return entry;
|
|
461
|
+
});
|
|
238
462
|
}
|
|
239
463
|
|
|
240
464
|
/**
|
|
@@ -244,29 +468,43 @@ export function updateSessionTaskStatus(
|
|
|
244
468
|
meshId: string,
|
|
245
469
|
sessionId: string,
|
|
246
470
|
status: MeshTaskStatus,
|
|
471
|
+
opts?: { occurredAt?: string },
|
|
247
472
|
): MeshWorkQueueEntry | null {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
473
|
+
return withQueueLock(meshId, () => {
|
|
474
|
+
const occurredAtIso = opts?.occurredAt ? new Date(opts.occurredAt).toISOString() : undefined;
|
|
475
|
+
const entry = MeshRuntimeStore.getInstance().findAssignedBySession(meshId, sessionId, occurredAtIso);
|
|
476
|
+
if (!entry) return null;
|
|
477
|
+
entry.status = status;
|
|
478
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
479
|
+
if (DEPENDENCY_FAILURE_TERMINALS.has(status)) propagateDependencyFailure(meshId, entry.id);
|
|
480
|
+
return entry;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* M1-3: true when at least one pending task is waiting on the given task.
|
|
486
|
+
* Used by the completion event path to decide whether to wake the queue.
|
|
487
|
+
*/
|
|
488
|
+
export function hasPendingDependents(meshId: string, taskId: string): boolean {
|
|
489
|
+
return MeshRuntimeStore.getInstance().getQueueEntries(meshId, ['pending'])
|
|
490
|
+
.some(entry => Array.isArray(entry.dependsOn) && entry.dependsOn.includes(taskId));
|
|
491
|
+
}
|
|
265
492
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
493
|
+
/**
|
|
494
|
+
* M1-4: view-time dependency state for a task — unmet dependency ids and
|
|
495
|
+
* whether the task is currently claimable from a dependency standpoint.
|
|
496
|
+
* Not stored (truth stays in task statuses).
|
|
497
|
+
*/
|
|
498
|
+
export function describeTaskDependencyState(
|
|
499
|
+
entry: Pick<MeshWorkQueueEntry, 'dependsOn' | 'blockedReason'>,
|
|
500
|
+
statusById: Map<string, MeshTaskStatus | string>,
|
|
501
|
+
): { waitingOn: string[]; dependenciesSatisfied: boolean } {
|
|
502
|
+
const deps = Array.isArray(entry.dependsOn) ? entry.dependsOn : [];
|
|
503
|
+
const waitingOn = deps.filter(depId => statusById.get(depId) !== 'completed');
|
|
504
|
+
return {
|
|
505
|
+
waitingOn,
|
|
506
|
+
dependenciesSatisfied: waitingOn.length === 0 && !entry.blockedReason,
|
|
507
|
+
};
|
|
270
508
|
}
|
|
271
509
|
|
|
272
510
|
export interface MeshWorkQueueStats {
|
|
@@ -294,14 +532,16 @@ export interface MeshWorkQueueStats {
|
|
|
294
532
|
* Return aggregate queue statistics for the given mesh.
|
|
295
533
|
*/
|
|
296
534
|
export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
const
|
|
535
|
+
const rows = MeshRuntimeStore.getInstance().getQueueStatsByStatus(meshId);
|
|
536
|
+
const counts: Record<string, number> = {};
|
|
537
|
+
for (const r of rows) counts[r.status] = r.count;
|
|
538
|
+
const pending = counts['pending'] ?? 0;
|
|
539
|
+
const assigned = counts['assigned'] ?? 0;
|
|
540
|
+
const completed = counts['completed'] ?? 0;
|
|
541
|
+
const failed = counts['failed'] ?? 0;
|
|
542
|
+
const cancelled = counts['cancelled'] ?? 0;
|
|
303
543
|
return {
|
|
304
|
-
total:
|
|
544
|
+
total: pending + assigned + completed + failed + cancelled,
|
|
305
545
|
active: pending + assigned,
|
|
306
546
|
historical: completed + failed + cancelled,
|
|
307
547
|
pending,
|
|
@@ -309,22 +549,106 @@ export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
|
|
|
309
549
|
completed,
|
|
310
550
|
failed,
|
|
311
551
|
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
|
-
})),
|
|
552
|
+
activeCounts: { pending, assigned },
|
|
553
|
+
historicalCounts: { completed, failed, cancelled },
|
|
554
|
+
activeAssignments: MeshRuntimeStore.getInstance().getActiveAssignmentDetails(meshId),
|
|
329
555
|
};
|
|
330
556
|
}
|
|
557
|
+
|
|
558
|
+
export function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
559
|
+
MeshRuntimeStore.getInstance().transaction(() => {
|
|
560
|
+
MeshRuntimeStore.getInstance().replaceQueue(meshId, queue);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function __clearMeshQueueForTests(meshId: string): void {
|
|
565
|
+
MeshRuntimeStore.getInstance().deleteQueue(meshId);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function __clearDirectDispatchesForTests(meshId: string): void {
|
|
569
|
+
MeshRuntimeStore.getInstance().deleteDirectDispatches(meshId);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function __resetMeshRuntimeStoreForTests(): void {
|
|
573
|
+
MeshRuntimeStore.resetForTests();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// ── Direct Dispatch Tracking ─────────────────────────────────────────────────
|
|
577
|
+
// Persists direct (non-queue) task dispatches so buildMeshActiveWork can read
|
|
578
|
+
// active work from MeshRuntimeStore instead of scanning ledger JSONL entries.
|
|
579
|
+
|
|
580
|
+
export type DirectDispatchRecord = ReturnType<MeshRuntimeStore['getActiveDirectDispatches']>[number];
|
|
581
|
+
|
|
582
|
+
export function insertDirectDispatch(
|
|
583
|
+
meshId: string,
|
|
584
|
+
data: {
|
|
585
|
+
taskId: string;
|
|
586
|
+
nodeId?: string;
|
|
587
|
+
sessionId?: string;
|
|
588
|
+
providerType?: string;
|
|
589
|
+
message: string;
|
|
590
|
+
taskMode?: string;
|
|
591
|
+
via: string;
|
|
592
|
+
dispatchedToIdleSession?: boolean;
|
|
593
|
+
dispatchedAt: string;
|
|
594
|
+
},
|
|
595
|
+
): void {
|
|
596
|
+
try {
|
|
597
|
+
MeshRuntimeStore.getInstance().insertDirectDispatch({ ...data, meshId });
|
|
598
|
+
} catch (e: any) {
|
|
599
|
+
process.stderr.write(`[adhdev-mesh] insertDirectDispatch failed for task ${data.taskId}: ${e?.message || e}\n`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function getActiveDirectDispatches(meshId: string): DirectDispatchRecord[] {
|
|
604
|
+
try {
|
|
605
|
+
return MeshRuntimeStore.getInstance().getActiveDirectDispatches(meshId);
|
|
606
|
+
} catch {
|
|
607
|
+
return [];
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function updateDirectDispatchStatus(
|
|
612
|
+
meshId: string,
|
|
613
|
+
sessionId: string,
|
|
614
|
+
status: 'acked' | 'completed' | 'failed' | 'stale',
|
|
615
|
+
): void {
|
|
616
|
+
try {
|
|
617
|
+
MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
|
|
618
|
+
} catch { /* best-effort */ }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function cleanupTerminalDirectDispatches(olderThanMs = 7 * 24 * 60 * 60_000): void {
|
|
622
|
+
try {
|
|
623
|
+
MeshRuntimeStore.getInstance().cleanupTerminalDirectDispatches(olderThanMs);
|
|
624
|
+
} catch { /* best-effort */ }
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function markStaleDirectDispatches(meshId: string, olderThanMs = 60 * 60_000): void {
|
|
628
|
+
try {
|
|
629
|
+
MeshRuntimeStore.getInstance().markStaleDirectDispatches(meshId, olderThanMs);
|
|
630
|
+
} catch { /* best-effort */ }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export type MeshToolCallRateResult = { rateLimitExceeded: boolean; callsInWindow: number; advisory: string | null };
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Record a coordinator tool call and return a rate-limit advisory when the
|
|
637
|
+
* call rate for that tool exceeds the allowed threshold.
|
|
638
|
+
*
|
|
639
|
+
* Defaults: 10-second sliding window, max 5 calls before advisory is raised.
|
|
640
|
+
* Returns { rateLimitExceeded: false } on any store error so callers are not blocked.
|
|
641
|
+
*/
|
|
642
|
+
export function recordMeshToolCall(opts: {
|
|
643
|
+
meshId: string;
|
|
644
|
+
tool: string;
|
|
645
|
+
sessionId?: string | null;
|
|
646
|
+
windowMs?: number;
|
|
647
|
+
maxCalls?: number;
|
|
648
|
+
}): MeshToolCallRateResult {
|
|
649
|
+
try {
|
|
650
|
+
return MeshRuntimeStore.getInstance().recordMeshToolCall(opts);
|
|
651
|
+
} catch {
|
|
652
|
+
return { rateLimitExceeded: false, callsInWindow: 0, advisory: null };
|
|
653
|
+
}
|
|
654
|
+
}
|