@adhdev/daemon-core 0.9.76-rc.5 → 0.9.76-rc.51
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.js +1398 -436
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1425 -467
- 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 +11 -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 +13 -0
- package/dist/shared-types.d.ts +4 -0
- package/package.json +4 -5
- 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 +109 -8
- package/src/commands/cli-manager.ts +78 -5
- package/src/commands/handler.ts +13 -4
- package/src/commands/mesh-coordinator.ts +149 -6
- package/src/commands/router.d.ts +1 -0
- package/src/commands/router.ts +554 -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/mesh/coordinator-prompt.ts +25 -10
- package/src/mesh/mesh-events.ts +109 -43
- package/src/providers/chat-message-normalization.ts +54 -0
- package/src/providers/cli-provider-instance.d.ts +2 -0
- package/src/providers/cli-provider-instance.ts +58 -7
- package/src/providers/provider-instance-manager.ts +20 -1
- package/src/providers/provider-instance.ts +2 -0
- package/src/repo-mesh-types.ts +15 -0
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +17 -12
- package/src/status/reporter.ts +6 -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;
|
|
@@ -63,3 +63,14 @@ export declare function buildUserChatMessage<T extends Omit<ChatMessage, 'role'
|
|
|
63
63
|
});
|
|
64
64
|
export declare function normalizeChatMessage<T extends ChatMessage>(message: T): T;
|
|
65
65
|
export declare function normalizeChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[];
|
|
66
|
+
/**
|
|
67
|
+
* Product chat transcript visibility contract.
|
|
68
|
+
*
|
|
69
|
+
* read_chat/debug paths may preserve every normalized message, including tool,
|
|
70
|
+
* terminal, thought, status, and control rows. The default user-facing chat UX
|
|
71
|
+
* should only render meaningful conversation turns unless a producer explicitly
|
|
72
|
+
* marks a non-standard row as user-facing. This keeps internal tool/status/control
|
|
73
|
+
* plumbing out of the ordinary transcript without matching provider-specific text.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isUserFacingChatMessage(message: ChatMessage | null | undefined): boolean;
|
|
76
|
+
export declare function filterUserFacingChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): 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,19 @@ 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;
|
|
51
58
|
}
|
|
52
59
|
export interface RepoMeshNodePolicy {
|
|
53
60
|
readOnly?: boolean;
|
|
54
61
|
canPush?: boolean;
|
|
55
62
|
maxConcurrentSessions?: number;
|
|
63
|
+
/** Ordered provider preference used when mesh_launch_session omits an explicit type. */
|
|
64
|
+
providerPriority?: string[];
|
|
56
65
|
}
|
|
57
66
|
export declare const DEFAULT_MESH_POLICY: RepoMeshPolicy;
|
|
58
67
|
export interface RepoMeshNodeCapabilities {
|
|
@@ -149,6 +158,10 @@ export interface LocalMeshNodeEntry {
|
|
|
149
158
|
policy: RepoMeshNodePolicy;
|
|
150
159
|
/** For single-machine mesh: same daemon, different worktree */
|
|
151
160
|
isLocalWorktree?: boolean;
|
|
161
|
+
/** Branch this worktree tracks (set when created via clone_mesh_node) */
|
|
162
|
+
worktreeBranch?: string;
|
|
163
|
+
/** Node ID this worktree was cloned from */
|
|
164
|
+
clonedFromNodeId?: string;
|
|
152
165
|
}
|
|
153
166
|
export interface RepoMeshStatus {
|
|
154
167
|
meshId: string;
|
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/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.51",
|
|
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",
|
|
@@ -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.
|
|
@@ -22,12 +22,17 @@ import type { SessionTransport } from '../shared-types.js';
|
|
|
22
22
|
|
|
23
23
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
24
24
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
25
|
+
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
25
26
|
const recentSendByTarget = new Map<string, number>();
|
|
26
27
|
|
|
27
28
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
28
29
|
recordApprovalSelection?(buttonText: string): void;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
interface RuntimeChatMessageMerger extends ProviderInstance {
|
|
33
|
+
mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
|
|
32
37
|
|
|
33
38
|
function getCurrentProviderType(h: CommandHelpers, fallback = ''): string {
|
|
@@ -97,6 +102,19 @@ function getSendChatInputEnvelope(args: any): InputEnvelope {
|
|
|
97
102
|
return normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
function sleep(ms: number): Promise<void> {
|
|
106
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: string) => void): Promise<void> {
|
|
110
|
+
if (adapter.cliType !== 'hermes-cli') return;
|
|
111
|
+
const status = typeof adapter.getStatus === 'function' ? adapter.getStatus()?.status : undefined;
|
|
112
|
+
if (status !== 'starting') return;
|
|
113
|
+
|
|
114
|
+
log(`Hermes CLI is still starting; waiting ${HERMES_CLI_STARTING_SEND_SETTLE_MS}ms before first send`);
|
|
115
|
+
await sleep(HERMES_CLI_STARTING_SEND_SETTLE_MS);
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
101
119
|
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
102
120
|
if (explicit) return explicit;
|
|
@@ -250,6 +268,40 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
250
268
|
}
|
|
251
269
|
}
|
|
252
270
|
|
|
271
|
+
function isGeneratingLikeStatus(status: unknown): boolean {
|
|
272
|
+
return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
|
|
276
|
+
if (!isGeneratingLikeStatus(parsedStatus)) return false;
|
|
277
|
+
if (hasNonEmptyModalButtons(activeModal)) return false;
|
|
278
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
279
|
+
if (adapterRawStatus !== 'idle') return false;
|
|
280
|
+
if (typeof adapter.isProcessing === 'function' && adapter.isProcessing()) return false;
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
285
|
+
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
286
|
+
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function finalizeStreamingMessagesWhenIdle(messages: ChatMessage[], status: string): ChatMessage[] {
|
|
290
|
+
if (status !== 'idle') return messages;
|
|
291
|
+
return messages.map((message) => {
|
|
292
|
+
const meta = message.meta && typeof message.meta === 'object'
|
|
293
|
+
? message.meta as Record<string, unknown>
|
|
294
|
+
: undefined;
|
|
295
|
+
const hasStreamingMeta = meta?.streaming === true;
|
|
296
|
+
if (message.bubbleState !== 'streaming' && !hasStreamingMeta) return message;
|
|
297
|
+
return {
|
|
298
|
+
...message,
|
|
299
|
+
...(message.bubbleState === 'streaming' ? { bubbleState: 'final' as const } : {}),
|
|
300
|
+
...(hasStreamingMeta ? { meta: { ...meta, streaming: false } } : {}),
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
253
305
|
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
254
306
|
let validatedPayload: Record<string, any>;
|
|
255
307
|
const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
|
|
@@ -464,6 +516,18 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
464
516
|
const readChat = bundle.readChat && typeof bundle.readChat === 'object' ? bundle.readChat as Record<string, unknown> : {};
|
|
465
517
|
const cli = bundle.cli && typeof bundle.cli === 'object' ? bundle.cli as Record<string, unknown> : null;
|
|
466
518
|
const frontend = bundle.frontend && typeof bundle.frontend === 'object' ? bundle.frontend as Record<string, unknown> : null;
|
|
519
|
+
const debugReadChat = readChat.debugReadChat && typeof readChat.debugReadChat === 'object'
|
|
520
|
+
? readChat.debugReadChat as Record<string, unknown>
|
|
521
|
+
: {};
|
|
522
|
+
const parsedStatus = cli?.parsedStatus && typeof cli.parsedStatus === 'object'
|
|
523
|
+
? cli.parsedStatus as Record<string, unknown>
|
|
524
|
+
: null;
|
|
525
|
+
const cliParsedMessageCount = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : undefined;
|
|
526
|
+
const readChatReturnedMessages = Array.isArray(readChat.messagesTail) ? readChat.messagesTail.length : undefined;
|
|
527
|
+
const cliPartialResponse = typeof cli?.partialResponse === 'string' ? cli.partialResponse : '';
|
|
528
|
+
const readChatStatus = typeof readChat.status === 'string' ? readChat.status : '';
|
|
529
|
+
const cliStatus = typeof cli?.status === 'string' ? cli.status : '';
|
|
530
|
+
const cliParsedStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status : '';
|
|
467
531
|
return {
|
|
468
532
|
createdAt: bundle.createdAt,
|
|
469
533
|
targetSessionId: target.targetSessionId,
|
|
@@ -472,8 +536,22 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
472
536
|
readChatSuccess: readChat.success,
|
|
473
537
|
readChatStatus: readChat.status,
|
|
474
538
|
readChatTotalMessages: readChat.totalMessages,
|
|
539
|
+
readChatReturnedMessages,
|
|
475
540
|
cliStatus: cli?.status,
|
|
541
|
+
cliParsedStatus: cliParsedStatus || undefined,
|
|
476
542
|
cliMessageCount: cli?.messageCount,
|
|
543
|
+
cliParsedMessageCount,
|
|
544
|
+
cliPartialResponseChars: cliPartialResponse.length,
|
|
545
|
+
parserAdapterStatusMismatch: Boolean(cliStatus && cliParsedStatus && cliStatus !== cliParsedStatus),
|
|
546
|
+
parserReadChatStatusMismatch: Boolean(readChatStatus && cliParsedStatus && readChatStatus !== cliParsedStatus),
|
|
547
|
+
readChatDebug: Object.keys(debugReadChat).length ? {
|
|
548
|
+
adapterStatus: debugReadChat.adapterStatus,
|
|
549
|
+
parsedStatus: debugReadChat.parsedStatus,
|
|
550
|
+
returnedStatus: debugReadChat.returnedStatus,
|
|
551
|
+
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
552
|
+
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
553
|
+
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
554
|
+
} : undefined,
|
|
477
555
|
hasFrontendSnapshot: !!frontend,
|
|
478
556
|
};
|
|
479
557
|
}
|
|
@@ -720,7 +798,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
720
798
|
}
|
|
721
799
|
|
|
722
800
|
export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
723
|
-
const provider = h.getProvider(args?.agentType);
|
|
801
|
+
const provider = h.getProvider(args?.agentType || args?.providerType);
|
|
724
802
|
const transport = getTargetTransport(h, provider);
|
|
725
803
|
const historySessionId = getHistorySessionId(h, args);
|
|
726
804
|
|
|
@@ -760,10 +838,17 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
760
838
|
? parsedRecord.coverage
|
|
761
839
|
: undefined;
|
|
762
840
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
763
|
-
const returnedStatus = parsedRecord.status
|
|
764
|
-
|
|
841
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
842
|
+
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
843
|
+
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
844
|
+
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
845
|
+
&& runtimeMessageMerger.type === adapter.cliType
|
|
846
|
+
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
847
|
+
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
848
|
+
: parsedMessages;
|
|
849
|
+
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
850
|
return buildReadChatCommandResult({
|
|
766
|
-
messages:
|
|
851
|
+
messages: returnedMessages,
|
|
767
852
|
status: returnedStatus,
|
|
768
853
|
activeModal,
|
|
769
854
|
debugReadChat: {
|
|
@@ -774,7 +859,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
774
859
|
returnedStatus: String(returnedStatus || ''),
|
|
775
860
|
shouldPreferAdapterMessages: false,
|
|
776
861
|
parsedMsgCount: parsedRecord.messages.length,
|
|
777
|
-
returnedMsgCount:
|
|
862
|
+
returnedMsgCount: returnedMessages.length,
|
|
778
863
|
},
|
|
779
864
|
...(title ? { title } : {}),
|
|
780
865
|
...(providerSessionId ? { providerSessionId } : {}),
|
|
@@ -1049,6 +1134,7 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1049
1134
|
try {
|
|
1050
1135
|
assertTextOnlyInput(provider, input);
|
|
1051
1136
|
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
1137
|
+
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1052
1138
|
await adapter.sendMessage(text);
|
|
1053
1139
|
return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
|
|
1054
1140
|
} catch (e: any) {
|
|
@@ -1571,11 +1657,26 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1571
1657
|
&& status.activeModal.buttons.some((candidate) => typeof candidate === 'string' && candidate.trim())
|
|
1572
1658
|
? status.activeModal
|
|
1573
1659
|
: null;
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1660
|
+
const parsedStatus = !statusModal && !surfacedModal && typeof adapter.getScriptParsedStatus === 'function'
|
|
1661
|
+
? (() => {
|
|
1662
|
+
try {
|
|
1663
|
+
return parseMaybeJson(adapter.getScriptParsedStatus());
|
|
1664
|
+
} catch {
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
})()
|
|
1668
|
+
: null;
|
|
1669
|
+
const parsedModal = parsedStatus?.status === 'waiting_approval'
|
|
1670
|
+
&& parsedStatus?.activeModal
|
|
1671
|
+
&& Array.isArray(parsedStatus.activeModal.buttons)
|
|
1672
|
+
&& parsedStatus.activeModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim())
|
|
1673
|
+
? parsedStatus.activeModal
|
|
1674
|
+
: null;
|
|
1675
|
+
const effectiveModal = statusModal || surfacedModal || parsedModal;
|
|
1676
|
+
const effectiveStatus = status?.status === 'waiting_approval' || targetState?.activeChat?.status === 'waiting_approval' || parsedStatus?.status === 'waiting_approval'
|
|
1576
1677
|
? 'waiting_approval'
|
|
1577
1678
|
: 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'}`);
|
|
1679
|
+
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
1680
|
if (!effectiveModal) {
|
|
1580
1681
|
return { success: false, error: 'Not in approval state' };
|
|
1581
1682
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
|
-
import { existsSync } from 'fs';
|
|
11
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
12
12
|
import { execFileSync } from 'child_process';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -132,6 +132,62 @@ type CliAdapterWithExtraArgs = CliAdapter & {
|
|
|
132
132
|
extraArgs?: string[];
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
type CliStartOptions = {
|
|
136
|
+
resumeSessionId?: string;
|
|
137
|
+
settingsOverride?: Record<string, any>;
|
|
138
|
+
extraEnv?: Record<string, string>;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const COORDINATOR_DELEGATED_ENV_UNSETS: Record<string, string> = {
|
|
142
|
+
ADHDEV_INLINE_MESH: '',
|
|
143
|
+
ADHDEV_MCP_TRANSPORT: '',
|
|
144
|
+
ADHDEV_MESH_ID: '',
|
|
145
|
+
HERMES_EPHEMERAL_SYSTEM_PROMPT: '',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export interface CoordinatorDelegatedCliLaunchOptionsInput {
|
|
149
|
+
cliType: string;
|
|
150
|
+
workspace: string;
|
|
151
|
+
cliArgs?: string[];
|
|
152
|
+
env?: Record<string, string>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface CoordinatorDelegatedCliLaunchOptions {
|
|
156
|
+
cliArgs: string[];
|
|
157
|
+
env: Record<string, string>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hasCliArg(args: string[], flag: string): boolean {
|
|
161
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ensureEmptyDelegatedMcpConfig(workspace: string): string {
|
|
165
|
+
const baseDir = path.join(os.tmpdir(), 'adhdev-delegated-agent-empty-mcp');
|
|
166
|
+
mkdirSync(baseDir, { recursive: true });
|
|
167
|
+
const workspaceHash = crypto.createHash('sha256').update(path.resolve(workspace || os.tmpdir())).digest('hex').slice(0, 16);
|
|
168
|
+
const filePath = path.join(baseDir, `${workspaceHash}.json`);
|
|
169
|
+
writeFileSync(filePath, JSON.stringify({ mcpServers: {} }, null, 2), 'utf-8');
|
|
170
|
+
return filePath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function buildCoordinatorDelegatedCliLaunchOptions(
|
|
174
|
+
input: CoordinatorDelegatedCliLaunchOptionsInput,
|
|
175
|
+
): CoordinatorDelegatedCliLaunchOptions {
|
|
176
|
+
const cliType = String(input.cliType || '').trim();
|
|
177
|
+
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
178
|
+
const env: Record<string, string> = { ...(input.env || {}), ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
179
|
+
|
|
180
|
+
if (cliType === 'hermes-cli' && !hasCliArg(cliArgs, '--ignore-user-config')) {
|
|
181
|
+
cliArgs.unshift('--ignore-user-config');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (cliType === 'claude-cli' && !hasCliArg(cliArgs, '--mcp-config')) {
|
|
185
|
+
cliArgs.unshift('--mcp-config', ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { cliArgs, env };
|
|
189
|
+
}
|
|
190
|
+
|
|
135
191
|
function isUuid(value: string): boolean {
|
|
136
192
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
137
193
|
}
|
|
@@ -365,6 +421,7 @@ export class DaemonCliManager {
|
|
|
365
421
|
runtimeId: string,
|
|
366
422
|
providerSessionId?: string,
|
|
367
423
|
attachExisting = false,
|
|
424
|
+
extraEnv?: Record<string, string>,
|
|
368
425
|
): CliAdapter {
|
|
369
426
|
// cliType normalize (Resolve alias)
|
|
370
427
|
const normalizedType = this.providerLoader.resolveAlias(cliType);
|
|
@@ -382,7 +439,7 @@ export class DaemonCliManager {
|
|
|
382
439
|
providerSessionId,
|
|
383
440
|
attachExisting,
|
|
384
441
|
);
|
|
385
|
-
return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
442
|
+
return new ProviderCliAdapter(resolvedProvider as CliProviderModule, workingDir, cliArgs, extraEnv || {}, transportFactory);
|
|
386
443
|
}
|
|
387
444
|
|
|
388
445
|
throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
|
|
@@ -425,6 +482,7 @@ export class DaemonCliManager {
|
|
|
425
482
|
options?: {
|
|
426
483
|
providerSessionId?: string;
|
|
427
484
|
launchMode?: CliLaunchMode;
|
|
485
|
+
extraEnv?: Record<string, string>;
|
|
428
486
|
onProviderSessionResolved?: (info: {
|
|
429
487
|
instanceId: string;
|
|
430
488
|
providerType: string;
|
|
@@ -480,7 +538,7 @@ export class DaemonCliManager {
|
|
|
480
538
|
workingDir: string,
|
|
481
539
|
cliArgs?: string[],
|
|
482
540
|
initialModel?: string,
|
|
483
|
-
options?:
|
|
541
|
+
options?: CliStartOptions,
|
|
484
542
|
): Promise<{ runtimeSessionId: string; providerSessionId?: string }> {
|
|
485
543
|
const trimmed = (workingDir || '').trim();
|
|
486
544
|
if (!trimmed) throw new Error('working directory required');
|
|
@@ -629,6 +687,7 @@ export class DaemonCliManager {
|
|
|
629
687
|
{
|
|
630
688
|
providerSessionId: sessionBinding.providerSessionId,
|
|
631
689
|
launchMode: sessionBinding.launchMode,
|
|
690
|
+
extraEnv: options?.extraEnv,
|
|
632
691
|
onProviderSessionResolved: ({ providerSessionId, providerName, providerType, workspace }) => {
|
|
633
692
|
this.persistRecentActivity({
|
|
634
693
|
kind: 'cli',
|
|
@@ -651,6 +710,7 @@ export class DaemonCliManager {
|
|
|
651
710
|
key,
|
|
652
711
|
sessionBinding.providerSessionId,
|
|
653
712
|
false,
|
|
713
|
+
options?.extraEnv,
|
|
654
714
|
);
|
|
655
715
|
try {
|
|
656
716
|
await adapter.spawn();
|
|
@@ -899,12 +959,25 @@ export class DaemonCliManager {
|
|
|
899
959
|
const launchSource = resolved.source;
|
|
900
960
|
if (!cliType) throw new Error('cliType required');
|
|
901
961
|
|
|
962
|
+
const settingsOverride = args?.settings && typeof args.settings === 'object' ? args.settings : undefined;
|
|
963
|
+
const delegatedLaunch = settingsOverride?.launchedByCoordinator === true
|
|
964
|
+
? buildCoordinatorDelegatedCliLaunchOptions({
|
|
965
|
+
cliType,
|
|
966
|
+
workspace: dir,
|
|
967
|
+
cliArgs: args?.cliArgs,
|
|
968
|
+
env: args?.env,
|
|
969
|
+
})
|
|
970
|
+
: null;
|
|
902
971
|
const started = await this.startSession(
|
|
903
972
|
cliType,
|
|
904
973
|
dir,
|
|
905
|
-
args?.cliArgs,
|
|
974
|
+
delegatedLaunch ? delegatedLaunch.cliArgs : args?.cliArgs,
|
|
906
975
|
args?.initialModel,
|
|
907
|
-
{
|
|
976
|
+
{
|
|
977
|
+
resumeSessionId: args?.resumeSessionId,
|
|
978
|
+
settingsOverride,
|
|
979
|
+
extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
|
|
980
|
+
},
|
|
908
981
|
);
|
|
909
982
|
|
|
910
983
|
return {
|
package/src/commands/handler.ts
CHANGED
|
@@ -303,14 +303,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
303
303
|
const sessionLookupFailed = !!targetSessionId && !session;
|
|
304
304
|
|
|
305
305
|
const managerKey = this.extractIdeType(args, sessionLookupFailed);
|
|
306
|
-
let providerType: string | undefined;
|
|
306
|
+
let providerType: string | undefined = args?.agentType || args?.providerType;
|
|
307
307
|
|
|
308
308
|
if (!sessionLookupFailed) {
|
|
309
309
|
providerType =
|
|
310
310
|
session?.providerType
|
|
311
|
-
||
|
|
312
|
-
|| args?.providerType
|
|
311
|
+
|| providerType
|
|
313
312
|
|| this.inferProviderType(managerKey);
|
|
313
|
+
} else if (!providerType) {
|
|
314
|
+
providerType = this.inferProviderType(managerKey);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
return { session, managerKey, providerType, sessionLookupFailed };
|
|
@@ -407,7 +408,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
407
408
|
'invoke_provider_script',
|
|
408
409
|
]);
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
const allowsInactiveReadChatFallback =
|
|
412
|
+
cmd === 'read_chat'
|
|
413
|
+
&& !!this._currentRoute.providerType
|
|
414
|
+
&& (
|
|
415
|
+
(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
|
|
416
|
+
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
|
|
411
420
|
const result = {
|
|
412
421
|
success: false,
|
|
413
422
|
error: `Live session not found for targetSessionId: ${String(args?.targetSessionId || '').trim() || 'unknown'}`,
|