@adhdev/daemon-core 0.9.82-rc.401 → 0.9.82-rc.403

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.401",
3
+ "version": "0.9.82-rc.403",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.401",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.403",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -7,6 +7,7 @@ import { getMesh } from '../config/mesh-config.js';
7
7
  import { LOG } from '../logging/logger.js';
8
8
  import { appendLedgerEntry } from './mesh-ledger.js';
9
9
  import type { MeshLedgerKind } from './mesh-ledger.js';
10
+ import { createSessionDelivery } from './mesh-delivery-policy.js';
10
11
 
11
12
  export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
12
13
  export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
@@ -836,6 +837,28 @@ export function recordDirectDispatchTask(
836
837
  updatedAt: now,
837
838
  };
838
839
  MeshRuntimeStore.getInstance().insertQueueEntry(entry);
840
+ // R2 / NOTIF-DROP: a mission-attributed DIRECT dispatch (mesh_send_task) has
841
+ // already been handed to the transport by the time we materialise this assigned
842
+ // row — unlike a queue claim, there is no later delivery-confirmation write for
843
+ // it. Without a confirmed delivery record keyed by this taskId, the assigned-
844
+ // stranded watchdog (recoverStrandedAssignedDispatches → taskHasConfirmedDelivery)
845
+ // sees the row as never-confirmed after ASSIGNED_STRANDED_DEADLINE_MS and reclaims
846
+ // a task the worker already COMPLETED, dropping its agent:generating_completed
847
+ // (live PROBE-B repro: "never confirmed delivered → pending"). Record a confirmed
848
+ // delivery here so taskHasConfirmedDelivery() is true and the watchdog leaves the
849
+ // row to PHASE 4 completion reconcile. This point is only reached after the direct
850
+ // dispatch's result.success, so 'delivered' is the accurate state.
851
+ try {
852
+ createSessionDelivery({
853
+ meshId,
854
+ ...(opts.assignedNodeId ? { nodeId: opts.assignedNodeId } : {}),
855
+ ...(opts.assignedSessionId ? { sessionId: opts.assignedSessionId } : {}),
856
+ taskId,
857
+ kind: 'task',
858
+ message,
859
+ status: 'delivered',
860
+ });
861
+ } catch { /* best-effort — the assigned row is already recorded */ }
839
862
  return entry;
840
863
  });
841
864
  }
@@ -2276,6 +2276,75 @@ export class CliProviderInstance implements ProviderInstance {
2276
2276
  }
2277
2277
  } else if (newStatus === 'idle' && this.lastStatus === 'starting') {
2278
2278
  this.emitAgentReadyOnce(chatTitle, now);
2279
+ // GENERATING-BOUNDARY fast-collapse (R4, win32 startup-grace first turn):
2280
+ // a turn dispatched into the startup-grace window can START and FINISH while
2281
+ // the FSM is still in 'starting'. On a daemon whose claude-cli spec has NOT yet
2282
+ // synced the starting→busy edge (the primary cure lives in the spec's
2283
+ // idle→busy.from), the FSM never reaches 'busy'/generating, so
2284
+ // detectStatusTransition observes starting→idle DIRECTLY with no intervening
2285
+ // 'generating' frame. The idle→generating arm — the only path that sets
2286
+ // generatingStartedAt and arms the completion — never fired, so the completing
2287
+ // turn's agent:generating_completed is never emitted and the mesh coordinator
2288
+ // never learns the worker went idle. Synthesize the started+completed pair here.
2289
+ //
2290
+ // Discriminator (false-positive safe — must NOT fire on a benign boot):
2291
+ // adapter.currentTurnTaskId is set ONLY by onTurnStarted (a real turn STARTED
2292
+ // this boot) and persists past completion, so it cleanly separates the three
2293
+ // non-firing cases — a true idle boot (no turn → null), a queued-pending
2294
+ // first turn that only runs AFTER startup-grace drains the composer (onTurnStarted
2295
+ // not yet called → null; it completes normally later via idle→busy→idle), and a
2296
+ // turn STILL running at the 8s mark (hasAdapterPendingResponse() still true →
2297
+ // excluded so we don't fire a premature mid-turn completion; idle→busy self-
2298
+ // corrects once the FSM reaches idle). We fire only when a turn started AND has
2299
+ // already finished: started-this-boot && !still-in-flight.
2300
+ const startedTurnTaskId = typeof (this.adapter as any)?.currentTurnTaskId === 'string'
2301
+ && (this.adapter as any).currentTurnTaskId.trim()
2302
+ ? (this.adapter as any).currentTurnTaskId as string
2303
+ : undefined;
2304
+ const fastCollapsed = !!startedTurnTaskId
2305
+ && !this.hasAdapterPendingResponse()
2306
+ && !this.generatingStartedAt
2307
+ && !this.generatingDebouncePending;
2308
+ if (fastCollapsed) {
2309
+ let fcFinalSummary: string | undefined;
2310
+ let fcEvidenceSource: CompletionFinalAssistantEvidence['source'] = 'unavailable';
2311
+ try {
2312
+ const parsedMessages = this.adapter?.getScriptParsedStatus()?.messages;
2313
+ const evidence = this.completionFinalAssistantEvidence(parsedMessages);
2314
+ fcEvidenceSource = evidence.source;
2315
+ fcFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
2316
+ } catch { /* best-effort */ }
2317
+ const missingEvidence = ((this.provider as any).requiresFinalAssistantBeforeIdle === true || fcEvidenceSource === 'external-native') && !fcFinalSummary;
2318
+ // Mirror the short-generating idle path's suppression: a provider that
2319
+ // requires a final assistant (or external-native history) with NO confirmed
2320
+ // summary and NO mesh context emits nothing — the session is idle with no
2321
+ // confirmed turn, matching startup-blip semantics. With mesh context we still
2322
+ // emit so the coordinator can apply its own timeout/retry logic.
2323
+ const hasMeshContext = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
2324
+ if (missingEvidence && !hasMeshContext) {
2325
+ LOG.info('CLI', `[${this.type}] startup-grace fast-collapse suppressed: missing final assistant evidence, no mesh context (source=${fcEvidenceSource})`);
2326
+ } else {
2327
+ LOG.info('CLI', `[${this.type}] startup-grace fast-collapse: synthesizing started+completed (taskId=${startedTurnTaskId} source=${fcEvidenceSource} hadFinalSummary=${!!fcFinalSummary})`);
2328
+ // Retroactive started so the started→completed pair (and the chat bubble)
2329
+ // is well-formed; pushEvent stamps the per-turn taskId for CANON-B ack.
2330
+ this.pushEvent({ event: 'agent:generating_started', chatTitle, timestamp: now });
2331
+ if (this.isMeshWorkerSession()) {
2332
+ traceMeshEventStage('fired', this.meshTraceCtx(), `startup-grace fast-collapse (source=${fcEvidenceSource})`);
2333
+ }
2334
+ this.pushEvent({
2335
+ event: 'agent:generating_completed',
2336
+ chatTitle,
2337
+ duration: 0,
2338
+ timestamp: now,
2339
+ finalSummary: fcFinalSummary,
2340
+ completionDiagnostic: {
2341
+ reason: 'startup_grace_fast_collapse',
2342
+ finalAssistantEvidenceSource: fcEvidenceSource,
2343
+ ...(missingEvidence ? { blockReason: 'missing_final_assistant' } : {}),
2344
+ },
2345
+ });
2346
+ }
2347
+ }
2279
2348
  } else if (newStatus === 'error') {
2280
2349
  if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
2281
2350
  this.generatingDebouncePending = null;