@adhdev/daemon-core 0.9.82-rc.339 → 0.9.82-rc.340

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.339",
3
+ "version": "0.9.82-rc.340",
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",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.339",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.340",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -1008,6 +1008,19 @@ export function readCachedInlineMeshActiveSessionDetails(node: any): Array<Recor
1008
1008
  startedAt: readStringValue(fallbackSession.startedAt, fallbackSession.started_at) ?? null,
1009
1009
  lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
1010
1010
  recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
1011
+ // [T2] Carry the worker-computed last-message preview through the cached inline-mesh
1012
+ // active-session entry. The worker's get_status_metadata slim now ships these
1013
+ // (mesh-tools.ts), and this is the surface the coordinator's inbox-preview path reads
1014
+ // (buildDaemonMetadataUpdateForSubscription → stampLocalAssistantPreviewOnCachedEntry).
1015
+ // Without carrying them here, the coordinator could only derive the preview from a live
1016
+ // in-process instance it doesn't host for a remote worker, so the inbox stuck on the
1017
+ // dispatched user task. Only present when the worker reported them.
1018
+ ...(readStringValue(fallbackSession.lastMessagePreview, fallbackSession.last_message_preview)
1019
+ ? { lastMessagePreview: readStringValue(fallbackSession.lastMessagePreview, fallbackSession.last_message_preview) } : {}),
1020
+ ...(readStringValue(fallbackSession.lastMessageRole, fallbackSession.last_message_role)
1021
+ ? { lastMessageRole: readStringValue(fallbackSession.lastMessageRole, fallbackSession.last_message_role) } : {}),
1022
+ ...(readNumberValue(fallbackSession.lastMessageAt, fallbackSession.last_message_at) !== undefined
1023
+ ? { lastMessageAt: readNumberValue(fallbackSession.lastMessageAt, fallbackSession.last_message_at) } : {}),
1011
1024
  isCached: true,
1012
1025
  }];
1013
1026
  }
@@ -3384,6 +3397,23 @@ const CHAT_COMMANDS = [
3384
3397
  'send_chat', 'new_chat', 'switch_chat', 'set_mode',
3385
3398
  'change_model',
3386
3399
  ];
3400
+
3401
+ // [Z] Session-scoped commands that must be forwarded to the owning REMOTE worker daemon
3402
+ // when their target session is not hosted on this coordinator. These are the interactive
3403
+ // controlbar / modal mutations the dashboard issues against a specific session:
3404
+ // - invoke_provider_script: controlbar Model/Mode selectors run a provider script
3405
+ // - resolve_action: approve/reject a modal prompt
3406
+ // - set_mode / change_model / set_thought_level: direct control mutations
3407
+ // send_chat is intentionally NOT here — it already reaches the worker by its own route and
3408
+ // double-forwarding would be redundant. read_chat is also excluded: it can serve historical
3409
+ // transcript data locally and has its own inactive-session fallback in the CommandHandler.
3410
+ const MESH_FORWARDABLE_SESSION_COMMANDS = new Set([
3411
+ 'invoke_provider_script',
3412
+ 'resolve_action',
3413
+ 'set_mode',
3414
+ 'change_model',
3415
+ 'set_thought_level',
3416
+ ]);
3387
3417
  const READ_DEBUG_ENABLED = process.argv.includes('--dev') || process.env.ADHDEV_READ_DEBUG === '1';
3388
3418
 
