@adhdev/daemon-core 0.8.24 → 0.8.27

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.
Files changed (34) hide show
  1. package/dist/agent-stream/provider-adapter.d.ts +1 -0
  2. package/dist/boot/daemon-lifecycle.d.ts +2 -0
  3. package/dist/cli-adapters/pty-transport.d.ts +3 -0
  4. package/dist/commands/router.d.ts +24 -0
  5. package/dist/index.js +309 -19
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +309 -19
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/providers/cli-provider-instance.d.ts +1 -0
  10. package/dist/providers/extension-provider-instance.d.ts +7 -0
  11. package/dist/sessions/reconcile.d.ts +22 -0
  12. package/dist/status/normalize.js +14 -2
  13. package/dist/status/normalize.js.map +1 -1
  14. package/dist/status/normalize.mjs +14 -2
  15. package/dist/status/normalize.mjs.map +1 -1
  16. package/dist/status/snapshot.d.ts +0 -2
  17. package/node_modules/@adhdev/session-host-core/dist/index.d.mts +72 -1
  18. package/node_modules/@adhdev/session-host-core/dist/index.d.ts +72 -1
  19. package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
  20. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  21. package/package.json +1 -1
  22. package/src/agent-stream/poller.ts +4 -0
  23. package/src/agent-stream/provider-adapter.ts +30 -1
  24. package/src/boot/daemon-lifecycle.ts +3 -0
  25. package/src/cli-adapters/pty-transport.ts +3 -0
  26. package/src/cli-adapters/session-host-transport.ts +8 -0
  27. package/src/commands/chat-commands.ts +2 -2
  28. package/src/commands/handler.ts +79 -13
  29. package/src/commands/router.ts +132 -0
  30. package/src/providers/cli-provider-instance.ts +29 -1
  31. package/src/providers/extension-provider-instance.ts +24 -1
  32. package/src/sessions/reconcile.ts +85 -0
  33. package/src/status/normalize.ts +19 -2
  34. package/src/status/snapshot.ts +6 -13
