@lelouchhe/webagent 0.8.0 → 0.10.0

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 (56) hide show
  1. package/README.md +43 -15
  2. package/config.toml +7 -27
  3. package/dist/index.html +21 -5
  4. package/dist/js/app.INIQQEGD.js +5 -0
  5. package/dist/js/chunk.3CLGCUHW.js +1 -0
  6. package/dist/js/{chunk.CT5WBNGZ.js → chunk.7WADDFJZ.js} +50 -49
  7. package/dist/js/chunk.AOTG3PL7.js +20 -0
  8. package/dist/js/{login.2WA6DTGM.js → login.WMURU4NI.js} +1 -1
  9. package/dist/js/viewer.RHZMFYWJ.js +1 -0
  10. package/dist/login.html +2 -2
  11. package/dist/share-viewer.html +6 -6
  12. package/dist/{styles.00etlpgs.css → styles.01aj0l37.css} +186 -4
  13. package/dist/sw.js +6 -6
  14. package/lib/agent-key.js +6 -0
  15. package/lib/attachment-dispatch.js +60 -31
  16. package/lib/attachment-interceptor.js +7 -7
  17. package/lib/attachment-labels.js +1 -1
  18. package/lib/attachments.js +69 -7
  19. package/lib/auth-middleware.js +11 -4
  20. package/lib/auth.js +2 -2
  21. package/lib/bridge.js +209 -90
  22. package/lib/client-registry.js +12 -12
  23. package/lib/config.js +2 -31
  24. package/lib/event-handler.js +166 -85
  25. package/lib/files/limits.js +15 -0
  26. package/lib/files/paths.js +155 -0
  27. package/lib/files/routes.js +232 -0
  28. package/lib/home-path.js +35 -0
  29. package/lib/http-status.js +1 -0
  30. package/lib/mcp/capability.js +74 -0
  31. package/lib/mcp/server.js +148 -0
  32. package/lib/mcp/task-history.js +245 -0
  33. package/lib/mcp/task-host.js +253 -0
  34. package/lib/mcp/tools.js +168 -0
  35. package/lib/mode-bucket.js +1 -1
  36. package/lib/push-service.js +33 -35
  37. package/lib/routes.js +1022 -475
  38. package/lib/server.js +84 -34
  39. package/lib/share/routes.js +97 -85
  40. package/lib/shared/task-reference.js +20 -0
  41. package/lib/sse-manager.js +8 -8
  42. package/lib/store.js +992 -284
  43. package/lib/task-collaboration.js +15 -0
  44. package/lib/task-manager.js +1409 -0
  45. package/lib/task-path.js +131 -0
  46. package/lib/{session-state.js → task-state.js} +90 -38
  47. package/lib/task-tree-lock.js +74 -0
  48. package/lib/{sessions-anchor.js → tasks-anchor.js} +8 -7
  49. package/lib/tokens.js +1 -1
  50. package/lib/types.js +2 -2
  51. package/package.json +8 -1
  52. package/dist/js/app.XBFXH37R.js +0 -2
  53. package/dist/js/chunk.UMQMOGWO.js +0 -1
  54. package/dist/js/viewer.CVWXSKJM.js +0 -1
  55. package/lib/session-manager.js +0 -613
  56. package/lib/title-service.js +0 -95
@@ -11,16 +11,16 @@ const VAPID_FILE = "vapid.json";
11
11
  const MAX_CONSECUTIVE_FAILURES = 5;
12
12
  /** Derive the push tag for an ACP event. Kept module-level so it can be
13
13
  * reused by the close-on-handle path without instantiating the service. */