3389
3419
  function normalizeCommandSource(source: string): CommandLogEntry['source'] {
@@ -3782,6 +3812,41 @@ export class DaemonCommandRouter {
3782
3812
  return nodes;
3783
3813
  }
3784
3814
 
3815
+ /**
3816
+ * Resolve the REMOTE worker daemonId that owns a given session, when the session
3817
+ * belongs to a mesh node hosted on a DIFFERENT daemon than this coordinator.
3818
+ *
3819
+ * The coordinator does not host remote-worker session instances in its own
3820
+ * instanceManager/sessionRegistry — only their cached mesh-node metadata. A
3821
+ * dashboard-issued session-scoped command (invoke_provider_script / resolve_action /
3822
+ * set_mode / …) lands on the coordinator with a targetSessionId the coordinator can't
3823
+ * find locally, and without forwarding it dies as "Live session not found". send_chat
3824
+ * happens to survive (its target resolves to the worker by another route), but the
3825
+ * controlbar commands do not — so the controlbar buttons appear to do nothing.
3826
+ *
3827
+ * Mirror the existing node-level remote-forward pattern (fast_forward_mesh_node etc.):
3828
+ * scan the cached inline-mesh nodes for the one whose active session matches the
3829
+ * targetSessionId, and return its daemonId when that daemonId is a remote daemon (i.e.
3830
+ * not this coordinator's own statusInstanceId). Returns undefined for a locally-hosted
3831
+ * session (no forward — execute locally as before) or when ownership can't be resolved.
3832
+ */
3833
+ public resolveRemoteMeshSessionOwnerDaemonId(sessionId: string): string | undefined {
3834
+ const trimmed = typeof sessionId === 'string' ? sessionId.trim() : '';
3835
+ if (!trimmed) return undefined;
3836
+ const selfDaemonId = this.deps.statusInstanceId;
3837
+ for (const node of this.getCachedInlineMeshNodes()) {
3838
+ const nodeSessions = readCachedInlineMeshActiveSessions(node);
3839
+ if (!nodeSessions.includes(trimmed)) continue;
3840
+ const nodeDaemonId = readMeshNodeDaemonId(readObjectRecord(node));
3841
+ if (!nodeDaemonId) return undefined;
3842
+ // Only forward to a genuinely remote daemon. When the owning node is this
3843
+ // coordinator itself (locally hosted worker), fall through to local handling.
3844
+ if (selfDaemonId && nodeDaemonId === selfDaemonId) return undefined;
3845
+ return nodeDaemonId;
3846
+ }
3847
+ return undefined;
3848
+ }
3849
+
3785
3850
  public getCachedInlineMesh(meshId: string, inlineMesh?: unknown): any | undefined {
3786
3851
  if (inlineMesh && typeof inlineMesh === 'object') {
3787
3852
  return this.warmInlineMeshCache(meshId, inlineMesh);
@@ -6115,6 +6180,38 @@ export class DaemonCommandRouter {
6115
6180
  * Returns null if not handled at this level → caller delegates to CommandHandler.
6116
6181
  */
6117
6182
  private async executeDaemonCommand(cmd: string, args: any): Promise<CommandRouterResult | null> {
6183
+ // [Z] Remote mesh worker session-scoped command forward.
6184
+ //
6185
+ // Session-scoped commands issued from the dashboard (the controlbar Model/Mode
6186
+ // selectors → invoke_provider_script, and modal approval → resolve_action, plus the
6187
+ // direct set_mode/change_model/set_thought_level mutations) target a session by
6188
+ // targetSessionId. When that session is a mesh worker hosted on a REMOTE daemon, this
6189
+ // coordinator never holds its live instance, so the CommandHandler delegation would
6190
+ // fail with "Live session not found". Forward to the owning worker daemon — the same
6191
+ // daemon that already executes send_chat for that session — so the controlbar acts on
6192
+ // the real worker. _meshDirectDispatch prevents re-forwarding once the call lands on
6193
+ // the owning daemon (it then handles the session locally). A locally-hosted worker (or
6194
+ // any session this coordinator owns) resolves to undefined below and falls through to
6195
+ // normal local handling — no regression.
6196
+ if (MESH_FORWARDABLE_SESSION_COMMANDS.has(cmd) && this.deps.dispatchMeshCommand && !args?._meshDirectDispatch) {
6197
+ const targetSessionId = readStringValue(args?.targetSessionId, args?.sessionId, args?.instanceId);
6198
+ if (targetSessionId) {
6199
+ const localInstance = this.deps.instanceManager?.getInstance(targetSessionId);
6200
+ const localRegistry = this.deps.sessionRegistry?.get?.(targetSessionId);
6201
+ if (!localInstance && !localRegistry) {
6202
+ const ownerDaemonId = this.resolveRemoteMeshSessionOwnerDaemonId(targetSessionId);
6203
+ if (ownerDaemonId) {
6204
+ LOG.info('Mesh', `[Mesh] Forwarding session-scoped '${cmd}' for remote worker session ${targetSessionId.split('_')[0]} → daemon ${ownerDaemonId.slice(0, 12)}`);
6205
+ const forwarded = await this.deps.dispatchMeshCommand(ownerDaemonId, cmd, {
6206
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
6207
+ _meshDirectDispatch: true,
6208
+ });
6209
+ return (forwarded ?? { success: false, error: 'no response from remote worker daemon' }) as CommandRouterResult;
6210
+ }
6211
+ }
6212
+ }
6213
+ }
6214
+
6118
6215
  switch (cmd) {
6119
6216
  // ─── CLI / ACP commands ───
6120
6217
  case 'mesh_forward_event': {