@adhdev/daemon-core 0.9.82-rc.185 → 0.9.82-rc.187

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.
@@ -68,5 +68,6 @@ export declare class BeadsDB {
68
68
  }>;
69
69
  updateDirectDispatchStatus(meshId: string, sessionId: string, status: 'acked' | 'completed' | 'failed' | 'stale'): void;
70
70
  cleanupTerminalDirectDispatches(olderThanMs: number): void;
71
+ deleteDirectDispatches(meshId: string): void;
71
72
  markStaleDirectDispatches(meshId: string, olderThanMs: number): void;
72
73
  }
@@ -1,4 +1,5 @@
1
1
  import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
+ import type { MeshLedgerKind } from './mesh-ledger.js';
2
3
  export declare function __resetIdleAutoFastForwardForTests(): void;
3
4
  export interface PendingMeshCoordinatorEvent {
4
5
  event: string;
@@ -23,12 +24,56 @@ export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coord
23
24
  export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): readonly PendingMeshCoordinatorEvent[];
24
25
  /** Explicitly clear all pending coordinator events for a mesh (and coordinator if scoped). */
25
26
  export declare function clearPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): void;
27
+ export declare function reconcileDirectDispatchCompletionFromTranscript(args: {
28
+ meshId: string;
29
+ nodeId?: string;
30
+ sessionId: string;
31
+ providerType?: string;
32
+ providerSessionId?: string;
33
+ taskId: string;
34
+ finalSummary: string;
35
+ transcriptMessageAt?: string;
36
+ completedAt?: string;
37
+ targetCoordinatorDaemonId?: string;
38
+ source?: string;
39
+ }): {
40
+ reconciled: boolean;
41
+ kind?: MeshLedgerKind;
42
+ alreadyTerminal?: boolean;
43
+ workerResult?: unknown;
44
+ ledgerEntryId?: string;
45
+ reason?: string;
46
+ };
26
47
  export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
48
+ export interface MeshQueueTriggerResult {
49
+ success: true;
50
+ meshId: string;
51
+ pendingBefore: number;
52
+ assignedBefore: number;
53
+ pendingAfter: number;
54
+ assignedAfter: number;
55
+ claimed: boolean;
56
+ newlyAssignedTasks: Array<{
57
+ id: string;
58
+ nodeId?: string;
59
+ sessionId?: string;
60
+ }>;
61
+ localIdleSessionsChecked: number;
62
+ remoteIdleSessionsChecked: number;
63
+ skippedSessions: Array<{
64
+ nodeId?: string;
65
+ sessionId?: string;
66
+ reason: string;
67
+ status?: string;
68
+ }>;
69
+ autoLaunchStarted: boolean;
70
+ noIdleMeshSessionAvailable?: boolean;
71
+ }
27
72
  /**
28
73
  * Triggers a queue check for all nodes in the mesh.
29
74
  * Called when a new task is enqueued, in case nodes are already idle.
30
75
  */
31
- export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): Promise<void>;
76
+ export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): Promise<MeshQueueTriggerResult>;
32
77
  export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
33
78
  success: boolean;
34
79
  forwarded: number;
