@adhdev/daemon-core 0.9.82-rc.410 → 0.9.82-rc.412
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/commands/med-family/mesh-crud.d.ts +27 -0
- package/dist/config/mesh-json-config.d.ts +31 -131
- package/dist/config/repo-settings.d.ts +77 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +372 -419
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +360 -408
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/package.json +2 -2
- package/src/commands/high-family/mesh-coordinator-launch.ts +9 -4
- package/src/commands/med-family/mesh-crud.ts +85 -8
- package/src/config/mesh-json-config.ts +40 -281
- package/src/config/repo-settings.ts +111 -0
- package/src/index.ts +5 -0
- package/src/mesh/mesh-ledger.ts +10 -0
- package/src/mesh/mesh-missions.ts +86 -1
- package/src/mesh/mesh-queue-assignment.ts +15 -51
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo Settings — one unified loader over the repo-committed `.adhdev/*` config
|
|
3
|
+
* files (the "single source" assembly point).
|
|
4
|
+
*
|
|
5
|
+
* DESIGN (option B — file separation preserved): the repo keeps its declarative
|
|
6
|
+
* config in SEPARATE files, each with its own dedicated loader:
|
|
7
|
+
*
|
|
8
|
+
* .adhdev/mesh.json → loadRepoMeshJsonConfig (coordinator + operatingNotes)
|
|
9
|
+
* .adhdev/refine.json → loadMeshRefineConfig (Refinery validation)
|
|
10
|
+
* .adhdev/worktree_bootstrap.json → loadMeshWorktreeBootstrapConfig
|
|
11
|
+
* .adhdev/change-impact.json → loadChangeImpactConfig
|
|
12
|
+
*
|
|
13
|
+
* Nothing is inlined into mesh.json. `loadRepoSettings` simply CALLS each
|
|
14
|
+
* dedicated loader and assembles the results into one `RepoSettings` object so a
|
|
15
|
+
* consumer can read every repo-shared setting through a single call instead of
|
|
16
|
+
* threading four loaders. The per-file loaders remain the source of truth (and
|
|
17
|
+
* stay independently usable); this is a convenience aggregator, not a new schema.
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT — policy is NOT here. RepoMeshPolicy (the 15 scheduling/approval
|
|
20
|
+
* fields) is MACHINE-LOCAL only: it lives in meshes.json and is never sourced
|
|
21
|
+
* from or merged with a repo file. mesh.json carries only coordinator prompt
|
|
22
|
+
* config + operating notes (+ advisory limits).
|
|
23
|
+
*
|
|
24
|
+
* The refine/worktree-bootstrap loaders also honor a machine-local INLINE seam
|
|
25
|
+
* (mesh.refineConfig / mesh.policy.worktreeBootstrap, etc.) — that seam is for
|
|
26
|
+
* machine-local config and is unchanged here; mesh.json gains no such zone.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
loadRepoMeshJsonConfig,
|
|
31
|
+
type RepoMeshDeclarativeCoordinatorConfig,
|
|
32
|
+
type RepoMeshDeclarativeLimits,
|
|
33
|
+
type RepoMeshJsonConfigLoadResult,
|
|
34
|
+
} from './mesh-json-config.js';
|
|
35
|
+
import {
|
|
36
|
+
loadMeshRefineConfig,
|
|
37
|
+
type MeshRefineConfigLoadResult,
|
|
38
|
+
} from '../mesh/refine-config.js';
|
|
39
|
+
import {
|
|
40
|
+
loadMeshWorktreeBootstrapConfig,
|
|
41
|
+
type WorktreeBootstrapConfigLoadResult,
|
|
42
|
+
} from '../mesh/worktree-bootstrap-config.js';
|
|
43
|
+
import {
|
|
44
|
+
loadChangeImpactConfig,
|
|
45
|
+
type ChangeImpactConfigLoadResult,
|
|
46
|
+
} from '../git/change-impact-config.js';
|
|
47
|
+
import type { CoordinatorOperatingNote } from '../mesh/coordinator-prompt.js';
|
|
48
|
+
|
|
49
|
+
export interface LoadRepoSettingsOptions {
|
|
50
|
+
/** Workspace path whose `.adhdev/*` files are resolved (mesh.json / refine / bootstrap). */
|
|
51
|
+
workspace: string;
|
|
52
|
+
/**
|
|
53
|
+
* Machine-local mesh entry, consulted ONLY for the INLINE seam of the
|
|
54
|
+
* refine / worktree-bootstrap loaders (mesh.refineConfig etc.). Optional —
|
|
55
|
+
* omit it and only the repo files are considered. NEVER used to source policy.
|
|
56
|
+
*/
|
|
57
|
+
mesh?: any;
|
|
58
|
+
/**
|
|
59
|
+
* Repo root for change-impact resolution. Defaults to `workspace` when omitted
|
|
60
|
+
* (change-impact compares the daemon build commit against the workspace HEAD).
|
|
61
|
+
*/
|
|
62
|
+
repoRoot?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The assembled repo-shared settings. Each sub-config carries its own load
|
|
67
|
+
* result (source / sourceType / error) so a consumer can tell "absent" from
|
|
68
|
+
* "invalid" per file. `coordinator` / `operatingNotes` / `limits` are lifted out
|
|
69
|
+
* of the mesh.json load result for convenience; `meshJson` keeps the full result
|
|
70
|
+
* (e.g. for the `sourceType === 'invalid'` warning on coordinator launch).
|
|
71
|
+
*/
|
|
72
|
+
export interface RepoSettings {
|
|
73
|
+
/** Repo-shared coordinator prompt config (override/append) from `.adhdev/mesh.json`. */
|
|
74
|
+
coordinator?: RepoMeshDeclarativeCoordinatorConfig;
|
|
75
|
+
/** Repo-declared baseline operating notes from `.adhdev/mesh.json`. */
|
|
76
|
+
operatingNotes?: CoordinatorOperatingNote[];
|
|
77
|
+
/** Advisory-only limits from `.adhdev/mesh.json` (recorded, not enforced in v1). */
|
|
78
|
+
limits?: RepoMeshDeclarativeLimits;
|
|
79
|
+
/** Full `.adhdev/mesh.json` declarative load result (coordinator/operatingNotes source). */
|
|
80
|
+
meshJson: RepoMeshJsonConfigLoadResult;
|
|
81
|
+
/** `.adhdev/refine.json` Refinery validation config load result. */
|
|
82
|
+
refine: MeshRefineConfigLoadResult;
|
|
83
|
+
/** `.adhdev/worktree_bootstrap.json` bootstrap config load result. */
|
|
84
|
+
worktreeBootstrap: WorktreeBootstrapConfigLoadResult;
|
|
85
|
+
/** `.adhdev/change-impact.json` change-impact config load result. */
|
|
86
|
+
changeImpact: ChangeImpactConfigLoadResult;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load every repo-committed `.adhdev/*` setting for a workspace and assemble them
|
|
91
|
+
* into one object. Each dedicated loader degrades to an `unavailable`/`invalid`
|
|
92
|
+
* load result rather than throwing, so this never throws on a missing or broken
|
|
93
|
+
* file — a consumer inspects the per-sub-config `sourceType`.
|
|
94
|
+
*/
|
|
95
|
+
export function loadRepoSettings(opts: LoadRepoSettingsOptions): RepoSettings {
|
|
96
|
+
const workspace = typeof opts.workspace === 'string' ? opts.workspace : '';
|
|
97
|
+
const mesh = opts.mesh;
|
|
98
|
+
const repoRoot = typeof opts.repoRoot === 'string' && opts.repoRoot ? opts.repoRoot : workspace;
|
|
99
|
+
|
|
100
|
+
const meshJson = loadRepoMeshJsonConfig(workspace);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
coordinator: meshJson.config?.coordinator,
|
|
104
|
+
operatingNotes: meshJson.config?.operatingNotes,
|
|
105
|
+
limits: meshJson.config?.limits,
|
|
106
|
+
meshJson,
|
|
107
|
+
refine: loadMeshRefineConfig(mesh, workspace),
|
|
108
|
+
worktreeBootstrap: loadMeshWorktreeBootstrapConfig(mesh, workspace),
|
|
109
|
+
changeImpact: loadChangeImpactConfig(repoRoot),
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -237,6 +237,11 @@ export type {
|
|
|
237
237
|
RepoMeshRefineConfig,
|
|
238
238
|
RepoMeshRefineValidationCommandConfig,
|
|
239
239
|
} from './mesh/refine-config.js';
|
|
240
|
+
// Unified repo-settings loader: assembles the separate `.adhdev/*` config files
|
|
241
|
+
// (mesh.json coordinator/operatingNotes, refine, worktree-bootstrap, change-impact)
|
|
242
|
+
// into one object. Policy is machine-local and not part of repo settings.
|
|
243
|
+
export { loadRepoSettings } from './config/repo-settings.js';
|
|
244
|
+
export type { RepoSettings, LoadRepoSettingsOptions } from './config/repo-settings.js';
|
|
240
245
|
|
|
241
246
|
// ── Mesh Task Ledger ──
|
|
242
247
|
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, readLedgerSliceFromStore, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
package/src/mesh/mesh-ledger.ts
CHANGED
|
@@ -49,6 +49,16 @@ export type MeshLedgerKind =
|
|
|
49
49
|
// the ledger so it survives coordinator restarts and is provider-neutral.
|
|
50
50
|
// payload: { text, category?, createdAt?, sourceCoordinator? }
|
|
51
51
|
| 'coordinator_operating_note'
|
|
52
|
+
// Mission audit trail: mission record mutations (mesh_mission_upsert) so the
|
|
53
|
+
// ledger captures mission lifecycle, not just task events. Without these a
|
|
54
|
+
// mission create / goal rewrite / status transition left no ledger trace,
|
|
55
|
+
// breaking audit continuity and post-restart recovery.
|
|
56
|
+
// mission_created payload: { missionId, title, goalSummary, goalLength, goalTruncated, status }
|
|
57
|
+
// mission_status_changed payload: { missionId, title, fromStatus, toStatus }
|
|
58
|
+
// mission_goal_updated payload: { missionId, title, prevGoalSummary, nextGoalSummary, prevGoalLength, nextGoalLength, goalTruncated }
|
|
59
|
+
| 'mission_created'
|
|
60
|
+
| 'mission_status_changed'
|
|
61
|
+
| 'mission_goal_updated'
|
|
52
62
|
;
|
|
53
63
|
|
|
54
64
|
export interface MeshLedgerEntry {
|
|
@@ -16,6 +16,19 @@ import { randomUUID } from 'crypto';
|
|
|
16
16
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
17
17
|
import { getQueue } from './mesh-work-queue.js';
|
|
18
18
|
import { computeMeshMissionStats, type MeshMissionStats } from './mesh-task-stats.js';
|
|
19
|
+
import { appendLedgerEntry } from './mesh-ledger.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Max chars of mission goal text written into a ledger entry payload. Mission
|
|
23
|
+
* goals can be hundreds–thousands of chars; the ledger is an append-only audit
|
|
24
|
+
* stream, so we store a bounded summary (+ a length/truncated flag) rather than
|
|
25
|
+
* the full text to keep the ledger from bloating on repeated goal rewrites.
|
|
26
|
+
*/
|
|
27
|
+
const LEDGER_GOAL_SUMMARY_MAX = 200;
|
|
28
|
+
|
|
29
|
+
function summarizeGoalForLedger(goal: string): string {
|
|
30
|
+
return goal.length > LEDGER_GOAL_SUMMARY_MAX ? goal.slice(0, LEDGER_GOAL_SUMMARY_MAX) : goal;
|
|
31
|
+
}
|
|
19
32
|
|
|
20
33
|
export type MeshMissionStatus = 'active' | 'paused' | 'completed' | 'abandoned';
|
|
21
34
|
|
|
@@ -102,6 +115,8 @@ export function upsertMeshMission(meshId: string, input: {
|
|
|
102
115
|
const id = typeof input.id === 'string' && input.id.trim() ? input.id.trim() : randomUUID();
|
|
103
116
|
const store = MeshRuntimeStore.getInstance();
|
|
104
117
|
const existing = store.getMission(meshId, id);
|
|
118
|
+
const prevStatus = existing ? normalizeMissionStatus(existing.status) : null;
|
|
119
|
+
const prevGoal = existing?.goal ?? '';
|
|
105
120
|
const record = {
|
|
106
121
|
id,
|
|
107
122
|
meshId,
|
|
@@ -111,7 +126,77 @@ export function upsertMeshMission(meshId: string, input: {
|
|
|
111
126
|
};
|
|
112
127
|
store.upsertMission(record);
|
|
113
128
|
const saved = store.getMission(meshId, id)!;
|
|
114
|
-
|
|
129
|
+
const result: MeshMissionRecord = { ...saved, status: normalizeMissionStatus(saved.status) };
|
|
130
|
+
|
|
131
|
+
// Mission audit trail: record this mutation in the mesh ledger so mission
|
|
132
|
+
// lifecycle (create, goal rewrite, status transition) is auditable alongside
|
|
133
|
+
// task events and survives a coordinator restart. Best-effort: a ledger
|
|
134
|
+
// failure must never break the primary mission write.
|
|
135
|
+
appendMissionLedgerEntries(meshId, {
|
|
136
|
+
isCreate: !existing,
|
|
137
|
+
record: result,
|
|
138
|
+
prevStatus,
|
|
139
|
+
prevGoal,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Append the relevant ledger entries for a mission upsert. Emits at most one
|
|
147
|
+
* entry per distinct change:
|
|
148
|
+
* - new mission → mission_created
|
|
149
|
+
* - status differs from prior → mission_status_changed
|
|
150
|
+
* - goal text differs from prior → mission_goal_updated (no-op rewrites skipped)
|
|
151
|
+
*/
|
|
152
|
+
function appendMissionLedgerEntries(
|
|
153
|
+
meshId: string,
|
|
154
|
+
args: { isCreate: boolean; record: MeshMissionRecord; prevStatus: MeshMissionStatus | null; prevGoal: string },
|
|
155
|
+
): void {
|
|
156
|
+
const { isCreate, record, prevStatus, prevGoal } = args;
|
|
157
|
+
try {
|
|
158
|
+
if (isCreate) {
|
|
159
|
+
const goal = record.goal ?? '';
|
|
160
|
+
appendLedgerEntry(meshId, {
|
|
161
|
+
kind: 'mission_created',
|
|
162
|
+
payload: {
|
|
163
|
+
missionId: record.id,
|
|
164
|
+
title: record.title,
|
|
165
|
+
goalSummary: summarizeGoalForLedger(goal),
|
|
166
|
+
goalLength: goal.length,
|
|
167
|
+
goalTruncated: goal.length > LEDGER_GOAL_SUMMARY_MAX,
|
|
168
|
+
status: record.status,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (prevStatus !== null && prevStatus !== record.status) {
|
|
174
|
+
appendLedgerEntry(meshId, {
|
|
175
|
+
kind: 'mission_status_changed',
|
|
176
|
+
payload: {
|
|
177
|
+
missionId: record.id,
|
|
178
|
+
title: record.title,
|
|
179
|
+
fromStatus: prevStatus,
|
|
180
|
+
toStatus: record.status,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const nextGoal = record.goal ?? '';
|
|
185
|
+
if (nextGoal !== prevGoal) {
|
|
186
|
+
appendLedgerEntry(meshId, {
|
|
187
|
+
kind: 'mission_goal_updated',
|
|
188
|
+
payload: {
|
|
189
|
+
missionId: record.id,
|
|
190
|
+
title: record.title,
|
|
191
|
+
prevGoalSummary: summarizeGoalForLedger(prevGoal),
|
|
192
|
+
nextGoalSummary: summarizeGoalForLedger(nextGoal),
|
|
193
|
+
prevGoalLength: prevGoal.length,
|
|
194
|
+
nextGoalLength: nextGoal.length,
|
|
195
|
+
goalTruncated: prevGoal.length > LEDGER_GOAL_SUMMARY_MAX || nextGoal.length > LEDGER_GOAL_SUMMARY_MAX,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} catch { /* audit trail is best-effort; never break the mission write */ }
|
|
115
200
|
}
|
|
116
201
|
|
|
117
202
|
export function getMeshMissions(meshId: string, statuses?: MeshMissionStatus[]): MeshMissionRecord[] {
|
|
@@ -13,9 +13,8 @@ import { createSessionDelivery, updateSessionDeliveryStatus } from './mesh-deliv
|
|
|
13
13
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
14
14
|
import { traceMeshEventDrop } from './mesh-event-trace.js';
|
|
15
15
|
import { awaitWithWarmupDeadline, resolveWarmupDeadlineOpts } from './mesh-warmup-deadline.js';
|
|
16
|
-
import { resolveDelegatedWorkerAutoApprove, resolveProviderMaxParallel, resolveNodeSchedulingPriority, normalizeMeshSchedulingStrategy, resolveMaxParallelTasks, resolveMaxReadonlyParallelTasks
|
|
16
|
+
import { resolveDelegatedWorkerAutoApprove, resolveProviderMaxParallel, resolveNodeSchedulingPriority, normalizeMeshSchedulingStrategy, resolveMaxParallelTasks, resolveMaxReadonlyParallelTasks } from '../repo-mesh-types.js';
|
|
17
17
|
import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
18
|
-
import { loadMeshJsonConfig, type MeshJsonSchedulingConfig } from '../config/mesh-json-config.js';
|
|
19
18
|
import { normalizeMeshNodeId, meshNodeIdMatches, daemonIdsEquivalent, normalizeMeshWorkspaceForCompare, meshWorkspacesEquivalent, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
20
19
|
import { findTerminalLedgerEvidenceForTask, hasUnterminalDirectDispatchLedgerEntry } from './mesh-events-stale.js';
|
|
21
20
|
import { readNonEmptyString } from './mesh-events-utils.js';
|
|
@@ -825,46 +824,15 @@ function nodeActiveLoad(meshId: string, nodeId: string): number {
|
|
|
825
824
|
}
|
|
826
825
|
|
|
827
826
|
/**
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const pickRoot = (n: any) => readNonEmptyString(n?.repoRoot) || readNonEmptyString(n?.workspace);
|
|
836
|
-
const base = nodes.find((n: any) => n?.isLocalWorktree !== true && pickRoot(n));
|
|
837
|
-
if (base) return pickRoot(base);
|
|
838
|
-
const anyNode = nodes.find((n: any) => pickRoot(n));
|
|
839
|
-
return anyNode ? pickRoot(anyNode) : '';
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* The repo-local `.adhdev/mesh.json` `policy.scheduling` overlay for this mesh, when
|
|
844
|
-
* present and valid. LOCAL-WINS: a value here overrides the stored mesh policy. Cached
|
|
845
|
-
* by mtime in the loader, so calling this on every reconcile tick is cheap.
|
|
846
|
-
*/
|
|
847
|
-
function resolveMeshSchedulingOverride(mesh: any): MeshJsonSchedulingConfig | undefined {
|
|
848
|
-
const repoRoot = resolveMeshRepoRootForScheduling(mesh);
|
|
849
|
-
if (!repoRoot) return undefined;
|
|
850
|
-
try {
|
|
851
|
-
return loadMeshJsonConfig(repoRoot).config?.scheduling;
|
|
852
|
-
} catch {
|
|
853
|
-
return undefined;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* The mesh-wide scheduling strategy. Resolution order (LOCAL-WINS):
|
|
859
|
-
* 1. `.adhdev/mesh.json` policy.scheduling.distribution (2-mode → strategy), then
|
|
860
|
-
* 2. the stored mesh policy schedulingStrategy raw 4-union (escape hatch), then
|
|
861
|
-
* 3. 'first_eligible' (strict no-change default).
|
|
862
|
-
* Only governs the final tie-break; eligibility, capacity, and priority gates apply
|
|
863
|
-
* identically to every strategy.
|
|
827
|
+
* The mesh-wide scheduling strategy, read from the MACHINE-LOCAL stored mesh
|
|
828
|
+
* policy. Resolution order:
|
|
829
|
+
* 1. the stored mesh policy schedulingStrategy raw 4-union, then
|
|
830
|
+
* 2. 'first_eligible' (strict no-change default).
|
|
831
|
+
* Policy is machine-local only — there is no repo-file (`.adhdev/mesh.json`)
|
|
832
|
+
* overlay. Only governs the final tie-break; eligibility, capacity, and priority
|
|
833
|
+
* gates apply identically to every strategy.
|
|
864
834
|
*/
|
|
865
835
|
function resolveSchedulingStrategy(mesh: any): RepoMeshSchedulingStrategy {
|
|
866
|
-
const override = resolveMeshSchedulingOverride(mesh);
|
|
867
|
-
if (override?.distribution) return distributionToStrategy(override.distribution);
|
|
868
836
|
return normalizeMeshSchedulingStrategy(mesh?.policy?.schedulingStrategy);
|
|
869
837
|
}
|
|
870
838
|
|
|
@@ -1131,19 +1099,15 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
|
|
|
1131
1099
|
const pending = queue.filter(task => task.status === 'pending');
|
|
1132
1100
|
if (!pending.length) return false;
|
|
1133
1101
|
|
|
1134
|
-
// Write cap + read-only cap resolved through the shared helpers
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
//
|
|
1138
|
-
|
|
1139
|
-
const schedulingOverride = resolveMeshSchedulingOverride(mesh);
|
|
1140
|
-
const maxParallelTasks = resolveMaxParallelTasks(
|
|
1141
|
-
schedulingOverride?.maxParallel ?? mesh?.policy?.maxParallelTasks,
|
|
1142
|
-
);
|
|
1102
|
+
// Write cap + read-only cap resolved through the shared helpers from the
|
|
1103
|
+
// MACHINE-LOCAL stored mesh policy (no repo-file overlay). These are the same
|
|
1104
|
+
// resolvers the observability projection uses, so the enforced and exposed
|
|
1105
|
+
// caps can never drift.
|
|
1106
|
+
const maxParallelTasks = resolveMaxParallelTasks(mesh?.policy?.maxParallelTasks);
|
|
1143
1107
|
// Read-only diagnoses carry no isolation/merge cost, so they are exempt from the
|
|
1144
1108
|
// write-task parallel cap. To prevent runaway auto-launch they get their own,
|
|
1145
|
-
// higher safety cap (
|
|
1146
|
-
const maxReadonlyParallelTasks = resolveMaxReadonlyParallelTasks(maxParallelTasks
|
|
1109
|
+
// higher safety cap (default 2× the write cap).
|
|
1110
|
+
const maxReadonlyParallelTasks = resolveMaxReadonlyParallelTasks(maxParallelTasks);
|
|
1147
1111
|
for (const task of pending) {
|
|
1148
1112
|
const isReadonly = isTaskReadonly(task);
|
|
1149
1113
|
if (isReadonly) {
|