@adhdev/daemon-core 0.9.82-rc.310 → 0.9.82-rc.311
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/router.d.ts +4 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +525 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +517 -54
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-coordinator.d.ts +31 -0
- package/dist/mesh/mesh-runtime-store.d.ts +18 -0
- package/dist/mesh/mesh-work-queue.d.ts +23 -0
- package/dist/providers/spec/cli-adapter.d.ts +34 -3
- package/dist/providers/spec/types.d.ts +36 -0
- package/dist/repo-mesh-types.d.ts +97 -9
- package/package.json +2 -2
- package/src/commands/chat-commands.ts +10 -2
- package/src/commands/router.ts +139 -3
- package/src/commands/stream-commands.ts +8 -0
- package/src/config/chat-history.ts +9 -0
- package/src/config/mesh-config.ts +17 -1
- package/src/index.ts +13 -2
- package/src/mesh/mesh-events-coordinator.ts +165 -9
- package/src/mesh/mesh-runtime-store.ts +52 -0
- package/src/mesh/mesh-work-queue.ts +105 -1
- package/src/providers/spec/cli-adapter.ts +155 -13
- package/src/providers/spec/fsm-driver.ts +14 -1
- package/src/providers/spec/native-history-executor.ts +114 -22
- package/src/providers/spec/types.ts +37 -0
- package/src/repo-mesh-types.ts +128 -9
package/dist/index.mjs
CHANGED
|
@@ -26,6 +26,18 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
28
|
// src/repo-mesh-types.ts
|
|
29
|
+
function normalizeMeshSchedulingStrategy(value) {
|
|
30
|
+
if (typeof value !== "string") return DEFAULT_MESH_SCHEDULING_STRATEGY;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
return MESH_SCHEDULING_STRATEGIES.includes(trimmed) ? trimmed : DEFAULT_MESH_SCHEDULING_STRATEGY;
|
|
33
|
+
}
|
|
34
|
+
function resolveNodeSchedulingPriority(nodePolicy) {
|
|
35
|
+
const raw = Number(nodePolicy?.schedulingPriority);
|
|
36
|
+
return Number.isFinite(raw) ? raw : 0;
|
|
37
|
+
}
|
|
38
|
+
function resolveAutoConvergeCodeChange(policy) {
|
|
39
|
+
return policy?.autoConvergeCodeChange === true;
|
|
40
|
+
}
|
|
29
41
|
function resolveDelegatedWorkerAutoApprove(meshPolicy, nodePolicy) {
|
|
30
42
|
if (typeof nodePolicy?.delegatedWorkerAutoApprove === "boolean") {
|
|
31
43
|
return nodePolicy.delegatedWorkerAutoApprove;
|
|
@@ -53,10 +65,19 @@ function resolveProviderMaxParallel(nodePolicy, providerType) {
|
|
|
53
65
|
if (!Number.isFinite(raw) || raw < 0) return void 0;
|
|
54
66
|
return Math.floor(raw);
|
|
55
67
|
}
|
|
56
|
-
var DEFAULT_MESH_POLICY;
|
|
68
|
+
var MESH_SCHEDULING_STRATEGIES, DEFAULT_MESH_SCHEDULING_STRATEGY, MESH_CONVERGE_REFINE_TAG, MESH_CONVERGE_FAST_FORWARD_TAG, DEFAULT_MESH_POLICY;
|
|
57
69
|
var init_repo_mesh_types = __esm({
|
|
58
70
|
"src/repo-mesh-types.ts"() {
|
|
59
71
|
"use strict";
|
|
72
|
+
MESH_SCHEDULING_STRATEGIES = [
|
|
73
|
+
"first_eligible",
|
|
74
|
+
"least_loaded",
|
|
75
|
+
"round_robin",
|
|
76
|
+
"priority_only"
|
|
77
|
+
];
|
|
78
|
+
DEFAULT_MESH_SCHEDULING_STRATEGY = "first_eligible";
|
|
79
|
+
MESH_CONVERGE_REFINE_TAG = "converge=refine";
|
|
80
|
+
MESH_CONVERGE_FAST_FORWARD_TAG = "converge=fast_forward";
|
|
60
81
|
DEFAULT_MESH_POLICY = {
|
|
61
82
|
requirePreTaskCheckpoint: false,
|
|
62
83
|
requirePostTaskCheckpoint: true,
|
|
@@ -290,10 +311,10 @@ function readInjected(value) {
|
|
|
290
311
|
}
|
|
291
312
|
function getDaemonBuildInfo() {
|
|
292
313
|
if (cached) return cached;
|
|
293
|
-
const commit = readInjected(true ? "
|
|
294
|
-
const commitShort = readInjected(true ? "
|
|
295
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
296
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
314
|
+
const commit = readInjected(true ? "3902f3d63eabe7b9e34d0e849dc0fed2fd66c026" : void 0) ?? "unknown";
|
|
315
|
+
const commitShort = readInjected(true ? "3902f3d6" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
316
|
+
const version = readInjected(true ? "0.9.82-rc.311" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
317
|
+
const builtAt = readInjected(true ? "2026-06-17T12:17:11.243Z" : void 0);
|
|
297
318
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
298
319
|
return cached;
|
|
299
320
|
}
|
|
@@ -1543,6 +1564,17 @@ function mergeMeshPolicy(base, patch) {
|
|
|
1543
1564
|
if (!SPAWNED_SESSION_VISIBILITY_MODES.has(String(policy.spawnedSessionVisibility))) {
|
|
1544
1565
|
policy.spawnedSessionVisibility = "visible";
|
|
1545
1566
|
}
|
|
1567
|
+
const normalizedStrategy = normalizeMeshSchedulingStrategy(policy.schedulingStrategy);
|
|
1568
|
+
if (normalizedStrategy === "first_eligible") {
|
|
1569
|
+
delete policy.schedulingStrategy;
|
|
1570
|
+
} else {
|
|
1571
|
+
policy.schedulingStrategy = normalizedStrategy;
|
|
1572
|
+
}
|
|
1573
|
+
if (policy.autoConvergeCodeChange === true) {
|
|
1574
|
+
policy.autoConvergeCodeChange = true;
|
|
1575
|
+
} else {
|
|
1576
|
+
delete policy.autoConvergeCodeChange;
|
|
1577
|
+
}
|
|
1546
1578
|
return policy;
|
|
1547
1579
|
}
|
|
1548
1580
|
function normalizeAutoFastForwardPolicy(value) {
|
|
@@ -2833,6 +2865,7 @@ __export(mesh_work_queue_exports, {
|
|
|
2833
2865
|
recordMeshToolCall: () => recordMeshToolCall,
|
|
2834
2866
|
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
2835
2867
|
requeueTask: () => requeueTask,
|
|
2868
|
+
resolveConvergeRequiredTags: () => resolveConvergeRequiredTags,
|
|
2836
2869
|
updateDirectDispatchStatus: () => updateDirectDispatchStatus,
|
|
2837
2870
|
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
2838
2871
|
updateTaskStatus: () => updateTaskStatus,
|
|
@@ -2907,6 +2940,21 @@ function firstProviderPriority(policy) {
|
|
|
2907
2940
|
if (!Array.isArray(raw)) return void 0;
|
|
2908
2941
|
return raw.find((type) => typeof type === "string" && type.trim())?.trim();
|
|
2909
2942
|
}
|
|
2943
|
+
function roleCapabilityTags(policy, providerType) {
|
|
2944
|
+
const roles = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerRoles : void 0;
|
|
2945
|
+
if (!Array.isArray(roles)) return [];
|
|
2946
|
+
const wantedProvider = typeof providerType === "string" && providerType.trim() ? providerType.trim().toLowerCase() : "";
|
|
2947
|
+
const out = [];
|
|
2948
|
+
for (const entry of roles) {
|
|
2949
|
+
if (!entry || typeof entry !== "object") continue;
|
|
2950
|
+
const type = typeof entry.providerType === "string" ? entry.providerType.trim().toLowerCase() : "";
|
|
2951
|
+
const role = typeof entry.role === "string" ? entry.role.trim().toLowerCase() : "";
|
|
2952
|
+
if (!role) continue;
|
|
2953
|
+
if (wantedProvider && type && type !== wantedProvider) continue;
|
|
2954
|
+
out.push(`role=${role}`);
|
|
2955
|
+
}
|
|
2956
|
+
return out;
|
|
2957
|
+
}
|
|
2910
2958
|
function buildMeshNodeCapabilityTags(node, providerType) {
|
|
2911
2959
|
const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
|
|
2912
2960
|
const worktreeBranch = typeof node?.worktreeBranch === "string" && node.worktreeBranch.trim() ? node.worktreeBranch.trim() : null;
|
|
@@ -2918,7 +2966,25 @@ function buildMeshNodeCapabilityTags(node, providerType) {
|
|
|
2918
2966
|
// Worktree nodes automatically expose a "worktree=<branch>" tag so that
|
|
2919
2967
|
// mesh_enqueue_task with required_tags: ["worktree=<branch>"] routes
|
|
2920
2968
|
// only to the matching worktree node.
|
|
2921
|
-
...node?.isLocalWorktree === true && worktreeBranch ? [`worktree=${worktreeBranch}`] : []
|
|
2969
|
+
...node?.isLocalWorktree === true && worktreeBranch ? [`worktree=${worktreeBranch}`] : [],
|
|
2970
|
+
// Convergence routing: advertise how this node can land its work onto base.
|
|
2971
|
+
// - converge=refine: local worktree nodes (on ANY machine — refine_mesh_node
|
|
2972
|
+
// now forwards to the owning daemon) can run the Refinery merge → push →
|
|
2973
|
+
// cleanup against their own checkout, so they accept code_change tasks.
|
|
2974
|
+
// - converge=fast_forward: non-worktree nodes (the machine itself) can only
|
|
2975
|
+
// ff/push an already-converged branch; they are NOT a destination for
|
|
2976
|
+
// code_change work (a worktree is created first, and that worktree node
|
|
2977
|
+
// receives the task instead). Reuses the ordinary required-tags filter —
|
|
2978
|
+
// the load-balancing scheduler auto-injects converge=refine for code_change
|
|
2979
|
+
// so such work is hard-filtered onto refine-capable nodes.
|
|
2980
|
+
...node?.isLocalWorktree === true ? ["converge=refine"] : ["converge=fast_forward"],
|
|
2981
|
+
// Role-based routing: advertise role=<x> for each (node, provider) role
|
|
2982
|
+
// declared in policy.providerRoles. Narrowed to the selected provider when
|
|
2983
|
+
// one is given so the chosen provider must match a task's required role;
|
|
2984
|
+
// when no provider is selected, all declared roles are advertised for the
|
|
2985
|
+
// node-level eligibility scan. Reuses the ordinary required-tags filter —
|
|
2986
|
+
// no separate role field/gate.
|
|
2987
|
+
...roleCapabilityTags(node?.policy, providerType)
|
|
2922
2988
|
]);
|
|
2923
2989
|
}
|
|
2924
2990
|
function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
|
|
@@ -2927,6 +2993,18 @@ function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
|
|
|
2927
2993
|
const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
|
|
2928
2994
|
return required.every((tag) => available.has(tag));
|
|
2929
2995
|
}
|
|
2996
|
+
function resolveConvergeRequiredTags(meshId, taskMode, explicitRequiredTags, opts) {
|
|
2997
|
+
if (taskMode !== "code_change") return explicitRequiredTags;
|
|
2998
|
+
if (typeof opts?.targetNodeId === "string" && opts.targetNodeId.trim()) return explicitRequiredTags;
|
|
2999
|
+
let optedIn = false;
|
|
3000
|
+
try {
|
|
3001
|
+
optedIn = resolveAutoConvergeCodeChange(getMesh(meshId)?.policy);
|
|
3002
|
+
} catch {
|
|
3003
|
+
optedIn = false;
|
|
3004
|
+
}
|
|
3005
|
+
if (!optedIn) return explicitRequiredTags;
|
|
3006
|
+
return normalizeMeshCapabilityTags([...explicitRequiredTags, MESH_CONVERGE_REFINE_TAG]);
|
|
3007
|
+
}
|
|
2930
3008
|
function withQueueLock(_meshId, fn) {
|
|
2931
3009
|
return MeshRuntimeStore.getInstance().transaction(fn);
|
|
2932
3010
|
}
|
|
@@ -2985,7 +3063,15 @@ function enqueueTask(meshId, message, opts) {
|
|
|
2985
3063
|
taskMode: modeValidation.taskMode,
|
|
2986
3064
|
targetNodeId: opts?.targetNodeId,
|
|
2987
3065
|
targetSessionId: opts?.targetSessionId,
|
|
2988
|
-
|
|
3066
|
+
// Convergence routing (opt-in): auto-inject converge=refine for code_change
|
|
3067
|
+
// tasks so they hard-filter onto refine-capable worktree nodes. No-op unless
|
|
3068
|
+
// the mesh opts in; explicit target_node_id / required_tags are preserved.
|
|
3069
|
+
requiredTags: resolveConvergeRequiredTags(
|
|
3070
|
+
meshId,
|
|
3071
|
+
modeValidation.taskMode,
|
|
3072
|
+
normalizeMeshCapabilityTags(opts?.requiredTags),
|
|
3073
|
+
{ targetNodeId: opts?.targetNodeId }
|
|
3074
|
+
),
|
|
2989
3075
|
...dependsOn.length > 0 ? { dependsOn } : {},
|
|
2990
3076
|
...typeof opts?.missionId === "string" && opts.missionId.trim() ? { missionId: opts.missionId.trim() } : {},
|
|
2991
3077
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3245,6 +3331,7 @@ var init_mesh_work_queue = __esm({
|
|
|
3245
3331
|
"src/mesh/mesh-work-queue.ts"() {
|
|
3246
3332
|
"use strict";
|
|
3247
3333
|
init_mesh_host_ownership();
|
|
3334
|
+
init_repo_mesh_types();
|
|
3248
3335
|
init_mesh_runtime_store();
|
|
3249
3336
|
init_mesh_config();
|
|
3250
3337
|
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
@@ -3538,6 +3625,17 @@ var init_mesh_runtime_store = __esm({
|
|
|
3538
3625
|
|
|
3539
3626
|
CREATE INDEX IF NOT EXISTS idx_mesh_missions_mesh_status
|
|
3540
3627
|
ON mesh_missions(mesh_id, status, updated_at);
|
|
3628
|
+
|
|
3629
|
+
-- Load-balancing scheduler: per-mesh round-robin rotation cursor. When
|
|
3630
|
+
-- the schedulingStrategy is 'round_robin', several eligible nodes tied at
|
|
3631
|
+
-- the least load are rotated by this cursor so the tie-break winner cycles
|
|
3632
|
+
-- across scheduling passes instead of always favouring the same array-order
|
|
3633
|
+
-- node. Persisted (not a module Map) so rotation survives daemon restarts
|
|
3634
|
+
-- and stays a single source of truth across scheduling entry points.
|
|
3635
|
+
CREATE TABLE IF NOT EXISTS mesh_scheduler_cursor (
|
|
3636
|
+
mesh_id TEXT PRIMARY KEY,
|
|
3637
|
+
cursor INTEGER NOT NULL DEFAULT 0
|
|
3638
|
+
);
|
|
3541
3639
|
`);
|
|
3542
3640
|
}
|
|
3543
3641
|
hasCompletionFingerprint(fingerprint) {
|
|
@@ -3715,6 +3813,44 @@ var init_mesh_runtime_store = __esm({
|
|
|
3715
3813
|
`).get(meshId, nodeId);
|
|
3716
3814
|
return row !== void 0;
|
|
3717
3815
|
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Count active (status='assigned') tasks on a node, regardless of provider or
|
|
3818
|
+
* task mode. This is the load metric for least-loaded / round-robin ranking:
|
|
3819
|
+
* the scheduler prefers the node with the fewest active assignments so
|
|
3820
|
+
* untargeted work spreads instead of piling onto whichever node asks first.
|
|
3821
|
+
*/
|
|
3822
|
+
nodeActiveAssignmentCount(meshId, nodeId) {
|
|
3823
|
+
const row = this.db.prepare(`
|
|
3824
|
+
SELECT COUNT(*) as count FROM mesh_queue
|
|
3825
|
+
WHERE mesh_id = ? AND status = 'assigned' AND assigned_node_id = ?
|
|
3826
|
+
`).get(meshId, nodeId);
|
|
3827
|
+
return row?.count ?? 0;
|
|
3828
|
+
}
|
|
3829
|
+
/**
|
|
3830
|
+
* Read the current per-mesh round-robin cursor (0 when unset). Used to rotate
|
|
3831
|
+
* the tie-break winner among nodes tied at the least load.
|
|
3832
|
+
*/
|
|
3833
|
+
getSchedulerCursor(meshId) {
|
|
3834
|
+
const row = this.db.prepare(
|
|
3835
|
+
"SELECT cursor FROM mesh_scheduler_cursor WHERE mesh_id = ?"
|
|
3836
|
+
).get(meshId);
|
|
3837
|
+
return row?.cursor ?? 0;
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Atomically advance the per-mesh round-robin cursor by one and return the
|
|
3841
|
+
* value that was current BEFORE the bump (the value the caller should rotate
|
|
3842
|
+
* by for this pass). UPSERT keeps it lock-free across concurrent passes.
|
|
3843
|
+
*/
|
|
3844
|
+
bumpSchedulerCursor(meshId) {
|
|
3845
|
+
return this.transaction(() => {
|
|
3846
|
+
const current = this.getSchedulerCursor(meshId);
|
|
3847
|
+
this.db.prepare(`
|
|
3848
|
+
INSERT INTO mesh_scheduler_cursor (mesh_id, cursor) VALUES (?, ?)
|
|
3849
|
+
ON CONFLICT(mesh_id) DO UPDATE SET cursor = excluded.cursor
|
|
3850
|
+
`).run(meshId, current + 1);
|
|
3851
|
+
return current;
|
|
3852
|
+
});
|
|
3853
|
+
}
|
|
3718
3854
|
/**
|
|
3719
3855
|
* Count active (status='assigned') tasks on a (node, provider) combination,
|
|
3720
3856
|
* matched by the assignedProviderType stamped on the payload at claim time.
|
|
@@ -8286,6 +8422,33 @@ function activeReadonlyAssignedCount(meshId) {
|
|
|
8286
8422
|
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
8287
8423
|
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
8288
8424
|
}
|
|
8425
|
+
function nodeActiveLoad(meshId, nodeId) {
|
|
8426
|
+
return MeshRuntimeStore.getInstance().nodeActiveAssignmentCount(meshId, nodeId);
|
|
8427
|
+
}
|
|
8428
|
+
function resolveSchedulingStrategy(mesh) {
|
|
8429
|
+
return normalizeMeshSchedulingStrategy(mesh?.policy?.schedulingStrategy);
|
|
8430
|
+
}
|
|
8431
|
+
function orderEligibleNodes(meshId, strategy, nodes, opts) {
|
|
8432
|
+
if (strategy === "first_eligible" || nodes.length <= 1) {
|
|
8433
|
+
return nodes;
|
|
8434
|
+
}
|
|
8435
|
+
const priorityOf = (n) => resolveNodeSchedulingPriority(n.node?.policy);
|
|
8436
|
+
let rotation = 0;
|
|
8437
|
+
if (strategy === "round_robin") {
|
|
8438
|
+
const cursor = opts?.bumpCursor ? MeshRuntimeStore.getInstance().bumpSchedulerCursor(meshId) : MeshRuntimeStore.getInstance().getSchedulerCursor(meshId);
|
|
8439
|
+
rotation = (cursor % nodes.length + nodes.length) % nodes.length;
|
|
8440
|
+
}
|
|
8441
|
+
const rotationRank = (index) => (index - rotation + nodes.length) % nodes.length;
|
|
8442
|
+
return [...nodes].sort((a, b) => {
|
|
8443
|
+
const prioDelta = priorityOf(b) - priorityOf(a);
|
|
8444
|
+
if (prioDelta !== 0) return prioDelta;
|
|
8445
|
+
if (strategy === "least_loaded" || strategy === "round_robin") {
|
|
8446
|
+
const loadDelta = nodeActiveLoad(meshId, a.nodeId) - nodeActiveLoad(meshId, b.nodeId);
|
|
8447
|
+
if (loadDelta !== 0) return loadDelta;
|
|
8448
|
+
}
|
|
8449
|
+
return rotationRank(a.index) - rotationRank(b.index);
|
|
8450
|
+
});
|
|
8451
|
+
}
|
|
8289
8452
|
function activeProviderAssignedCount(meshId, nodeId, providerType) {
|
|
8290
8453
|
return getQueue(meshId, { status: ["assigned"] }).filter((task) => task.assignedNodeId === nodeId && task.assignedProviderType === providerType).length;
|
|
8291
8454
|
}
|
|
@@ -8422,7 +8585,14 @@ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
|
8422
8585
|
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_node_satisfies_required_tags", nodeId: task.targetNodeId });
|
|
8423
8586
|
continue;
|
|
8424
8587
|
}
|
|
8425
|
-
|
|
8588
|
+
const strategy = resolveSchedulingStrategy(mesh);
|
|
8589
|
+
const orderedCandidateNodes = strategy === "first_eligible" ? candidateNodes : orderEligibleNodes(
|
|
8590
|
+
meshId,
|
|
8591
|
+
strategy,
|
|
8592
|
+
candidateNodes.map((node, index) => ({ nodeId: readMeshNodeId(node), node, index })).filter((c) => c.nodeId),
|
|
8593
|
+
{ bumpCursor: true }
|
|
8594
|
+
).map((c) => c.node);
|
|
8595
|
+
for (const node of orderedCandidateNodes) {
|
|
8426
8596
|
const nodeId = readMeshNodeId(node);
|
|
8427
8597
|
if (!nodeId) continue;
|
|
8428
8598
|
const launchKey = `${meshId}:${nodeId}`;
|
|
@@ -8549,6 +8719,8 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
8549
8719
|
noIdleMeshSessionAvailable: true
|
|
8550
8720
|
};
|
|
8551
8721
|
}
|
|
8722
|
+
const strategy = resolveSchedulingStrategy(mesh);
|
|
8723
|
+
const localCandidates = [];
|
|
8552
8724
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
8553
8725
|
for (const inst of cliInstances) {
|
|
8554
8726
|
const state = inst.getState();
|
|
@@ -8571,7 +8743,7 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
8571
8743
|
const providerType = state.type || readNonEmptyString2(settings.providerType);
|
|
8572
8744
|
if (providerType) {
|
|
8573
8745
|
localIdleSessionsChecked += 1;
|
|
8574
|
-
|
|
8746
|
+
localCandidates.push({ nodeId, sessionId, providerType, origin: "local", node: mesh.nodes.find((n) => readMeshNodeId(n) === nodeId) });
|
|
8575
8747
|
} else {
|
|
8576
8748
|
skippedSessions.push({
|
|
8577
8749
|
nodeId,
|
|
@@ -8585,18 +8757,49 @@ async function triggerMeshQueue(components, meshId) {
|
|
|
8585
8757
|
remoteSessions = MeshRuntimeStore.getInstance().getRemoteIdleSessions();
|
|
8586
8758
|
} catch {
|
|
8587
8759
|
}
|
|
8760
|
+
const remoteCandidates = [];
|
|
8588
8761
|
for (const idle of remoteSessions) {
|
|
8589
8762
|
const node = mesh.nodes.find((n) => n.id === idle.nodeId);
|
|
8590
8763
|
if (node) {
|
|
8591
8764
|
remoteIdleSessionsChecked += 1;
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8765
|
+
remoteCandidates.push({ nodeId: idle.nodeId, sessionId: idle.sessionId, providerType: idle.providerType, origin: "remote", node });
|
|
8766
|
+
}
|
|
8767
|
+
}
|
|
8768
|
+
const assignIdleCandidate = (candidate) => {
|
|
8769
|
+
const assigned = tryAssignQueueTask(components, meshId, candidate.nodeId, candidate.sessionId, candidate.providerType);
|
|
8770
|
+
if (assigned && candidate.origin === "remote") {
|
|
8771
|
+
try {
|
|
8772
|
+
MeshRuntimeStore.getInstance().deleteRemoteIdleSession(candidate.nodeId, candidate.sessionId);
|
|
8773
|
+
} catch {
|
|
8598
8774
|
}
|
|
8599
8775
|
}
|
|
8776
|
+
};
|
|
8777
|
+
if (strategy === "first_eligible") {
|
|
8778
|
+
for (const candidate of localCandidates) assignIdleCandidate(candidate);
|
|
8779
|
+
for (const candidate of remoteCandidates) assignIdleCandidate(candidate);
|
|
8780
|
+
} else {
|
|
8781
|
+
const pool = [...localCandidates, ...remoteCandidates];
|
|
8782
|
+
const baseIndex = /* @__PURE__ */ new Map();
|
|
8783
|
+
pool.forEach((c, i) => {
|
|
8784
|
+
if (!baseIndex.has(c.nodeId)) baseIndex.set(c.nodeId, i);
|
|
8785
|
+
});
|
|
8786
|
+
const uniqueNodes = [...new Set(pool.map((c) => c.nodeId))].map((nodeId, index) => ({ nodeId, node: pool.find((c) => c.nodeId === nodeId)?.node, index }));
|
|
8787
|
+
const ranked = orderEligibleNodes(meshId, strategy, uniqueNodes, { bumpCursor: true });
|
|
8788
|
+
const rankIndex = new Map(ranked.map((r, i) => [r.nodeId, i]));
|
|
8789
|
+
const remaining = [...pool];
|
|
8790
|
+
while (remaining.length > 0) {
|
|
8791
|
+
remaining.sort((a, b) => {
|
|
8792
|
+
const aPrio = resolveNodeSchedulingPriority(a.node?.policy);
|
|
8793
|
+
const bPrio = resolveNodeSchedulingPriority(b.node?.policy);
|
|
8794
|
+
if (aPrio !== bPrio) return bPrio - aPrio;
|
|
8795
|
+
if (strategy === "least_loaded" || strategy === "round_robin") {
|
|
8796
|
+
const loadDelta = nodeActiveLoad(meshId, a.nodeId) - nodeActiveLoad(meshId, b.nodeId);
|
|
8797
|
+
if (loadDelta !== 0) return loadDelta;
|
|
8798
|
+
}
|
|
8799
|
+
return (rankIndex.get(a.nodeId) ?? 0) - (rankIndex.get(b.nodeId) ?? 0);
|
|
8800
|
+
});
|
|
8801
|
+
assignIdleCandidate(remaining.shift());
|
|
8802
|
+
}
|
|
8600
8803
|
}
|
|
8601
8804
|
autoLaunchStarted = await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
8602
8805
|
const afterQueue = getQueue(meshId);
|
|
@@ -11972,7 +12175,7 @@ var init_cli_state_engine = __esm({
|
|
|
11972
12175
|
scheduleSettle() {
|
|
11973
12176
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
11974
12177
|
const epoch = this.responseEpoch;
|
|
11975
|
-
const
|
|
12178
|
+
const delay2 = Math.max(
|
|
11976
12179
|
this.timeouts.outputSettle,
|
|
11977
12180
|
this.submitPendingUntil > Date.now() ? this.submitPendingUntil - Date.now() + this.timeouts.outputSettle : 0
|
|
11978
12181
|
);
|
|
@@ -11980,7 +12183,7 @@ var init_cli_state_engine = __esm({
|
|
|
11980
12183
|
this.settleTimer = null;
|
|
11981
12184
|
if (epoch !== this.responseEpoch) return;
|
|
11982
12185
|
this.evaluateSettled(this.transport.getSnapshot());
|
|
11983
|
-
},
|
|
12186
|
+
}, delay2);
|
|
11984
12187
|
}
|
|
11985
12188
|
/** Called from sendMessage in transport once a turn scope is established. */
|
|
11986
12189
|
onTurnStarted(turnScope) {
|
|
@@ -17795,9 +17998,9 @@ function readString6(value) {
|
|
|
17795
17998
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
17796
17999
|
}
|
|
17797
18000
|
function summarizeMessage(message) {
|
|
17798
|
-
const
|
|
17799
|
-
const title =
|
|
17800
|
-
return { title: title || "(untitled task)", summary:
|
|
18001
|
+
const oneLine2 = message.replace(/\s+/g, " ").trim();
|
|
18002
|
+
const title = oneLine2.length > 96 ? `${oneLine2.slice(0, 93)}...` : oneLine2;
|
|
18003
|
+
return { title: title || "(untitled task)", summary: oneLine2 };
|
|
17801
18004
|
}
|
|
17802
18005
|
function elapsedSince(value, now) {
|
|
17803
18006
|
const started = value ? new Date(value).getTime() : Number.NaN;
|
|
@@ -20903,6 +21106,11 @@ function collapseReplayAssistantTurns(messages, historyBehavior) {
|
|
|
20903
21106
|
continue;
|
|
20904
21107
|
}
|
|
20905
21108
|
if (message.role === "assistant") {
|
|
21109
|
+
const isActivity = message.kind === "tool" || message.kind === "terminal" || message.kind === "thought";
|
|
21110
|
+
if (isActivity) {
|
|
21111
|
+
collapsed.push(message);
|
|
21112
|
+
continue;
|
|
21113
|
+
}
|
|
20906
21114
|
if (sawAssistantSinceLastUser) continue;
|
|
20907
21115
|
sawAssistantSinceLastUser = true;
|
|
20908
21116
|
collapsed.push(message);
|
|
@@ -26046,7 +26254,8 @@ function buildReadChatCommandResult(payload, args, h) {
|
|
|
26046
26254
|
const sessionIdHint = typeof args?.targetSessionId === "string" ? args.targetSessionId : typeof args?.sessionId === "string" ? args.sessionId : "";
|
|
26047
26255
|
const providerHint = typeof args?.cliType === "string" ? args.cliType : typeof args?.providerType === "string" ? args.providerType : typeof args?.agentType === "string" ? args.agentType : "";
|
|
26048
26256
|
const filteredMessages = h ? maybeHideCoordinatorPromptMessage(h, providerHint, sessionIdHint, messages) : messages;
|
|
26049
|
-
const
|
|
26257
|
+
const includeActivity = args?.includeActivity === true || args?.includeActivity === "true";
|
|
26258
|
+
const visibleMessages = includeActivity ? filteredMessages.filter((m) => isUserFacingChatMessage(m) || isActivityChatMessage(m)) : filterUserFacingChatMessages(filteredMessages);
|
|
26050
26259
|
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
26051
26260
|
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
26052
26261
|
const preservedPayloadFields = Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key)));
|
|
@@ -28415,6 +28624,9 @@ function normalizeProviderScriptArgs(args, scriptName) {
|
|
|
28415
28624
|
}
|
|
28416
28625
|
function buildControlScriptResult(scriptName, payload) {
|
|
28417
28626
|
if (!payload || typeof payload !== "object") return {};
|
|
28627
|
+
if (payload.controlResult && typeof payload.controlResult === "object") {
|
|
28628
|
+
return { controlResult: payload.controlResult };
|
|
28629
|
+
}
|
|
28418
28630
|
const legacyListPayload = (() => {
|
|
28419
28631
|
if (Array.isArray(payload.options)) return payload;
|
|
28420
28632
|
if (/^listmodels$/i.test(scriptName) && Array.isArray(payload.models)) {
|
|
@@ -30265,12 +30477,12 @@ var FsmDriver = class {
|
|
|
30265
30477
|
scheduleSpawnPrime() {
|
|
30266
30478
|
const seqs = this.spec.send_on_spawn;
|
|
30267
30479
|
if (!Array.isArray(seqs) || seqs.length === 0) return;
|
|
30268
|
-
const
|
|
30480
|
+
const delay2 = Math.max(0, this.spec.send_on_spawn_delay_ms ?? 250);
|
|
30269
30481
|
setTimeout(() => {
|
|
30270
30482
|
for (const seq of seqs) {
|
|
30271
30483
|
if (typeof seq === "string" && seq.length > 0) this.adapter.send_keys(seq);
|
|
30272
30484
|
}
|
|
30273
|
-
},
|
|
30485
|
+
}, delay2);
|
|
30274
30486
|
}
|
|
30275
30487
|
dispatch(cmd) {
|
|
30276
30488
|
switch (cmd.kind) {
|
|
@@ -30636,11 +30848,11 @@ var FsmDriver = class {
|
|
|
30636
30848
|
const armed = this.delegateTimers.has(d.id);
|
|
30637
30849
|
const shouldFire = d.when_state === currentStateId;
|
|
30638
30850
|
if (shouldFire && !armed) {
|
|
30639
|
-
const
|
|
30851
|
+
const delay2 = d.after_duration_ms ?? 0;
|
|
30640
30852
|
const t = setTimeout(() => {
|
|
30641
30853
|
this.fireDelegate(d);
|
|
30642
30854
|
this.delegateTimers.delete(d.id);
|
|
30643
|
-
},
|
|
30855
|
+
}, delay2);
|
|
30644
30856
|
this.delegateTimers.set(d.id, t);
|
|
30645
30857
|
} else if (!shouldFire && armed) {
|
|
30646
30858
|
clearTimeout(this.delegateTimers.get(d.id));
|
|
@@ -30694,7 +30906,15 @@ var FsmDriver = class {
|
|
|
30694
30906
|
this.adapter.send_keys(a.keys);
|
|
30695
30907
|
return;
|
|
30696
30908
|
case "open_picker":
|
|
30697
|
-
|
|
30909
|
+
{
|
|
30910
|
+
const m = /^([\s\S]*?)([\r\n]+)$/.exec(a.trigger_keys);
|
|
30911
|
+
if (m && m[1]) {
|
|
30912
|
+
this.adapter.send_keys(m[1]);
|
|
30913
|
+
setTimeout(() => this.adapter.send_keys(m[2]), 200);
|
|
30914
|
+
} else {
|
|
30915
|
+
this.adapter.send_keys(a.trigger_keys);
|
|
30916
|
+
}
|
|
30917
|
+
}
|
|
30698
30918
|
this.pickerInProgress = { control_id: ctl.id, spec: ctl };
|
|
30699
30919
|
return;
|
|
30700
30920
|
case "attach_image": {
|
|
@@ -30879,8 +31099,7 @@ function executeJsonl(src, input) {
|
|
|
30879
31099
|
for (let i = 0; i < lines.length; i += 1) {
|
|
30880
31100
|
const rec = lines[i];
|
|
30881
31101
|
if (filter && !filter(rec)) continue;
|
|
30882
|
-
const msg
|
|
30883
|
-
if (msg) {
|
|
31102
|
+
for (const msg of projectMessages(rec, src.message_map, i, lines.length, mtime)) {
|
|
30884
31103
|
if (transcriptWorkspace) msg.workspace = transcriptWorkspace;
|
|
30885
31104
|
messages.push(msg);
|
|
30886
31105
|
}
|
|
@@ -30960,8 +31179,9 @@ function executeSqlite(src, input) {
|
|
|
30960
31179
|
const mtime = safeMtimeMs(resolved);
|
|
30961
31180
|
const messages = [];
|
|
30962
31181
|
for (let i = 0; i < messageRows.length; i += 1) {
|
|
30963
|
-
const msg
|
|
30964
|
-
|
|
31182
|
+
for (const msg of projectMessages(messageRows[i], src.message_map, i, messageRows.length, mtime)) {
|
|
31183
|
+
messages.push(msg);
|
|
31184
|
+
}
|
|
30965
31185
|
}
|
|
30966
31186
|
if (messages.length === 0) return null;
|
|
30967
31187
|
return {
|
|
@@ -31359,11 +31579,42 @@ function jsonPathGet(record, expr) {
|
|
|
31359
31579
|
}
|
|
31360
31580
|
return cur;
|
|
31361
31581
|
}
|
|
31362
|
-
function
|
|
31582
|
+
function projectMessages(record, map, index, total, sourceMtimeMs) {
|
|
31363
31583
|
const roleRaw = jsonPathGet(record, map.role);
|
|
31364
|
-
const contentRaw = jsonPathGet(record, map.content);
|
|
31365
31584
|
const role = normalizeRole(roleRaw);
|
|
31366
|
-
let
|
|
31585
|
+
let receivedAt = sourceMtimeMs - (total - 1 - index) * 1e3;
|
|
31586
|
+
if (map.timestamp_ms) {
|
|
31587
|
+
const tsRaw = jsonPathGet(record, map.timestamp_ms);
|
|
31588
|
+
const parsed = parseTimestamp(tsRaw);
|
|
31589
|
+
if (parsed != null) receivedAt = parsed;
|
|
31590
|
+
}
|
|
31591
|
+
const kindRaw = map.kind ? jsonPathGet(record, map.kind) : void 0;
|
|
31592
|
+
const kind = typeof kindRaw === "string" && kindRaw ? kindRaw : "standard";
|
|
31593
|
+
const out = [];
|
|
31594
|
+
if (map.tools) {
|
|
31595
|
+
const recordTool = projectToolBlock(record, role, map.tools);
|
|
31596
|
+
if (recordTool) {
|
|
31597
|
+
out.push({ ...recordTool, receivedAt });
|
|
31598
|
+
return out;
|
|
31599
|
+
}
|
|
31600
|
+
}
|
|
31601
|
+
const contentRaw = jsonPathGet(record, map.content);
|
|
31602
|
+
const content = cleanContent(stringifyContent(contentRaw), map);
|
|
31603
|
+
if (content) out.push({ role, content, receivedAt, kind });
|
|
31604
|
+
if (map.tools && Array.isArray(contentRaw)) {
|
|
31605
|
+
let nudge = 1;
|
|
31606
|
+
for (const block2 of contentRaw) {
|
|
31607
|
+
const tool = projectToolBlock(block2, role, map.tools);
|
|
31608
|
+
if (tool) {
|
|
31609
|
+
out.push({ ...tool, receivedAt: receivedAt + nudge });
|
|
31610
|
+
nudge += 1;
|
|
31611
|
+
}
|
|
31612
|
+
}
|
|
31613
|
+
}
|
|
31614
|
+
return out;
|
|
31615
|
+
}
|
|
31616
|
+
function cleanContent(input, map) {
|
|
31617
|
+
let content = input;
|
|
31367
31618
|
if (content && map.content_strip) {
|
|
31368
31619
|
for (const tag of map.content_strip) {
|
|
31369
31620
|
const safeTag = tag.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
@@ -31379,17 +31630,33 @@ function projectMessage(record, map, index, total, sourceMtimeMs) {
|
|
|
31379
31630
|
content = content.replace(open, "").replace(close, "");
|
|
31380
31631
|
}
|
|
31381
31632
|
}
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
|
|
31385
|
-
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31633
|
+
return content ? content.trim() : "";
|
|
31634
|
+
}
|
|
31635
|
+
var DEFAULT_TOOL_CALL_TYPES = ["tool_use", "function_call", "custom_tool_call"];
|
|
31636
|
+
var DEFAULT_TOOL_RESULT_TYPES = ["tool_result", "function_call_output", "custom_tool_call_output"];
|
|
31637
|
+
function projectToolBlock(block2, role, tmap) {
|
|
31638
|
+
void role;
|
|
31639
|
+
if (block2 == null || typeof block2 !== "object") return null;
|
|
31640
|
+
const typeVal = String(jsonPathGet(block2, tmap.block_type || "$.type") ?? "");
|
|
31641
|
+
if (!typeVal) return null;
|
|
31642
|
+
const callTypes = tmap.call_types ?? DEFAULT_TOOL_CALL_TYPES;
|
|
31643
|
+
const resultTypes = tmap.result_types ?? DEFAULT_TOOL_RESULT_TYPES;
|
|
31644
|
+
if (callTypes.includes(typeVal)) {
|
|
31645
|
+
const name = String(jsonPathGet(block2, tmap.call_name || "$.name") ?? "tool").trim() || "tool";
|
|
31646
|
+
const args = oneLine(stringifyContent(jsonPathGet(block2, tmap.call_args || "$.input")), 240);
|
|
31647
|
+
const content = args ? `\u2197 ${name}: ${args}` : `\u2197 ${name}`;
|
|
31648
|
+
return { role: "assistant", content, receivedAt: 0, kind: "tool" };
|
|
31649
|
+
}
|
|
31650
|
+
if (resultTypes.includes(typeVal)) {
|
|
31651
|
+
const result = oneLine(stringifyContent(jsonPathGet(block2, tmap.result_content || "$.content")), 600);
|
|
31652
|
+
if (!result) return null;
|
|
31653
|
+
return { role: "assistant", content: `\u2198 ${result}`, receivedAt: 0, kind: "tool" };
|
|
31389
31654
|
}
|
|
31390
|
-
|
|
31391
|
-
|
|
31392
|
-
|
|
31655
|
+
return null;
|
|
31656
|
+
}
|
|
31657
|
+
function oneLine(s, max) {
|
|
31658
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
31659
|
+
return flat.length > max ? flat.slice(0, max - 1) + "\u2026" : flat;
|
|
31393
31660
|
}
|
|
31394
31661
|
function parseTimestamp(v) {
|
|
31395
31662
|
if (v == null) return null;
|
|
@@ -31530,6 +31797,9 @@ import * as fs13 from "fs";
|
|
|
31530
31797
|
function stripAnsi3(text) {
|
|
31531
31798
|
return String(text || "").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
31532
31799
|
}
|
|
31800
|
+
function delay(ms) {
|
|
31801
|
+
return new Promise((resolve24) => setTimeout(resolve24, ms));
|
|
31802
|
+
}
|
|
31533
31803
|
var SpecCliAdapter = class _SpecCliAdapter {
|
|
31534
31804
|
cliType;
|
|
31535
31805
|
cliName;
|
|
@@ -31751,9 +32021,15 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31751
32021
|
* drives the dispatch:
|
|
31752
32022
|
*
|
|
31753
32023
|
* send_keys → click_control (e.g. stop)
|
|
31754
|
-
* open_picker →
|
|
31755
|
-
*
|
|
31756
|
-
*
|
|
32024
|
+
* open_picker → two roles, driven by the screen, not a hardcoded list:
|
|
32025
|
+
* - LIST (no choice arg): open the picker, wait for it
|
|
32026
|
+
* to render, parse the on-screen options via
|
|
32027
|
+
* `extract_choices`, and return them as
|
|
32028
|
+
* `controlResult.options` (+ `currentValue`). This is
|
|
32029
|
+
* how the dashboard's Model/Mode controls learn what is
|
|
32030
|
+
* actually selectable in this CLI right now.
|
|
32031
|
+
* - SELECT (args.choiceIndex / args.choiceLabel): drive
|
|
32032
|
+
* the picker to that option using `submit_key`.
|
|
31757
32033
|
* attach_image → attach_image dispatch; expects args.blob (data url
|
|
31758
32034
|
* or base64) and args.mime
|
|
31759
32035
|
*
|
|
@@ -31778,11 +32054,128 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
31778
32054
|
this.driver.dispatch({ kind: "attach_image", blob, mime });
|
|
31779
32055
|
return Promise.resolve({ ok: true, effects: [{ type: "attached_image", controlId: ctl.id }] });
|
|
31780
32056
|
}
|
|
32057
|
+
if (action.type === "open_picker") {
|
|
32058
|
+
const choiceIndex = typeof flat.choiceIndex === "number" ? flat.choiceIndex : typeof flat.choiceIndex === "string" && flat.choiceIndex.trim() ? Number(flat.choiceIndex) : void 0;
|
|
32059
|
+
const choiceLabel = typeof flat.choiceLabel === "string" ? flat.choiceLabel : typeof flat.choice === "string" ? flat.choice : void 0;
|
|
32060
|
+
if (typeof choiceIndex === "number" && Number.isFinite(choiceIndex) || choiceLabel && choiceLabel.trim()) {
|
|
32061
|
+
return this.selectPickerChoice(ctl, action, choiceIndex, choiceLabel);
|
|
32062
|
+
}
|
|
32063
|
+
return this.openPickerAndListChoices(ctl, action);
|
|
32064
|
+
}
|
|
31781
32065
|
this.driver.dispatch({ kind: "click_control", control_id: ctl.id, payload: flat });
|
|
31782
|
-
|
|
31783
|
-
|
|
31784
|
-
|
|
31785
|
-
|
|
32066
|
+
return Promise.resolve({ ok: true, effects: [{ type: "sent_keys", controlId: ctl.id }] });
|
|
32067
|
+
}
|
|
32068
|
+
/**
|
|
32069
|
+
* Open an `open_picker` control and return the options the CLI is showing,
|
|
32070
|
+
* parsed live from the screen via `extract_choices`. Nothing is selected —
|
|
32071
|
+
* the picker is left open so a follow-up SELECT invoke can commit a choice.
|
|
32072
|
+
*/
|
|
32073
|
+
async openPickerAndListChoices(ctl, action) {
|
|
32074
|
+
this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
|
|
32075
|
+
const ready = await this.waitForPickerRendered(action);
|
|
32076
|
+
const options = this.extractPickerChoices(action);
|
|
32077
|
+
const currentValue = options.find((o) => o.current)?.label;
|
|
32078
|
+
return {
|
|
32079
|
+
ok: true,
|
|
32080
|
+
effects: [{ type: "opened_picker", controlId: ctl.id }],
|
|
32081
|
+
controlResult: {
|
|
32082
|
+
options: options.map((o) => ({ value: o.label, label: o.label, current: o.current })),
|
|
32083
|
+
...currentValue ? { currentValue } : {},
|
|
32084
|
+
source: "screen-parse",
|
|
32085
|
+
...ready ? {} : { warning: "picker_render_timeout" }
|
|
32086
|
+
}
|
|
32087
|
+
};
|
|
32088
|
+
}
|
|
32089
|
+
/**
|
|
32090
|
+
* Drive an already-listable picker to a specific option. The option can be
|
|
32091
|
+
* named (choiceLabel — matched against the parsed on-screen labels) or
|
|
32092
|
+
* positional (choiceIndex — the on-screen number). The actual keystrokes
|
|
32093
|
+
* come from the spec's `submit_key` with `{index}` substituted, so the spec
|
|
32094
|
+
* — not this code — decides how a selection is keyed for each CLI.
|
|
32095
|
+
*/
|
|
32096
|
+
async selectPickerChoice(ctl, action, choiceIndex, choiceLabel) {
|
|
32097
|
+
this.driver.dispatch({ kind: "click_control", control_id: ctl.id });
|
|
32098
|
+
await this.waitForPickerRendered(action);
|
|
32099
|
+
const options = this.extractPickerChoices(action);
|
|
32100
|
+
let index = choiceIndex;
|
|
32101
|
+
if ((index == null || !Number.isFinite(index)) && choiceLabel) {
|
|
32102
|
+
const needle = choiceLabel.trim().toLowerCase();
|
|
32103
|
+
const match = options.find((o) => o.label.toLowerCase().includes(needle));
|
|
32104
|
+
if (!match) {
|
|
32105
|
+
return { ok: false, error: `choice not found on screen: ${choiceLabel}`, controlResult: { options: options.map((o) => ({ value: o.label, label: o.label })) } };
|
|
32106
|
+
}
|
|
32107
|
+
index = match.index;
|
|
32108
|
+
}
|
|
32109
|
+
if (index == null || !Number.isFinite(index)) {
|
|
32110
|
+
return { ok: false, error: "choiceIndex or choiceLabel required to select" };
|
|
32111
|
+
}
|
|
32112
|
+
const keys = (action.submit_key || "{index}\r").replace(/\{index\}/g, String(index));
|
|
32113
|
+
this.driver.dispatch({ kind: "pty_write", data: keys });
|
|
32114
|
+
const selected = options.find((o) => o.index === index);
|
|
32115
|
+
return {
|
|
32116
|
+
ok: true,
|
|
32117
|
+
effects: [{ type: "selected_choice", controlId: ctl.id }],
|
|
32118
|
+
controlResult: {
|
|
32119
|
+
ok: true,
|
|
32120
|
+
...selected ? { currentValue: selected.label } : {},
|
|
32121
|
+
selectedIndex: index
|
|
32122
|
+
}
|
|
32123
|
+
};
|
|
32124
|
+
}
|
|
32125
|
+
/** Poll the live screen until the picker's `wait_for` condition matches,
|
|
32126
|
+
* up to a short budget. Returns true if it rendered, false on timeout. */
|
|
32127
|
+
async waitForPickerRendered(action) {
|
|
32128
|
+
const wf = action.wait_for;
|
|
32129
|
+
if (!wf?.regex) {
|
|
32130
|
+
await delay(250);
|
|
32131
|
+
return true;
|
|
32132
|
+
}
|
|
32133
|
+
const re = new RegExp(wf.regex, wf.flags ?? "i");
|
|
32134
|
+
const deadline = Date.now() + 2500;
|
|
32135
|
+
while (Date.now() < deadline) {
|
|
32136
|
+
await delay(120);
|
|
32137
|
+
const hay = this.readScreenSectionText(wf.section);
|
|
32138
|
+
if (re.test(hay)) return true;
|
|
32139
|
+
}
|
|
32140
|
+
return false;
|
|
32141
|
+
}
|
|
32142
|
+
/** Parse the picker's `extract_choices` pattern against the live screen.
|
|
32143
|
+
* Each match yields { index, label, current }. `current` is true for the
|
|
32144
|
+
* line the CLI marks with its cursor glyph (❯ ›). Purely screen-driven —
|
|
32145
|
+
* no model/mode names are baked in. */
|
|
32146
|
+
extractPickerChoices(action) {
|
|
32147
|
+
const ec = action.extract_choices;
|
|
32148
|
+
if (!ec?.pattern) return [];
|
|
32149
|
+
const text = this.readScreenSectionText(ec.section);
|
|
32150
|
+
const out = [];
|
|
32151
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32152
|
+
for (const rawLine of text.split("\n")) {
|
|
32153
|
+
const line = rawLine.replace(/\r$/, "");
|
|
32154
|
+
const m = new RegExp(ec.pattern, ec.flags ?? "").exec(line);
|
|
32155
|
+
if (!m) continue;
|
|
32156
|
+
const idx = Number(m[1]);
|
|
32157
|
+
if (!Number.isFinite(idx) || seen.has(idx)) continue;
|
|
32158
|
+
const label = (m[2] ?? "").replace(/\s+/g, " ").trim();
|
|
32159
|
+
if (!label) continue;
|
|
32160
|
+
const current = /^\s*[❯›>]/.test(line) || /[✔✓●]\s*$/.test(label);
|
|
32161
|
+
seen.add(idx);
|
|
32162
|
+
out.push({ index: idx, label, current });
|
|
32163
|
+
}
|
|
32164
|
+
return out;
|
|
32165
|
+
}
|
|
32166
|
+
/** Live text of a named screen section (or the whole screen when no
|
|
32167
|
+
* section is named), resolved from the driver's current sections. */
|
|
32168
|
+
readScreenSectionText(sectionId) {
|
|
32169
|
+
try {
|
|
32170
|
+
const sections = this.driver.getSections();
|
|
32171
|
+
if (sectionId && sections) {
|
|
32172
|
+
const hit = sections.find((s) => s.id === sectionId);
|
|
32173
|
+
if (hit) return hit.text;
|
|
32174
|
+
}
|
|
32175
|
+
return this.driver.getScreen();
|
|
32176
|
+
} catch {
|
|
32177
|
+
return "";
|
|
32178
|
+
}
|
|
31786
32179
|
}
|
|
31787
32180
|
getDebugSnapshot() {
|
|
31788
32181
|
let screen = "";
|
|
@@ -42003,6 +42396,43 @@ function readMeshTimeoutEnvMs(name, defaultMs) {
|
|
|
42003
42396
|
}
|
|
42004
42397
|
var MESH_DIRECT_PROBE_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_TIMEOUT_MS", 25e3);
|
|
42005
42398
|
var MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS", 25e3);
|
|
42399
|
+
var MESH_DIRECT_PROBE_REUSE_MS = readMeshTimeoutEnvMs("MESH_DIRECT_PROBE_REUSE_MS", 12e3);
|
|
42400
|
+
var MeshGitProbeCache = class {
|
|
42401
|
+
constructor(reuseMs, now = Date.now) {
|
|
42402
|
+
this.reuseMs = reuseMs;
|
|
42403
|
+
this.now = now;
|
|
42404
|
+
}
|
|
42405
|
+
inflight = /* @__PURE__ */ new Map();
|
|
42406
|
+
recent = /* @__PURE__ */ new Map();
|
|
42407
|
+
key(daemonId, workspace) {
|
|
42408
|
+
return `${daemonId}::${workspace}`;
|
|
42409
|
+
}
|
|
42410
|
+
/**
|
|
42411
|
+
* Run `probe` for this peer, but reuse a fresh recent result or an in-flight
|
|
42412
|
+
* probe for the same key when one is available. `probe` is only invoked when
|
|
42413
|
+
* neither gate is satisfied.
|
|
42414
|
+
*/
|
|
42415
|
+
async probe(daemonId, workspace, probe) {
|
|
42416
|
+
const key = this.key(daemonId, workspace);
|
|
42417
|
+
const cached2 = this.recent.get(key);
|
|
42418
|
+
if (cached2 && this.now() - cached2.at < this.reuseMs) {
|
|
42419
|
+
return cached2.value;
|
|
42420
|
+
}
|
|
42421
|
+
const existing = this.inflight.get(key);
|
|
42422
|
+
if (existing) return existing;
|
|
42423
|
+
const pending = (async () => {
|
|
42424
|
+
const result = await probe();
|
|
42425
|
+
if (result) this.recent.set(key, { at: this.now(), value: result });
|
|
42426
|
+
return result;
|
|
42427
|
+
})();
|
|
42428
|
+
this.inflight.set(key, pending);
|
|
42429
|
+
try {
|
|
42430
|
+
return await pending;
|
|
42431
|
+
} finally {
|
|
42432
|
+
if (this.inflight.get(key) === pending) this.inflight.delete(key);
|
|
42433
|
+
}
|
|
42434
|
+
}
|
|
42435
|
+
};
|
|
42006
42436
|
async function probeRemoteMeshGitStatus(args) {
|
|
42007
42437
|
if (!args.dispatchMeshCommand) return null;
|
|
42008
42438
|
const remoteResult = await Promise.race([
|
|
@@ -42096,7 +42526,7 @@ async function hydrateInlineMeshDirectTruth(args) {
|
|
|
42096
42526
|
continue;
|
|
42097
42527
|
}
|
|
42098
42528
|
peerAttemptedCount += 1;
|
|
42099
|
-
const
|
|
42529
|
+
const runProbe = () => probeRemoteMeshGitStatusWithRetry({
|
|
42100
42530
|
dispatchMeshCommand: args.dispatchMeshCommand,
|
|
42101
42531
|
daemonId,
|
|
42102
42532
|
workspace,
|
|
@@ -42104,6 +42534,7 @@ async function hydrateInlineMeshDirectTruth(args) {
|
|
|
42104
42534
|
retryTimeoutMs: MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
|
|
42105
42535
|
getConnection: args.getMeshPeerConnectionStatus
|
|
42106
42536
|
});
|
|
42537
|
+
const remoteGit = args.probeCache ? await args.probeCache.probe(daemonId, workspace, runProbe) : await runProbe();
|
|
42107
42538
|
if (remoteGit) {
|
|
42108
42539
|
recordInlineMeshDirectGitTruth(node, remoteGit, "selected_coordinator_mesh_p2p_git");
|
|
42109
42540
|
peerConfirmedCount += 1;
|
|
@@ -43426,6 +43857,10 @@ var DaemonCommandRouter = class {
|
|
|
43426
43857
|
inlineMeshCache = /* @__PURE__ */ new Map();
|
|
43427
43858
|
/** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
|
|
43428
43859
|
aggregateMeshStatusCache = /* @__PURE__ */ new Map();
|
|
43860
|
+
/** Shared per-peer git_status probe dedup + recently-probed reuse gate.
|
|
43861
|
+
* Spans separate mesh_status/get_mesh calls so the dashboard auto-retry
|
|
43862
|
+
* loop cannot storm a slow peer with back-to-back refreshUpstream probes. */
|
|
43863
|
+
meshGitProbeCache = new MeshGitProbeCache(MESH_DIRECT_PROBE_REUSE_MS);
|
|
43429
43864
|
/** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
|
|
43430
43865
|
runningRefineJobs = /* @__PURE__ */ new Map();
|
|
43431
43866
|
/** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
|
|
@@ -46328,7 +46763,8 @@ ${hintLines.join("\n")}` : "",
|
|
|
46328
46763
|
getMeshPeerConnectionStatus: this.deps.getMeshPeerConnectionStatus,
|
|
46329
46764
|
statusInstanceId: this.deps.statusInstanceId,
|
|
46330
46765
|
localMachineId: loadConfig().machineId || "",
|
|
46331
|
-
probeRemotePeers
|
|
46766
|
+
probeRemotePeers,
|
|
46767
|
+
probeCache: this.meshGitProbeCache
|
|
46332
46768
|
});
|
|
46333
46769
|
const directTruthSatisfied = meshRecord.source !== "inline_bootstrap" || directTruth.directEvidenceCount > 0;
|
|
46334
46770
|
const sourceOfTruth = {
|
|
@@ -46932,6 +47368,22 @@ ${hintLines.join("\n")}` : "",
|
|
|
46932
47368
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
46933
47369
|
const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
|
|
46934
47370
|
if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
|
|
47371
|
+
{
|
|
47372
|
+
const meshRecordForForward = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
|
|
47373
|
+
const forwardNode = meshRecordForForward?.mesh?.nodes?.find((n) => meshNodeIdMatches(n, nodeId));
|
|
47374
|
+
const nodeDaemonId = typeof forwardNode?.daemonId === "string" ? forwardNode.daemonId.trim() : void 0;
|
|
47375
|
+
const selfDaemonId = this.deps.statusInstanceId;
|
|
47376
|
+
const isRemote = nodeDaemonId && selfDaemonId && nodeDaemonId !== selfDaemonId;
|
|
47377
|
+
if (isRemote && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
|
|
47378
|
+
const callerCoordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : void 0;
|
|
47379
|
+
const forwarded = await this.deps.dispatchMeshCommand(nodeDaemonId, "refine_mesh_node", {
|
|
47380
|
+
...typeof args === "object" && args !== null ? args : {},
|
|
47381
|
+
coordinatorDaemonId: callerCoordinatorDaemonId || selfDaemonId,
|
|
47382
|
+
_meshDirectDispatch: true
|
|
47383
|
+
});
|
|
47384
|
+
return forwarded ?? { success: false, error: "no response from remote node" };
|
|
47385
|
+
}
|
|
47386
|
+
}
|
|
46935
47387
|
const isDryRun = args?.dryRun !== false && args?.execute !== true;
|
|
46936
47388
|
if (isDryRun) {
|
|
46937
47389
|
const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
|
|
@@ -47904,6 +48356,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47904
48356
|
const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
|
|
47905
48357
|
const localMachineId = loadConfig().machineId || "";
|
|
47906
48358
|
const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
|
|
48359
|
+
const meshGitProbeCache = this.meshGitProbeCache;
|
|
47907
48360
|
const directTruth = requireDirectPeerTruth ? await hydrateInlineMeshDirectTruth({
|
|
47908
48361
|
mesh,
|
|
47909
48362
|
meshSource: meshRecord.source,
|
|
@@ -47914,7 +48367,8 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
47914
48367
|
// Standing-state model: only an explicit refresh fans
|
|
47915
48368
|
// out a blocking peer git probe. Default loads return
|
|
47916
48369
|
// held truth so one slow peer can't block the graph.
|
|
47917
|
-
probeRemotePeers: refreshRequested
|
|
48370
|
+
probeRemotePeers: refreshRequested,
|
|
48371
|
+
probeCache: meshGitProbeCache
|
|
47918
48372
|
}) : {
|
|
47919
48373
|
directEvidenceCount: 0,
|
|
47920
48374
|
localConfirmedCount: 0,
|
|
@@ -48075,7 +48529,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
48075
48529
|
}
|
|
48076
48530
|
remoteProbeApplied = true;
|
|
48077
48531
|
} else if (refreshRequested && !isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
|
|
48078
|
-
const
|
|
48532
|
+
const runNodeProbe = () => probeRemoteMeshGitStatusWithRetry({
|
|
48079
48533
|
dispatchMeshCommand: this.deps.dispatchMeshCommand,
|
|
48080
48534
|
daemonId,
|
|
48081
48535
|
workspace,
|
|
@@ -48086,6 +48540,7 @@ ${ptyResult.output.slice(-2e3)}`);
|
|
|
48086
48540
|
status.connection = connection;
|
|
48087
48541
|
}
|
|
48088
48542
|
});
|
|
48543
|
+
const remoteGit = await meshGitProbeCache.probe(daemonId, workspace, runNodeProbe);
|
|
48089
48544
|
if (remoteGit) {
|
|
48090
48545
|
status.git = remoteGit;
|
|
48091
48546
|
status.health = remoteGit.isGitRepo ? deriveMeshNodeHealthFromGit(remoteGit) : "degraded";
|
|
@@ -56909,6 +57364,7 @@ export {
|
|
|
56909
57364
|
DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
56910
57365
|
DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
56911
57366
|
DEFAULT_MESH_POLICY,
|
|
57367
|
+
DEFAULT_MESH_SCHEDULING_STRATEGY,
|
|
56912
57368
|
DEFAULT_SESSION_HOST_APP_NAME,
|
|
56913
57369
|
DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
56914
57370
|
DEFAULT_SESSION_HOST_READY_TIMEOUT_MS,
|
|
@@ -56934,9 +57390,12 @@ export {
|
|
|
56934
57390
|
InMemoryGitSnapshotStore,
|
|
56935
57391
|
LOG,
|
|
56936
57392
|
MAX_LEDGER_SLICE_LIMIT,
|
|
57393
|
+
MESH_CONVERGE_FAST_FORWARD_TAG,
|
|
57394
|
+
MESH_CONVERGE_REFINE_TAG,
|
|
56937
57395
|
MESH_MISSION_STATUSES,
|
|
56938
57396
|
MESH_REFINE_CONFIG_LOCATIONS,
|
|
56939
57397
|
MESH_REFINE_CONFIG_SCHEMA,
|
|
57398
|
+
MESH_SCHEDULING_STRATEGIES,
|
|
56940
57399
|
MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
|
|
56941
57400
|
MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
|
|
56942
57401
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
@@ -57137,6 +57596,7 @@ export {
|
|
|
57137
57596
|
normalizeManagedStatus,
|
|
57138
57597
|
normalizeMeshCapabilityTags,
|
|
57139
57598
|
normalizeMeshDaemonRole,
|
|
57599
|
+
normalizeMeshSchedulingStrategy,
|
|
57140
57600
|
normalizeMeshTaskMode,
|
|
57141
57601
|
normalizeMeshWorkerResult,
|
|
57142
57602
|
normalizeMessageParts,
|
|
@@ -57174,7 +57634,9 @@ export {
|
|
|
57174
57634
|
resetConfig,
|
|
57175
57635
|
resetDebugRuntimeConfig,
|
|
57176
57636
|
resetState,
|
|
57637
|
+
resolveAutoConvergeCodeChange,
|
|
57177
57638
|
resolveChatMessageKind,
|
|
57639
|
+
resolveConvergeRequiredTags,
|
|
57178
57640
|
resolveCurrentGlobalInstallSurface,
|
|
57179
57641
|
resolveDebugRuntimeConfig,
|
|
57180
57642
|
resolveDelegatedWorkerAutoApprove,
|
|
@@ -57182,6 +57644,7 @@ export {
|
|
|
57182
57644
|
resolveGitRepository,
|
|
57183
57645
|
resolveMeshHostStatus,
|
|
57184
57646
|
resolveMeshRefineValidationPlan,
|
|
57647
|
+
resolveNodeSchedulingPriority,
|
|
57185
57648
|
resolveSessionHostAppName,
|
|
57186
57649
|
resolveSessionHostAppNameResolution,
|
|
57187
57650
|
resolveWorktreePath,
|