adhdev 0.9.60 → 0.9.62

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/index.js CHANGED
@@ -59187,7 +59187,7 @@ var init_adhdev_daemon = __esm({
59187
59187
  init_version();
59188
59188
  init_src();
59189
59189
  init_runtime_defaults();
59190
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.60" });
59190
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.62" });
59191
59191
  AdhdevDaemon = class _AdhdevDaemon {
59192
59192
  localHttpServer = null;
59193
59193
  localWss = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.9.60",
3
+ "version": "0.9.62",
4
4
  "description": "ADHDev — Agent Dashboard Hub for Dev. Remote-control AI coding agents from anywhere.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -129,6 +129,18 @@ var CloudTransport = class {
129
129
  if (!res.ok) throw new Error(`Git status failed: ${res.status}`);
130
130
  return res.json();
131
131
  }
132
+ async stop(daemonId, opts) {
133
+ const res = await fetch(
134
+ `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/stop`,
135
+ {
136
+ method: "POST",
137
+ headers: this.headers(),
138
+ body: JSON.stringify(opts)
139
+ }
140
+ );
141
+ if (!res.ok) throw new Error(`Stop failed: ${res.status}`);
142
+ return res.json();
143
+ }
132
144
  async launch(daemonId, opts) {
133
145
  const res = await fetch(
134
146
  `${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/launch`,
@@ -161,10 +173,14 @@ var FORMAT_PROP = {
161
173
  };
162
174
  var LIST_SESSIONS_TOOL = {
163
175
  name: "list_sessions",
164
- description: "List all currently connected IDE and CLI agent sessions on the local machine.",
176
+ description: "List all connected agent sessions. In cloud mode, fetches session state from each daemon (data is sourced from daemon WS status reports, up to 30s stale). Pass daemon_id to scope to a single daemon.",
165
177
  inputSchema: {
166
178
  type: "object",
167
179
  properties: {
180
+ daemon_id: {
181
+ type: "string",
182
+ description: "Daemon ID (cloud mode only). Omit to list sessions across all daemons."
183
+ },
168
184
  ...FORMAT_PROP
169
185
  },
170
186
  required: []
@@ -187,46 +203,122 @@ async function listSessions(transport, args = {}) {
187
203
  }, null, 2);
188
204
  }
189
205
  if (sessions.length === 0) return "No active sessions.";
190
- const lines2 = sessions.map((s) => {
206
+ const lines = sessions.map((s) => {
191
207
  const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
192
208
  if (s.label) parts.push(`label: ${s.label}`);
193
- if (s.agentStatus) parts.push(`status: ${s.agentStatus}`);
209
+ if (s.status ?? s.agentStatus) parts.push(`status: ${s.status ?? s.agentStatus}`);
194
210
  if (s.workspace) parts.push(`workspace: ${s.workspace}`);
195
211
  return parts.join(", ");
196
212
  });
197
213
  return `Sessions (${sessions.length}):
198
- ${lines2.join("\n")}`;
214
+ ${lines.join("\n")}`;
199
215
  }
200
- const data = await transport.listDaemons();
201
- const daemons = data?.daemons ?? data ?? [];
202
- if (asJson) {
203
- const sessions = [];
204
- for (const d of daemons) {
205
- for (const s of d.sessions ?? []) {
206
- sessions.push({
207
- daemon_id: d.id,
208
- id: s.id,
209
- type: s.providerType ?? "unknown",
210
- status: s.status ?? s.agentStatus ?? null,
211
- workspace: s.workspace ?? null
212
- });
213
- }
216
+ return listSessionsCloud(transport, args.daemon_id, asJson);
217
+ }
218
+ async function listSessionsCloud(transport, daemonId, asJson) {
219
+ const collected = [];
220
+ if (daemonId) {
221
+ const daemonStatus = await transport.getDaemonStatus(daemonId);
222
+ for (const s of daemonStatus?.sessions ?? []) {
223
+ collected.push({ daemonId, session: s });
214
224
  }
215
- return JSON.stringify({ sessions }, null, 2);
216
- }
217
- if (daemons.length === 0) return "No connected daemons.";
218
- const lines = [];
219
- for (const d of daemons) {
220
- const sessions = d.sessions ?? [];
221
- for (const s of sessions) {
222
- lines.push(
223
- `daemon: ${d.id}, session: ${s.id}, type: ${s.providerType ?? "unknown"}${s.agentStatus ? `, status: ${s.agentStatus}` : ""}`
225
+ } else {
226
+ const data = await transport.listDaemons();
227
+ const daemons = data?.daemons ?? [];
228
+ for (let i = 0; i < daemons.length; i += 5) {
229
+ await Promise.allSettled(
230
+ daemons.slice(i, i + 5).map(async (d) => {
231
+ try {
232
+ const daemonStatus = await transport.getDaemonStatus(d.id);
233
+ for (const s of daemonStatus?.sessions ?? []) {
234
+ collected.push({ daemonId: d.id, session: s });
235
+ }
236
+ } catch {
237
+ }
238
+ })
224
239
  );
225
240
  }
226
- if (sessions.length === 0) lines.push(`daemon: ${d.id} (no sessions)`);
227
241
  }
228
- return lines.length > 0 ? `Sessions:
229
- ${lines.join("\n")}` : "No active sessions.";
242
+ if (asJson) {
243
+ return JSON.stringify({
244
+ sessions: collected.map(({ daemonId: dId, session: s }) => ({
245
+ daemon_id: dId,
246
+ id: s.id,
247
+ type: s.providerType ?? "unknown",
248
+ status: s.status ?? null,
249
+ workspace: s.workspace ?? null
250
+ }))
251
+ }, null, 2);
252
+ }
253
+ if (collected.length === 0) return "No active sessions.";
254
+ const lines = collected.map(({ daemonId: dId, session: s }) => {
255
+ const parts = [
256
+ `daemon: ${dId}`,
257
+ `session: ${s.id}`,
258
+ `type: ${s.providerType ?? "unknown"}`
259
+ ];
260
+ if (s.status) parts.push(`status: ${s.status}`);
261
+ if (s.workspace) parts.push(`workspace: ${s.workspace}`);
262
+ return parts.join(", ");
263
+ });
264
+ return `Sessions (${collected.length}):
265
+ ${lines.join("\n")}`;
266
+ }
267
+
268
+ // src/tools/list-daemons.ts
269
+ var LIST_DAEMONS_TOOL = {
270
+ name: "list_daemons",
271
+ description: "List all connected daemons (machines running the ADHDev agent). Use this to discover daemon IDs before calling launch_session, git_status, or other tools that require daemon_id. In local mode returns the single standalone daemon info.",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ ...FORMAT_PROP
276
+ },
277
+ required: []
278
+ }
279
+ };
280
+ async function listDaemons(transport, args = {}) {
281
+ const asJson = args.format === "json";
282
+ if ("getStatus" in transport) {
283
+ const status = await transport.getStatus();
284
+ const daemon = {
285
+ id: status?.id ?? status?.instanceId ?? "standalone",
286
+ hostname: status?.hostname ?? status?.machine?.hostname ?? "localhost",
287
+ platform: status?.platform ?? status?.machine?.platform ?? "unknown",
288
+ version: status?.version ?? null,
289
+ sessions: (status?.sessions ?? []).length
290
+ };
291
+ if (asJson) return JSON.stringify({ daemons: [daemon] }, null, 2);
292
+ return `Daemons (1):
293
+ id: ${daemon.id}, hostname: ${daemon.hostname}, platform: ${daemon.platform}${daemon.version ? `, version: ${daemon.version}` : ""}, sessions: ${daemon.sessions}`;
294
+ }
295
+ const data = await transport.listDaemons();
296
+ const daemons = data?.daemons ?? [];
297
+ if (asJson) {
298
+ return JSON.stringify({
299
+ daemons: daemons.map((d) => ({
300
+ id: d.id,
301
+ hostname: d.hostname ?? null,
302
+ platform: d.platform ?? null,
303
+ nickname: d.nickname ?? null,
304
+ version: d.version ?? null,
305
+ p2p_available: d.p2p?.available ?? null,
306
+ cdp_connected: d.cdpConnected ?? null
307
+ }))
308
+ }, null, 2);
309
+ }
310
+ if (daemons.length === 0) return "No connected daemons.";
311
+ const lines = daemons.map((d) => {
312
+ const parts = [`id: ${d.id}`];
313
+ if (d.nickname) parts.push(`nickname: ${d.nickname}`);
314
+ if (d.hostname) parts.push(`hostname: ${d.hostname}`);
315
+ if (d.platform) parts.push(`platform: ${d.platform}`);
316
+ if (d.version) parts.push(`version: ${d.version}`);
317
+ if (d.p2p?.available != null) parts.push(`p2p: ${d.p2p.available ? "yes" : "no"}`);
318
+ return parts.join(", ");
319
+ });
320
+ return `Daemons (${daemons.length}):
321
+ ${lines.join("\n")}`;
230
322
  }
231
323
 
232
324
  // src/tools/read-chat.ts
@@ -380,17 +472,13 @@ async function approve(transport, args) {
380
472
  // src/tools/screenshot.ts
381
473
  var SCREENSHOT_TOOL = {
382
474
  name: "screenshot",
383
- description: "Capture a screenshot of the current IDE window. Returns the image.",
475
+ description: "Capture a screenshot of the current IDE window. Returns the image. Local mode only \u2014 screenshots require direct P2P access to the daemon and are not available in cloud mode.",
384
476
  inputSchema: {
385
477
  type: "object",
386
478
  properties: {
387
479
  session_id: {
388
480
  type: "string",
389
481
  description: "Target session ID. Omit to use the active session."
390
- },
391
- daemon_id: {
392
- type: "string",
393
- description: "Daemon ID (cloud mode only)."
394
482
  }
395
483
  },
396
484
  required: []
@@ -579,6 +667,57 @@ async function launchSession(transport, args) {
579
667
  return id ? `Session launched. id: ${id}, type: ${args.type}` : `Launched: ${JSON.stringify(result)}`;
580
668
  }
581
669
 
670
+ // src/tools/stop-session.ts
671
+ var STOP_SESSION_TOOL = {
672
+ name: "stop_session",
673
+ description: "Stop a running agent session. For CLI agents (hermes-cli, claude-cli, etc.) this sends a graceful stop signal. Use list_sessions to find the session_id.",
674
+ inputSchema: {
675
+ type: "object",
676
+ properties: {
677
+ session_id: {
678
+ type: "string",
679
+ description: "Session ID to stop (from list_sessions)."
680
+ },
681
+ daemon_id: {
682
+ type: "string",
683
+ description: "Daemon ID (cloud mode only, required)."
684
+ },
685
+ type: {
686
+ type: "string",
687
+ description: "Provider type (e.g. hermes-cli, claude-cli). Cloud mode: auto-resolved from session_id if omitted."
688
+ }
689
+ },
690
+ required: ["session_id"]
691
+ }
692
+ };
693
+ async function stopSession(transport, args) {
694
+ if ("command" in transport) {
695
+ const local = transport;
696
+ let resolvedType = args.type;
697
+ if (!resolvedType) {
698
+ const status = await local.getStatus();
699
+ const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
700
+ resolvedType = session?.providerType ?? session?.type;
701
+ }
702
+ if (!resolvedType) {
703
+ return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
704
+ }
705
+ const result2 = await local.command("stop_cli", {
706
+ targetSessionId: args.session_id,
707
+ cliType: resolvedType
708
+ });
709
+ if (result2?.success === false) return `Error: ${result2.error ?? "stop failed"}`;
710
+ return `Session ${args.session_id} stopped.`;
711
+ }
712
+ if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
713
+ const result = await transport.stop(args.daemon_id, {
714
+ id: args.session_id,
715
+ ...args.type ? { type: args.type } : {}
716
+ });
717
+ if (result?.success === false || result?.error) return `Error: ${result.error ?? "stop failed"}`;
718
+ return `Session ${args.session_id} stopped.`;
719
+ }
720
+
582
721
  // src/tools/check-pending.ts
583
722
  var CHECK_PENDING_TOOL = {
584
723
  name: "check_pending",
@@ -695,8 +834,10 @@ async function startMcpServer(opts) {
695
834
  }
696
835
  const isLocal = opts.mode === "local";
697
836
  const allTools = [
837
+ LIST_DAEMONS_TOOL,
698
838
  LIST_SESSIONS_TOOL,
699
839
  LAUNCH_SESSION_TOOL,
840
+ STOP_SESSION_TOOL,
700
841
  CHECK_PENDING_TOOL,
701
842
  READ_CHAT_TOOL,
702
843
  SEND_CHAT_TOOL,
@@ -705,7 +846,7 @@ async function startMcpServer(opts) {
705
846
  ...isLocal ? [SCREENSHOT_TOOL] : []
706
847
  ];
707
848
  const server = new import_server.Server(
708
- { name: "adhdev-mcp-server", version: "0.9.60" },
849
+ { name: "adhdev-mcp-server", version: "0.9.62" },
709
850
  { capabilities: { tools: {} } }
710
851
  );
711
852
  server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({ tools: allTools }));
@@ -714,8 +855,12 @@ async function startMcpServer(opts) {
714
855
  const a = args ?? {};
715
856
  try {
716
857
  switch (name) {
858
+ case "list_daemons": {
859
+ const text = await listDaemons(transport, { format: a.format });
860
+ return { content: [{ type: "text", text }] };
861
+ }
717
862
  case "list_sessions": {
718
- const text = await listSessions(transport, { format: a.format });
863
+ const text = await listSessions(transport, { format: a.format, daemon_id: a.daemon_id });
719
864
  return { content: [{ type: "text", text }] };
720
865
  }
721
866
  case "read_chat": {
@@ -732,7 +877,7 @@ async function startMcpServer(opts) {
732
877
  return { content: [{ type: "text", text }] };
733
878
  }
734
879
  case "screenshot": {
735
- const result = await screenshot(transport, { session_id: a.session_id, daemon_id: a.daemon_id });
880
+ const result = await screenshot(transport, { session_id: a.session_id });
736
881
  if (result.type === "image") {
737
882
  return {
738
883
  content: [{ type: "image", data: result.data, mimeType: result.mimeType }]
@@ -753,6 +898,14 @@ async function startMcpServer(opts) {
753
898
  });
754
899
  return { content: [{ type: "text", text }] };
755
900
  }
901
+ case "stop_session": {
902
+ const text = await stopSession(transport, {
903
+ session_id: a.session_id,
904
+ daemon_id: a.daemon_id,
905
+ type: a.type
906
+ });
907
+ return { content: [{ type: "text", text }] };
908
+ }
756
909
  case "check_pending": {
757
910
  const text = await checkPending(transport, { daemon_id: a.daemon_id, format: a.format });
758
911
  return { content: [{ type: "text", text }] };
@@ -823,8 +976,8 @@ Environment variables:
823
976
  ADHDEV_API_KEY API key (cloud mode)
824
977
  ADHDEV_PASSWORD Daemon password (local mode)
825
978
 
826
- Local mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status, screenshot
827
- Cloud mode tools: list_sessions, launch_session, check_pending, read_chat, send_chat, approve, git_status
979
+ Local mode tools: list_daemons, list_sessions, launch_session, stop_session, check_pending, read_chat, send_chat, approve, git_status, screenshot
980
+ Cloud mode tools: list_daemons, list_sessions, launch_session, stop_session, check_pending, read_chat, send_chat, approve, git_status
828
981
  `.trim());
829
982
  }
830
983
  startMcpServer(parseArgs(process.argv)).catch((err) => {