@adhdev/daemon-core 0.9.76-rc.6 → 0.9.76-rc.61
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 +2 -1
- package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +17 -4
- package/dist/commands/mesh-coordinator.d.ts +2 -0
- package/dist/commands/router.d.ts +11 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/git/git-types.d.ts +1 -1
- package/dist/git/git-worktree.d.ts +64 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1690 -447
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1703 -478
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +9 -0
- package/dist/providers/chat-message-normalization.d.ts +40 -0
- package/dist/providers/cli-provider-instance.d.ts +3 -0
- package/dist/providers/provider-instance-manager.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +2 -0
- package/dist/repo-mesh-types.d.ts +27 -0
- package/dist/session-host/runtime-support.d.ts +2 -1
- package/dist/shared-types.d.ts +4 -0
- package/dist/types.d.ts +9 -0
- package/package.json +4 -5
- package/src/chat/subscription-updates.ts +3 -1
- package/src/cli-adapters/provider-cli-adapter.ts +28 -7
- package/src/cli-adapters/provider-cli-runtime.ts +3 -2
- package/src/commands/chat-commands.ts +126 -11
- package/src/commands/cli-manager.ts +78 -5
- package/src/commands/handler.ts +13 -4
- package/src/commands/mesh-coordinator.ts +148 -5
- package/src/commands/router.d.ts +1 -0
- package/src/commands/router.ts +553 -34
- package/src/config/mesh-config.ts +23 -2
- package/src/git/git-commands.ts +5 -1
- package/src/git/git-types.ts +1 -0
- package/src/git/git-worktree.ts +214 -0
- package/src/git/index.ts +14 -0
- package/src/index.ts +16 -1
- package/src/mesh/coordinator-prompt.ts +29 -14
- package/src/mesh/mesh-events.ts +109 -43
- package/src/providers/chat-message-normalization.ts +241 -0
- package/src/providers/cli-provider-instance.d.ts +2 -0
- package/src/providers/cli-provider-instance.ts +93 -8
- package/src/providers/provider-instance-manager.ts +20 -1
- package/src/providers/provider-instance.ts +2 -0
- package/src/providers/read-chat-contract.ts +8 -0
- package/src/repo-mesh-types.ts +30 -0
- package/src/session-host/runtime-support.ts +55 -7
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +17 -12
- package/src/status/reporter.ts +6 -0
- package/src/types.ts +9 -0
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
|
|
2
|
+
export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
|
|
3
|
+
success: boolean;
|
|
4
|
+
forwarded: number;
|
|
5
|
+
error?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
success: boolean;
|
|
8
|
+
error: string;
|
|
9
|
+
forwarded?: undefined;
|
|
10
|
+
};
|
|
2
11
|
export declare function setupMeshEventForwarding(components: DaemonComponents): void;
|
|
@@ -2,6 +2,31 @@ import type { ChatMessage } from '../types.js';
|
|
|
2
2
|
export declare const BUILTIN_CHAT_MESSAGE_KINDS: readonly ["standard", "thought", "tool", "terminal", "system"];
|
|
3
3
|
export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
4
4
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
5
|
+
export declare const CHAT_MESSAGE_VISIBILITIES: readonly ["user", "debug", "internal", "hidden"];
|
|
6
|
+
export declare const CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES: readonly ["visible", "chat", "user", "debug", "internal", "hidden"];
|
|
7
|
+
export declare const CHAT_MESSAGE_AUDIENCES: readonly ["chat", "debug", "trace", "internal"];
|
|
8
|
+
export declare const CHAT_MESSAGE_SOURCES: readonly ["assistant_text", "tool_call", "terminal_command", "runtime_activity", "runtime_status", "provider_chrome", "control"];
|
|
9
|
+
export declare const CHAT_MESSAGE_ACTIVITY_SOURCES: readonly ["tool_call", "terminal_command", "runtime_activity"];
|
|
10
|
+
export declare const CHAT_MESSAGE_INTERNAL_SOURCES: readonly ["runtime_status", "provider_chrome", "control"];
|
|
11
|
+
export type ChatMessageVisibility = typeof CHAT_MESSAGE_VISIBILITIES[number] | (string & {});
|
|
12
|
+
export type ChatMessageTranscriptVisibility = typeof CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES[number] | (string & {});
|
|
13
|
+
export type ChatMessageAudience = typeof CHAT_MESSAGE_AUDIENCES[number] | (string & {});
|
|
14
|
+
export type ChatMessageSource = typeof CHAT_MESSAGE_SOURCES[number] | (string & {});
|
|
15
|
+
export type ChatMessageTranscriptSurface = 'chat' | 'activity' | 'internal';
|
|
16
|
+
export interface ChatMessageVisibilityClassification {
|
|
17
|
+
surface: ChatMessageTranscriptSurface;
|
|
18
|
+
isUserFacing: boolean;
|
|
19
|
+
isActivityFacing: boolean;
|
|
20
|
+
isInternal: boolean;
|
|
21
|
+
explicitUserFacing: boolean;
|
|
22
|
+
explicitHidden: boolean;
|
|
23
|
+
role: string;
|
|
24
|
+
kind: ChatMessageKind;
|
|
25
|
+
visibility: string;
|
|
26
|
+
transcriptVisibility: string;
|
|
27
|
+
audience: string;
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
5
30
|
export declare function isBuiltinChatMessageKind(kind: unknown): kind is BuiltinChatMessageKind;
|
|
6
31
|
export declare function normalizeChatMessageKind(kind: unknown, role: unknown): ChatMessageKind;
|
|
7
32
|
export declare function resolveChatMessageKind<T extends ChatMessage>(message: T): ChatMessageKind;
|
|
@@ -63,3 +88,18 @@ export declare function buildUserChatMessage<T extends Omit<ChatMessage, 'role'
|
|
|
63
88
|
});
|
|
64
89
|
export declare function normalizeChatMessage<T extends ChatMessage>(message: T): T;
|
|
65
90
|
export declare function normalizeChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[];
|
|
91
|
+
/**
|
|
92
|
+
* Shared transcript visibility protocol for all ADHDev provider chat messages.
|
|
93
|
+
*
|
|
94
|
+
* Producers can stamp visibility/audience/source/userFacing/internal/debug either
|
|
95
|
+
* at the top level or under `meta`. Consumers should use this classifier instead
|
|
96
|
+
* of matching command text, icons, provider names, or terminal UI fragments.
|
|
97
|
+
*/
|
|
98
|
+
export declare function classifyChatMessageVisibility(message: ChatMessage | null | undefined): ChatMessageVisibilityClassification;
|
|
99
|
+
export declare function isUserFacingChatMessage(message: ChatMessage | null | undefined): boolean;
|
|
100
|
+
export declare function isActivityChatMessage(message: ChatMessage | null | undefined): boolean;
|
|
101
|
+
export declare function isInternalChatMessage(message: ChatMessage | null | undefined): boolean;
|
|
102
|
+
export declare function filterUserFacingChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[];
|
|
103
|
+
export declare function filterActivityChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[];
|
|
104
|
+
export declare function filterInternalChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[];
|
|
105
|
+
export declare function filterChatMessagesByVisibility<T extends ChatMessage>(messages: T[] | null | undefined, surface: ChatMessageTranscriptSurface): T[];
|
|
@@ -8,6 +8,7 @@ import { type ProviderModule } from './contracts.js';
|
|
|
8
8
|
import type { ProviderInstance, ProviderState, InstanceContext, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
9
9
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
10
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
11
|
+
import type { ChatMessage } from '../types.js';
|
|
11
12
|
type PersistableCliHistoryMessage = {
|
|
12
13
|
role: string;
|
|
13
14
|
content: string;
|
|
@@ -67,6 +68,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
67
68
|
constructor(provider: ProviderModule, workingDir: string, cliArgs?: string[], instanceId?: string, transportFactory?: PtyTransportFactory, options?: {
|
|
68
69
|
providerSessionId?: string;
|
|
69
70
|
launchMode?: 'new' | 'resume' | 'manual';
|
|
71
|
+
extraEnv?: Record<string, string>;
|
|
70
72
|
onProviderSessionResolved?: (info: {
|
|
71
73
|
instanceId: string;
|
|
72
74
|
providerType: string;
|
|
@@ -112,6 +114,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
112
114
|
private maybeAppendRuntimeRecoveryMessage;
|
|
113
115
|
private appendRuntimeSystemMessage;
|
|
114
116
|
private appendRuntimeMessage;
|
|
117
|
+
mergeRuntimeChatMessages(parsedMessages: ChatMessage[]): ChatMessage[];
|
|
115
118
|
private mergeConversationMessages;
|
|
116
119
|
private formatApprovalRequestMessage;
|
|
117
120
|
private promoteProviderSessionId;
|
|
@@ -67,6 +67,7 @@ export declare class ProviderInstanceManager {
|
|
|
67
67
|
onEvent(listener: (event: ProviderEvent & {
|
|
68
68
|
providerType: string;
|
|
69
69
|
}) => void): void;
|
|
70
|
+
emitProviderEvent(providerType: string, instanceId: string, event: ProviderEvent): void;
|
|
70
71
|
private emitPendingEvents;
|
|
71
72
|
/**
|
|
72
73
|
* Forward event to specific Instance
|
|
@@ -147,6 +147,8 @@ export interface InstanceContext {
|
|
|
147
147
|
onPtyData?: (data: string) => void;
|
|
148
148
|
/** Provider configvalue (resolved) */
|
|
149
149
|
settings: Record<string, any>;
|
|
150
|
+
/** Immediate provider-originated status/event emission. Used to avoid waiting for status polling. */
|
|
151
|
+
emitProviderEvent?: (event: ProviderEvent) => void;
|
|
150
152
|
}
|
|
151
153
|
export interface ProviderInstance {
|
|
152
154
|
/** Provider type */
|
|
@@ -40,6 +40,7 @@ export interface RepoMeshNode {
|
|
|
40
40
|
status: 'enabled' | 'disabled' | 'removed';
|
|
41
41
|
}
|
|
42
42
|
export type RepoMeshNodeHealth = 'online' | 'offline' | 'degraded' | 'dirty' | 'wrong_branch' | 'unknown';
|
|
43
|
+
export type RepoMeshSessionCleanupMode = 'preserve' | 'stop' | 'delete_stopped' | 'stop_and_delete';
|
|
43
44
|
export interface RepoMeshPolicy {
|
|
44
45
|
requirePreTaskCheckpoint: boolean;
|
|
45
46
|
requirePostTaskCheckpoint: boolean;
|
|
@@ -48,11 +49,31 @@ export interface RepoMeshPolicy {
|
|
|
48
49
|
dirtyWorkspaceBehavior: 'block' | 'warn' | 'checkpoint_then_continue';
|
|
49
50
|
maxParallelTasks: number;
|
|
50
51
|
allowedProviders?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* What to do with delegated session-host records for a node when it is removed.
|
|
54
|
+
* Defaults to 'preserve' so completed work can be reviewed later and live
|
|
55
|
+
* runtimes are never stopped/deleted unless the mesh owner opts in.
|
|
56
|
+
*/
|
|
57
|
+
sessionCleanupOnNodeRemove?: RepoMeshSessionCleanupMode;
|
|
58
|
+
}
|
|
59
|
+
export interface RepoMeshRelatedRepo {
|
|
60
|
+
/** Stable display label for an explicitly configured associated checkout. */
|
|
61
|
+
label: string;
|
|
62
|
+
/** Absolute checkout/workspace path for git freshness probes. */
|
|
63
|
+
workspace: string;
|
|
51
64
|
}
|
|
52
65
|
export interface RepoMeshNodePolicy {
|
|
53
66
|
readOnly?: boolean;
|
|
54
67
|
canPush?: boolean;
|
|
55
68
|
maxConcurrentSessions?: number;
|
|
69
|
+
/** Ordered provider preference used when mesh_launch_session omits an explicit type. */
|
|
70
|
+
providerPriority?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Optional associated/external repos that must be checked alongside this node.
|
|
73
|
+
* These are explicit policy/config entries only; Repo Mesh does not auto-discover
|
|
74
|
+
* sibling paths so freshness checks stay fail-closed and non-surprising.
|
|
75
|
+
*/
|
|
76
|
+
relatedRepos?: RepoMeshRelatedRepo[];
|
|
56
77
|
}
|
|
57
78
|
export declare const DEFAULT_MESH_POLICY: RepoMeshPolicy;
|
|
58
79
|
export interface RepoMeshNodeCapabilities {
|
|
@@ -149,6 +170,12 @@ export interface LocalMeshNodeEntry {
|
|
|
149
170
|
policy: RepoMeshNodePolicy;
|
|
150
171
|
/** For single-machine mesh: same daemon, different worktree */
|
|
151
172
|
isLocalWorktree?: boolean;
|
|
173
|
+
/** Branch this worktree tracks (set when created via clone_mesh_node) */
|
|
174
|
+
worktreeBranch?: string;
|
|
175
|
+
/** Node ID this worktree was cloned from */
|
|
176
|
+
clonedFromNodeId?: string;
|
|
177
|
+
/** Optional associated/external repos configured as node metadata. */
|
|
178
|
+
relatedRepos?: RepoMeshRelatedRepo[];
|
|
152
179
|
}
|
|
153
180
|
export interface RepoMeshStatus {
|
|
154
181
|
meshId: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type SessionHostEndpoint } from '@adhdev/session-host-core';
|
|
1
|
+
import { type SessionHostEndpoint, type SessionHostRequestType } from '@adhdev/session-host-core';
|
|
2
2
|
import type { HostedCliRuntimeDescriptor } from '../commands/cli-manager.js';
|
|
3
3
|
export declare function ensureSessionHostReady(options: {
|
|
4
4
|
appName?: string;
|
|
5
5
|
spawnHost: () => void;
|
|
6
6
|
timeoutMs?: number;
|
|
7
|
+
requiredRequestTypes?: readonly SessionHostRequestType[];
|
|
7
8
|
}): Promise<SessionHostEndpoint>;
|
|
8
9
|
export declare function listHostedCliRuntimes(endpoint: SessionHostEndpoint): Promise<HostedCliRuntimeDescriptor[]>;
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -522,6 +522,8 @@ export interface DaemonStatusEventPayload {
|
|
|
522
522
|
timestamp: number;
|
|
523
523
|
targetSessionId?: string;
|
|
524
524
|
providerType?: string;
|
|
525
|
+
providerSessionId?: string;
|
|
526
|
+
workspaceName?: string;
|
|
525
527
|
duration?: number;
|
|
526
528
|
elapsedSec?: number;
|
|
527
529
|
modalMessage?: string;
|
|
@@ -535,6 +537,8 @@ export interface DashboardStatusEventPayload {
|
|
|
535
537
|
daemonId?: string;
|
|
536
538
|
providerType?: string;
|
|
537
539
|
targetSessionId?: string;
|
|
540
|
+
providerSessionId?: string;
|
|
541
|
+
workspaceName?: string;
|
|
538
542
|
duration?: number;
|
|
539
543
|
elapsedSec?: number;
|
|
540
544
|
modalMessage?: string;
|
package/dist/types.d.ts
CHANGED
|
@@ -42,6 +42,15 @@ export interface ChatMessage {
|
|
|
42
42
|
/** Optional: fiber metadata */
|
|
43
43
|
_type?: string;
|
|
44
44
|
_sub?: string;
|
|
45
|
+
/** Transcript visibility/audience contract for separating chat-visible content from internal/debug runtime rows. */
|
|
46
|
+
visibility?: 'visible' | 'user' | 'chat' | 'hidden' | 'debug' | 'internal' | (string & {});
|
|
47
|
+
transcriptVisibility?: 'visible' | 'user' | 'chat' | 'hidden' | 'debug' | 'internal' | (string & {});
|
|
48
|
+
audience?: 'chat' | 'debug' | 'trace' | 'internal' | (string & {});
|
|
49
|
+
source?: 'assistant_text' | 'tool_call' | 'terminal_command' | 'runtime_activity' | 'runtime_status' | 'provider_chrome' | 'control' | (string & {});
|
|
50
|
+
userFacing?: boolean;
|
|
51
|
+
internal?: boolean;
|
|
52
|
+
isInternal?: boolean;
|
|
53
|
+
debug?: boolean;
|
|
45
54
|
/** Meta information for thought/terminal logs etc */
|
|
46
55
|
meta?: {
|
|
47
56
|
label?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.76-rc.
|
|
3
|
+
"version": "0.9.76-rc.61",
|
|
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",
|
|
@@ -50,18 +50,17 @@
|
|
|
50
50
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
51
51
|
"@xterm/xterm": "^6.0.0",
|
|
52
52
|
"chalk": "^5.3.0",
|
|
53
|
-
"chokidar": "^
|
|
53
|
+
"chokidar": "^4.0.3",
|
|
54
54
|
"conf": "^13.0.0",
|
|
55
|
+
"js-yaml": "^4.1.1",
|
|
55
56
|
"node-pty": "^1.2.0-beta.12",
|
|
56
57
|
"ws": "^8.19.0"
|
|
57
58
|
},
|
|
58
|
-
"bundleDependencies": [
|
|
59
|
-
"@adhdev/session-host-core"
|
|
60
|
-
],
|
|
61
59
|
"optionalDependencies": {
|
|
62
60
|
"@adhdev/ghostty-vt-node": "*"
|
|
63
61
|
},
|
|
64
62
|
"devDependencies": {
|
|
63
|
+
"@types/js-yaml": "^4.0.9",
|
|
65
64
|
"@types/node": "^22.0.0",
|
|
66
65
|
"@types/ws": "^8.18.1",
|
|
67
66
|
"tsup": "^8.2.0",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
buildSessionModalDeliverySignature,
|
|
9
9
|
} from './chat-signatures.js'
|
|
10
10
|
import { normalizeManagedStatus } from '../status/normalize.js'
|
|
11
|
+
import { filterUserFacingChatMessages, normalizeChatMessages } from '../providers/chat-message-normalization.js'
|
|
11
12
|
|
|
12
13
|
export interface ChatTailSubscriptionCursor {
|
|
13
14
|
tailLimit: number
|
|
@@ -101,7 +102,8 @@ export function prepareSessionChatTailUpdate(
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
const
|
|
105
|
+
const fullMessages = normalizeChatMessages(Array.isArray(result.messages) ? result.messages as any[] : [])
|
|
106
|
+
const messages = filterUserFacingChatMessages(fullMessages)
|
|
105
107
|
const title = typeof result.title === 'string' ? result.title : undefined
|
|
106
108
|
const activeModal = normalizeChatTailActiveModal(result.activeModal)
|
|
107
109
|
const status = typeof result.status === 'string' ? result.status : 'idle'
|
|
@@ -266,9 +266,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
266
266
|
const currentSnapshot = normalizeScreenSnapshot(screenText);
|
|
267
267
|
const lastSnapshot = this.lastScreenSnapshot;
|
|
268
268
|
if (!lastSnapshot || lastSnapshot === currentSnapshot) return screenText;
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
269
|
+
const activeScreenPattern = /\besc to (?:interrupt|stop)\b|Enter to interrupt, Ctrl\+C to cancel|Enter to confirm\s*[·•-]\s*Esc to cancel|\b(?:MCP servers?|tool calls?)\b[^\n\r]{0,160}\brequire approval\b/i;
|
|
270
|
+
const staleSnapshotLooksActive = activeScreenPattern.test(lastSnapshot);
|
|
271
|
+
const currentScreenLooksIdle = /(?:^|\n|\r)\s*[❯›>]\s*(?:Try\s+["“][^\n\r"”]+["”])?\s*(?:\n|\r|$)/.test(screenText)
|
|
272
|
+
&& !activeScreenPattern.test(screenText);
|
|
272
273
|
if (staleSnapshotLooksActive && currentScreenLooksIdle) return screenText;
|
|
273
274
|
if (currentSnapshot.length >= lastSnapshot.length) return screenText;
|
|
274
275
|
// Terminal screen reads can miss a just-rendered completed Hermes box while
|
|
@@ -421,6 +422,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
421
422
|
provider: CliProviderModule,
|
|
422
423
|
workingDir: string,
|
|
423
424
|
private extraArgs: string[] = [],
|
|
425
|
+
private extraEnv: Record<string, string> = {},
|
|
424
426
|
transportFactory: PtyTransportFactory = new NodePtyTransportFactory(),
|
|
425
427
|
) {
|
|
426
428
|
this.provider = provider;
|
|
@@ -522,6 +524,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
522
524
|
runtimeSettings: this.runtimeSettings,
|
|
523
525
|
workingDir: this.workingDir,
|
|
524
526
|
extraArgs: this.extraArgs,
|
|
527
|
+
extraEnv: this.extraEnv,
|
|
525
528
|
});
|
|
526
529
|
|
|
527
530
|
LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
@@ -1854,9 +1857,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1854
1857
|
};
|
|
1855
1858
|
this.recordTrace('submit_echo_missing', diagnostic);
|
|
1856
1859
|
if (this.requirePromptEchoBeforeSubmit) {
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
+
// At this point the prompt text write already completed. Rejecting without
|
|
1861
|
+
// a submit key can leave the delegated CLI with an unsent prompt sitting at
|
|
1862
|
+
// the input line, which makes later coordinator sends appear stuck. Prefer a
|
|
1863
|
+
// guarded submit after the full echo wait; the existing stuck-submit retry
|
|
1864
|
+
// will send a delayed follow-up Enter if the prompt remains visible.
|
|
1865
|
+
LOG.warn('CLI', `[${this.cliType}] prompt echo was not observed before submit; sending guarded submit key anyway elapsed=${elapsed}ms maxEchoWaitMs=${state.maxEchoWaitMs} screen=${JSON.stringify(diagnostic.screenText).slice(0, 240)}`);
|
|
1866
|
+
this.submitSendKey(state, completion);
|
|
1860
1867
|
return;
|
|
1861
1868
|
}
|
|
1862
1869
|
LOG.warn('CLI', `[${this.cliType}] prompt echo was not observed before submit; sending submit key anyway elapsed=${elapsed}ms maxEchoWaitMs=${state.maxEchoWaitMs}`);
|
|
@@ -1911,7 +1918,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1911
1918
|
? String(parsedStatusBeforeSend.status)
|
|
1912
1919
|
: '';
|
|
1913
1920
|
if (!allowInputDuringGeneration && (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating')) {
|
|
1914
|
-
|
|
1921
|
+
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
1922
|
+
const parsedHasActionableModal = Boolean(
|
|
1923
|
+
parsedModal
|
|
1924
|
+
&& Array.isArray(parsedModal.buttons)
|
|
1925
|
+
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
1926
|
+
);
|
|
1927
|
+
const terminalLooksIdle = this.currentStatus === 'idle'
|
|
1928
|
+
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
1929
|
+
&& !this.isWaitingForResponse
|
|
1930
|
+
&& !this.currentTurnScope
|
|
1931
|
+
&& !this.hasActionableApproval()
|
|
1932
|
+
&& !parsedHasActionableModal;
|
|
1933
|
+
if (!terminalLooksIdle) {
|
|
1934
|
+
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
1935
|
+
}
|
|
1915
1936
|
}
|
|
1916
1937
|
if (this.isWaitingForResponse && !allowInputDuringGeneration) {
|
|
1917
1938
|
if (!this.clearStaleIdleResponseGuard('send_message_guard')) {
|
|
@@ -27,8 +27,9 @@ export function resolveCliSpawnPlan(options: {
|
|
|
27
27
|
runtimeSettings: Record<string, any>;
|
|
28
28
|
workingDir: string;
|
|
29
29
|
extraArgs: string[];
|
|
30
|
+
extraEnv?: Record<string, string>;
|
|
30
31
|
}): CliSpawnPlan {
|
|
31
|
-
const { provider, runtimeSettings, workingDir, extraArgs } = options;
|
|
32
|
+
const { provider, runtimeSettings, workingDir, extraArgs, extraEnv } = options;
|
|
32
33
|
const { spawn: spawnConfig } = provider;
|
|
33
34
|
const configuredCommand = typeof runtimeSettings.executablePath === 'string' && runtimeSettings.executablePath.trim()
|
|
34
35
|
? runtimeSettings.executablePath.trim()
|
|
@@ -65,7 +66,7 @@ export function resolveCliSpawnPlan(options: {
|
|
|
65
66
|
shellArgs = allArgs;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const env = buildCliSpawnEnv(process.env, spawnConfig.env);
|
|
69
|
+
const env = buildCliSpawnEnv(process.env, { ...(spawnConfig.env || {}), ...(extraEnv || {}) });
|
|
69
70
|
// Some CLI agents, notably Hermes, route their tools through TERMINAL_CWD
|
|
70
71
|
// rather than process.cwd(). Keep the generic ADHDev launch workspace as
|
|
71
72
|
// the single source of truth so PTY cwd and tool cwd cannot diverge.
|
|
@@ -19,15 +19,21 @@ import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js
|
|
|
19
19
|
import { buildChatMessageSignature } from '../chat/chat-signatures.js';
|
|
20
20
|
import type { ChatMessage } from '../types.js';
|
|
21
21
|
import type { SessionTransport } from '../shared-types.js';
|
|
22
|
+
import { filterUserFacingChatMessages, normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
22
23
|
|
|
23
24
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
24
25
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
|
+
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
25
27
|
const recentSendByTarget = new Map<string, number>();
|
|
26
28
|
|
|
27
29
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
28
30
|
recordApprovalSelection?(buttonText: string): void;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
interface RuntimeChatMessageMerger extends ProviderInstance {
|
|
34
|
+
mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
|
|
32
38
|
|
|
33
39
|
function getCurrentProviderType(h: CommandHelpers, fallback = ''): string {
|
|
@@ -97,6 +103,19 @@ function getSendChatInputEnvelope(args: any): InputEnvelope {
|
|
|
97
103
|
return normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
function sleep(ms: number): Promise<void> {
|
|
107
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: string) => void): Promise<void> {
|
|
111
|
+
if (adapter.cliType !== 'hermes-cli') return;
|
|
112
|
+
const status = typeof adapter.getStatus === 'function' ? adapter.getStatus()?.status : undefined;
|
|
113
|
+
if (status !== 'starting') return;
|
|
114
|
+
|
|
115
|
+
log(`Hermes CLI is still starting; waiting ${HERMES_CLI_STARTING_SEND_SETTLE_MS}ms before first send`);
|
|
116
|
+
await sleep(HERMES_CLI_STARTING_SEND_SETTLE_MS);
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
101
120
|
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
102
121
|
if (explicit) return explicit;
|
|
@@ -177,7 +196,7 @@ function normalizeReadChatTailLimit(args: any): number {
|
|
|
177
196
|
|
|
178
197
|
function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[] {
|
|
179
198
|
const messages = Array.isArray(payload.messages) ? payload.messages as ChatMessage[] : [];
|
|
180
|
-
return messages;
|
|
199
|
+
return normalizeChatMessages(messages);
|
|
181
200
|
}
|
|
182
201
|
|
|
183
202
|
|
|
@@ -250,6 +269,40 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
|
|
272
|
+
function isGeneratingLikeStatus(status: unknown): boolean {
|
|
273
|
+
return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
|
|
277
|
+
if (!isGeneratingLikeStatus(parsedStatus)) return false;
|
|
278
|
+
if (hasNonEmptyModalButtons(activeModal)) return false;
|
|
279
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
280
|
+
if (adapterRawStatus !== 'idle') return false;
|
|
281
|
+
if (typeof adapter.isProcessing === 'function' && adapter.isProcessing()) return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
286
|
+
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
287
|
+
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function finalizeStreamingMessagesWhenIdle(messages: ChatMessage[], status: string): ChatMessage[] {
|
|
291
|
+
if (status !== 'idle') return messages;
|
|
292
|
+
return messages.map((message) => {
|
|
293
|
+
const meta = message.meta && typeof message.meta === 'object'
|
|
294
|
+
? message.meta as Record<string, unknown>
|
|
295
|
+
: undefined;
|
|
296
|
+
const hasStreamingMeta = meta?.streaming === true;
|
|
297
|
+
if (message.bubbleState !== 'streaming' && !hasStreamingMeta) return message;
|
|
298
|
+
return {
|
|
299
|
+
...message,
|
|
300
|
+
...(message.bubbleState === 'streaming' ? { bubbleState: 'final' as const } : {}),
|
|
301
|
+
...(hasStreamingMeta ? { meta: { ...meta, streaming: false } } : {}),
|
|
302
|
+
};
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
253
306
|
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
254
307
|
let validatedPayload: Record<string, any>;
|
|
255
308
|
const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
|
|
@@ -264,13 +317,26 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
264
317
|
return { success: false, error: error?.message || String(error) };
|
|
265
318
|
}
|
|
266
319
|
const messages = normalizeReadChatMessages(validatedPayload);
|
|
267
|
-
const
|
|
320
|
+
const visibleMessages = filterUserFacingChatMessages(messages);
|
|
321
|
+
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
322
|
+
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
323
|
+
const returnedDebugReadChat = debugReadChat
|
|
324
|
+
? {
|
|
325
|
+
...debugReadChat,
|
|
326
|
+
fullMsgCount: typeof debugReadChat.fullMsgCount === 'number'
|
|
327
|
+
? debugReadChat.fullMsgCount
|
|
328
|
+
: messages.length,
|
|
329
|
+
visibleMsgCount: visibleMessages.length,
|
|
330
|
+
hiddenMsgCount,
|
|
331
|
+
returnedMsgCount: sync.messages.length,
|
|
332
|
+
}
|
|
333
|
+
: undefined;
|
|
268
334
|
return {
|
|
269
335
|
success: true,
|
|
270
336
|
...validatedPayload,
|
|
271
337
|
messages: sync.messages,
|
|
272
338
|
totalMessages: sync.totalMessages,
|
|
273
|
-
...(
|
|
339
|
+
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
274
340
|
};
|
|
275
341
|
}
|
|
276
342
|
|
|
@@ -464,6 +530,18 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
464
530
|
const readChat = bundle.readChat && typeof bundle.readChat === 'object' ? bundle.readChat as Record<string, unknown> : {};
|
|
465
531
|
const cli = bundle.cli && typeof bundle.cli === 'object' ? bundle.cli as Record<string, unknown> : null;
|
|
466
532
|
const frontend = bundle.frontend && typeof bundle.frontend === 'object' ? bundle.frontend as Record<string, unknown> : null;
|
|
533
|
+
const debugReadChat = readChat.debugReadChat && typeof readChat.debugReadChat === 'object'
|
|
534
|
+
? readChat.debugReadChat as Record<string, unknown>
|
|
535
|
+
: {};
|
|
536
|
+
const parsedStatus = cli?.parsedStatus && typeof cli.parsedStatus === 'object'
|
|
537
|
+
? cli.parsedStatus as Record<string, unknown>
|
|
538
|
+
: null;
|
|
539
|
+
const cliParsedMessageCount = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : undefined;
|
|
540
|
+
const readChatReturnedMessages = Array.isArray(readChat.messagesTail) ? readChat.messagesTail.length : undefined;
|
|
541
|
+
const cliPartialResponse = typeof cli?.partialResponse === 'string' ? cli.partialResponse : '';
|
|
542
|
+
const readChatStatus = typeof readChat.status === 'string' ? readChat.status : '';
|
|
543
|
+
const cliStatus = typeof cli?.status === 'string' ? cli.status : '';
|
|
544
|
+
const cliParsedStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status : '';
|
|
467
545
|
return {
|
|
468
546
|
createdAt: bundle.createdAt,
|
|
469
547
|
targetSessionId: target.targetSessionId,
|
|
@@ -472,8 +550,22 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
472
550
|
readChatSuccess: readChat.success,
|
|
473
551
|
readChatStatus: readChat.status,
|
|
474
552
|
readChatTotalMessages: readChat.totalMessages,
|
|
553
|
+
readChatReturnedMessages,
|
|
475
554
|
cliStatus: cli?.status,
|
|
555
|
+
cliParsedStatus: cliParsedStatus || undefined,
|
|
476
556
|
cliMessageCount: cli?.messageCount,
|
|
557
|
+
cliParsedMessageCount,
|
|
558
|
+
cliPartialResponseChars: cliPartialResponse.length,
|
|
559
|
+
parserAdapterStatusMismatch: Boolean(cliStatus && cliParsedStatus && cliStatus !== cliParsedStatus),
|
|
560
|
+
parserReadChatStatusMismatch: Boolean(readChatStatus && cliParsedStatus && readChatStatus !== cliParsedStatus),
|
|
561
|
+
readChatDebug: Object.keys(debugReadChat).length ? {
|
|
562
|
+
adapterStatus: debugReadChat.adapterStatus,
|
|
563
|
+
parsedStatus: debugReadChat.parsedStatus,
|
|
564
|
+
returnedStatus: debugReadChat.returnedStatus,
|
|
565
|
+
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
566
|
+
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
567
|
+
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
568
|
+
} : undefined,
|
|
477
569
|
hasFrontendSnapshot: !!frontend,
|
|
478
570
|
};
|
|
479
571
|
}
|
|
@@ -720,7 +812,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
720
812
|
}
|
|
721
813
|
|
|
722
814
|
export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
723
|
-
const provider = h.getProvider(args?.agentType);
|
|
815
|
+
const provider = h.getProvider(args?.agentType || args?.providerType);
|
|
724
816
|
const transport = getTargetTransport(h, provider);
|
|
725
817
|
const historySessionId = getHistorySessionId(h, args);
|
|
726
818
|
|
|
@@ -760,10 +852,17 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
760
852
|
? parsedRecord.coverage
|
|
761
853
|
: undefined;
|
|
762
854
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
763
|
-
const returnedStatus = parsedRecord.status
|
|
764
|
-
|
|
855
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
856
|
+
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
857
|
+
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
858
|
+
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
859
|
+
&& runtimeMessageMerger.type === adapter.cliType
|
|
860
|
+
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
861
|
+
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
862
|
+
: parsedMessages;
|
|
863
|
+
LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
|
|
765
864
|
return buildReadChatCommandResult({
|
|
766
|
-
messages:
|
|
865
|
+
messages: returnedMessages,
|
|
767
866
|
status: returnedStatus,
|
|
768
867
|
activeModal,
|
|
769
868
|
debugReadChat: {
|
|
@@ -774,7 +873,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
774
873
|
returnedStatus: String(returnedStatus || ''),
|
|
775
874
|
shouldPreferAdapterMessages: false,
|
|
776
875
|
parsedMsgCount: parsedRecord.messages.length,
|
|
777
|
-
returnedMsgCount:
|
|
876
|
+
returnedMsgCount: returnedMessages.length,
|
|
778
877
|
},
|
|
779
878
|
...(title ? { title } : {}),
|
|
780
879
|
...(providerSessionId ? { providerSessionId } : {}),
|
|
@@ -1049,6 +1148,7 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1049
1148
|
try {
|
|
1050
1149
|
assertTextOnlyInput(provider, input);
|
|
1051
1150
|
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
1151
|
+
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1052
1152
|
await adapter.sendMessage(text);
|
|
1053
1153
|
return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
|
|
1054
1154
|
} catch (e: any) {
|
|
@@ -1571,11 +1671,26 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1571
1671
|
&& status.activeModal.buttons.some((candidate) => typeof candidate === 'string' && candidate.trim())
|
|
1572
1672
|
? status.activeModal
|
|
1573
1673
|
: null;
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1674
|
+
const parsedStatus = !statusModal && !surfacedModal && typeof adapter.getScriptParsedStatus === 'function'
|
|
1675
|
+
? (() => {
|
|
1676
|
+
try {
|
|
1677
|
+
return parseMaybeJson(adapter.getScriptParsedStatus());
|
|
1678
|
+
} catch {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
})()
|
|
1682
|
+
: null;
|
|
1683
|
+
const parsedModal = parsedStatus?.status === 'waiting_approval'
|
|
1684
|
+
&& parsedStatus?.activeModal
|
|
1685
|
+
&& Array.isArray(parsedStatus.activeModal.buttons)
|
|
1686
|
+
&& parsedStatus.activeModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim())
|
|
1687
|
+
? parsedStatus.activeModal
|
|
1688
|
+
: null;
|
|
1689
|
+
const effectiveModal = statusModal || surfacedModal || parsedModal;
|
|
1690
|
+
const effectiveStatus = status?.status === 'waiting_approval' || targetState?.activeChat?.status === 'waiting_approval' || parsedStatus?.status === 'waiting_approval'
|
|
1576
1691
|
? 'waiting_approval'
|
|
1577
1692
|
: status?.status;
|
|
1578
|
-
LOG.info('Command', `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || '')} rawStatus=${String(status?.status || '')} effectiveStatus=${String(effectiveStatus || '')} statusModal=${statusModal ? 'yes' : 'no'} surfacedModal=${surfacedModal ? 'yes' : 'no'} instance=${targetInstance ? 'yes' : 'no'}`);
|
|
1693
|
+
LOG.info('Command', `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || '')} rawStatus=${String(status?.status || '')} effectiveStatus=${String(effectiveStatus || '')} statusModal=${statusModal ? 'yes' : 'no'} surfacedModal=${surfacedModal ? 'yes' : 'no'} parsedModal=${parsedModal ? 'yes' : 'no'} instance=${targetInstance ? 'yes' : 'no'}`);
|
|
1579
1694
|
if (!effectiveModal) {
|
|
1580
1695
|
return { success: false, error: 'Not in approval state' };
|
|
1581
1696
|
}
|