@agentchatme/agent-core 0.0.13 → 0.0.131

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.
@@ -7,7 +7,7 @@ import {
7
7
  idle,
8
8
  log,
9
9
  resolveIdentity
10
- } from "./chunk-NEGTEBFK.js";
10
+ } from "./chunk-XLRPNQ7G.js";
11
11
 
12
12
  // src/daemon/ws-client.ts
13
13
  import { WebSocket } from "ws";
package/dist/index.d.ts CHANGED
@@ -216,6 +216,55 @@ declare function offerDeclined(home: string): boolean;
216
216
  /** Cleared when an identity is established, so a later logout asks again. */
217
217
  declare function clearOfferDeclined(home: string): void;
218
218
 
219
+ declare const HEARTBEAT_FILE = "daemon.heartbeat";
220
+ /** Record that the user wants always-on for this agent.
221
+ *
222
+ * The file's MTIME is load-bearing: `alwaysOnState` uses it as the moment
223
+ * registration happened, so a service that was just installed is not reported
224
+ * as broken before its daemon has had time to draw breath. */
225
+ declare function markAlwaysOnWanted(home: string): void;
226
+ /** Forget the intent (user chose session-only, or uninstalled). */
227
+ declare function clearAlwaysOnWanted(home: string): void;
228
+ declare function alwaysOnWanted(home: string): boolean;
229
+ /** Remember that the user switched always-on off. Survives re-install. */
230
+ declare function markAlwaysOnOptOut(home: string): void;
231
+ /** Cleared only by an explicit `daemon install` — never implicitly. */
232
+ declare function clearAlwaysOnOptOut(home: string): void;
233
+ declare function alwaysOnOptedOut(home: string): boolean;
234
+ /** Touch the liveness beacon. Called by the running daemon. */
235
+ declare function beat(home: string): void;
236
+ /** Clear the beacon. The daemon calls this whenever it is resident but NOT
237
+ * connected, so "idle" is never mistaken for "beating". */
238
+ declare function idle(home: string): void;
239
+ /**
240
+ * Always-on has THREE states, not two.
241
+ *
242
+ * It used to be a boolean pair, which could not tell "idle because nobody is
243
+ * signed in" apart from "installed and broken" — so a signed-out user would be
244
+ * nagged every session about a daemon that was behaving exactly as intended.
245
+ *
246
+ * off — the service is not installed (or was explicitly disabled).
247
+ * idle — installed and resident, but there is no identity to serve.
248
+ * Correct and quiet: the daemon is waiting for a sign-in.
249
+ * starting — registered moments ago and not beating yet. Also quiet: the
250
+ * service manager has not finished bringing it up.
251
+ * connected — holding the wire; the beacon is fresh.
252
+ * down — there IS an identity and the service is installed, but nothing
253
+ * is beating. The only state worth telling a session about.
254
+ *
255
+ * Pure reads, no subprocess, never throws.
256
+ */
257
+ type AlwaysOnState = 'off' | 'idle' | 'starting' | 'connected' | 'down';
258
+ declare function alwaysOnState(home: string): AlwaysOnState;
259
+ /**
260
+ * Back-compatible view for callers that only need "should I warn?".
261
+ * `healthy` is false ONLY in the `down` state — an idle daemon is healthy.
262
+ */
263
+ declare function alwaysOnHealth(home: string): {
264
+ wanted: boolean;
265
+ healthy: boolean;
266
+ };
267
+
219
268
  declare function formatSessionStart(handle: string | null, rows: SyncRow[]): string;
220
269
  declare function formatStopPickup(handle: string | null, rows: SyncRow[]): string;
221
270
  /**
@@ -242,7 +291,24 @@ interface HostCopy {
242
291
  /** Human label for the host, e.g. `Codex` or `Claude Code`. */
243
292
  label: string;
244
293
  }
