@agentchatme/agent-core 0.0.1312 → 0.0.13131

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
@@ -5,6 +5,7 @@ import {
5
5
  HEARTBEAT_FILE,
6
6
  VERSION,
7
7
  WireError,
8
+ absoluteUtc,
8
9
  acquireLeaderLock,
9
10
  alwaysOnHealth,
10
11
  alwaysOnOptedOut,
@@ -14,15 +15,18 @@ import {
14
15
  atomicWriteFile,
15
16
  beat,
16
17
  claimReply,
18
+ claimReplyBatch,
17
19
  clearAlwaysOnInstalledVersion,
18
20
  clearAlwaysOnOptOut,
19
21
  clearAlwaysOnWanted,
20
22
  clearCredentials,
23
+ clearForegroundTurn,
21
24
  clearPending,
22
25
  clearSessionActive,
23
26
  contextOf,
24
27
  credentialsPath,
25
28
  external_exports,
29
+ formatWhen,
26
30
  getMeLite,
27
31
  idle,
28
32
  lastDeliveryId,
@@ -30,19 +34,22 @@ import {
30
34
  markAlwaysOnInstalledVersion,
31
35
  markAlwaysOnOptOut,
32
36
  markAlwaysOnWanted,
37
+ markForegroundTurn,
33
38
  markSessionActive,
34
39
  pendingPath,
35
40
  readAlwaysOnInstalledVersion,
36
41
  readCredentials,
37
42
  readJsonFile,
38
43
  readPending,
44
+ relativeAge,
45
+ relativeWhen,
39
46
  resolveIdentity,
40
47
  statePath,
41
48
  syncAck,
42
49
  syncPeek,
43
50
  writeCredentials,
44
51
  writePending
45
- } from "./chunk-AGDJ4A6R.js";
52
+ } from "./chunk-M2X5WY7Q.js";
46
53
 
47
54
  // src/identity/state.ts
48
55
  var SESSION_TTL_MS = 48 * 60 * 60 * 1e3;
@@ -257,37 +264,6 @@ function stripAllBlocks(existing, start, end) {
257
264
  return text;
258
265
  }
259
266
 
260
- // src/util/when.ts
261
- var SEC = 1e3;
262
- var MIN = 60 * SEC;
263
- var HOUR = 60 * MIN;
264
- var DAY = 24 * HOUR;
265
- function relativeAge(ms) {
266
- if (ms < 45 * SEC) return "just now";
267
- if (ms < 90 * SEC) return "1 minute ago";
268
- if (ms < 45 * MIN) return `${Math.round(ms / MIN)} minutes ago`;
269
- if (ms < 90 * MIN) return "1 hour ago";
270
- if (ms < 22 * HOUR) return `${Math.round(ms / HOUR)} hours ago`;
271
- if (ms < 36 * HOUR) return "1 day ago";
272
- return `${Math.round(ms / DAY)} days ago`;
273
- }
274
- function absoluteUtc(t) {
275
- const iso = new Date(t).toISOString();
276
- return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
277
- }
278
- function relativeWhen(createdAt, now = Date.now()) {
279
- if (!createdAt) return "";
280
- const t = Date.parse(createdAt);
281
- if (Number.isNaN(t)) return "";
282
- return relativeAge(Math.max(0, now - t));
283
- }
284
- function formatWhen(createdAt, now = Date.now()) {
285
- if (!createdAt) return "at an unknown time";
286
- const t = Date.parse(createdAt);
287
- if (Number.isNaN(t)) return "at an unknown time";
288
- return `${relativeAge(Math.max(0, now - t))} (${absoluteUtc(t)})`;
289
- }
290
-
291
267
  // src/digest/summary.ts
292
268
  var SNIPPET_MAX = 140;
293
269
  function snippetOf(row) {
@@ -309,9 +285,11 @@ function digestConversations(rows, selfHandle = null) {
309
285
  existing.count += 1;
310
286
  if (!existing.senders.includes(sender)) existing.senders.push(sender);
311
287
  existing.latestSnippet = snippetOf(row);
288
+ existing.latestMessageId = row.id;
312
289
  existing.latestCreatedAt = row.created_at ?? existing.latestCreatedAt;
313
290
  existing.groupName = ctx.groupName ?? existing.groupName;
314
291
  existing.mentionsYou = existing.mentionsYou || mentionsSelf;
292
+ if (mentionsSelf) existing.mentionedMessageIds.push(row.id);
315
293
  } else {
316
294
  byConversation.set(row.conversation_id, {
317
295
  conversationId: row.conversation_id,
@@ -319,9 +297,11 @@ function digestConversations(rows, selfHandle = null) {
319
297
  senders: [sender],
320
298
  count: 1,
321
299
  latestSnippet: snippetOf(row),
300
+ latestMessageId: row.id,
322
301
  latestCreatedAt: row.created_at,
323
302
  groupName: ctx.groupName,
324
- mentionsYou: mentionsSelf
303
+ mentionsYou: mentionsSelf,
304
+ mentionedMessageIds: mentionsSelf ? [row.id] : []
325
305
  });
326
306
  }
327
307
  }
@@ -335,7 +315,9 @@ function digestLines(digests) {
335
315
  const age = relativeWhen(d.latestCreatedAt);
336
316
  const recency = age ? `, latest ${age}` : "";
337
317
  const mention = d.mentionsYou ? " \u2014 mentions you" : "";
338
- return `${i + 1}. ${who} (${count}, ${kind}${recency}${mention}), preview=${JSON.stringify(d.latestSnippet)}`;
318
+ const attentionIds = d.mentionedMessageIds.slice(-30);
319
+ const attention = attentionIds.length > 0 ? `, attention_message_ids=${JSON.stringify(attentionIds)}${d.mentionedMessageIds.length > attentionIds.length ? ` (+${d.mentionedMessageIds.length - attentionIds.length} older mentions in history)` : ""}` : "";
320
+ return `${i + 1}. ${who} (${count}, ${kind}${recency}${mention}), latest_message_id=${d.latestMessageId}${attention}, preview=${JSON.stringify(d.latestSnippet)}`;
339
321
  });
340
322
  }
