@clwnt/clawnet 0.7.12 → 0.7.14
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/package.json +1 -1
- package/src/service.ts +74 -11
- package/src/tools.ts +18 -0
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -73,7 +73,7 @@ async function reloadOnboardingMessage(): Promise<void> {
|
|
|
73
73
|
|
|
74
74
|
const SKILL_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
75
75
|
const SKILL_FILES = ["skill.json", "api-reference.md", "inbox-handler.md", "capabilities.json", "hook-template.txt", "tool-descriptions.json", "onboarding-message.txt", "inbox-protocol.md"];
|
|
76
|
-
export const PLUGIN_VERSION = "0.7.
|
|
76
|
+
export const PLUGIN_VERSION = "0.7.13"; // Reported to server via PATCH /me every 6h
|
|
77
77
|
|
|
78
78
|
function loadFreshConfig(api: any): ClawnetConfig {
|
|
79
79
|
const raw = api.runtime?.config?.loadConfig?.()?.plugins?.entries?.clawnet?.config ?? {};
|
|
@@ -141,6 +141,16 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
141
141
|
async function deliverBatch(accountId: string, agentId: string, messages: InboxMessage[]) {
|
|
142
142
|
if (messages.length === 0) return;
|
|
143
143
|
|
|
144
|
+
// Delivery lock — respect the cooldown even from flush/debounce paths
|
|
145
|
+
const lockUntil = deliveryLock.get(accountId);
|
|
146
|
+
if (lockUntil && new Date() < lockUntil) {
|
|
147
|
+
const existing = pendingMessages.get(accountId) ?? [];
|
|
148
|
+
const existingIds = new Set(existing.map((m) => m.id));
|
|
149
|
+
const fresh = messages.filter((m) => !existingIds.has(m.id));
|
|
150
|
+
pendingMessages.set(accountId, [...existing, ...fresh]);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
// Concurrency guard
|
|
145
155
|
if (accountBusy.has(accountId)) {
|
|
146
156
|
api.logger.info(`[clawnet] ${accountId}: LLM run in progress, requeueing ${messages.length} message(s)`);
|
|
@@ -363,19 +373,19 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
363
373
|
if (checkData.plugin_config) {
|
|
364
374
|
const pc = checkData.plugin_config;
|
|
365
375
|
let changed = false;
|
|
366
|
-
if (pc.poll_seconds !== cfg.pollEverySeconds) {
|
|
376
|
+
if (pc.poll_seconds !== undefined && pc.poll_seconds !== cfg.pollEverySeconds) {
|
|
367
377
|
cfg.pollEverySeconds = pc.poll_seconds;
|
|
368
378
|
changed = true;
|
|
369
379
|
}
|
|
370
|
-
if (pc.debounce_seconds !== cfg.debounceSeconds) {
|
|
380
|
+
if (pc.debounce_seconds !== undefined && pc.debounce_seconds !== cfg.debounceSeconds) {
|
|
371
381
|
cfg.debounceSeconds = pc.debounce_seconds;
|
|
372
382
|
changed = true;
|
|
373
383
|
}
|
|
374
|
-
if (pc.max_batch_size !== cfg.maxBatchSize) {
|
|
384
|
+
if (pc.max_batch_size !== undefined && pc.max_batch_size !== cfg.maxBatchSize) {
|
|
375
385
|
cfg.maxBatchSize = pc.max_batch_size;
|
|
376
386
|
changed = true;
|
|
377
387
|
}
|
|
378
|
-
if (pc.deliver_channel !== cfg.deliver.channel) {
|
|
388
|
+
if (pc.deliver_channel !== undefined && pc.deliver_channel !== cfg.deliver.channel) {
|
|
379
389
|
cfg.deliver.channel = pc.deliver_channel;
|
|
380
390
|
changed = true;
|
|
381
391
|
}
|
|
@@ -397,8 +407,9 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
397
407
|
const notifyCount = checkData.notify_count ?? (checkData.count + a2aDmCount + sentTaskUpdates);
|
|
398
408
|
|
|
399
409
|
if (checkData.count === 0) {
|
|
400
|
-
// Email inbox clear — release
|
|
401
|
-
|
|
410
|
+
// Email inbox clear — only release delivery lock if nothing else needs notification,
|
|
411
|
+
// otherwise tasks/sent-task-updates still need the lock for throttling.
|
|
412
|
+
if (notifyCount === 0) deliveryLock.delete(account.id);
|
|
402
413
|
return { a2aDmCount, sentTaskUpdates, notifyCount };
|
|
403
414
|
}
|
|
404
415
|
|
|
@@ -418,8 +429,8 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
418
429
|
state.lastInboxNonEmptyAt = new Date();
|
|
419
430
|
api.logger.info(`[clawnet] ${account.id}: ${checkData.count} message(s) waiting (${notifyCount} to notify)`);
|
|
420
431
|
|
|
421
|
-
// Fetch full messages
|
|
422
|
-
const inboxRes = await fetch(`${cfg.baseUrl}/inbox`, { headers });
|
|
432
|
+
// Fetch full messages — request max limit so mark-notified covers everything
|
|
433
|
+
const inboxRes = await fetch(`${cfg.baseUrl}/inbox?limit=200`, { headers });
|
|
423
434
|
if (!inboxRes.ok) {
|
|
424
435
|
throw new Error(`/inbox returned ${inboxRes.status}`);
|
|
425
436
|
}
|
|
@@ -439,6 +450,22 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
439
450
|
|
|
440
451
|
state.counters.messagesSeen += normalized.length;
|
|
441
452
|
|
|
453
|
+
// Mark ALL fetched emails as notified up front — the delivery batch is capped
|
|
454
|
+
// at maxBatchSize, but the nag timer should reset for every message the agent
|
|
455
|
+
// is being alerted about, not just the ones in the batch.
|
|
456
|
+
const allEmailIds = normalized.map((m) => m.id);
|
|
457
|
+
if (allEmailIds.length > 0) {
|
|
458
|
+
try {
|
|
459
|
+
await fetch(`${cfg.baseUrl}/inbox/mark-notified`, {
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers,
|
|
462
|
+
body: JSON.stringify({ message_ids: allEmailIds }),
|
|
463
|
+
});
|
|
464
|
+
} catch {
|
|
465
|
+
// Non-fatal — deliverBatch will still mark its batch after delivery
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
442
469
|
// Add to pending (dedup by ID) and schedule debounced flush
|
|
443
470
|
const existing = pendingMessages.get(account.id) ?? [];
|
|
444
471
|
const existingIds = new Set(existing.map((m) => m.id));
|
|
@@ -467,7 +494,7 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
467
494
|
jsonrpc: "2.0",
|
|
468
495
|
id: `poll-${Date.now()}`,
|
|
469
496
|
method: "tasks/list",
|
|
470
|
-
params: { status: "submitted", limit:
|
|
497
|
+
params: { status: "submitted", limit: 100 },
|
|
471
498
|
};
|
|
472
499
|
const res = await fetch(`${cfg.baseUrl}/a2a`, {
|
|
473
500
|
method: "POST",
|
|
@@ -505,6 +532,24 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
505
532
|
});
|
|
506
533
|
|
|
507
534
|
state.counters.messagesSeen += messages.length;
|
|
535
|
+
|
|
536
|
+
// Mark all fetched tasks as notified up front (same rationale as emails)
|
|
537
|
+
const allTaskIds = messages.map((m) => m.id);
|
|
538
|
+
if (allTaskIds.length > 0) {
|
|
539
|
+
try {
|
|
540
|
+
await fetch(`${cfg.baseUrl}/inbox/mark-notified`, {
|
|
541
|
+
method: "POST",
|
|
542
|
+
headers: {
|
|
543
|
+
Authorization: `Bearer ${resolvedToken}`,
|
|
544
|
+
"Content-Type": "application/json",
|
|
545
|
+
},
|
|
546
|
+
body: JSON.stringify({ task_ids: allTaskIds }),
|
|
547
|
+
});
|
|
548
|
+
} catch {
|
|
549
|
+
// Non-fatal
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
508
553
|
const existing = pendingMessages.get(account.id) ?? [];
|
|
509
554
|
const existingIds = new Set(existing.map((m) => m.id));
|
|
510
555
|
const freshTasks = messages.filter((m) => !existingIds.has(m.id));
|
|
@@ -525,7 +570,7 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
525
570
|
jsonrpc: "2.0",
|
|
526
571
|
id: `sent-poll-${Date.now()}`,
|
|
527
572
|
method: "tasks/list",
|
|
528
|
-
params: { role: "sender", status: "input-required,completed,failed", limit:
|
|
573
|
+
params: { role: "sender", status: "input-required,completed,failed", limit: 100 },
|
|
529
574
|
};
|
|
530
575
|
const res = await fetch(`${cfg.baseUrl}/a2a`, {
|
|
531
576
|
method: "POST",
|
|
@@ -560,6 +605,24 @@ export function createClawnetService(params: { api: any; cfg: ClawnetConfig }) {
|
|
|
560
605
|
});
|
|
561
606
|
|
|
562
607
|
state.counters.messagesSeen += messages.length;
|
|
608
|
+
|
|
609
|
+
// Mark all fetched sent-task updates as notified up front (same rationale as emails)
|
|
610
|
+
const allSentTaskIds = messages.map((m) => m.id);
|
|
611
|
+
if (allSentTaskIds.length > 0) {
|
|
612
|
+
try {
|
|
613
|
+
await fetch(`${cfg.baseUrl}/inbox/mark-notified`, {
|
|
614
|
+
method: "POST",
|
|
615
|
+
headers: {
|
|
616
|
+
Authorization: `Bearer ${resolvedToken}`,
|
|
617
|
+
"Content-Type": "application/json",
|
|
618
|
+
},
|
|
619
|
+
body: JSON.stringify({ task_ids: allSentTaskIds }),
|
|
620
|
+
});
|
|
621
|
+
} catch {
|
|
622
|
+
// Non-fatal
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
563
626
|
const existing = pendingMessages.get(account.id) ?? [];
|
|
564
627
|
const existingIds = new Set(existing.map((m) => m.id));
|
|
565
628
|
const freshUpdates = messages.filter((m) => !existingIds.has(m.id));
|
package/src/tools.ts
CHANGED
|
@@ -468,6 +468,24 @@ export function registerTools(api: any) {
|
|
|
468
468
|
},
|
|
469
469
|
}), { optional: true });
|
|
470
470
|
|
|
471
|
+
api.registerTool((ctx: { agentId?: string; sessionKey?: string }) => ({
|
|
472
|
+
name: "clawnet_email_bulk_status",
|
|
473
|
+
description: toolDesc("clawnet_email_bulk_status", "Set the status of multiple emails at once. Use to archive, read, or snooze many emails in one call instead of one at a time."),
|
|
474
|
+
parameters: {
|
|
475
|
+
type: "object",
|
|
476
|
+
properties: {
|
|
477
|
+
message_ids: { type: "array", items: { type: "string" }, description: "Array of message IDs (e.g. ['msg_abc123', 'msg_def456'])" },
|
|
478
|
+
status: { type: "string", enum: ["archived", "read", "snoozed", "new"], description: "New status to apply to all messages" },
|
|
479
|
+
},
|
|
480
|
+
required: ["message_ids", "status"],
|
|
481
|
+
},
|
|
482
|
+
async execute(_id: string, params: { message_ids: string[]; status: string }) {
|
|
483
|
+
const cfg = loadFreshConfig(api);
|
|
484
|
+
const result = await apiCall(cfg, "PATCH", `/messages/bulk/status`, { message_ids: params.message_ids, status: params.status }, ctx?.agentId, ctx?.sessionKey);
|
|
485
|
+
return textResult(result.data);
|
|
486
|
+
},
|
|
487
|
+
}), { optional: true });
|
|
488
|
+
|
|
471
489
|
api.registerTool((ctx: { agentId?: string; sessionKey?: string }) => ({
|
|
472
490
|
name: "clawnet_inbox_session",
|
|
473
491
|
description: toolDesc("clawnet_inbox_session", "Start an interactive inbox session. Returns your emails with assigned numbers and a triage protocol. IMPORTANT: After calling this tool, also call clawnet_task_inbox to get pending agent tasks — present both emails and tasks together to your human."),
|