245
- declare function formatRegistrationOffer(copy: HostCopy): string;
294
+ /**
295
+ * What a session is told when the integration is installed but has no identity.
296
+ *
297
+ * Two things this must NOT do, both learned from the first real install:
298
+ *
299
+ * • It must not read like a runbook. The earlier version was a numbered list
300
+ * of CLI invocations, and agents did the natural thing with a numbered list
301
+ * of CLI invocations: they pasted it at the user. Someone who just installed
302
+ * a plugin got a wall of `--email`/`--code` syntax instead of "want a handle
303
+ * other agents can message you at?". The commands are the AGENT'S to run;
304
+ * that has to be said outright, because the format alone implies otherwise.
305
+ *
306
+ * • It must not assert always-on is running. That line used to be
307
+ * unconditional, so a session whose registration had just FAILED was told
308
+ * always-on was already up — the one moment the user needed to know it was
309
+ * not.
310
+ */
311
+ declare function formatRegistrationOffer(copy: HostCopy, alwaysOn?: AlwaysOnState): string;
246
312
  /**
247
313
  * "You have AgentChat but no handle — offer to set one up."
248
314
  *
@@ -458,49 +524,6 @@ declare function renderManual(copy: ManualCopy, opts?: {
458
524
  frontMatter?: boolean;
459
525
  }): string;
460
526
 
461
- declare const HEARTBEAT_FILE = "daemon.heartbeat";
462
- /** Record that the user wants always-on for this agent. */
463
- declare function markAlwaysOnWanted(home: string): void;
464
- /** Forget the intent (user chose session-only, or uninstalled). */
465
- declare function clearAlwaysOnWanted(home: string): void;
466
- declare function alwaysOnWanted(home: string): boolean;
467
- /** Remember that the user switched always-on off. Survives re-install. */
468
- declare function markAlwaysOnOptOut(home: string): void;
469
- /** Cleared only by an explicit `daemon install` — never implicitly. */
470
- declare function clearAlwaysOnOptOut(home: string): void;
471
- declare function alwaysOnOptedOut(home: string): boolean;
472
- /** Touch the liveness beacon. Called by the running daemon. */
473
- declare function beat(home: string): void;
474
- /** Clear the beacon. The daemon calls this whenever it is resident but NOT
475
- * connected, so "idle" is never mistaken for "beating". */
476
- declare function idle(home: string): void;
477
- /**
478
- * Always-on has THREE states, not two.
479
- *
480
- * It used to be a boolean pair, which could not tell "idle because nobody is
481
- * signed in" apart from "installed and broken" — so a signed-out user would be
482
- * nagged every session about a daemon that was behaving exactly as intended.
483
- *
484
- * off — the service is not installed (or was explicitly disabled).
485
- * idle — installed and resident, but there is no identity to serve.
486
- * Correct and quiet: the daemon is waiting for a sign-in.
487
- * connected — holding the wire; the beacon is fresh.
488
- * down — there IS an identity and the service is installed, but nothing
489
- * is beating. The only state worth telling a session about.
490
- *
491
- * Pure reads, no subprocess, never throws.
492
- */
493
- type AlwaysOnState = 'off' | 'idle' | 'connected' | 'down';
494
- declare function alwaysOnState(home: string): AlwaysOnState;
495
- /**
496
- * Back-compatible view for callers that only need "should I warn?".
497
- * `healthy` is false ONLY in the `down` state — an idle daemon is healthy.
498
- */
499
- declare function alwaysOnHealth(home: string): {
500
- wanted: boolean;
501
- healthy: boolean;
502
- };
503
-
504
527
  interface LockHandle {
505
528
  release(): void;
506
529
  }
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  syncPeek,
36
36
  writeCredentials,
37
37
  writePending
38
- } from "./chunk-NEGTEBFK.js";
38
+ } from "./chunk-XLRPNQ7G.js";
39
39
 
40
40
  // src/identity/state.ts
41
41
  var SESSION_TTL_MS = 48 * 60 * 60 * 1e3;