341
323
  function formatSessionStart(handle, rows) {
@@ -350,7 +332,7 @@ function formatSessionStart(handle, rows) {
350
332
  "",
351
333
  ...digestLines(digests),
352
334
  "",
353
- "Triage per your AgentChat skill: read a conversation with agentchat_get_conversation before replying; reply only where an open request is addressed to you; finished conversations get silence, not acknowledgments. Mention anything the user should know about."
335
+ "Triage per your AgentChat skill: read a conversation with agentchat_get_conversation before replying, passing its latest_message_id as around_message_id and any listed attention_message_ids exactly; reply only where an open request is addressed to you; finished conversations get silence, not acknowledgments. Mention anything the user should know about."
354
336
  ].join("\n");
355
337
  }
356
338
  function formatStopPickup(handle, rows) {
@@ -364,7 +346,7 @@ function formatStopPickup(handle, rows) {
364
346
  "",
365
347
  ...digestLines(digests),
366
348
  "",
367
- "Handle these per your AgentChat skill, then finish. Reply via agentchat_send_message only where warranted \u2014 if nothing is actionable, simply end the turn (silence is a valid outcome)."
349
+ "Handle these per your AgentChat skill, opening each conversation with agentchat_get_conversation, its latest_message_id as around_message_id, and any listed attention_message_ids exactly. Reply via agentchat_send_message only where warranted \u2014 if nothing is actionable, simply end the turn (silence is a valid outcome)."
368
350
  ].join("\n");
369
351
  }
