@adhdev/daemon-core 0.9.82-rc.539 → 0.9.82-rc.540
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/cli-adapters/provider-cli-adapter.d.ts +32 -0
- package/dist/index.js +63 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -15
- package/dist/index.mjs.map +1 -1
- package/dist/providers/approval-utils.d.ts +1 -0
- package/package.json +3 -3
- package/src/cli-adapters/provider-cli-adapter.ts +47 -1
- package/src/commands/chat-commands-write.ts +7 -2
- package/src/providers/approval-utils.ts +38 -11
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProviderModule } from './contracts.js';
|
|
2
2
|
export declare function normalizeApprovalLabel(value: string): string;
|
|
3
|
+
export declare function isNegativeApprovalLabel(value: string): boolean;
|
|
3
4
|
/**
|
|
4
5
|
* True when any of the given button labels reads as a decline/negative option
|
|
5
6
|
* (No / Deny / Cancel / Skip / …). Used as the second half of an approval-modal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.540",
|
|
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",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "0.9.82-rc.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.540",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.540",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -421,6 +421,49 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
421
421
|
return this.providerOwnsTranscript() && this.provider.transcriptContext === 'full';
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
+
/**
|
|
425
|
+
* MULTI-TURN TRANSCRIPT FLICKER (kimi): whether this provider reconstructs
|
|
426
|
+
* its ENTIRE transcript from the rendered PTY buffer on every read and so
|
|
427
|
+
* must be fed the full accumulated buffer rather than the current-turn slice.
|
|
428
|
+
*
|
|
429
|
+
* The turn-scope slice (buildCliParseInput → sliceFromOffset(accumulatedBuffer,
|
|
430
|
+
* scope.bufferStart)) exists so a mid-turn parse only sees the fresh output of
|
|
431
|
+
* the CURRENT turn — correct for status/streaming detection and for providers
|
|
432
|
+
* that reassemble prior history elsewhere (native-history on disk, or a
|
|
433
|
+
* provider-owned full-context parser). But a pure-PTY provider with NO native
|
|
434
|
+
* history and NO provider transcript authority, whose tui parser walks the
|
|
435
|
+
* whole buffer for its per-turn bubble markers (kimi's `●`/`✨` bullets),
|
|
436
|
+
* loses every prior turn's bubbles the instant a new turn starts: bufferStart
|
|
437
|
+
* jumps to the new turn's offset, the slice drops turns 1..n-1, and until the
|
|
438
|
+
* new turn emits its first bullet parseSession returns zero messages — so
|
|
439
|
+
* read_chat momentarily goes EMPTY and the dashboard clears every bubble,
|
|
440
|
+
* then restores them once output arrives (the observed flicker).
|
|
441
|
+
*
|
|
442
|
+
* accumulatedBuffer is the terminal-EMULATED rendered scrollback (overwritten
|
|
443
|
+
* cells collapsed), not raw PTY append, so parsing it in full yields the clean
|
|
444
|
+
* cumulative transcript with no repaint duplication. Scope to exactly this
|
|
445
|
+
* class (tui transcriptPty scope 'buffer' + no native history + not
|
|
446
|
+
* provider-owned) so no other provider's turn-scoped parse changes.
|
|
447
|
+
*/
|
|
448
|
+
private parsesFullPtyTranscriptFromBuffer(): boolean {
|
|
449
|
+
if (this.providerOwnsTranscript()) return false;
|
|
450
|
+
// nativeHistory is a top-level provider field not surfaced on
|
|
451
|
+
// CliProviderModule; read it via the same structural cast used for `tui`.
|
|
452
|
+
if ((this.provider as { nativeHistory?: unknown }).nativeHistory) return false;
|
|
453
|
+
const transcriptPty = (this.provider.tui as { transcriptPty?: { scope?: unknown } } | undefined)?.transcriptPty;
|
|
454
|
+
return transcriptPty?.scope === 'buffer';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* The turn scope to feed the transcript parser. Normally the live turn scope
|
|
459
|
+
* (so a mid-turn parse is current-turn-only), but null (= full accumulated
|
|
460
|
+
* buffer) for pure-PTY full-transcript providers so prior turns never drop.
|
|
461
|
+
* See {@link parsesFullPtyTranscriptFromBuffer}.
|
|
462
|
+
*/
|
|
463
|
+
private transcriptParseScope(): TurnParseScope | null {
|
|
464
|
+
return this.parsesFullPtyTranscriptFromBuffer() ? null : this.engine.currentTurnScope;
|
|
465
|
+
}
|
|
466
|
+
|
|
424
467
|
private getIdleFinishConfirmMs(): number {
|
|
425
468
|
return this.timeouts.idleFinishConfirm;
|
|
426
469
|
}
|
|
@@ -918,7 +961,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
918
961
|
baseMessages: [],
|
|
919
962
|
partialResponse: this.responseBuffer,
|
|
920
963
|
isWaitingForResponse: this.engine.isWaitingForResponse,
|
|
921
|
-
scope
|
|
964
|
+
// Full accumulated buffer (scope null) for pure-PTY full-transcript
|
|
965
|
+
// providers so prior turns' bubbles never drop when a new turn starts;
|
|
966
|
+
// the current-turn slice otherwise. See parsesFullPtyTranscriptFromBuffer.
|
|
967
|
+
scope: this.transcriptParseScope(),
|
|
922
968
|
runtimeSettings: this.runtimeSettings,
|
|
923
969
|
spawnAt: this.spawnAt,
|
|
924
970
|
});
|
|
@@ -8,7 +8,7 @@ import type { CommandResult, CommandHelpers } from './handler.js';
|
|
|
8
8
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
9
9
|
import { normalizeInputEnvelope, type InputEnvelope, type ProviderModule, type ProviderScripts } from '../providers/contracts.js';
|
|
10
10
|
import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../providers/provider-input-support.js';
|
|
11
|
-
import { pickApprovalButton } from '../providers/approval-utils.js';
|
|
11
|
+
import { pickApprovalButton, isNegativeApprovalLabel } from '../providers/approval-utils.js';
|
|
12
12
|
import { LOG } from '../logging/logger.js';
|
|
13
13
|
import {
|
|
14
14
|
READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS,
|
|
@@ -773,7 +773,12 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
773
773
|
buttonIndex = buttons.findIndex(b => b.toLowerCase().includes(btnLower));
|
|
774
774
|
}
|
|
775
775
|
if (buttonIndex < 0 && (action === 'reject' || action === 'deny')) {
|
|
776
|
-
|
|
776
|
+
// Use the canonical negative detector (no|deny|reject|cancel|skip|exit|stop)
|
|
777
|
+
// rather than a narrow /deny|reject|no/ — cursor's decline button is
|
|
778
|
+
// "Skip", which the old regex missed, so reject reported "did not match
|
|
779
|
+
// any visible button" and left the modal wedged. This mirrors the
|
|
780
|
+
// negative guard pickApprovalButton already applies on the approve side.
|
|
781
|
+
buttonIndex = buttons.findIndex(b => isNegativeApprovalLabel(b));
|
|
777
782
|
}
|
|
778
783
|
if (buttonIndex < 0 && (action === 'always' || /always/i.test(button))) {
|
|
779
784
|
buttonIndex = buttons.findIndex(b => /always/i.test(b));
|
|
@@ -24,7 +24,7 @@ export function normalizeApprovalLabel(value: string): string {
|
|
|
24
24
|
.trim();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function isNegativeApprovalLabel(value: string): boolean {
|
|
27
|
+
export function isNegativeApprovalLabel(value: string): boolean {
|
|
28
28
|
const label = normalizeApprovalLabel(value);
|
|
29
29
|
return /^(no|deny|reject|cancel|skip|exit|stop)\b/.test(label)
|
|
30
30
|
|| /\bwithout\b/.test(label)
|
|
@@ -104,18 +104,45 @@ export function pickApprovalButton(
|
|
|
104
104
|
const normalizedButtons = labels.map((label) => normalizeApprovalLabel(label));
|
|
105
105
|
const hints = getApprovalPositiveHints(provider);
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// Match QUALITY dominates hint ORDER: try the strongest tier (exact) across
|
|
108
|
+
// ALL hints before falling to prefix, and prefix before the weakest tier
|
|
109
|
+
// (substring). The old code exhausted all three tiers per-hint, so a weak
|
|
110
|
+
// substring hit on an early hint beat a strong prefix hit on a later hint.
|
|
111
|
+
//
|
|
112
|
+
// Live cursor defect: the 'Not in allowlist: git status' modal offers
|
|
113
|
+
// ["Run (once)", "Add Shell(git status) to allowlist?", "Run Everything", "Skip"]
|
|
114
|
+
// with hints ["trust","allow",…,"run","yes"]. `allow` (earlier) substring-hit
|
|
115
|
+
// "add shell … to allow-LIST" → index 1 (a scope-broadening grant that does
|
|
116
|
+
// NOT execute the command) before `run` (later) prefix-hit "run once" → the
|
|
117
|
+
// correct least-permissive affirmative at index 0. Approve then wedged.
|
|
118
|
+
//
|
|
119
|
+
// Substring is further constrained to WHOLE-WORD hits so `allow` matches
|
|
120
|
+
// "allow all edits" but never "allowlist" — a hint buried inside a larger
|
|
121
|
+
// word (allowlist, disallow, runtime) is almost never the intended button.
|
|
122
|
+
const includesWord = (label: string, hint: string): boolean => {
|
|
123
|
+
if (!label.includes(hint)) return false;
|
|
124
|
+
const re = new RegExp(`(?:^|\\s)${hint.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`);
|
|
125
|
+
return re.test(label);
|
|
126
|
+
};
|
|
110
127
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
const findMatch = (
|
|
129
|
+
predicate: (label: string, hint: string) => boolean,
|
|
130
|
+
): { index: number; label: string } | null => {
|
|
131
|
+
for (const hint of hints) {
|
|
132
|
+
const idx = normalizedButtons.findIndex(
|
|
133
|
+
(label, index) => predicate(label, hint) && !isNegativeApprovalLabel(labels[index]),
|
|
134
|
+
);
|
|
135
|
+
if (idx >= 0) return { index: idx, label: labels[idx] };
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
117
139
|
|
|
118
|
-
return
|
|
140
|
+
return (
|
|
141
|
+
findMatch((label, hint) => label === hint)
|
|
142
|
+
?? findMatch((label, hint) => label.startsWith(hint))
|
|
143
|
+
?? findMatch((label, hint) => includesWord(label, hint))
|
|
144
|
+
?? { index: -1, label: '' }
|
|
145
|
+
);
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
export function pickAutoApprovalButton(
|