instar 0.8.8 → 0.8.10

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.
@@ -196,6 +196,30 @@ Strip the \`[telegram:N]\` prefix before interpreting the message. Respond natur
196
196
  else {
197
197
  result.skipped.push('CLAUDE.md: Private Viewer section already present');
198
198
  }
199
+ // Dashboard section
200
+ if (!content.includes('**Dashboard**') && !content.includes('/dashboard')) {
201
+ const section = `
202
+ **Dashboard** — Visual web interface for monitoring and managing sessions. Accessible from any device (phone, tablet, laptop) via tunnel.
203
+ - Local: \`http://localhost:${port}/dashboard\`
204
+ - Remote: When a tunnel is running, the dashboard is accessible at \`{tunnelUrl}/dashboard\`
205
+ - Authentication: Uses a 6-digit PIN (configured via \`dashboardPin\` in \`.instar/config.json\`) — no need to enter the full bearer token
206
+ - Features: Real-time terminal streaming of all running sessions, session management, model badges, mobile-responsive
207
+ - **Sharing the dashboard**: When the user wants to check on sessions from their phone, give them the tunnel URL + PIN. Check tunnel status: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/tunnel\`
208
+ `;
209
+ // Insert after Server Status or before Scripts section
210
+ const insertBefore = content.indexOf('**Scripts**');
211
+ if (insertBefore >= 0) {
212
+ content = content.slice(0, insertBefore) + section + '\n' + content.slice(insertBefore);
213
+ }
214
+ else {
215
+ content += '\n' + section;
216
+ }
217
+ patched = true;
218
+ result.upgraded.push('CLAUDE.md: added Dashboard section');
219
+ }
220
+ else {
221
+ result.skipped.push('CLAUDE.md: Dashboard section already present');
222
+ }
199
223
  if (patched) {
200
224
  try {
201
225
  fs.writeFileSync(claudeMdPath, content);
@@ -268,6 +268,13 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
268
268
  **Server Status** — Detailed runtime information beyond health checks.
269
269
  - Status: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/status\`
270
270
 
271
+ **Dashboard** — Visual web interface for monitoring and managing sessions. Accessible from any device (phone, tablet, laptop) via tunnel.
272
+ - Local: \`http://localhost:${port}/dashboard\`
273
+ - Remote: When a tunnel is running, the dashboard is accessible at \`{tunnelUrl}/dashboard\`
274
+ - Authentication: Uses a 6-digit PIN (configured via \`dashboardPin\` in \`.instar/config.json\`) — no need to enter the full bearer token
275
+ - Features: Real-time terminal streaming of all running sessions, session management, model badges, mobile-responsive
276
+ - **Sharing the dashboard**: When the user wants to check on sessions from their phone, give them the tunnel URL + PIN. Check tunnel status: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/tunnel\`
277
+
271
278
  **Scripts** — Reusable capabilities in \`.claude/scripts/\`.
272
279
 
273
280
  **Skills** — Reusable behavioral capabilities in \`.claude/skills/\`.
@@ -142,6 +142,7 @@ export class AgentServer {
142
142
  sessionManager: this.sessionManager,
143
143
  state: this.state,
144
144
  authToken: this.config.authToken,
145
+ instarDir: this.config.stateDir,
145
146
  });
146
147
  resolve();
147
148
  });
@@ -36,11 +36,13 @@ export declare class WebSocketManager {
36
36
  private sessionManager;
37
37
  private state;
38
38
  private authToken?;
39
+ private registryPath?;
39
40
  constructor(options: {
40
41
  server: HttpServer;
41
42
  sessionManager: SessionManager;
42
43
  state: StateManager;
43
44
  authToken?: string;
45
+ instarDir?: string;
44
46
  });
45
47
  private authenticate;
46
48
  private verifyToken;
@@ -50,6 +52,12 @@ export declare class WebSocketManager {
50
52
  * Uses diff-based approach: only sends new content since last capture.
51
53
  */
52
54
  private startStreaming;
55
+ /**
56
+ * Resolve display names by cross-referencing the topic-session registry.
57
+ * Maps tmux session names to their Telegram topic names.
58
+ */
59
+ private getTopicDisplayNames;
60
+ private buildSessionList;
53
61
  private sendSessionList;
54
62
  private broadcastSessionList;
55
63
  private clientId;
@@ -25,6 +25,8 @@
25
25
  */
26
26
  import { WebSocketServer, WebSocket } from 'ws';
27
27
  import { createHash, timingSafeEqual } from 'node:crypto';
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
28
30
  export class WebSocketManager {
29
31
  wss;
30
32
  clients = new Map();
@@ -35,10 +37,14 @@ export class WebSocketManager {
35
37
  sessionManager;
36
38
  state;
37
39
  authToken;
40
+ registryPath;
38
41
  constructor(options) {
39
42
  this.sessionManager = options.sessionManager;
40
43
  this.state = options.state;
41
44
  this.authToken = options.authToken;
45
+ if (options.instarDir) {
46
+ this.registryPath = path.join(options.instarDir, 'topic-session-registry.json');
47
+ }
42
48
  this.wss = new WebSocketServer({
43
49
  noServer: true,
44
50
  });
@@ -226,32 +232,52 @@ export class WebSocketManager {
226
232
  }, 500);
227
233
  this.streamInterval.unref();
228
234
  }
229
- sendSessionList(ws) {
235
+ /**
236
+ * Resolve display names by cross-referencing the topic-session registry.
237
+ * Maps tmux session names to their Telegram topic names.
238
+ */
239
+ getTopicDisplayNames() {
240
+ const map = new Map();
241
+ if (!this.registryPath)
242
+ return map;
243
+ try {
244
+ const data = JSON.parse(fs.readFileSync(this.registryPath, 'utf-8'));
245
+ const topicToSession = data.topicToSession || {};
246
+ const topicToName = data.topicToName || {};
247
+ // Build reverse map: tmux session name → topic display name
248
+ for (const [topicId, tmuxSession] of Object.entries(topicToSession)) {
249
+ const name = topicToName[topicId];
250
+ if (name) {
251
+ map.set(tmuxSession, name);
252
+ }
253
+ }
254
+ }
255
+ catch {
256
+ // Registry missing or corrupt — skip
257
+ }
258
+ return map;
259
+ }
260
+ buildSessionList() {
230
261
  const running = this.sessionManager.listRunningSessions();
231
- const sessions = running.map(s => ({
262
+ const displayNames = this.getTopicDisplayNames();
263
+ return running.map(s => ({
232
264
  id: s.id,
233
- name: s.name,
265
+ name: displayNames.get(s.tmuxSession) || s.name,
234
266
  tmuxSession: s.tmuxSession,
235
267
  status: s.status,
236
268
  startedAt: s.startedAt,
237
269
  jobSlug: s.jobSlug,
238
270
  model: s.model,
239
271
  }));
272
+ }
273
+ sendSessionList(ws) {
274
+ const sessions = this.buildSessionList();
240
275
  this.send(ws, { type: 'sessions', sessions });
241
276
  }
242
277
  broadcastSessionList() {
243
278
  if (this.clients.size === 0)
244
279
  return;
245
- const running = this.sessionManager.listRunningSessions();
246
- const sessions = running.map(s => ({
247
- id: s.id,
248
- name: s.name,
249
- tmuxSession: s.tmuxSession,
250
- status: s.status,
251
- startedAt: s.startedAt,
252
- jobSlug: s.jobSlug,
253
- model: s.model,
254
- }));
280
+ const sessions = this.buildSessionList();
255
281
  const msg = JSON.stringify({ type: 'sessions', sessions });
256
282
  for (const client of this.clients.values()) {
257
283
  if (client.ws.readyState === WebSocket.OPEN) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",