@adhdev/daemon-core 0.9.82-rc.174 → 0.9.82-rc.176
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/dist/index.js +86 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +86 -10
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +3 -0
- package/dist/providers/spec/driver.d.ts +10 -0
- package/package.json +1 -1
- package/src/providers/cli-provider-instance.ts +90 -13
- package/src/providers/spec/driver.ts +39 -1
|
@@ -104,6 +104,9 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
104
104
|
private completedDebouncePending;
|
|
105
105
|
private enforceFreshSessionLaunchIfNeeded;
|
|
106
106
|
private completionHasFinalAssistantMessage;
|
|
107
|
+
private readExternalCompletionMessages;
|
|
108
|
+
private completionFinalAssistantEvidence;
|
|
109
|
+
private completionFinalSummary;
|
|
107
110
|
private buildCompletedFinalizationDiagnostic;
|
|
108
111
|
private hasAdapterPendingResponse;
|
|
109
112
|
private shouldSuppressStaleParsedBusyStatus;
|
|
@@ -86,6 +86,16 @@ export interface SpecDriverOpts {
|
|
|
86
86
|
*/
|
|
87
87
|
extraCliArgs?: string[];
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Pick the effective delay (ms) to wait between writing the prompt body and
|
|
91
|
+
* writing the submit_key. Exported for tests; production callers go through
|
|
92
|
+
* actuallySendMessage. Floors to SUBMIT_DELAY_FLOOR_MS so specs that omit
|
|
93
|
+
* delay_ms_before_submit (e.g. claude, antigravity) still get baseline
|
|
94
|
+
* protection against the paste→submit race. Adds line-count bonus so
|
|
95
|
+
* multi-line prompts get more settling time on TUIs that re-render per
|
|
96
|
+
* embedded `\n`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number;
|
|
89
99
|
export declare class SpecDriver {
|
|
90
100
|
private readonly opts;
|
|
91
101
|
private spec;
|
package/package.json
CHANGED
|
@@ -64,6 +64,12 @@ type CompletedFinalizationBlock = {
|
|
|
64
64
|
terminal?: boolean;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
type CompletionFinalAssistantEvidence = {
|
|
68
|
+
present: boolean;
|
|
69
|
+
messages: unknown[];
|
|
70
|
+
source: 'parsed' | 'external-native' | 'unavailable';
|
|
71
|
+
};
|
|
72
|
+
|
|
67
73
|
const COMPLETED_FINALIZATION_RETRY_MS = 1000;
|
|
68
74
|
const COMPLETED_FINALIZATION_MAX_WAIT_MS = 30_000;
|
|
69
75
|
|
|
@@ -890,6 +896,56 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
890
896
|
return true;
|
|
891
897
|
}
|
|
892
898
|
|
|
899
|
+
private readExternalCompletionMessages(): unknown[] | null {
|
|
900
|
+
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
901
|
+
if (!adapterOwnsMessagesElsewhere) return null;
|
|
902
|
+
if (!this.providerSessionId) return null;
|
|
903
|
+
if (!isNativeSourceCanonicalHistory(this.provider.nativeHistory)) return null;
|
|
904
|
+
|
|
905
|
+
const restoredHistory = readProviderChatHistory(this.type, {
|
|
906
|
+
canonicalHistory: this.provider.nativeHistory,
|
|
907
|
+
historySessionId: this.providerSessionId,
|
|
908
|
+
workspace: this.workingDir,
|
|
909
|
+
offset: 0,
|
|
910
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
911
|
+
historyBehavior: this.provider.historyBehavior,
|
|
912
|
+
scripts: this.provider.scripts as any,
|
|
913
|
+
sessionStartedAtMs: this.startedAt,
|
|
914
|
+
});
|
|
915
|
+
if (restoredHistory.source !== 'provider-native') return null;
|
|
916
|
+
return restoredHistory.messages;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private completionFinalAssistantEvidence(parsedMessages: unknown): CompletionFinalAssistantEvidence {
|
|
920
|
+
if (this.completionHasFinalAssistantMessage(parsedMessages)) {
|
|
921
|
+
return {
|
|
922
|
+
present: true,
|
|
923
|
+
messages: Array.isArray(parsedMessages) ? parsedMessages : [],
|
|
924
|
+
source: 'parsed',
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const externalMessages = this.readExternalCompletionMessages();
|
|
929
|
+
if (externalMessages) {
|
|
930
|
+
return {
|
|
931
|
+
present: this.completionHasFinalAssistantMessage(externalMessages),
|
|
932
|
+
messages: externalMessages,
|
|
933
|
+
source: 'external-native',
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return {
|
|
938
|
+
present: false,
|
|
939
|
+
messages: Array.isArray(parsedMessages) ? parsedMessages : [],
|
|
940
|
+
source: 'unavailable',
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
private completionFinalSummary(parsedMessages: unknown): string | undefined {
|
|
945
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
946
|
+
return extractFinalSummaryFromMessages(evidence.messages as any);
|
|
947
|
+
}
|
|
948
|
+
|
|
893
949
|
private buildCompletedFinalizationDiagnostic(args: {
|
|
894
950
|
blockReason: string;
|
|
895
951
|
latestStatus?: any;
|
|
@@ -906,7 +962,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
906
962
|
parseError = error?.message || String(error);
|
|
907
963
|
}
|
|
908
964
|
|
|
909
|
-
const
|
|
965
|
+
const evidence = this.completionFinalAssistantEvidence(parsed?.messages);
|
|
966
|
+
const visibleMessages = (Array.isArray(evidence.messages) ? evidence.messages : [])
|
|
910
967
|
.filter((message: any) => isUserFacingChatMessage(message as ChatMessage));
|
|
911
968
|
const lastVisible = visibleMessages[visibleMessages.length - 1] as ChatMessage | undefined;
|
|
912
969
|
const lastVisibleRole = typeof lastVisible?.role === 'string' ? lastVisible.role.trim().toLowerCase() : null;
|
|
@@ -926,7 +983,8 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
926
983
|
latestVisibleStatus: args.latestVisibleStatus,
|
|
927
984
|
parsedStatus: typeof parsed?.status === 'string' ? parsed.status : (parseError ? 'parse_error' : 'unknown'),
|
|
928
985
|
parseError: parseError || undefined,
|
|
929
|
-
finalAssistantPresent:
|
|
986
|
+
finalAssistantPresent: evidence.present,
|
|
987
|
+
finalAssistantEvidenceSource: evidence.source,
|
|
930
988
|
visibleMessageCount: visibleMessages.length,
|
|
931
989
|
lastVisibleRole,
|
|
932
990
|
lastVisibleKind,
|
|
@@ -1003,14 +1061,26 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1003
1061
|
return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
|
|
1004
1062
|
}
|
|
1005
1063
|
if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
|
|
1006
|
-
// SpecCliAdapter never populates parsed.messages — chat history flows
|
|
1007
|
-
// through the daemon's native-history pipeline, not the status hook.
|
|
1008
|
-
// Skipping the final-assistant gate avoids a 30s stall on every turn
|
|
1009
|
-
// for spec-routed providers (agy / codex / claude / hermes).
|
|
1010
1064
|
const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1065
|
+
const finalAssistantEvidence = this.completionFinalAssistantEvidence(parsed?.messages);
|
|
1066
|
+
if (!finalAssistantEvidence.present) {
|
|
1067
|
+
if (adapterOwnsMessagesElsewhere) {
|
|
1068
|
+
if (finalAssistantEvidence.source === 'external-native') {
|
|
1069
|
+
return { reason: 'missing_final_assistant', terminal: true };
|
|
1070
|
+
}
|
|
1071
|
+
// SpecCliAdapter never populates parsed.messages — chat history flows
|
|
1072
|
+
// through the daemon's native-history pipeline, not the status hook.
|
|
1073
|
+
// If that pipeline is unavailable, keep the old skip behavior for
|
|
1074
|
+
// providers that have not opted into strict final-assistant evidence.
|
|
1075
|
+
if ((this.provider as any).requiresFinalAssistantBeforeIdle === true) {
|
|
1076
|
+
return { reason: 'missing_final_assistant', terminal: true };
|
|
1077
|
+
}
|
|
1078
|
+
} else {
|
|
1079
|
+
return {
|
|
1080
|
+
reason: 'missing_final_assistant',
|
|
1081
|
+
terminal: (this.provider as any).requiresFinalAssistantBeforeIdle === true,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1014
1084
|
}
|
|
1015
1085
|
|
|
1016
1086
|
// Guard: if the screen still shows an approval/choice prompt as the last visible text,
|
|
@@ -1082,7 +1152,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1082
1152
|
timestamp: pending.timestamp,
|
|
1083
1153
|
finalSummary: blockReason.startsWith('parsed_status:')
|
|
1084
1154
|
? ''
|
|
1085
|
-
:
|
|
1155
|
+
: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1086
1156
|
completionDiagnostic,
|
|
1087
1157
|
});
|
|
1088
1158
|
this.completedDebouncePending = null;
|
|
@@ -1098,7 +1168,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1098
1168
|
chatTitle: pending.chatTitle,
|
|
1099
1169
|
duration: pending.duration,
|
|
1100
1170
|
timestamp: pending.timestamp,
|
|
1101
|
-
finalSummary:
|
|
1171
|
+
finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages),
|
|
1102
1172
|
});
|
|
1103
1173
|
this.completedDebouncePending = null;
|
|
1104
1174
|
this.completedDebounceTimer = null;
|
|
@@ -1255,8 +1325,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1255
1325
|
// Emit completion for mesh task association even though the UI generating
|
|
1256
1326
|
// started/completed pair is suppressed (too short for visible UI update).
|
|
1257
1327
|
let shortFinalSummary: string | undefined;
|
|
1258
|
-
|
|
1259
|
-
|
|
1328
|
+
let shortEvidenceSource: CompletionFinalAssistantEvidence['source'] = 'unavailable';
|
|
1329
|
+
try {
|
|
1330
|
+
const parsedMessages = this.adapter?.getScriptParsedStatus()?.messages;
|
|
1331
|
+
const evidence = this.completionFinalAssistantEvidence(parsedMessages);
|
|
1332
|
+
shortEvidenceSource = evidence.source;
|
|
1333
|
+
shortFinalSummary = extractFinalSummaryFromMessages(evidence.messages as any);
|
|
1334
|
+
} catch { /* best-effort */ }
|
|
1335
|
+
if (((this.provider as any).requiresFinalAssistantBeforeIdle === true || shortEvidenceSource === 'external-native') && !shortFinalSummary) {
|
|
1260
1336
|
LOG.info('CLI', `[${this.type}] suppressed short completion without final assistant evidence`);
|
|
1261
1337
|
} else {
|
|
1262
1338
|
this.pushEvent({
|
|
@@ -1268,6 +1344,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1268
1344
|
completionDiagnostic: {
|
|
1269
1345
|
reason: 'short_generating_suppressed',
|
|
1270
1346
|
shortDurationMs,
|
|
1347
|
+
finalAssistantEvidenceSource: shortEvidenceSource,
|
|
1271
1348
|
},
|
|
1272
1349
|
});
|
|
1273
1350
|
}
|
|
@@ -102,6 +102,36 @@ const STARTUP_GRACE_MS = 2500;
|
|
|
102
102
|
* the post-turn idle indication noticeably. */
|
|
103
103
|
const BUSY_HOLD_MS = 6000;
|
|
104
104
|
|
|
105
|
+
/** Minimum delay between the prompt body and the submit_key in send_message.
|
|
106
|
+
* Claude / antigravity specs ship without an explicit delay_ms_before_submit
|
|
107
|
+
* and their TUIs sometimes drop the `\r` event when it arrives in the same
|
|
108
|
+
* PTY chunk as the text body — the prompt sits visible in the input field
|
|
109
|
+
* but is never submitted until the user hits Enter manually. 200ms matches
|
|
110
|
+
* codex's explicit setting and is barely perceptible to a human caller. */
|
|
111
|
+
const SUBMIT_DELAY_FLOOR_MS = 200;
|
|
112
|
+
|
|
113
|
+
function countNewlines(s: string): number {
|
|
114
|
+
let n = 0;
|
|
115
|
+
for (let i = 0; i < s.length; i += 1) if (s.charCodeAt(i) === 10) n += 1;
|
|
116
|
+
return n;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Pick the effective delay (ms) to wait between writing the prompt body and
|
|
121
|
+
* writing the submit_key. Exported for tests; production callers go through
|
|
122
|
+
* actuallySendMessage. Floors to SUBMIT_DELAY_FLOOR_MS so specs that omit
|
|
123
|
+
* delay_ms_before_submit (e.g. claude, antigravity) still get baseline
|
|
124
|
+
* protection against the paste→submit race. Adds line-count bonus so
|
|
125
|
+
* multi-line prompts get more settling time on TUIs that re-render per
|
|
126
|
+
* embedded `\n`.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveSubmitDelayMs(specBeforeSubmit: number | undefined, text: string): number {
|
|
129
|
+
const lines = countNewlines(text);
|
|
130
|
+
const linesBonus = Math.min(800, lines * 80);
|
|
131
|
+
const spec = typeof specBeforeSubmit === 'number' && specBeforeSubmit > 0 ? specBeforeSubmit : 0;
|
|
132
|
+
return Math.max(spec, SUBMIT_DELAY_FLOOR_MS + linesBonus);
|
|
133
|
+
}
|
|
134
|
+
|
|
105
135
|
export class SpecDriver {
|
|
106
136
|
private spec!: CliSpec;
|
|
107
137
|
private adapter!: TerminalAdapter;
|
|
@@ -371,7 +401,15 @@ export class SpecDriver {
|
|
|
371
401
|
private actuallySendMessage(text: string): void {
|
|
372
402
|
const sm = this.spec.send_message;
|
|
373
403
|
const perChar = sm.delay_ms_per_char ?? 0;
|
|
374
|
-
|
|
404
|
+
// Floor the gap between text and submit_key. Without this, claude /
|
|
405
|
+
// antigravity specs (which leave delay_ms_before_submit unset)
|
|
406
|
+
// race: text bytes and `\r` arrive back-to-back, the TUI processes
|
|
407
|
+
// the `\r` while still digesting the text input, and the prompt
|
|
408
|
+
// sits visible in the input field but never submits — the user has
|
|
409
|
+
// to press Enter manually. Scale with line count so multi-line
|
|
410
|
+
// pastes (which take longer for the TUI to render) get more
|
|
411
|
+
// settling time.
|
|
412
|
+
const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
|
|
375
413
|
if (perChar === 0) {
|
|
376
414
|
this.adapter.send_keys(text);
|
|
377
415
|
if (beforeSubmit > 0) setTimeout(() => this.adapter.send_keys(sm.submit_key), beforeSubmit);
|