@adhdev/daemon-core 0.9.82-rc.169 → 0.9.82-rc.170
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-adapter-types.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +474 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +467 -74
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +19 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -1
- package/dist/providers/provider-instance.d.ts +3 -0
- package/dist/providers/spec/cli-adapter.d.ts +10 -0
- package/dist/providers/spec/driver.d.ts +1 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/shared-types.d.ts +3 -0
- package/package.json +1 -1
- package/src/cli-adapter-types.ts +3 -0
- package/src/cli-adapters/provider-cli-adapter.ts +6 -0
- package/src/cli-adapters/provider-cli-shared.ts +2 -0
- package/src/index.ts +15 -0
- package/src/mesh/mesh-events.ts +41 -0
- package/src/providers/cli-provider-instance.ts +61 -12
- package/src/providers/provider-instance.ts +3 -0
- package/src/providers/spec/cli-adapter.ts +109 -6
- package/src/providers/spec/driver.ts +4 -0
- package/src/providers/types/interactive-prompt.ts +297 -0
- package/src/shared-types.ts +3 -0
- package/src/status/builders.ts +1 -0
|
@@ -36,6 +36,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
36
36
|
terminalLedgerEvidence?: undefined;
|
|
37
37
|
terminalLedgerKind?: undefined;
|
|
38
38
|
duplicateRefineTerminalEvent?: undefined;
|
|
39
|
+
duplicateApproval?: undefined;
|
|
39
40
|
duplicateCompletion?: undefined;
|
|
40
41
|
error?: undefined;
|
|
41
42
|
bufferedForGeneratingCoordinator?: undefined;
|
|
@@ -47,6 +48,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
47
48
|
terminalLedgerKind: unknown;
|
|
48
49
|
intentionalCleanupStop?: undefined;
|
|
49
50
|
duplicateRefineTerminalEvent?: undefined;
|
|
51
|
+
duplicateApproval?: undefined;
|
|
50
52
|
duplicateCompletion?: undefined;
|
|
51
53
|
error?: undefined;
|
|
52
54
|
bufferedForGeneratingCoordinator?: undefined;
|
|
@@ -58,6 +60,19 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
58
60
|
intentionalCleanupStop?: undefined;
|
|
59
61
|
terminalLedgerEvidence?: undefined;
|
|
60
62
|
terminalLedgerKind?: undefined;
|
|
63
|
+
duplicateApproval?: undefined;
|
|
64
|
+
duplicateCompletion?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
|
+
bufferedForGeneratingCoordinator?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
success: boolean;
|
|
69
|
+
forwarded: number;
|
|
70
|
+
suppressed: boolean;
|
|
71
|
+
duplicateApproval: boolean;
|
|
72
|
+
intentionalCleanupStop?: undefined;
|
|
73
|
+
terminalLedgerEvidence?: undefined;
|
|
74
|
+
terminalLedgerKind?: undefined;
|
|
75
|
+
duplicateRefineTerminalEvent?: undefined;
|
|
61
76
|
duplicateCompletion?: undefined;
|
|
62
77
|
error?: undefined;
|
|
63
78
|
bufferedForGeneratingCoordinator?: undefined;
|
|
@@ -70,6 +85,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
70
85
|
intentionalCleanupStop?: undefined;
|
|
71
86
|
terminalLedgerKind?: undefined;
|
|
72
87
|
duplicateRefineTerminalEvent?: undefined;
|
|
88
|
+
duplicateApproval?: undefined;
|
|
73
89
|
error?: undefined;
|
|
74
90
|
bufferedForGeneratingCoordinator?: undefined;
|
|
75
91
|
} | {
|
|
@@ -81,6 +97,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
81
97
|
terminalLedgerEvidence?: undefined;
|
|
82
98
|
terminalLedgerKind?: undefined;
|
|
83
99
|
duplicateRefineTerminalEvent?: undefined;
|
|
100
|
+
duplicateApproval?: undefined;
|
|
84
101
|
error?: undefined;
|
|
85
102
|
bufferedForGeneratingCoordinator?: undefined;
|
|
86
103
|
} | {
|
|
@@ -91,6 +108,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
91
108
|
terminalLedgerEvidence?: undefined;
|
|
92
109
|
terminalLedgerKind?: undefined;
|
|
93
110
|
duplicateRefineTerminalEvent?: undefined;
|
|
111
|
+
duplicateApproval?: undefined;
|
|
94
112
|
duplicateCompletion?: undefined;
|
|
95
113
|
error?: undefined;
|
|
96
114
|
bufferedForGeneratingCoordinator?: undefined;
|
|
@@ -103,6 +121,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
|
|
|
103
121
|
terminalLedgerEvidence?: undefined;
|
|
104
122
|
terminalLedgerKind?: undefined;
|
|
105
123
|
duplicateRefineTerminalEvent?: undefined;
|
|
124
|
+
duplicateApproval?: undefined;
|
|
106
125
|
duplicateCompletion?: undefined;
|
|
107
126
|
error?: undefined;
|
|
108
127
|
} | {
|
|
@@ -45,7 +45,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
45
45
|
private monitor;
|
|
46
46
|
private generatingDebounceTimer;
|
|
47
47
|
private generatingDebouncePending;
|
|
48
|
-
private
|
|
48
|
+
private lastApprovalEventFingerprint;
|
|
49
49
|
private autoApproveBusy;
|
|
50
50
|
private autoApproveBusyTimer;
|
|
51
51
|
private lastAutoApprovalSignature;
|
|
@@ -64,6 +64,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
64
64
|
private suppressIdleHistoryReplay;
|
|
65
65
|
private errorMessage;
|
|
66
66
|
private errorReason;
|
|
67
|
+
private activeInteractivePrompt;
|
|
67
68
|
private presentationMode;
|
|
68
69
|
private providerSessionId?;
|
|
69
70
|
private launchMode;
|
|
@@ -11,6 +11,7 @@ import type { ProviderModule, ProviderResumeCapability } from './contracts.js';
|
|
|
11
11
|
import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata, SessionCapability } from '../shared-types.js';
|
|
12
12
|
import type { MessageInputSupport } from './provider-input-support.js';
|
|
13
13
|
import type { ChatMessage } from '../types.js';
|
|
14
|
+
import type { InteractivePrompt } from './types/interactive-prompt.js';
|
|
14
15
|
export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
15
16
|
export interface ProviderRuntimeWriteOwner {
|
|
16
17
|
clientId: string;
|
|
@@ -42,6 +43,7 @@ export interface ActiveChatData {
|
|
|
42
43
|
message: string;
|
|
43
44
|
buttons: string[];
|
|
44
45
|
} | null;
|
|
46
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
45
47
|
inputContent?: string;
|
|
46
48
|
}
|
|
47
49
|
/** Standardized error reasons across all provider categories */
|
|
@@ -56,6 +58,7 @@ interface ProviderStateBase {
|
|
|
56
58
|
status: ProviderStatus;
|
|
57
59
|
/** chat data */
|
|
58
60
|
activeChat: ActiveChatData | null;
|
|
61
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
59
62
|
/** Workspace — project path or name (all categories) */
|
|
60
63
|
workspace?: string | null;
|
|
61
64
|
/** Runtime info (real-time detection) */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CliAdapter, CliAdapterStatus } from '../../cli-adapter-types.js';
|
|
2
2
|
import type { PtyTransportFactory } from '../../cli-adapters/pty-transport.js';
|
|
3
|
+
import { type InteractivePromptResponse } from '../types/interactive-prompt.js';
|
|
3
4
|
export declare class SpecCliAdapter implements CliAdapter {
|
|
4
5
|
readonly cliType: string;
|
|
5
6
|
readonly cliName: string;
|
|
@@ -21,6 +22,10 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
21
22
|
private statusCallback;
|
|
22
23
|
private ptyDataCallback;
|
|
23
24
|
private partialResponse;
|
|
25
|
+
private activeInteractivePrompt;
|
|
26
|
+
private interactivePromptTransport;
|
|
27
|
+
private claudeTuiPromptCaptureInFlight;
|
|
28
|
+
private jsonLineTail;
|
|
24
29
|
private exited;
|
|
25
30
|
private spawned;
|
|
26
31
|
private providerSessionId;
|
|
@@ -56,6 +61,7 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
56
61
|
resize(cols: number, rows: number): void;
|
|
57
62
|
resolveModal(buttonIndex: number): void;
|
|
58
63
|
resolveAction(data: unknown): Promise<void>;
|
|
64
|
+
setInteractivePromptResponse(response: InteractivePromptResponse): Promise<void>;
|
|
59
65
|
isApprovalRecentlyResolved(): boolean;
|
|
60
66
|
clearHistory(): void;
|
|
61
67
|
updateRuntimeSettings(): void;
|
|
@@ -82,4 +88,8 @@ export declare class SpecCliAdapter implements CliAdapter {
|
|
|
82
88
|
updateRuntimeMeta(meta?: Record<string, unknown>): void;
|
|
83
89
|
refreshProviderDefinition(): void;
|
|
84
90
|
private handleEvent;
|
|
91
|
+
private detectInteractivePromptFromPtyChunk;
|
|
92
|
+
private maybeCaptureClaudeTuiPrompt;
|
|
93
|
+
private readClaudeTuiHeaders;
|
|
94
|
+
private captureClaudeTuiPrompt;
|
|
85
95
|
}
|
|
@@ -123,6 +123,7 @@ export declare class SpecDriver {
|
|
|
123
123
|
subscribe(listener: (ev: DashboardEvent) => void): () => void;
|
|
124
124
|
start(): void;
|
|
125
125
|
dispatch(cmd: DashboardCommand): void;
|
|
126
|
+
snapshot(): string;
|
|
126
127
|
shutdown(): void;
|
|
127
128
|
private loadSpecOrThrow;
|
|
128
129
|
private buildAdapterOpts;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface InteractivePrompt {
|
|
2
|
+
promptId: string;
|
|
3
|
+
origin: 'cli' | 'mcp' | 'agent';
|
|
4
|
+
providerType: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
questions: InteractiveQuestion[];
|
|
7
|
+
}
|
|
8
|
+
export interface InteractiveQuestion {
|
|
9
|
+
questionId: string;
|
|
10
|
+
question: string;
|
|
11
|
+
header?: string;
|
|
12
|
+
multiSelect: boolean;
|
|
13
|
+
options: InteractiveOption[];
|
|
14
|
+
allowFreeform?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface InteractiveOption {
|
|
17
|
+
label: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
preview?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface InteractivePromptResponse {
|
|
22
|
+
promptId: string;
|
|
23
|
+
answers: Record<string, InteractiveAnswer>;
|
|
24
|
+
}
|
|
25
|
+
export interface InteractiveAnswer {
|
|
26
|
+
selectedLabels: string[];
|
|
27
|
+
freeformText?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function normalizeInteractivePrompt(raw: unknown): InteractivePrompt | null;
|
|
30
|
+
export declare function normalizeInteractivePromptResponse(raw: unknown): InteractivePromptResponse;
|
|
31
|
+
export declare function buildClaudeInteractiveToolResult(response: InteractivePromptResponse): string;
|
|
32
|
+
export interface ClaudeInteractiveTuiPage {
|
|
33
|
+
screenText: string;
|
|
34
|
+
header?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function detectClaudeAskUserQuestionPromptFromTuiPages(pages: ClaudeInteractiveTuiPage[], options: {
|
|
37
|
+
promptId: string;
|
|
38
|
+
providerType?: string;
|
|
39
|
+
createdAt?: number;
|
|
40
|
+
}): InteractivePrompt | null;
|
|
41
|
+
export declare function buildClaudeInteractiveTuiAnswerSteps(prompt: InteractivePrompt, response: InteractivePromptResponse): string[];
|
|
42
|
+
export declare function interactivePromptFromClaudeAskUserQuestion(input: unknown, options: {
|
|
43
|
+
promptId: string;
|
|
44
|
+
providerType: string;
|
|
45
|
+
createdAt?: number;
|
|
46
|
+
origin?: InteractivePrompt['origin'];
|
|
47
|
+
}): InteractivePrompt | null;
|
|
48
|
+
export declare function detectClaudeAskUserQuestionPromptFromJson(value: unknown, providerType?: string): InteractivePrompt | null;
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _Provide
|
|
|
14
14
|
import type { WorkspaceEntry } from './config/workspaces.js';
|
|
15
15
|
import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
|
|
16
16
|
import type { GitCompactSummary, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams } from './git/git-types.js';
|
|
17
|
+
import type { InteractivePrompt } from './providers/types/interactive-prompt.js';
|
|
17
18
|
export type { GitCommandName, GitCompactSummary, GitDiffSummary, GitFailureReason, GitFileChange, GitFileChangeStatus, GitRepoIdentity, GitRepoStatus, GitSnapshot, GitSnapshotCompareSummary, GitSnapshotReason, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams, } from './git/git-types.js';
|
|
18
19
|
export interface SessionActiveChatData extends Omit<_ActiveChatData, 'messages'> {
|
|
19
20
|
messages?: _ActiveChatData['messages'];
|
|
@@ -49,6 +50,7 @@ export interface ReadChatSyncResult {
|
|
|
49
50
|
message: string;
|
|
50
51
|
buttons: string[];
|
|
51
52
|
} | null;
|
|
53
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
52
54
|
/**
|
|
53
55
|
* Chat source provenance from ChatSourceMachine (A2). Carries the
|
|
54
56
|
* selected source, transition cause, lock state, and legacy
|
|
@@ -275,6 +277,7 @@ export interface SessionEntry {
|
|
|
275
277
|
runtimeRecoveryState?: string | null;
|
|
276
278
|
resume?: ProviderResumeCapability;
|
|
277
279
|
activeChat: SessionActiveChatData | null;
|
|
280
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
278
281
|
capabilities?: SessionCapability[];
|
|
279
282
|
/** Effective message input/media support for this session. Defaults fail-closed to text-only. */
|
|
280
283
|
messageInput?: MessageInputSupport;
|
package/package.json
CHANGED
package/src/cli-adapter-types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ChatMessage } from './types.js';
|
|
8
|
+
import type { InteractivePrompt, InteractivePromptResponse } from './providers/types/interactive-prompt.js';
|
|
8
9
|
|
|
9
10
|
export interface CliAdapterStatus {
|
|
10
11
|
status?: string;
|
|
@@ -13,6 +14,7 @@ export interface CliAdapterStatus {
|
|
|
13
14
|
message: string;
|
|
14
15
|
buttons: string[];
|
|
15
16
|
} | null;
|
|
17
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
16
18
|
providerSessionId?: string;
|
|
17
19
|
errorMessage?: string;
|
|
18
20
|
errorReason?: string;
|
|
@@ -60,6 +62,7 @@ export interface CliAdapter {
|
|
|
60
62
|
setServerConn?(serverConn: unknown): void;
|
|
61
63
|
clearHistory?(): void;
|
|
62
64
|
resolveAction?(data: unknown): Promise<void>;
|
|
65
|
+
setInteractivePromptResponse?(response: InteractivePromptResponse): Promise<void>;
|
|
63
66
|
resolveModal?(buttonIndex: number): void;
|
|
64
67
|
isApprovalRecentlyResolved?(): boolean;
|
|
65
68
|
// Raw PTY I/O (for terminal view)
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
19
|
+
import type { InteractivePromptResponse } from '../providers/types/interactive-prompt.js';
|
|
19
20
|
import { LOG } from '../logging/logger.js';
|
|
20
21
|
import { getDebugRuntimeConfig } from '../logging/debug-config.js';
|
|
21
22
|
import { TerminalScreen } from './terminal-screen.js';
|
|
@@ -1076,6 +1077,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1076
1077
|
await this.sendMessage(promptText);
|
|
1077
1078
|
}
|
|
1078
1079
|
|
|
1080
|
+
async setInteractivePromptResponse(_response: InteractivePromptResponse): Promise<void> {
|
|
1081
|
+
// Legacy TUI providers do not currently expose an interactive prompt
|
|
1082
|
+
// protocol. Spec-backed claude-cli implements the first real wiring.
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1079
1085
|
private isSubmitStuck(normalizedPromptSnippet: string): boolean {
|
|
1080
1086
|
if (!this.ptyProcess || !this.engine.isWaitingForResponse || this.engine.submitRetryUsed) return false;
|
|
1081
1087
|
if (this.engine.hasActionableApproval()) return false;
|
|
@@ -4,6 +4,7 @@ import { execSync } from 'child_process';
|
|
|
4
4
|
import type { ProviderResumeCapability } from '../providers/contracts.js';
|
|
5
5
|
import type { ChatMessageKind } from '../providers/chat-message-normalization.js';
|
|
6
6
|
import type { ChatBubbleState } from '../types.js';
|
|
7
|
+
import type { InteractivePrompt } from '../providers/types/interactive-prompt.js';
|
|
7
8
|
import { sanitizeSpawnEnv } from './spawn-env.js';
|
|
8
9
|
|
|
9
10
|
export interface CliChatMessage {
|
|
@@ -28,6 +29,7 @@ export interface CliSessionStatus {
|
|
|
28
29
|
messages: CliChatMessage[];
|
|
29
30
|
workingDir: string;
|
|
30
31
|
activeModal: { message: string; buttons: string[] } | null;
|
|
32
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
31
33
|
pendingOutboundCount?: number;
|
|
32
34
|
pendingOutboundMessages?: Array<{
|
|
33
35
|
id: string;
|
package/src/index.ts
CHANGED
|
@@ -85,6 +85,21 @@ export type {
|
|
|
85
85
|
InputMediaType,
|
|
86
86
|
} from './shared-types.js';
|
|
87
87
|
|
|
88
|
+
export type {
|
|
89
|
+
InteractivePrompt,
|
|
90
|
+
InteractiveQuestion,
|
|
91
|
+
InteractiveOption,
|
|
92
|
+
InteractivePromptResponse,
|
|
93
|
+
InteractiveAnswer,
|
|
94
|
+
} from './providers/types/interactive-prompt.js';
|
|
95
|
+
export {
|
|
96
|
+
normalizeInteractivePrompt,
|
|
97
|
+
normalizeInteractivePromptResponse,
|
|
98
|
+
buildClaudeInteractiveToolResult,
|
|
99
|
+
interactivePromptFromClaudeAskUserQuestion,
|
|
100
|
+
detectClaudeAskUserQuestionPromptFromJson,
|
|
101
|
+
} from './providers/types/interactive-prompt.js';
|
|
102
|
+
|
|
88
103
|
// ── Repo Mesh Types (cross-package) ──
|
|
89
104
|
export type {
|
|
90
105
|
RepoMesh,
|
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -508,6 +508,33 @@ function isDuplicateMeshCompletionEvent(args: {
|
|
|
508
508
|
return false;
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
function isDuplicateMeshApprovalEvent(args: {
|
|
512
|
+
meshId: string;
|
|
513
|
+
sessionId: string;
|
|
514
|
+
providerType?: string;
|
|
515
|
+
timestamp?: number | null;
|
|
516
|
+
modalMessage?: string;
|
|
517
|
+
modalButtons?: unknown;
|
|
518
|
+
}): boolean {
|
|
519
|
+
const modalButtons = Array.isArray(args.modalButtons)
|
|
520
|
+
? args.modalButtons.map(button => String(button).trim()).filter(Boolean)
|
|
521
|
+
: [];
|
|
522
|
+
const approvalIdentity = Number.isFinite(args.timestamp)
|
|
523
|
+
? String(args.timestamp)
|
|
524
|
+
: JSON.stringify({ message: args.modalMessage || '', buttons: modalButtons });
|
|
525
|
+
if (!approvalIdentity || approvalIdentity === '{"message":"","buttons":[]}') return false;
|
|
526
|
+
const fingerprint = [
|
|
527
|
+
args.meshId,
|
|
528
|
+
'agent:waiting_approval',
|
|
529
|
+
args.sessionId,
|
|
530
|
+
args.providerType || '',
|
|
531
|
+
approvalIdentity,
|
|
532
|
+
].join('::');
|
|
533
|
+
if (hasFingerprintSeen(fingerprint)) return true;
|
|
534
|
+
recordFingerprintSeen(fingerprint);
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
511
538
|
function isDuplicateRefineTerminalEvent(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): boolean {
|
|
512
539
|
const fingerprint = buildRefineTerminalEventFingerprint(meshId, eventName, metadataEvent);
|
|
513
540
|
if (!fingerprint) return false;
|
|
@@ -1214,6 +1241,20 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1214
1241
|
}
|
|
1215
1242
|
|
|
1216
1243
|
const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
|
|
1244
|
+
if (args.event === 'agent:waiting_approval' && eventSessionId) {
|
|
1245
|
+
const duplicateApproval = isDuplicateMeshApprovalEvent({
|
|
1246
|
+
meshId: args.meshId,
|
|
1247
|
+
sessionId: eventSessionId,
|
|
1248
|
+
providerType: readNonEmptyString(args.metadataEvent.providerType) || undefined,
|
|
1249
|
+
timestamp: eventTimestamp,
|
|
1250
|
+
modalMessage: readNonEmptyString(args.metadataEvent.modalMessage) || undefined,
|
|
1251
|
+
modalButtons: args.metadataEvent.modalButtons,
|
|
1252
|
+
});
|
|
1253
|
+
if (duplicateApproval) {
|
|
1254
|
+
LOG.info('MeshEvents', `Suppressed duplicate approval event for mesh ${args.meshId} session ${eventSessionId}`);
|
|
1255
|
+
return { success: true, forwarded: 0, suppressed: true, duplicateApproval: true };
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1217
1258
|
if (args.event === 'agent:generating_completed' && eventSessionId) {
|
|
1218
1259
|
const terminal = findRecentTerminalLedgerEvidence({
|
|
1219
1260
|
meshId: args.meshId,
|
|
@@ -13,6 +13,7 @@ import { createRequire } from 'node:module';
|
|
|
13
13
|
import { normalizeInputEnvelope, type ProviderModule, flattenContent, type InputEnvelope, type InputPart } from './contracts.js';
|
|
14
14
|
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
15
15
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext, ProviderErrorReason, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
16
|
+
import { normalizeInteractivePrompt, normalizeInteractivePromptResponse, type InteractivePrompt } from './types/interactive-prompt.js';
|
|
16
17
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
17
18
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
18
19
|
import { createCliAdapter } from './spec/route.js';
|
|
@@ -41,6 +42,7 @@ type CompletedDebouncePending = {
|
|
|
41
42
|
duration: number;
|
|
42
43
|
timestamp: number;
|
|
43
44
|
firstObservedAt: number;
|
|
45
|
+
previousStatus: string;
|
|
44
46
|
loggedBlockReason?: string;
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -328,7 +330,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
328
330
|
private monitor: StatusMonitor;
|
|
329
331
|
private generatingDebounceTimer: NodeJS.Timeout | null = null;
|
|
330
332
|
private generatingDebouncePending: { chatTitle: string; timestamp: number } | null = null;
|
|
331
|
-
private
|
|
333
|
+
private lastApprovalEventFingerprint = '';
|
|
332
334
|
private autoApproveBusy = false;
|
|
333
335
|
private autoApproveBusyTimer: NodeJS.Timeout | null = null;
|
|
334
336
|
private lastAutoApprovalSignature = '';
|
|
@@ -350,6 +352,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
350
352
|
private suppressIdleHistoryReplay = false;
|
|
351
353
|
private errorMessage: string | undefined = undefined;
|
|
352
354
|
private errorReason: ProviderErrorReason | undefined = undefined;
|
|
355
|
+
private activeInteractivePrompt: InteractivePrompt | null = null;
|
|
353
356
|
|
|
354
357
|
private presentationMode: 'terminal' | 'chat';
|
|
355
358
|
private providerSessionId?: string;
|
|
@@ -525,6 +528,9 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
525
528
|
// parseSession), with provider-loader.ts updated to store script source strings
|
|
526
529
|
// alongside the loaded function references for extended-legacy providers.
|
|
527
530
|
const adapterStatus = this.adapter.getStatus();
|
|
531
|
+
if (Object.prototype.hasOwnProperty.call(adapterStatus, 'activeInteractivePrompt')) {
|
|
532
|
+
this.activeInteractivePrompt = adapterStatus.activeInteractivePrompt ?? null;
|
|
533
|
+
}
|
|
528
534
|
let parsedStatus: any = null;
|
|
529
535
|
let parseErrorMessage: string | undefined;
|
|
530
536
|
if (typeof this.adapter.getScriptParsedStatus === 'function') {
|
|
@@ -674,8 +680,10 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
674
680
|
status: activeChatStatus,
|
|
675
681
|
messages: statusMessages,
|
|
676
682
|
activeModal: autoApproveActive ? null : (parsedStatus?.activeModal ?? adapterStatus.activeModal),
|
|
683
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
677
684
|
inputContent: '',
|
|
678
685
|
},
|
|
686
|
+
activeInteractivePrompt: this.activeInteractivePrompt,
|
|
679
687
|
workspace: this.workingDir,
|
|
680
688
|
instanceId: this.instanceId,
|
|
681
689
|
providerSessionId: this.providerSessionId,
|
|
@@ -771,6 +779,32 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
771
779
|
void this.adapter.resolveAction(data).catch((e: any) => {
|
|
772
780
|
LOG.warn('CLI', `[${this.type}] resolve_action failed: ${e?.message || e}`);
|
|
773
781
|
});
|
|
782
|
+
} else if (event === 'interactive_prompt' && data) {
|
|
783
|
+
const prompt = normalizeInteractivePrompt(data);
|
|
784
|
+
if (prompt) {
|
|
785
|
+
this.activeInteractivePrompt = prompt;
|
|
786
|
+
this.events.push({
|
|
787
|
+
event: 'interactive_prompt',
|
|
788
|
+
timestamp: Date.now(),
|
|
789
|
+
promptId: prompt.promptId,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
} else if (event === 'interactive_prompt_response' && data) {
|
|
793
|
+
try {
|
|
794
|
+
const response = normalizeInteractivePromptResponse(data);
|
|
795
|
+
if (this.activeInteractivePrompt?.promptId === response.promptId) {
|
|
796
|
+
this.activeInteractivePrompt = null;
|
|
797
|
+
}
|
|
798
|
+
if (typeof this.adapter.setInteractivePromptResponse !== 'function') {
|
|
799
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response ignored: adapter does not support interactive prompts`);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
void this.adapter.setInteractivePromptResponse(response).catch((e: any) => {
|
|
803
|
+
LOG.warn('CLI', `[${this.type}] interactive_prompt_response failed: ${e?.message || e}`);
|
|
804
|
+
});
|
|
805
|
+
} catch (e: any) {
|
|
806
|
+
LOG.warn('CLI', `[${this.type}] invalid interactive_prompt_response: ${e?.message || e}`);
|
|
807
|
+
}
|
|
774
808
|
} else if (event === 'provider_state_patch' && data && typeof data === 'object') {
|
|
775
809
|
this.applyProviderResponse(data, { phase: 'immediate' });
|
|
776
810
|
}
|
|
@@ -939,13 +973,16 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
939
973
|
return true;
|
|
940
974
|
}
|
|
941
975
|
|
|
942
|
-
private getCompletedFinalizationBlock(latestVisibleStatus: string): CompletedFinalizationBlock | null {
|
|
976
|
+
private getCompletedFinalizationBlock(latestVisibleStatus: string, pending: CompletedDebouncePending): CompletedFinalizationBlock | null {
|
|
943
977
|
if (latestVisibleStatus !== 'idle') return { reason: `status:${latestVisibleStatus}`, terminal: true };
|
|
944
978
|
|
|
945
979
|
const adapterAny = this.adapter as any;
|
|
946
|
-
|
|
947
|
-
if (
|
|
948
|
-
|
|
980
|
+
const approvalResolvedIdle = pending.previousStatus === 'waiting_approval';
|
|
981
|
+
if (!approvalResolvedIdle) {
|
|
982
|
+
if (adapterAny?.isWaitingForResponse === true) return { reason: 'adapter_waiting_for_response', terminal: true };
|
|
983
|
+
if (adapterAny?.currentTurnScope) return { reason: 'adapter_turn_scope_active', terminal: true };
|
|
984
|
+
if (this.hasAdapterPendingResponse()) return { reason: 'adapter_pending_response', terminal: true };
|
|
985
|
+
}
|
|
949
986
|
|
|
950
987
|
const partial = typeof this.adapter.getPartialResponse === 'function'
|
|
951
988
|
? this.adapter.getPartialResponse()
|
|
@@ -987,7 +1024,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
987
1024
|
if (screenText) {
|
|
988
1025
|
const tailLines = screenText.split(/\r?\n/).slice(-16).join('\n');
|
|
989
1026
|
if (looksLikeActiveApprovalPromptText(tailLines)) {
|
|
990
|
-
return { reason: 'screen_shows_approval_prompt', terminal:
|
|
1027
|
+
return { reason: 'screen_shows_approval_prompt', terminal: approvalResolvedIdle };
|
|
991
1028
|
}
|
|
992
1029
|
}
|
|
993
1030
|
} catch { /* defensive: screen text read is best-effort */ }
|
|
@@ -1017,7 +1054,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1017
1054
|
return;
|
|
1018
1055
|
}
|
|
1019
1056
|
|
|
1020
|
-
const block = this.getCompletedFinalizationBlock(latestVisibleStatus);
|
|
1057
|
+
const block = this.getCompletedFinalizationBlock(latestVisibleStatus, pending);
|
|
1021
1058
|
if (block) {
|
|
1022
1059
|
const blockReason = block.reason;
|
|
1023
1060
|
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
@@ -1051,6 +1088,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1051
1088
|
this.completedDebouncePending = null;
|
|
1052
1089
|
this.completedDebounceTimer = null;
|
|
1053
1090
|
this.generatingStartedAt = 0;
|
|
1091
|
+
this.lastApprovalEventFingerprint = '';
|
|
1054
1092
|
return;
|
|
1055
1093
|
}
|
|
1056
1094
|
|
|
@@ -1065,6 +1103,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1065
1103
|
this.completedDebouncePending = null;
|
|
1066
1104
|
this.completedDebounceTimer = null;
|
|
1067
1105
|
this.generatingStartedAt = 0;
|
|
1106
|
+
this.lastApprovalEventFingerprint = '';
|
|
1068
1107
|
}
|
|
1069
1108
|
|
|
1070
1109
|
private maybeAutoApproveStatus(adapterStatus: any, now = Date.now()): boolean {
|
|
@@ -1183,10 +1222,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1183
1222
|
if (!this.generatingStartedAt) this.generatingStartedAt = now;
|
|
1184
1223
|
const modal = adapterStatus.activeModal;
|
|
1185
1224
|
LOG.info('CLI', `[${this.type}] approval modal: "${modal?.message?.slice(0, 80) ?? 'none'}"`);
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1225
|
+
const approvalFingerprint = JSON.stringify({
|
|
1226
|
+
message: typeof modal?.message === 'string' ? modal.message.trim() : '',
|
|
1227
|
+
buttons: Array.isArray(modal?.buttons) ? modal.buttons.map((button: unknown) => String(button).trim()) : [],
|
|
1228
|
+
});
|
|
1229
|
+
// PTY redraws can briefly leave waiting_approval and then re-enter it.
|
|
1230
|
+
// Keep one coordinator event per logical modal until the turn completes.
|
|
1231
|
+
if (this.lastStatus !== 'waiting_approval' && approvalFingerprint !== this.lastApprovalEventFingerprint) {
|
|
1232
|
+
this.lastApprovalEventFingerprint = approvalFingerprint;
|
|
1190
1233
|
this.appendRuntimeSystemMessage(
|
|
1191
1234
|
this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
|
|
1192
1235
|
`approval_request:${now}`,
|
|
@@ -1227,7 +1270,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
1227
1270
|
} else {
|
|
1228
1271
|
// Debounce completed, then require the rich transcript path that read_chat
|
|
1229
1272
|
// uses to show an idle turn whose last user-facing message is assistant.
|
|
1230
|
-
this.completedDebouncePending = {
|
|
1273
|
+
this.completedDebouncePending = {
|
|
1274
|
+
chatTitle,
|
|
1275
|
+
duration,
|
|
1276
|
+
timestamp: now,
|
|
1277
|
+
firstObservedAt: now,
|
|
1278
|
+
previousStatus: this.lastStatus,
|
|
1279
|
+
};
|
|
1231
1280
|
this.scheduleCompletedDebounceFlush(3000);
|
|
1232
1281
|
}
|
|
1233
1282
|
} else if (newStatus === 'idle' && this.lastStatus === 'starting') {
|
|
@@ -12,6 +12,7 @@ import type { ProviderModule, ProviderSettingDef, ProviderResumeCapability } fro
|
|
|
12
12
|
import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata, SessionCapability } from '../shared-types.js';
|
|
13
13
|
import type { MessageInputSupport } from './provider-input-support.js';
|
|
14
14
|
import type { ChatMessage } from '../types.js';
|
|
15
|
+
import type { InteractivePrompt } from './types/interactive-prompt.js';
|
|
15
16
|
|
|
16
17
|
// ─── ProviderState — Discriminated union by category ─────────────
|
|
17
18
|
|
|
@@ -47,6 +48,7 @@ export interface ActiveChatData {
|
|
|
47
48
|
status: string;
|
|
48
49
|
messages: ChatMessage[];
|
|
49
50
|
activeModal: { message: string; buttons: string[] } | null;
|
|
51
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
50
52
|
inputContent?: string;
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -72,6 +74,7 @@ interface ProviderStateBase {
|
|
|
72
74
|
status: ProviderStatus;
|
|
73
75
|
/** chat data */
|
|
74
76
|
activeChat: ActiveChatData | null;
|
|
77
|
+
activeInteractivePrompt?: InteractivePrompt | null;
|
|
75
78
|
/** Workspace — project path or name (all categories) */
|
|
76
79
|
workspace?: string | null;
|
|
77
80
|
/** Runtime info (real-time detection) */
|