@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
|
@@ -5,6 +5,43 @@ export const BUILTIN_CHAT_MESSAGE_KINDS = ['standard', 'thought', 'tool', 'termi
|
|
|
5
5
|
export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
6
6
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
7
7
|
|
|
8
|
+
export const CHAT_MESSAGE_VISIBILITIES = ['user', 'debug', 'internal', 'hidden'] as const;
|
|
9
|
+
export const CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ['visible', 'chat', 'user', 'debug', 'internal', 'hidden'] as const;
|
|
10
|
+
export const CHAT_MESSAGE_AUDIENCES = ['chat', 'debug', 'trace', 'internal'] as const;
|
|
11
|
+
export const CHAT_MESSAGE_SOURCES = [
|
|
12
|
+
'assistant_text',
|
|
13
|
+
'tool_call',
|
|
14
|
+
'terminal_command',
|
|
15
|
+
'runtime_activity',
|
|
16
|
+
'runtime_status',
|
|
17
|
+
'provider_chrome',
|
|
18
|
+
'control',
|
|
19
|
+
] as const;
|
|
20
|
+
export const CHAT_MESSAGE_ACTIVITY_SOURCES = ['tool_call', 'terminal_command', 'runtime_activity'] as const;
|
|
21
|
+
export const CHAT_MESSAGE_INTERNAL_SOURCES = ['runtime_status', 'provider_chrome', 'control'] as const;
|
|
22
|
+
|
|
23
|
+
export type ChatMessageVisibility = typeof CHAT_MESSAGE_VISIBILITIES[number] | (string & {});
|
|
24
|
+
export type ChatMessageTranscriptVisibility = typeof CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES[number] | (string & {});
|
|
25
|
+
export type ChatMessageAudience = typeof CHAT_MESSAGE_AUDIENCES[number] | (string & {});
|
|
26
|
+
export type ChatMessageSource = typeof CHAT_MESSAGE_SOURCES[number] | (string & {});
|
|
27
|
+
export type ChatMessageTranscriptSurface = 'chat' | 'activity' | 'internal';
|
|
28
|
+
|
|
29
|
+
export interface ChatMessageVisibilityClassification {
|
|
30
|
+
surface: ChatMessageTranscriptSurface;
|
|
31
|
+
isUserFacing: boolean;
|
|
32
|
+
isActivityFacing: boolean;
|
|
33
|
+
isInternal: boolean;
|
|
34
|
+
explicitUserFacing: boolean;
|
|
35
|
+
explicitHidden: boolean;
|
|
36
|
+
role: string;
|
|
37
|
+
kind: ChatMessageKind;
|
|
38
|
+
visibility: string;
|
|
39
|
+
transcriptVisibility: string;
|
|
40
|
+
audience: string;
|
|
41
|
+
source: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
8
45
|
const KNOWN_CHAT_MESSAGE_KINDS = new Set<string>(BUILTIN_CHAT_MESSAGE_KINDS);
|
|
9
46
|
const CHAT_MESSAGE_KIND_ALIASES: Record<string, BuiltinChatMessageKind> = {
|
|
10
47
|
text: 'standard',
|
|
@@ -171,3 +208,207 @@ export function normalizeChatMessage<T extends ChatMessage>(message: T): T {
|
|
|
171
208
|
export function normalizeChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
172
209
|
return (Array.isArray(messages) ? messages : []).map((message) => normalizeChatMessage(message));
|
|
173
210
|
}
|
|
211
|
+
|
|
212
|
+
function readMessageMeta(message: ChatMessage): Record<string, unknown> | null {
|
|
213
|
+
const meta = message?.meta;
|
|
214
|
+
return meta && typeof meta === 'object' && !Array.isArray(meta)
|
|
215
|
+
? meta as Record<string, unknown>
|
|
216
|
+
: null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function readStringField(value: unknown): string {
|
|
220
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function readRecordField(message: ChatMessage, meta: Record<string, unknown> | null, key: string): unknown {
|
|
224
|
+
const record = message as ChatMessage & Record<string, unknown>;
|
|
225
|
+
return record[key] ?? meta?.[key];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function readVisibilityField(message: ChatMessage, meta: Record<string, unknown> | null): string {
|
|
229
|
+
return readStringField(readRecordField(message, meta, 'visibility'));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function readTranscriptVisibilityField(message: ChatMessage, meta: Record<string, unknown> | null): string {
|
|
233
|
+
const record = message as ChatMessage & Record<string, unknown>;
|
|
234
|
+
return readStringField(record.transcriptVisibility ?? meta?.transcriptVisibility ?? record.visibility ?? meta?.visibility);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const EXPLICIT_HIDDEN_VISIBILITIES = new Set(['hidden', 'debug', 'internal']);
|
|
238
|
+
const EXPLICIT_VISIBLE_VISIBILITIES = new Set(['visible', 'user', 'chat']);
|
|
239
|
+
const HIDDEN_AUDIENCES = new Set(['debug', 'trace', 'internal']);
|
|
240
|
+
const ACTIVITY_SOURCE_SET = new Set<string>(CHAT_MESSAGE_ACTIVITY_SOURCES);
|
|
241
|
+
const INTERNAL_SOURCE_SET = new Set<string>(CHAT_MESSAGE_INTERNAL_SOURCES);
|
|
242
|
+
|
|
243
|
+
function hasBooleanMarker(message: ChatMessage, meta: Record<string, unknown> | null, keys: string[]): boolean {
|
|
244
|
+
const record = message as ChatMessage & Record<string, unknown>;
|
|
245
|
+
return keys.some((key) => record[key] === true || meta?.[key] === true);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isActivityKind(kind: ChatMessageKind): boolean {
|
|
249
|
+
return kind === 'thought' || kind === 'tool' || kind === 'terminal';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isOrdinaryVisibleTurn(message: ChatMessage, role: string, kind: ChatMessageKind): boolean {
|
|
253
|
+
if (role === 'user' || role === 'human') return kind === 'standard' || kind === '';
|
|
254
|
+
if (role === 'assistant') return kind === 'standard' || kind === '';
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Shared transcript visibility protocol for all ADHDev provider chat messages.
|
|
260
|
+
*
|
|
261
|
+
* Producers can stamp visibility/audience/source/userFacing/internal/debug either
|
|
262
|
+
* at the top level or under `meta`. Consumers should use this classifier instead
|
|
263
|
+
* of matching command text, icons, provider names, or terminal UI fragments.
|
|
264
|
+
*/
|
|
265
|
+
export function classifyChatMessageVisibility(message: ChatMessage | null | undefined): ChatMessageVisibilityClassification {
|
|
266
|
+
if (!message) {
|
|
267
|
+
return {
|
|
268
|
+
surface: 'internal',
|
|
269
|
+
isUserFacing: false,
|
|
270
|
+
isActivityFacing: false,
|
|
271
|
+
isInternal: true,
|
|
272
|
+
explicitUserFacing: false,
|
|
273
|
+
explicitHidden: true,
|
|
274
|
+
role: '',
|
|
275
|
+
kind: 'standard',
|
|
276
|
+
visibility: '',
|
|
277
|
+
transcriptVisibility: '',
|
|
278
|
+
audience: '',
|
|
279
|
+
source: '',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const meta = readMessageMeta(message);
|
|
284
|
+
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
285
|
+
const kind = resolveChatMessageKind(message);
|
|
286
|
+
const visibility = readVisibilityField(message, meta);
|
|
287
|
+
const transcriptVisibility = readTranscriptVisibilityField(message, meta);
|
|
288
|
+
const audience = readStringField(readRecordField(message, meta, 'audience'));
|
|
289
|
+
const source = readStringField(readRecordField(message, meta, 'source'));
|
|
290
|
+
const explicitHidden = EXPLICIT_HIDDEN_VISIBILITIES.has(visibility)
|
|
291
|
+
|| EXPLICIT_HIDDEN_VISIBILITIES.has(transcriptVisibility)
|
|
292
|
+
|| HIDDEN_AUDIENCES.has(audience)
|
|
293
|
+
|| hasBooleanMarker(message, meta, ['internal', 'isInternal', 'debug', 'statusOnly', 'controlOnly']);
|
|
294
|
+
const explicitUserFacing = EXPLICIT_VISIBLE_VISIBILITIES.has(visibility)
|
|
295
|
+
|| EXPLICIT_VISIBLE_VISIBILITIES.has(transcriptVisibility)
|
|
296
|
+
|| audience === 'chat'
|
|
297
|
+
|| hasBooleanMarker(message, meta, ['userFacing']);
|
|
298
|
+
|
|
299
|
+
if (explicitHidden) {
|
|
300
|
+
const activityLike = isActivityKind(kind) || ACTIVITY_SOURCE_SET.has(source);
|
|
301
|
+
return {
|
|
302
|
+
surface: activityLike ? 'activity' : 'internal',
|
|
303
|
+
isUserFacing: false,
|
|
304
|
+
isActivityFacing: activityLike,
|
|
305
|
+
isInternal: !activityLike,
|
|
306
|
+
explicitUserFacing,
|
|
307
|
+
explicitHidden,
|
|
308
|
+
role,
|
|
309
|
+
kind,
|
|
310
|
+
visibility,
|
|
311
|
+
transcriptVisibility,
|
|
312
|
+
audience,
|
|
313
|
+
source,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (explicitUserFacing) {
|
|
318
|
+
return {
|
|
319
|
+
surface: 'chat',
|
|
320
|
+
isUserFacing: true,
|
|
321
|
+
isActivityFacing: false,
|
|
322
|
+
isInternal: false,
|
|
323
|
+
explicitUserFacing,
|
|
324
|
+
explicitHidden,
|
|
325
|
+
role,
|
|
326
|
+
kind,
|
|
327
|
+
visibility,
|
|
328
|
+
transcriptVisibility,
|
|
329
|
+
audience,
|
|
330
|
+
source,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (INTERNAL_SOURCE_SET.has(source) || role === 'system' || kind === 'system') {
|
|
335
|
+
return {
|
|
336
|
+
surface: 'internal',
|
|
337
|
+
isUserFacing: false,
|
|
338
|
+
isActivityFacing: false,
|
|
339
|
+
isInternal: true,
|
|
340
|
+
explicitUserFacing,
|
|
341
|
+
explicitHidden,
|
|
342
|
+
role,
|
|
343
|
+
kind,
|
|
344
|
+
visibility,
|
|
345
|
+
transcriptVisibility,
|
|
346
|
+
audience,
|
|
347
|
+
source,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (ACTIVITY_SOURCE_SET.has(source) || isActivityKind(kind)) {
|
|
352
|
+
return {
|
|
353
|
+
surface: 'activity',
|
|
354
|
+
isUserFacing: false,
|
|
355
|
+
isActivityFacing: true,
|
|
356
|
+
isInternal: false,
|
|
357
|
+
explicitUserFacing,
|
|
358
|
+
explicitHidden,
|
|
359
|
+
role,
|
|
360
|
+
kind,
|
|
361
|
+
visibility,
|
|
362
|
+
transcriptVisibility,
|
|
363
|
+
audience,
|
|
364
|
+
source,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const isUserFacing = isOrdinaryVisibleTurn(message, role, kind);
|
|
369
|
+
return {
|
|
370
|
+
surface: isUserFacing ? 'chat' : 'internal',
|
|
371
|
+
isUserFacing,
|
|
372
|
+
isActivityFacing: false,
|
|
373
|
+
isInternal: !isUserFacing,
|
|
374
|
+
explicitUserFacing,
|
|
375
|
+
explicitHidden,
|
|
376
|
+
role,
|
|
377
|
+
kind,
|
|
378
|
+
visibility,
|
|
379
|
+
transcriptVisibility,
|
|
380
|
+
audience,
|
|
381
|
+
source,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function isUserFacingChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
386
|
+
return classifyChatMessageVisibility(message).isUserFacing;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function isActivityChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
390
|
+
return classifyChatMessageVisibility(message).isActivityFacing;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function isInternalChatMessage(message: ChatMessage | null | undefined): boolean {
|
|
394
|
+
return classifyChatMessageVisibility(message).isInternal;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function filterUserFacingChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
398
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function filterActivityChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
402
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => isActivityChatMessage(message));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function filterInternalChatMessages<T extends ChatMessage>(messages: T[] | null | undefined): T[] {
|
|
406
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => isInternalChatMessage(message));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function filterChatMessagesByVisibility<T extends ChatMessage>(
|
|
410
|
+
messages: T[] | null | undefined,
|
|
411
|
+
surface: ChatMessageTranscriptSurface,
|
|
412
|
+
): T[] {
|
|
413
|
+
return (Array.isArray(messages) ? messages : []).filter((message) => classifyChatMessageVisibility(message).surface === surface);
|
|
414
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ProviderModule } from './contracts.js';
|
|
8
8
|
import type { ProviderInstance, ProviderState, InstanceContext } from './provider-instance.js';
|
|
9
|
+
import type { ChatMessage } from '../types.js';
|
|
9
10
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
11
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
11
12
|
export declare class CliProviderInstance implements ProviderInstance {
|
|
@@ -77,6 +78,7 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
77
78
|
private formatMarkerTimestamp;
|
|
78
79
|
private maybeAppendRuntimeRecoveryMessage;
|
|
79
80
|
private appendRuntimeSystemMessage;
|
|
81
|
+
mergeRuntimeChatMessages(parsedMessages: ChatMessage[]): ChatMessage[];
|
|
80
82
|
private mergeConversationMessages;
|
|
81
83
|
private formatApprovalRequestMessage;
|
|
82
84
|
private promoteProviderSessionId;
|
|
@@ -25,7 +25,7 @@ import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.
|
|
|
25
25
|
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
26
26
|
import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
|
|
27
27
|
import { normalizeProviderSessionId } from './provider-session-id.js';
|
|
28
|
-
import { buildChatMessage, buildRuntimeSystemChatMessage, normalizeChatMessages } from './chat-message-normalization.js';
|
|
28
|
+
import { buildChatMessage, buildRuntimeSystemChatMessage, isUserFacingChatMessage, normalizeChatMessages, resolveChatMessageKind } from './chat-message-normalization.js';
|
|
29
29
|
|
|
30
30
|
type PersistableCliHistoryMessage = {
|
|
31
31
|
role: string;
|
|
@@ -214,6 +214,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
214
214
|
options?: {
|
|
215
215
|
providerSessionId?: string;
|
|
216
216
|
launchMode?: 'new' | 'resume' | 'manual';
|
|
217
|
+
extraEnv?: Record<string, string>;
|
|
217
218
|
onProviderSessionResolved?: (info: {
|
|
218
219
|
instanceId: string;
|
|
219
220
|
providerType: string;
|
|
@@ -230,7 +231,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
230
231
|
this.providerSessionId = options?.providerSessionId;
|
|
231
232
|
this.launchMode = options?.launchMode || 'new';
|
|
232
233
|
this.onProviderSessionResolved = options?.onProviderSessionResolved;
|
|
233
|
-
this.adapter = new ProviderCliAdapter(provider as CliProviderModule, workingDir, cliArgs, transportFactory);
|
|
234
|
+
this.adapter = new ProviderCliAdapter(provider as CliProviderModule, workingDir, cliArgs, options?.extraEnv || {}, transportFactory);
|
|
234
235
|
this.monitor = new StatusMonitor();
|
|
235
236
|
this.historyWriter = new ChatHistoryWriter();
|
|
236
237
|
}
|
|
@@ -737,7 +738,29 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
737
738
|
}
|
|
738
739
|
|
|
739
740
|
private pushEvent(event: ProviderEvent): void {
|
|
740
|
-
|
|
741
|
+
const enrichedEvent: ProviderEvent = {
|
|
742
|
+
...event,
|
|
743
|
+
instanceId: typeof event.instanceId === 'string' && event.instanceId.trim()
|
|
744
|
+
? event.instanceId
|
|
745
|
+
: this.instanceId,
|
|
746
|
+
targetSessionId: typeof event.targetSessionId === 'string' && event.targetSessionId.trim()
|
|
747
|
+
? event.targetSessionId
|
|
748
|
+
: this.instanceId,
|
|
749
|
+
providerType: typeof event.providerType === 'string' && event.providerType.trim()
|
|
750
|
+
? event.providerType
|
|
751
|
+
: this.type,
|
|
752
|
+
workspaceName: typeof event.workspaceName === 'string' && event.workspaceName.trim()
|
|
753
|
+
? event.workspaceName
|
|
754
|
+
: this.workingDir,
|
|
755
|
+
providerSessionId: typeof event.providerSessionId === 'string' && event.providerSessionId.trim()
|
|
756
|
+
? event.providerSessionId
|
|
757
|
+
: this.providerSessionId,
|
|
758
|
+
};
|
|
759
|
+
if (this.context?.emitProviderEvent) {
|
|
760
|
+
this.context.emitProviderEvent(enrichedEvent);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
this.events.push(enrichedEvent);
|
|
741
764
|
}
|
|
742
765
|
|
|
743
766
|
private flushEvents(): ProviderEvent[] {
|
|
@@ -977,15 +1000,77 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
977
1000
|
}
|
|
978
1001
|
}
|
|
979
1002
|
|
|
1003
|
+
mergeRuntimeChatMessages(parsedMessages: ChatMessage[]): ChatMessage[] {
|
|
1004
|
+
return this.mergeConversationMessages(parsedMessages);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
980
1007
|
private mergeConversationMessages(parsedMessages: any[]): ChatMessage[] {
|
|
981
1008
|
if (this.runtimeMessages.length === 0) return normalizeChatMessages(parsedMessages);
|
|
982
1009
|
|
|
983
|
-
|
|
984
|
-
|
|
1010
|
+
type MergeEntry = { message: ChatMessage; index: number; source: 'parsed' | 'runtime'; runtimeKey?: string };
|
|
1011
|
+
const parsedEntries: MergeEntry[] = parsedMessages.map((message, index) => ({
|
|
1012
|
+
message,
|
|
1013
|
+
index,
|
|
1014
|
+
source: 'parsed',
|
|
1015
|
+
}));
|
|
1016
|
+
const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
|
|
1017
|
+
message: entry.message,
|
|
1018
|
+
index: parsedMessages.length + index,
|
|
1019
|
+
source: 'runtime',
|
|
1020
|
+
runtimeKey: entry.key,
|
|
1021
|
+
}));
|
|
1022
|
+
const getTime = (message: ChatMessage): number => {
|
|
1023
|
+
const value = typeof message.receivedAt === 'number'
|
|
1024
|
+
? message.receivedAt
|
|
1025
|
+
: typeof message.timestamp === 'number'
|
|
1026
|
+
? message.timestamp
|
|
1027
|
+
: 0;
|
|
1028
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
const getRole = (message: ChatMessage): string => typeof message.role === 'string'
|
|
1032
|
+
? message.role.trim().toLowerCase()
|
|
1033
|
+
: '';
|
|
1034
|
+
const isRuntimeOverlay = (entry: MergeEntry): boolean => {
|
|
1035
|
+
if (entry.source !== 'runtime') return false;
|
|
1036
|
+
const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';
|
|
1037
|
+
if (key.startsWith('auto_approval:')) return true;
|
|
1038
|
+
return !isUserFacingChatMessage(entry.message);
|
|
1039
|
+
};
|
|
1040
|
+
const shouldKeepParsedBeforeUntimedRuntime = (message: ChatMessage): boolean => {
|
|
1041
|
+
const role = getRole(message);
|
|
1042
|
+
return role === 'user' || role === 'human';
|
|
1043
|
+
};
|
|
1044
|
+
const shouldKeepParsedAfterUntimedRuntime = (message: ChatMessage): boolean => {
|
|
1045
|
+
const role = getRole(message);
|
|
1046
|
+
if (role !== 'assistant') return false;
|
|
1047
|
+
const kind = resolveChatMessageKind(message);
|
|
1048
|
+
return kind === 'standard' || kind === 'terminal';
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
return normalizeChatMessages([...parsedEntries, ...runtimeEntries]
|
|
985
1052
|
.sort((a, b) => {
|
|
986
|
-
const aTime = a.message
|
|
987
|
-
const bTime = b.message
|
|
988
|
-
if (aTime !== bTime) return aTime - bTime;
|
|
1053
|
+
const aTime = getTime(a.message);
|
|
1054
|
+
const bTime = getTime(b.message);
|
|
1055
|
+
if (aTime && bTime && aTime !== bTime) return aTime - bTime;
|
|
1056
|
+
if (a.source !== b.source && aTime !== bTime) {
|
|
1057
|
+
const parsedEntry = a.source === 'parsed' ? a : b.source === 'parsed' ? b : null;
|
|
1058
|
+
const runtimeEntry = a.source === 'runtime' ? a : b.source === 'runtime' ? b : null;
|
|
1059
|
+
if (parsedEntry && runtimeEntry && isRuntimeOverlay(runtimeEntry) && getTime(parsedEntry.message) === 0 && getTime(runtimeEntry.message) > 0) {
|
|
1060
|
+
if (shouldKeepParsedBeforeUntimedRuntime(parsedEntry.message)) {
|
|
1061
|
+
return a.source === 'parsed' ? -1 : 1;
|
|
1062
|
+
}
|
|
1063
|
+
if (shouldKeepParsedAfterUntimedRuntime(parsedEntry.message)) {
|
|
1064
|
+
return a.source === 'parsed' ? 1 : -1;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// Many provider-owned CLI transcripts (including Hermes CLI in debug bundles)
|
|
1069
|
+
// do not carry timestamps on parsed messages. In that case there is no safe
|
|
1070
|
+
// clock basis for interleaving timestamped runtime/system messages into the
|
|
1071
|
+
// provider transcript. Keep user prompts before runtime overlays, but do not
|
|
1072
|
+
// let timed runtime/system/tool/internal overlays become the final chat turns
|
|
1073
|
+
// after an untimed parsed assistant transcript.
|
|
989
1074
|
return a.index - b.index;
|
|
990
1075
|
})
|
|
991
1076
|
.map((entry) => entry.message));
|
|
@@ -47,7 +47,10 @@ export class ProviderInstanceManager {
|
|
|
47
47
|
this.instances.get(id)!.dispose();
|
|
48
48
|
}
|
|
49
49
|
this.instances.set(id, instance);
|
|
50
|
-
await instance.init(
|
|
50
|
+
await instance.init({
|
|
51
|
+
...context,
|
|
52
|
+
emitProviderEvent: (event) => this.emitProviderEvent(instance.type, id, event),
|
|
53
|
+
});
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -237,6 +240,22 @@ export class ProviderInstanceManager {
|
|
|
237
240
|
this.eventListeners.push(listener);
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
emitProviderEvent(providerType: string, instanceId: string, event: ProviderEvent): void {
|
|
244
|
+
const payload = {
|
|
245
|
+
...event,
|
|
246
|
+
providerType,
|
|
247
|
+
instanceId: typeof event.instanceId === 'string' && event.instanceId.trim()
|
|
248
|
+
? event.instanceId
|
|
249
|
+
: instanceId,
|
|
250
|
+
targetSessionId: typeof event.targetSessionId === 'string' && event.targetSessionId.trim()
|
|
251
|
+
? event.targetSessionId
|
|
252
|
+
: instanceId,
|
|
253
|
+
} as ProviderEvent & { providerType: string };
|
|
254
|
+
for (const listener of this.eventListeners) {
|
|
255
|
+
listener(payload);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
240
259
|
private emitPendingEvents(
|
|
241
260
|
providerType: string,
|
|
242
261
|
state: ProviderState,
|
|
@@ -174,6 +174,8 @@ export interface InstanceContext {
|
|
|
174
174
|
onPtyData?: (data: string) => void;
|
|
175
175
|
/** Provider configvalue (resolved) */
|
|
176
176
|
settings: Record<string, any>;
|
|
177
|
+
/** Immediate provider-originated status/event emission. Used to avoid waiting for status polling. */
|
|
178
|
+
emitProviderEvent?: (event: ProviderEvent) => void;
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
export interface ProviderInstance {
|
|
@@ -77,6 +77,14 @@ function validateMessage(message: unknown, source: string, index: number): ChatM
|
|
|
77
77
|
if (typeof message.senderName === 'string') normalized.senderName = message.senderName
|
|
78
78
|
if (typeof (message as any)._type === 'string') normalized._type = (message as any)._type
|
|
79
79
|
if (typeof (message as any)._sub === 'string') normalized._sub = (message as any)._sub
|
|
80
|
+
if (typeof (message as any).visibility === 'string') normalized.visibility = (message as any).visibility
|
|
81
|
+
if (typeof (message as any).transcriptVisibility === 'string') normalized.transcriptVisibility = (message as any).transcriptVisibility
|
|
82
|
+
if (typeof (message as any).audience === 'string') normalized.audience = (message as any).audience
|
|
83
|
+
if (typeof (message as any).source === 'string') normalized.source = (message as any).source
|
|
84
|
+
if (typeof (message as any).userFacing === 'boolean') normalized.userFacing = (message as any).userFacing
|
|
85
|
+
if (typeof (message as any).internal === 'boolean') normalized.internal = (message as any).internal
|
|
86
|
+
if (typeof (message as any).isInternal === 'boolean') normalized.isInternal = (message as any).isInternal
|
|
87
|
+
if (typeof (message as any).debug === 'boolean') normalized.debug = (message as any).debug
|
|
80
88
|
|
|
81
89
|
return normalized
|
|
82
90
|
}
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -55,6 +55,8 @@ export type RepoMeshNodeHealth =
|
|
|
55
55
|
|
|
56
56
|
// ─── Policy Types ───────────────────────────────
|
|
57
57
|
|
|
58
|
+
export type RepoMeshSessionCleanupMode = 'preserve' | 'stop' | 'delete_stopped' | 'stop_and_delete';
|
|
59
|
+
|
|
58
60
|
export interface RepoMeshPolicy {
|
|
59
61
|
requirePreTaskCheckpoint: boolean;
|
|
60
62
|
requirePostTaskCheckpoint: boolean;
|
|
@@ -63,12 +65,33 @@ export interface RepoMeshPolicy {
|
|
|
63
65
|
dirtyWorkspaceBehavior: 'block' | 'warn' | 'checkpoint_then_continue';
|
|
64
66
|
maxParallelTasks: number;
|
|
65
67
|
allowedProviders?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* What to do with delegated session-host records for a node when it is removed.
|
|
70
|
+
* Defaults to 'preserve' so completed work can be reviewed later and live
|
|
71
|
+
* runtimes are never stopped/deleted unless the mesh owner opts in.
|
|
72
|
+
*/
|
|
73
|
+
sessionCleanupOnNodeRemove?: RepoMeshSessionCleanupMode;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RepoMeshRelatedRepo {
|
|
77
|
+
/** Stable display label for an explicitly configured associated checkout. */
|
|
78
|
+
label: string;
|
|
79
|
+
/** Absolute checkout/workspace path for git freshness probes. */
|
|
80
|
+
workspace: string;
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
export interface RepoMeshNodePolicy {
|
|
69
84
|
readOnly?: boolean;
|
|
70
85
|
canPush?: boolean;
|
|
71
86
|
maxConcurrentSessions?: number;
|
|
87
|
+
/** Ordered provider preference used when mesh_launch_session omits an explicit type. */
|
|
88
|
+
providerPriority?: string[];
|
|
89
|
+
/**
|
|
90
|
+
* Optional associated/external repos that must be checked alongside this node.
|
|
91
|
+
* These are explicit policy/config entries only; Repo Mesh does not auto-discover
|
|
92
|
+
* sibling paths so freshness checks stay fail-closed and non-surprising.
|
|
93
|
+
*/
|
|
94
|
+
relatedRepos?: RepoMeshRelatedRepo[];
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
@@ -78,6 +101,7 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
|
|
|
78
101
|
requireApprovalForDestructiveGit: true,
|
|
79
102
|
dirtyWorkspaceBehavior: 'warn',
|
|
80
103
|
maxParallelTasks: 2,
|
|
104
|
+
sessionCleanupOnNodeRemove: 'preserve',
|
|
81
105
|
};
|
|
82
106
|
|
|
83
107
|
// ─── Capabilities ───────────────────────────────
|
|
@@ -189,6 +213,12 @@ export interface LocalMeshNodeEntry {
|
|
|
189
213
|
policy: RepoMeshNodePolicy;
|
|
190
214
|
/** For single-machine mesh: same daemon, different worktree */
|
|
191
215
|
isLocalWorktree?: boolean;
|
|
216
|
+
/** Branch this worktree tracks (set when created via clone_mesh_node) */
|
|
217
|
+
worktreeBranch?: string;
|
|
218
|
+
/** Node ID this worktree was cloned from */
|
|
219
|
+
clonedFromNodeId?: string;
|
|
220
|
+
/** Optional associated/external repos configured as node metadata. */
|
|
221
|
+
relatedRepos?: RepoMeshRelatedRepo[];
|
|
192
222
|
}
|
|
193
223
|
|
|
194
224
|
// ─── Mesh Status (runtime, not persisted) ───────
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionHostClient,
|
|
3
3
|
getDefaultSessionHostEndpoint,
|
|
4
|
+
type SessionHostDiagnostics,
|
|
4
5
|
type SessionHostEndpoint,
|
|
5
6
|
type SessionHostRecord,
|
|
7
|
+
type SessionHostRequestType,
|
|
6
8
|
} from '@adhdev/session-host-core';
|
|
7
9
|
import type { HostedCliRuntimeDescriptor } from '../commands/cli-manager.js';
|
|
8
10
|
import { DEFAULT_SESSION_HOST_READY_TIMEOUT_MS } from '../runtime-defaults.js';
|
|
@@ -10,21 +12,65 @@ import { DEFAULT_SESSION_HOST_READY_TIMEOUT_MS } from '../runtime-defaults.js';
|
|
|
10
12
|
const STARTUP_TIMEOUT_MS = DEFAULT_SESSION_HOST_READY_TIMEOUT_MS;
|
|
11
13
|
const STARTUP_POLL_MS = 200;
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
class SessionHostCompatibilityError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'SessionHostCompatibilityError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getMissingRequestTypes(
|
|
23
|
+
diagnostics: SessionHostDiagnostics | undefined,
|
|
24
|
+
requiredRequestTypes: readonly SessionHostRequestType[],
|
|
25
|
+
): SessionHostRequestType[] {
|
|
26
|
+
const supported = new Set(diagnostics?.supportedRequestTypes || []);
|
|
27
|
+
return requiredRequestTypes.filter((requestType) => !supported.has(requestType));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function assertRequiredRequestTypes(
|
|
31
|
+
client: SessionHostClient,
|
|
32
|
+
requiredRequestTypes: readonly SessionHostRequestType[],
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
if (requiredRequestTypes.length === 0) return;
|
|
35
|
+
|
|
36
|
+
const response = await client.request<SessionHostDiagnostics>({
|
|
37
|
+
type: 'get_host_diagnostics',
|
|
38
|
+
payload: { includeSessions: false },
|
|
39
|
+
});
|
|
40
|
+
const missing = getMissingRequestTypes(response.success ? response.result : undefined, requiredRequestTypes);
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
const detail = response.success ? '' : ` (${response.error || 'capability probe failed'})`;
|
|
43
|
+
throw new SessionHostCompatibilityError(
|
|
44
|
+
`Session host does not support required request types: ${missing.join(', ')}${detail}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function canConnect(
|
|
50
|
+
endpoint: SessionHostEndpoint,
|
|
51
|
+
requiredRequestTypes: readonly SessionHostRequestType[] = [],
|
|
52
|
+
): Promise<boolean> {
|
|
14
53
|
const client = new SessionHostClient({ endpoint });
|
|
15
54
|
try {
|
|
16
55
|
await client.connect();
|
|
17
|
-
await client
|
|
56
|
+
await assertRequiredRequestTypes(client, requiredRequestTypes);
|
|
18
57
|
return true;
|
|
19
|
-
} catch {
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof SessionHostCompatibilityError) throw error;
|
|
20
60
|
return false;
|
|
61
|
+
} finally {
|
|
62
|
+
await client.close().catch(() => {});
|
|
21
63
|
}
|
|
22
64
|
}
|
|
23
65
|
|
|
24
|
-
async function waitForReady(
|
|
66
|
+
async function waitForReady(
|
|
67
|
+
endpoint: SessionHostEndpoint,
|
|
68
|
+
timeoutMs = STARTUP_TIMEOUT_MS,
|
|
69
|
+
requiredRequestTypes: readonly SessionHostRequestType[] = [],
|
|
70
|
+
): Promise<void> {
|
|
25
71
|
const deadline = Date.now() + timeoutMs;
|
|
26
72
|
while (Date.now() < deadline) {
|
|
27
|
-
if (await canConnect(endpoint)) return;
|
|
73
|
+
if (await canConnect(endpoint, requiredRequestTypes)) return;
|
|
28
74
|
await new Promise((resolve) => setTimeout(resolve, STARTUP_POLL_MS));
|
|
29
75
|
}
|
|
30
76
|
throw new Error(`Session host did not become ready within ${timeoutMs}ms`);
|
|
@@ -34,11 +80,13 @@ export async function ensureSessionHostReady(options: {
|
|
|
34
80
|
appName?: string;
|
|
35
81
|
spawnHost: () => void;
|
|
36
82
|
timeoutMs?: number;
|
|
83
|
+
requiredRequestTypes?: readonly SessionHostRequestType[];
|
|
37
84
|
}): Promise<SessionHostEndpoint> {
|
|
38
85
|
const endpoint = getDefaultSessionHostEndpoint(options.appName || 'adhdev');
|
|
39
|
-
|
|
86
|
+
const requiredRequestTypes = options.requiredRequestTypes || [];
|
|
87
|
+
if (await canConnect(endpoint, requiredRequestTypes)) return endpoint;
|
|
40
88
|
options.spawnHost();
|
|
41
|
-
await waitForReady(endpoint, options.timeoutMs);
|
|
89
|
+
await waitForReady(endpoint, options.timeoutMs, requiredRequestTypes);
|
|
42
90
|
return endpoint;
|
|
43
91
|
}
|
|
44
92
|
|
package/src/shared-types.ts
CHANGED
|
@@ -641,6 +641,8 @@ export interface DaemonStatusEventPayload {
|
|
|
641
641
|
timestamp: number;
|
|
642
642
|
targetSessionId?: string;
|
|
643
643
|
providerType?: string;
|
|
644
|
+
providerSessionId?: string;
|
|
645
|
+
workspaceName?: string;
|
|
644
646
|
duration?: number;
|
|
645
647
|
elapsedSec?: number;
|
|
646
648
|
modalMessage?: string;
|
|
@@ -662,6 +664,8 @@ export interface DashboardStatusEventPayload {
|
|
|
662
664
|
daemonId?: string;
|
|
663
665
|
providerType?: string;
|
|
664
666
|
targetSessionId?: string;
|
|
667
|
+
providerSessionId?: string;
|
|
668
|
+
workspaceName?: string;
|
|
665
669
|
duration?: number;
|
|
666
670
|
elapsedSec?: number;
|
|
667
671
|
modalMessage?: string;
|