@adhdev/daemon-core 0.9.82-rc.56 → 0.9.82-rc.58

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.
@@ -101,6 +101,7 @@ export declare class CliProviderInstance implements ProviderInstance {
101
101
  private completedDebouncePending;
102
102
  private enforceFreshSessionLaunchIfNeeded;
103
103
  private completionHasFinalAssistantMessage;
104
+ private buildCompletedFinalizationDiagnostic;
104
105
  private hasAdapterPendingResponse;
105
106
  private shouldSuppressStaleParsedBusyStatus;
106
107
  private getCompletedFinalizationBlockReason;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.56",
3
+ "version": "0.9.82-rc.58",
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",
@@ -36,7 +36,9 @@ export function resolveCliSpawnPlan(options: {
36
36
  : spawnConfig.command;
37
37
  const binaryPath = findBinary(configuredCommand);
38
38
  const isWin = os.platform() === 'win32';
39
- const allArgs = [...spawnConfig.args, ...extraArgs];
39
+ const allArgs = [...spawnConfig.args, ...extraArgs].map((arg) =>
40
+ typeof arg === 'string' ? arg.replace(/\{\{workingDir\}\}/g, workingDir) : arg,
41
+ );
40
42
 
41
43
  let shellCmd: string;
42
44
  let shellArgs: string[];
@@ -460,16 +460,16 @@ function reconcileInlineMeshCache(cached: any, incoming: any): any {
460
460
  const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
461
461
  if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
462
462
 
463
- const incomingById = new Map<string, any>();
464
- for (const node of incomingNodes) {
463
+ const cachedById = new Map<string, any>();
464
+ for (const node of cachedNodes) {
465
465
  const nodeId = readInlineMeshNodeId(node);
466
- if (nodeId) incomingById.set(nodeId, node);
466
+ if (nodeId) cachedById.set(nodeId, node);
467
467
  }
468
468
 
469
- const nodes = cachedNodes.map((cachedNode: any) => {
470
- const nodeId = readInlineMeshNodeId(cachedNode);
471
- const incomingNode = nodeId ? incomingById.get(nodeId) : undefined;
472
- if (!incomingNode) return cachedNode;
469
+ const nodes = incomingNodes.map((incomingNode: any) => {
470
+ const nodeId = readInlineMeshNodeId(incomingNode);
471
+ const cachedNode = nodeId ? cachedById.get(nodeId) : undefined;
472
+ if (!cachedNode) return incomingNode;
473
473
  if (hasInlineMeshTransientNodeState(incomingNode)) {
474
474
  return { ...cachedNode, ...incomingNode };
475
475
  }
@@ -673,7 +673,7 @@ async function hydrateInlineMeshDirectTruth(args: {
673
673
  continue;
674
674
  }
675
675
 
676
- if (isSelfNode && fs.existsSync(workspace)) {
676
+ if (fs.existsSync(workspace)) {
677
677
  try {
678
678
  const localGit = await getGitRepoStatus(workspace, { timeoutMs: 10_000, refreshUpstream: true });
679
679
  if (localGit?.isGitRepo) {
@@ -135,10 +135,21 @@ function isMeshCoordinatorEvent(eventName: unknown): eventName is string {
135
135
  }
136
136
 
137
137
  function formatCompletionMetadata(event: Record<string, unknown>): string {
138
+ const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === 'object'
139
+ ? event.completionDiagnostic as Record<string, unknown>
140
+ : null;
141
+ const diagnosticReason = completionDiagnostic
142
+ ? readNonEmptyString(completionDiagnostic.blockReason) || 'present'
143
+ : '';
144
+ const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
145
+ ? String(completionDiagnostic.finalAssistantPresent)
146
+ : '';
138
147
  const parts = [
139
148
  readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
140
149
  readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
141
150
  readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
151
+ diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
152
+ finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
142
153
  ].filter(Boolean);
143
154
  return parts.length > 0 ? ` (${parts.join('; ')})` : '';
144
155
  }
@@ -836,6 +847,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
836
847
  taskId: completedTaskForLedger?.id || undefined,
837
848
  providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
838
849
  finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
850
+ completionDiagnostic: args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === 'object'
851
+ ? args.metadataEvent.completionDiagnostic
852
+ : undefined,
839
853
  evidence: completionEvidence,
840
854
  },
841
855
  });
@@ -743,6 +743,55 @@ export class CliProviderInstance implements ProviderInstance {
743
743
  return role === 'assistant' && !!content;
744
744
  }
745
745
 
746
+ private buildCompletedFinalizationDiagnostic(args: {
747
+ blockReason: string;
748
+ latestStatus?: any;
749
+ latestVisibleStatus: string;
750
+ waitedMs: number;
751
+ pending: CompletedDebouncePending;
752
+ emittedAfterFinalizationTimeout: boolean;
753
+ }): Record<string, unknown> {
754
+ let parsed: any = null;
755
+ let parseError: string | undefined;
756
+ try {
757
+ parsed = this.adapter.getScriptParsedStatus();
758
+ } catch (error: any) {
759
+ parseError = error?.message || String(error);
760
+ }
761
+
762
+ const visibleMessages = (Array.isArray(parsed?.messages) ? parsed.messages : [])
763
+ .filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
764
+ const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
765
+ const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
766
+ const lastVisibleKind = typeof (lastVisible as any)?.kind === 'string' ? (lastVisible as any).kind : null;
767
+ const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
768
+
769
+ return {
770
+ providerType: this.type,
771
+ sessionId: this.instanceId,
772
+ providerSessionId: this.providerSessionId || null,
773
+ workspace: this.workingDir,
774
+ blockReason: args.blockReason,
775
+ emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
776
+ waitedMs: args.waitedMs,
777
+ maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
778
+ adapterStatus: typeof args.latestStatus?.status === 'string' ? args.latestStatus.status : null,
779
+ latestVisibleStatus: args.latestVisibleStatus,
780
+ parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
781
+ parseError: parseError || undefined,
782
+ finalAssistantPresent: this.completionHasFinalAssistantMessage(parsed?.messages),
783
+ visibleMessageCount: visibleMessages.length,
784
+ lastVisibleRole,
785
+ lastVisibleKind,
786
+ lastVisibleContentLength,
787
+ pendingStartedAt: this.generatingStartedAt || null,
788
+ pendingFirstObservedAt: args.pending.firstObservedAt,
789
+ pendingTimestamp: args.pending.timestamp,
790
+ pendingDurationSec: args.pending.duration,
791
+ previousBlockReason: args.pending.loggedBlockReason || null,
792
+ };
793
+ }
794
+
746
795
  private hasAdapterPendingResponse(): boolean {
747
796
  const adapterAny = this.adapter as any;
748
797
  if (adapterAny?.isWaitingForResponse === true) return true;
@@ -828,7 +877,23 @@ export class CliProviderInstance implements ProviderInstance {
828
877
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
829
878
  return;
830
879
  }
831
- LOG.warn('CLI', `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
880
+ const completionDiagnostic = this.buildCompletedFinalizationDiagnostic({
881
+ blockReason,
882
+ latestStatus,
883
+ latestVisibleStatus,
884
+ waitedMs,
885
+ pending,
886
+ emittedAfterFinalizationTimeout: true,
887
+ });
888
+ LOG.warn('CLI', `[${this.type}] emitting completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
889
+ this.pushEvent({
890
+ event: 'agent:generating_completed',
891
+ chatTitle: pending.chatTitle,
892
+ duration: pending.duration,
893
+ timestamp: pending.timestamp,
894
+ finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
895
+ completionDiagnostic,
896
+ });
832
897
  this.completedDebouncePending = null;
833
898
  this.completedDebounceTimer = null;
834
899
  this.generatingStartedAt = 0;