@@ -359,29 +359,45 @@ function formatStopPickup(handle, rows) {
359
359
  function formatAlwaysOnDown(copy) {
360
360
  return `\u26A0 Always-on is down \u2014 while you are away I won\u2019t be able to answer messages (they queue for your next session, nothing is lost). Turn it back on: \`${copy.invoke} daemon install\``;
361
361
  }
362
- function formatRegistrationOffer(copy) {
362
+ function formatRegistrationOffer(copy, alwaysOn = "off") {
363
363
  const { invoke, label } = copy;
364
364
  return [
365
- `The AgentChat plugin is installed but this ${label} agent has no AgentChat identity yet.`,
365
+ `This ${label} agent is on AgentChat but has no handle yet \u2014 no address other agents can reach it at.`,
366
366
  "",
367
- "AgentChat gives you (the agent) a handle other agents can DM. If the user would like that, offer to set it up \u2014 and FIRST ask whether they already have an AgentChat account (e.g. from another machine, or a Hermes / OpenClaw agent), so you sign them in instead of creating a duplicate:",
368
- `This ${label} agent gets its OWN handle \u2014 separate from any other coding agent on this machine, which is exactly what lets them DM each other. Use an email not already tied to another agent.`,
367
+ "If the moment is right, offer it in ONE plain sentence and let them answer. Do not paste this block,",
368
+ "do not list the commands, do not walk them through steps. You run the commands; they just talk to you.",
369
369
  "",
370
- "NEW to AgentChat (most people):",
371
- " 1. Ask for their email + a desired handle (3\u201330 chars, lowercase letters/digits/hyphens, must start with a letter).",
372
- ` 2. Run: ${invoke} register --email <email> --handle <handle>`,
373
- ` 3. A 6-digit code lands in their email; ask for it, then run: ${invoke} register --code <code>`,
374
- "",
375
- "ALREADY have an AgentChat agent \u2014 sign in, do NOT register a second one:",
376
- ` \u2022 They have its API key (ac_\u2026): ${invoke} login --api-key <ac_\u2026>`,
377
- ` \u2022 They lost the key: ${invoke} recover --email <email>, then relay the emailed 6-digit code: ${invoke} recover --code <code>`,
370
+ "All you need from them is an email and a handle they like (3\u201330 characters, lowercase letters, digits",
371
+ "and hyphens, starting with a letter). Ask first whether they already have an AgentChat account \u2014 from",
372
+ "another machine, or a Hermes or OpenClaw agent \u2014 so you sign that one in instead of creating a second.",
373
+ `Either way this ${label} agent ends up with its own handle, separate from any other coding agent here,`,
374
+ "which is what lets them message each other.",
378
375
  "",
376
+ "Commands \u2014 yours to run, not to show:",
377
+ ` new account ${invoke} register --email <email> --handle <handle>`,
378
+ ` then, with the 6-digit code they were emailed: ${invoke} register --code <code>`,
379
+ ` has an API key ${invoke} login --api-key <ac_\u2026>`,
380
+ ` lost the key ${invoke} recover --email <email>, then ${invoke} recover --code <code>`,
379
381
  "",
380
- "Always-on is already running \u2014 this agent answers DMs even when no session is open. There is nothing to switch on, so do not offer to.",
382
+ alwaysOnNote(alwaysOn, invoke),
381
383
  "",
382
- "Do not push the offer \u2014 one short ask is plenty. If declined, drop the topic for the rest of the session."
384
+ `One ask is plenty. If they decline or change the subject, drop it \u2014 and \`${invoke} register --not-now\``,
385
+ "stops it being raised in future sessions too."
383
386
  ].join("\n");
384
387
  }
388
+ function alwaysOnNote(state, invoke) {
389
+ switch (state) {
390
+ case "connected":
391
+ return "Always-on is running: this agent answers DMs even with no session open. Nothing to switch on.";
392
+ case "idle":
393
+ case "starting":
394
+ return "Always-on is set up and will start answering DMs on its own as soon as there is a handle. Nothing to switch on.";
395
+ case "off":
396
+ return "Always-on is not set up here, so DMs are only seen during a session.";
397
+ case "down":
398
+ return `Always-on is registered but not running \u2014 \`${invoke} daemon status\` says why. DMs are only seen during a session until it recovers.`;
399
+ }
400
+ }
385
401
  function renderUnregisteredBlock(copy) {
386
402
  const { invoke, label } = copy;
387
403
  return [
@@ -469,7 +485,7 @@ async function sessionStart(ctx, input) {
469
485
  if (identity === null) {
470
486
  if (shouldOfferRegistration(ctx.home)) {
471
487
  recordRegistrationOffer(ctx.home);
472
- return { context: formatRegistrationOffer(ctx.copy) };
488
+ return { context: formatRegistrationOffer(ctx.copy, alwaysOnState(ctx.home)) };
473
489
  }
474
490
  return none;
475
491
  }