@@ -16,6 +16,7 @@ export declare class ProviderStreamAdapter implements IAgentStreamAdapter {
16
16
  constructor(provider: ProviderModule);
17
17
  private callScript;
18
18
  private hasScript;
19
+ private parseMaybeJson;
19
20
  private summarizeRaw;
20
21
  private isTransportError;
21
22
  readChat(evaluate: AgentEvaluateFn): Promise<AgentStreamState>;
@@ -10,6 +10,7 @@ import { DaemonCdpManager } from '../cdp/manager.js';
10
10
  import { DaemonCdpInitializer } from '../cdp/initializer.js';
11
11
  import { DaemonCommandHandler } from '../commands/handler.js';
12
12
  import { DaemonCommandRouter } from '../commands/router.js';
13
+ import type { SessionHostControlPlane } from '../commands/router.js';
13
14
  import { DaemonCliManager, type CliTransportFactoryParams, type HostedCliRuntimeDescriptor } from '../commands/cli-manager.js';
14
15
  import { DaemonAgentStreamManager } from '../agent-stream/manager.js';
15
16
  import { AgentStreamPoller } from '../agent-stream/poller.js';
@@ -35,6 +36,7 @@ export interface DaemonInitConfig {
35
36
  /** Router transport-specific callbacks */
36
37
  onStatusChange?: () => void;
37
38
  onPostChatCommand?: () => void;
39
+ sessionHostControl?: SessionHostControlPlane | null;
38
40
  getCdpLogFn?: (ideType: string) => (msg: string) => void;
39
41
  /** Additional callback after CDP manager created (transport-specific extras) */
40
42
  onCdpManagerSetup?: (ideType: string, manager: DaemonCdpManager, managerKey: string) => void | Promise<void>;
@@ -20,6 +20,9 @@ export interface PtyRuntimeMetadata {
20
20
  workspaceLabel?: string;
21
21
  writeOwner?: PtyRuntimeWriteOwner | null;
22
22
  attachedClients?: PtyRuntimeClientInfo[];
23
+ restoredFromStorage?: boolean;
24
+ recoveryState?: string | null;
25
+ recoveryError?: string | null;
23
26
  }
24
27
  export interface PtyRuntimeTransport {
25
28
  readonly pid: number;
@@ -14,6 +14,28 @@ import { DaemonCliManager } from './cli-manager.js';
14
14
  import type { ProviderLoader } from '../providers/provider-loader.js';
15
15
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
16
16
  import { SessionRegistry } from '../sessions/registry.js';
17
+ export interface SessionHostControlPlane {
18
+ getDiagnostics(payload?: {
19
+ includeSessions?: boolean;
20
+ limit?: number;
21
+ }): Promise<any>;
22
+ listSessions(): Promise<any[]>;
23
+ stopSession(sessionId: string): Promise<any>;
24
+ resumeSession(sessionId: string): Promise<any>;
25
+ restartSession(sessionId: string): Promise<any>;
26
+ sendSignal(sessionId: string, signal: string): Promise<any>;
27
+ forceDetachClient(sessionId: string, clientId: string): Promise<any>;
28
+ acquireWrite(payload: {
29
+ sessionId: string;
30
+ clientId: string;
31
+ ownerType: 'agent' | 'user';
32
+ force?: boolean;
33
+ }): Promise<any>;
34
+ releaseWrite(payload: {
35
+ sessionId: string;
36
+ clientId: string;
37
+ }): Promise<any>;
38
+ }
17
39
  export interface CommandRouterDeps {
18
40
  commandHandler: DaemonCommandHandler;
19
41
  cliManager: DaemonCliManager;
@@ -37,6 +59,8 @@ export interface CommandRouterDeps {
37
59
  getCdpLogFn?: (ideType: string) => (msg: string) => void;
38
60
  /** Package name for upgrade detection ('adhdev' or '@adhdev/daemon-standalone') */
39
61
  packageName?: string;
62
+ /** Session host control plane */
63
+ sessionHostControl?: SessionHostControlPlane | null;
40
64
  }
41
65
  export interface CommandRouterResult {
42
66
  success: boolean;
package/dist/index.js CHANGED
@@ -5201,6 +5201,7 @@ var ExtensionProviderInstance = class {
5201
5201
  currentStatus = "idle";
5202
5202
  agentStreams = [];
5203
5203
  messages = [];
5204
+ prevMessageHashes = /* @__PURE__ */ new Map();
5204
5205
  activeModal = null;
5205
5206
  currentModel = "";
5206
5207
  currentMode = "";
@@ -5266,7 +5267,7 @@ var ExtensionProviderInstance = class {
5266
5267
  onEvent(event, data) {
5267
5268
  if (event === "stream_update") {
5268
5269
  if (data?.streams) this.agentStreams = data.streams;
5269
- if (data?.messages) this.messages = data.messages;
5270
+ if (data?.messages) this.messages = this.assignReceivedAt(data.messages);
5270
5271
  if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
5271
5272
  if (data?.model) this.currentModel = data.model;
5272
5273
  if (data?.mode) this.currentMode = data.mode;
@@ -5292,6 +5293,7 @@ var ExtensionProviderInstance = class {
5292
5293
  dispose() {
5293
5294
  this.agentStreams = [];
5294
5295
  this.messages = [];
5296
+ this.prevMessageHashes.clear();
5295
5297
  this.monitor.reset();
5296
5298
  this.appliedEffectKeys.clear();
5297
5299
  this.runtimeMessages = [];
@@ -5447,6 +5449,23 @@ var ExtensionProviderInstance = class {
5447
5449
  this.chatId || this.instanceId
5448
5450
  );
5449
5451
  }
5452
+ /**
5453
+ * Assign stable receivedAt to extension messages.
5454
+ * Same pattern as IdeProviderInstance.readChat() prevByHash —
5455
+ * preserves first-seen timestamp across polling cycles.
5456
+ */
5457
+ assignReceivedAt(messages) {
5458
+ const now = Date.now();
5459
+ const nextHashes = /* @__PURE__ */ new Map();
5460
+ for (const msg of messages) {
5461
+ const hash = `${msg.role}:${(msg.content || "").slice(0, 100)}`;
5462
+ const prevTime = this.prevMessageHashes.get(hash);
5463
+ msg.receivedAt = prevTime || now;
5464
+ nextHashes.set(hash, msg.receivedAt);
5465
+ }
5466
+ this.prevMessageHashes = nextHashes;
5467
+ return messages;
5468
+ }
5450
5469
  mergeConversationMessages(messages) {
5451
5470
  if (this.runtimeMessages.length === 0) return messages;
5452
5471
  return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
@@ -5503,6 +5522,7 @@ ${effect.notification.body || ""}`.trim();
5503
5522
  }
5504
5523
  this.agentStreams = [];
5505
5524
  this.messages = [];
5525
+ this.prevMessageHashes.clear();
5506
5526
  this.activeModal = null;
5507
5527
  this.currentModel = "";
5508
5528
  this.currentMode = "";
@@ -6488,16 +6508,28 @@ function trimMessageForStatus(message, stringLimit) {
6488
6508
  if (!message || typeof message !== "object") return message;
6489
6509
  return trimStructuredStrings(message, stringLimit);
6490
6510
  }
6511
+ function normalizeMessageTime(message) {
6512
+ if (!message || typeof message !== "object") return message;
6513
+ const msg = message;
6514
+ if (msg.receivedAt == null) {
6515
+ const fallback = msg.timestamp ?? msg.createdAt;
6516
+ if (fallback != null) {
6517
+ const ts2 = typeof fallback === "string" ? Date.parse(fallback) : Number(fallback);
6518
+ if (Number.isFinite(ts2) && ts2 > 0) msg.receivedAt = ts2;
6519
+ }
6520
+ }
6521
+ return msg;
6522
+ }
6491
6523
  function trimMessagesForStatus(messages) {
6492
6524
  if (!Array.isArray(messages) || messages.length === 0) return [];
6493
6525
  const recent = messages.slice(-STATUS_ACTIVE_CHAT_MESSAGE_LIMIT);
6494
6526
  const kept = [];
6495
6527
  let totalBytes = 0;
6496
6528
  for (let i = recent.length - 1; i >= 0; i -= 1) {
6497
- let normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT);
6529
+ let normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_STRING_LIMIT));
6498
6530
  let size = estimateBytes(normalized);
6499
6531
  if (size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
6500
- normalized = trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT);
6532
+ normalized = normalizeMessageTime(trimMessageForStatus(recent[i], STATUS_ACTIVE_CHAT_FALLBACK_STRING_LIMIT));
6501
6533
  size = estimateBytes(normalized);
6502
6534
  }
6503
6535
  if (kept.length > 0 && totalBytes + size > STATUS_ACTIVE_CHAT_TOTAL_BYTES_LIMIT) {
@@ -6809,6 +6841,51 @@ function buildSessionEntries(allStates, cdpManagers) {
6809
6841
  return sessions;
6810
6842
  }
6811
6843
 
6844
+ // src/sessions/reconcile.ts
6845
+ function upsertSessionTarget(sessionRegistry, target) {
6846
+ const existing = sessionRegistry.get(target.sessionId);
6847
+ if (existing && existing.parentSessionId === target.parentSessionId && existing.providerType === target.providerType && existing.transport === target.transport && existing.cdpManagerKey === target.cdpManagerKey && existing.instanceKey === target.instanceKey) {
6848
+ return;
6849
+ }
6850
+ sessionRegistry.register(target);
6851
+ }
6852
+ function reconcileIdeRuntimeSessions(instanceManager, sessionRegistry) {
6853
+ if (!instanceManager || !sessionRegistry) return;
6854
+ for (const instanceKey of instanceManager.listInstanceIds()) {
6855
+ if (!instanceKey.startsWith("ide:")) continue;
6856
+ const ideInstance = instanceManager.getInstance(instanceKey);
6857
+ if (!ideInstance || ideInstance.category !== "ide" || typeof ideInstance.getInstanceId !== "function") {
6858
+ continue;
6859
+ }
6860
+ const managerKey = instanceKey.slice(4);
6861
+ const ideType = typeof ideInstance.type === "string" && ideInstance.type.trim() ? ideInstance.type.trim() : managerKey.split("_")[0];
6862
+ const parentSessionId = ideInstance.getInstanceId();
6863
+ if (!parentSessionId) continue;
6864
+ upsertSessionTarget(sessionRegistry, {
6865
+ sessionId: parentSessionId,
6866
+ parentSessionId: null,
6867
+ providerType: ideType,
6868
+ transport: "cdp-page",
6869
+ cdpManagerKey: managerKey,
6870
+ instanceKey
6871
+ });
6872
+ const extensions = ideInstance.getExtensionInstances?.() || [];
6873
+ for (const ext of extensions) {
6874
+ const extType = typeof ext?.type === "string" ? ext.type.trim() : "";
6875
+ const extSessionId = ext?.getInstanceId?.();
6876
+ if (!extType || !extSessionId) continue;
6877
+ upsertSessionTarget(sessionRegistry, {
6878
+ sessionId: extSessionId,
6879
+ parentSessionId,
6880
+ providerType: extType,
6881
+ transport: "cdp-webview",
6882
+ cdpManagerKey: managerKey,
6883
+ instanceKey
6884
+ });
6885
+ }
6886
+ }
6887
+ }
6888
+
6812
6889
  // src/commands/handler.ts
6813
6890
  init_logger();
6814
6891
 
@@ -7075,7 +7152,7 @@ async function handleSendChat(h, args) {
7075
7152
  if (isExtensionTransport(transport)) {
7076
7153
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7077
7154
  try {
7078
- const evalResult = await h.evaluateProviderScript("sendMessage", { MESSAGE: text }, 3e4);
7155
+ const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7079
7156
  if (evalResult?.result) {
7080
7157
  const parsed = parseMaybeJson(evalResult.result);
7081
7158
  if (didProviderConfirmSend(parsed)) {
@@ -7106,7 +7183,7 @@ async function handleSendChat(h, args) {
7106
7183
  return { success: false, error: `CDP for ${managerKey || "unknown"} not connected` };
7107
7184
  }
7108
7185
  _log(`Targeting IDE: ${getCurrentManagerKey(h)}`);
7109
- const sendScript = h.getProviderScript("sendMessage", { MESSAGE: text });
7186
+ const sendScript = h.getProviderScript("sendMessage", { message: text });
7110
7187
  if (sendScript) {
7111
7188
  try {
7112
7189
  const result = await targetCdp.evaluate(sendScript, 3e4);
@@ -8508,9 +8585,26 @@ var DaemonCommandHandler = class {
8508
8585
  if (provider?.scripts) {
8509
8586
  const fn = provider.scripts[scriptName];
8510
8587
  if (typeof fn === "function") {
8511
- const firstVal = params ? Object.values(params)[0] : void 0;
8512
- const script = firstVal ? fn(firstVal) : fn();
8513
- if (script) return script;
8588
+ if (params && Object.keys(params).length > 0) {
8589
+ const firstVal = Object.values(params)[0];
8590
+ if (scriptName === "sendMessage" && typeof firstVal === "string") {
8591
+ const legacyScript = fn(firstVal);
8592
+ if (legacyScript) return legacyScript;
8593
+ }
8594
+ const script = fn(params);
8595
+ if (script) {
8596
+ const likelyLegacyObjectLeak = typeof script === "string" && script.includes("[object Object]") && typeof firstVal === "string";
8597
+ if (!likelyLegacyObjectLeak) return script;
8598
+ }
8599
+ if (firstVal !== void 0) {
8600
+ const legacyScript = fn(firstVal);
8601
+ if (legacyScript) return legacyScript;
8602
+ }
8603
+ if (script) return script;
8604
+ } else {
8605
+ const script = fn();
8606
+ if (script) return script;
8607
+ }
8514
8608
  }
8515
8609
  }
8516
8610
  return null;
@@ -8566,17 +8660,27 @@ var DaemonCommandHandler = class {
8566
8660
  return key.split("_")[0];
8567
8661
  }
8568
8662
  resolveRoute(args) {
8569
- const session = this._ctx.sessionRegistry?.get(args?.targetSessionId);
8570
- const managerKey = this.extractIdeType(args);
8571
- const providerType = args?.agentType || args?.providerType || session?.providerType || this.inferProviderType(managerKey);
8572
- return { session, managerKey, providerType };
8663
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
8664
+ let session = targetSessionId ? this._ctx.sessionRegistry?.get(targetSessionId) : void 0;
8665
+ if (targetSessionId && !session) {
8666
+ reconcileIdeRuntimeSessions(this._ctx.instanceManager, this._ctx.sessionRegistry);
8667
+ session = this._ctx.sessionRegistry?.get(targetSessionId);
8668
+ }
8669
+ const sessionLookupFailed = !!targetSessionId && !session;
8670
+ const managerKey = this.extractIdeType(args, sessionLookupFailed);
8671
+ let providerType;
8672
+ if (!sessionLookupFailed) {
8673
+ providerType = session?.providerType || args?.agentType || args?.providerType || this.inferProviderType(managerKey);
8674
+ }
8675
+ return { session, managerKey, providerType, sessionLookupFailed };
8573
8676
  }
8574
8677
  /** Extract CDP scope key from target session or explicit ideType */
8575
- extractIdeType(args) {
8678
+ extractIdeType(args, sessionLookupFailed = false) {
8576
8679
  if (args?.targetSessionId) {
8577
8680
  const target = this._ctx.sessionRegistry?.get(args.targetSessionId);
8578
8681
  if (target?.cdpManagerKey) return target.cdpManagerKey;
8579
8682
  if (this._ctx.cdpManagers.has(args.targetSessionId)) return args.targetSessionId;
8683
+ if (sessionLookupFailed) return void 0;
8580
8684
  }
8581
8685
  if (args?.ideType) {
8582
8686
  const target = this._ctx.sessionRegistry?.get(args.ideType);
@@ -8623,6 +8727,33 @@ var DaemonCommandHandler = class {
8623
8727
  this._currentRoute = this.resolveRoute(args);
8624
8728
  const startedAt = Date.now();
8625
8729
  this.logCommandStart(cmd, args);
8730
+ const sessionScopedCommands = /* @__PURE__ */ new Set([
8731
+ "read_chat",
8732
+ "send_chat",
8733
+ "list_chats",
8734
+ "new_chat",
8735
+ "switch_chat",
8736
+ "set_mode",
8737
+ "change_model",
8738
+ "set_thought_level",
8739
+ "resolve_action",
8740
+ "focus_session",
8741
+ "pty_input",
8742
+ "pty_resize",
8743
+ "invoke_provider_script",
8744
+ "list_extension_models",
8745
+ "set_extension_model",
8746
+ "list_extension_modes",
8747
+ "set_extension_mode"
8748
+ ]);
8749
+ if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd)) {
8750
+ const result2 = {
8751
+ success: false,
8752
+ error: `Live session not found for targetSessionId: ${String(args?.targetSessionId || "").trim() || "unknown"}`
8753
+ };
8754
+ this.logCommandEnd(cmd, result2, startedAt);
8755
+ return result2;
8756
+ }
8626
8757
  let result;
8627
8758
  if (!this._currentRoute.session && !this._currentRoute.managerKey && !this._currentRoute.providerType) {
8628
8759
  const cdpCommands = ["send_chat", "read_chat", "list_chats", "new_chat", "switch_chat", "set_mode", "change_model", "set_thought_level", "resolve_action"];
@@ -8934,6 +9065,7 @@ var CliProviderInstance = class {
8934
9065
  this.detectStatusTransition();
8935
9066
  });
8936
9067
  await this.adapter.spawn();
9068
+ this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
8937
9069
  if (this.providerSessionId) {
8938
9070
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
8939
9071
  if (restoredHistory.messages.length > 0) {
@@ -9028,6 +9160,7 @@ var CliProviderInstance = class {
9028
9160
  this.promoteProviderSessionId(parsedProviderSessionId);
9029
9161
  }
9030
9162
  const runtime = this.adapter.getRuntimeMetadata();
9163
+ this.maybeAppendRuntimeRecoveryMessage(runtime);
9031
9164
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
9032
9165
  const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
9033
9166
  if (controlValues) {
@@ -9354,6 +9487,28 @@ ${effect.notification.body || ""}`.trim();
9354
9487
  const pad = (value) => String(value).padStart(2, "0");
9355
9488
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
9356
9489
  }
9490
+ maybeAppendRuntimeRecoveryMessage(runtime) {
9491
+ if (!runtime?.restoredFromStorage || !runtime.runtimeId) return;
9492
+ const recoveryState = String(runtime.recoveryState || "").trim();
9493
+ if (!recoveryState) return;
9494
+ let content = "";
9495
+ if (recoveryState === "auto_resumed") {
9496
+ content = "Session host restored this CLI after restart and reattached it from a saved snapshot.";
9497
+ } else if (recoveryState === "resume_failed") {
9498
+ const errorSuffix = runtime.recoveryError ? ` Resume failed: ${runtime.recoveryError}` : "";
9499
+ content = `Session host found this CLI after restart, but automatic resume failed.${errorSuffix}`;
9500
+ } else if (recoveryState === "host_restart_interrupted") {
9501
+ content = "Session host found this CLI in interrupted state after restart and is attempting to resume it.";
9502
+ } else if (recoveryState === "orphan_snapshot") {
9503
+ content = "Session host restored the last snapshot for this CLI, but the original runtime was not resumed automatically.";
9504
+ } else {
9505
+ content = `Session host restored this CLI after restart (${recoveryState}).`;
9506
+ }
9507
+ this.appendRuntimeSystemMessage(
9508
+ content,
9509
+ `runtime_recovery:${runtime.runtimeId}:${recoveryState}`
9510
+ );
9511
+ }
9357
9512
  appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
9358
9513
  const normalizedContent = String(content || "").trim();
9359
9514
  if (!normalizedContent) return;
@@ -12642,17 +12797,17 @@ function parseMessageTime(value) {
12642
12797
  function getSessionMessageUpdatedAt(session) {
12643
12798
  const lastMessage = session.activeChat?.messages?.at?.(-1);
12644
12799
  if (!lastMessage) return 0;
12645
- return parseMessageTime(lastMessage.timestamp) || parseMessageTime(lastMessage.receivedAt) || parseMessageTime(lastMessage.createdAt) || 0;
12800
+ return parseMessageTime(lastMessage.receivedAt) || 0;
12646
12801
  }
12647
12802
  function getSessionCompletionMarker(session) {
12648
12803
  const lastMessage = session.activeChat?.messages?.at?.(-1);
12649
12804
  if (!lastMessage) return "";
12650
12805
  const role = typeof lastMessage.role === "string" ? lastMessage.role : "";
12651
- if (role === "user" || role === "human") return "";
12806
+ if (role === "user" || role === "human" || role === "system") return "";
12652
12807
  if (typeof lastMessage._turnKey === "string" && lastMessage._turnKey) return `turn:${lastMessage._turnKey}`;
12653
12808
  if (typeof lastMessage.id === "string" && lastMessage.id) return `id:${lastMessage.id}`;
12654
12809
  if (typeof lastMessage.index === "number" && Number.isFinite(lastMessage.index)) return `idx:${lastMessage.index}`;
12655
- const timestamp = parseMessageTime(lastMessage.timestamp) || parseMessageTime(lastMessage.receivedAt) || parseMessageTime(lastMessage.createdAt);
12810
+ const timestamp = parseMessageTime(lastMessage.receivedAt);
12656
12811
  return timestamp > 0 ? `ts:${timestamp}` : "";
12657
12812
  }
12658
12813
  function getSessionLastUsedAt(session) {
@@ -12669,7 +12824,7 @@ function getUnreadState(hasContentChange, status, lastUsedAt, lastSeenAt, lastRo
12669
12824
  if (status === "generating" || status === "starting") {
12670
12825
  return { unread: false, inboxBucket: "working" };
12671
12826
  }
12672
- const unread = completionMarker ? completionMarker !== seenCompletionMarker : hasContentChange && lastUsedAt > lastSeenAt && lastRole !== "user" && lastRole !== "human";
12827
+ const unread = completionMarker ? completionMarker !== seenCompletionMarker : hasContentChange && lastUsedAt > lastSeenAt && lastRole !== "user" && lastRole !== "human" && lastRole !== "system";
12673
12828
  return { unread, inboxBucket: unread ? "task_complete" : "idle" };
12674
12829
  }
12675
12830
  function buildRecentLaunches(recentActivity) {
@@ -12954,6 +13109,25 @@ var CHAT_COMMANDS = [
12954
13109
  "change_model"
12955
13110
  ];
12956
13111
  var READ_DEBUG_ENABLED2 = process.argv.includes("--dev") || process.env.ADHDEV_READ_DEBUG === "1";
13112
+ function toHostedCliRuntimeDescriptor(record) {
13113
+ if (!record || typeof record !== "object") return null;
13114
+ const runtimeId = typeof record.sessionId === "string" ? record.sessionId : "";
13115
+ const cliType = typeof record.providerType === "string" ? record.providerType : "";
13116
+ const workspace = typeof record.workspace === "string" ? record.workspace : "";
13117
+ if (!runtimeId || !cliType || !workspace) return null;
13118
+ return {
13119
+ runtimeId,
13120
+ runtimeKey: typeof record.runtimeKey === "string" ? record.runtimeKey : void 0,
13121
+ displayName: typeof record.displayName === "string" ? record.displayName : void 0,
13122
+ workspaceLabel: typeof record.workspaceLabel === "string" ? record.workspaceLabel : void 0,
13123
+ lifecycle: typeof record.lifecycle === "string" ? record.lifecycle : void 0,
13124
+ recoveryState: typeof record.meta?.runtimeRecoveryState === "string" ? String(record.meta.runtimeRecoveryState) : null,
13125
+ cliType,
13126
+ workspace,
13127
+ cliArgs: Array.isArray(record.meta?.cliArgs) ? record.meta.cliArgs : [],
13128
+ providerSessionId: typeof record.meta?.providerSessionId === "string" ? String(record.meta.providerSessionId) : void 0
13129
+ };
13130
+ }
12957
13131
  var DaemonCommandRouter = class {
12958
13132
  deps;
12959
13133
  constructor(deps) {
@@ -13027,6 +13201,90 @@ var DaemonCommandRouter = class {
13027
13201
  return { success: false, error: e.message };
13028
13202
  }
13029
13203
  }
13204
+ case "session_host_get_diagnostics": {
13205
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13206
+ const diagnostics = await this.deps.sessionHostControl.getDiagnostics({
13207
+ includeSessions: args?.includeSessions !== false,
13208
+ limit: Number(args?.limit) || void 0
13209
+ });
13210
+ return { success: true, diagnostics };
13211
+ }
13212
+ case "session_host_list_sessions": {
13213
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13214
+ const sessions = await this.deps.sessionHostControl.listSessions();
13215
+ return { success: true, sessions };
13216
+ }
13217
+ case "session_host_stop_session": {
13218
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13219
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13220
+ if (!sessionId) return { success: false, error: "sessionId required" };
13221
+ const record = await this.deps.sessionHostControl.stopSession(sessionId);
13222
+ return { success: true, record };
13223
+ }
13224
+ case "session_host_resume_session": {
13225
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13226
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13227
+ if (!sessionId) return { success: false, error: "sessionId required" };
13228
+ const record = await this.deps.sessionHostControl.resumeSession(sessionId);
13229
+ const hosted = toHostedCliRuntimeDescriptor(record);
13230
+ if (hosted) {
13231
+ await this.deps.cliManager.restoreHostedSessions([hosted]);
13232
+ }
13233
+ return { success: true, record };
13234
+ }
13235
+ case "session_host_restart_session": {
13236
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13237
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13238
+ if (!sessionId) return { success: false, error: "sessionId required" };
13239
+ const record = await this.deps.sessionHostControl.restartSession(sessionId);
13240
+ const hosted = toHostedCliRuntimeDescriptor(record);
13241
+ if (hosted) {
13242
+ await this.deps.cliManager.restoreHostedSessions([hosted]);
13243
+ }
13244
+ return { success: true, record };
13245
+ }
13246
+ case "session_host_send_signal": {
13247
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13248
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13249
+ const signal = typeof args?.signal === "string" ? args.signal : "";
13250
+ if (!sessionId) return { success: false, error: "sessionId required" };
13251
+ if (!signal) return { success: false, error: "signal required" };
13252
+ const record = await this.deps.sessionHostControl.sendSignal(sessionId, signal);
13253
+ return { success: true, record };
13254
+ }
13255
+ case "session_host_force_detach_client": {
13256
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13257
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13258
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
13259
+ if (!sessionId) return { success: false, error: "sessionId required" };
13260
+ if (!clientId) return { success: false, error: "clientId required" };
13261
+ const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13262
+ return { success: true, record };
13263
+ }
13264
+ case "session_host_acquire_write": {
13265
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13266
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13267
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
13268
+ const ownerType = args?.ownerType === "agent" ? "agent" : "user";
13269
+ if (!sessionId) return { success: false, error: "sessionId required" };
13270
+ if (!clientId) return { success: false, error: "clientId required" };
13271
+ const record = await this.deps.sessionHostControl.acquireWrite({
13272
+ sessionId,
13273
+ clientId,
13274
+ ownerType,
13275
+ force: args?.force !== false
13276
+ });
13277
+ return { success: true, record };
13278
+ }
13279
+ case "session_host_release_write": {
13280
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13281
+ const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
13282
+ const clientId = typeof args?.clientId === "string" ? args.clientId : "";
13283
+ if (!sessionId) return { success: false, error: "sessionId required" };
13284
+ if (!clientId) return { success: false, error: "clientId required" };
13285
+ const record = await this.deps.sessionHostControl.releaseWrite({ sessionId, clientId });
13286
+ return { success: true, record };
13287
+ }
13030
13288
  case "list_saved_sessions": {
13031
13289
  const providerType = typeof args?.providerType === "string" ? args.providerType.trim() : typeof args?.agentType === "string" ? args.agentType.trim() : "";
13032
13290
  const kind = args?.kind === "acp" ? "acp" : "cli";
@@ -13555,6 +13813,14 @@ var ProviderStreamAdapter = class {
13555
13813
  hasScript(name) {
13556
13814
  return typeof this.provider.scripts?.[name] === "function";
13557
13815
  }
13816
+ parseMaybeJson(raw) {
13817
+ if (typeof raw !== "string") return raw;
13818
+ try {
13819
+ return JSON.parse(raw);
13820
+ } catch {
13821
+ return raw;
13822
+ }
13823
+ }
13558
13824
  summarizeRaw(raw) {
13559
13825
  try {
13560
13826
  if (typeof raw === "string") return raw.replace(/\s+/g, " ").trim().slice(0, 240);
@@ -13615,12 +13881,30 @@ var ProviderStreamAdapter = class {
13615
13881
  }
13616
13882
  }
13617
13883
  async sendMessage(evaluate, text) {
13618
- const script = this.callScript("sendMessage", text);
13884
+ const params = { message: text };
13885
+ const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
13619
13886
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
13620
13887
  const result = await evaluate(script);
13621
13888
  if (result && typeof result === "string" && result.startsWith("error:")) {
13622
13889
  throw new Error(`[${this.agentName}] sendMessage failed: ${result}`);
13623
13890
  }
13891
+ const parsed = this.parseMaybeJson(result);
13892
+ if (parsed === true) return;
13893
+ if (typeof parsed === "string") {
13894
+ const normalized = parsed.trim().toLowerCase();
13895
+ if (normalized === "ok" || normalized === "sent" || normalized === "success" || normalized === "true") {
13896
+ return;
13897
+ }
13898
+ }
13899
+ if (parsed && typeof parsed === "object") {
13900
+ if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
13901
+ return;
13902
+ }
13903
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
13904
+ throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
13905
+ }
13906
+ }
13907
+ throw new Error(`[${this.agentName}] sendMessage was not confirmed`);
13624
13908
  }
13625
13909
  async resolveAction(evaluate, action, button) {
13626
13910
  const script = this.callScript("resolveAction", { action, button });
@@ -14005,6 +14289,7 @@ var AgentStreamPoller = class {
14005
14289
  sessionRegistry
14006
14290
  } = this.deps;
14007
14291
  if (!agentStreamManager || cdpManagers.size === 0) return;
14292
+ reconcileIdeRuntimeSessions(instanceManager, sessionRegistry);
14008
14293
  for (const [ideType, cdp] of cdpManagers) {
14009
14294
  registerExtensionProviders(providerLoader, cdp, ideType);
14010
14295
  const ideInstance = instanceManager.getInstance(`ide:${ideType}`);
@@ -19870,6 +20155,7 @@ var SessionHostRuntimeTransport = class {
19870
20155
  }
19871
20156
  }
19872
20157
  handleEvent(event) {
20158
+ if (!("sessionId" in event)) return;
19873
20159
  if (event.sessionId !== this.options.runtimeId) return;
19874
20160
  if ((event.type === "session_started" || event.type === "session_resumed") && typeof event.pid === "number") {
19875
20161
  this.currentPid = event.pid;
@@ -19945,7 +20231,10 @@ var SessionHostRuntimeTransport = class {
19945
20231
  clientId: client.clientId,
19946
20232
  type: client.type,
19947
20233
  readOnly: client.readOnly
19948
- }))
20234
+ })),
20235
+ restoredFromStorage: record.meta?.restoredFromStorage === true,
20236
+ recoveryState: typeof record.meta?.runtimeRecoveryState === "string" ? String(record.meta.runtimeRecoveryState) : null,
20237
+ recoveryError: typeof record.meta?.runtimeRecoveryError === "string" ? String(record.meta.runtimeRecoveryError) : null
19949
20238
  };
19950
20239
  }
19951
20240
  enqueue(action) {
@@ -20389,6 +20678,7 @@ async function initDaemonComponents(config) {
20389
20678
  onIdeConnected: () => poller?.start(),
20390
20679
  onStatusChange: config.onStatusChange,
20391
20680
  onPostChatCommand: config.onPostChatCommand,
20681
+ sessionHostControl: config.sessionHostControl,
20392
20682
  getCdpLogFn: config.getCdpLogFn || ((ideType) => LOG.forComponent(`CDP:${ideType}`).asLogFn())
20393
20683
  });
20394
20684
  poller = new AgentStreamPoller({