@adhdev/daemon-standalone 0.9.77-rc.34 → 0.9.77-rc.35

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-CWWeDAva.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-Crr3Vqz7.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-CLec0455.js">
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-DftJ2WZr.css">
13
13
  </head>
@@ -26416,12 +26416,14 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
26416
26416
  return "";
26417
26417
  }
26418
26418
  function injectMeshSystemMessage(components, args) {
26419
+ let completedTaskForLedger = null;
26419
26420
  if (args.event === "agent:generating_completed") {
26420
26421
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
26421
26422
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
26422
26423
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
26423
26424
  if (sessionId) {
26424
- updateSessionTaskStatus(args.meshId, sessionId, "completed");
26425
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
26426
+ completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
26425
26427
  if (nodeId && providerType) {
26426
26428
  setTimeout(() => {
26427
26429
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
@@ -26434,6 +26436,7 @@ function injectMeshSystemMessage(components, args) {
26434
26436
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
26435
26437
  const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
26436
26438
  if (completedTask) {
26439
+ completedTaskForLedger = { id: completedTask.id };
26437
26440
  try {
26438
26441
  appendLedgerEntry(args.meshId, {
26439
26442
  kind: "task_completed",
@@ -26445,7 +26448,8 @@ function injectMeshSystemMessage(components, args) {
26445
26448
  nodeLabel: args.nodeLabel,
26446
26449
  taskId: completedTask.id,
26447
26450
  completedViaReady: true,
26448
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
26451
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
26452
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
26449
26453
  }
26450
26454
  });
26451
26455
  } catch (e) {
@@ -26488,7 +26492,9 @@ function injectMeshSystemMessage(components, args) {
26488
26492
  payload: {
26489
26493
  event: args.event,
26490
26494
  nodeLabel: args.nodeLabel,
26491
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
26495
+ taskId: completedTaskForLedger?.id || void 0,
26496
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
26497
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
26492
26498
  }
26493
26499
  });
26494
26500
  } catch (e) {
@@ -26603,7 +26609,8 @@ function handleMeshForwardEvent(components, payload) {
26603
26609
  metadataEvent: {
26604
26610
  targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
26605
26611
  providerType: readNonEmptyString(payload.providerType),
26606
- providerSessionId: readNonEmptyString(payload.providerSessionId)
26612
+ providerSessionId: readNonEmptyString(payload.providerSessionId),
26613
+ finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary)
26607
26614
  }
26608
26615
  });
26609
26616
  }
@@ -43073,6 +43080,11 @@ ${lastSnapshot}`;
43073
43080
  };
43074
43081
  }
43075
43082
  // ─── Script Execution ──────────────────────────
43083
+ invokeCliScript(script, input) {
43084
+ const hasStateFactory = typeof this.cliScripts?.createState === "function";
43085
+ const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
43086
+ return expectsStateArgument ? script(this.scriptState, input) : script(input);
43087
+ }
43076
43088
  runParseSession() {
43077
43089
  if (typeof this.cliScripts?.parseSession !== "function") {
43078
43090
  this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
@@ -43093,7 +43105,10 @@ ${lastSnapshot}`;
43093
43105
  scope: this.currentTurnScope,
43094
43106
  runtimeSettings: this.runtimeSettings
43095
43107
  });
43096
- const session = this.cliScripts.parseSession(this.scriptState, { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
43108
+ const session = this.invokeCliScript(
43109
+ this.cliScripts.parseSession,
43110
+ { ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
43111
+ );
43097
43112
  this.parseErrorMessage = null;
43098
43113
  return session && typeof session === "object" ? session : null;
43099
43114
  } catch (e) {
@@ -43107,7 +43122,7 @@ ${lastSnapshot}`;
43107
43122
  if (!this.cliScripts?.detectStatus) return null;
43108
43123
  try {
43109
43124
  const screenText = this.terminalScreen.getText();
43110
- const status = this.cliScripts.detectStatus(this.scriptState, {
43125
+ const status = this.invokeCliScript(this.cliScripts.detectStatus, {
43111
43126
  tail: text.slice(-500),
43112
43127
  screenText,
43113
43128
  rawBuffer: this.accumulatedRawBuffer,
@@ -43126,7 +43141,7 @@ ${lastSnapshot}`;
43126
43141
  try {
43127
43142
  const screenText = this.terminalScreen.getText();
43128
43143
  const buffer = screenText || this.accumulatedBuffer;
43129
- return this.cliScripts.parseApproval(this.scriptState, {
43144
+ return this.invokeCliScript(this.cliScripts.parseApproval, {
43130
43145
  buffer,
43131
43146
  screenText,
43132
43147
  rawBuffer: this.accumulatedRawBuffer,
@@ -53375,7 +53390,13 @@ Run 'adhdev doctor' for detailed diagnostics.`
53375
53390
  appendLedgerEntry2(meshId, {
53376
53391
  kind: "node_removed",
53377
53392
  nodeId,
53378
- payload: { worktree: !!node?.isLocalWorktree, sessionCleanupMode }
53393
+ payload: {
53394
+ worktree: !!node?.isLocalWorktree,
53395
+ sessionCleanupMode,
53396
+ workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
53397
+ daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
53398
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0
53399
+ }
53379
53400
  });
53380
53401
  } catch {
53381
53402
  }
@@ -57531,6 +57552,11 @@ function annotateRapidReadChatAdvisory(payload, options) {
57531
57552
  // src/tools/mesh-tools.ts
57532
57553
  init_dist2();
57533
57554
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
57555
+ function readString(value) {
57556
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
57557
+ }
57558
+ var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
57559
+ var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
57534
57560
  async function refreshMeshFromDaemon(ctx) {
57535
57561
  if (!(ctx.transport instanceof IpcTransport)) return;
57536
57562
  try {
@@ -57551,6 +57577,138 @@ async function findNodeWithRefresh(ctx, nodeId) {
57551
57577
  if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
57552
57578
  return refreshed;
57553
57579
  }
57580
+ async function findOptionalNodeWithRefresh(ctx, nodeId) {
57581
+ const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
57582
+ if (hit) return hit;
57583
+ await refreshMeshFromDaemon(ctx);
57584
+ return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
57585
+ }
57586
+ function hasRecentDuplicateDispatch(ctx, args) {
57587
+ const now = Date.now();
57588
+ const normalizedMessage = args.message.trim();
57589
+ for (const task of getQueue(ctx.mesh.id)) {
57590
+ const timestamp2 = new Date(task.updatedAt || task.createdAt).getTime();
57591
+ if (!Number.isFinite(timestamp2) || now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) continue;
57592
+ if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
57593
+ if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
57594
+ if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
57595
+ if (task.message?.trim() === normalizedMessage) {
57596
+ return { duplicate: true, entry: task, source: "queue" };
57597
+ }
57598
+ }
57599
+ const entries = readLedgerEntries(ctx.mesh.id, { tail: 200 });
57600
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
57601
+ const entry = entries[i];
57602
+ const timestamp2 = new Date(entry.timestamp).getTime();
57603
+ if (Number.isFinite(timestamp2) && now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) break;
57604
+ if (entry.kind !== "task_dispatched") continue;
57605
+ if (entry.nodeId !== args.node_id) continue;
57606
+ if (args.session_id && entry.sessionId !== args.session_id) continue;
57607
+ if (typeof entry.payload?.message !== "string") continue;
57608
+ if (entry.payload.message.trim() === normalizedMessage) {
57609
+ return { duplicate: true, entry, source: "ledger" };
57610
+ }
57611
+ }
57612
+ return { duplicate: false };
57613
+ }
57614
+ function buildMissingNodeReadChatRecovery(ctx, args) {
57615
+ const entries = readLedgerEntries(ctx.mesh.id, { tail: 300 });
57616
+ const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
57617
+ const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
57618
+ const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
57619
+ const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
57620
+ const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
57621
+ const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
57622
+ const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
57623
+ const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
57624
+ const ledger = {
57625
+ taskCompletedFound: completedEntries.length > 0,
57626
+ nodeRemovedFound: !!lastRemoved,
57627
+ providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
57628
+ providerSessionId,
57629
+ nodeRemovedAt: lastRemoved?.timestamp,
57630
+ sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
57631
+ readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
57632
+ };
57633
+ if (finalSummary) {
57634
+ return {
57635
+ success: true,
57636
+ compact: args.compact === true,
57637
+ recoveredFromLedger: true,
57638
+ nodeId: args.node_id,
57639
+ sessionId: args.session_id,
57640
+ summary: finalSummary,
57641
+ ledger,
57642
+ messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
57643
+ };
57644
+ }
57645
+ return {
57646
+ success: false,
57647
+ recoverable: true,
57648
+ code: "mesh_removed_node_transcript_unavailable",
57649
+ error: `Node '${args.node_id}' is not a current member of mesh '${ctx.mesh.name}'.`,
57650
+ nodeId: args.node_id,
57651
+ sessionId: args.session_id,
57652
+ providerSessionId,
57653
+ reason: "node_not_in_current_mesh_snapshot",
57654
+ ledger,
57655
+ completedSessionSeenInLedger: ledger.taskCompletedFound,
57656
+ lastDispatch: lastDispatch ? {
57657
+ timestamp: lastDispatch.timestamp,
57658
+ sessionId: lastDispatch.sessionId,
57659
+ providerType: lastDispatch.providerType,
57660
+ taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
57661
+ messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
57662
+ } : null,
57663
+ lastTerminalEvent: lastTerminal ? {
57664
+ kind: lastTerminal.kind,
57665
+ timestamp: lastTerminal.timestamp,
57666
+ sessionId: lastTerminal.sessionId,
57667
+ providerType: lastTerminal.providerType,
57668
+ taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
57669
+ payload: lastTerminal.payload
57670
+ } : null,
57671
+ nextSteps: [
57672
+ providerSessionId ? `Retry mesh_read_chat with provider_session_id='${providerSessionId}' on a current live node for the same daemon if one exists.` : "If the node UI shows a provider transcript id, retry mesh_read_chat/mesh_read_debug with provider_session_id.",
57673
+ "Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
57674
+ "Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
57675
+ "If this node was removed with stop_and_delete, the runtime transcript may be gone; rely on the ledger summary/locator or ask the operator for the saved UI output."
57676
+ ],
57677
+ recoveryHints: [
57678
+ "The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
57679
+ "If you have a provider_session_id, retry mesh_read_chat with that value while targeting a live node for the same daemon if available.",
57680
+ "Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
57681
+ "Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
57682
+ ]
57683
+ };
57684
+ }
57685
+ function annotateQueueStaleness(queue) {
57686
+ const now = Date.now();
57687
+ return queue.map((task) => {
57688
+ const taskStatus = typeof task?.status === "string" ? task.status : void 0;
57689
+ const annotated = {
57690
+ ...task,
57691
+ taskStatus,
57692
+ dispatchedAt: task?.createdAt,
57693
+ ...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
57694
+ ...taskStatus === "completed" || taskStatus === "failed" ? {
57695
+ isHistorical: true,
57696
+ completedAt: task.updatedAt
57697
+ } : {}
57698
+ };
57699
+ if (taskStatus !== "assigned") return annotated;
57700
+ const updatedAt = new Date(task.updatedAt).getTime();
57701
+ const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
57702
+ if (ageMs === null || ageMs < STALE_ASSIGNED_QUEUE_MS) return annotated;
57703
+ return {
57704
+ ...annotated,
57705
+ stale: true,
57706
+ staleAssigned: true,
57707
+ staleReason: "assigned task has not reached a terminal state within 30 minutes",
57708
+ assignedAgeMs: ageMs
57709
+ };
57710
+ });
57711
+ }
57554
57712
  function unwrapCommandPayload(value) {
57555
57713
  let current = value;
57556
57714
  const seen = /* @__PURE__ */ new Set();
@@ -58180,8 +58338,16 @@ async function meshEnqueueTask(ctx, args) {
58180
58338
  }
58181
58339
  async function meshViewQueue(ctx, args) {
58182
58340
  try {
58183
- const queue = getQueue(ctx.mesh.id, { status: args.status });
58184
- return JSON.stringify({ success: true, queue }, null, 2);
58341
+ const queue = annotateQueueStaleness(getQueue(ctx.mesh.id, { status: args.status }));
58342
+ const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned);
58343
+ return JSON.stringify({
58344
+ success: true,
58345
+ queue,
58346
+ staleAssignedTasks,
58347
+ staleAssignedCount: staleAssignedTasks.length,
58348
+ // Back-compat alias for callers already reading the first hardening payload.
58349
+ staleAssignments: staleAssignedTasks
58350
+ }, null, 2);
58185
58351
  } catch (e) {
58186
58352
  return JSON.stringify({ success: false, error: e.message });
58187
58353
  }
@@ -58226,6 +58392,24 @@ async function meshSendTask(ctx, args) {
58226
58392
  if (node.policy?.readOnly) {
58227
58393
  return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
58228
58394
  }
58395
+ const duplicate = hasRecentDuplicateDispatch(ctx, args);
58396
+ if (duplicate.duplicate) {
58397
+ return JSON.stringify({
58398
+ success: true,
58399
+ duplicate: true,
58400
+ dispatched: false,
58401
+ warning: "Duplicate mesh_send_task suppressed: the same node/session/message was dispatched recently.",
58402
+ nodeId: args.node_id,
58403
+ sessionId: args.session_id,
58404
+ source: duplicate.source,
58405
+ previousDispatch: duplicate.entry ? {
58406
+ id: duplicate.entry.id,
58407
+ timestamp: duplicate.entry.timestamp || duplicate.entry.updatedAt || duplicate.entry.createdAt,
58408
+ nodeId: duplicate.entry.nodeId || duplicate.entry.targetNodeId || duplicate.entry.assignedNodeId,
58409
+ sessionId: duplicate.entry.sessionId || duplicate.entry.targetSessionId || duplicate.entry.assignedSessionId
58410
+ } : void 0
58411
+ });
58412
+ }
58229
58413
  try {
58230
58414
  if (!isLocalTransport(ctx.transport) && node.daemonId) {
58231
58415
  const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
@@ -58244,17 +58428,22 @@ async function meshSendTask(ctx, args) {
58244
58428
  providerType: cached2?.providerType
58245
58429
  });
58246
58430
  if (result.success) {
58431
+ const dispatchedSessionId = args.session_id || result.sessionId;
58247
58432
  try {
58248
58433
  appendLedgerEntry(ctx.mesh.id, {
58249
58434
  kind: "task_dispatched",
58250
58435
  nodeId: args.node_id,
58251
- sessionId: result.sessionId,
58252
- payload: { message: args.message, via: "p2p_direct" }
58436
+ sessionId: dispatchedSessionId,
58437
+ payload: {
58438
+ message: args.message,
58439
+ via: "p2p_direct",
58440
+ ...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
58441
+ }
58253
58442
  });
58254
58443
  } catch {
58255
58444
  }
58256
58445
  }
58257
- return JSON.stringify({ ...result, nodeId: args.node_id });
58446
+ return JSON.stringify({ ...result, nodeId: args.node_id, dispatched: result.success === true });
58258
58447
  }
58259
58448
  if (args.session_id && isLocalTransport(ctx.transport)) {
58260
58449
  const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
@@ -58299,7 +58488,10 @@ async function meshSendTask(ctx, args) {
58299
58488
  }
58300
58489
  }
58301
58490
  async function meshReadChat(ctx, args) {
58302
- const node = await findNodeWithRefresh(ctx, args.node_id);
58491
+ const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
58492
+ if (!node) {
58493
+ return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
58494
+ }
58303
58495
  if (isLocalTransport(ctx.transport)) {
58304
58496
  const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
58305
58497
  const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached2?.providerSessionId;