@adhdev/daemon-standalone 1.0.56-rc.6 → 1.0.56-rc.8

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/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-B43wNsru.js"></script>
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>
@@ -1323,7 +1323,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
1323
1323
  };
1324
1324
  var MESH_QUEUE_REQUEUE_TOOL = {
1325
1325
  name: "mesh_queue_requeue",
1326
- 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.",
1327
1327
  inputSchema: {
1328
1328
  type: "object",
1329
1329
  properties: {
@@ -1333,7 +1333,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
1333
1333
  target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
1334
1334
  clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
1335
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." },
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." }
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." }
1337
1338
  },
1338
1339
  required: ["task_id"]
1339
1340
  }
@@ -1353,7 +1354,13 @@ var MESH_SEND_TASK_TOOL = {
1353
1354
  read_only: { type: "boolean", description: "Snake-case alias for readonly." },
1354
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." },
1355
1356
  missionId: { type: "string", description: "CamelCase alias for mission_id." },
1356
- 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." }
1357
1364
  },
1358
1365
  required: ["node_id", "session_id", "message", "difficulty"]
1359
1366
  }
@@ -5754,6 +5761,16 @@ async function meshViewQueue(ctx, args) {
5754
5761
  via: e.payload?.via,
5755
5762
  failedAt: e.payload?.dispatchFailedAt || e.timestamp
5756
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
+ }));
5757
5774
  const staleAssignedTasks = maintenance.staleAssignedTasks || [];
5758
5775
  const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
5759
5776
  const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
@@ -5808,6 +5825,11 @@ async function meshViewQueue(ctx, args) {
5808
5825
  historicalCount: summary.historicalCount,
5809
5826
  visibleActiveCount: visibleSummary.activeCount,
5810
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
+ } : {},
5811
5833
  staleAssignedTasks: compact ? staleAssignedTasks.slice(0, 10).map(compactQueueRow) : staleAssignedTasks,
5812
5834
  staleAssignedCount: maintenance.staleAssignedCount,
5813
5835
  queueMaintenance: maintenanceForResponse,
@@ -5913,6 +5935,7 @@ async function meshQueueRequeue(ctx, args) {
5913
5935
  const clearTargetNode = args.clear_target_node === true || args.clearTargetNode === true;
5914
5936
  const clearTargetSession = targetSessionId ? false : !keepTargetSession;
5915
5937
  const force = args.force === true;
5938
+ const message = typeof args.message === "string" && args.message.trim() ? args.message : void 0;
5916
5939
  if (ctx.transport instanceof IpcTransport) {
5917
5940
  const raw = await ctx.transport.command("requeue_mesh_queue_task", {
5918
5941
  meshId: ctx.mesh.id,
@@ -5922,7 +5945,8 @@ async function meshQueueRequeue(ctx, args) {
5922
5945
  ...targetSessionId ? { targetSessionId } : {},
5923
5946
  clearTargetNode,
5924
5947
  clearTargetSession,
5925
- force
5948
+ force,
5949
+ ...message ? { message } : {}
5926
5950
  });
5927
5951
  const result = unwrapCommandPayload(raw) || {};
5928
5952
  if (result.success === false) {
@@ -5953,7 +5977,8 @@ async function meshQueueRequeue(ctx, args) {
5953
5977
  targetSessionId,
5954
5978
  clearTargetNode,
5955
5979
  clearTargetSession,
5956
- force
5980
+ force,
5981
+ ...message ? { message } : {}
5957
5982
  });
5958
5983
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
5959
5984
  if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
@@ -8426,8 +8451,107 @@ async function meshSendTask(ctx, args) {
8426
8451
  }
8427
8452
  if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
8428
8453
  const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
8429
- const { resolveDeliveryDecision } = await import("@adhdev/daemon-core");
8430
- const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
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
+ }
8431
8555
  if (policyResult.decision === "queued") {
8432
8556
  const queuedTask = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
8433
8557
  targetNodeId: args.node_id,
@@ -8450,6 +8574,12 @@ async function meshSendTask(ctx, args) {
8450
8574
  taskMode: taskMode || void 0,
8451
8575
  message: policyResult.message,
8452
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
+ } : {},
8453
8583
  ...buildMissionInactiveWarning(ctx, missionId) ?? {}
8454
8584
  });
8455
8585
  }