@adhdev/daemon-standalone 1.0.56-rc.5 → 1.0.56-rc.7
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/index.js +1203 -506
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/{index-B43wNsru.js → index-keXYbbIn.js} +1 -1
- package/public/index.html +1 -1
- package/vendor/mcp-server/index.js +178 -11
- package/vendor/mcp-server/index.js.map +1 -1
package/public/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
|
|
8
8
|
<link rel="icon" href="/otter-logo.png" />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-keXYbbIn.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-DgFmqOGd.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-BN4rzCA5.css">
|
|
13
13
|
</head>
|
|
@@ -1085,7 +1085,17 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1085
1085
|
block_duplicate: { type: "boolean", description: "G4 (duplicate detection, block mode). Default false = warn-only: if an in-flight (pending/assigned) task with the same message (+ target node when pinned) already exists, the task is still enqueued but the response carries duplicateSuspect. Set true to REFUSE the enqueue with code duplicate_suspect instead (structural TASKBUBBLE-DUP defense \u2014 use when re-sending a task that a slow prior turn may have already enqueued)." },
|
|
1086
1086
|
blockDuplicate: { type: "boolean", description: "CamelCase alias for block_duplicate." },
|
|
1087
1087
|
allow_duplicate: { type: "boolean", description: "G4. Set true to skip duplicate detection entirely (no warning, no block) for an intentional re-enqueue of the same instruction." },
|
|
1088
|
-
allowDuplicate: { type: "boolean", description: "CamelCase alias for allow_duplicate." }
|
|
1088
|
+
allowDuplicate: { type: "boolean", description: "CamelCase alias for allow_duplicate." },
|
|
1089
|
+
// design :692 — "The single tool should require an orchestration_decision".
|
|
1090
|
+
// OPTIONAL here on purpose: Phase F is warn-only (see the discovery-meta note
|
|
1091
|
+
// above), and a required field would break legacy/external clients that
|
|
1092
|
+
// predate it. An omitted record degrades to decision_missing, which is itself
|
|
1093
|
+
// the signal — never an enqueue failure.
|
|
1094
|
+
orchestration_decision: {
|
|
1095
|
+
type: "object",
|
|
1096
|
+
description: "Record of your planning decision, for adoption measurement: {decision, ready_worker_tasks, known_graph_steps, single_reason, capability_blockers}. On this single-task surface, single_reason says why one task was right \u2014 one of only_one_step_known, future_step_not_specifiable, same_session_continuation, legacy_client, operator_override. output_needed / workspace_unresolved / coordinator_action_between are NOT blockers any more (mesh_enqueue_batch covers them via inputs_from, workspace_ref and coordinator gates); reporting one returns a batch_capability_available warning. Optional and never rejected: omitting it is recorded as decision_missing, and declaring known_graph_steps >= 2 here is recorded as a declared eligible single. Provenance only \u2014 it never changes execution."
|
|
1097
|
+
},
|
|
1098
|
+
orchestrationDecision: { type: "object", description: "CamelCase alias for orchestration_decision." }
|
|
1089
1099
|
},
|
|
1090
1100
|
required: ["message", "difficulty"]
|
|
1091
1101
|
}
|
|
@@ -1313,7 +1323,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
|
|
|
1313
1323
|
};
|
|
1314
1324
|
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
1315
1325
|
name: "mesh_queue_requeue",
|
|
1316
|
-
description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override.",
|
|
1326
|
+
description: "Return a mesh queue task to pending for retry, optionally re-targeting it and/or REWRITING its instruction. By default clears stale assigned owner and target session so another live session can claim it. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override. This is also the way OUT of parking: a task whose target-session pin went stale is PARKED (held, still addressed, claimable by nobody) and any requeue unparks it \u2014 see parkedTasks in mesh_view_queue.",
|
|
1317
1327
|
inputSchema: {
|
|
1318
1328
|
type: "object",
|
|
1319
1329
|
properties: {
|
|
@@ -1323,7 +1333,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
|
|
|
1323
1333
|
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
1324
1334
|
clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
|
|
1325
1335
|
keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." },
|
|
1326
|
-
force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
|
|
1336
|
+
force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." },
|
|
1337
|
+
message: { type: "string", description: "Optional REPLACEMENT instruction for the task. Use when the situation moved on while the task waited \u2014 the common case for a parked delta, e.g. the worker already finished the part your correction was about, so the original wording would now be wrong or redundant. Preserves the task id, mission linkage and dependents (unlike cancel + re-enqueue). Omitted or blank leaves the existing message untouched." }
|
|
1327
1338
|
},
|
|
1328
1339
|
required: ["task_id"]
|
|
1329
1340
|
}
|
|
@@ -1343,7 +1354,13 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1343
1354
|
read_only: { type: "boolean", description: "Snake-case alias for readonly." },
|
|
1344
1355
|
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id, full/exact). When set, the directly dispatched task is attributed to the mission task aggregates exactly like mesh_enqueue_task, including terminal completion. Omit for an unattributed direct dispatch. An unresolvable id is REJECTED before dispatch (mission_not_found), never silently attached." },
|
|
1345
1356
|
missionId: { type: "string", description: "CamelCase alias for mission_id." },
|
|
1346
|
-
difficulty: { type: "string", enum: ["easy", "medium", "difficult", "freeform"], description: "REQUIRED task execution difficulty. Classify each task by how hard the work actually is. On a direct dispatch the target node/session is already chosen, so difficulty is not used to ROUTE \u2014 it is recorded on the task so scheduling analytics, mission aggregates and (critically) failure-recovery relaunch all see the same axis a queued task carries. A recovery relaunch inherits this value from the ledger, so an unclassified direct dispatch would silently downgrade its own retry." }
|
|
1357
|
+
difficulty: { type: "string", enum: ["easy", "medium", "difficult", "freeform"], description: "REQUIRED task execution difficulty. Classify each task by how hard the work actually is. On a direct dispatch the target node/session is already chosen, so difficulty is not used to ROUTE \u2014 it is recorded on the task so scheduling analytics, mission aggregates and (critically) failure-recovery relaunch all see the same axis a queued task carries. A recovery relaunch inherits this value from the ledger, so an unclassified direct dispatch would silently downgrade its own retry." },
|
|
1358
|
+
delivery_mode: {
|
|
1359
|
+
type: "string",
|
|
1360
|
+
enum: ["when_idle", "interrupt"],
|
|
1361
|
+
description: "How to deliver when the target session is BUSY. Default 'when_idle': never disturbs the running turn \u2014 the task is queued and auto-delivered the moment the session goes idle. \u2605'interrupt' ABORTS the turn currently in flight by pressing the provider's own stop control (Ctrl-C, or ESC on antigravity-cli), then delivers this task once the session settles. THE WORK IN PROGRESS IS DISCARDED \u2014 whatever the agent had not yet finished is lost, and any partial edits it was mid-way through are left as they are. Use it only when the running turn is genuinely going the wrong way and finishing it is worse than losing it. If the target provider cannot interrupt (no stop control declared, or an empty stop key), the dispatch is REJECTED rather than quietly falling back to when_idle \u2014 so a steering attempt never reports success while the session actually runs on to completion under the old instructions. Re-send with 'when_idle' if delivery-after-completion is acceptable. Has no effect on an idle session (delivered immediately either way)."
|
|
1362
|
+
},
|
|
1363
|
+
deliveryMode: { type: "string", enum: ["when_idle", "interrupt"], description: "CamelCase alias for delivery_mode." }
|
|
1347
1364
|
},
|
|
1348
1365
|
required: ["node_id", "session_id", "message", "difficulty"]
|
|
1349
1366
|
}
|
|
@@ -1506,7 +1523,7 @@ var MESH_CHECKPOINT_TOOL = {
|
|
|
1506
1523
|
};
|
|
1507
1524
|
var MESH_MISSION_UPSERT_TOOL = {
|
|
1508
1525
|
name: "mesh_mission_upsert",
|
|
1509
|
-
description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch,
|
|
1526
|
+
description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch, then submit that plan as ONE mesh_enqueue_batch carrying the mission_id (a top-level mission_id applies to every entry; mesh_enqueue_task is the single-step fallback). Update status to completed/abandoned when the outcome is decided. Progress is derived from task statuses \u2014 there is no separate progress field. Single mission: pass title (and optionally mission_id to update an existing one). Bulk status transition (e.g. one-time stale cleanup): pass mission_ids (array) + status to apply that status to many missions at once; title/goal are ignored and a per-mission result array is returned. mission_ids takes precedence over mission_id when both are given.",
|
|
1510
1527
|
inputSchema: {
|
|
1511
1528
|
type: "object",
|
|
1512
1529
|
properties: {
|
|
@@ -1636,7 +1653,7 @@ var MESH_PLAN_ONBOARDING_TOOL = {
|
|
|
1636
1653
|
};
|
|
1637
1654
|
var MESH_ADD_NODE_TOOL = {
|
|
1638
1655
|
name: "mesh_add_node",
|
|
1639
|
-
description: "Register a workspace as a node in an EXISTING mesh \u2014 the second bootstrap step after mesh_create (or to add more nodes later). Mirrors `adhdev mesh add-node <mesh_id>` with --workspace / --read-only / --provider-priority. A node is a repo checkout on a daemon that the coordinator can launch agents on and delegate tasks to. mesh_id is REQUIRED in standard mode (pass the id returned by mesh_create); in mesh mode it defaults to the active mesh. workspace is the absolute path to the repo checkout ON THE DAEMON that owns it \u2014 the local base node is added by the daemon that created the mesh. This is a persistent mesh write: call mesh_plan_onboarding first and obtain explicit user approval. The implementation re-runs that preflight before writing. NOTE: this registers an EXISTING directory as a node (including auto-detected linked worktrees). To CREATE a fresh git worktree + branch for isolated parallel work, use mesh_clone_node instead \u2014 that runs the actual `git worktree add`. Returns node_id + workspace so you can immediately target the node with mesh_launch_session / mesh_send_task / mesh_enqueue_task.",
|
|
1656
|
+
description: "Register a workspace as a node in an EXISTING mesh \u2014 the second bootstrap step after mesh_create (or to add more nodes later). Mirrors `adhdev mesh add-node <mesh_id>` with --workspace / --read-only / --provider-priority. A node is a repo checkout on a daemon that the coordinator can launch agents on and delegate tasks to. mesh_id is REQUIRED in standard mode (pass the id returned by mesh_create); in mesh mode it defaults to the active mesh. workspace is the absolute path to the repo checkout ON THE DAEMON that owns it \u2014 the local base node is added by the daemon that created the mesh. This is a persistent mesh write: call mesh_plan_onboarding first and obtain explicit user approval. The implementation re-runs that preflight before writing. NOTE: this registers an EXISTING directory as a node (including auto-detected linked worktrees). To CREATE a fresh git worktree + branch for isolated parallel work, use mesh_clone_node instead \u2014 that runs the actual `git worktree add`. Returns node_id + workspace so you can immediately target the node with mesh_launch_session / mesh_send_task / mesh_enqueue_task. That immediate-targeting path is right when the node is the only thing you were waiting on; when the work behind it is a multi-step plan, prefer declaring the whole plan in one mesh_enqueue_batch instead of registering, then enqueueing step by step.",
|
|
1640
1657
|
inputSchema: {
|
|
1641
1658
|
type: "object",
|
|
1642
1659
|
properties: {
|
|
@@ -1655,7 +1672,7 @@ var MESH_ADD_NODE_TOOL = {
|
|
|
1655
1672
|
};
|
|
1656
1673
|
var MESH_CLONE_NODE_TOOL = {
|
|
1657
1674
|
name: "mesh_clone_node",
|
|
1658
|
-
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously. This writes a branch, worktree and mesh node: call mesh_plan_onboarding with operation=clone_worktree and obtain explicit user approval first; the implementation re-runs the clean/source preflight.",
|
|
1675
|
+
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously. This writes a branch, worktree and mesh node: call mesh_plan_onboarding with operation=clone_worktree and obtain explicit user approval first; the implementation re-runs the clean/source preflight. Call this directly when you need the worktree NOW and will target it right away. When the worktree only exists to host a plan you already know, that plan can be submitted as one mesh_enqueue_batch: declare the worktree in the top-level `workspaces` array and point its tasks at it with `workspace_ref`, so preparation happens as part of the graph instead of a manual clone followed by step-by-step enqueues.",
|
|
1659
1676
|
inputSchema: {
|
|
1660
1677
|
type: "object",
|
|
1661
1678
|
properties: {
|
|
@@ -5042,6 +5059,7 @@ async function meshGraphGateRelease(ctx, args) {
|
|
|
5042
5059
|
duplicate: result.duplicate
|
|
5043
5060
|
});
|
|
5044
5061
|
const queueTrigger = result.materializedNodeIds.length > 0 ? await triggerMeshQueueAndReport(ctx) : void 0;
|
|
5062
|
+
const releasedNothingDownstream = !result.duplicate && result.materializedNodeIds.length === 0 && result.downstreamNodeCount === 0 && result.graphCompleted !== true;
|
|
5045
5063
|
return JSON.stringify({
|
|
5046
5064
|
success: true,
|
|
5047
5065
|
released: true,
|
|
@@ -5054,6 +5072,10 @@ async function meshGraphGateRelease(ctx, args) {
|
|
|
5054
5072
|
outcome,
|
|
5055
5073
|
materializedNodeIds: result.materializedNodeIds,
|
|
5056
5074
|
materializedCount: result.materializedNodeIds.length,
|
|
5075
|
+
...result.downstreamNodeCount !== void 0 ? { downstreamNodeCount: result.downstreamNodeCount } : {},
|
|
5076
|
+
...releasedNothingDownstream ? {
|
|
5077
|
+
noDownstreamAdvisory: "This gate opened nothing downstream: no task declared it in gated_by, so claiming and releasing it bought no scheduling while the rest of the graph is still outstanding. Declare the next step in the same batch and point it at this gate with gated_by, so releasing the gate dispatches it \u2014 or drop the gate and enqueue that step directly."
|
|
5078
|
+
} : {},
|
|
5057
5079
|
...queueTrigger ? { queueTrigger } : {},
|
|
5058
5080
|
...result.duplicate ? { duplicateHint: "This idempotency_key + payload was already committed; nothing changed. Re-sending an identical release is safe." } : {}
|
|
5059
5081
|
});
|
|
@@ -5293,6 +5315,17 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
5293
5315
|
} = normalized.value;
|
|
5294
5316
|
const allowDuplicate = args.allowDuplicate === true || args.allow_duplicate === true;
|
|
5295
5317
|
const blockDuplicate = args.blockDuplicate === true || args.block_duplicate === true;
|
|
5318
|
+
const rawDecision = args.orchestration_decision ?? args.orchestrationDecision;
|
|
5319
|
+
const decisionMissing = rawDecision === void 0 || rawDecision === null;
|
|
5320
|
+
const orchestration = (0, import_daemon_core6.normalizeOrchestrationDecision)(rawDecision, "single");
|
|
5321
|
+
const orchestrationWarning = {
|
|
5322
|
+
...orchestration.batchCapabilityAvailable ? { batchCapabilityAvailable: orchestration.batchCapabilityAvailable } : {},
|
|
5323
|
+
...orchestration.declaredEligibleSingle ? {
|
|
5324
|
+
declaredEligibleSingle: true,
|
|
5325
|
+
declaredEligibleSingleHint: import_daemon_core6.MESH_DECLARED_ELIGIBLE_SINGLE_HINT
|
|
5326
|
+
} : {},
|
|
5327
|
+
...decisionMissing ? { orchestrationDecisionMissing: true } : {}
|
|
5328
|
+
};
|
|
5296
5329
|
const duplicateSuspect = allowDuplicate ? null : findInFlightDuplicate(ctx, message, targetNodeId);
|
|
5297
5330
|
if (duplicateSuspect && blockDuplicate) {
|
|
5298
5331
|
return JSON.stringify({
|
|
@@ -5318,6 +5351,15 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
5318
5351
|
...maxRetries !== void 0 ? { maxRetries } : {},
|
|
5319
5352
|
...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
|
|
5320
5353
|
});
|
|
5354
|
+
(0, import_daemon_core6.recordSingleEnqueueDecision)(ctx.mesh.id, {
|
|
5355
|
+
taskId: task.id,
|
|
5356
|
+
...missionId ? { missionId } : {},
|
|
5357
|
+
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
5358
|
+
decision: orchestration.decision,
|
|
5359
|
+
...decisionMissing ? { decisionMissing: true } : {},
|
|
5360
|
+
...orchestration.declaredEligibleSingle ? { declaredEligibleSingle: true } : {},
|
|
5361
|
+
...orchestration.batchCapabilityAvailable ? { batchCapabilityAvailable: orchestration.batchCapabilityAvailable.reportedReason } : {}
|
|
5362
|
+
});
|
|
5321
5363
|
const duplicateWarning = duplicateSuspect ? { duplicateSuspect: { taskId: duplicateSuspect.id, status: duplicateSuspect.status, assignedNodeId: duplicateSuspect.assignedNodeId, targetNodeId: duplicateSuspect.targetNodeId }, duplicateSuspectHint: "An in-flight task with the same message+target already exists. This new task was enqueued anyway (warn-only). Cancel one via mesh_queue_cancel if it is an accidental re-enqueue, or pass allow_duplicate=true to silence this, or block_duplicate=true to refuse next time." } : {};
|
|
5322
5364
|
const missionWarning = buildMissionInactiveWarning(ctx, missionId) ?? {};
|
|
5323
5365
|
const worktreeAdvisory = buildUntargetedCodeChangeWorktreeAdvisory({
|
|
@@ -5347,6 +5389,7 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
5347
5389
|
...duplicateWarning,
|
|
5348
5390
|
...missionWarning,
|
|
5349
5391
|
...worktreeAdvisory,
|
|
5392
|
+
...orchestrationWarning,
|
|
5350
5393
|
queueTrigger,
|
|
5351
5394
|
...buildQueueTriggerGuidance(queueTrigger)
|
|
5352
5395
|
});
|
|
@@ -5375,6 +5418,7 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
5375
5418
|
...duplicateWarning,
|
|
5376
5419
|
...missionWarning,
|
|
5377
5420
|
...worktreeAdvisory,
|
|
5421
|
+
...orchestrationWarning,
|
|
5378
5422
|
queueTrigger,
|
|
5379
5423
|
...buildQueueTriggerGuidance(queueTrigger)
|
|
5380
5424
|
});
|
|
@@ -5717,6 +5761,16 @@ async function meshViewQueue(ctx, args) {
|
|
|
5717
5761
|
via: e.payload?.via,
|
|
5718
5762
|
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
5719
5763
|
}));
|
|
5764
|
+
const parkedTasks = fullQueue.filter((task) => task?.parked?.reason).map((task) => ({
|
|
5765
|
+
taskId: task.id,
|
|
5766
|
+
parkedAt: task.parked.parkedAt,
|
|
5767
|
+
reason: task.parked.reason,
|
|
5768
|
+
originalTargetSessionId: task.parked.targetSessionId,
|
|
5769
|
+
originalTargetNodeId: task.parked.targetNodeId,
|
|
5770
|
+
currentTargetSessionId: task.targetSessionId,
|
|
5771
|
+
missionId: task.missionId,
|
|
5772
|
+
message: summarizeTaskMessage(task.message)
|
|
5773
|
+
}));
|
|
5720
5774
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
5721
5775
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
5722
5776
|
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
@@ -5771,6 +5825,11 @@ async function meshViewQueue(ctx, args) {
|
|
|
5771
5825
|
historicalCount: summary.historicalCount,
|
|
5772
5826
|
visibleActiveCount: visibleSummary.activeCount,
|
|
5773
5827
|
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
5828
|
+
...parkedTasks.length > 0 ? {
|
|
5829
|
+
parkedTasks,
|
|
5830
|
+
parkedTaskCount: parkedTasks.length,
|
|
5831
|
+
parkedTaskNote: "PARKED tasks are held for an explicit coordinator decision and are claimable by NOBODY \u2014 their delta was addressed to a session whose pin went stale, and the daemon deliberately will not re-home it onto another session. Nothing will move them until you act. Exits: mesh_queue_requeue(task_id, target_session_id=<live session>) to re-target, or with clear_target_session=true to let any compatible session take it; add message=<rewritten instruction> to the same call if the situation moved on while it waited; mesh_queue_cancel(task_id) if it is moot. Any requeue unparks the task. Left untouched they are failed (with a notification) once past the parked-task retention window."
|
|
5832
|
+
} : {},
|
|
5774
5833
|
staleAssignedTasks: compact ? staleAssignedTasks.slice(0, 10).map(compactQueueRow) : staleAssignedTasks,
|
|
5775
5834
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
5776
5835
|
queueMaintenance: maintenanceForResponse,
|
|
@@ -5876,6 +5935,7 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
5876
5935
|
const clearTargetNode = args.clear_target_node === true || args.clearTargetNode === true;
|
|
5877
5936
|
const clearTargetSession = targetSessionId ? false : !keepTargetSession;
|
|
5878
5937
|
const force = args.force === true;
|
|
5938
|
+
const message = typeof args.message === "string" && args.message.trim() ? args.message : void 0;
|
|
5879
5939
|
if (ctx.transport instanceof IpcTransport) {
|
|
5880
5940
|
const raw = await ctx.transport.command("requeue_mesh_queue_task", {
|
|
5881
5941
|
meshId: ctx.mesh.id,
|
|
@@ -5885,7 +5945,8 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
5885
5945
|
...targetSessionId ? { targetSessionId } : {},
|
|
5886
5946
|
clearTargetNode,
|
|
5887
5947
|
clearTargetSession,
|
|
5888
|
-
force
|
|
5948
|
+
force,
|
|
5949
|
+
...message ? { message } : {}
|
|
5889
5950
|
});
|
|
5890
5951
|
const result = unwrapCommandPayload(raw) || {};
|
|
5891
5952
|
if (result.success === false) {
|
|
@@ -5916,7 +5977,8 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
5916
5977
|
targetSessionId,
|
|
5917
5978
|
clearTargetNode,
|
|
5918
5979
|
clearTargetSession,
|
|
5919
|
-
force
|
|
5980
|
+
force,
|
|
5981
|
+
...message ? { message } : {}
|
|
5920
5982
|
});
|
|
5921
5983
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
5922
5984
|
if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
|
|
@@ -8389,8 +8451,107 @@ async function meshSendTask(ctx, args) {
|
|
|
8389
8451
|
}
|
|
8390
8452
|
if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
|
|
8391
8453
|
const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
|
|
8392
|
-
const { resolveDeliveryDecision } = await import("@adhdev/daemon-core");
|
|
8393
|
-
const
|
|
8454
|
+
const { resolveDeliveryDecision, normalizeDeliveryMode } = await import("@adhdev/daemon-core");
|
|
8455
|
+
const modeInput = args.delivery_mode ?? args.deliveryMode;
|
|
8456
|
+
const { mode: deliveryMode, unrecognized: unrecognizedDeliveryMode } = normalizeDeliveryMode(modeInput);
|
|
8457
|
+
let interruptSupported = false;
|
|
8458
|
+
let interruptUnsupportedMessage;
|
|
8459
|
+
let interruptConfidence;
|
|
8460
|
+
if (deliveryMode === "interrupt") {
|
|
8461
|
+
try {
|
|
8462
|
+
const probe = unwrapCommandPayload(await commandForNode(ctx, node, "agent_command", {
|
|
8463
|
+
targetSessionId: args.session_id,
|
|
8464
|
+
agentType: resolvedProviderType,
|
|
8465
|
+
cliType: resolvedProviderType,
|
|
8466
|
+
providerType: resolvedProviderType,
|
|
8467
|
+
action: "interrupt_capability"
|
|
8468
|
+
}));
|
|
8469
|
+
interruptSupported = probe?.supported === true;
|
|
8470
|
+
interruptUnsupportedMessage = typeof probe?.message === "string" ? probe.message : void 0;
|
|
8471
|
+
interruptConfidence = typeof probe?.confidence === "string" ? probe.confidence : void 0;
|
|
8472
|
+
} catch (e) {
|
|
8473
|
+
interruptSupported = false;
|
|
8474
|
+
interruptUnsupportedMessage = `Could not determine interrupt capability for provider '${resolvedProviderType}' on node '${args.node_id}': ${e?.message || e}. Refusing to interrupt on an unverified capability.`;
|
|
8475
|
+
}
|
|
8476
|
+
}
|
|
8477
|
+
const policyResult = resolveDeliveryDecision(sessionStatus, {
|
|
8478
|
+
kind: "task",
|
|
8479
|
+
deliveryMode,
|
|
8480
|
+
interruptSupported,
|
|
8481
|
+
...interruptUnsupportedMessage ? { interruptUnsupportedMessage } : {}
|
|
8482
|
+
});
|
|
8483
|
+
if (policyResult.decision === "rejected" && policyResult.reason === "interrupt_unsupported_for_provider") {
|
|
8484
|
+
return JSON.stringify({
|
|
8485
|
+
success: false,
|
|
8486
|
+
dispatched: false,
|
|
8487
|
+
decision: "interrupt_unsupported",
|
|
8488
|
+
reason: policyResult.reason,
|
|
8489
|
+
nodeId: args.node_id,
|
|
8490
|
+
sessionId: args.session_id,
|
|
8491
|
+
sessionStatus,
|
|
8492
|
+
providerType: resolvedProviderType,
|
|
8493
|
+
requestedDeliveryMode: deliveryMode,
|
|
8494
|
+
message: policyResult.message,
|
|
8495
|
+
nextAction: `Re-send this task with delivery_mode 'when_idle' to have it delivered when session '${args.session_id}' finishes on its own, or stop the session and launch a fresh one if the in-flight work must not complete.`
|
|
8496
|
+
});
|
|
8497
|
+
}
|
|
8498
|
+
if (policyResult.decision === "interrupt") {
|
|
8499
|
+
const interruptResult = unwrapCommandPayload(await commandForNode(ctx, node, "agent_command", {
|
|
8500
|
+
targetSessionId: args.session_id,
|
|
8501
|
+
agentType: resolvedProviderType,
|
|
8502
|
+
cliType: resolvedProviderType,
|
|
8503
|
+
providerType: resolvedProviderType,
|
|
8504
|
+
action: "interrupt_turn",
|
|
8505
|
+
dispatchSource: "mesh-tools-session:mesh_send_task:interrupt"
|
|
8506
|
+
}));
|
|
8507
|
+
if (interruptResult?.success !== true || interruptResult?.interrupted !== true) {
|
|
8508
|
+
return JSON.stringify({
|
|
8509
|
+
success: false,
|
|
8510
|
+
dispatched: false,
|
|
8511
|
+
decision: "interrupt_failed",
|
|
8512
|
+
reason: interruptResult?.reason || "interrupt_rejected",
|
|
8513
|
+
nodeId: args.node_id,
|
|
8514
|
+
sessionId: args.session_id,
|
|
8515
|
+
sessionStatus,
|
|
8516
|
+
providerType: resolvedProviderType,
|
|
8517
|
+
error: interruptResult?.error || "The provider did not accept the interrupt.",
|
|
8518
|
+
nextAction: `Nothing was cancelled and nothing was delivered. Re-send with delivery_mode 'when_idle', or inspect the session with mesh_read_terminal before retrying.`
|
|
8519
|
+
});
|
|
8520
|
+
}
|
|
8521
|
+
const interruptedTask = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
|
|
8522
|
+
targetNodeId: args.node_id,
|
|
8523
|
+
targetSessionId: args.session_id,
|
|
8524
|
+
taskMode,
|
|
8525
|
+
difficulty,
|
|
8526
|
+
...readonly ? { readonly: true } : {},
|
|
8527
|
+
...missionId ? { missionId } : {},
|
|
8528
|
+
...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
|
|
8529
|
+
});
|
|
8530
|
+
return JSON.stringify({
|
|
8531
|
+
success: true,
|
|
8532
|
+
dispatched: false,
|
|
8533
|
+
decision: "interrupted_and_queued",
|
|
8534
|
+
taskId: interruptedTask.id,
|
|
8535
|
+
reason: policyResult.reason,
|
|
8536
|
+
nodeId: args.node_id,
|
|
8537
|
+
sessionId: args.session_id,
|
|
8538
|
+
sessionStatus,
|
|
8539
|
+
providerType: resolvedProviderType,
|
|
8540
|
+
taskMode: taskMode || void 0,
|
|
8541
|
+
interrupt: {
|
|
8542
|
+
sent: true,
|
|
8543
|
+
key: interruptResult?.keyName,
|
|
8544
|
+
// 'declared' means the stop key is declared by the spec but the
|
|
8545
|
+
// busy->idle effect was not measured live for this provider.
|
|
8546
|
+
confidence: interruptResult?.confidence || interruptConfidence || "declared"
|
|
8547
|
+
},
|
|
8548
|
+
turnDiscarded: true,
|
|
8549
|
+
message: `Interrupted the in-flight turn on session '${args.session_id}' via ${interruptResult?.keyName || "the stop control"}. That turn was cancelled and its unfinished work is lost. Task '${interruptedTask.id}' is pinned to this session and delivers as soon as it reports idle.`,
|
|
8550
|
+
nextAction: interruptResult?.confidence === "proven" ? `Track with mesh_status; no manual resend needed.` : `Interrupt support for '${resolvedProviderType}' is DECLARED by its spec but not live-verified. Confirm with mesh_status that the session returned to idle and picked up the task; if it did not, use mesh_read_terminal to inspect.`,
|
|
8551
|
+
...unrecognizedDeliveryMode ? { deliveryModeWarning: `Unrecognized delivery_mode '${unrecognizedDeliveryMode}' ignored.` } : {},
|
|
8552
|
+
...buildMissionInactiveWarning(ctx, missionId) ?? {}
|
|
8553
|
+
});
|
|
8554
|
+
}
|
|
8394
8555
|
if (policyResult.decision === "queued") {
|
|
8395
8556
|
const queuedTask = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
|
|
8396
8557
|
targetNodeId: args.node_id,
|
|
@@ -8413,6 +8574,12 @@ async function meshSendTask(ctx, args) {
|
|
|
8413
8574
|
taskMode: taskMode || void 0,
|
|
8414
8575
|
message: policyResult.message,
|
|
8415
8576
|
nextAction: `Task '${queuedTask.id}' is queued and pinned to session '${args.session_id}' \u2014 it auto-delivers the moment the session goes idle. Use mesh_status or mesh_task_history to track it; no manual resend needed.`,
|
|
8577
|
+
// A misspelled delivery_mode silently became when_idle. Say so — a
|
|
8578
|
+
// caller who meant to interrupt must not read this queued result as
|
|
8579
|
+
// "my steering landed".
|
|
8580
|
+
...unrecognizedDeliveryMode ? {
|
|
8581
|
+
deliveryModeWarning: `Unrecognized delivery_mode '${unrecognizedDeliveryMode}' was ignored; this task was queued (when_idle) and the running turn was NOT interrupted. Valid values are 'when_idle' and 'interrupt'.`
|
|
8582
|
+
} : {},
|
|
8416
8583
|
...buildMissionInactiveWarning(ctx, missionId) ?? {}
|
|
8417
8584
|
});
|
|
8418
8585
|
}
|