@adhdev/daemon-core 0.9.82-rc.133 → 0.9.82-rc.134

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.133",
3
+ "version": "0.9.82-rc.134",
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",
@@ -415,22 +415,38 @@ function findRecentTerminalLedgerEvidence(args: {
415
415
  meshId: string;
416
416
  sessionId?: string;
417
417
  nodeId?: string;
418
- }): { kind: MeshLedgerKind; payload: Record<string, unknown>; timestamp: string } | null {
418
+ }): { id: string; kind: MeshLedgerKind; payload: Record<string, unknown>; timestamp: string } | null {
419
419
  if (!args.sessionId && !args.nodeId) return null;
420
420
  const entries = readLedgerEntries(args.meshId);
421
421
  for (let i = entries.length - 1; i >= 0; i--) {
422
422
  const entry = entries[i];
423
423
  if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
424
424
  if (args.sessionId && entry.sessionId === args.sessionId) {
425
- return { kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
425
+ return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
426
426
  }
427
427
  if (!args.sessionId && args.nodeId && entry.nodeId === args.nodeId) {
428
- return { kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
428
+ return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
429
429
  }
430
430
  }
431
431
  return null;
432
432
  }
433
433
 
434
+ // Returns true when a task_dispatched entry for the given session appears AFTER the terminal
435
+ // entry (identified by terminalId) in ledger order. Positional (append) order is used rather
436
+ // than timestamp comparison because both entries may share the same millisecond.
437
+ function hasDispatchAfterTerminal(meshId: string, sessionId: string, terminalId: string): boolean {
438
+ const entries = readLedgerEntries(meshId);
439
+ let pastTerminal = false;
440
+ for (const entry of entries) {
441
+ if (!pastTerminal) {
442
+ if (entry.id === terminalId) pastTerminal = true;
443
+ continue;
444
+ }
445
+ if (entry.kind === 'task_dispatched' && entry.sessionId === sessionId) return true;
446
+ }
447
+ return false;
448
+ }
449
+
434
450
  function buildLongGeneratingCompletionReconciliation(args: {
435
451
  meshId: string;
436
452
  nodeId?: string;
@@ -1060,17 +1076,22 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1060
1076
  nodeId: eventNodeId || undefined,
1061
1077
  });
1062
1078
  if (terminal?.kind === 'task_completed' && !sessionHasActiveAssignment(args.meshId, eventSessionId)) {
1063
- const terminalProviderSessionId = readNonEmptyString(terminal.payload.providerSessionId);
1064
- const terminalFinalSummary = readNonEmptyString(terminal.payload.finalSummary);
1065
- const eventProviderSessionId = readNonEmptyString(args.metadataEvent.providerSessionId);
1066
- const eventFinalSummary = readNonEmptyString(args.metadataEvent.finalSummary);
1067
- if (
1068
- (terminalProviderSessionId && terminalProviderSessionId === eventProviderSessionId)
1069
- || (terminalFinalSummary && terminalFinalSummary === eventFinalSummary)
1070
- || args.metadataEvent.source === 'long_generating_reconciliation'
1071
- ) {
1072
- LOG.info('MeshEvents', `Suppressed duplicate completion with existing terminal ledger evidence for mesh ${args.meshId} session ${eventSessionId}`);
1073
- return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true, terminalLedgerEvidence: true };
1079
+ // If a new task_dispatched was recorded for this session after the prior terminal,
1080
+ // this completion belongs to the new task — never suppress it as a duplicate.
1081
+ const newDispatchAfterTerminal = hasDispatchAfterTerminal(args.meshId, eventSessionId, terminal.id);
1082
+ if (!newDispatchAfterTerminal) {
1083
+ const terminalProviderSessionId = readNonEmptyString(terminal.payload.providerSessionId);
1084
+ const terminalFinalSummary = readNonEmptyString(terminal.payload.finalSummary);
1085
+ const eventProviderSessionId = readNonEmptyString(args.metadataEvent.providerSessionId);
1086
+ const eventFinalSummary = readNonEmptyString(args.metadataEvent.finalSummary);
1087
+ if (
1088
+ (terminalProviderSessionId && terminalProviderSessionId === eventProviderSessionId)
1089
+ || (terminalFinalSummary && terminalFinalSummary === eventFinalSummary)
1090
+ || args.metadataEvent.source === 'long_generating_reconciliation'
1091
+ ) {
1092
+ LOG.info('MeshEvents', `Suppressed duplicate completion with existing terminal ledger evidence for mesh ${args.meshId} session ${eventSessionId}`);
1093
+ return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true, terminalLedgerEvidence: true };
1094
+ }
1074
1095
  }
1075
1096
  }
1076
1097
  const duplicateCompletion = isDuplicateMeshCompletionEvent({
@@ -899,7 +899,16 @@ export class CliProviderInstance implements ProviderInstance {
899
899
  if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
900
900
  if (adapterRawStatus !== 'idle') return false;
901
901
  if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
902
- return !this.hasAdapterPendingResponse();
902
+ if (this.hasAdapterPendingResponse()) return false;
903
+ // Do not suppress when the adapter's raw response buffer is still non-empty.
904
+ // This catches the case where isWaitingForResponse has already flipped to false
905
+ // (so getPartialResponse() returns '') but the provider's native parser still
906
+ // reports generating because it's parsing buffered content. Suppressing the
907
+ // finalization block here would emit a false completion event while the provider
908
+ // session is still actively processing its response stream.
909
+ const adapterAny = this.adapter as any;
910
+ if (typeof adapterAny?.responseBuffer === 'string' && adapterAny.responseBuffer.trim()) return false;
911
+ return true;
903
912
  }
904
913
 
905
914
  private getCompletedFinalizationBlock(latestVisibleStatus: string): CompletedFinalizationBlock | null {
@@ -1122,12 +1131,30 @@ export class CliProviderInstance implements ProviderInstance {
1122
1131
  }
1123
1132
  } else if (newStatus === 'idle' && (this.lastStatus === 'generating' || this.lastStatus === 'waiting_approval')) {
1124
1133
  const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1000) : 0;
1125
- // If debounce still pending (generating lasted < 1s), cancel both events
1134
+ // If debounce still pending (generating lasted < 1s), cancel both UI events.
1135
+ // Still emit agent:generating_completed so mesh orchestration can record
1136
+ // task_completed for direct dispatches that complete faster than the debounce.
1126
1137
  if (this.generatingDebouncePending) {
1127
- LOG.info('CLI', `[${this.type}] suppressed short generating (${now - this.generatingStartedAt}ms)`);
1138
+ const shortDurationMs = this.generatingStartedAt ? now - this.generatingStartedAt : 0;
1139
+ LOG.info('CLI', `[${this.type}] suppressed short generating (${shortDurationMs}ms)`);
1128
1140
  if (this.generatingDebounceTimer) { clearTimeout(this.generatingDebounceTimer); this.generatingDebounceTimer = null; }
1129
1141
  this.generatingDebouncePending = null;
1130
1142
  this.generatingStartedAt = 0;
1143
+ // Emit completion for mesh task association even though the UI generating
1144
+ // started/completed pair is suppressed (too short for visible UI update).
1145
+ let shortFinalSummary: string | undefined;
1146
+ try { shortFinalSummary = extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages); } catch { /* best-effort */ }
1147
+ this.pushEvent({
1148
+ event: 'agent:generating_completed',
1149
+ chatTitle,
1150
+ duration: 0,
1151
+ timestamp: now,
1152
+ finalSummary: shortFinalSummary,
1153
+ completionDiagnostic: {
1154
+ reason: 'short_generating_suppressed',
1155
+ shortDurationMs,
1156
+ },
1157
+ });
1131
1158
  } else {
1132
1159
  // Debounce completed, then require the rich transcript path that read_chat
1133
1160
  // uses to show an idle turn whose last user-facing message is assistant.