370
352
  function formatAlwaysOnDown(copy) {
@@ -451,6 +433,7 @@ function renderDeclinedBlock(copy) {
451
433
  var SESSION_START_PEEK_LIMIT = 100;
452
434
  var STOP_PEEK_LIMIT = 50;
453
435
  var DEFAULT_MAX_CONTINUATIONS = 5;
436
+ var FOREGROUND_TURN_TTL_SECONDS = 600;
454
437
  function hooksDisabled() {
455
438
  return process.env["AGENTCHAT_HOOKS_ENABLED"] === "0";
456
439
  }
@@ -479,12 +462,12 @@ function ackableRows(rows) {
479
462
  return usable;
480
463
  }
481
464
  async function claimContiguousPrefix(cfg, rows, holder) {
482
- const won = await Promise.all(rows.map((r) => claimReply(cfg, r.id, holder)));
483
- const prefix = [];
484
- for (let i = 0; i < rows.length; i++) {
485
- if (!won[i]) break;
486
- prefix.push(rows[i]);
487
- }
465
+ const claimed = await claimReplyBatch(
466
+ cfg,
467
+ rows.map((row) => row.id),
468
+ holder
469
+ );
470
+ const prefix = rows.slice(0, claimed);
488
471
  if (prefix.length < rows.length) {
489
472
  log.info(
490
473
  `coexistence: daemon owns ${rows.length - prefix.length} row(s); surfacing ${prefix.length}`
@@ -507,7 +490,6 @@ async function sessionStart(ctx, input) {
507
490
  return none;
508
491
  }
509
492
  const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
510
- await markSessionActive(cfg);
511
493
  const h = alwaysOnHealth(ctx.home);
512
494
  const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(ctx.copy) : null;
513
495
  const peeked = ackableRows(await syncPeek(cfg, { limit: SESSION_START_PEEK_LIMIT }));
@@ -531,9 +513,10 @@ async function userPrompt(ctx, input) {
531
513
  if (hooksDisabled()) return;
532
514
  const identity = resolveIdentity(ctx.home);
533
515
  if (identity === null) return;
516
+ const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
517
+ await markForegroundTurn(cfg, input.sessionId, FOREGROUND_TURN_TTL_SECONDS);
534
518
  const cursor = takePendingAck(ctx.home, input.sessionId);
535
519
  if (cursor === null) return;
536
- const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
537
520
  try {
538
521
  await syncAck(cfg, cursor);
539
522
  } catch (err) {
@@ -547,43 +530,64 @@ async function userPrompt(ctx, input) {
547
530
  async function stop(ctx, input) {
548
531
  const none = { reason: null, commit: async () => {
549
532
  } };
533
+ let cfg = null;
550
534
  try {
551
535
  if (hooksDisabled()) return none;
552
536
  const identity = resolveIdentity(ctx.home);
553
537
  if (identity === null) return none;
554
- const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
555
- await markSessionActive(cfg);
538
+ cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
539
+ await markForegroundTurn(cfg, input.sessionId, FOREGROUND_TURN_TTL_SECONDS);
556
540
  const cap = maxContinuations();
557
541
  if (getContinuations(ctx.home, input.sessionId) >= cap) {
558
542
  log.info(
559
543
  `stop hook: continuation cap (${cap}) reached for ${input.sessionId}; leaving inbox queued`
560
544
  );
545
+ await clearForegroundTurn(cfg, input.sessionId);
561
546
  return none;
562
547
  }
563
548
  const peeked = ackableRows(await syncPeek(cfg, { limit: STOP_PEEK_LIMIT }));
564
- if (peeked.length === 0) return none;
549
+ if (peeked.length === 0) {
550
+ await clearForegroundTurn(cfg, input.sessionId);
551
+ return none;
552
+ }
565
553
  const rows = await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`);
566
- if (rows.length === 0) return none;
554
+ if (rows.length === 0) {
555
+ await clearForegroundTurn(cfg, input.sessionId);
556
+ return none;
557
+ }
567
558
  recordContinuation(ctx.home, input.sessionId);
568
559
  const handle = await resolveHandle(cfg, identity.handle);
569
560
  const reason = formatStopPickup(handle, rows);
570
561
  const cursor = lastDeliveryId(rows);
562
+ const claimedCfg = cfg;
571
563
  return {
572
564
  reason,
573
565
  commit: async () => {
574
566
  if (cursor === null) return;
575
567
  try {
576
- await syncAck(cfg, cursor);
568
+ await syncAck(claimedCfg, cursor);
577
569
  } catch (err) {
578
570
  log.warn(`stop ack failed (messages stay queued): ${String(err)}`);
579
571
  }
580
572
  }
581
573
  };
582
574
  } catch (err) {
575
+ if (cfg !== null) await clearForegroundTurn(cfg, input.sessionId);
583
576
  log.warn(`stop hook degraded to no-op: ${String(err)}`);
584
577
  return none;
585
578
  }
586
579
  }
580
+ async function sessionEnd(ctx, input) {
581
+ try {
582
+ if (hooksDisabled()) return;
583
+ const identity = resolveIdentity(ctx.home);
584
+ if (identity === null) return;
585
+ const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
586
+ await clearForegroundTurn(cfg, input.sessionId);
587
+ } catch (err) {
588
+ log.warn(`session-end hook degraded to no-op: ${String(err)}`);
589
+ }
590
+ }
587
591
 
588
592
  // src/hooks/hook-input.ts
589
593
  async function readHookInput(stream = process.stdin) {
@@ -653,6 +657,14 @@ function createHookRunners(context, dialect) {
653
657
  } catch (err) {
654
658
  log.warn(`stop hook degraded to no-op: ${String(err)}`);
655
659
  }
660
+ },
661
+ async runSessionEnd() {
662
+ try {
663
+ const input = await readHookInput();
664
+ await sessionEnd(context(), input);
665
+ } catch (err) {
666
+ log.warn(`session-end hook degraded to no-op: ${String(err)}`);
667
+ }
656
668
  }
657
669
  };
658
670
  }
@@ -1696,10 +1708,12 @@ export {
1696
1708
  atomicWriteFile,
1697
1709
  beat,
1698
1710
  claimReply,
1711
+ claimReplyBatch,
1699
1712
  clearAlwaysOnInstalledVersion,
1700
1713
  clearAlwaysOnOptOut,
1701
1714
  clearAlwaysOnWanted,
1702
1715
  clearCredentials,
1716
+ clearForegroundTurn,
1703
1717
  clearOfferDeclined,
1704
1718
  clearPending,
1705
1719
  clearSessionActive,
@@ -1724,6 +1738,7 @@ export {
1724
1738
  markAlwaysOnInstalledVersion,
1725
1739
  markAlwaysOnOptOut,
1726
1740
  markAlwaysOnWanted,
1741
+ markForegroundTurn,
1727
1742
  markSessionActive,
1728
1743
  offerDeclined,
1729
1744
  pendingPath,
@@ -1751,6 +1766,7 @@ export {
1751
1766
  serviceDefinitionCurrent,
1752
1767
  serviceInstalled,
1753
1768
  serviceStatus,
1769
+ sessionEnd,
1754
1770
  sessionStart,
1755
1771
  setPendingAck,
1756
1772
  shouldOfferRegistration,