@lelouchhe/webagent 0.9.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.
- package/README.md +39 -14
- package/config.toml +7 -27
- package/dist/index.html +4 -4
- package/dist/js/app.INIQQEGD.js +5 -0
- package/dist/js/{chunk.S5LRNRJI.js → chunk.7WADDFJZ.js} +27 -27
- package/dist/js/viewer.RHZMFYWJ.js +1 -0
- package/dist/login.html +1 -1
- package/dist/share-viewer.html +5 -5
- package/dist/{styles.00nlhhf3.css → styles.01aj0l37.css} +19 -2
- package/dist/sw.js +6 -6
- package/lib/attachment-dispatch.js +60 -31
- package/lib/attachment-interceptor.js +7 -7
- package/lib/attachment-labels.js +1 -1
- package/lib/attachments.js +25 -0
- package/lib/auth-middleware.js +2 -2
- package/lib/auth.js +2 -2
- package/lib/bridge.js +109 -83
- package/lib/client-registry.js +12 -12
- package/lib/config.js +2 -31
- package/lib/event-handler.js +143 -90
- package/lib/files/routes.js +1 -1
- package/lib/mcp/capability.js +74 -0
- package/lib/mcp/server.js +148 -0
- package/lib/mcp/task-history.js +245 -0
- package/lib/mcp/task-host.js +253 -0
- package/lib/mcp/tools.js +168 -0
- package/lib/mode-bucket.js +1 -1
- package/lib/push-service.js +33 -35
- package/lib/routes.js +947 -489
- package/lib/server.js +64 -16
- package/lib/share/routes.js +88 -88
- package/lib/shared/task-reference.js +20 -0
- package/lib/sse-manager.js +8 -8
- package/lib/store.js +941 -314
- package/lib/task-collaboration.js +15 -0
- package/lib/task-manager.js +1409 -0
- package/lib/task-path.js +131 -0
- package/lib/{session-state.js → task-state.js} +64 -41
- package/lib/task-tree-lock.js +74 -0
- package/lib/{sessions-anchor.js → tasks-anchor.js} +8 -7
- package/lib/tokens.js +1 -1
- package/lib/types.js +2 -2
- package/package.json +7 -1
- package/dist/js/app.QC7IRDTP.js +0 -5
- package/dist/js/viewer.GP5VXAUY.js +0 -1
- package/lib/session-manager.js +0 -638
- package/lib/title-service.js +0 -95
package/lib/push-service.js
CHANGED
|
@@ -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(
|
|
14
|
+
export function pushTagForEvent(taskId, event) {
|
|
15
15
|
switch (event.type) {
|
|
16
16
|
case "prompt_done":
|
|
17
|
-
return `sess-${
|
|
17
|
+
return `sess-${taskId}-done`;
|
|
18
18
|
case "permission_request":
|
|
19
|
-
return `sess-${
|
|
19
|
+
return `sess-${taskId}-perm-${event.eventId ?? "0"}`;
|
|
20
20
|
case "bash_done":
|
|
21
|
-
return `sess-${
|
|
21
|
+
return `sess-${taskId}-bash-${event.eventId ?? "0"}`;
|
|
22
22
|
default:
|
|
23
|
-
return `sess-${
|
|
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(
|
|
101
|
-
const title =
|
|
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: {
|
|
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
|
|
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
|
|
174
|
-
*
|
|
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
|
-
|
|
182
|
+
isTaskVisibleToAnyClient(taskId) {
|
|
183
183
|
if (!this.globalVisibilitySuppression)
|
|
184
184
|
return false;
|
|
185
|
-
return this.clientRegistry.
|
|
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
|
|
198
|
+
* Global task visibility suppression happens inside sendToAll.
|
|
199
199
|
*/
|
|
200
|
-
maybeNotify(
|
|
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
|
|
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
|
|
217
|
-
if (
|
|
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
|
|
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
|
|
338
|
-
// open — a confusing UX when multiple
|
|
339
|
-
const
|
|
340
|
-
? msg.to.slice("
|
|
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:
|
|
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
|
|
363
|
-
* bash_done). Handles tag derivation, visibility suppression, and
|
|
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(
|
|
364
|
+
async sendForEvent(taskId, event) {
|
|
367
365
|
if (!PushService.NOTIFIABLE.has(event.type))
|
|
368
366
|
return false;
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
const tag = pushTagForEvent(
|
|
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.
|
|
380
|
+
const suppressed = this.isTaskVisibleToAnyClient(taskId);
|
|
383
381
|
const subs = this.store.getAllSubscriptions();
|
|
384
382
|
elog.info("sendForEvent", {
|
|
385
|
-
sess_id:
|
|
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(
|
|
389
|
+
const notification = this.formatNotification(taskId, taskTitle, event.type, eventData, tag);
|
|
392
390
|
await this.sendToAll(notification);
|
|
393
391
|
return true;
|
|
394
392
|
}
|