14
- export function pushTagForEvent(sessionId, event) {
14
+ export function pushTagForEvent(taskId, event) {
15
15
  switch (event.type) {
16
16
  case "prompt_done":
17
- return `sess-${sessionId}-done`;
17
+ return `sess-${taskId}-done`;
18
18
  case "permission_request":
19
- return `sess-${sessionId}-perm-${event.eventId ?? "0"}`;
19
+ return `sess-${taskId}-perm-${event.eventId ?? "0"}`;
20
20
  case "bash_done":
21
- return `sess-${sessionId}-bash-${event.eventId ?? "0"}`;
21
+ return `sess-${taskId}-bash-${event.eventId ?? "0"}`;
22
22
  default:
23
- return `sess-${sessionId}-${event.type}`;
23
+ return `sess-${taskId}-${event.type}`;
24
24
  }
25
25
  }
26
26
  /**
@@ -97,8 +97,8 @@ export class PushService {
97
97
  // ---------------------------------------------------------------------------
98
98
  // Notification formatting
99
99
  // ---------------------------------------------------------------------------
100
- formatNotification(sessionId, sessionTitle, eventType, eventData, tag) {
101
- const title = sessionTitle ?? "WebAgent";
100
+ formatNotification(taskId, taskTitle, eventType, eventData, tag) {
101
+ const title = taskTitle ?? "WebAgent";
102
102
  let body;
103
103
  switch (eventType) {
104
104
  case "permission_request":
@@ -119,14 +119,14 @@ export class PushService {
119
119
  default:
120
120
  body = eventType;
121
121
  }
122
- return { kind: "notify", title, body, tag, data: { sessionId } };
122
+ return { kind: "notify", title, body, tag, data: { taskId } };
123
123
  }
124
124
  // ---------------------------------------------------------------------------
125
125
  // Endpoint mapping (transport-only as of Plan C Step 4)
126
126
  // ---------------------------------------------------------------------------
127
127
  /**
128
128
  * Set the push endpoint for a client. Identity-layer state (visibility,
129
- * active session) goes through ClientRegistry.setVisibility, not here.
129
+ * active task) goes through ClientRegistry.setVisibility, not here.
130
130
  */
131
131
  updateClient(clientId, patch) {
132
132
  const prev = this.clients.get(clientId) ?? emptyClientState();
@@ -170,8 +170,8 @@ export class PushService {
170
170
  }
171
171
  /**
172
172
  * Check if any client (across all endpoints) is visible and viewing the
173
- * given session. A client with no session set does not suppress any
174
- * session's push. Stale records (older than `visibilityTtlMs` since the
173
+ * given task. A client with no task set does not suppress any
174
+ * task's push. Stale records (older than `visibilityTtlMs` since the
175
175
  * last heartbeat refresh) are ignored — this is the server-side safety
176
176
  * net for iOS PWA suspension, where the client's `visible:false` POST may
177
177
  * never leave the device.
@@ -179,10 +179,10 @@ export class PushService {
179
179
  * Returns false unconditionally when global suppression is disabled via
180
180
  * the `globalVisibilitySuppression` option (kill switch).
181
181
  */
182
- isSessionVisibleToAnyClient(sessionId) {
182
+ isTaskVisibleToAnyClient(taskId) {
183
183
  if (!this.globalVisibilitySuppression)
184
184
  return false;
185
- return this.clientRegistry.isSessionVisibleToAnyClient(sessionId);
185
+ return this.clientRegistry.isTaskVisibleToAnyClient(taskId);
186
186
  }
187
187
  // ---------------------------------------------------------------------------
188
188
  // High-level: decide whether to push, and if so, send
@@ -195,9 +195,9 @@ export class PushService {
195
195
  /**
196
196
  * Check if this event should trigger a push notification.
197
197
  * Returns true if a notification should be sent (caller should then call sendToAll).
198
- * Global session visibility suppression happens inside sendToAll.
198
+ * Global task visibility suppression happens inside sendToAll.
199
199
  */
200
- maybeNotify(sessionId, sessionTitle, eventType, _eventData) {
200
+ maybeNotify(taskId, taskTitle, eventType, _eventData) {
201
201
  if (!PushService.NOTIFIABLE.has(eventType))
202
202
  return false;
203
203
  return true;
@@ -209,12 +209,12 @@ export class PushService {
209
209
  const allSubs = this.store.getAllSubscriptions();
210
210
  if (allSubs.length === 0)
211
211
  return;
212
- // Global visibility: if any client is viewing this session, suppress notify pushes.
212
+ // Global visibility: if any client is viewing this task, suppress notify pushes.
213
213
  // Close pushes are never suppressed — they're silent and are the mechanism
214
214
  // by which cross-device recall actually closes banners on the "losing" devices.
215
215
  if (notification.kind === "notify") {
216
- const targetSession = notification.data.sessionId;
217
- if (targetSession && this.isSessionVisibleToAnyClient(targetSession))
216
+ const targetTask = notification.data.taskId;
217
+ if (targetTask && this.isTaskVisibleToAnyClient(targetTask))
218
218
  return;
219
219
  }
220
220
  // Skip Apple endpoints for silent close pushes to preserve iOS PWA's
@@ -332,12 +332,12 @@ export class PushService {
332
332
  const title = msg.from_label ?? msg.from_ref ?? "Message";
333
333
  const body = msg.body.length > 140 ? msg.body.slice(0, 137) + "…" : msg.body;
334
334
  const tag = msg.dedup_key ? `dedup-${msg.to}-${msg.dedup_key}` : msg.id;
335
- // If this message targets a specific session, surface the sid in the
335
+ // If this message targets a specific task, surface the sid in the
336
336
  // push data so SW notificationclick can route the user there. Without
337
- // this, clicks fall back to "/" and land on whatever session was last
338
- // open — a confusing UX when multiple sessions get background pushes.
339
- const sessionId = msg.to.startsWith("session:")
340
- ? msg.to.slice("session:".length)
337
+ // this, clicks fall back to "/" and land on whatever task was last
338
+ // open — a confusing UX when multiple tasks get background pushes.
339
+ const taskId = msg.to.startsWith("task:")
340
+ ? msg.to.slice("task:".length)
341
341
  : undefined;
342
342
  // Observability signal #7 — sendForMessage entry.
343
343
  elog.info("sendForMessage", {
@@ -352,23 +352,21 @@ export class PushService {
352
352
  title,
353
353
  body,
354
354
  tag,
355
- data: sessionId
356
- ? { messageId: msg.id, sessionId }
357
- : { messageId: msg.id },
355
+ data: taskId ? { messageId: msg.id, taskId } : { messageId: msg.id },
358
356
  });
359
357
  return true;
360
358
  }
361
359
  /**
362
- * Send a push for an ACP session event (permission_request / prompt_done /
363
- * bash_done). Handles tag derivation, visibility suppression, and session-
360
+ * Send a push for an ACP task event (permission_request / prompt_done /
361
+ * bash_done). Handles tag derivation, visibility suppression, and task-
364
362
  * title lookup. Returns `true` if a push attempt was made.
365
363
  */
366
- async sendForEvent(sessionId, event) {
364
+ async sendForEvent(taskId, event) {
367
365
  if (!PushService.NOTIFIABLE.has(event.type))
368
366
  return false;
369
- const session = this.store.getSession(sessionId);
370
- const sessionTitle = session?.title ?? null;
371
- const tag = pushTagForEvent(sessionId, event);
367
+ const task = this.store.getTask(taskId);
368
+ const taskTitle = task?.title ?? null;
369
+ const tag = pushTagForEvent(taskId, event);
372
370
  const eventData = {};
373
371
  if (event.type === "permission_request" && event.title !== undefined) {
374
372
  eventData.description = event.title;
@@ -379,16 +377,16 @@ export class PushService {
379
377
  if (event.exitCode !== undefined)
380
378
  eventData.exitCode = event.exitCode;
381
379
  }
382
- const suppressed = this.isSessionVisibleToAnyClient(sessionId);
380
+ const suppressed = this.isTaskVisibleToAnyClient(taskId);
383
381
  const subs = this.store.getAllSubscriptions();
384
382
  elog.info("sendForEvent", {
385
- sess_id: sessionId.slice(0, 8),
383
+ sess_id: taskId.slice(0, 8),
386
384
  type: event.type,
387
385
  tag,
388
386
  endpoints: subs.length,
389
387
  suppressed_by_visibility: suppressed,
390
388
  });
391
- const notification = this.formatNotification(sessionId, sessionTitle, event.type, eventData, tag);
389
+ const notification = this.formatNotification(taskId, taskTitle, event.type, eventData, tag);
392
390
  await this.sendToAll(notification);
393
391
  return true;
394
392
  }