@adhdev/daemon-core 0.9.73 → 0.9.75

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.73",
3
+ "version": "0.9.75",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.73",
3
+ "version": "0.9.75",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -262,6 +262,17 @@ export class ProviderCliAdapter implements CliAdapter {
262
262
  return screenText;
263
263
  }
264
264
 
265
+ private getParseScreenText(screenText: string): string {
266
+ const currentSnapshot = normalizeScreenSnapshot(screenText);
267
+ const lastSnapshot = this.lastScreenSnapshot;
268
+ if (!lastSnapshot || lastSnapshot === currentSnapshot) return screenText;
269
+ // Terminal screen reads can miss a just-rendered completed Hermes box while
270
+ // the normalized snapshot captured during output still has it. Feed both
271
+ // views to provider parsers so flattened snapshot-only final bubbles do
272
+ // not disappear from read_chat/chat_tail.
273
+ return `${screenText}\n${lastSnapshot}`;
274
+ }
275
+
265
276
  private shouldReadTerminalScreenSnapshot(now: number): boolean {
266
277
  if (!this.lastScreenText) return true;
267
278
  return (now - this.lastScreenSnapshotReadAt) >= ProviderCliAdapter.SCREEN_SNAPSHOT_MIN_INTERVAL_MS;
@@ -1424,12 +1435,13 @@ export class ProviderCliAdapter implements CliAdapter {
1424
1435
  }
1425
1436
  try {
1426
1437
  const screenText = this.terminalScreen.getText();
1438
+ const parseScreenText = this.getParseScreenText(screenText);
1427
1439
  const tail = this.recentOutputBuffer.slice(-500);
1428
1440
  const input = buildCliParseInput({
1429
1441
  accumulatedBuffer: this.accumulatedBuffer,
1430
1442
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1431
1443
  recentOutputBuffer: this.recentOutputBuffer,
1432
- terminalScreenText: screenText,
1444
+ terminalScreenText: parseScreenText,
1433
1445
  baseMessages: [],
1434
1446
  partialResponse: this.responseBuffer,
1435
1447
  isWaitingForResponse: this.isWaitingForResponse,
@@ -1534,6 +1546,7 @@ export class ProviderCliAdapter implements CliAdapter {
1534
1546
  */
1535
1547
  getScriptParsedStatus(): any {
1536
1548
  const screenText = this.readTerminalScreenText();
1549
+ const parseScreenText = this.getParseScreenText(screenText);
1537
1550
  const cached = this.parsedStatusCache;
1538
1551
  if (
1539
1552
  cached
@@ -1541,7 +1554,7 @@ export class ProviderCliAdapter implements CliAdapter {
1541
1554
  && cached.currentTurnScope === this.currentTurnScope
1542
1555
  && cached.recentOutputBuffer === this.recentOutputBuffer
1543
1556
  && cached.accumulatedBuffer === this.accumulatedBuffer
1544
- && cached.screenText === screenText
1557
+ && cached.screenText === parseScreenText
1545
1558
  && cached.currentStatus === this.currentStatus
1546
1559
  && cached.activeModal === this.activeModal
1547
1560
  && cached.cliName === this.cliName
@@ -1580,7 +1593,7 @@ export class ProviderCliAdapter implements CliAdapter {
1580
1593
  currentTurnScope: this.currentTurnScope,
1581
1594
  recentOutputBuffer: this.recentOutputBuffer,
1582
1595
  accumulatedBuffer: this.accumulatedBuffer,
1583
- screenText,
1596
+ screenText: parseScreenText,
1584
1597
  currentStatus: this.currentStatus,
1585
1598
  activeModal: this.activeModal,
1586
1599
  cliName: this.cliName,
@@ -1598,7 +1611,7 @@ export class ProviderCliAdapter implements CliAdapter {
1598
1611
  accumulatedBuffer: this.accumulatedBuffer,
1599
1612
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1600
1613
  recentOutputBuffer: this.recentOutputBuffer,
1601
- terminalScreenText: this.terminalScreen.getText(),
1614
+ terminalScreenText: this.getParseScreenText(this.terminalScreen.getText()),
1602
1615
  baseMessages: [],
1603
1616
  partialResponse: this.responseBuffer,
1604
1617
  isWaitingForResponse: this.isWaitingForResponse,
@@ -149,6 +149,11 @@ function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
149
149
  addCandidate(resolve(dir, '../vendor/mcp-server/index.js'))
150
150
  addCandidate(resolve(dir, '../../vendor/mcp-server/index.js'))
151
151
  addCandidate(resolve(dir, '../../../vendor/mcp-server/index.js'))
152
+ // Source checkout/dev mode does not vendor the MCP server into daemon-standalone.
153
+ // Resolve the sibling workspace build directly so Repo Mesh auto-import still
154
+ // writes an absolute Node entrypoint instead of falling back to a PATH bin shim.
155
+ addCandidate(resolve(dir, '../../mcp-server/dist/index.js'))
156
+ addCandidate(resolve(dir, '../../../mcp-server/dist/index.js'))
152
157
  }
153
158
 
154
159
  addPackagedCandidates(process.argv[1])
@@ -38,6 +38,25 @@ import { buildSessionEntries } from '../status/builders.js';
38
38
  import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
39
39
  import { getSessionCompletionMarker } from '../status/snapshot.js';
40
40
  import { execNpmCommandSync, resolveCurrentGlobalInstallSurface, spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
41
+
42
+ type ReleaseChannel = 'stable' | 'preview';
43
+ const CHANNEL_NPM_TAG: Record<ReleaseChannel, 'latest' | 'next'> = { stable: 'latest', preview: 'next' };
44
+
45
+ function normalizeReleaseChannel(value: unknown): ReleaseChannel | null {
46
+ if (typeof value !== 'string') return null;
47
+ const normalized = value.trim().toLowerCase();
48
+ if (normalized === 'stable' || normalized === 'latest') return 'stable';
49
+ if (normalized === 'preview' || normalized === 'next') return 'preview';
50
+ return null;
51
+ }
52
+
53
+ function resolveUpgradeChannel(args: any): ReleaseChannel {
54
+ return normalizeReleaseChannel(args?.channel)
55
+ || normalizeReleaseChannel(args?.updatePolicy?.channel)
56
+ || normalizeReleaseChannel(args?.npmTag)
57
+ || normalizeReleaseChannel(loadConfig().updateChannel)
58
+ || 'stable';
59
+ }
41
60
  import * as fs from 'fs';
42
61
 
43
62
  // ─── Types ───
@@ -194,6 +213,10 @@ function summarizeSessionHostPruneResult(result: unknown): Record<string, unknow
194
213
 
195
214
  export class DaemonCommandRouter {
196
215
  private deps: CommandRouterDeps;
216
+ /** In-memory cache for cloud-originating meshes passed via inlineMesh.
217
+ * Allows the MCP server to query mesh data via get_mesh even when
218
+ * the mesh doesn't exist in the local meshes.json file. */
219
+ private inlineMeshCache = new Map<string, any>();
197
220
 
198
221
  constructor(deps: CommandRouterDeps) {
199
222
  this.deps = deps;
@@ -863,10 +886,12 @@ export class DaemonCommandRouter {
863
886
  || process.argv[1]?.includes('daemon-standalone');
864
887
  const pkgName = isStandalone ? '@adhdev/daemon-standalone' : 'adhdev';
865
888
  const npmSurface = resolveCurrentGlobalInstallSurface({ packageName: pkgName });
889
+ const channel = resolveUpgradeChannel(args);
890
+ const npmTag = CHANNEL_NPM_TAG[channel];
866
891
 
867
- // Check latest version
868
- const latest = String(execNpmCommandSync(['view', pkgName, 'version'], { encoding: 'utf-8', timeout: 10000 }, npmSurface)).trim();
869
- LOG.info('Upgrade', `Latest ${pkgName}: v${latest}`);
892
+ // Check channel-pinned dist-tag and resolve it to a concrete install version.
893
+ const latest = String(execNpmCommandSync(['view', `${pkgName}@${npmTag}`, 'version'], { encoding: 'utf-8', timeout: 10000 }, npmSurface)).trim();
894
+ LOG.info('Upgrade', `Latest ${pkgName}@${npmTag}: v${latest}`);
870
895
  let currentInstalled: string | null = null;
871
896
  try {
872
897
  const currentJson = String(execNpmCommandSync(['ls', '-g', pkgName, '--depth=0', '--json'], {
@@ -884,8 +909,8 @@ export class DaemonCommandRouter {
884
909
  ? this.deps.statusVersion.trim().replace(/^v/, '')
885
910
  : null;
886
911
  if (currentInstalled === latest && runningVersion === latest) {
887
- LOG.info('Upgrade', `Already on latest version v${latest}; skipping install`);
888
- return { success: true, upgraded: false, alreadyLatest: true, version: latest };
912
+ LOG.info('Upgrade', `Already on ${channel} channel version v${latest}; skipping install`);
913
+ return { success: true, upgraded: false, alreadyLatest: true, version: latest, channel, npmTag };
889
914
  }
890
915
  if (currentInstalled === latest && runningVersion && runningVersion !== latest) {
891
916
  LOG.info('Upgrade', `Installed package is v${latest}, but running daemon is v${runningVersion}; scheduling restart`);
@@ -899,7 +924,7 @@ export class DaemonCommandRouter {
899
924
  cwd: process.cwd(),
900
925
  sessionHostAppName: process.env.ADHDEV_SESSION_HOST_NAME || 'adhdev',
901
926
  });
902
- LOG.info('Upgrade', `Scheduled detached upgrade to v${latest}`);
927
+ LOG.info('Upgrade', `Scheduled detached ${channel} upgrade to v${latest}`);
903
928
 
904
929
  // Exit after the command response has been sent so the helper can replace the package cleanly.
905
930
  setTimeout(() => {
@@ -907,7 +932,7 @@ export class DaemonCommandRouter {
907
932
  process.exit(0);
908
933
  }, 3000);
909
934
 
910
- return { success: true, upgraded: true, version: latest, restarting: true };
935
+ return { success: true, upgraded: true, version: latest, restarting: true, channel, npmTag };
911
936
  } catch (e: any) {
912
937
  LOG.error('Upgrade', `Failed: ${e.message}`);
913
938
  return { success: false, error: e.message };
@@ -937,11 +962,12 @@ export class DaemonCommandRouter {
937
962
  try {
938
963
  const { getMesh } = await import('../config/mesh-config.js');
939
964
  const mesh = getMesh(meshId);
940
- if (!mesh) return { success: false, error: 'Mesh not found' };
941
- return { success: true, mesh };
942
- } catch (e: any) {
943
- return { success: false, error: e.message };
944
- }
965
+ if (mesh) return { success: true, mesh };
966
+ } catch { /* fall through to inline cache */ }
967
+ // Fallback: check in-memory cache for cloud-originating meshes
968
+ const cached = this.inlineMeshCache.get(meshId);
969
+ if (cached) return { success: true, mesh: cached };
970
+ return { success: false, error: 'Mesh not found' };
945
971
  }
946
972
 
947
973
  case 'create_mesh': {
@@ -1012,6 +1038,8 @@ export class DaemonCommandRouter {
1012
1038
  let mesh: any;
1013
1039
  if (args?.inlineMesh && typeof args.inlineMesh === 'object') {
1014
1040
  mesh = args.inlineMesh;
1041
+ // Cache cloud mesh so the MCP server can retrieve it via get_mesh
1042
+ this.inlineMeshCache.set(meshId, mesh);
1015
1043
  } else {
1016
1044
  const { getMesh } = await import('../config/mesh-config.js');
1017
1045
  mesh = getMesh(meshId);
@@ -1094,14 +1122,22 @@ export class DaemonCommandRouter {
1094
1122
  }
1095
1123
 
1096
1124
  // Merge ADHDev mesh server into existing config.
1125
+ // Pass full mesh data as env var so the MCP server can bootstrap
1126
+ // without depending on meshes.json or a running daemon.
1127
+ const mcpServerEntry: Record<string, any> = {
1128
+ command: coordinatorSetup.mcpServer.command,
1129
+ args: coordinatorSetup.mcpServer.args,
1130
+ };
1131
+ if (args?.inlineMesh) {
1132
+ mcpServerEntry.env = {
1133
+ ADHDEV_INLINE_MESH: JSON.stringify(mesh),
1134
+ };
1135
+ }
1097
1136
  const mcpConfig = {
1098
1137
  ...existingMcpConfig,
1099
1138
  mcpServers: {
1100
1139
  ...(existingMcpConfig.mcpServers || {}),
1101
- [coordinatorSetup.serverName]: {
1102
- command: coordinatorSetup.mcpServer.command,
1103
- args: coordinatorSetup.mcpServer.args,
1104
- },
1140
+ [coordinatorSetup.serverName]: mcpServerEntry,
1105
1141
  },
1106
1142
  };
1107
1143
  writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');
@@ -15,6 +15,7 @@ export type { SavedProviderSessionEntry } from './saved-sessions.js';
15
15
  export type { DaemonState } from './state-store.js';
16
16
 
17
17
  export type ProviderSourceMode = 'normal' | 'no-upstream';
18
+ export type ReleaseChannel = 'stable' | 'preview';
18
19
 
19
20
  export function resolveProviderSourceMode(
20
21
  providerSourceMode: unknown,
@@ -122,6 +123,9 @@ export interface ADHDevConfig {
122
123
  // Optional explicit provider override root (for example a local adhdev-providers checkout)
123
124
  providerDir?: string;
124
125
 
126
+ /** Preferred daemon update channel. Defaults to stable/latest. */
127
+ updateChannel?: ReleaseChannel;
128
+
125
129
  /**
126
130
  * Browser terminal sizing behavior for dashboard CLI panes.
127
131
  * Default `measured` keeps terminal size daemon-authoritative.
@@ -151,6 +155,7 @@ const DEFAULT_CONFIG: ADHDevConfig = {
151
155
  machineProviders: {},
152
156
  ideSettings: {},
153
157
  providerSourceMode: 'normal',
158
+ updateChannel: 'stable',
154
159
  terminalSizingMode: 'measured',
155
160
  };
156
161
 
@@ -228,6 +233,7 @@ function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: str
228
233
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
229
234
  providerSourceMode: resolveProviderSourceMode(parsed.providerSourceMode, parsed.disableUpstream),
230
235
  providerDir: asOptionalString(parsed.providerDir),
236
+ updateChannel: parsed.updateChannel === 'preview' ? 'preview' : 'stable',
231
237
  terminalSizingMode: parsed.terminalSizingMode === 'fit' ? 'fit' : 'measured',
232
238
  };
233
239
  }