@@ -136,6 +136,7 @@ export interface MeshWorkQueueStats {
136
136
  export declare function getMeshQueueStats(meshId: string): MeshWorkQueueStats;
137
137
  export declare function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void;
138
138
  export declare function __clearMeshQueueForTests(meshId: string): void;
139
+ export declare function __clearDirectDispatchesForTests(meshId: string): void;
139
140
  export declare function __resetBeadsDBForTests(): void;
140
141
  export type DirectDispatchRecord = ReturnType<BeadsDB['getActiveDirectDispatches']>[number];
141
142
  export declare function insertDirectDispatch(meshId: string, data: {
@@ -302,7 +302,7 @@ export interface ProviderMeshCoordinatorConfig {
302
302
  requiresRestart?: boolean;
303
303
  /** User-facing setup explanation for manual modes. */
304
304
  instructions?: string;
305
- /** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{workspace}}, {{serverName}}. */
305
+ /** Copyable setup template. Supports {{meshId}}, {{adhdevMcpCommand}}, {{adhdevMcpArgs}}, {{workspace}}, {{serverName}}. */
306
306
  template?: string;
307
307
  };
308
308
  /**
@@ -5,6 +5,7 @@ export interface NativeHistoryInput {
5
5
  providerSessionId?: string;
6
6
  historySessionId?: string;
7
7
  workspace?: string;
8
+ sessionStartedAtMs?: number;
8
9
  format?: string;
9
10
  watchPath?: string;
10
11
  forceRefresh?: boolean;
@@ -16,6 +17,7 @@ export interface NativeHistoryResult {
16
17
  content: string;
17
18
  receivedAt?: number;
18
19
  kind?: string;
20
+ workspace?: string;
19
21
  }>;
20
22
  providerSessionId?: string;
21
23
  sourcePath: string;
@@ -90,6 +90,7 @@ export declare class SpecCliAdapter implements CliAdapter {
90
90
  private handleEvent;
91
91
  private detectInteractivePromptFromPtyChunk;
92
92
  private readCurrentScreenSections;
93
+ private extractProviderSessionIdFromScreen;
93
94
  private readClaudeScreenAssistantMessages;
94
95
  private maybeCaptureClaudeTuiPrompt;
95
96
  private readClaudeTuiHeaders;
@@ -28,6 +28,7 @@ export interface NativeHistoryMessage {
28
28
  content: string;
29
29
  receivedAt: number;
30
30
  kind?: string;
31
+ workspace?: string;
31
32
  }
32
33
  export interface NativeHistoryResult {
33
34
  messages: NativeHistoryMessage[];
@@ -35,5 +36,6 @@ export interface NativeHistoryResult {
35
36
  sourcePath: string;
36
37
  sourceMtimeMs: number;
37
38
  nativeHistoryCoverage?: 'full' | 'partial' | 'best-effort';
39
+ workspace?: string;
38
40
  }
39
41
  export declare function executeNativeHistory(cfg: NativeHistoryConfig, input: NativeHistoryInput): NativeHistoryResult | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.185",
3
+ "version": "0.9.82-rc.187",
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",
@@ -110,6 +110,10 @@ export function prepareSessionChatTailUpdate(
110
110
  const messages = fullMessages
111
111
  const title = typeof result.title === 'string' ? result.title : undefined
112
112
  const activeModal = normalizeChatTailActiveModal(result.activeModal)
113
+ const activeInteractivePrompt = (result as { activeInteractivePrompt?: unknown }).activeInteractivePrompt
114
+ const promptForUpdate = activeInteractivePrompt && typeof activeInteractivePrompt === 'object'
115
+ ? activeInteractivePrompt as Record<string, unknown>
116
+ : null
113
117
  const status = typeof result.status === 'string' ? result.status : 'idle'
114
118
  // (A3) messageSource passthrough. v1 deliberately dropped this on the
115
119
  // subscription wire so the frontend had to infer source from message
@@ -126,6 +130,7 @@ export function prepareSessionChatTailUpdate(
126
130
  status,
127
131
  ...(title ? { title } : {}),
128
132
  ...(activeModal ? { activeModal } : {}),
133
+ ...(promptForUpdate ? { activeInteractivePrompt: promptForUpdate } : {}),
129
134
  })
130
135
  const seq = input.seq + 1
131
136
 
@@ -154,6 +159,7 @@ export function prepareSessionChatTailUpdate(
154
159
  status,
155
160
  ...(title ? { title } : {}),
156
161
  ...(activeModal ? { activeModal } : {}),
162
+ ...(promptForUpdate ? { activeInteractivePrompt: promptForUpdate as any } : {}),
157
163
  ...(messageSource ? { messageSource } : {}),
158
164
  },
159
165
  }
@@ -222,6 +222,41 @@ function resolveCliNativeHistorySessionId(args: any, currentHistorySessionId: st
222
222
  return current || parsed || undefined;
223
223
  }
224
224
 
225
+ function shouldSkipLiveCliNativeHistoryWithoutProviderSession(args: {
226
+ adapter?: CliAdapter | null;
227
+ providerType?: string;
228
+ readChatArgs: any;
229
+ nativeHistorySessionId?: string;
230
+ parsedProviderSessionId?: string;
231
+ }): boolean {
232
+ const explicit = getExplicitHistorySessionId(args.readChatArgs);
233
+ if (explicit) return false;
234
+
235
+ const targetSessionId = typeof args.readChatArgs?.targetSessionId === 'string'
236
+ ? args.readChatArgs.targetSessionId.trim()
237
+ : '';
238
+ if (!targetSessionId) return false;
239
+
240
+ const resolved = typeof args.nativeHistorySessionId === 'string'
241
+ ? args.nativeHistorySessionId.trim()
242
+ : '';
243
+ if (!resolved || resolved !== targetSessionId) return false;
244
+
245
+ const parsed = typeof args.parsedProviderSessionId === 'string'
246
+ ? args.parsedProviderSessionId.trim()
247
+ : '';
248
+ if (parsed) return false;
249
+
250
+ const cliType = args.adapter?.cliType || args.providerType || '';
251
+ if (cliType !== 'codex-cli') return false;
252
+
253
+ // A live Codex session starts with only the daemon runtime UUID. That UUID
254
+ // is not the provider-native rollout id, so using it for native history
255
+ // lets the file picker fall back to the newest same-workspace transcript
256
+ // and makes concurrent fresh sessions all show the same old conversation.
257
+ return !!args.adapter;
258
+ }
259
+
225
260
  function getInteractionId(args: any): string | undefined {
226
261
  return typeof args?._interactionId === 'string' && args._interactionId.trim()
227
262
  ? args._interactionId.trim()
@@ -444,6 +479,7 @@ function normalizeNativeHistoryMessages(providerType: string, messages: ChatMess
444
479
  ...message,
445
480
  role: role === 'human' ? 'user' : (role || 'assistant'),
446
481
  kind: isSystemSessionStart ? 'system' : kind,
482
+ ...(nativeIdentitySessionId ? { historySessionId: nativeIdentitySessionId } : {}),
447
483
  providerUnitKey,
448
484
  bubbleId: typeof message.bubbleId === 'string' && message.bubbleId.trim()
449
485
  && preserveNativeIdentity
@@ -604,6 +640,7 @@ function decideCliReadChatSource(args: {
604
640
  nativeHistoryResult: any | null;
605
641
  nativeHistoryError?: unknown;
606
642
  safeMapping: boolean;
643
+ trustedExactNativeIdentity?: boolean;
607
644
  sessionWorkspace?: string;
608
645
  intendedWorkspace?: string;
609
646
  ptyMessages: ChatMessage[];
@@ -617,7 +654,26 @@ function decideCliReadChatSource(args: {
617
654
  const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
618
655
  const observation = buildObservationForCli(args, supportsNative);
619
656
  const sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
620
- const decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
657
+ let decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
658
+
659
+ // A restored runtime can briefly expose different native slices while the
660
+ // provider transcript settles (for example, startup/system rows may be
661
+ // filtered after the first read). The source machine correctly treats a
662
+ // shrinking slice as regression, but an exact provider-session lookup with
663
+ // no PTY transcript has no safer fallback. Re-bootstrap only this proven
664
+ // identity so the chat does not disappear after daemon restart.
665
+ if (
666
+ decision.selected === 'pty-parser'
667
+ && args.trustedExactNativeIdentity === true
668
+ && args.safeMapping
669
+ && args.ptyMessages.length === 0
670
+ && observation.kind === 'native_present'
671
+ && observation.coverage !== 'partial'
672
+ && observation.messages.length > 0
673
+ ) {
674
+ CHAT_SOURCE_REGISTRY.clear(sessionKey);
675
+ decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
676
+ }
621
677
 
622
678
  const nativeMessages: ChatMessage[] = observation.kind === 'native_present'
623
679
  ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult)
@@ -1034,6 +1090,17 @@ function hasSafeNativeHistoryMapping(args: {
1034
1090
 
1035
1091
  const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
1036
1092
  if (explicitSessionId) {
1093
+ const expectedWorkspace = normalizeComparableWorkspace(args.workspace);
1094
+ const declaredWorkspaces = args.nativeMessages
1095
+ .map((message: any) => normalizeComparableWorkspace(message?.workspace))
1096
+ .filter(Boolean);
1097
+ if (
1098
+ expectedWorkspace
1099
+ && declaredWorkspaces.length > 0
1100
+ && !declaredWorkspaces.some((workspace) => workspace === expectedWorkspace)
1101
+ ) {
1102
+ return false;
1103
+ }
1037
1104
  const messageSessionIds = args.nativeMessages
1038
1105
  .map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
1039
1106
  .filter(Boolean);
@@ -1128,7 +1195,12 @@ function readCliProviderNativeHistory(agentStr: string, args: {
1128
1195
  sessionStartedAtMs?: number;
1129
1196
  envOverrides?: Record<string, string>;
1130
1197
  }): ReturnType<typeof readProviderChatHistory> & { lookup: 'session' | 'workspace' } {
1131
- if (!args.historySessionId) {
1198
+ const canBindFromLiveSession = !args.historySessionId
1199
+ && typeof args.sessionStartedAtMs === 'number'
1200
+ && args.sessionStartedAtMs > 0
1201
+ && typeof args.workspace === 'string'
1202
+ && args.workspace.trim().length > 0;
1203
+ if (!args.historySessionId && !canBindFromLiveSession) {
1132
1204
  return {
1133
1205
  messages: [],
1134
1206
  hasMore: false,
@@ -1150,10 +1222,17 @@ function readCliProviderNativeHistory(agentStr: string, args: {
1150
1222
  sessionStartedAtMs: args.sessionStartedAtMs,
1151
1223
  envOverrides: args.envOverrides,
1152
1224
  });
1153
- // Native transcripts are keyed by provider/runtime session identity. Falling
1154
- // back to workspace makes concurrent local Codex/Hermes sessions alias each
1155
- // other when they share the same cwd.
1156
- return { ...(sessionHistory as any), lookup: 'session' };
1225
+ const boundProviderSessionId = typeof (sessionHistory as any)?.providerSessionId === 'string'
1226
+ ? (sessionHistory as any).providerSessionId.trim()
1227
+ : '';
1228
+ // A fresh live session can be bound without a provider id when the native
1229
+ // reader matched both cwd and session_meta.timestamp to spawnedAtMs.
1230
+ return {
1231
+ ...(sessionHistory as any),
1232
+ lookup: args.historySessionId || (canBindFromLiveSession && boundProviderSessionId)
1233
+ ? 'session'
1234
+ : 'workspace',
1235
+ };
1157
1236
  }
1158
1237
 
1159
1238
  function readLiveCodexWorkspaceNativeHistory(agentStr: string, args: {
@@ -1613,8 +1692,10 @@ function summarizeStateForDebug(state: any): Record<string, unknown> | null {
1613
1692
  title: activeChat.title,
1614
1693
  messageCount: Array.isArray(activeChat.messages) ? activeChat.messages.length : undefined,
1615
1694
  activeModal: activeChat.activeModal,
1695
+ activeInteractivePrompt: activeChat.activeInteractivePrompt ?? null,
1616
1696
  messagesTail: Array.isArray(activeChat.messages) ? activeChat.messages.slice(-10) : undefined,
1617
1697
  } : null,
1698
+ activeInteractivePrompt: (state as { activeInteractivePrompt?: unknown }).activeInteractivePrompt ?? null,
1618
1699
  controlValues: state.controlValues,
1619
1700
  summaryMetadata: state.summaryMetadata,
1620
1701
  };
@@ -2115,11 +2196,21 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2115
2196
  ? resolveCliNativeHistorySessionId(args, historySessionId, providerSessionId)
2116
2197
  : undefined;
2117
2198
  const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
2199
+ const skipLiveNativeHistoryWithoutProviderSession = shouldSkipLiveCliNativeHistoryWithoutProviderSession({
2200
+ adapter,
2201
+ providerType,
2202
+ readChatArgs: args,
2203
+ nativeHistorySessionId,
2204
+ parsedProviderSessionId: providerSessionId,
2205
+ });
2206
+ const nativeHistoryReadSessionId = skipLiveNativeHistoryWithoutProviderSession
2207
+ ? undefined
2208
+ : nativeHistorySessionId;
2118
2209
  const exactNativeHistoryScope = Boolean(
2119
2210
  (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
2120
2211
  || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
2121
2212
  || providerSessionId
2122
- || (nativeHistorySessionId && nativeHistorySessionId !== targetSessionId)
2213
+ || (nativeHistoryReadSessionId && nativeHistoryReadSessionId !== targetSessionId)
2123
2214
  || ((h.currentSession as any)?.sessionId === args?.targetSessionId && typeof (h.currentSession as any)?.providerSessionId === 'string' && (h.currentSession as any).providerSessionId.trim())
2124
2215
  );
2125
2216
 
@@ -2130,7 +2221,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2130
2221
  try {
2131
2222
  nativeHistory = readCliProviderNativeHistory(agentStr, {
2132
2223
  canonicalHistory: provider?.nativeHistory,
2133
- historySessionId: nativeHistorySessionId,
2224
+ historySessionId: nativeHistoryReadSessionId,
2134
2225
  workspace,
2135
2226
  offset: 0,
2136
2227
  limit: nativeHistoryLimit,
@@ -2149,20 +2240,21 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2149
2240
 
2150
2241
  // 2. Compute safeMapping with the same rules the v1 code used so the
2151
2242
  // machine sees the same observation it always would have.
2152
- const nativeMessages: ChatMessage[] = nativeHistory && Array.isArray(nativeHistory.messages)
2243
+ let nativeMessages: ChatMessage[] = nativeHistory && Array.isArray(nativeHistory.messages)
2153
2244
  ? normalizeAndFilterNativeHistory(h, agentStr, args, nativeHistory.messages as ChatMessage[], nativeHistory.providerSessionId)
2154
2245
  : [];
2155
- const historyProviderSessionId = typeof nativeHistory?.providerSessionId === 'string'
2246
+ const sessionStartedAtMs = sessionStartedAtMsFromRegistry(h, args?.targetSessionId);
2247
+ let historyProviderSessionId = typeof nativeHistory?.providerSessionId === 'string'
2156
2248
  ? nativeHistory.providerSessionId
2157
- : readHistorySessionIdFromMessages(nativeMessages) || nativeHistorySessionId || historySessionId;
2158
- const lookup = nativeHistory?.lookup === 'workspace' ? 'workspace' : 'session';
2159
- const nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
2249
+ : readHistorySessionIdFromMessages(nativeMessages) || nativeHistoryReadSessionId || historySessionId;
2250
+ let lookup = nativeHistory?.lookup === 'workspace' ? 'workspace' : 'session';
2251
+ let nativeHistorySessionForMapping = adapter.cliType === 'antigravity-cli'
2160
2252
  && historyProviderSessionId
2161
- && nativeHistorySessionId
2162
- && historyProviderSessionId !== nativeHistorySessionId
2253
+ && nativeHistoryReadSessionId
2254
+ && historyProviderSessionId !== nativeHistoryReadSessionId
2163
2255
  ? undefined
2164
- : nativeHistorySessionId;
2165
- const safeMapping = supportsNative && nativeHistory
2256
+ : nativeHistoryReadSessionId;
2257
+ let safeMapping = supportsNative && nativeHistory
2166
2258
  ? hasSafeNativeHistoryMapping({
2167
2259
  historySessionId: lookup === 'workspace' ? undefined : nativeHistorySessionForMapping,
2168
2260
  providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId || providerSessionId,
@@ -2172,6 +2264,64 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2172
2264
  requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
2173
2265
  })
2174
2266
  : false;
2267
+ if (skipLiveNativeHistoryWithoutProviderSession && (!safeMapping || returnedMessages.length === 0)) {
2268
+ nativeHistory = null;
2269
+ nativeMessages = [];
2270
+ historyProviderSessionId = undefined;
2271
+ lookup = 'session';
2272
+ safeMapping = false;
2273
+ }
2274
+ const mayRetryUnsafeAutoDetectedCodexSession = adapter.cliType === 'codex-cli'
2275
+ && !getExplicitHistorySessionId(args)
2276
+ && Boolean(sessionStartedAtMs && sessionStartedAtMs > 0)
2277
+ && !skipLiveNativeHistoryWithoutProviderSession
2278
+ && !safeMapping;
2279
+ if (mayRetryUnsafeAutoDetectedCodexSession) {
2280
+ try {
2281
+ nativeHistory = readCliProviderNativeHistory(agentStr, {
2282
+ canonicalHistory: provider?.nativeHistory,
2283
+ historySessionId: undefined,
2284
+ workspace,
2285
+ offset: 0,
2286
+ limit: nativeHistoryLimit,
2287
+ excludeRecentCount: 0,
2288
+ historyBehavior: provider?.historyBehavior,
2289
+ scripts: provider?.scripts as any,
2290
+ excludeInProgressTurn: returnedStatus === 'waiting_approval',
2291
+ sessionStartedAtMs,
2292
+ envOverrides: sessionSpawnEnvFromAdapter(h, args?.targetSessionId),
2293
+ });
2294
+ nativeHistoryError = undefined;
2295
+ nativeMessages = nativeHistory && Array.isArray(nativeHistory.messages)
2296
+ ? normalizeAndFilterNativeHistory(h, agentStr, args, nativeHistory.messages as ChatMessage[], nativeHistory.providerSessionId)
2297
+ : [];
2298
+ historyProviderSessionId = typeof nativeHistory?.providerSessionId === 'string'
2299
+ ? nativeHistory.providerSessionId
2300
+ : readHistorySessionIdFromMessages(nativeMessages);
2301
+ lookup = nativeHistory?.lookup === 'workspace' ? 'workspace' : 'session';
2302
+ nativeHistorySessionForMapping = undefined;
2303
+ safeMapping = supportsNative && nativeHistory
2304
+ ? hasSafeNativeHistoryMapping({
2305
+ historySessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
2306
+ providerSessionId: lookup === 'workspace' ? undefined : historyProviderSessionId,
2307
+ workspace,
2308
+ nativeMessages,
2309
+ ptyMessages: returnedMessages,
2310
+ requireWorkspaceContentOverlap: lookup === 'workspace' && !exactNativeHistoryScope,
2311
+ })
2312
+ : false;
2313
+ } catch (error: any) {
2314
+ nativeHistoryError = error;
2315
+ nativeHistory = null;
2316
+ nativeMessages = [];
2317
+ historyProviderSessionId = undefined;
2318
+ safeMapping = false;
2319
+ }
2320
+ }
2321
+ const trustedExactNativeIdentity = lookup !== 'workspace'
2322
+ && Boolean(nativeHistoryReadSessionId)
2323
+ && Boolean(historyProviderSessionId)
2324
+ && nativeHistoryReadSessionId === historyProviderSessionId;
2175
2325
 
2176
2326
  // 3. Drive ChatSourceMachine — one observation per readChat call,
2177
2327
  // keyed by (providerType, sessionKey-for-this-call). targetSessionId
@@ -2191,6 +2341,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2191
2341
  nativeHistoryResult: nativeHistory,
2192
2342
  nativeHistoryError,
2193
2343
  safeMapping,
2344
+ trustedExactNativeIdentity,
2194
2345
  sessionWorkspace,
2195
2346
  intendedWorkspace,
2196
2347
  ptyMessages: returnedMessages,
@@ -2205,6 +2356,9 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2205
2356
  selectedProviderSessionId = historyProviderSessionId || providerSessionId;
2206
2357
  selectedTranscriptAuthority = 'provider';
2207
2358
  selectedCoverage = nativeHistory?.hasMore ? 'tail' : 'full';
2359
+ if (selectedProviderSessionId && selectedProviderSessionId !== providerSessionId) {
2360
+ adapter.updateRuntimeMeta?.({ providerSessionId: selectedProviderSessionId });
2361
+ }
2208
2362
  } else if (supportsNative) {
2209
2363
  // Native not selected. Two preserved v1 fallbacks before settling
2210
2364
  // on PTY: (a) Codex-only live workspace native probe; (b) unsafe-
@@ -2222,7 +2376,9 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2222
2376
  && liveCurrentRuntimePtySafe
2223
2377
  && !(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
2224
2378
  && !(providerSessionId && providerSessionId.trim())
2225
- && (!historyProviderSessionId || historyProviderSessionId === nativeHistorySessionId || historyProviderSessionId === historySessionId);
2379
+ && !nativeHistoryReadSessionId
2380
+ && (!historyProviderSessionId || historyProviderSessionId === nativeHistoryReadSessionId || historyProviderSessionId === historySessionId)
2381
+ && !skipLiveNativeHistoryWithoutProviderSession;
2226
2382
  const liveWorkspaceNativeHistory = mayProbeLiveCodexWorkspaceNative
2227
2383
  ? readLiveCodexWorkspaceNativeHistory(agentStr, {
2228
2384
  canonicalHistory: provider?.nativeHistory,
@@ -2267,6 +2423,9 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2267
2423
  selectedProviderSessionId = liveWorkspaceNativeProviderSessionId || providerSessionId;
2268
2424
  selectedTranscriptAuthority = 'provider';
2269
2425
  selectedCoverage = (liveWorkspaceNativeHistory as any).hasMore ? 'tail' : 'full';
2426
+ if (selectedProviderSessionId && selectedProviderSessionId !== providerSessionId) {
2427
+ adapter.updateRuntimeMeta?.({ providerSessionId: selectedProviderSessionId });
2428
+ }
2270
2429
  messageSource = liveDecision.messageSource;
2271
2430
  (messageSource as any).selectedDaemonSource = 'live-workspace-native-history';
2272
2431
  (messageSource as any).runtimeMappingSafe = true;
@@ -2404,6 +2563,10 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2404
2563
  nativeMessages: historyMessages,
2405
2564
  })
2406
2565
  : false;
2566
+ const trustedExactNativeIdentity = lookup !== 'workspace'
2567
+ && Boolean(historySessionId)
2568
+ && Boolean(historyProviderSessionId)
2569
+ && historySessionId === historyProviderSessionId;
2407
2570
 
2408
2571
  const machineSessionKey = String(
2409
2572
  args?.targetSessionId
@@ -2418,6 +2581,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2418
2581
  sessionId: machineSessionKey,
2419
2582
  nativeHistoryResult: history,
2420
2583
  safeMapping,
2584
+ trustedExactNativeIdentity,
2421
2585
  sessionWorkspace: workspace,
2422
2586
  intendedWorkspace,
2423
2587
  ptyMessages: [],
@@ -1305,6 +1305,10 @@ export class DaemonCliManager {
1305
1305
  } else {
1306
1306
  await adapter.sendMessage(message);
1307
1307
  }
1308
+ const targetInstance = this.deps.getInstanceManager()?.getInstance(key) as
1309
+ | { recordAcknowledgedUserInput?: (input: unknown) => void }
1310
+ | undefined;
1311
+ targetInstance?.recordAcknowledgedUserInput?.(input);
1308
1312
  return {
1309
1313
  success: true,
1310
1314
  status: BUSY_AGENT_STATUSES.has(currentStatus) ? currentStatus : 'generating',