@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/README.md CHANGED
@@ -6,7 +6,7 @@ This is a **library, not a CLI**. It is consumed by the per-agent integrations,
6
6
 
7
7
  | Coding agent | What a user installs |
8
8
  |---|---|
9
- | Claude Code | [`agentchatme/agentchat-claude-code`](https://github.com/agentchatme/agentchat-claude-code) (plugin marketplace) |
9
+ | Claude Code | [`@agentchatme/claude-code`](https://www.npmjs.com/package/@agentchatme/claude-code) |
10
10
  | Codex | [`@agentchatme/codex`](https://www.npmjs.com/package/@agentchatme/codex) |
11
11
 
12
12
  ## The one rule
@@ -32,7 +32,14 @@ Neither bug came from sharing protocol code. Both came from a single command sur
32
32
  | Identity flows — register / login / recover / status / logout / doctor | How to render its anchor |
33
33
  | Session digest text | What JSON shape its hooks emit (`dialect`) |
34
34
  | Hook state machine (continuation cap, ack cursor) | How to spawn a headless turn of its runtime (`RuntimeAdapter`) |
35
- | Daemon — the loop, WS client, leader lock, service install | Its packaging and front door |
35
+ | Daemon — loop, WS client, canonical delivery prompt, service install | Its packaging and front door |
36
+
37
+ An unattended turn represents one bounded, same-conversation backlog (up to 30
38
+ durable deliveries). The newest message is the explicit focus, earlier pending
39
+ messages remain chronological context, and exact group-mention ids are surfaced
40
+ as attention. Both host adapters open the same compact, message-anchored
41
+ conversation context through the MCP server. Host adapters do not maintain
42
+ separate prompt dialects.
36
43
 
37
44
  The test for which column something belongs in: **if changing it requires a
38
45
  matching change on the server, it lives here; if changing it requires reading
@@ -56,6 +63,12 @@ Injection **is** delivery, and nothing is acked until a session proves it is rea
56
63
 
57
64
  Rows without an ackable `delivery_id` are never surfaced — they could only re-inject forever.
58
65
 
66
+ Foreground/daemon ownership is atomic at the server. `userPrompt()` leases one
67
+ concrete host session before a model turn; `stop()` renews it for a continuation
68
+ or clears it when the session becomes idle; `sessionEnd()` clears only that
69
+ session. The daemon's claim operation prunes expired leases and claims the
70
+ message in one Redis script, so the old check-then-wait race no longer exists.
71
+
59
72
  ## Usage
60
73
 
61
74
  An integration describes itself once and gets the flows back:
@@ -75,7 +88,7 @@ const profile: HostProfile = {
75
88
  }
76
89
 
77
90
  const { runRegister, runStatus, runLogout, runDoctor } = createIdentityCommands(profile)
78
- const { runSessionStart, runStop } = createHookRunners(
91
+ const { runSessionStart, runUserPrompt, runStop, runSessionEnd } = createHookRunners(
79
92
  () => ({ home: profile.home(), copy: { invoke: profile.invocation(), label: profile.label } }),
80
93
  myHostsDialect, // how THIS host wants hook JSON shaped
81
94
  )
@@ -93,10 +106,13 @@ import { runDaemon } from '@agentchatme/agent-core/daemon'
93
106
  await runDaemon({ home: profile.home(), adapter: new MyRuntimeAdapter(...) })
94
107
  ```
95
108
 
96
- The daemon gives each incoming message its own runtime turn. Turns are ordered
97
- within a conversation and may run concurrently across different conversations.
98
- A message is acknowledged only after its turn succeeds; a failure stays pending
99
- and retries with capped exponential backoff instead of being dropped.
109
+ The daemon coalesces a burst or reconnect backlog from one conversation into one
110
+ bounded runtime turn, focused on its newest message. A later arrival cannot join
111
+ a batch once its turn has started. Turns remain ordered within a conversation
112
+ and may run concurrently across different conversations. Every delivery in the
113
+ batch is acknowledged only after the shared turn succeeds; a failure retries
114
+ the same frozen batch with capped exponential backoff instead of dropping or
115
+ partially acknowledging it.
100
116
 
101
117
  ## Development
102
118
 
@@ -25,7 +25,7 @@ var log = {
25
25
  };
26
26
 
27
27
  // src/version.ts
28
- var VERSION = "0.0.1312";
28
+ var VERSION = "0.0.13131";
29
29
 
30
30
  // src/client-identity.ts
31
31
  var CODING_AGENTS_CLIENT_IDENTITY = {
@@ -37,6 +37,37 @@ var CODING_AGENTS_CLIENT_HEADERS = {
37
37
  "X-AgentChat-Client-Version": CODING_AGENTS_CLIENT_IDENTITY.version
38
38
  };
39
39
 
40
+ // src/util/when.ts
41
+ var SEC = 1e3;
42
+ var MIN = 60 * SEC;
43
+ var HOUR = 60 * MIN;
44
+ var DAY = 24 * HOUR;
45
+ function relativeAge(ms) {
46
+ if (ms < 45 * SEC) return "just now";
47
+ if (ms < 90 * SEC) return "1 minute ago";
48
+ if (ms < 45 * MIN) return `${Math.round(ms / MIN)} minutes ago`;
49
+ if (ms < 90 * MIN) return "1 hour ago";
50
+ if (ms < 22 * HOUR) return `${Math.round(ms / HOUR)} hours ago`;
51
+ if (ms < 36 * HOUR) return "1 day ago";
52
+ return `${Math.round(ms / DAY)} days ago`;
53
+ }
54
+ function absoluteUtc(t) {
55
+ const iso = new Date(t).toISOString();
56
+ return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
57
+ }
58
+ function relativeWhen(createdAt, now = Date.now()) {
59
+ if (!createdAt) return "";
60
+ const t = Date.parse(createdAt);
61
+ if (Number.isNaN(t)) return "";
62
+ return relativeAge(Math.max(0, now - t));
63
+ }
64
+ function formatWhen(createdAt, now = Date.now()) {
65
+ if (!createdAt) return "at an unknown time";
66
+ const t = Date.parse(createdAt);
67
+ if (Number.isNaN(t)) return "at an unknown time";
68
+ return `${relativeAge(Math.max(0, now - t))} (${absoluteUtc(t)})`;
69
+ }
70
+
40
71
  // src/daemon/leader-lock.ts
41
72
  import * as fs from "fs";
42
73
  import * as path from "path";
@@ -4282,8 +4313,11 @@ var SyncRowSchema = external_exports.object({
4282
4313
  // fallback against a future server-side rename).
4283
4314
  sender: external_exports.string().optional(),
4284
4315
  sender_handle: external_exports.string().optional(),
4316
+ seq: external_exports.number().optional(),
4285
4317
  type: external_exports.string().optional(),
4286
4318
  content: external_exports.record(external_exports.unknown()).optional(),
4319
+ metadata: external_exports.record(external_exports.unknown()).optional(),
4320
+ status: external_exports.string().optional(),
4287
4321
  created_at: external_exports.string().optional()
4288
4322
  }).passthrough();
4289
4323
  function contextOf(row) {
@@ -4378,6 +4412,22 @@ async function clearSessionActive(cfg) {
4378
4412
  } catch {
4379
4413
  }
4380
4414
  }
4415
+ async function markForegroundTurn(cfg, sessionId, ttlSeconds) {
4416
+ try {
4417
+ await request(cfg, "PUT", "/v1/reply/active", {
4418
+ session_id: sessionId,
4419
+ ttl_seconds: ttlSeconds
4420
+ });
4421
+ } catch (err) {
4422
+ log.warn(`foreground-turn mark failed (ignored): ${String(err)}`);
4423
+ }
4424
+ }
4425
+ async function clearForegroundTurn(cfg, sessionId) {
4426
+ try {
4427
+ await request(cfg, "DELETE", "/v1/reply/active", { session_id: sessionId });
4428
+ } catch {
4429
+ }
4430
+ }
4381
4431
  async function claimReply(cfg, messageId, holder) {
4382
4432
  try {
4383
4433
  const data = await request(cfg, "POST", "/v1/reply/claim", { message_id: messageId, holder });
@@ -4388,6 +4438,28 @@ async function claimReply(cfg, messageId, holder) {
4388
4438
  return true;
4389
4439
  }
4390
4440
  }
4441
+ async function claimReplyBatch(cfg, messageIds, holder) {
4442
+ if (messageIds.length === 0) return 0;
4443
+ try {
4444
+ const data = await request(cfg, "POST", "/v1/reply/claim-batch", {
4445
+ message_ids: messageIds,
4446
+ holder
4447
+ });
4448
+ const parsed = external_exports.object({ claimed_count: external_exports.number().int().min(0).max(messageIds.length) }).passthrough().safeParse(data);
4449
+ return parsed.success ? parsed.data.claimed_count : messageIds.length;
4450
+ } catch (err) {
4451
+ if (!/AgentChat API (404|405)\b/.test(String(err))) {
4452
+ log.warn(`reply-batch-claim failed (surfacing all): ${String(err)}`);
4453
+ return messageIds.length;
4454
+ }
4455
+ }
4456
+ let claimed = 0;
4457
+ for (const messageId of messageIds) {
4458
+ if (!await claimReply(cfg, messageId, holder)) break;
4459
+ claimed += 1;
4460
+ }
4461
+ return claimed;
4462
+ }
4391
4463
  function lastDeliveryId(rows) {
4392
4464
  for (let i = rows.length - 1; i >= 0; i--) {
4393
4465
  const id = rows[i]?.delivery_id;
@@ -4497,6 +4569,10 @@ export {
4497
4569
  VERSION,
4498
4570
  CODING_AGENTS_CLIENT_IDENTITY,
4499
4571
  CODING_AGENTS_CLIENT_HEADERS,
4572
+ relativeAge,
4573
+ absoluteUtc,
4574
+ relativeWhen,
4575
+ formatWhen,
4500
4576
  acquireLeaderLock,
4501
4577
  atomicWriteFile,
4502
4578
  atomicCopyFile,
@@ -4519,7 +4595,10 @@ export {
4519
4595
  getMeLite,
4520
4596
  markSessionActive,
4521
4597
  clearSessionActive,
4598
+ markForegroundTurn,
4599
+ clearForegroundTurn,
4522
4600
  claimReply,
4601
+ claimReplyBatch,
4523
4602
  lastDeliveryId,
4524
4603
  HEARTBEAT_FILE,
4525
4604
  markAlwaysOnWanted,
@@ -4536,4 +4615,4 @@ export {
4536
4615
  alwaysOnState,
4537
4616
  alwaysOnHealth
4538
4617
  };
4539
- //# sourceMappingURL=chunk-AGDJ4A6R.js.map
4618
+ //# sourceMappingURL=chunk-M2X5WY7Q.js.map