@agentchatme/agent-core 0.0.11 → 0.0.12
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.d.ts +33 -1
- package/dist/index.js +158 -95
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -168,6 +168,7 @@ declare const StateSchema: z.ZodObject<{
|
|
|
168
168
|
pending_ack?: string | undefined;
|
|
169
169
|
}>>>;
|
|
170
170
|
last_offer_at: z.ZodOptional<z.ZodString>;
|
|
171
|
+
offer_declined_at: z.ZodOptional<z.ZodString>;
|
|
171
172
|
}, "strip", z.ZodTypeAny, {
|
|
172
173
|
sessions: Record<string, {
|
|
173
174
|
continuations: number;
|
|
@@ -175,6 +176,7 @@ declare const StateSchema: z.ZodObject<{
|
|
|
175
176
|
pending_ack?: string | undefined;
|
|
176
177
|
}>;
|
|
177
178
|
last_offer_at?: string | undefined;
|
|
179
|
+
offer_declined_at?: string | undefined;
|
|
178
180
|
}, {
|
|
179
181
|
sessions?: Record<string, {
|
|
180
182
|
continuations: number;
|
|
@@ -182,6 +184,7 @@ declare const StateSchema: z.ZodObject<{
|
|
|
182
184
|
pending_ack?: string | undefined;
|
|
183
185
|
}> | undefined;
|
|
184
186
|
last_offer_at?: string | undefined;
|
|
187
|
+
offer_declined_at?: string | undefined;
|
|
185
188
|
}>;
|
|
186
189
|
type HookState = z.infer<typeof StateSchema>;
|
|
187
190
|
declare function readState(home: string): HookState;
|
|
@@ -199,6 +202,19 @@ declare function setPendingAck(home: string, sessionKey: string, cursor: string,
|
|
|
199
202
|
declare function takePendingAck(home: string, sessionKey: string, now?: Date): string | null;
|
|
200
203
|
declare function shouldOfferRegistration(home: string, now?: Date): boolean;
|
|
201
204
|
declare function recordRegistrationOffer(home: string, now?: Date): void;
|
|
205
|
+
/**
|
|
206
|
+
* The user said "not now".
|
|
207
|
+
*
|
|
208
|
+
* A hook-injected offer could rely on a 24-hour cooldown, because the hook
|
|
209
|
+
* re-runs and can consult state. A prompt written into the always-loaded
|
|
210
|
+
* instruction file cannot: it is static text the agent re-reads every session,
|
|
211
|
+
* with no memory that it was already declined. So the decline has to be
|
|
212
|
+
* recorded here, and the instruction file rewritten to stop asking.
|
|
213
|
+
*/
|
|
214
|
+
declare function recordOfferDeclined(home: string, now?: Date): void;
|
|
215
|
+
declare function offerDeclined(home: string): boolean;
|
|
216
|
+
/** Cleared when an identity is established, so a later logout asks again. */
|
|
217
|
+
declare function clearOfferDeclined(home: string): void;
|
|
202
218
|
|
|
203
219
|
declare function formatSessionStart(handle: string | null, rows: SyncRow[]): string;
|
|
204
220
|
declare function formatStopPickup(handle: string | null, rows: SyncRow[]): string;
|
|
@@ -227,6 +243,22 @@ interface HostCopy {
|
|
|
227
243
|
label: string;
|
|
228
244
|
}
|
|
229
245
|
declare function formatRegistrationOffer(copy: HostCopy): string;
|
|
246
|
+
/**
|
|
247
|
+
* "You have AgentChat but no handle — offer to set one up."
|
|
248
|
+
*
|
|
249
|
+
* Deliberately bounded: static text is re-read every session, so without an
|
|
250
|
+
* explicit stop condition an agent would raise it forever. `--not-now` records
|
|
251
|
+
* the decline and rewrites this block to the silent variant below.
|
|
252
|
+
*/
|
|
253
|
+
declare function renderUnregisteredBlock(copy: HostCopy): string;
|
|
254
|
+
/**
|
|
255
|
+
* The silent variant, written after `--not-now`.
|
|
256
|
+
*
|
|
257
|
+
* Still states the fact — an agent asked "am I on AgentChat?" should be able to
|
|
258
|
+
* answer, and a user who changes their mind should find the command — but it
|
|
259
|
+
* gives no instruction to act on, so there is nothing to nag with.
|
|
260
|
+
*/
|
|
261
|
+
declare function renderDeclinedBlock(copy: HostCopy): string;
|
|
230
262
|
|
|
231
263
|
declare const ANCHOR_START = "<!-- agentchat:start -->";
|
|
232
264
|
declare const ANCHOR_END = "<!-- agentchat:end -->";
|
|
@@ -533,4 +565,4 @@ declare function formatWhen(createdAt: string | undefined, now?: number): string
|
|
|
533
565
|
declare function atomicWriteFile(filePath: string, data: string, mode?: number): void;
|
|
534
566
|
declare function readJsonFile<T>(filePath: string): T | null;
|
|
535
567
|
|
|
536
|
-
export { ANCHOR_END, ANCHOR_START, type AlwaysOnState, type AnchorAction, type Credentials, DEFAULT_API_BASE, type DoctorCheck, type DoctorOpts, HEARTBEAT_FILE, type HookContext, type HookDialect, type HookInput, type HookRunners, type HookState, type HostCopy, type HostProfile, type IdentityCommands, type LockHandle, type MessageContext, type PendingRegistration, type RegisterOpts, type ResolvedIdentity, type ServiceOpts, type ServiceRef, type SessionStartResult, type StopResult, type SyncRow, type Verdict, type WireConfig, WireError, absoluteUtc, acquireLeaderLock, alwaysOnHealth, alwaysOnOptedOut, alwaysOnState, alwaysOnWanted, anchorLabelOf, atomicWriteFile, beat, claimReply, clearAlwaysOnOptOut, clearAlwaysOnWanted, clearCredentials, clearPending, clearSessionActive, contextOf, createHookRunners, createIdentityCommands, credentialsPath, formatAlwaysOnDown, formatRegistrationOffer, formatSessionStart, formatStopPickup, formatWhen, getContinuations, getMeLite, hasAnchorAt, hooksDisabled, idle, installService, lastDeliveryId, log, markAlwaysOnOptOut, markAlwaysOnWanted, markSessionActive, pendingPath, planForTest, readAnchorHandleAt, readAnchorHandleFrom, readCredentials, readHookInput, readJsonFile, readPending, readState, recordContinuation, recordRegistrationOffer, relativeAge, relativeWhen, removeAnchorAt, renderAnchorBlock, resetSession, resolveIdentity, serviceStatus, sessionStart, setPendingAck, shouldOfferRegistration, statePath, stop, stripAnchorBlock, syncAck, syncPeek, takePendingAck, uninstallService, upsertAnchorBlock, userPrompt, writeAnchor, writeCredentials, writePending, writeState };
|
|
568
|
+
export { ANCHOR_END, ANCHOR_START, type AlwaysOnState, type AnchorAction, type Credentials, DEFAULT_API_BASE, type DoctorCheck, type DoctorOpts, HEARTBEAT_FILE, type HookContext, type HookDialect, type HookInput, type HookRunners, type HookState, type HostCopy, type HostProfile, type IdentityCommands, type LockHandle, type MessageContext, type PendingRegistration, type RegisterOpts, type ResolvedIdentity, type ServiceOpts, type ServiceRef, type SessionStartResult, type StopResult, type SyncRow, type Verdict, type WireConfig, WireError, absoluteUtc, acquireLeaderLock, alwaysOnHealth, alwaysOnOptedOut, alwaysOnState, alwaysOnWanted, anchorLabelOf, atomicWriteFile, beat, claimReply, clearAlwaysOnOptOut, clearAlwaysOnWanted, clearCredentials, clearOfferDeclined, clearPending, clearSessionActive, contextOf, createHookRunners, createIdentityCommands, credentialsPath, formatAlwaysOnDown, formatRegistrationOffer, formatSessionStart, formatStopPickup, formatWhen, getContinuations, getMeLite, hasAnchorAt, hooksDisabled, idle, installService, lastDeliveryId, log, markAlwaysOnOptOut, markAlwaysOnWanted, markSessionActive, offerDeclined, pendingPath, planForTest, readAnchorHandleAt, readAnchorHandleFrom, readCredentials, readHookInput, readJsonFile, readPending, readState, recordContinuation, recordOfferDeclined, recordRegistrationOffer, relativeAge, relativeWhen, removeAnchorAt, renderAnchorBlock, renderDeclinedBlock, renderUnregisteredBlock, resetSession, resolveIdentity, serviceStatus, sessionStart, setPendingAck, shouldOfferRegistration, statePath, stop, stripAnchorBlock, syncAck, syncPeek, takePendingAck, uninstallService, upsertAnchorBlock, userPrompt, writeAnchor, writeCredentials, writePending, writeState };
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,12 @@ var StateSchema = external_exports.object({
|
|
|
55
55
|
// Machine-wide timestamp of the last registration offer injected by the
|
|
56
56
|
// session-start hook. Keeps the unregistered-plugin nag to once a day
|
|
57
57
|
// instead of once per session.
|
|
58
|
-
last_offer_at: external_exports.string().optional()
|
|
58
|
+
last_offer_at: external_exports.string().optional(),
|
|
59
|
+
// Set when the user has said "not now" to setting up a handle. Unlike the
|
|
60
|
+
// cooldown above this is PERMANENT until they change their mind, because the
|
|
61
|
+
// prompt that needs suppressing lives in the always-loaded instruction file
|
|
62
|
+
// and would otherwise be re-read — and re-acted on — every single session.
|
|
63
|
+
offer_declined_at: external_exports.string().optional()
|
|
59
64
|
});
|
|
60
65
|
function readState(home) {
|
|
61
66
|
const raw = readJsonFile(statePath(home));
|
|
@@ -128,6 +133,122 @@ function recordRegistrationOffer(home, now = /* @__PURE__ */ new Date()) {
|
|
|
128
133
|
state.last_offer_at = now.toISOString();
|
|
129
134
|
writeState(home, state);
|
|
130
135
|
}
|
|
136
|
+
function recordOfferDeclined(home, now = /* @__PURE__ */ new Date()) {
|
|
137
|
+
const state = readState(home);
|
|
138
|
+
state.offer_declined_at = now.toISOString();
|
|
139
|
+
writeState(home, state);
|
|
140
|
+
}
|
|
141
|
+
function offerDeclined(home) {
|
|
142
|
+
return readState(home).offer_declined_at !== void 0;
|
|
143
|
+
}
|
|
144
|
+
function clearOfferDeclined(home) {
|
|
145
|
+
const state = readState(home);
|
|
146
|
+
delete state.offer_declined_at;
|
|
147
|
+
writeState(home, state);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/anchor/block.ts
|
|
151
|
+
import * as fs from "fs";
|
|
152
|
+
import * as path from "path";
|
|
153
|
+
var ANCHOR_START = "<!-- agentchat:start -->";
|
|
154
|
+
var ANCHOR_END = "<!-- agentchat:end -->";
|
|
155
|
+
var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
|
|
156
|
+
var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
|
|
157
|
+
function renderAnchorBlock(handle) {
|
|
158
|
+
return [
|
|
159
|
+
ANCHOR_START,
|
|
160
|
+
"## On AgentChat",
|
|
161
|
+
"",
|
|
162
|
+
`You are **@${handle}** on AgentChat \u2014 a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,
|
|
163
|
+
"",
|
|
164
|
+
"- Other agents can DM you. You can DM them \u2014 any time, for anything that fits your context.",
|
|
165
|
+
`- Share \`@${handle}\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,
|
|
166
|
+
"- When you need to send, search, or handle an error, your AgentChat skill has the full reference.",
|
|
167
|
+
"",
|
|
168
|
+
"This is an identity, not a tool you reach for occasionally.",
|
|
169
|
+
ANCHOR_END
|
|
170
|
+
].join("\n");
|
|
171
|
+
}
|
|
172
|
+
function writeAnchor(filePath, block, expectHandle) {
|
|
173
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
174
|
+
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf-8") : "";
|
|
175
|
+
fs.writeFileSync(filePath, upsertAnchorBlock(existing, block), "utf-8");
|
|
176
|
+
if (expectHandle !== void 0) {
|
|
177
|
+
const verify = fs.readFileSync(filePath, "utf-8");
|
|
178
|
+
if (!verify.includes(`@${expectHandle}`)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`writeAnchor: handle @${expectHandle} did not land in ${filePath} \u2014 remove the agentchat block manually and re-run.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return "written";
|
|
185
|
+
}
|
|
186
|
+
function removeAnchorAt(filePath) {
|
|
187
|
+
if (!fs.existsSync(filePath)) return "noop";
|
|
188
|
+
const existing = fs.readFileSync(filePath, "utf-8");
|
|
189
|
+
const next = stripAnchorBlock(existing);
|
|
190
|
+
if (next === existing) return "noop";
|
|
191
|
+
fs.writeFileSync(filePath, next, "utf-8");
|
|
192
|
+
return "removed";
|
|
193
|
+
}
|
|
194
|
+
function hasAnchorAt(filePath) {
|
|
195
|
+
if (!fs.existsSync(filePath)) return false;
|
|
196
|
+
return findBlock(fs.readFileSync(filePath, "utf-8"), ANCHOR_START, ANCHOR_END) !== null;
|
|
197
|
+
}
|
|
198
|
+
function readAnchorHandleAt(filePath) {
|
|
199
|
+
if (!fs.existsSync(filePath)) return null;
|
|
200
|
+
return readAnchorHandleFrom(fs.readFileSync(filePath, "utf-8"));
|
|
201
|
+
}
|
|
202
|
+
function readAnchorHandleFrom(text) {
|
|
203
|
+
const block = findBlock(text, ANCHOR_START, ANCHOR_END);
|
|
204
|
+
if (block === null) return null;
|
|
205
|
+
const match = /\*\*@([^*\s]+)\*\*/.exec(text.slice(block.from, block.to));
|
|
206
|
+
return match?.[1] ?? null;
|
|
207
|
+
}
|
|
208
|
+
function lineAnchoredIndex(text, marker, fromIndex = 0) {
|
|
209
|
+
let idx = text.indexOf(marker, fromIndex);
|
|
210
|
+
while (idx >= 0) {
|
|
211
|
+
const lineStart = text.lastIndexOf("\n", idx - 1) + 1;
|
|
212
|
+
const lineEndRaw = text.indexOf("\n", idx);
|
|
213
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
214
|
+
if (text.slice(lineStart, lineEnd).trim() === marker) {
|
|
215
|
+
return { start: lineStart, end: lineEnd };
|
|
216
|
+
}
|
|
217
|
+
idx = text.indexOf(marker, idx + marker.length);
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function findBlock(text, startMarker, endMarker) {
|
|
222
|
+
const start = lineAnchoredIndex(text, startMarker);
|
|
223
|
+
if (start === null) return null;
|
|
224
|
+
const end = lineAnchoredIndex(text, endMarker, start.end);
|
|
225
|
+
if (end === null) return null;
|
|
226
|
+
return { from: start.start, to: end.end };
|
|
227
|
+
}
|
|
228
|
+
function upsertAnchorBlock(existing, block) {
|
|
229
|
+
const cleaned = stripAnchorBlock(existing);
|
|
230
|
+
const trimmed = cleaned.replace(/\n+$/, "");
|
|
231
|
+
if (trimmed.length === 0) return block + "\n";
|
|
232
|
+
return trimmed + "\n\n" + block + "\n";
|
|
233
|
+
}
|
|
234
|
+
function stripAnchorBlock(existing) {
|
|
235
|
+
const afterUnified = stripAllBlocks(existing, ANCHOR_START, ANCHOR_END);
|
|
236
|
+
return stripAllBlocks(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
|
|
237
|
+
}
|
|
238
|
+
function stripAllBlocks(existing, start, end) {
|
|
239
|
+
let text = existing;
|
|
240
|
+
for (let i = 0; i < 1e4; i++) {
|
|
241
|
+
const block = findBlock(text, start, end);
|
|
242
|
+
if (block === null) return text;
|
|
243
|
+
const before = text.slice(0, block.from).replace(/\n+$/, "");
|
|
244
|
+
const after = text.slice(block.to).replace(/^\n+/, "");
|
|
245
|
+
if (before.length === 0 && after.length === 0) return "";
|
|
246
|
+
if (before.length === 0) text = after.endsWith("\n") ? after : after + "\n";
|
|
247
|
+
else if (after.length === 0) text = before + "\n";
|
|
248
|
+
else text = before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
|
|
249
|
+
}
|
|
250
|
+
return text;
|
|
251
|
+
}
|
|
131
252
|
|
|
132
253
|
// src/util/when.ts
|
|
133
254
|
var SEC = 1e3;
|
|
@@ -261,108 +382,42 @@ function formatRegistrationOffer(copy) {
|
|
|
261
382
|
"Do not push the offer \u2014 one short ask is plenty. If declined, drop the topic for the rest of the session."
|
|
262
383
|
].join("\n");
|
|
263
384
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
import * as fs from "fs";
|
|
267
|
-
import * as path from "path";
|
|
268
|
-
var ANCHOR_START = "<!-- agentchat:start -->";
|
|
269
|
-
var ANCHOR_END = "<!-- agentchat:end -->";
|
|
270
|
-
var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
|
|
271
|
-
var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
|
|
272
|
-
function renderAnchorBlock(handle) {
|
|
385
|
+
function renderUnregisteredBlock(copy) {
|
|
386
|
+
const { invoke, label } = copy;
|
|
273
387
|
return [
|
|
274
388
|
ANCHOR_START,
|
|
275
|
-
"##
|
|
389
|
+
"## AgentChat is installed \u2014 this agent has no handle yet",
|
|
276
390
|
"",
|
|
277
|
-
`
|
|
391
|
+
`[AgentChat](https://agentchat.me) gives you, the ${label} agent, a persistent \`@handle\` other AI agents can DM. It is set up on this machine but no identity has been created, so you are not reachable yet.`,
|
|
278
392
|
"",
|
|
279
|
-
"
|
|
280
|
-
`- Share \`@${handle}\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,
|
|
281
|
-
"- When you need to send, search, or handle an error, your AgentChat skill has the full reference.",
|
|
393
|
+
"**Offer this ONCE, early, and only if it fits the conversation.** Do not interrupt work in progress to raise it, and never raise it twice in one session.",
|
|
282
394
|
"",
|
|
283
|
-
"
|
|
395
|
+
"FIRST ask whether they already have an AgentChat account \u2014 from another machine, or another coding agent \u2014 so you sign in instead of creating a duplicate.",
|
|
396
|
+
"",
|
|
397
|
+
"New to AgentChat:",
|
|
398
|
+
" 1. Ask for their email and a desired handle (3\u201330 chars, lowercase letters/digits/hyphens, starts with a letter).",
|
|
399
|
+
` 2. Run: ${invoke} register --email <email> --handle <handle>`,
|
|
400
|
+
` 3. A 6-digit code arrives by email; ask for it, then run: ${invoke} register --code <code>`,
|
|
401
|
+
"",
|
|
402
|
+
"Already have an agent elsewhere:",
|
|
403
|
+
` \u2022 With its API key: ${invoke} login --api-key <ac_\u2026>`,
|
|
404
|
+
` \u2022 Key lost: ${invoke} recover --email <email>, then ${invoke} recover --code <code>`,
|
|
405
|
+
"",
|
|
406
|
+
`**If they decline or say "later", run \`${invoke} register --not-now\` immediately.** That records the answer and removes this prompt \u2014 otherwise you will re-read it and ask again every session, which is exactly what it must not do.`,
|
|
284
407
|
ANCHOR_END
|
|
285
408
|
].join("\n");
|
|
286
409
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
return "written";
|
|
300
|
-
}
|
|
301
|
-
function removeAnchorAt(filePath) {
|
|
302
|
-
if (!fs.existsSync(filePath)) return "noop";
|
|
303
|
-
const existing = fs.readFileSync(filePath, "utf-8");
|
|
304
|
-
const next = stripAnchorBlock(existing);
|
|
305
|
-
if (next === existing) return "noop";
|
|
306
|
-
fs.writeFileSync(filePath, next, "utf-8");
|
|
307
|
-
return "removed";
|
|
308
|
-
}
|
|
309
|
-
function hasAnchorAt(filePath) {
|
|
310
|
-
if (!fs.existsSync(filePath)) return false;
|
|
311
|
-
return findBlock(fs.readFileSync(filePath, "utf-8"), ANCHOR_START, ANCHOR_END) !== null;
|
|
312
|
-
}
|
|
313
|
-
function readAnchorHandleAt(filePath) {
|
|
314
|
-
if (!fs.existsSync(filePath)) return null;
|
|
315
|
-
return readAnchorHandleFrom(fs.readFileSync(filePath, "utf-8"));
|
|
316
|
-
}
|
|
317
|
-
function readAnchorHandleFrom(text) {
|
|
318
|
-
const block = findBlock(text, ANCHOR_START, ANCHOR_END);
|
|
319
|
-
if (block === null) return null;
|
|
320
|
-
const match = /\*\*@([^*\s]+)\*\*/.exec(text.slice(block.from, block.to));
|
|
321
|
-
return match?.[1] ?? null;
|
|
322
|
-
}
|
|
323
|
-
function lineAnchoredIndex(text, marker, fromIndex = 0) {
|
|
324
|
-
let idx = text.indexOf(marker, fromIndex);
|
|
325
|
-
while (idx >= 0) {
|
|
326
|
-
const lineStart = text.lastIndexOf("\n", idx - 1) + 1;
|
|
327
|
-
const lineEndRaw = text.indexOf("\n", idx);
|
|
328
|
-
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
329
|
-
if (text.slice(lineStart, lineEnd).trim() === marker) {
|
|
330
|
-
return { start: lineStart, end: lineEnd };
|
|
331
|
-
}
|
|
332
|
-
idx = text.indexOf(marker, idx + marker.length);
|
|
333
|
-
}
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
function findBlock(text, startMarker, endMarker) {
|
|
337
|
-
const start = lineAnchoredIndex(text, startMarker);
|
|
338
|
-
if (start === null) return null;
|
|
339
|
-
const end = lineAnchoredIndex(text, endMarker, start.end);
|
|
340
|
-
if (end === null) return null;
|
|
341
|
-
return { from: start.start, to: end.end };
|
|
342
|
-
}
|
|
343
|
-
function upsertAnchorBlock(existing, block) {
|
|
344
|
-
const cleaned = stripAnchorBlock(existing);
|
|
345
|
-
const trimmed = cleaned.replace(/\n+$/, "");
|
|
346
|
-
if (trimmed.length === 0) return block + "\n";
|
|
347
|
-
return trimmed + "\n\n" + block + "\n";
|
|
348
|
-
}
|
|
349
|
-
function stripAnchorBlock(existing) {
|
|
350
|
-
const afterUnified = stripAllBlocks(existing, ANCHOR_START, ANCHOR_END);
|
|
351
|
-
return stripAllBlocks(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END);
|
|
352
|
-
}
|
|
353
|
-
function stripAllBlocks(existing, start, end) {
|
|
354
|
-
let text = existing;
|
|
355
|
-
for (let i = 0; i < 1e4; i++) {
|
|
356
|
-
const block = findBlock(text, start, end);
|
|
357
|
-
if (block === null) return text;
|
|
358
|
-
const before = text.slice(0, block.from).replace(/\n+$/, "");
|
|
359
|
-
const after = text.slice(block.to).replace(/^\n+/, "");
|
|
360
|
-
if (before.length === 0 && after.length === 0) return "";
|
|
361
|
-
if (before.length === 0) text = after.endsWith("\n") ? after : after + "\n";
|
|
362
|
-
else if (after.length === 0) text = before + "\n";
|
|
363
|
-
else text = before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
|
|
364
|
-
}
|
|
365
|
-
return text;
|
|
410
|
+
function renderDeclinedBlock(copy) {
|
|
411
|
+
const { invoke, label } = copy;
|
|
412
|
+
return [
|
|
413
|
+
ANCHOR_START,
|
|
414
|
+
"## AgentChat",
|
|
415
|
+
"",
|
|
416
|
+
`AgentChat is installed for this ${label} agent, but no handle is configured and the user has said not for now. **Do not offer to set it up.** Mention it only if they ask about AgentChat or about messaging other agents.`,
|
|
417
|
+
"",
|
|
418
|
+
`If they ask to set it up later: ${invoke} register --email <email> --handle <handle>`,
|
|
419
|
+
ANCHOR_END
|
|
420
|
+
].join("\n");
|
|
366
421
|
}
|
|
367
422
|
|
|
368
423
|
// src/hooks/engine.ts
|
|
@@ -671,6 +726,7 @@ function createIdentityCommands(profile) {
|
|
|
671
726
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
672
727
|
});
|
|
673
728
|
clearPending(home);
|
|
729
|
+
clearOfferDeclined(home);
|
|
674
730
|
console.log(
|
|
675
731
|
[
|
|
676
732
|
`Registered: @${pendingHandle} for ${LABEL}.`,
|
|
@@ -780,6 +836,7 @@ function createIdentityCommands(profile) {
|
|
|
780
836
|
...apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {},
|
|
781
837
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
782
838
|
});
|
|
839
|
+
clearOfferDeclined(home);
|
|
783
840
|
console.log([`Signed in as @${me.handle} for ${LABEL}.`, ...writeOurAnchor(me.handle), RESTART_HINT].join("\n"));
|
|
784
841
|
return 0;
|
|
785
842
|
} catch (err) {
|
|
@@ -812,6 +869,7 @@ function createIdentityCommands(profile) {
|
|
|
812
869
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
813
870
|
});
|
|
814
871
|
clearPending(home);
|
|
872
|
+
clearOfferDeclined(home);
|
|
815
873
|
console.log(
|
|
816
874
|
[
|
|
817
875
|
`Recovered: @${result.handle} for ${LABEL} \u2014 a fresh API key is stored (the old key is now revoked).`,
|
|
@@ -1315,6 +1373,7 @@ export {
|
|
|
1315
1373
|
clearAlwaysOnOptOut,
|
|
1316
1374
|
clearAlwaysOnWanted,
|
|
1317
1375
|
clearCredentials,
|
|
1376
|
+
clearOfferDeclined,
|
|
1318
1377
|
clearPending,
|
|
1319
1378
|
clearSessionActive,
|
|
1320
1379
|
contextOf,
|
|
@@ -1337,6 +1396,7 @@ export {
|
|
|
1337
1396
|
markAlwaysOnOptOut,
|
|
1338
1397
|
markAlwaysOnWanted,
|
|
1339
1398
|
markSessionActive,
|
|
1399
|
+
offerDeclined,
|
|
1340
1400
|
pendingPath,
|
|
1341
1401
|
planForTest,
|
|
1342
1402
|
readAnchorHandleAt,
|
|
@@ -1347,11 +1407,14 @@ export {
|
|
|
1347
1407
|
readPending,
|
|
1348
1408
|
readState,
|
|
1349
1409
|
recordContinuation,
|
|
1410
|
+
recordOfferDeclined,
|
|
1350
1411
|
recordRegistrationOffer,
|
|
1351
1412
|
relativeAge,
|
|
1352
1413
|
relativeWhen,
|
|
1353
1414
|
removeAnchorAt,
|
|
1354
1415
|
renderAnchorBlock,
|
|
1416
|
+
renderDeclinedBlock,
|
|
1417
|
+
renderUnregisteredBlock,
|
|
1355
1418
|
resetSession,
|
|
1356
1419
|
resolveIdentity,
|
|
1357
1420
|
serviceStatus,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/identity/state.ts","../src/util/when.ts","../src/digest/summary.ts","../src/anchor/block.ts","../src/hooks/engine.ts","../src/hooks/hook-input.ts","../src/hooks/runners.ts","../src/identity/commands.ts","../src/identity/host-profile.ts","../src/daemon/service.ts"],"sourcesContent":["import { z } from 'zod'\nimport { atomicWriteFile, readJsonFile } from '../util/fsutil.js'\nimport { statePath } from './credentials.js'\n\n// ─── Per-session hook state ─────────────────────────────────────────────────\n//\n// The stop hook must be loop-capped: without a ceiling, two plugged-in\n// agents DMing each other could keep their sessions alive indefinitely.\n// State is keyed by session id and pruned after 48h so the file never grows\n// past a screenful. It lives in the CALLER's identity home — passed in, never\n// resolved here, so one integration's hook state can never land in another\n// agent's directory.\n//\n// Concurrency note: read-modify-write here is not atomic across processes.\n// One session's own hooks are serialized by the host, so the cap holds\n// where it matters; concurrent hooks from SEPARATE sessions can lose each\n// other's counter updates, which at worst leaks a few extra continuations\n// (each still bounded by its own session's cap). Accepted — an flock would\n// buy little and cost a platform-specific dependency.\n\nconst SESSION_TTL_MS = 48 * 60 * 60 * 1000\n\nconst SessionStateSchema = z.object({\n continuations: z.number().int().min(0),\n updated_at: z.string(),\n // Ack cursor for the batch the session-start hook injected but has NOT\n // yet committed. Committed by the user-prompt hook — proof the session\n // actually ran a turn. A session that dies before its first prompt\n // (arg-error invocations, crashed startups) leaves this uncommitted and\n // the batch re-digests next session instead of being consumed by a\n // ghost. Live-fire lesson, 2026-07-12.\n pending_ack: z.string().optional(),\n})\n\nconst StateSchema = z.object({\n sessions: z.record(SessionStateSchema).default({}),\n // Machine-wide timestamp of the last registration offer injected by the\n // session-start hook. Keeps the unregistered-plugin nag to once a day\n // instead of once per session.\n last_offer_at: z.string().optional(),\n})\n\nexport type HookState = z.infer<typeof StateSchema>\n\nexport function readState(home: string): HookState {\n const raw = readJsonFile<unknown>(statePath(home))\n if (raw !== null) {\n const parsed = StateSchema.safeParse(raw)\n if (parsed.success) return parsed.data\n }\n return { sessions: {} }\n}\n\nexport function writeState(home: string, state: HookState): void {\n atomicWriteFile(statePath(home), JSON.stringify(state, null, 2) + '\\n', 0o600)\n}\n\nfunction prune(state: HookState, now: Date): void {\n const cutoff = now.getTime() - SESSION_TTL_MS\n for (const [key, entry] of Object.entries(state.sessions)) {\n const t = Date.parse(entry.updated_at)\n if (Number.isNaN(t) || t < cutoff) {\n delete state.sessions[key]\n }\n }\n}\n\nexport function getContinuations(home: string, sessionKey: string): number {\n return readState(home).sessions[sessionKey]?.continuations ?? 0\n}\n\nexport function recordContinuation(home: string, sessionKey: string, now: Date = new Date()): number {\n const state = readState(home)\n prune(state, now)\n const current = state.sessions[sessionKey]?.continuations ?? 0\n const next = current + 1\n state.sessions[sessionKey] = { continuations: next, updated_at: now.toISOString() }\n writeState(home, state)\n return next\n}\n\n/**\n * Give a session a fresh continuation budget. Called by the session-start\n * hook: resuming a capped session is a new sitting, and its stop hook\n * should be allowed to pick messages up again.\n */\nexport function resetSession(home: string, sessionKey: string): void {\n const state = readState(home)\n if (state.sessions[sessionKey] === undefined) return\n delete state.sessions[sessionKey]\n writeState(home, state)\n}\n\nexport function setPendingAck(home: string, sessionKey: string, cursor: string, now: Date = new Date()): void {\n const state = readState(home)\n prune(state, now)\n const existing = state.sessions[sessionKey]\n state.sessions[sessionKey] = {\n continuations: existing?.continuations ?? 0,\n updated_at: now.toISOString(),\n pending_ack: cursor,\n }\n writeState(home, state)\n}\n\n/** Read-and-clear the pending cursor for a session (user-prompt hook). */\nexport function takePendingAck(home: string, sessionKey: string, now: Date = new Date()): string | null {\n const state = readState(home)\n const entry = state.sessions[sessionKey]\n if (entry?.pending_ack === undefined) return null\n const cursor = entry.pending_ack\n delete entry.pending_ack\n entry.updated_at = now.toISOString()\n writeState(home, state)\n return cursor\n}\n\nconst OFFER_COOLDOWN_MS = 24 * 60 * 60 * 1000\n\nexport function shouldOfferRegistration(home: string, now: Date = new Date()): boolean {\n const last = readState(home).last_offer_at\n if (last === undefined) return true\n const t = Date.parse(last)\n return Number.isNaN(t) || now.getTime() - t >= OFFER_COOLDOWN_MS\n}\n\nexport function recordRegistrationOffer(home: string, now: Date = new Date()): void {\n const state = readState(home)\n state.last_offer_at = now.toISOString()\n writeState(home, state)\n}\n","// ─── Message \"when\" formatting ──────────────────────────────────────────────\n//\n// A coding agent has no clock of its own: a bare message body gives it no way\n// to tell whether something arrived five seconds ago or five days ago, so it\n// can't reason about staleness, urgency, or \"I already handled this.\" The\n// server puts `created_at` (ISO-8601 UTC) on every message; these helpers turn\n// it into something a model reads at a glance — a relative age plus an\n// unambiguous absolute UTC stamp.\n//\n// `now` is injectable so callers/tests are deterministic; it defaults to the\n// wall clock in the real hook/daemon runtime.\n\nconst SEC = 1000\nconst MIN = 60 * SEC\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\n/** Coarse, human-facing age of a duration in ms. Never negative (a slightly\n * future timestamp from clock skew reads as \"just now\"). */\nexport function relativeAge(ms: number): string {\n if (ms < 45 * SEC) return 'just now'\n if (ms < 90 * SEC) return '1 minute ago'\n if (ms < 45 * MIN) return `${Math.round(ms / MIN)} minutes ago`\n if (ms < 90 * MIN) return '1 hour ago'\n if (ms < 22 * HOUR) return `${Math.round(ms / HOUR)} hours ago`\n if (ms < 36 * HOUR) return '1 day ago'\n return `${Math.round(ms / DAY)} days ago`\n}\n\n/** \"2026-07-24 14:32 UTC\" — minute precision, timezone stated explicitly so an\n * agent never has to guess. `t` is an epoch-ms instant, so this is pure. */\nexport function absoluteUtc(t: number): string {\n const iso = new Date(t).toISOString() // e.g. 2026-07-24T14:32:10.000Z\n return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`\n}\n\n/** Relative age only, e.g. \"3 minutes ago\". Empty string if `createdAt` is\n * missing or unparseable — callers append it conditionally so a bad/absent\n * timestamp degrades to today's timestamp-less line rather than \"time unknown\"\n * noise in a scan-list digest. */\nexport function relativeWhen(createdAt: string | undefined, now: number = Date.now()): string {\n if (!createdAt) return ''\n const t = Date.parse(createdAt)\n if (Number.isNaN(t)) return ''\n return relativeAge(Math.max(0, now - t))\n}\n\n/** Relative age + absolute UTC, e.g. \"3 minutes ago (2026-07-24 14:32 UTC)\".\n * Falls back to \"at an unknown time\" when the stamp is missing/unparseable so\n * a single-message wake still reads as a sentence. */\nexport function formatWhen(createdAt: string | undefined, now: number = Date.now()): string {\n if (!createdAt) return 'at an unknown time'\n const t = Date.parse(createdAt)\n if (Number.isNaN(t)) return 'at an unknown time'\n return `${relativeAge(Math.max(0, now - t))} (${absoluteUtc(t)})`\n}\n","import { contextOf, type SyncRow } from '../wire/index.js'\nimport { relativeWhen } from '../util/when.js'\n\n// ─── Unread digest formatting ───────────────────────────────────────────────\n//\n// Turns a batch of sync rows into the text that gets injected into the\n// agent's context. The digest is deliberately factual — counts, senders,\n// snippets — with a short skill-directed footer. Judgement about whether\n// to reply lives in the etiquette skill, not here (same separation as the\n// Hermes notification prompt: one line of fact, manual on demand).\n\ninterface ConversationDigest {\n conversationId: string\n isGroup: boolean\n senders: string[]\n count: number\n latestSnippet: string\n /** created_at of the newest message in this conversation, for a relative\n * \"latest N ago\" recency cue that lets the agent triage by freshness.\n * Explicit `| undefined` so a row missing created_at is assignable under\n * exactOptionalPropertyTypes. */\n latestCreatedAt?: string | undefined\n /** Server-resolved group name (names the room instead of an opaque id). */\n groupName?: string | null | undefined\n /** True if any unread message in this conversation @-mentioned this agent —\n * a strong triage signal to open it first. */\n mentionsYou?: boolean | undefined\n}\n\nconst SNIPPET_MAX = 140\n\nfunction snippetOf(row: SyncRow): string {\n const content = row.content ?? {}\n const text = typeof content['text'] === 'string' ? (content['text'] as string) : ''\n if (text.length === 0) return `[${row.type ?? 'message'}]`\n const oneLine = text.replace(/\\s+/g, ' ').trim()\n return oneLine.length > SNIPPET_MAX ? `${oneLine.slice(0, SNIPPET_MAX - 1)}…` : oneLine\n}\n\nexport function digestConversations(\n rows: SyncRow[],\n selfHandle: string | null = null,\n): ConversationDigest[] {\n const self = selfHandle?.replace(/^@/, '').toLowerCase() ?? null\n const byConversation = new Map<string, ConversationDigest>()\n for (const row of rows) {\n const sender = row.sender ?? row.sender_handle ?? 'unknown'\n const ctx = contextOf(row)\n const mentionsSelf = self !== null && ctx.mentions.includes(self)\n const existing = byConversation.get(row.conversation_id)\n if (existing) {\n existing.count += 1\n if (!existing.senders.includes(sender)) existing.senders.push(sender)\n existing.latestSnippet = snippetOf(row) // rows arrive oldest-first; last write wins\n existing.latestCreatedAt = row.created_at ?? existing.latestCreatedAt\n existing.groupName = ctx.groupName ?? existing.groupName\n existing.mentionsYou = existing.mentionsYou || mentionsSelf\n } else {\n byConversation.set(row.conversation_id, {\n conversationId: row.conversation_id,\n isGroup: row.conversation_id.startsWith('grp_'),\n senders: [sender],\n count: 1,\n latestSnippet: snippetOf(row),\n latestCreatedAt: row.created_at,\n groupName: ctx.groupName,\n mentionsYou: mentionsSelf,\n })\n }\n }\n return [...byConversation.values()]\n}\n\nfunction digestLines(digests: ConversationDigest[]): string[] {\n return digests.map((d, i) => {\n const who = d.senders.map((s) => `@${s}`).join(', ')\n // Name the room when the server resolved it; otherwise the opaque id.\n const kind = d.isGroup\n ? d.groupName\n ? `group \"${d.groupName}\"`\n : `group ${d.conversationId}`\n : d.conversationId\n const count = d.count === 1 ? '1 message' : `${d.count} messages`\n const age = relativeWhen(d.latestCreatedAt)\n const recency = age ? `, latest ${age}` : ''\n const mention = d.mentionsYou ? ' — mentions you' : ''\n return `${i + 1}. ${who} (${count}, ${kind}${recency}${mention}): \"${d.latestSnippet}\"`\n })\n}\n\nexport function formatSessionStart(handle: string | null, rows: SyncRow[]): string {\n const digests = digestConversations(rows, handle)\n const total = rows.length\n // Never assert a handle we don't actually know — an agent will repeat it.\n const identity = handle ? `You are @${handle} on AgentChat. ` : 'AgentChat: '\n const header =\n identity +\n `${total} unread message${total === 1 ? '' : 's'} in ${digests.length} conversation${digests.length === 1 ? '' : 's'}:`\n return [\n header,\n '',\n ...digestLines(digests),\n '',\n '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.',\n ].join('\\n')\n}\n\nexport function formatStopPickup(handle: string | null, rows: SyncRow[]): string {\n const digests = digestConversations(rows, handle)\n const total = rows.length\n const addressee = handle ? ` for @${handle}` : ''\n return [\n `While you were working, ${total} AgentChat message${total === 1 ? '' : 's'} arrived${addressee}:`,\n '',\n ...digestLines(digests),\n '',\n 'Handle these per your AgentChat skill, then finish. Reply via agentchat_send_message only where warranted — if nothing is actionable, simply end the turn (silence is a valid outcome).',\n ].join('\\n')\n}\n\n/**\n * Injected at session start when always-on was set up but the daemon isn't\n * beating (its heartbeat is stale — see alwaysOnHealth). Written in the FIRST\n * person because the agent relays it to its user, and deliberately careful not\n * to imply loss: messages that arrive while away queue for the next session,\n * they don't vanish. The one-line fix is inline so the agent can act on it.\n */\nexport function formatAlwaysOnDown(copy: HostCopy): string {\n return (\n '⚠ Always-on is down — while you are away I won’t be able to answer messages ' +\n '(they queue for your next session, nothing is lost). ' +\n `Turn it back on: \\`${copy.invoke} daemon install\\``\n )\n}\n\n/**\n * How ONE integration names itself in user-facing copy.\n *\n * There is deliberately no `--platform` anywhere in this module. An\n * integration's CLI acts on exactly one agent — its own — so a flag naming\n * which agent to act on has nothing to select between. Removing the flag is\n * what makes the wrong-agent mistake unrepresentable rather than merely\n * guarded against.\n */\nexport interface HostCopy {\n /** Exactly what the user types, e.g. `npx -y @agentchatme/codex`, or\n * `node \"/abs/path/to/bin/agentchat\"` for a plugin-shipped bundle. */\n invoke: string\n /** Human label for the host, e.g. `Codex` or `Claude Code`. */\n label: string\n}\n\nexport function formatRegistrationOffer(copy: HostCopy): string {\n const { invoke, label } = copy\n return [\n `The AgentChat plugin is installed but this ${label} agent has no AgentChat identity yet.`,\n '',\n 'AgentChat gives you (the agent) a handle other agents can DM. If the user would like that, offer to set it up — 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:',\n `This ${label} agent gets its OWN handle — 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.`,\n '',\n 'NEW to AgentChat (most people):',\n ' 1. Ask for their email + a desired handle (3–30 chars, lowercase letters/digits/hyphens, must start with a letter).',\n ` 2. Run: ${invoke} register --email <email> --handle <handle>`,\n ` 3. A 6-digit code lands in their email; ask for it, then run: ${invoke} register --code <code>`,\n '',\n 'ALREADY have an AgentChat agent — sign in, do NOT register a second one:',\n ` • They have its API key (ac_…): ${invoke} login --api-key <ac_…>`,\n ` • They lost the key: ${invoke} recover --email <email>, then relay the emailed 6-digit code: ${invoke} recover --code <code>`,\n '',\n '',\n \"Always-on is already running — this agent answers DMs even when no session is open. There is nothing to switch on, so do not offer to.\",\n '',\n 'Do not push the offer — one short ask is plenty. If declined, drop the topic for the rest of the session.',\n ].join('\\n')\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n// ─── Instruction-file identity anchor (host-agnostic) ───────────────────────\n//\n// A fenced block in whatever file the host loads into EVERY session, so the\n// agent has \"you have a phone number\" awareness even in turns that have\n// nothing to do with AgentChat. Same mechanism as the Hermes SOUL.md anchor\n// and the OpenClaw AGENTS.md anchor.\n//\n// This module knows how to edit a fenced block in a file. It does NOT know\n// WHICH file — that is the integration's business, because the file IS the\n// host (`~/.claude/CLAUDE.md`, `$CODEX_HOME/AGENTS.md`, …). A shared function\n// that picked the file itself is precisely what once stamped one agent's\n// handle into another agent's instruction file.\n//\n// Markers are shared verbatim across ALL AgentChat integrations — whichever\n// integration wrote last owns the block, and switching integrations overwrites\n// cleanly instead of duplicating. DO NOT change them without updating the\n// OpenClaw plugin (src/binding/agents-anchor.ts) and the Hermes plugin\n// (soul_anchor.py).\n\nexport const ANCHOR_START = '<!-- agentchat:start -->'\nexport const ANCHOR_END = '<!-- agentchat:end -->'\n\nconst LEGACY_ANCHOR_START = '<!-- agentchat-skill:start -->'\nconst LEGACY_ANCHOR_END = '<!-- agentchat-skill:end -->'\n\n/** The default identity block. Integrations whose host wants richer copy\n * (Codex folds etiquette in, since its skills are on-demand) render their\n * own and pass it to `writeAnchor` instead. */\nexport function renderAnchorBlock(handle: string): string {\n return [\n ANCHOR_START,\n '## On AgentChat',\n '',\n `You are **@${handle}** on AgentChat — a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,\n '',\n '- Other agents can DM you. You can DM them — any time, for anything that fits your context.',\n `- Share \\`@${handle}\\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,\n '- When you need to send, search, or handle an error, your AgentChat skill has the full reference.',\n '',\n 'This is an identity, not a tool you reach for occasionally.',\n ANCHOR_END,\n ].join('\\n')\n}\n\nexport type AnchorAction = 'written' | 'removed' | 'noop'\n\n/**\n * Upsert `block` into the file at `filePath`, creating it if needed.\n *\n * Throws if `expectHandle` is given and does not appear in what was written —\n * a silently-wrong identity block is worse than a loud failure, because the\n * agent would spend every session announcing an address that isn't its own.\n */\nexport function writeAnchor(filePath: string, block: string, expectHandle?: string): AnchorAction {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''\n fs.writeFileSync(filePath, upsertAnchorBlock(existing, block), 'utf-8')\n\n if (expectHandle !== undefined) {\n const verify = fs.readFileSync(filePath, 'utf-8')\n if (!verify.includes(`@${expectHandle}`)) {\n throw new Error(\n `writeAnchor: handle @${expectHandle} did not land in ${filePath} — remove the agentchat block manually and re-run.`,\n )\n }\n }\n return 'written'\n}\n\nexport function removeAnchorAt(filePath: string): AnchorAction {\n if (!fs.existsSync(filePath)) return 'noop'\n const existing = fs.readFileSync(filePath, 'utf-8')\n const next = stripAnchorBlock(existing)\n if (next === existing) return 'noop'\n fs.writeFileSync(filePath, next, 'utf-8')\n return 'removed'\n}\n\nexport function hasAnchorAt(filePath: string): boolean {\n if (!fs.existsSync(filePath)) return false\n return findBlock(fs.readFileSync(filePath, 'utf-8'), ANCHOR_START, ANCHOR_END) !== null\n}\n\n/**\n * The handle a file's anchor block currently CLAIMS, or null if there is no\n * block. Every anchor rendering states it as `**@handle**`, so one pattern\n * covers them all.\n *\n * Lets an integration catch an anchor that disagrees with its own credential —\n * an agent told it is @a while authenticating as @b hands peers an address\n * that reaches someone else. Matched loosely (not against the canonical handle\n * rule) precisely so a WRONG value is still read back and reported rather than\n * silently treated as \"no anchor\".\n */\nexport function readAnchorHandleAt(filePath: string): string | null {\n if (!fs.existsSync(filePath)) return null\n return readAnchorHandleFrom(fs.readFileSync(filePath, 'utf-8'))\n}\n\nexport function readAnchorHandleFrom(text: string): string | null {\n const block = findBlock(text, ANCHOR_START, ANCHOR_END)\n if (block === null) return null\n const match = /\\*\\*@([^*\\s]+)\\*\\*/.exec(text.slice(block.from, block.to))\n return match?.[1] ?? null\n}\n\n// Markers only count when they are a whole line — a marker quoted inside user\n// prose (\"the plugin uses <!-- agentchat:start --> fences\") must never be\n// treated as a fence, or the upsert would eat the user's content between the\n// quote and the real block.\nfunction lineAnchoredIndex(\n text: string,\n marker: string,\n fromIndex = 0,\n): { start: number; end: number } | null {\n let idx = text.indexOf(marker, fromIndex)\n while (idx >= 0) {\n const lineStart = text.lastIndexOf('\\n', idx - 1) + 1\n const lineEndRaw = text.indexOf('\\n', idx)\n const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw\n if (text.slice(lineStart, lineEnd).trim() === marker) {\n return { start: lineStart, end: lineEnd }\n }\n idx = text.indexOf(marker, idx + marker.length)\n }\n return null\n}\n\nfunction findBlock(\n text: string,\n startMarker: string,\n endMarker: string,\n): { from: number; to: number } | null {\n const start = lineAnchoredIndex(text, startMarker)\n if (start === null) return null\n const end = lineAnchoredIndex(text, endMarker, start.end)\n if (end === null) return null\n return { from: start.start, to: end.end }\n}\n\nexport function upsertAnchorBlock(existing: string, block: string): string {\n // Strip every existing block (unified AND legacy) first, then append the\n // fresh one — replacement and dedup in one motion, so a file that somehow\n // accumulated multiple blocks converges back to exactly one.\n const cleaned = stripAnchorBlock(existing)\n const trimmed = cleaned.replace(/\\n+$/, '')\n if (trimmed.length === 0) return block + '\\n'\n return trimmed + '\\n\\n' + block + '\\n'\n}\n\nexport function stripAnchorBlock(existing: string): string {\n const afterUnified = stripAllBlocks(existing, ANCHOR_START, ANCHOR_END)\n return stripAllBlocks(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END)\n}\n\nfunction stripAllBlocks(existing: string, start: string, end: string): string {\n let text = existing\n // Bounded loop: each pass removes one block; a file can't hold more blocks\n // than lines.\n for (let i = 0; i < 10_000; i++) {\n const block = findBlock(text, start, end)\n if (block === null) return text\n const before = text.slice(0, block.from).replace(/\\n+$/, '')\n const after = text.slice(block.to).replace(/^\\n+/, '')\n if (before.length === 0 && after.length === 0) return ''\n if (before.length === 0) text = after.endsWith('\\n') ? after : after + '\\n'\n else if (after.length === 0) text = before + '\\n'\n else text = before + '\\n\\n' + after + (after.endsWith('\\n') ? '' : '\\n')\n }\n return text\n}\n","import { log } from '../util/log.js'\nimport { resolveIdentity } from '../identity/credentials.js'\nimport type { HookInput } from './hook-input.js'\nimport {\n getContinuations,\n recordContinuation,\n resetSession,\n setPendingAck,\n takePendingAck,\n shouldOfferRegistration,\n recordRegistrationOffer,\n} from '../identity/state.js'\nimport {\n syncPeek,\n syncAck,\n lastDeliveryId,\n markSessionActive,\n claimReply,\n getMeLite,\n type WireConfig,\n type SyncRow,\n} from '../wire/index.js'\nimport {\n formatSessionStart,\n formatStopPickup,\n formatRegistrationOffer,\n formatAlwaysOnDown,\n type HostCopy,\n} from '../digest/summary.js'\nimport { alwaysOnHealth } from '../daemon/health.js'\n\n// ─── Session hook engine (host-agnostic) ────────────────────────────────────\n//\n// Decides WHAT the agent should be told; the integration decides HOW to say it\n// to its host. This module never writes to stdout and never formats a host's\n// hook JSON — Claude Code, Codex and Cursor each expect a different envelope,\n// and a shared function choosing between them is another place to pick wrong.\n//\n// Invariants that hold no matter what goes wrong:\n// 1. These functions never throw. A failing hook must degrade to \"no\n// AgentChat context this turn\", never to a broken session.\n// 2. Ack only when a session PROVES it is real. Session-start injects the\n// digest and records the cursor as pending; the user-prompt hook commits\n// it — a prompt actually running is the proof. A session that dies before\n// its first prompt (arg-error invocation, crashed startup — live-fired\n// 2026-07-12) leaves the batch unacked and it re-digests next session:\n// duplicate beats loss, always. Cap-exceeded never acks. Rows without an\n// ackable delivery_id are never injected — they could only re-inject\n// forever.\n// 3. The stop path acks AFTER the host has been handed the text. That\n// ordering is expressed in the return type: `stop()` gives back a\n// `commit()` the integration calls once it has printed. An engine that\n// acked eagerly would lose a message whenever printing failed.\n//\n// Session state is keyed by session id ALONE. It used to be\n// `${platform}:${sessionId}` because one state file served every host; now the\n// file lives in this integration's own identity home, so the prefix has\n// nothing to disambiguate. (Entries written by an older release keep their\n// prefixed keys and simply age out under the 48h TTL — worst case one session\n// gets a fresh continuation budget.)\n\nconst SESSION_START_PEEK_LIMIT = 100\nconst STOP_PEEK_LIMIT = 50\nconst DEFAULT_MAX_CONTINUATIONS = 5\n\nexport interface HookContext {\n /** THIS integration's identity home. Never resolved here. */\n home: string\n /** How this integration names itself in user-facing copy. */\n copy: HostCopy\n}\n\nexport interface SessionStartResult {\n /** Text to inject into the session, or null for \"say nothing\". */\n context: string | null\n}\n\nexport interface StopResult {\n /** Text to continue the session with, or null to let it stop. */\n reason: string | null\n /**\n * Commit the surfaced batch as delivered. Call this ONLY after the host has\n * actually been given `reason` (invariant 3). Safe to call when `reason` is\n * null — it is a no-op.\n */\n commit: () => Promise<void>\n}\n\nexport function hooksDisabled(): boolean {\n return process.env['AGENTCHAT_HOOKS_ENABLED'] === '0'\n}\n\nfunction maxContinuations(): number {\n const raw = process.env['AGENTCHAT_HOOK_MAX_CONTINUATIONS']\n if (raw === undefined) return DEFAULT_MAX_CONTINUATIONS\n const n = Number.parseInt(raw, 10)\n return Number.isFinite(n) && n >= 0 ? n : DEFAULT_MAX_CONTINUATIONS\n}\n\nasync function resolveHandle(cfg: WireConfig, cachedHandle: string | null): Promise<string | null> {\n if (cachedHandle) return cachedHandle\n const me = await getMeLite(cfg)\n return me?.handle ?? null // never inject a made-up handle into the agent's identity\n}\n\n/** Only rows with a real delivery cursor can be surfaced — an unackable row\n * would re-inject on every hook run forever. (The production sync path always\n * has one; this guards the typed-nullable case.) */\nfunction ackableRows<T extends { delivery_id: string | null }>(rows: T[]): T[] {\n const usable = rows.filter((r) => typeof r.delivery_id === 'string' && r.delivery_id.length > 0)\n if (usable.length < rows.length) {\n log.warn(`${rows.length - usable.length} sync row(s) without delivery_id excluded from digest`)\n }\n return usable\n}\n\n/**\n * Coexistence with the always-on daemon: claim the sole right to reply to each\n * row before surfacing it, so the daemon stands down for what this session\n * takes. Returns the CONTIGUOUS oldest-first prefix this session won — stopping\n * at the first row the daemon already owns keeps the ack cursor (which commits\n * everything at-or-before it) from acking a message the daemon is mid-turn on.\n * Claims run in parallel; each is fail-open (a coord outage yields the row to\n * this session, i.e. the no-daemon behavior). Rows past a daemon-owned one stay\n * queued and re-surface next turn — duplicate beats loss, per invariant 2.\n */\nasync function claimContiguousPrefix(\n cfg: WireConfig,\n rows: SyncRow[],\n holder: string,\n): Promise<SyncRow[]> {\n const won = await Promise.all(rows.map((r) => claimReply(cfg, r.id, holder)))\n const prefix: SyncRow[] = []\n for (let i = 0; i < rows.length; i++) {\n if (!won[i]) break // daemon owns this one — stop so the ack cursor stays clean\n prefix.push(rows[i] as SyncRow)\n }\n if (prefix.length < rows.length) {\n log.info(\n `coexistence: daemon owns ${rows.length - prefix.length} row(s); surfacing ${prefix.length}`,\n )\n }\n return prefix\n}\n\nexport async function sessionStart(\n ctx: HookContext,\n input: HookInput,\n): Promise<SessionStartResult> {\n const none: SessionStartResult = { context: null }\n try {\n if (hooksDisabled()) return none\n\n // Compaction is NOT a new sitting: the host matcher usually excludes it,\n // and this guard covers hosts/installs without matcher support. Resetting\n // the stop budget on compact would let two chatting agents refill their\n // loop cap every time their own ping-pong forces a compaction —\n // unbounded, the exact loop the cap exists to stop.\n if (input.source === 'compact') return none\n\n // A session start is a new sitting — give this session a fresh stop-hook\n // continuation budget (matters when resuming a session that hit the cap).\n resetSession(ctx.home, input.sessionId)\n\n const identity = resolveIdentity(ctx.home)\n if (identity === null) {\n // First-run experience: let the agent offer registration — but at most\n // once a day, not once per session. An unregistered plugin should be\n // quiet, not a nag.\n if (shouldOfferRegistration(ctx.home)) {\n recordRegistrationOffer(ctx.home)\n return { context: formatRegistrationOffer(ctx.copy) }\n }\n return none\n }\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n // Announce this session so the always-on daemon (if the user runs one)\n // yields incoming messages to it. Fail-open: a no-op without /v1/reply.\n await markSessionActive(cfg)\n\n // If the user set up always-on but its daemon isn't beating, surface that —\n // independent of the inbox, since being silently unreachable is the point.\n const h = alwaysOnHealth(ctx.home)\n const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(ctx.copy) : null\n\n const peeked = ackableRows(await syncPeek(cfg, { limit: SESSION_START_PEEK_LIMIT }))\n const rows =\n peeked.length > 0\n ? await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`)\n : []\n\n if (rows.length === 0) return { context: alert }\n\n const handle = await resolveHandle(cfg, identity.handle)\n const digest = formatSessionStart(handle, rows)\n const context = alert !== null ? `${alert}\\n\\n${digest}` : digest\n\n // Record the cursor as pending — committed by the user-prompt hook once a\n // turn actually runs (invariant 2).\n const cursor = lastDeliveryId(rows)\n if (cursor !== null) setPendingAck(ctx.home, input.sessionId, cursor)\n\n return { context }\n } catch (err) {\n log.warn(`session-start hook degraded to no-op: ${String(err)}`)\n return none\n }\n}\n\n/**\n * A prompt is running, so the session is real — commit the digest batch that\n * session-start injected. Silent in every outcome.\n */\nexport async function userPrompt(ctx: HookContext, input: HookInput): Promise<void> {\n try {\n if (hooksDisabled()) return\n const identity = resolveIdentity(ctx.home)\n if (identity === null) return\n\n const cursor = takePendingAck(ctx.home, input.sessionId)\n if (cursor === null) return\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n try {\n await syncAck(cfg, cursor)\n } catch (err) {\n // Put it back — the next prompt retries. Rows stay stored server-side\n // either way, so the worst case is a duplicate digest next session.\n setPendingAck(ctx.home, input.sessionId, cursor)\n log.warn(`user-prompt ack failed (will retry next prompt): ${String(err)}`)\n }\n } catch (err) {\n log.warn(`user-prompt hook degraded to no-op: ${String(err)}`)\n }\n}\n\nexport async function stop(ctx: HookContext, input: HookInput): Promise<StopResult> {\n const none: StopResult = { reason: null, commit: async () => {} }\n try {\n if (hooksDisabled()) return none\n\n const identity = resolveIdentity(ctx.home)\n if (identity === null) return none\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n // Keep announcing this session so the daemon keeps yielding — even when\n // we're capped this sitting: a capped session still OWNS its inbox, and\n // the daemon taking over would defeat the continuation loop-guard.\n await markSessionActive(cfg)\n\n const cap = maxContinuations()\n if (getContinuations(ctx.home, input.sessionId) >= cap) {\n log.info(\n `stop hook: continuation cap (${cap}) reached for ${input.sessionId}; leaving inbox queued`,\n )\n return none\n }\n\n const peeked = ackableRows(await syncPeek(cfg, { limit: STOP_PEEK_LIMIT }))\n if (peeked.length === 0) return none\n const rows = await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`)\n if (rows.length === 0) return none\n\n recordContinuation(ctx.home, input.sessionId)\n\n const handle = await resolveHandle(cfg, identity.handle)\n const reason = formatStopPickup(handle, rows)\n const cursor = lastDeliveryId(rows)\n\n return {\n reason,\n commit: async () => {\n if (cursor === null) return\n try {\n await syncAck(cfg, cursor)\n } catch (err) {\n log.warn(`stop ack failed (messages stay queued): ${String(err)}`)\n }\n },\n }\n } catch (err) {\n log.warn(`stop hook degraded to no-op: ${String(err)}`)\n return none\n }\n}\n","import { log } from '../util/log.js'\n\n// ─── Hook stdin parsing ─────────────────────────────────────────────────────\n//\n// Every host pipes a JSON event object to the hook on stdin. We only need\n// a session identifier (for the continuation cap) and the session-start\n// `source` (to ignore compaction), and we must never fail if the shape\n// surprises us: a hook that dies on parse breaks every session.\n\nexport interface HookInput {\n sessionId: string\n /** Claude Code SessionStart source: startup | resume | clear | compact.\n * Undefined on other hosts/events. */\n source: string | undefined\n}\n\nexport async function readHookInput(stream: NodeJS.ReadStream = process.stdin): Promise<HookInput> {\n let raw = ''\n if (!stream.isTTY) {\n try {\n const chunks: Buffer[] = []\n for await (const chunk of stream) {\n chunks.push(Buffer.from(chunk))\n if (Buffer.concat(chunks).length > 1_000_000) break // never buffer unbounded stdin\n }\n raw = Buffer.concat(chunks).toString('utf-8')\n } catch {\n raw = ''\n }\n }\n\n let parsed: Record<string, unknown> = {}\n if (raw.trim().length > 0) {\n try {\n const value = JSON.parse(raw)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n parsed = value as Record<string, unknown>\n }\n } catch {\n log.debug('hook stdin was not valid JSON; proceeding with defaults')\n }\n }\n\n const sessionId =\n firstString(parsed, ['session_id', 'sessionId', 'thread_id', 'conversation_id']) ?? 'unknown'\n const source = firstString(parsed, ['source']) ?? undefined\n\n return { sessionId, source }\n}\n\nfunction firstString(obj: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = obj[key]\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n }\n return null\n}\n","import { sessionStart, userPrompt, stop, type HookContext } from './engine.js'\nimport { readHookInput } from './hook-input.js'\nimport { log } from '../util/log.js'\n\n// ─── Session hook runners ───────────────────────────────────────────────────\n//\n// The engine decides WHAT the agent is told; a dialect decides HOW to say it to\n// one host. This is the wiring between them, and it is the same everywhere:\n// read stdin, call the engine, print if there is something to print.\n//\n// It was duplicated per integration and the two copies were byte-identical —\n// including a comment in the Claude Code copy explaining how it talks to Codex.\n//\n// Invariant preserved from the engine: exit code is ALWAYS 0. A failing hook\n// degrades to \"no AgentChat context this turn\", never to a broken session.\n// Diagnostics go to stderr only; stdout carries one JSON object or nothing.\n\n/**\n * How one host wants hook output shaped. Each integration owns its own — every\n * coding agent expects a different envelope, and a shared module choosing\n * between them would be one more place to pick the wrong one.\n */\nexport interface HookDialect {\n sessionStartOutput(context: string): Record<string, unknown>\n stopOutput(reason: string): Record<string, unknown>\n printJson(payload: Record<string, unknown>): void\n}\n\nexport interface HookRunners {\n runSessionStart(): Promise<void>\n runUserPrompt(): Promise<void>\n runStop(): Promise<void>\n}\n\n/**\n * Build the three hook entrypoints for one coding agent.\n *\n * `context` is a factory, not a value: the hooks run in a fresh process where\n * the host's env (`CODEX_HOME` and friends) must be read at call time.\n */\nexport function createHookRunners(context: () => HookContext, dialect: HookDialect): HookRunners {\n return {\n async runSessionStart(): Promise<void> {\n try {\n const input = await readHookInput()\n const { context: text } = await sessionStart(context(), input)\n if (text !== null) dialect.printJson(dialect.sessionStartOutput(text))\n } catch (err) {\n log.warn(`session-start hook degraded to no-op: ${String(err)}`)\n }\n },\n\n async runUserPrompt(): Promise<void> {\n try {\n const input = await readHookInput()\n await userPrompt(context(), input)\n } catch (err) {\n log.warn(`user-prompt hook degraded to no-op: ${String(err)}`)\n }\n },\n\n async runStop(): Promise<void> {\n try {\n const input = await readHookInput()\n const { reason, commit } = await stop(context(), input)\n if (reason === null) return\n // Print FIRST, commit second: the ack means \"the agent has this\", so a\n // failed print must not leave the message marked delivered. The engine\n // hands back `commit` precisely so this ordering lives at the call site.\n dialect.printJson(dialect.stopOutput(reason))\n await commit()\n } catch (err) {\n log.warn(`stop hook degraded to no-op: ${String(err)}`)\n }\n },\n }\n}\n","import * as readline from 'node:readline'\nimport { AgentChatClient } from 'agentchatme'\nimport {\n DEFAULT_API_BASE,\n credentialsPath,\n readCredentials,\n resolveIdentity,\n writeCredentials,\n clearCredentials,\n readPending,\n writePending,\n clearPending,\n} from './credentials.js'\nimport { writeAnchor, removeAnchorAt, readAnchorHandleAt, hasAnchorAt } from '../anchor/block.js'\nimport { syncPeek } from '../wire/index.js'\nimport { anchorLabelOf, type DoctorCheck, type HostProfile, type Verdict } from './host-profile.js'\n\n// ─── Identity commands, for exactly one agent ───────────────────────────────\n//\n// Dual-mode by design: a human runs these in a terminal and gets prompts; a\n// coding agent runs them with flags and gets deterministic, parseable output.\n// The OTP round-trip is split across two invocations with the pending state\n// persisted, so the agent can ask its user for the emailed code between them.\n//\n// These flows are a contract with the AgentChat server — the pending-state\n// machine, the error vocabulary, what a credential file holds — so they are\n// shared rather than reimplemented per host. They lived in each integration\n// once, and within a week the two copies were 94% identical and had already\n// drifted: the Claude Code copy reported `\"host\": \"codex\"` in `status --json`,\n// because that is what copy-paste does to code nobody diffs.\n//\n// Every function acts on `profile.home()` — the caller's own agent. There is no\n// host argument to get wrong, and no branch that could reach another agent's\n// files.\n\n// Canonical handle rule, mirrored from the server so obviously-bad input fails\n// locally with a helpful message instead of a round-trip.\nconst HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\nfunction validHandle(handle: string): boolean {\n return handle.length >= 3 && handle.length <= 30 && HANDLE_PATTERN.test(handle)\n}\n\n/**\n * Ask one question on the TTY.\n *\n * Deliberately the classic `node:readline`, not `node:readline/promises`.\n * esbuild strips the `node:` prefix from builtin imports when it bundles this\n * package, and a bare `readline/promises` is not on its builtin list — so the\n * subpath version resolves fine here but fails every integration's build the\n * moment they inline the engine. A callback wrapped in a Promise costs four\n * lines and cannot break a downstream bundle.\n */\nasync function prompt(question: string): Promise<string> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout })\n try {\n const answer = await new Promise<string>((resolve) => rl.question(question, resolve))\n return answer.trim()\n } finally {\n rl.close()\n }\n}\n\ninterface ApiErrorLike {\n code?: string\n message?: string\n}\n\nfunction describeApiError(err: unknown, invocation: string): string {\n const e = (err ?? {}) as ApiErrorLike\n const code = typeof e.code === 'string' ? e.code : undefined\n const message = typeof e.message === 'string' ? e.message : String(err)\n switch (code) {\n case 'HANDLE_TAKEN':\n return 'That handle is already taken — pick another and re-run.'\n case 'EMAIL_TAKEN':\n return `This email already has an active agent. Use \\`${invocation} login\\` with its key, or \\`${invocation} recover --email <email>\\` to re-key it.`\n case 'EMAIL_EXHAUSTED':\n return 'This email has used its lifetime maximum of 3 registrations.'\n case 'INVALID_HANDLE':\n return 'The server rejected the handle (invalid or reserved word).'\n case 'INVALID_CODE':\n return `Wrong or expired code. Re-check the 6 digits; after too many misses you must restart with \\`${invocation} register\\`.`\n case 'EXPIRED':\n return `This registration expired (codes last 10 minutes). Start over with \\`${invocation} register\\`.`\n default:\n return code ? `${code}: ${message}` : message\n }\n}\n\nconst RESTART_HINT =\n 'Your messaging tools pick this up immediately — no restart needed. (If a send still says NOT_REGISTERED, you’re on an older MCP; start a fresh session once to refresh it.)'\n\nexport interface RegisterOpts {\n email?: string\n handle?: string\n displayName?: string\n description?: string\n code?: string\n apiBase?: string\n}\n\nexport interface DoctorOpts {\n fix?: boolean\n}\n\nexport interface IdentityCommands {\n runRegister(opts: RegisterOpts): Promise<number>\n runLogin(opts: { apiKey?: string; apiBase?: string }): Promise<number>\n runRecover(opts: { email?: string; code?: string; apiBase?: string }): Promise<number>\n runStatus(opts: { json?: boolean }): Promise<number>\n runLogout(): number\n runDoctor(opts?: DoctorOpts): Promise<number>\n}\n\n/** Build the identity command set for one coding agent. */\nexport function createIdentityCommands(profile: HostProfile): IdentityCommands {\n const invocation = (): string => profile.invocation()\n const apiErr = (err: unknown): string => describeApiError(err, invocation())\n const LABEL = profile.label\n\n /** Write THIS agent's anchor. Only ever touches `profile.anchorFile()`. */\n function writeOurAnchor(handle: string): string[] {\n const file = profile.anchorFile()\n const label = anchorLabelOf(profile)\n // A host that wires itself separately must not be handed an identity block\n // before that wiring exists — it would announce a handle with nothing\n // listening. Once an anchor is already there, keep it current regardless.\n if (profile.isWired !== undefined && !profile.isWired() && !hasAnchorAt(file)) return []\n try {\n writeAnchor(file, profile.renderAnchor(handle), handle)\n return [` ${label}: @${handle} → ${file}`]\n } catch (err) {\n return [` ${label}: FAILED — ${String(err)}`]\n }\n }\n\n async function runRegister(opts: RegisterOpts): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n\n // Completion leg\n if (opts.code !== undefined) {\n const code = opts.code.trim()\n if (!/^\\d{6}$/.test(code)) {\n console.error('The code is the 6-digit number from the verification email.')\n return 1\n }\n const pending = readPending(home)\n if (pending === null) {\n console.error(\n `No registration in progress. Start with: ${invocation()} register --email <email> --handle <handle>`,\n )\n return 1\n }\n if (pending.kind === 'recover') {\n console.error(\n `The pending code belongs to an account RECOVERY — complete it with: ${invocation()} recover --code ${code}`,\n )\n return 1\n }\n const pendingHandle = pending.handle\n if (pendingHandle === undefined) {\n clearPending(home)\n console.error(`Pending registration was corrupt — start again with: ${invocation()} register`)\n return 1\n }\n try {\n const result = await AgentChatClient.verify(pending.pending_id, code, {\n baseUrl: pending.api_base ?? apiBase,\n })\n writeCredentials(home, {\n api_key: result.apiKey,\n handle: pendingHandle,\n ...(pending.api_base ? { api_base: pending.api_base } : {}),\n created_at: new Date().toISOString(),\n })\n clearPending(home)\n console.log(\n [\n `Registered: @${pendingHandle} for ${LABEL}.`,\n `API key stored at ${credentialsPath(home)} (never commit this file).`,\n ...writeOurAnchor(pendingHandle),\n '',\n `This handle belongs to your ${LABEL} agent. Another coding agent on this machine is a separate peer with its own handle — you can DM each other.`,\n `Other agents can DM you at @${pendingHandle}. Check \\`${invocation()} status\\` any time.`,\n RESTART_HINT,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Verification failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n // Initiation leg. The gate is about THIS agent only — another coding agent\n // having an identity is irrelevant and must never block this one.\n if (resolveIdentity(home) !== null) {\n console.error(\n `${LABEL} already has an AgentChat identity (see \\`${invocation()} status\\`). Run \\`${invocation()} logout\\` first to replace it.`,\n )\n return 1\n }\n const inFlight = readPending(home)\n if (inFlight?.kind === 'recover') {\n console.error(\n `An account recovery is in progress — finish it with \\`${invocation()} recover --code <code>\\`, or discard it with \\`${invocation()} logout\\` before registering.`,\n )\n return 1\n }\n\n let email = opts.email?.trim().toLowerCase()\n let handle = opts.handle?.trim().toLowerCase()\n const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true\n\n if (!email) {\n if (!interactive) {\n console.error(`Missing --email. Usage: ${invocation()} register --email <email> --handle <handle>`)\n return 1\n }\n email = (await prompt('Email for verification codes: ')).toLowerCase()\n }\n if (!handle) {\n if (!interactive) {\n console.error(`Missing --handle. Usage: ${invocation()} register --email <email> --handle <handle>`)\n return 1\n }\n handle = (await prompt('Desired handle (3–30 chars, e.g. sanim-dev): ')).toLowerCase()\n }\n\n if (!email.includes('@')) {\n console.error(`\"${email}\" does not look like an email address.`)\n return 1\n }\n if (!validHandle(handle)) {\n console.error(\n `Handle \"@${handle}\" is invalid. Rules: 3–30 characters, lowercase letters/digits/hyphens, must start with a letter, no trailing or doubled hyphens.`,\n )\n return 1\n }\n\n try {\n const result = await AgentChatClient.register({\n email,\n handle,\n ...(opts.displayName ? { display_name: opts.displayName } : {}),\n ...(opts.description ? { description: opts.description } : {}),\n baseUrl: apiBase,\n })\n writePending(home, {\n kind: 'register',\n pending_id: result.pending_id,\n email,\n handle,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n console.log(\n [\n `Verification code sent to ${email} (valid ~10 minutes).`,\n `Complete with: ${invocation()} register --code <6-digit-code>`,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Registration failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runLogin(opts: { apiKey?: string; apiBase?: string }): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n let apiKey = opts.apiKey?.trim()\n\n if (!apiKey) {\n if (process.stdin.isTTY !== true) {\n console.error(`Missing --api-key. Usage: ${invocation()} login --api-key ac_live_…`)\n return 1\n }\n apiKey = await prompt('AgentChat API key (ac_…): ')\n }\n if (apiKey.length < 20) {\n console.error('That does not look like an AgentChat API key (too short).')\n return 1\n }\n\n try {\n const client = new AgentChatClient({ apiKey, baseUrl: apiBase })\n const me = await client.getMe()\n writeCredentials(home, {\n api_key: apiKey,\n handle: me.handle,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n console.log([`Signed in as @${me.handle} for ${LABEL}.`, ...writeOurAnchor(me.handle), RESTART_HINT].join('\\n'))\n return 0\n } catch (err) {\n console.error(`Login failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runRecover(opts: { email?: string; code?: string; apiBase?: string }): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n\n if (opts.code !== undefined) {\n const code = opts.code.trim()\n if (!/^\\d{6}$/.test(code)) {\n console.error('The code is the 6-digit number from the recovery email.')\n return 1\n }\n const pending = readPending(home)\n if (pending === null || pending.kind !== 'recover') {\n console.error(`No recovery in progress. Start with: ${invocation()} recover --email <email>`)\n return 1\n }\n try {\n const result = await AgentChatClient.recoverVerify(pending.pending_id, code, {\n baseUrl: pending.api_base ?? apiBase,\n })\n writeCredentials(home, {\n api_key: result.apiKey,\n handle: result.handle,\n ...(pending.api_base ? { api_base: pending.api_base } : {}),\n created_at: new Date().toISOString(),\n })\n clearPending(home)\n console.log(\n [\n `Recovered: @${result.handle} for ${LABEL} — a fresh API key is stored (the old key is now revoked).`,\n ...writeOurAnchor(result.handle),\n RESTART_HINT,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Recovery failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n let email = opts.email?.trim().toLowerCase()\n if (!email) {\n if (process.stdin.isTTY !== true) {\n console.error(`Missing --email. Usage: ${invocation()} recover --email <email>`)\n return 1\n }\n email = (await prompt('Email the agent was registered with: ')).toLowerCase()\n }\n if (!email.includes('@')) {\n console.error(`\"${email}\" does not look like an email address.`)\n return 1\n }\n\n try {\n const result = await AgentChatClient.recover(email, { baseUrl: apiBase })\n if (!result.pending_id) {\n console.log('If an agent is registered with that email, a recovery code was sent to it.')\n return 0\n }\n writePending(home, {\n kind: 'recover',\n pending_id: result.pending_id,\n email,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n console.log(\n [\n 'Recovery code sent (valid ~10 minutes).',\n `Complete with: ${invocation()} recover --code <6-digit-code>`,\n 'Note: completing recovery rotates the API key — anything using the old key stops working.',\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Recovery failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runStatus(opts: { json?: boolean }): Promise<number> {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const identity = resolveIdentity(home)\n const pending = readPending(home)\n\n if (identity === null) {\n if (opts.json) {\n console.log(\n JSON.stringify({ configured: false, pending: pending !== null, pending_kind: pending?.kind ?? null }),\n )\n } else if (pending?.kind === 'recover') {\n console.log(\n `No identity yet, but an account recovery is waiting on its emailed code — finish with: ${invocation()} recover --code <code>`,\n )\n } else if (pending !== null) {\n console.log(\n `No identity yet, but a registration for @${pending.handle ?? '?'} is waiting on its emailed code — finish with: ${invocation()} register --code <code>`,\n )\n } else {\n console.log(`No AgentChat identity for this ${LABEL} agent. Set one up with: ${invocation()} register`)\n }\n return 0\n }\n\n try {\n const client = new AgentChatClient({ apiKey: identity.apiKey, baseUrl: identity.apiBase })\n const me = await client.getMe()\n const rows = await syncPeek({ apiKey: identity.apiKey, apiBase: identity.apiBase }, { limit: 100 })\n const unread = rows.length === 100 ? '100+' : String(rows.length)\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n configured: true,\n // Was hard-coded, and the Claude Code copy said 'codex'.\n host: profile.id,\n handle: me.handle,\n status: me.status ?? 'unknown',\n unread: rows.length,\n unread_capped: rows.length === 100,\n key_source: identity.source,\n api_base: identity.apiBase,\n home,\n anchor: hasAnchorAt(anchorFile),\n }),\n )\n } else {\n console.log(\n [\n `@${me.handle} — ${me.status ?? 'active'} (${LABEL})`,\n `Unread: ${unread} message(s) queued`,\n `Key source: ${identity.source} (${identity.source === 'file' ? credentialsPath(home) : 'AGENTCHAT_API_KEY'})`,\n `API: ${identity.apiBase}`,\n `Anchor: ${hasAnchorAt(anchorFile) ? 'yes' : 'no'} (${anchorFile})`,\n ].join('\\n'),\n )\n }\n return 0\n } catch (err) {\n console.error(`Could not reach AgentChat: ${apiErr(err)}`)\n return 1\n }\n }\n\n /**\n * Sign out THIS agent and remove THIS agent's wiring. There is no `--all`,\n * because a profile has no way to reach another agent — that is the point.\n */\n function runLogout(): number {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const reports: string[] = []\n let any = false\n\n if (clearCredentials(home)) {\n any = true\n reports.push(' credentials deleted')\n }\n if (profile.removeWiring !== undefined) {\n try {\n const removed = profile.removeWiring()\n if (removed.length > 0) reports.push(` removed ${removed.join(', ')}`)\n } catch {\n reports.push(` could not fully clean up the ${LABEL} wiring`)\n }\n }\n if (removeAnchorAt(anchorFile) === 'removed') {\n reports.push(` ${anchorLabelOf(profile)} anchor removed`)\n }\n\n console.log(\n [\n any ? `Signed out of ${LABEL}.` : 'Nothing to sign out of.',\n ...reports,\n ...(any\n ? [\n 'Any other coding agent on this machine is untouched — it is a separate AgentChat agent with its own handle.',\n ...(profile.logoutHints?.() ?? []),\n ]\n : []),\n ].join('\\n'),\n )\n return 0\n }\n\n async function runDoctor(opts: DoctorOpts = {}): Promise<number> {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const checks: DoctorCheck[] = []\n\n checks.push({ name: 'node', verdict: 'PASS', detail: process.version })\n checks.push({ name: 'home', verdict: 'PASS', detail: home })\n\n const creds = readCredentials(home)\n if (creds === null) {\n checks.push({\n name: 'credentials',\n verdict: 'FAIL',\n detail: `no identity at ${credentialsPath(home)} — run \\`${invocation()} register\\``,\n })\n } else {\n checks.push({ name: 'credentials', verdict: 'PASS', detail: `@${creds.handle}` })\n const identity = resolveIdentity(home)\n if (identity !== null) {\n try {\n const client = new AgentChatClient({ apiKey: identity.apiKey, baseUrl: identity.apiBase })\n const started = Date.now()\n const me = await client.getMe()\n const verdict: Verdict = (me.status ?? 'active') === 'active' ? 'PASS' : 'WARN'\n checks.push({\n name: 'api-auth',\n verdict,\n detail: `@${me.handle} status=${me.status ?? 'active'} (${Date.now() - started}ms)`,\n })\n if (me.handle !== creds.handle) {\n checks.push({\n name: 'handle-drift',\n verdict: 'WARN',\n detail: `credentials say @${creds.handle} but the key authenticates as @${me.handle} — re-run \\`${invocation()} login\\``,\n })\n }\n } catch (err) {\n checks.push({ name: 'api-auth', verdict: 'FAIL', detail: `getMe failed: ${String(err)}` })\n }\n }\n\n // The anchor must name THIS agent. Releases of the old shared CLI wrote\n // the anchor for every host on the machine whenever any one registered,\n // so a two-agent box could end up with an instruction file announcing the\n // OTHER agent's handle — telling peers to DM an address that reaches\n // someone else.\n const claimed = readAnchorHandleAt(anchorFile)\n if (claimed === creds.handle) {\n checks.push({ name: 'anchor', verdict: 'PASS', detail: `@${claimed} in ${anchorFile}` })\n } else {\n const why =\n claimed === null\n ? `no identity block in ${anchorFile}`\n : `${anchorFile} says @${claimed} but this agent is @${creds.handle}`\n if (opts.fix === true) {\n const report = writeOurAnchor(creds.handle)\n const failed = report.some((l) => l.includes('FAILED'))\n checks.push({\n name: 'anchor',\n verdict: failed ? 'FAIL' : 'PASS',\n detail: failed ? `could not repair: ${report.join('; ')}` : `repaired → @${creds.handle}`,\n })\n } else {\n checks.push({\n name: 'anchor',\n verdict: 'WARN',\n detail: `${why} — repair with \\`${invocation()} doctor --fix\\``,\n })\n }\n }\n }\n\n if (profile.extraDoctorChecks !== undefined) checks.push(...profile.extraDoctorChecks())\n\n console.log(checks.map((c) => `${c.verdict.padEnd(4)} ${c.name}: ${c.detail}`).join('\\n'))\n return checks.some((c) => c.verdict === 'FAIL') ? 1 : 0\n }\n\n return { runRegister, runLogin, runRecover, runStatus, runLogout, runDoctor }\n}\n","export type Verdict = 'PASS' | 'WARN' | 'FAIL'\n\nexport interface DoctorCheck {\n name: string\n verdict: Verdict\n detail: string\n}\n\n/**\n * Everything the shared identity commands need to know about ONE coding agent.\n *\n * This is the seam. The register / login / recover / status / logout / doctor\n * flows are the same everywhere because they are a contract with the AgentChat\n * server; what differs per host is only what is described here. An integration\n * builds one of these and gets the commands back.\n *\n * Note what is NOT here: any way to name a *different* host. A profile\n * describes the caller and nothing else, so a command built from it cannot\n * reach another agent's files. That is the same property the per-repo split\n * gives, kept intact while the flow itself is shared once.\n *\n * The path-ish fields are functions, not strings: `CODEX_HOME` and friends are\n * read per call, and evaluating them at module load would freeze a value the\n * user can still change.\n */\nexport interface HostProfile {\n /** Human name for output, e.g. `Claude Code`. */\n label: string\n /** Stable machine identifier for `--json`, e.g. `claude-code`. */\n id: string\n /** THE identity home for this agent. */\n home(): string\n /** This host's always-loaded instruction file. */\n anchorFile(): string\n /** Exactly what a user types to reach this integration. */\n invocation(): string\n /** Render the anchor block body this host wants. */\n renderAnchor(handle: string): string\n /**\n * What to call the anchor in output. Defaults to the instruction file's own\n * basename (`CLAUDE.md`, `AGENTS.md`), which is what a user actually looks\n * for on disk.\n */\n anchorLabel?: string\n /**\n * Whether this host is wired up enough for an anchor to mean anything.\n * A host that must edit config files first (Codex) uses this so it never\n * writes an identity block announcing a phone number with nothing to answer\n * it. Hosts wired by their own installer (a Claude Code plugin) omit it.\n */\n isWired?(): boolean\n /** Extra teardown on logout; returns descriptions of what was removed. */\n removeWiring?(): string[]\n /** Host-specific doctor checks, appended after the shared ones. */\n extraDoctorChecks?(): DoctorCheck[]\n /** Extra lines printed after a successful logout. */\n logoutHints?(): string[]\n}\n\n/** The label to use for this host's anchor in user-facing output. */\nexport function anchorLabelOf(profile: HostProfile): string {\n if (profile.anchorLabel !== undefined) return profile.anchorLabel\n const file = profile.anchorFile()\n const slash = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\\\'))\n return slash >= 0 ? file.slice(slash + 1) : file\n}\n","import * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport { spawnSync, spawn } from 'node:child_process'\nimport { log } from '../util/log.js'\n\n// ─── Service lifecycle (systemd / launchd / Windows Startup) ─────────────────\n//\n// `agentchatd start` only runs while the shell is open — no good for a 24/7\n// consumer product. `install` registers the daemon as a real background\n// service that starts on boot/login and restarts on crash:\n// * Linux (native) → a systemd USER unit + `loginctl enable-linger`.\n// * macOS → a launchd LaunchAgent with KeepAlive + RunAtLoad.\n// * Windows / WSL2 → a hidden, restart-on-exit VBScript in the user's Startup\n// folder (no admin). See the Windows section below.\n// One service per INTEGRATION (the leader lock already enforces one daemon per\n// identity on a box). The caller supplies the label and the env to capture —\n// this module has no idea which coding agents exist, which is what keeps two\n// integrations' services from ever colliding on a name or clobbering each\n// other's unit file. Convention is `agentchatd-<integration>`.\n\n/**\n * Enough to ADDRESS an already-installed service. Removing one or reading its\n * status needs only its name — deliberately a narrower type than install, so\n * `uninstall` and `status` cannot be made to depend on a daemon entry they have\n * no use for.\n */\nexport interface ServiceRef {\n /** Unit/service name, e.g. `agentchatd-codex`. Must be unique per integration. */\n label: string\n home: string\n}\n\nexport interface ServiceOpts extends ServiceRef {\n /**\n * Absolute path to the integration's DAEMON bundle — the script this service\n * runs. Required, and deliberately not defaulted.\n *\n * It used to default to `process.argv[1]`, i.e. whichever binary happened to\n * call `install`. That was the CLI, which has no daemon in it, so every\n * installed unit ran a command that exited 1 and then restart-looped forever\n * under `KeepAlive`/`Restart=on-failure` while the CLI cheerfully reported\n * \"Always-on is ON\". Making the caller name the entry is what stops a service\n * from being installed against a script that cannot serve it.\n *\n * Must also be a STABLE path. A plugin's own bundle lives in a\n * version-scoped cache directory that the next upgrade deletes, so an\n * integration distributed that way copies itself somewhere durable first and\n * passes that copy here.\n */\n entry: string\n /** Host-specific env the service must see because a systemd/launchd service\n * does NOT inherit the login shell (e.g. CODEX_HOME). PATH is captured\n * automatically. */\n env?: Record<string, string>\n}\n\ninterface Plan {\n label: string // service/unit name\n node: string // absolute node path\n bin: string // absolute daemon entry, supplied by the integration\n home: string\n /** Host env captured from the current shell so the service sees the same\n * CODEX_HOME / CLAUDE_CONFIG_DIR the user installed under. */\n env: Record<string, string>\n}\n\nexport function planForTest(opts: ServiceOpts): Plan {\n return plan(opts)\n}\n\nfunction plan(opts: ServiceOpts): Plan {\n const env: Record<string, string> = {}\n // CRITICAL: a systemd/launchd service does NOT inherit the login shell's\n // PATH, so it can't find `claude` / `codex` / `npx` (often in ~/.local/bin\n // or a version-manager dir). Capture the installer's PATH so the service\n // resolves the same binaries the user does. (Without this the daemon exits\n // \"claude CLI not found on PATH\" and restart-loops.)\n if (process.env['PATH']) env['PATH'] = process.env['PATH']\n // Whatever host env the integration says its adapter needs.\n for (const [k, v] of Object.entries(opts.env ?? {})) {\n if (typeof v === 'string' && v.length > 0) env[k] = v\n }\n return {\n label: opts.label,\n node: process.execPath,\n bin: path.resolve(opts.entry),\n home: path.resolve(opts.home),\n env,\n }\n}\n\nfunction run(cmd: string, args: string[]): { ok: boolean; out: string } {\n const r = spawnSync(cmd, args, { encoding: 'utf-8' })\n return { ok: !r.error && r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() }\n}\n\n/**\n * Write the unit file but do NOT hand it to the OS service manager.\n *\n * `HOME` sandboxes where the unit file lands, but it does not sandbox\n * `launchctl` or `systemctl` — those always address the real user's domain. So\n * a test that exercises `daemon install` registers a REAL service on the\n * developer's machine, pointed at a temp directory that is about to be deleted.\n * That happened, and it left two loaded launchd jobs behind.\n *\n * With this set, everything up to registration still runs and can be asserted\n * on; only the escaping side effect is skipped.\n */\nfunction registrationSkipped(): boolean {\n const v = process.env['AGENTCHAT_SERVICE_DRY_RUN']\n return v !== undefined && v !== '' && v !== '0'\n}\n\n// ─── systemd (Linux) ─────────────────────────────────────────────────────────\n\nfunction systemdUnitPath(label: string): string {\n return path.join(os.homedir(), '.config', 'systemd', 'user', `${label}.service`)\n}\n\nfunction systemdUnit(p: Plan): string {\n const envLines = Object.entries(p.env)\n .map(([k, v]) => `Environment=${k}=${v}`)\n .join('\\n')\n return [\n '[Unit]',\n `Description=AgentChat always-on daemon (${p.label})`,\n 'After=network-online.target',\n 'Wants=network-online.target',\n '',\n '[Service]',\n 'Type=simple',\n `ExecStart=${p.node} ${p.bin} --home ${p.home}`,\n ...(envLines ? [envLines] : []),\n 'Restart=on-failure',\n 'RestartSec=5',\n '',\n '[Install]',\n 'WantedBy=default.target',\n '',\n ].join('\\n')\n}\n\nfunction installSystemd(p: Plan): void {\n const unitPath = systemdUnitPath(p.label)\n fs.mkdirSync(path.dirname(unitPath), { recursive: true })\n fs.writeFileSync(unitPath, systemdUnit(p))\n log.info(`wrote ${unitPath}`)\n if (registrationSkipped()) return\n run('systemctl', ['--user', 'daemon-reload'])\n // enable-linger keeps user services running with no active login (VPS).\n const linger = run('loginctl', ['enable-linger', os.userInfo().username])\n if (!linger.ok) {\n log.warn(`could not enable-linger (service won't survive logout until you run: sudo loginctl enable-linger ${os.userInfo().username})`)\n }\n const enabled = run('systemctl', ['--user', 'enable', '--now', p.label])\n if (!enabled.ok) throw new Error(`systemctl enable failed: ${enabled.out}`)\n log.info(`service ${p.label} enabled + started`)\n}\n\nfunction uninstallSystemd(label: string): void {\n run('systemctl', ['--user', 'disable', '--now', label])\n const unitPath = systemdUnitPath(label)\n if (fs.existsSync(unitPath)) fs.rmSync(unitPath)\n run('systemctl', ['--user', 'daemon-reload'])\n log.info(`service ${label} removed`)\n}\n\nfunction statusSystemd(label: string): string {\n const active = run('systemctl', ['--user', 'is-active', label]).out || 'unknown'\n const enabled = run('systemctl', ['--user', 'is-enabled', label]).out || 'unknown'\n return `systemd ${label}: ${active} (${enabled})`\n}\n\n// ─── launchd (macOS) ─────────────────────────────────────────────────────────\n\nfunction launchdPlistPath(label: string): string {\n return path.join(os.homedir(), 'Library', 'LaunchAgents', `${launchdLabel(label)}.plist`)\n}\nconst launchdLabel = (label: string): string => `me.agentchat.${label}`\n\nfunction launchdPlist(p: Plan): string {\n const args = [p.node, p.bin, '--home', p.home]\n const argXml = args.map((a) => ` <string>${a}</string>`).join('\\n')\n const envXml = Object.entries(p.env)\n .map(([k, v]) => ` <key>${k}</key><string>${v}</string>`)\n .join('\\n')\n const logPath = path.join(p.home, 'daemon.log')\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">',\n '<plist version=\"1.0\"><dict>',\n ` <key>Label</key><string>${launchdLabel(p.label)}</string>`,\n ' <key>ProgramArguments</key><array>',\n argXml,\n ' </array>',\n ...(envXml ? [' <key>EnvironmentVariables</key><dict>', envXml, ' </dict>'] : []),\n ' <key>RunAtLoad</key><true/>',\n ' <key>KeepAlive</key><true/>',\n ` <key>StandardErrorPath</key><string>${logPath}</string>`,\n ` <key>StandardOutPath</key><string>${logPath}</string>`,\n '</dict></plist>',\n '',\n ].join('\\n')\n}\n\nfunction installLaunchd(p: Plan): void {\n const plistPath = launchdPlistPath(p.label)\n fs.mkdirSync(path.dirname(plistPath), { recursive: true })\n fs.writeFileSync(plistPath, launchdPlist(p))\n log.info(`wrote ${plistPath}`)\n if (registrationSkipped()) return\n run('launchctl', ['unload', plistPath]) // idempotent: clear a prior load\n const loaded = run('launchctl', ['load', '-w', plistPath])\n if (!loaded.ok) throw new Error(`launchctl load failed: ${loaded.out}`)\n log.info(`service ${launchdLabel(p.label)} loaded + started`)\n}\n\nfunction uninstallLaunchd(label: string): void {\n const plistPath = launchdPlistPath(label)\n run('launchctl', ['unload', '-w', plistPath])\n if (fs.existsSync(plistPath)) fs.rmSync(plistPath)\n log.info(`service ${launchdLabel(label)} removed`)\n}\n\nfunction statusLaunchd(label: string): string {\n const r = run('launchctl', ['list', launchdLabel(label)])\n return `launchd ${launchdLabel(label)}: ${r.ok ? 'loaded' : 'not loaded'}`\n}\n\n// ─── Windows + WSL (Startup-folder launcher, no admin) ───────────────────────\n//\n// Windows has no systemd/launchd. For \"runs whenever you're logged in\" — the\n// consumer case, no admin needed — we drop a hidden, restart-on-exit VBScript\n// into the user's Startup folder. Two hosts land here:\n// * native Windows → the .vbs runs `node <daemon> start …` directly.\n// * WSL2 → the daemon lives in Linux, but the WSL VM shuts down when\n// the last terminal closes, so a Linux service can't stay\n// up. Instead the launcher goes in the WINDOWS Startup\n// folder (reached from WSL via /mnt/c) and runs\n// `wsl.exe -e bash <script>`, which boots the VM at Windows\n// login and runs the daemon inside it.\n// A \"master\" copy under ~/.agentchat/daemon is the installed-marker; the copy in\n// Startup is the enabled state (disable removes it, keeps the master — same\n// toggle semantics as systemd enable/disable).\n//\n// The file mechanics + generators are unit-tested and verified on a real\n// windows-latest CI runner. The live runtime (hidden loop, WSL boot, process\n// kill) still needs a real user machine — called out in the release notes.\n\nexport function isWslFromVersion(version: string): boolean {\n return /microsoft|wsl/i.test(version)\n}\nfunction isWsl(): boolean {\n if (process.platform !== 'linux') return false\n try {\n return isWslFromVersion(fs.readFileSync('/proc/version', 'utf-8'))\n } catch {\n return false\n }\n}\ntype WinMode = 'win32' | 'wsl'\nfunction winMode(): WinMode | null {\n if (process.platform === 'win32') return 'win32'\n if (isWsl()) return 'wsl'\n return null\n}\n\nfunction winMasterDir(): string {\n return path.join(os.homedir(), '.agentchat', 'daemon')\n}\nfunction winStartupDir(mode: WinMode): string {\n if (mode === 'win32') {\n return path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')\n }\n // WSL: translate the Windows %APPDATA% into a /mnt/c path.\n const appdata = run('cmd.exe', ['/c', 'echo %APPDATA%']).out.split(/\\r?\\n/).pop()?.trim() ?? ''\n const base = run('wslpath', ['-u', appdata]).out.trim()\n if (!base) throw new Error('could not locate the Windows Startup folder from WSL (is /mnt/c mounted?)')\n return path.join(base, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')\n}\n\nconst vbsEscape = (s: string): string => s.replace(/\"/g, '\"\"')\nconst shQuote = (s: string): string => `'${s.replace(/'/g, `'\\\\''`)}'`\n\n/** Hidden, restart-on-exit VBScript launcher. `command` is the full command\n * line; `env` is set on the launcher process (native Windows only). */\nexport function launcherVbs(command: string, env: Record<string, string>): string {\n const envLines = Object.entries(env).map(\n ([k, v]) => `sh.Environment(\"Process\").Item(\"${k}\") = \"${vbsEscape(v)}\"`,\n )\n return (\n [\n \"' AgentChat always-on launcher — runs hidden, restarts on exit.\",\n 'Set sh = CreateObject(\"WScript.Shell\")',\n ...envLines,\n 'Do',\n ` sh.Run \"${vbsEscape(command)}\", 0, True`,\n ' WScript.Sleep 5000',\n 'Loop',\n ].join('\\r\\n') + '\\r\\n'\n )\n}\n\n/** Native-Windows launch command. */\nexport function winCommandNative(p: Plan): string {\n return `\"${p.node}\" \"${p.bin}\" --home \"${p.home}\"`\n}\n\n/** The bash script WSL runs — a login-shell env plus the daemon. */\nexport function wslScriptContent(p: Plan): string {\n const exports = Object.entries(p.env).map(([k, v]) => `export ${k}=${shQuote(v)}`)\n return (\n ['#!/bin/bash', ...exports, `exec ${shQuote(p.node)} ${shQuote(p.bin)} --home ${shQuote(p.home)}`].join(\n '\\n',\n ) + '\\n'\n )\n}\n\nfunction startDetached(cmd: string, args: string[]): void {\n // Escape hatch for tests/headless installs: write the Startup entry but don't\n // actually launch the loop now (it still starts at next login).\n if (process.env['AGENTCHATD_SERVICE_NO_START'] === '1') return\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref()\n } catch (err) {\n log.warn(`could not start the launcher now (it starts at next login): ${String(err)}`)\n }\n}\nfunction winPathFromWsl(linuxPath: string): string {\n return run('wslpath', ['-w', linuxPath]).out.trim() || linuxPath\n}\nfunction killLauncher(label: string, mode: WinMode): void {\n // Best-effort: stop the running wscript launcher, matched by our .vbs name.\n const ps = `Get-CimInstance Win32_Process -Filter \"Name='wscript.exe'\" | Where-Object { $_.CommandLine -like '*${label}.vbs*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`\n run(mode === 'win32' ? 'powershell' : 'powershell.exe', ['-NoProfile', '-Command', ps])\n}\n\nfunction installWindows(p: Plan, mode: WinMode): void {\n const master = winMasterDir()\n fs.mkdirSync(master, { recursive: true })\n const masterVbs = path.join(master, `${p.label}.vbs`)\n if (mode === 'win32') {\n fs.writeFileSync(masterVbs, launcherVbs(winCommandNative(p), p.env))\n } else {\n const scriptPath = path.join(master, `${p.label}.sh`)\n fs.writeFileSync(scriptPath, wslScriptContent(p), { mode: 0o755 })\n const distro = process.env['WSL_DISTRO_NAME'] ?? ''\n if (!distro) throw new Error('WSL_DISTRO_NAME is not set — cannot target the right WSL distro')\n fs.writeFileSync(masterVbs, launcherVbs(`wsl.exe -d ${distro} -e bash \"${scriptPath}\"`, {}))\n }\n log.info(`wrote ${masterVbs}`)\n enableWindows(p.label, mode)\n log.info(`service ${p.label} installed (starts at login)`)\n}\nfunction enableWindows(label: string, mode: WinMode): void {\n const masterVbs = path.join(winMasterDir(), `${label}.vbs`)\n if (!fs.existsSync(masterVbs)) throw new Error(`no ${label} daemon installed — run install first`)\n const startup = winStartupDir(mode)\n fs.mkdirSync(startup, { recursive: true })\n const startupVbs = path.join(startup, `${label}.vbs`)\n fs.copyFileSync(masterVbs, startupVbs)\n if (registrationSkipped()) return\n if (mode === 'win32') startDetached('wscript.exe', [startupVbs])\n else startDetached('cmd.exe', ['/c', 'start', '', '/b', 'wscript.exe', winPathFromWsl(startupVbs)])\n}\nfunction disableWindows(label: string, mode: WinMode): void {\n const startupVbs = path.join(winStartupDir(mode), `${label}.vbs`)\n if (fs.existsSync(startupVbs)) fs.rmSync(startupVbs)\n killLauncher(label, mode)\n}\nfunction uninstallWindows(label: string, mode: WinMode): void {\n try {\n disableWindows(label, mode)\n } catch {\n /* startup dir may be unreadable; still clear the master below */\n }\n for (const f of [`${label}.vbs`, `${label}.sh`]) {\n const m = path.join(winMasterDir(), f)\n if (fs.existsSync(m)) fs.rmSync(m)\n }\n}\nfunction statusWindows(label: string, mode: WinMode): string {\n const installed = fs.existsSync(path.join(winMasterDir(), `${label}.vbs`))\n let enabled = false\n try {\n enabled = fs.existsSync(path.join(winStartupDir(mode), `${label}.vbs`))\n } catch {\n /* startup dir unresolved */\n }\n const host = mode === 'wsl' ? 'wsl' : 'windows'\n return `${host} ${label}: ${installed ? (enabled ? 'enabled' : 'installed (disabled)') : 'not installed'}`\n}\n\n// ─── platform dispatch ───────────────────────────────────────────────────────\n\nexport function installService(opts: ServiceOpts): void {\n const p = plan(opts)\n if (!p.bin) throw new Error('no daemon entrypoint given (ServiceOpts.entry)')\n const wm = winMode()\n if (wm) return installWindows(p, wm)\n if (process.platform === 'linux') return installSystemd(p) // native Linux (not WSL)\n if (process.platform === 'darwin') return installLaunchd(p)\n throw new Error(`service install is not supported on ${process.platform} — run \\`agentchatd start\\` under your own supervisor`)\n}\n\nexport function uninstallService(opts: ServiceRef): void {\n const label = opts.label\n const wm = winMode()\n if (wm) return uninstallWindows(label, wm)\n if (process.platform === 'linux') return uninstallSystemd(label)\n if (process.platform === 'darwin') return uninstallLaunchd(label)\n throw new Error(`service uninstall is not supported on ${process.platform}`)\n}\n\nexport function serviceStatus(opts: ServiceRef): string {\n const label = opts.label\n const wm = winMode()\n if (wm) return statusWindows(label, wm)\n if (process.platform === 'linux') return statusSystemd(label)\n if (process.platform === 'darwin') return statusLaunchd(label)\n return `service management not supported on ${process.platform}`\n}\n\n// ─── enable / disable (the away-replies opt-out) ─────────────────────────────\n//\n// Toggle the service's run-state WITHOUT uninstalling — so a user who wants\n// \"reply only while I'm in a session\" flips the daemon off, and back on later,\n// with one word. The unit/plist file stays on disk either way, so re-enabling\n// is instant. `install` already leaves it enabled (always-on by default);\n// these just turn that on and off after the fact.\n\nfunction unitInstalled(label: string): boolean {\n if (winMode()) return fs.existsSync(path.join(winMasterDir(), `${label}.vbs`))\n if (process.platform === 'linux') return fs.existsSync(systemdUnitPath(label))\n if (process.platform === 'darwin') return fs.existsSync(launchdPlistPath(label))\n return false\n}\n\nexport function disableService(opts: ServiceOpts): void {\n const label = opts.label\n if (!unitInstalled(label)) {\n throw new Error(`no ${opts.label} daemon installed — nothing to disable (run install first)`)\n }\n const wm = winMode()\n if (wm) {\n disableWindows(label, wm) // drop the Startup copy, keep the master\n } else if (process.platform === 'linux') {\n // Stops it AND removes the start-on-boot link; the unit file stays.\n run('systemctl', ['--user', 'disable', '--now', label])\n } else if (process.platform === 'darwin') {\n run('launchctl', ['unload', launchdPlistPath(label)]) // plist stays on disk\n } else {\n throw new Error(`not supported on ${process.platform}`)\n }\n log.info(`daemon ${label} disabled — session-only until you re-enable`)\n}\n\nexport function enableService(opts: ServiceOpts): void {\n const label = opts.label\n if (!unitInstalled(label)) {\n throw new Error(`no ${opts.label} daemon installed — run install first`)\n }\n const wm = winMode()\n if (wm) {\n enableWindows(label, wm) // copy the master back into Startup + start now\n } else if (process.platform === 'linux') {\n run('systemctl', ['--user', 'enable', '--now', label])\n } else if (process.platform === 'darwin') {\n run('launchctl', ['load', '-w', launchdPlistPath(label)])\n } else {\n throw new Error(`not supported on ${process.platform}`)\n }\n log.info(`daemon ${label} enabled — always-on while this machine is up`)\n}\n"],"mappings":"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","names":["resolve","fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/identity/state.ts","../src/anchor/block.ts","../src/util/when.ts","../src/digest/summary.ts","../src/hooks/engine.ts","../src/hooks/hook-input.ts","../src/hooks/runners.ts","../src/identity/commands.ts","../src/identity/host-profile.ts","../src/daemon/service.ts"],"sourcesContent":["import { z } from 'zod'\nimport { atomicWriteFile, readJsonFile } from '../util/fsutil.js'\nimport { statePath } from './credentials.js'\n\n// ─── Per-session hook state ─────────────────────────────────────────────────\n//\n// The stop hook must be loop-capped: without a ceiling, two plugged-in\n// agents DMing each other could keep their sessions alive indefinitely.\n// State is keyed by session id and pruned after 48h so the file never grows\n// past a screenful. It lives in the CALLER's identity home — passed in, never\n// resolved here, so one integration's hook state can never land in another\n// agent's directory.\n//\n// Concurrency note: read-modify-write here is not atomic across processes.\n// One session's own hooks are serialized by the host, so the cap holds\n// where it matters; concurrent hooks from SEPARATE sessions can lose each\n// other's counter updates, which at worst leaks a few extra continuations\n// (each still bounded by its own session's cap). Accepted — an flock would\n// buy little and cost a platform-specific dependency.\n\nconst SESSION_TTL_MS = 48 * 60 * 60 * 1000\n\nconst SessionStateSchema = z.object({\n continuations: z.number().int().min(0),\n updated_at: z.string(),\n // Ack cursor for the batch the session-start hook injected but has NOT\n // yet committed. Committed by the user-prompt hook — proof the session\n // actually ran a turn. A session that dies before its first prompt\n // (arg-error invocations, crashed startups) leaves this uncommitted and\n // the batch re-digests next session instead of being consumed by a\n // ghost. Live-fire lesson, 2026-07-12.\n pending_ack: z.string().optional(),\n})\n\nconst StateSchema = z.object({\n sessions: z.record(SessionStateSchema).default({}),\n // Machine-wide timestamp of the last registration offer injected by the\n // session-start hook. Keeps the unregistered-plugin nag to once a day\n // instead of once per session.\n last_offer_at: z.string().optional(),\n // Set when the user has said \"not now\" to setting up a handle. Unlike the\n // cooldown above this is PERMANENT until they change their mind, because the\n // prompt that needs suppressing lives in the always-loaded instruction file\n // and would otherwise be re-read — and re-acted on — every single session.\n offer_declined_at: z.string().optional(),\n})\n\nexport type HookState = z.infer<typeof StateSchema>\n\nexport function readState(home: string): HookState {\n const raw = readJsonFile<unknown>(statePath(home))\n if (raw !== null) {\n const parsed = StateSchema.safeParse(raw)\n if (parsed.success) return parsed.data\n }\n return { sessions: {} }\n}\n\nexport function writeState(home: string, state: HookState): void {\n atomicWriteFile(statePath(home), JSON.stringify(state, null, 2) + '\\n', 0o600)\n}\n\nfunction prune(state: HookState, now: Date): void {\n const cutoff = now.getTime() - SESSION_TTL_MS\n for (const [key, entry] of Object.entries(state.sessions)) {\n const t = Date.parse(entry.updated_at)\n if (Number.isNaN(t) || t < cutoff) {\n delete state.sessions[key]\n }\n }\n}\n\nexport function getContinuations(home: string, sessionKey: string): number {\n return readState(home).sessions[sessionKey]?.continuations ?? 0\n}\n\nexport function recordContinuation(home: string, sessionKey: string, now: Date = new Date()): number {\n const state = readState(home)\n prune(state, now)\n const current = state.sessions[sessionKey]?.continuations ?? 0\n const next = current + 1\n state.sessions[sessionKey] = { continuations: next, updated_at: now.toISOString() }\n writeState(home, state)\n return next\n}\n\n/**\n * Give a session a fresh continuation budget. Called by the session-start\n * hook: resuming a capped session is a new sitting, and its stop hook\n * should be allowed to pick messages up again.\n */\nexport function resetSession(home: string, sessionKey: string): void {\n const state = readState(home)\n if (state.sessions[sessionKey] === undefined) return\n delete state.sessions[sessionKey]\n writeState(home, state)\n}\n\nexport function setPendingAck(home: string, sessionKey: string, cursor: string, now: Date = new Date()): void {\n const state = readState(home)\n prune(state, now)\n const existing = state.sessions[sessionKey]\n state.sessions[sessionKey] = {\n continuations: existing?.continuations ?? 0,\n updated_at: now.toISOString(),\n pending_ack: cursor,\n }\n writeState(home, state)\n}\n\n/** Read-and-clear the pending cursor for a session (user-prompt hook). */\nexport function takePendingAck(home: string, sessionKey: string, now: Date = new Date()): string | null {\n const state = readState(home)\n const entry = state.sessions[sessionKey]\n if (entry?.pending_ack === undefined) return null\n const cursor = entry.pending_ack\n delete entry.pending_ack\n entry.updated_at = now.toISOString()\n writeState(home, state)\n return cursor\n}\n\nconst OFFER_COOLDOWN_MS = 24 * 60 * 60 * 1000\n\nexport function shouldOfferRegistration(home: string, now: Date = new Date()): boolean {\n const last = readState(home).last_offer_at\n if (last === undefined) return true\n const t = Date.parse(last)\n return Number.isNaN(t) || now.getTime() - t >= OFFER_COOLDOWN_MS\n}\n\nexport function recordRegistrationOffer(home: string, now: Date = new Date()): void {\n const state = readState(home)\n state.last_offer_at = now.toISOString()\n writeState(home, state)\n}\n\n/**\n * The user said \"not now\".\n *\n * A hook-injected offer could rely on a 24-hour cooldown, because the hook\n * re-runs and can consult state. A prompt written into the always-loaded\n * instruction file cannot: it is static text the agent re-reads every session,\n * with no memory that it was already declined. So the decline has to be\n * recorded here, and the instruction file rewritten to stop asking.\n */\nexport function recordOfferDeclined(home: string, now: Date = new Date()): void {\n const state = readState(home)\n state.offer_declined_at = now.toISOString()\n writeState(home, state)\n}\n\nexport function offerDeclined(home: string): boolean {\n return readState(home).offer_declined_at !== undefined\n}\n\n/** Cleared when an identity is established, so a later logout asks again. */\nexport function clearOfferDeclined(home: string): void {\n const state = readState(home)\n delete state.offer_declined_at\n writeState(home, state)\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\n\n// ─── Instruction-file identity anchor (host-agnostic) ───────────────────────\n//\n// A fenced block in whatever file the host loads into EVERY session, so the\n// agent has \"you have a phone number\" awareness even in turns that have\n// nothing to do with AgentChat. Same mechanism as the Hermes SOUL.md anchor\n// and the OpenClaw AGENTS.md anchor.\n//\n// This module knows how to edit a fenced block in a file. It does NOT know\n// WHICH file — that is the integration's business, because the file IS the\n// host (`~/.claude/CLAUDE.md`, `$CODEX_HOME/AGENTS.md`, …). A shared function\n// that picked the file itself is precisely what once stamped one agent's\n// handle into another agent's instruction file.\n//\n// Markers are shared verbatim across ALL AgentChat integrations — whichever\n// integration wrote last owns the block, and switching integrations overwrites\n// cleanly instead of duplicating. DO NOT change them without updating the\n// OpenClaw plugin (src/binding/agents-anchor.ts) and the Hermes plugin\n// (soul_anchor.py).\n\nexport const ANCHOR_START = '<!-- agentchat:start -->'\nexport const ANCHOR_END = '<!-- agentchat:end -->'\n\nconst LEGACY_ANCHOR_START = '<!-- agentchat-skill:start -->'\nconst LEGACY_ANCHOR_END = '<!-- agentchat-skill:end -->'\n\n/** The default identity block. Integrations whose host wants richer copy\n * (Codex folds etiquette in, since its skills are on-demand) render their\n * own and pass it to `writeAnchor` instead. */\nexport function renderAnchorBlock(handle: string): string {\n return [\n ANCHOR_START,\n '## On AgentChat',\n '',\n `You are **@${handle}** on AgentChat — a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent.`,\n '',\n '- Other agents can DM you. You can DM them — any time, for anything that fits your context.',\n `- Share \\`@${handle}\\` freely. Whenever and wherever you think you should share. It's how peers reach you.`,\n '- When you need to send, search, or handle an error, your AgentChat skill has the full reference.',\n '',\n 'This is an identity, not a tool you reach for occasionally.',\n ANCHOR_END,\n ].join('\\n')\n}\n\nexport type AnchorAction = 'written' | 'removed' | 'noop'\n\n/**\n * Upsert `block` into the file at `filePath`, creating it if needed.\n *\n * Throws if `expectHandle` is given and does not appear in what was written —\n * a silently-wrong identity block is worse than a loud failure, because the\n * agent would spend every session announcing an address that isn't its own.\n */\nexport function writeAnchor(filePath: string, block: string, expectHandle?: string): AnchorAction {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : ''\n fs.writeFileSync(filePath, upsertAnchorBlock(existing, block), 'utf-8')\n\n if (expectHandle !== undefined) {\n const verify = fs.readFileSync(filePath, 'utf-8')\n if (!verify.includes(`@${expectHandle}`)) {\n throw new Error(\n `writeAnchor: handle @${expectHandle} did not land in ${filePath} — remove the agentchat block manually and re-run.`,\n )\n }\n }\n return 'written'\n}\n\nexport function removeAnchorAt(filePath: string): AnchorAction {\n if (!fs.existsSync(filePath)) return 'noop'\n const existing = fs.readFileSync(filePath, 'utf-8')\n const next = stripAnchorBlock(existing)\n if (next === existing) return 'noop'\n fs.writeFileSync(filePath, next, 'utf-8')\n return 'removed'\n}\n\nexport function hasAnchorAt(filePath: string): boolean {\n if (!fs.existsSync(filePath)) return false\n return findBlock(fs.readFileSync(filePath, 'utf-8'), ANCHOR_START, ANCHOR_END) !== null\n}\n\n/**\n * The handle a file's anchor block currently CLAIMS, or null if there is no\n * block. Every anchor rendering states it as `**@handle**`, so one pattern\n * covers them all.\n *\n * Lets an integration catch an anchor that disagrees with its own credential —\n * an agent told it is @a while authenticating as @b hands peers an address\n * that reaches someone else. Matched loosely (not against the canonical handle\n * rule) precisely so a WRONG value is still read back and reported rather than\n * silently treated as \"no anchor\".\n */\nexport function readAnchorHandleAt(filePath: string): string | null {\n if (!fs.existsSync(filePath)) return null\n return readAnchorHandleFrom(fs.readFileSync(filePath, 'utf-8'))\n}\n\nexport function readAnchorHandleFrom(text: string): string | null {\n const block = findBlock(text, ANCHOR_START, ANCHOR_END)\n if (block === null) return null\n const match = /\\*\\*@([^*\\s]+)\\*\\*/.exec(text.slice(block.from, block.to))\n return match?.[1] ?? null\n}\n\n// Markers only count when they are a whole line — a marker quoted inside user\n// prose (\"the plugin uses <!-- agentchat:start --> fences\") must never be\n// treated as a fence, or the upsert would eat the user's content between the\n// quote and the real block.\nfunction lineAnchoredIndex(\n text: string,\n marker: string,\n fromIndex = 0,\n): { start: number; end: number } | null {\n let idx = text.indexOf(marker, fromIndex)\n while (idx >= 0) {\n const lineStart = text.lastIndexOf('\\n', idx - 1) + 1\n const lineEndRaw = text.indexOf('\\n', idx)\n const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw\n if (text.slice(lineStart, lineEnd).trim() === marker) {\n return { start: lineStart, end: lineEnd }\n }\n idx = text.indexOf(marker, idx + marker.length)\n }\n return null\n}\n\nfunction findBlock(\n text: string,\n startMarker: string,\n endMarker: string,\n): { from: number; to: number } | null {\n const start = lineAnchoredIndex(text, startMarker)\n if (start === null) return null\n const end = lineAnchoredIndex(text, endMarker, start.end)\n if (end === null) return null\n return { from: start.start, to: end.end }\n}\n\nexport function upsertAnchorBlock(existing: string, block: string): string {\n // Strip every existing block (unified AND legacy) first, then append the\n // fresh one — replacement and dedup in one motion, so a file that somehow\n // accumulated multiple blocks converges back to exactly one.\n const cleaned = stripAnchorBlock(existing)\n const trimmed = cleaned.replace(/\\n+$/, '')\n if (trimmed.length === 0) return block + '\\n'\n return trimmed + '\\n\\n' + block + '\\n'\n}\n\nexport function stripAnchorBlock(existing: string): string {\n const afterUnified = stripAllBlocks(existing, ANCHOR_START, ANCHOR_END)\n return stripAllBlocks(afterUnified, LEGACY_ANCHOR_START, LEGACY_ANCHOR_END)\n}\n\nfunction stripAllBlocks(existing: string, start: string, end: string): string {\n let text = existing\n // Bounded loop: each pass removes one block; a file can't hold more blocks\n // than lines.\n for (let i = 0; i < 10_000; i++) {\n const block = findBlock(text, start, end)\n if (block === null) return text\n const before = text.slice(0, block.from).replace(/\\n+$/, '')\n const after = text.slice(block.to).replace(/^\\n+/, '')\n if (before.length === 0 && after.length === 0) return ''\n if (before.length === 0) text = after.endsWith('\\n') ? after : after + '\\n'\n else if (after.length === 0) text = before + '\\n'\n else text = before + '\\n\\n' + after + (after.endsWith('\\n') ? '' : '\\n')\n }\n return text\n}\n","// ─── Message \"when\" formatting ──────────────────────────────────────────────\n//\n// A coding agent has no clock of its own: a bare message body gives it no way\n// to tell whether something arrived five seconds ago or five days ago, so it\n// can't reason about staleness, urgency, or \"I already handled this.\" The\n// server puts `created_at` (ISO-8601 UTC) on every message; these helpers turn\n// it into something a model reads at a glance — a relative age plus an\n// unambiguous absolute UTC stamp.\n//\n// `now` is injectable so callers/tests are deterministic; it defaults to the\n// wall clock in the real hook/daemon runtime.\n\nconst SEC = 1000\nconst MIN = 60 * SEC\nconst HOUR = 60 * MIN\nconst DAY = 24 * HOUR\n\n/** Coarse, human-facing age of a duration in ms. Never negative (a slightly\n * future timestamp from clock skew reads as \"just now\"). */\nexport function relativeAge(ms: number): string {\n if (ms < 45 * SEC) return 'just now'\n if (ms < 90 * SEC) return '1 minute ago'\n if (ms < 45 * MIN) return `${Math.round(ms / MIN)} minutes ago`\n if (ms < 90 * MIN) return '1 hour ago'\n if (ms < 22 * HOUR) return `${Math.round(ms / HOUR)} hours ago`\n if (ms < 36 * HOUR) return '1 day ago'\n return `${Math.round(ms / DAY)} days ago`\n}\n\n/** \"2026-07-24 14:32 UTC\" — minute precision, timezone stated explicitly so an\n * agent never has to guess. `t` is an epoch-ms instant, so this is pure. */\nexport function absoluteUtc(t: number): string {\n const iso = new Date(t).toISOString() // e.g. 2026-07-24T14:32:10.000Z\n return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`\n}\n\n/** Relative age only, e.g. \"3 minutes ago\". Empty string if `createdAt` is\n * missing or unparseable — callers append it conditionally so a bad/absent\n * timestamp degrades to today's timestamp-less line rather than \"time unknown\"\n * noise in a scan-list digest. */\nexport function relativeWhen(createdAt: string | undefined, now: number = Date.now()): string {\n if (!createdAt) return ''\n const t = Date.parse(createdAt)\n if (Number.isNaN(t)) return ''\n return relativeAge(Math.max(0, now - t))\n}\n\n/** Relative age + absolute UTC, e.g. \"3 minutes ago (2026-07-24 14:32 UTC)\".\n * Falls back to \"at an unknown time\" when the stamp is missing/unparseable so\n * a single-message wake still reads as a sentence. */\nexport function formatWhen(createdAt: string | undefined, now: number = Date.now()): string {\n if (!createdAt) return 'at an unknown time'\n const t = Date.parse(createdAt)\n if (Number.isNaN(t)) return 'at an unknown time'\n return `${relativeAge(Math.max(0, now - t))} (${absoluteUtc(t)})`\n}\n","import { contextOf, type SyncRow } from '../wire/index.js'\nimport { ANCHOR_START, ANCHOR_END } from '../anchor/block.js'\nimport { relativeWhen } from '../util/when.js'\n\n// ─── Unread digest formatting ───────────────────────────────────────────────\n//\n// Turns a batch of sync rows into the text that gets injected into the\n// agent's context. The digest is deliberately factual — counts, senders,\n// snippets — with a short skill-directed footer. Judgement about whether\n// to reply lives in the etiquette skill, not here (same separation as the\n// Hermes notification prompt: one line of fact, manual on demand).\n\ninterface ConversationDigest {\n conversationId: string\n isGroup: boolean\n senders: string[]\n count: number\n latestSnippet: string\n /** created_at of the newest message in this conversation, for a relative\n * \"latest N ago\" recency cue that lets the agent triage by freshness.\n * Explicit `| undefined` so a row missing created_at is assignable under\n * exactOptionalPropertyTypes. */\n latestCreatedAt?: string | undefined\n /** Server-resolved group name (names the room instead of an opaque id). */\n groupName?: string | null | undefined\n /** True if any unread message in this conversation @-mentioned this agent —\n * a strong triage signal to open it first. */\n mentionsYou?: boolean | undefined\n}\n\nconst SNIPPET_MAX = 140\n\nfunction snippetOf(row: SyncRow): string {\n const content = row.content ?? {}\n const text = typeof content['text'] === 'string' ? (content['text'] as string) : ''\n if (text.length === 0) return `[${row.type ?? 'message'}]`\n const oneLine = text.replace(/\\s+/g, ' ').trim()\n return oneLine.length > SNIPPET_MAX ? `${oneLine.slice(0, SNIPPET_MAX - 1)}…` : oneLine\n}\n\nexport function digestConversations(\n rows: SyncRow[],\n selfHandle: string | null = null,\n): ConversationDigest[] {\n const self = selfHandle?.replace(/^@/, '').toLowerCase() ?? null\n const byConversation = new Map<string, ConversationDigest>()\n for (const row of rows) {\n const sender = row.sender ?? row.sender_handle ?? 'unknown'\n const ctx = contextOf(row)\n const mentionsSelf = self !== null && ctx.mentions.includes(self)\n const existing = byConversation.get(row.conversation_id)\n if (existing) {\n existing.count += 1\n if (!existing.senders.includes(sender)) existing.senders.push(sender)\n existing.latestSnippet = snippetOf(row) // rows arrive oldest-first; last write wins\n existing.latestCreatedAt = row.created_at ?? existing.latestCreatedAt\n existing.groupName = ctx.groupName ?? existing.groupName\n existing.mentionsYou = existing.mentionsYou || mentionsSelf\n } else {\n byConversation.set(row.conversation_id, {\n conversationId: row.conversation_id,\n isGroup: row.conversation_id.startsWith('grp_'),\n senders: [sender],\n count: 1,\n latestSnippet: snippetOf(row),\n latestCreatedAt: row.created_at,\n groupName: ctx.groupName,\n mentionsYou: mentionsSelf,\n })\n }\n }\n return [...byConversation.values()]\n}\n\nfunction digestLines(digests: ConversationDigest[]): string[] {\n return digests.map((d, i) => {\n const who = d.senders.map((s) => `@${s}`).join(', ')\n // Name the room when the server resolved it; otherwise the opaque id.\n const kind = d.isGroup\n ? d.groupName\n ? `group \"${d.groupName}\"`\n : `group ${d.conversationId}`\n : d.conversationId\n const count = d.count === 1 ? '1 message' : `${d.count} messages`\n const age = relativeWhen(d.latestCreatedAt)\n const recency = age ? `, latest ${age}` : ''\n const mention = d.mentionsYou ? ' — mentions you' : ''\n return `${i + 1}. ${who} (${count}, ${kind}${recency}${mention}): \"${d.latestSnippet}\"`\n })\n}\n\nexport function formatSessionStart(handle: string | null, rows: SyncRow[]): string {\n const digests = digestConversations(rows, handle)\n const total = rows.length\n // Never assert a handle we don't actually know — an agent will repeat it.\n const identity = handle ? `You are @${handle} on AgentChat. ` : 'AgentChat: '\n const header =\n identity +\n `${total} unread message${total === 1 ? '' : 's'} in ${digests.length} conversation${digests.length === 1 ? '' : 's'}:`\n return [\n header,\n '',\n ...digestLines(digests),\n '',\n '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.',\n ].join('\\n')\n}\n\nexport function formatStopPickup(handle: string | null, rows: SyncRow[]): string {\n const digests = digestConversations(rows, handle)\n const total = rows.length\n const addressee = handle ? ` for @${handle}` : ''\n return [\n `While you were working, ${total} AgentChat message${total === 1 ? '' : 's'} arrived${addressee}:`,\n '',\n ...digestLines(digests),\n '',\n 'Handle these per your AgentChat skill, then finish. Reply via agentchat_send_message only where warranted — if nothing is actionable, simply end the turn (silence is a valid outcome).',\n ].join('\\n')\n}\n\n/**\n * Injected at session start when always-on was set up but the daemon isn't\n * beating (its heartbeat is stale — see alwaysOnHealth). Written in the FIRST\n * person because the agent relays it to its user, and deliberately careful not\n * to imply loss: messages that arrive while away queue for the next session,\n * they don't vanish. The one-line fix is inline so the agent can act on it.\n */\nexport function formatAlwaysOnDown(copy: HostCopy): string {\n return (\n '⚠ Always-on is down — while you are away I won’t be able to answer messages ' +\n '(they queue for your next session, nothing is lost). ' +\n `Turn it back on: \\`${copy.invoke} daemon install\\``\n )\n}\n\n/**\n * How ONE integration names itself in user-facing copy.\n *\n * There is deliberately no `--platform` anywhere in this module. An\n * integration's CLI acts on exactly one agent — its own — so a flag naming\n * which agent to act on has nothing to select between. Removing the flag is\n * what makes the wrong-agent mistake unrepresentable rather than merely\n * guarded against.\n */\nexport interface HostCopy {\n /** Exactly what the user types, e.g. `npx -y @agentchatme/codex`, or\n * `node \"/abs/path/to/bin/agentchat\"` for a plugin-shipped bundle. */\n invoke: string\n /** Human label for the host, e.g. `Codex` or `Claude Code`. */\n label: string\n}\n\nexport function formatRegistrationOffer(copy: HostCopy): string {\n const { invoke, label } = copy\n return [\n `The AgentChat plugin is installed but this ${label} agent has no AgentChat identity yet.`,\n '',\n 'AgentChat gives you (the agent) a handle other agents can DM. If the user would like that, offer to set it up — 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:',\n `This ${label} agent gets its OWN handle — 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.`,\n '',\n 'NEW to AgentChat (most people):',\n ' 1. Ask for their email + a desired handle (3–30 chars, lowercase letters/digits/hyphens, must start with a letter).',\n ` 2. Run: ${invoke} register --email <email> --handle <handle>`,\n ` 3. A 6-digit code lands in their email; ask for it, then run: ${invoke} register --code <code>`,\n '',\n 'ALREADY have an AgentChat agent — sign in, do NOT register a second one:',\n ` • They have its API key (ac_…): ${invoke} login --api-key <ac_…>`,\n ` • They lost the key: ${invoke} recover --email <email>, then relay the emailed 6-digit code: ${invoke} recover --code <code>`,\n '',\n '',\n \"Always-on is already running — this agent answers DMs even when no session is open. There is nothing to switch on, so do not offer to.\",\n '',\n 'Do not push the offer — one short ask is plenty. If declined, drop the topic for the rest of the session.',\n ].join('\\n')\n}\n\n// ─── Pre-registration instruction-file blocks ───────────────────────────────\n//\n// Discovery cannot depend on a session hook everywhere. Codex requires every\n// hook to be reviewed and trusted before it runs, and marks new or CHANGED\n// hooks as untrusted — so on a fresh install all three of ours are skipped, and\n// the agent is never told AgentChat exists. A user who does not already know to\n// ask sees an install that did nothing.\n//\n// The always-loaded instruction file needs no such approval, so that is where\n// discovery belongs. These render the block for an agent that has the\n// integration installed but no identity yet.\n//\n// The block is written by `upsertAnchorBlock`, which strips every existing\n// block before writing, so registering REPLACES this with the real identity\n// block and logout removes it. It cannot accumulate.\n\n/**\n * \"You have AgentChat but no handle — offer to set one up.\"\n *\n * Deliberately bounded: static text is re-read every session, so without an\n * explicit stop condition an agent would raise it forever. `--not-now` records\n * the decline and rewrites this block to the silent variant below.\n */\nexport function renderUnregisteredBlock(copy: HostCopy): string {\n const { invoke, label } = copy\n return [\n ANCHOR_START,\n '## AgentChat is installed — this agent has no handle yet',\n '',\n `[AgentChat](https://agentchat.me) gives you, the ${label} agent, a persistent \\`@handle\\` other AI agents can DM. It is set up on this machine but no identity has been created, so you are not reachable yet.`,\n '',\n '**Offer this ONCE, early, and only if it fits the conversation.** Do not interrupt work in progress to raise it, and never raise it twice in one session.',\n '',\n 'FIRST ask whether they already have an AgentChat account — from another machine, or another coding agent — so you sign in instead of creating a duplicate.',\n '',\n 'New to AgentChat:',\n ' 1. Ask for their email and a desired handle (3–30 chars, lowercase letters/digits/hyphens, starts with a letter).',\n ` 2. Run: ${invoke} register --email <email> --handle <handle>`,\n ` 3. A 6-digit code arrives by email; ask for it, then run: ${invoke} register --code <code>`,\n '',\n 'Already have an agent elsewhere:',\n ` • With its API key: ${invoke} login --api-key <ac_…>`,\n ` • Key lost: ${invoke} recover --email <email>, then ${invoke} recover --code <code>`,\n '',\n `**If they decline or say \"later\", run \\`${invoke} register --not-now\\` immediately.** That records the answer and removes this prompt — otherwise you will re-read it and ask again every session, which is exactly what it must not do.`,\n ANCHOR_END,\n ].join('\\n')\n}\n\n/**\n * The silent variant, written after `--not-now`.\n *\n * Still states the fact — an agent asked \"am I on AgentChat?\" should be able to\n * answer, and a user who changes their mind should find the command — but it\n * gives no instruction to act on, so there is nothing to nag with.\n */\nexport function renderDeclinedBlock(copy: HostCopy): string {\n const { invoke, label } = copy\n return [\n ANCHOR_START,\n '## AgentChat',\n '',\n `AgentChat is installed for this ${label} agent, but no handle is configured and the user has said not for now. **Do not offer to set it up.** Mention it only if they ask about AgentChat or about messaging other agents.`,\n '',\n `If they ask to set it up later: ${invoke} register --email <email> --handle <handle>`,\n ANCHOR_END,\n ].join('\\n')\n}\n","import { log } from '../util/log.js'\nimport { resolveIdentity } from '../identity/credentials.js'\nimport type { HookInput } from './hook-input.js'\nimport {\n getContinuations,\n recordContinuation,\n resetSession,\n setPendingAck,\n takePendingAck,\n shouldOfferRegistration,\n recordRegistrationOffer,\n} from '../identity/state.js'\nimport {\n syncPeek,\n syncAck,\n lastDeliveryId,\n markSessionActive,\n claimReply,\n getMeLite,\n type WireConfig,\n type SyncRow,\n} from '../wire/index.js'\nimport {\n formatSessionStart,\n formatStopPickup,\n formatRegistrationOffer,\n formatAlwaysOnDown,\n type HostCopy,\n} from '../digest/summary.js'\nimport { alwaysOnHealth } from '../daemon/health.js'\n\n// ─── Session hook engine (host-agnostic) ────────────────────────────────────\n//\n// Decides WHAT the agent should be told; the integration decides HOW to say it\n// to its host. This module never writes to stdout and never formats a host's\n// hook JSON — Claude Code, Codex and Cursor each expect a different envelope,\n// and a shared function choosing between them is another place to pick wrong.\n//\n// Invariants that hold no matter what goes wrong:\n// 1. These functions never throw. A failing hook must degrade to \"no\n// AgentChat context this turn\", never to a broken session.\n// 2. Ack only when a session PROVES it is real. Session-start injects the\n// digest and records the cursor as pending; the user-prompt hook commits\n// it — a prompt actually running is the proof. A session that dies before\n// its first prompt (arg-error invocation, crashed startup — live-fired\n// 2026-07-12) leaves the batch unacked and it re-digests next session:\n// duplicate beats loss, always. Cap-exceeded never acks. Rows without an\n// ackable delivery_id are never injected — they could only re-inject\n// forever.\n// 3. The stop path acks AFTER the host has been handed the text. That\n// ordering is expressed in the return type: `stop()` gives back a\n// `commit()` the integration calls once it has printed. An engine that\n// acked eagerly would lose a message whenever printing failed.\n//\n// Session state is keyed by session id ALONE. It used to be\n// `${platform}:${sessionId}` because one state file served every host; now the\n// file lives in this integration's own identity home, so the prefix has\n// nothing to disambiguate. (Entries written by an older release keep their\n// prefixed keys and simply age out under the 48h TTL — worst case one session\n// gets a fresh continuation budget.)\n\nconst SESSION_START_PEEK_LIMIT = 100\nconst STOP_PEEK_LIMIT = 50\nconst DEFAULT_MAX_CONTINUATIONS = 5\n\nexport interface HookContext {\n /** THIS integration's identity home. Never resolved here. */\n home: string\n /** How this integration names itself in user-facing copy. */\n copy: HostCopy\n}\n\nexport interface SessionStartResult {\n /** Text to inject into the session, or null for \"say nothing\". */\n context: string | null\n}\n\nexport interface StopResult {\n /** Text to continue the session with, or null to let it stop. */\n reason: string | null\n /**\n * Commit the surfaced batch as delivered. Call this ONLY after the host has\n * actually been given `reason` (invariant 3). Safe to call when `reason` is\n * null — it is a no-op.\n */\n commit: () => Promise<void>\n}\n\nexport function hooksDisabled(): boolean {\n return process.env['AGENTCHAT_HOOKS_ENABLED'] === '0'\n}\n\nfunction maxContinuations(): number {\n const raw = process.env['AGENTCHAT_HOOK_MAX_CONTINUATIONS']\n if (raw === undefined) return DEFAULT_MAX_CONTINUATIONS\n const n = Number.parseInt(raw, 10)\n return Number.isFinite(n) && n >= 0 ? n : DEFAULT_MAX_CONTINUATIONS\n}\n\nasync function resolveHandle(cfg: WireConfig, cachedHandle: string | null): Promise<string | null> {\n if (cachedHandle) return cachedHandle\n const me = await getMeLite(cfg)\n return me?.handle ?? null // never inject a made-up handle into the agent's identity\n}\n\n/** Only rows with a real delivery cursor can be surfaced — an unackable row\n * would re-inject on every hook run forever. (The production sync path always\n * has one; this guards the typed-nullable case.) */\nfunction ackableRows<T extends { delivery_id: string | null }>(rows: T[]): T[] {\n const usable = rows.filter((r) => typeof r.delivery_id === 'string' && r.delivery_id.length > 0)\n if (usable.length < rows.length) {\n log.warn(`${rows.length - usable.length} sync row(s) without delivery_id excluded from digest`)\n }\n return usable\n}\n\n/**\n * Coexistence with the always-on daemon: claim the sole right to reply to each\n * row before surfacing it, so the daemon stands down for what this session\n * takes. Returns the CONTIGUOUS oldest-first prefix this session won — stopping\n * at the first row the daemon already owns keeps the ack cursor (which commits\n * everything at-or-before it) from acking a message the daemon is mid-turn on.\n * Claims run in parallel; each is fail-open (a coord outage yields the row to\n * this session, i.e. the no-daemon behavior). Rows past a daemon-owned one stay\n * queued and re-surface next turn — duplicate beats loss, per invariant 2.\n */\nasync function claimContiguousPrefix(\n cfg: WireConfig,\n rows: SyncRow[],\n holder: string,\n): Promise<SyncRow[]> {\n const won = await Promise.all(rows.map((r) => claimReply(cfg, r.id, holder)))\n const prefix: SyncRow[] = []\n for (let i = 0; i < rows.length; i++) {\n if (!won[i]) break // daemon owns this one — stop so the ack cursor stays clean\n prefix.push(rows[i] as SyncRow)\n }\n if (prefix.length < rows.length) {\n log.info(\n `coexistence: daemon owns ${rows.length - prefix.length} row(s); surfacing ${prefix.length}`,\n )\n }\n return prefix\n}\n\nexport async function sessionStart(\n ctx: HookContext,\n input: HookInput,\n): Promise<SessionStartResult> {\n const none: SessionStartResult = { context: null }\n try {\n if (hooksDisabled()) return none\n\n // Compaction is NOT a new sitting: the host matcher usually excludes it,\n // and this guard covers hosts/installs without matcher support. Resetting\n // the stop budget on compact would let two chatting agents refill their\n // loop cap every time their own ping-pong forces a compaction —\n // unbounded, the exact loop the cap exists to stop.\n if (input.source === 'compact') return none\n\n // A session start is a new sitting — give this session a fresh stop-hook\n // continuation budget (matters when resuming a session that hit the cap).\n resetSession(ctx.home, input.sessionId)\n\n const identity = resolveIdentity(ctx.home)\n if (identity === null) {\n // First-run experience: let the agent offer registration — but at most\n // once a day, not once per session. An unregistered plugin should be\n // quiet, not a nag.\n if (shouldOfferRegistration(ctx.home)) {\n recordRegistrationOffer(ctx.home)\n return { context: formatRegistrationOffer(ctx.copy) }\n }\n return none\n }\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n // Announce this session so the always-on daemon (if the user runs one)\n // yields incoming messages to it. Fail-open: a no-op without /v1/reply.\n await markSessionActive(cfg)\n\n // If the user set up always-on but its daemon isn't beating, surface that —\n // independent of the inbox, since being silently unreachable is the point.\n const h = alwaysOnHealth(ctx.home)\n const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(ctx.copy) : null\n\n const peeked = ackableRows(await syncPeek(cfg, { limit: SESSION_START_PEEK_LIMIT }))\n const rows =\n peeked.length > 0\n ? await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`)\n : []\n\n if (rows.length === 0) return { context: alert }\n\n const handle = await resolveHandle(cfg, identity.handle)\n const digest = formatSessionStart(handle, rows)\n const context = alert !== null ? `${alert}\\n\\n${digest}` : digest\n\n // Record the cursor as pending — committed by the user-prompt hook once a\n // turn actually runs (invariant 2).\n const cursor = lastDeliveryId(rows)\n if (cursor !== null) setPendingAck(ctx.home, input.sessionId, cursor)\n\n return { context }\n } catch (err) {\n log.warn(`session-start hook degraded to no-op: ${String(err)}`)\n return none\n }\n}\n\n/**\n * A prompt is running, so the session is real — commit the digest batch that\n * session-start injected. Silent in every outcome.\n */\nexport async function userPrompt(ctx: HookContext, input: HookInput): Promise<void> {\n try {\n if (hooksDisabled()) return\n const identity = resolveIdentity(ctx.home)\n if (identity === null) return\n\n const cursor = takePendingAck(ctx.home, input.sessionId)\n if (cursor === null) return\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n try {\n await syncAck(cfg, cursor)\n } catch (err) {\n // Put it back — the next prompt retries. Rows stay stored server-side\n // either way, so the worst case is a duplicate digest next session.\n setPendingAck(ctx.home, input.sessionId, cursor)\n log.warn(`user-prompt ack failed (will retry next prompt): ${String(err)}`)\n }\n } catch (err) {\n log.warn(`user-prompt hook degraded to no-op: ${String(err)}`)\n }\n}\n\nexport async function stop(ctx: HookContext, input: HookInput): Promise<StopResult> {\n const none: StopResult = { reason: null, commit: async () => {} }\n try {\n if (hooksDisabled()) return none\n\n const identity = resolveIdentity(ctx.home)\n if (identity === null) return none\n\n const cfg: WireConfig = { apiKey: identity.apiKey, apiBase: identity.apiBase }\n // Keep announcing this session so the daemon keeps yielding — even when\n // we're capped this sitting: a capped session still OWNS its inbox, and\n // the daemon taking over would defeat the continuation loop-guard.\n await markSessionActive(cfg)\n\n const cap = maxContinuations()\n if (getContinuations(ctx.home, input.sessionId) >= cap) {\n log.info(\n `stop hook: continuation cap (${cap}) reached for ${input.sessionId}; leaving inbox queued`,\n )\n return none\n }\n\n const peeked = ackableRows(await syncPeek(cfg, { limit: STOP_PEEK_LIMIT }))\n if (peeked.length === 0) return none\n const rows = await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`)\n if (rows.length === 0) return none\n\n recordContinuation(ctx.home, input.sessionId)\n\n const handle = await resolveHandle(cfg, identity.handle)\n const reason = formatStopPickup(handle, rows)\n const cursor = lastDeliveryId(rows)\n\n return {\n reason,\n commit: async () => {\n if (cursor === null) return\n try {\n await syncAck(cfg, cursor)\n } catch (err) {\n log.warn(`stop ack failed (messages stay queued): ${String(err)}`)\n }\n },\n }\n } catch (err) {\n log.warn(`stop hook degraded to no-op: ${String(err)}`)\n return none\n }\n}\n","import { log } from '../util/log.js'\n\n// ─── Hook stdin parsing ─────────────────────────────────────────────────────\n//\n// Every host pipes a JSON event object to the hook on stdin. We only need\n// a session identifier (for the continuation cap) and the session-start\n// `source` (to ignore compaction), and we must never fail if the shape\n// surprises us: a hook that dies on parse breaks every session.\n\nexport interface HookInput {\n sessionId: string\n /** Claude Code SessionStart source: startup | resume | clear | compact.\n * Undefined on other hosts/events. */\n source: string | undefined\n}\n\nexport async function readHookInput(stream: NodeJS.ReadStream = process.stdin): Promise<HookInput> {\n let raw = ''\n if (!stream.isTTY) {\n try {\n const chunks: Buffer[] = []\n for await (const chunk of stream) {\n chunks.push(Buffer.from(chunk))\n if (Buffer.concat(chunks).length > 1_000_000) break // never buffer unbounded stdin\n }\n raw = Buffer.concat(chunks).toString('utf-8')\n } catch {\n raw = ''\n }\n }\n\n let parsed: Record<string, unknown> = {}\n if (raw.trim().length > 0) {\n try {\n const value = JSON.parse(raw)\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n parsed = value as Record<string, unknown>\n }\n } catch {\n log.debug('hook stdin was not valid JSON; proceeding with defaults')\n }\n }\n\n const sessionId =\n firstString(parsed, ['session_id', 'sessionId', 'thread_id', 'conversation_id']) ?? 'unknown'\n const source = firstString(parsed, ['source']) ?? undefined\n\n return { sessionId, source }\n}\n\nfunction firstString(obj: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = obj[key]\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n }\n return null\n}\n","import { sessionStart, userPrompt, stop, type HookContext } from './engine.js'\nimport { readHookInput } from './hook-input.js'\nimport { log } from '../util/log.js'\n\n// ─── Session hook runners ───────────────────────────────────────────────────\n//\n// The engine decides WHAT the agent is told; a dialect decides HOW to say it to\n// one host. This is the wiring between them, and it is the same everywhere:\n// read stdin, call the engine, print if there is something to print.\n//\n// It was duplicated per integration and the two copies were byte-identical —\n// including a comment in the Claude Code copy explaining how it talks to Codex.\n//\n// Invariant preserved from the engine: exit code is ALWAYS 0. A failing hook\n// degrades to \"no AgentChat context this turn\", never to a broken session.\n// Diagnostics go to stderr only; stdout carries one JSON object or nothing.\n\n/**\n * How one host wants hook output shaped. Each integration owns its own — every\n * coding agent expects a different envelope, and a shared module choosing\n * between them would be one more place to pick the wrong one.\n */\nexport interface HookDialect {\n sessionStartOutput(context: string): Record<string, unknown>\n stopOutput(reason: string): Record<string, unknown>\n printJson(payload: Record<string, unknown>): void\n}\n\nexport interface HookRunners {\n runSessionStart(): Promise<void>\n runUserPrompt(): Promise<void>\n runStop(): Promise<void>\n}\n\n/**\n * Build the three hook entrypoints for one coding agent.\n *\n * `context` is a factory, not a value: the hooks run in a fresh process where\n * the host's env (`CODEX_HOME` and friends) must be read at call time.\n */\nexport function createHookRunners(context: () => HookContext, dialect: HookDialect): HookRunners {\n return {\n async runSessionStart(): Promise<void> {\n try {\n const input = await readHookInput()\n const { context: text } = await sessionStart(context(), input)\n if (text !== null) dialect.printJson(dialect.sessionStartOutput(text))\n } catch (err) {\n log.warn(`session-start hook degraded to no-op: ${String(err)}`)\n }\n },\n\n async runUserPrompt(): Promise<void> {\n try {\n const input = await readHookInput()\n await userPrompt(context(), input)\n } catch (err) {\n log.warn(`user-prompt hook degraded to no-op: ${String(err)}`)\n }\n },\n\n async runStop(): Promise<void> {\n try {\n const input = await readHookInput()\n const { reason, commit } = await stop(context(), input)\n if (reason === null) return\n // Print FIRST, commit second: the ack means \"the agent has this\", so a\n // failed print must not leave the message marked delivered. The engine\n // hands back `commit` precisely so this ordering lives at the call site.\n dialect.printJson(dialect.stopOutput(reason))\n await commit()\n } catch (err) {\n log.warn(`stop hook degraded to no-op: ${String(err)}`)\n }\n },\n }\n}\n","import * as readline from 'node:readline'\nimport { AgentChatClient } from 'agentchatme'\nimport {\n DEFAULT_API_BASE,\n credentialsPath,\n readCredentials,\n resolveIdentity,\n writeCredentials,\n clearCredentials,\n readPending,\n writePending,\n clearPending,\n} from './credentials.js'\nimport { clearOfferDeclined } from './state.js'\nimport { writeAnchor, removeAnchorAt, readAnchorHandleAt, hasAnchorAt } from '../anchor/block.js'\nimport { syncPeek } from '../wire/index.js'\nimport { anchorLabelOf, type DoctorCheck, type HostProfile, type Verdict } from './host-profile.js'\n\n// ─── Identity commands, for exactly one agent ───────────────────────────────\n//\n// Dual-mode by design: a human runs these in a terminal and gets prompts; a\n// coding agent runs them with flags and gets deterministic, parseable output.\n// The OTP round-trip is split across two invocations with the pending state\n// persisted, so the agent can ask its user for the emailed code between them.\n//\n// These flows are a contract with the AgentChat server — the pending-state\n// machine, the error vocabulary, what a credential file holds — so they are\n// shared rather than reimplemented per host. They lived in each integration\n// once, and within a week the two copies were 94% identical and had already\n// drifted: the Claude Code copy reported `\"host\": \"codex\"` in `status --json`,\n// because that is what copy-paste does to code nobody diffs.\n//\n// Every function acts on `profile.home()` — the caller's own agent. There is no\n// host argument to get wrong, and no branch that could reach another agent's\n// files.\n\n// Canonical handle rule, mirrored from the server so obviously-bad input fails\n// locally with a helpful message instead of a round-trip.\nconst HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\nfunction validHandle(handle: string): boolean {\n return handle.length >= 3 && handle.length <= 30 && HANDLE_PATTERN.test(handle)\n}\n\n/**\n * Ask one question on the TTY.\n *\n * Deliberately the classic `node:readline`, not `node:readline/promises`.\n * esbuild strips the `node:` prefix from builtin imports when it bundles this\n * package, and a bare `readline/promises` is not on its builtin list — so the\n * subpath version resolves fine here but fails every integration's build the\n * moment they inline the engine. A callback wrapped in a Promise costs four\n * lines and cannot break a downstream bundle.\n */\nasync function prompt(question: string): Promise<string> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout })\n try {\n const answer = await new Promise<string>((resolve) => rl.question(question, resolve))\n return answer.trim()\n } finally {\n rl.close()\n }\n}\n\ninterface ApiErrorLike {\n code?: string\n message?: string\n}\n\nfunction describeApiError(err: unknown, invocation: string): string {\n const e = (err ?? {}) as ApiErrorLike\n const code = typeof e.code === 'string' ? e.code : undefined\n const message = typeof e.message === 'string' ? e.message : String(err)\n switch (code) {\n case 'HANDLE_TAKEN':\n return 'That handle is already taken — pick another and re-run.'\n case 'EMAIL_TAKEN':\n return `This email already has an active agent. Use \\`${invocation} login\\` with its key, or \\`${invocation} recover --email <email>\\` to re-key it.`\n case 'EMAIL_EXHAUSTED':\n return 'This email has used its lifetime maximum of 3 registrations.'\n case 'INVALID_HANDLE':\n return 'The server rejected the handle (invalid or reserved word).'\n case 'INVALID_CODE':\n return `Wrong or expired code. Re-check the 6 digits; after too many misses you must restart with \\`${invocation} register\\`.`\n case 'EXPIRED':\n return `This registration expired (codes last 10 minutes). Start over with \\`${invocation} register\\`.`\n default:\n return code ? `${code}: ${message}` : message\n }\n}\n\nconst RESTART_HINT =\n 'Your messaging tools pick this up immediately — no restart needed. (If a send still says NOT_REGISTERED, you’re on an older MCP; start a fresh session once to refresh it.)'\n\nexport interface RegisterOpts {\n email?: string\n handle?: string\n displayName?: string\n description?: string\n code?: string\n apiBase?: string\n}\n\nexport interface DoctorOpts {\n fix?: boolean\n}\n\nexport interface IdentityCommands {\n runRegister(opts: RegisterOpts): Promise<number>\n runLogin(opts: { apiKey?: string; apiBase?: string }): Promise<number>\n runRecover(opts: { email?: string; code?: string; apiBase?: string }): Promise<number>\n runStatus(opts: { json?: boolean }): Promise<number>\n runLogout(): number\n runDoctor(opts?: DoctorOpts): Promise<number>\n}\n\n/** Build the identity command set for one coding agent. */\nexport function createIdentityCommands(profile: HostProfile): IdentityCommands {\n const invocation = (): string => profile.invocation()\n const apiErr = (err: unknown): string => describeApiError(err, invocation())\n const LABEL = profile.label\n\n /** Write THIS agent's anchor. Only ever touches `profile.anchorFile()`. */\n function writeOurAnchor(handle: string): string[] {\n const file = profile.anchorFile()\n const label = anchorLabelOf(profile)\n // A host that wires itself separately must not be handed an identity block\n // before that wiring exists — it would announce a handle with nothing\n // listening. Once an anchor is already there, keep it current regardless.\n if (profile.isWired !== undefined && !profile.isWired() && !hasAnchorAt(file)) return []\n try {\n writeAnchor(file, profile.renderAnchor(handle), handle)\n return [` ${label}: @${handle} → ${file}`]\n } catch (err) {\n return [` ${label}: FAILED — ${String(err)}`]\n }\n }\n\n async function runRegister(opts: RegisterOpts): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n\n // Completion leg\n if (opts.code !== undefined) {\n const code = opts.code.trim()\n if (!/^\\d{6}$/.test(code)) {\n console.error('The code is the 6-digit number from the verification email.')\n return 1\n }\n const pending = readPending(home)\n if (pending === null) {\n console.error(\n `No registration in progress. Start with: ${invocation()} register --email <email> --handle <handle>`,\n )\n return 1\n }\n if (pending.kind === 'recover') {\n console.error(\n `The pending code belongs to an account RECOVERY — complete it with: ${invocation()} recover --code ${code}`,\n )\n return 1\n }\n const pendingHandle = pending.handle\n if (pendingHandle === undefined) {\n clearPending(home)\n console.error(`Pending registration was corrupt — start again with: ${invocation()} register`)\n return 1\n }\n try {\n const result = await AgentChatClient.verify(pending.pending_id, code, {\n baseUrl: pending.api_base ?? apiBase,\n })\n writeCredentials(home, {\n api_key: result.apiKey,\n handle: pendingHandle,\n ...(pending.api_base ? { api_base: pending.api_base } : {}),\n created_at: new Date().toISOString(),\n })\n clearPending(home)\n // They have an identity now, so a previous \"not now\" is spent. If they\n // ever sign out, the setup offer should be free to appear again.\n clearOfferDeclined(home)\n console.log(\n [\n `Registered: @${pendingHandle} for ${LABEL}.`,\n `API key stored at ${credentialsPath(home)} (never commit this file).`,\n ...writeOurAnchor(pendingHandle),\n '',\n `This handle belongs to your ${LABEL} agent. Another coding agent on this machine is a separate peer with its own handle — you can DM each other.`,\n `Other agents can DM you at @${pendingHandle}. Check \\`${invocation()} status\\` any time.`,\n RESTART_HINT,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Verification failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n // Initiation leg. The gate is about THIS agent only — another coding agent\n // having an identity is irrelevant and must never block this one.\n if (resolveIdentity(home) !== null) {\n console.error(\n `${LABEL} already has an AgentChat identity (see \\`${invocation()} status\\`). Run \\`${invocation()} logout\\` first to replace it.`,\n )\n return 1\n }\n const inFlight = readPending(home)\n if (inFlight?.kind === 'recover') {\n console.error(\n `An account recovery is in progress — finish it with \\`${invocation()} recover --code <code>\\`, or discard it with \\`${invocation()} logout\\` before registering.`,\n )\n return 1\n }\n\n let email = opts.email?.trim().toLowerCase()\n let handle = opts.handle?.trim().toLowerCase()\n const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true\n\n if (!email) {\n if (!interactive) {\n console.error(`Missing --email. Usage: ${invocation()} register --email <email> --handle <handle>`)\n return 1\n }\n email = (await prompt('Email for verification codes: ')).toLowerCase()\n }\n if (!handle) {\n if (!interactive) {\n console.error(`Missing --handle. Usage: ${invocation()} register --email <email> --handle <handle>`)\n return 1\n }\n handle = (await prompt('Desired handle (3–30 chars, e.g. sanim-dev): ')).toLowerCase()\n }\n\n if (!email.includes('@')) {\n console.error(`\"${email}\" does not look like an email address.`)\n return 1\n }\n if (!validHandle(handle)) {\n console.error(\n `Handle \"@${handle}\" is invalid. Rules: 3–30 characters, lowercase letters/digits/hyphens, must start with a letter, no trailing or doubled hyphens.`,\n )\n return 1\n }\n\n try {\n const result = await AgentChatClient.register({\n email,\n handle,\n ...(opts.displayName ? { display_name: opts.displayName } : {}),\n ...(opts.description ? { description: opts.description } : {}),\n baseUrl: apiBase,\n })\n writePending(home, {\n kind: 'register',\n pending_id: result.pending_id,\n email,\n handle,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n console.log(\n [\n `Verification code sent to ${email} (valid ~10 minutes).`,\n `Complete with: ${invocation()} register --code <6-digit-code>`,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Registration failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runLogin(opts: { apiKey?: string; apiBase?: string }): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n let apiKey = opts.apiKey?.trim()\n\n if (!apiKey) {\n if (process.stdin.isTTY !== true) {\n console.error(`Missing --api-key. Usage: ${invocation()} login --api-key ac_live_…`)\n return 1\n }\n apiKey = await prompt('AgentChat API key (ac_…): ')\n }\n if (apiKey.length < 20) {\n console.error('That does not look like an AgentChat API key (too short).')\n return 1\n }\n\n try {\n const client = new AgentChatClient({ apiKey, baseUrl: apiBase })\n const me = await client.getMe()\n writeCredentials(home, {\n api_key: apiKey,\n handle: me.handle,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n clearOfferDeclined(home)\n console.log([`Signed in as @${me.handle} for ${LABEL}.`, ...writeOurAnchor(me.handle), RESTART_HINT].join('\\n'))\n return 0\n } catch (err) {\n console.error(`Login failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runRecover(opts: { email?: string; code?: string; apiBase?: string }): Promise<number> {\n const home = profile.home()\n const apiBase = opts.apiBase ?? process.env['AGENTCHAT_API_BASE'] ?? DEFAULT_API_BASE\n\n if (opts.code !== undefined) {\n const code = opts.code.trim()\n if (!/^\\d{6}$/.test(code)) {\n console.error('The code is the 6-digit number from the recovery email.')\n return 1\n }\n const pending = readPending(home)\n if (pending === null || pending.kind !== 'recover') {\n console.error(`No recovery in progress. Start with: ${invocation()} recover --email <email>`)\n return 1\n }\n try {\n const result = await AgentChatClient.recoverVerify(pending.pending_id, code, {\n baseUrl: pending.api_base ?? apiBase,\n })\n writeCredentials(home, {\n api_key: result.apiKey,\n handle: result.handle,\n ...(pending.api_base ? { api_base: pending.api_base } : {}),\n created_at: new Date().toISOString(),\n })\n clearPending(home)\n clearOfferDeclined(home)\n console.log(\n [\n `Recovered: @${result.handle} for ${LABEL} — a fresh API key is stored (the old key is now revoked).`,\n ...writeOurAnchor(result.handle),\n RESTART_HINT,\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Recovery failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n let email = opts.email?.trim().toLowerCase()\n if (!email) {\n if (process.stdin.isTTY !== true) {\n console.error(`Missing --email. Usage: ${invocation()} recover --email <email>`)\n return 1\n }\n email = (await prompt('Email the agent was registered with: ')).toLowerCase()\n }\n if (!email.includes('@')) {\n console.error(`\"${email}\" does not look like an email address.`)\n return 1\n }\n\n try {\n const result = await AgentChatClient.recover(email, { baseUrl: apiBase })\n if (!result.pending_id) {\n console.log('If an agent is registered with that email, a recovery code was sent to it.')\n return 0\n }\n writePending(home, {\n kind: 'recover',\n pending_id: result.pending_id,\n email,\n ...(apiBase !== DEFAULT_API_BASE ? { api_base: apiBase } : {}),\n created_at: new Date().toISOString(),\n })\n console.log(\n [\n 'Recovery code sent (valid ~10 minutes).',\n `Complete with: ${invocation()} recover --code <6-digit-code>`,\n 'Note: completing recovery rotates the API key — anything using the old key stops working.',\n ].join('\\n'),\n )\n return 0\n } catch (err) {\n console.error(`Recovery failed. ${apiErr(err)}`)\n return 1\n }\n }\n\n async function runStatus(opts: { json?: boolean }): Promise<number> {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const identity = resolveIdentity(home)\n const pending = readPending(home)\n\n if (identity === null) {\n if (opts.json) {\n console.log(\n JSON.stringify({ configured: false, pending: pending !== null, pending_kind: pending?.kind ?? null }),\n )\n } else if (pending?.kind === 'recover') {\n console.log(\n `No identity yet, but an account recovery is waiting on its emailed code — finish with: ${invocation()} recover --code <code>`,\n )\n } else if (pending !== null) {\n console.log(\n `No identity yet, but a registration for @${pending.handle ?? '?'} is waiting on its emailed code — finish with: ${invocation()} register --code <code>`,\n )\n } else {\n console.log(`No AgentChat identity for this ${LABEL} agent. Set one up with: ${invocation()} register`)\n }\n return 0\n }\n\n try {\n const client = new AgentChatClient({ apiKey: identity.apiKey, baseUrl: identity.apiBase })\n const me = await client.getMe()\n const rows = await syncPeek({ apiKey: identity.apiKey, apiBase: identity.apiBase }, { limit: 100 })\n const unread = rows.length === 100 ? '100+' : String(rows.length)\n\n if (opts.json) {\n console.log(\n JSON.stringify({\n configured: true,\n // Was hard-coded, and the Claude Code copy said 'codex'.\n host: profile.id,\n handle: me.handle,\n status: me.status ?? 'unknown',\n unread: rows.length,\n unread_capped: rows.length === 100,\n key_source: identity.source,\n api_base: identity.apiBase,\n home,\n anchor: hasAnchorAt(anchorFile),\n }),\n )\n } else {\n console.log(\n [\n `@${me.handle} — ${me.status ?? 'active'} (${LABEL})`,\n `Unread: ${unread} message(s) queued`,\n `Key source: ${identity.source} (${identity.source === 'file' ? credentialsPath(home) : 'AGENTCHAT_API_KEY'})`,\n `API: ${identity.apiBase}`,\n `Anchor: ${hasAnchorAt(anchorFile) ? 'yes' : 'no'} (${anchorFile})`,\n ].join('\\n'),\n )\n }\n return 0\n } catch (err) {\n console.error(`Could not reach AgentChat: ${apiErr(err)}`)\n return 1\n }\n }\n\n /**\n * Sign out THIS agent and remove THIS agent's wiring. There is no `--all`,\n * because a profile has no way to reach another agent — that is the point.\n */\n function runLogout(): number {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const reports: string[] = []\n let any = false\n\n if (clearCredentials(home)) {\n any = true\n reports.push(' credentials deleted')\n }\n if (profile.removeWiring !== undefined) {\n try {\n const removed = profile.removeWiring()\n if (removed.length > 0) reports.push(` removed ${removed.join(', ')}`)\n } catch {\n reports.push(` could not fully clean up the ${LABEL} wiring`)\n }\n }\n if (removeAnchorAt(anchorFile) === 'removed') {\n reports.push(` ${anchorLabelOf(profile)} anchor removed`)\n }\n\n console.log(\n [\n any ? `Signed out of ${LABEL}.` : 'Nothing to sign out of.',\n ...reports,\n ...(any\n ? [\n 'Any other coding agent on this machine is untouched — it is a separate AgentChat agent with its own handle.',\n ...(profile.logoutHints?.() ?? []),\n ]\n : []),\n ].join('\\n'),\n )\n return 0\n }\n\n async function runDoctor(opts: DoctorOpts = {}): Promise<number> {\n const home = profile.home()\n const anchorFile = profile.anchorFile()\n const checks: DoctorCheck[] = []\n\n checks.push({ name: 'node', verdict: 'PASS', detail: process.version })\n checks.push({ name: 'home', verdict: 'PASS', detail: home })\n\n const creds = readCredentials(home)\n if (creds === null) {\n checks.push({\n name: 'credentials',\n verdict: 'FAIL',\n detail: `no identity at ${credentialsPath(home)} — run \\`${invocation()} register\\``,\n })\n } else {\n checks.push({ name: 'credentials', verdict: 'PASS', detail: `@${creds.handle}` })\n const identity = resolveIdentity(home)\n if (identity !== null) {\n try {\n const client = new AgentChatClient({ apiKey: identity.apiKey, baseUrl: identity.apiBase })\n const started = Date.now()\n const me = await client.getMe()\n const verdict: Verdict = (me.status ?? 'active') === 'active' ? 'PASS' : 'WARN'\n checks.push({\n name: 'api-auth',\n verdict,\n detail: `@${me.handle} status=${me.status ?? 'active'} (${Date.now() - started}ms)`,\n })\n if (me.handle !== creds.handle) {\n checks.push({\n name: 'handle-drift',\n verdict: 'WARN',\n detail: `credentials say @${creds.handle} but the key authenticates as @${me.handle} — re-run \\`${invocation()} login\\``,\n })\n }\n } catch (err) {\n checks.push({ name: 'api-auth', verdict: 'FAIL', detail: `getMe failed: ${String(err)}` })\n }\n }\n\n // The anchor must name THIS agent. Releases of the old shared CLI wrote\n // the anchor for every host on the machine whenever any one registered,\n // so a two-agent box could end up with an instruction file announcing the\n // OTHER agent's handle — telling peers to DM an address that reaches\n // someone else.\n const claimed = readAnchorHandleAt(anchorFile)\n if (claimed === creds.handle) {\n checks.push({ name: 'anchor', verdict: 'PASS', detail: `@${claimed} in ${anchorFile}` })\n } else {\n const why =\n claimed === null\n ? `no identity block in ${anchorFile}`\n : `${anchorFile} says @${claimed} but this agent is @${creds.handle}`\n if (opts.fix === true) {\n const report = writeOurAnchor(creds.handle)\n const failed = report.some((l) => l.includes('FAILED'))\n checks.push({\n name: 'anchor',\n verdict: failed ? 'FAIL' : 'PASS',\n detail: failed ? `could not repair: ${report.join('; ')}` : `repaired → @${creds.handle}`,\n })\n } else {\n checks.push({\n name: 'anchor',\n verdict: 'WARN',\n detail: `${why} — repair with \\`${invocation()} doctor --fix\\``,\n })\n }\n }\n }\n\n if (profile.extraDoctorChecks !== undefined) checks.push(...profile.extraDoctorChecks())\n\n console.log(checks.map((c) => `${c.verdict.padEnd(4)} ${c.name}: ${c.detail}`).join('\\n'))\n return checks.some((c) => c.verdict === 'FAIL') ? 1 : 0\n }\n\n return { runRegister, runLogin, runRecover, runStatus, runLogout, runDoctor }\n}\n","export type Verdict = 'PASS' | 'WARN' | 'FAIL'\n\nexport interface DoctorCheck {\n name: string\n verdict: Verdict\n detail: string\n}\n\n/**\n * Everything the shared identity commands need to know about ONE coding agent.\n *\n * This is the seam. The register / login / recover / status / logout / doctor\n * flows are the same everywhere because they are a contract with the AgentChat\n * server; what differs per host is only what is described here. An integration\n * builds one of these and gets the commands back.\n *\n * Note what is NOT here: any way to name a *different* host. A profile\n * describes the caller and nothing else, so a command built from it cannot\n * reach another agent's files. That is the same property the per-repo split\n * gives, kept intact while the flow itself is shared once.\n *\n * The path-ish fields are functions, not strings: `CODEX_HOME` and friends are\n * read per call, and evaluating them at module load would freeze a value the\n * user can still change.\n */\nexport interface HostProfile {\n /** Human name for output, e.g. `Claude Code`. */\n label: string\n /** Stable machine identifier for `--json`, e.g. `claude-code`. */\n id: string\n /** THE identity home for this agent. */\n home(): string\n /** This host's always-loaded instruction file. */\n anchorFile(): string\n /** Exactly what a user types to reach this integration. */\n invocation(): string\n /** Render the anchor block body this host wants. */\n renderAnchor(handle: string): string\n /**\n * What to call the anchor in output. Defaults to the instruction file's own\n * basename (`CLAUDE.md`, `AGENTS.md`), which is what a user actually looks\n * for on disk.\n */\n anchorLabel?: string\n /**\n * Whether this host is wired up enough for an anchor to mean anything.\n * A host that must edit config files first (Codex) uses this so it never\n * writes an identity block announcing a phone number with nothing to answer\n * it. Hosts wired by their own installer (a Claude Code plugin) omit it.\n */\n isWired?(): boolean\n /** Extra teardown on logout; returns descriptions of what was removed. */\n removeWiring?(): string[]\n /** Host-specific doctor checks, appended after the shared ones. */\n extraDoctorChecks?(): DoctorCheck[]\n /** Extra lines printed after a successful logout. */\n logoutHints?(): string[]\n}\n\n/** The label to use for this host's anchor in user-facing output. */\nexport function anchorLabelOf(profile: HostProfile): string {\n if (profile.anchorLabel !== undefined) return profile.anchorLabel\n const file = profile.anchorFile()\n const slash = Math.max(file.lastIndexOf('/'), file.lastIndexOf('\\\\'))\n return slash >= 0 ? file.slice(slash + 1) : file\n}\n","import * as fs from 'node:fs'\nimport * as os from 'node:os'\nimport * as path from 'node:path'\nimport { spawnSync, spawn } from 'node:child_process'\nimport { log } from '../util/log.js'\n\n// ─── Service lifecycle (systemd / launchd / Windows Startup) ─────────────────\n//\n// `agentchatd start` only runs while the shell is open — no good for a 24/7\n// consumer product. `install` registers the daemon as a real background\n// service that starts on boot/login and restarts on crash:\n// * Linux (native) → a systemd USER unit + `loginctl enable-linger`.\n// * macOS → a launchd LaunchAgent with KeepAlive + RunAtLoad.\n// * Windows / WSL2 → a hidden, restart-on-exit VBScript in the user's Startup\n// folder (no admin). See the Windows section below.\n// One service per INTEGRATION (the leader lock already enforces one daemon per\n// identity on a box). The caller supplies the label and the env to capture —\n// this module has no idea which coding agents exist, which is what keeps two\n// integrations' services from ever colliding on a name or clobbering each\n// other's unit file. Convention is `agentchatd-<integration>`.\n\n/**\n * Enough to ADDRESS an already-installed service. Removing one or reading its\n * status needs only its name — deliberately a narrower type than install, so\n * `uninstall` and `status` cannot be made to depend on a daemon entry they have\n * no use for.\n */\nexport interface ServiceRef {\n /** Unit/service name, e.g. `agentchatd-codex`. Must be unique per integration. */\n label: string\n home: string\n}\n\nexport interface ServiceOpts extends ServiceRef {\n /**\n * Absolute path to the integration's DAEMON bundle — the script this service\n * runs. Required, and deliberately not defaulted.\n *\n * It used to default to `process.argv[1]`, i.e. whichever binary happened to\n * call `install`. That was the CLI, which has no daemon in it, so every\n * installed unit ran a command that exited 1 and then restart-looped forever\n * under `KeepAlive`/`Restart=on-failure` while the CLI cheerfully reported\n * \"Always-on is ON\". Making the caller name the entry is what stops a service\n * from being installed against a script that cannot serve it.\n *\n * Must also be a STABLE path. A plugin's own bundle lives in a\n * version-scoped cache directory that the next upgrade deletes, so an\n * integration distributed that way copies itself somewhere durable first and\n * passes that copy here.\n */\n entry: string\n /** Host-specific env the service must see because a systemd/launchd service\n * does NOT inherit the login shell (e.g. CODEX_HOME). PATH is captured\n * automatically. */\n env?: Record<string, string>\n}\n\ninterface Plan {\n label: string // service/unit name\n node: string // absolute node path\n bin: string // absolute daemon entry, supplied by the integration\n home: string\n /** Host env captured from the current shell so the service sees the same\n * CODEX_HOME / CLAUDE_CONFIG_DIR the user installed under. */\n env: Record<string, string>\n}\n\nexport function planForTest(opts: ServiceOpts): Plan {\n return plan(opts)\n}\n\nfunction plan(opts: ServiceOpts): Plan {\n const env: Record<string, string> = {}\n // CRITICAL: a systemd/launchd service does NOT inherit the login shell's\n // PATH, so it can't find `claude` / `codex` / `npx` (often in ~/.local/bin\n // or a version-manager dir). Capture the installer's PATH so the service\n // resolves the same binaries the user does. (Without this the daemon exits\n // \"claude CLI not found on PATH\" and restart-loops.)\n if (process.env['PATH']) env['PATH'] = process.env['PATH']\n // Whatever host env the integration says its adapter needs.\n for (const [k, v] of Object.entries(opts.env ?? {})) {\n if (typeof v === 'string' && v.length > 0) env[k] = v\n }\n return {\n label: opts.label,\n node: process.execPath,\n bin: path.resolve(opts.entry),\n home: path.resolve(opts.home),\n env,\n }\n}\n\nfunction run(cmd: string, args: string[]): { ok: boolean; out: string } {\n const r = spawnSync(cmd, args, { encoding: 'utf-8' })\n return { ok: !r.error && r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() }\n}\n\n/**\n * Write the unit file but do NOT hand it to the OS service manager.\n *\n * `HOME` sandboxes where the unit file lands, but it does not sandbox\n * `launchctl` or `systemctl` — those always address the real user's domain. So\n * a test that exercises `daemon install` registers a REAL service on the\n * developer's machine, pointed at a temp directory that is about to be deleted.\n * That happened, and it left two loaded launchd jobs behind.\n *\n * With this set, everything up to registration still runs and can be asserted\n * on; only the escaping side effect is skipped.\n */\nfunction registrationSkipped(): boolean {\n const v = process.env['AGENTCHAT_SERVICE_DRY_RUN']\n return v !== undefined && v !== '' && v !== '0'\n}\n\n// ─── systemd (Linux) ─────────────────────────────────────────────────────────\n\nfunction systemdUnitPath(label: string): string {\n return path.join(os.homedir(), '.config', 'systemd', 'user', `${label}.service`)\n}\n\nfunction systemdUnit(p: Plan): string {\n const envLines = Object.entries(p.env)\n .map(([k, v]) => `Environment=${k}=${v}`)\n .join('\\n')\n return [\n '[Unit]',\n `Description=AgentChat always-on daemon (${p.label})`,\n 'After=network-online.target',\n 'Wants=network-online.target',\n '',\n '[Service]',\n 'Type=simple',\n `ExecStart=${p.node} ${p.bin} --home ${p.home}`,\n ...(envLines ? [envLines] : []),\n 'Restart=on-failure',\n 'RestartSec=5',\n '',\n '[Install]',\n 'WantedBy=default.target',\n '',\n ].join('\\n')\n}\n\nfunction installSystemd(p: Plan): void {\n const unitPath = systemdUnitPath(p.label)\n fs.mkdirSync(path.dirname(unitPath), { recursive: true })\n fs.writeFileSync(unitPath, systemdUnit(p))\n log.info(`wrote ${unitPath}`)\n if (registrationSkipped()) return\n run('systemctl', ['--user', 'daemon-reload'])\n // enable-linger keeps user services running with no active login (VPS).\n const linger = run('loginctl', ['enable-linger', os.userInfo().username])\n if (!linger.ok) {\n log.warn(`could not enable-linger (service won't survive logout until you run: sudo loginctl enable-linger ${os.userInfo().username})`)\n }\n const enabled = run('systemctl', ['--user', 'enable', '--now', p.label])\n if (!enabled.ok) throw new Error(`systemctl enable failed: ${enabled.out}`)\n log.info(`service ${p.label} enabled + started`)\n}\n\nfunction uninstallSystemd(label: string): void {\n run('systemctl', ['--user', 'disable', '--now', label])\n const unitPath = systemdUnitPath(label)\n if (fs.existsSync(unitPath)) fs.rmSync(unitPath)\n run('systemctl', ['--user', 'daemon-reload'])\n log.info(`service ${label} removed`)\n}\n\nfunction statusSystemd(label: string): string {\n const active = run('systemctl', ['--user', 'is-active', label]).out || 'unknown'\n const enabled = run('systemctl', ['--user', 'is-enabled', label]).out || 'unknown'\n return `systemd ${label}: ${active} (${enabled})`\n}\n\n// ─── launchd (macOS) ─────────────────────────────────────────────────────────\n\nfunction launchdPlistPath(label: string): string {\n return path.join(os.homedir(), 'Library', 'LaunchAgents', `${launchdLabel(label)}.plist`)\n}\nconst launchdLabel = (label: string): string => `me.agentchat.${label}`\n\nfunction launchdPlist(p: Plan): string {\n const args = [p.node, p.bin, '--home', p.home]\n const argXml = args.map((a) => ` <string>${a}</string>`).join('\\n')\n const envXml = Object.entries(p.env)\n .map(([k, v]) => ` <key>${k}</key><string>${v}</string>`)\n .join('\\n')\n const logPath = path.join(p.home, 'daemon.log')\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">',\n '<plist version=\"1.0\"><dict>',\n ` <key>Label</key><string>${launchdLabel(p.label)}</string>`,\n ' <key>ProgramArguments</key><array>',\n argXml,\n ' </array>',\n ...(envXml ? [' <key>EnvironmentVariables</key><dict>', envXml, ' </dict>'] : []),\n ' <key>RunAtLoad</key><true/>',\n ' <key>KeepAlive</key><true/>',\n ` <key>StandardErrorPath</key><string>${logPath}</string>`,\n ` <key>StandardOutPath</key><string>${logPath}</string>`,\n '</dict></plist>',\n '',\n ].join('\\n')\n}\n\nfunction installLaunchd(p: Plan): void {\n const plistPath = launchdPlistPath(p.label)\n fs.mkdirSync(path.dirname(plistPath), { recursive: true })\n fs.writeFileSync(plistPath, launchdPlist(p))\n log.info(`wrote ${plistPath}`)\n if (registrationSkipped()) return\n run('launchctl', ['unload', plistPath]) // idempotent: clear a prior load\n const loaded = run('launchctl', ['load', '-w', plistPath])\n if (!loaded.ok) throw new Error(`launchctl load failed: ${loaded.out}`)\n log.info(`service ${launchdLabel(p.label)} loaded + started`)\n}\n\nfunction uninstallLaunchd(label: string): void {\n const plistPath = launchdPlistPath(label)\n run('launchctl', ['unload', '-w', plistPath])\n if (fs.existsSync(plistPath)) fs.rmSync(plistPath)\n log.info(`service ${launchdLabel(label)} removed`)\n}\n\nfunction statusLaunchd(label: string): string {\n const r = run('launchctl', ['list', launchdLabel(label)])\n return `launchd ${launchdLabel(label)}: ${r.ok ? 'loaded' : 'not loaded'}`\n}\n\n// ─── Windows + WSL (Startup-folder launcher, no admin) ───────────────────────\n//\n// Windows has no systemd/launchd. For \"runs whenever you're logged in\" — the\n// consumer case, no admin needed — we drop a hidden, restart-on-exit VBScript\n// into the user's Startup folder. Two hosts land here:\n// * native Windows → the .vbs runs `node <daemon> start …` directly.\n// * WSL2 → the daemon lives in Linux, but the WSL VM shuts down when\n// the last terminal closes, so a Linux service can't stay\n// up. Instead the launcher goes in the WINDOWS Startup\n// folder (reached from WSL via /mnt/c) and runs\n// `wsl.exe -e bash <script>`, which boots the VM at Windows\n// login and runs the daemon inside it.\n// A \"master\" copy under ~/.agentchat/daemon is the installed-marker; the copy in\n// Startup is the enabled state (disable removes it, keeps the master — same\n// toggle semantics as systemd enable/disable).\n//\n// The file mechanics + generators are unit-tested and verified on a real\n// windows-latest CI runner. The live runtime (hidden loop, WSL boot, process\n// kill) still needs a real user machine — called out in the release notes.\n\nexport function isWslFromVersion(version: string): boolean {\n return /microsoft|wsl/i.test(version)\n}\nfunction isWsl(): boolean {\n if (process.platform !== 'linux') return false\n try {\n return isWslFromVersion(fs.readFileSync('/proc/version', 'utf-8'))\n } catch {\n return false\n }\n}\ntype WinMode = 'win32' | 'wsl'\nfunction winMode(): WinMode | null {\n if (process.platform === 'win32') return 'win32'\n if (isWsl()) return 'wsl'\n return null\n}\n\nfunction winMasterDir(): string {\n return path.join(os.homedir(), '.agentchat', 'daemon')\n}\nfunction winStartupDir(mode: WinMode): string {\n if (mode === 'win32') {\n return path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')\n }\n // WSL: translate the Windows %APPDATA% into a /mnt/c path.\n const appdata = run('cmd.exe', ['/c', 'echo %APPDATA%']).out.split(/\\r?\\n/).pop()?.trim() ?? ''\n const base = run('wslpath', ['-u', appdata]).out.trim()\n if (!base) throw new Error('could not locate the Windows Startup folder from WSL (is /mnt/c mounted?)')\n return path.join(base, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')\n}\n\nconst vbsEscape = (s: string): string => s.replace(/\"/g, '\"\"')\nconst shQuote = (s: string): string => `'${s.replace(/'/g, `'\\\\''`)}'`\n\n/** Hidden, restart-on-exit VBScript launcher. `command` is the full command\n * line; `env` is set on the launcher process (native Windows only). */\nexport function launcherVbs(command: string, env: Record<string, string>): string {\n const envLines = Object.entries(env).map(\n ([k, v]) => `sh.Environment(\"Process\").Item(\"${k}\") = \"${vbsEscape(v)}\"`,\n )\n return (\n [\n \"' AgentChat always-on launcher — runs hidden, restarts on exit.\",\n 'Set sh = CreateObject(\"WScript.Shell\")',\n ...envLines,\n 'Do',\n ` sh.Run \"${vbsEscape(command)}\", 0, True`,\n ' WScript.Sleep 5000',\n 'Loop',\n ].join('\\r\\n') + '\\r\\n'\n )\n}\n\n/** Native-Windows launch command. */\nexport function winCommandNative(p: Plan): string {\n return `\"${p.node}\" \"${p.bin}\" --home \"${p.home}\"`\n}\n\n/** The bash script WSL runs — a login-shell env plus the daemon. */\nexport function wslScriptContent(p: Plan): string {\n const exports = Object.entries(p.env).map(([k, v]) => `export ${k}=${shQuote(v)}`)\n return (\n ['#!/bin/bash', ...exports, `exec ${shQuote(p.node)} ${shQuote(p.bin)} --home ${shQuote(p.home)}`].join(\n '\\n',\n ) + '\\n'\n )\n}\n\nfunction startDetached(cmd: string, args: string[]): void {\n // Escape hatch for tests/headless installs: write the Startup entry but don't\n // actually launch the loop now (it still starts at next login).\n if (process.env['AGENTCHATD_SERVICE_NO_START'] === '1') return\n try {\n spawn(cmd, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref()\n } catch (err) {\n log.warn(`could not start the launcher now (it starts at next login): ${String(err)}`)\n }\n}\nfunction winPathFromWsl(linuxPath: string): string {\n return run('wslpath', ['-w', linuxPath]).out.trim() || linuxPath\n}\nfunction killLauncher(label: string, mode: WinMode): void {\n // Best-effort: stop the running wscript launcher, matched by our .vbs name.\n const ps = `Get-CimInstance Win32_Process -Filter \"Name='wscript.exe'\" | Where-Object { $_.CommandLine -like '*${label}.vbs*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`\n run(mode === 'win32' ? 'powershell' : 'powershell.exe', ['-NoProfile', '-Command', ps])\n}\n\nfunction installWindows(p: Plan, mode: WinMode): void {\n const master = winMasterDir()\n fs.mkdirSync(master, { recursive: true })\n const masterVbs = path.join(master, `${p.label}.vbs`)\n if (mode === 'win32') {\n fs.writeFileSync(masterVbs, launcherVbs(winCommandNative(p), p.env))\n } else {\n const scriptPath = path.join(master, `${p.label}.sh`)\n fs.writeFileSync(scriptPath, wslScriptContent(p), { mode: 0o755 })\n const distro = process.env['WSL_DISTRO_NAME'] ?? ''\n if (!distro) throw new Error('WSL_DISTRO_NAME is not set — cannot target the right WSL distro')\n fs.writeFileSync(masterVbs, launcherVbs(`wsl.exe -d ${distro} -e bash \"${scriptPath}\"`, {}))\n }\n log.info(`wrote ${masterVbs}`)\n enableWindows(p.label, mode)\n log.info(`service ${p.label} installed (starts at login)`)\n}\nfunction enableWindows(label: string, mode: WinMode): void {\n const masterVbs = path.join(winMasterDir(), `${label}.vbs`)\n if (!fs.existsSync(masterVbs)) throw new Error(`no ${label} daemon installed — run install first`)\n const startup = winStartupDir(mode)\n fs.mkdirSync(startup, { recursive: true })\n const startupVbs = path.join(startup, `${label}.vbs`)\n fs.copyFileSync(masterVbs, startupVbs)\n if (registrationSkipped()) return\n if (mode === 'win32') startDetached('wscript.exe', [startupVbs])\n else startDetached('cmd.exe', ['/c', 'start', '', '/b', 'wscript.exe', winPathFromWsl(startupVbs)])\n}\nfunction disableWindows(label: string, mode: WinMode): void {\n const startupVbs = path.join(winStartupDir(mode), `${label}.vbs`)\n if (fs.existsSync(startupVbs)) fs.rmSync(startupVbs)\n killLauncher(label, mode)\n}\nfunction uninstallWindows(label: string, mode: WinMode): void {\n try {\n disableWindows(label, mode)\n } catch {\n /* startup dir may be unreadable; still clear the master below */\n }\n for (const f of [`${label}.vbs`, `${label}.sh`]) {\n const m = path.join(winMasterDir(), f)\n if (fs.existsSync(m)) fs.rmSync(m)\n }\n}\nfunction statusWindows(label: string, mode: WinMode): string {\n const installed = fs.existsSync(path.join(winMasterDir(), `${label}.vbs`))\n let enabled = false\n try {\n enabled = fs.existsSync(path.join(winStartupDir(mode), `${label}.vbs`))\n } catch {\n /* startup dir unresolved */\n }\n const host = mode === 'wsl' ? 'wsl' : 'windows'\n return `${host} ${label}: ${installed ? (enabled ? 'enabled' : 'installed (disabled)') : 'not installed'}`\n}\n\n// ─── platform dispatch ───────────────────────────────────────────────────────\n\nexport function installService(opts: ServiceOpts): void {\n const p = plan(opts)\n if (!p.bin) throw new Error('no daemon entrypoint given (ServiceOpts.entry)')\n const wm = winMode()\n if (wm) return installWindows(p, wm)\n if (process.platform === 'linux') return installSystemd(p) // native Linux (not WSL)\n if (process.platform === 'darwin') return installLaunchd(p)\n throw new Error(`service install is not supported on ${process.platform} — run \\`agentchatd start\\` under your own supervisor`)\n}\n\nexport function uninstallService(opts: ServiceRef): void {\n const label = opts.label\n const wm = winMode()\n if (wm) return uninstallWindows(label, wm)\n if (process.platform === 'linux') return uninstallSystemd(label)\n if (process.platform === 'darwin') return uninstallLaunchd(label)\n throw new Error(`service uninstall is not supported on ${process.platform}`)\n}\n\nexport function serviceStatus(opts: ServiceRef): string {\n const label = opts.label\n const wm = winMode()\n if (wm) return statusWindows(label, wm)\n if (process.platform === 'linux') return statusSystemd(label)\n if (process.platform === 'darwin') return statusLaunchd(label)\n return `service management not supported on ${process.platform}`\n}\n\n// ─── enable / disable (the away-replies opt-out) ─────────────────────────────\n//\n// Toggle the service's run-state WITHOUT uninstalling — so a user who wants\n// \"reply only while I'm in a session\" flips the daemon off, and back on later,\n// with one word. The unit/plist file stays on disk either way, so re-enabling\n// is instant. `install` already leaves it enabled (always-on by default);\n// these just turn that on and off after the fact.\n\nfunction unitInstalled(label: string): boolean {\n if (winMode()) return fs.existsSync(path.join(winMasterDir(), `${label}.vbs`))\n if (process.platform === 'linux') return fs.existsSync(systemdUnitPath(label))\n if (process.platform === 'darwin') return fs.existsSync(launchdPlistPath(label))\n return false\n}\n\nexport function disableService(opts: ServiceOpts): void {\n const label = opts.label\n if (!unitInstalled(label)) {\n throw new Error(`no ${opts.label} daemon installed — nothing to disable (run install first)`)\n }\n const wm = winMode()\n if (wm) {\n disableWindows(label, wm) // drop the Startup copy, keep the master\n } else if (process.platform === 'linux') {\n // Stops it AND removes the start-on-boot link; the unit file stays.\n run('systemctl', ['--user', 'disable', '--now', label])\n } else if (process.platform === 'darwin') {\n run('launchctl', ['unload', launchdPlistPath(label)]) // plist stays on disk\n } else {\n throw new Error(`not supported on ${process.platform}`)\n }\n log.info(`daemon ${label} disabled — session-only until you re-enable`)\n}\n\nexport function enableService(opts: ServiceOpts): void {\n const label = opts.label\n if (!unitInstalled(label)) {\n throw new Error(`no ${opts.label} daemon installed — run install first`)\n }\n const wm = winMode()\n if (wm) {\n enableWindows(label, wm) // copy the master back into Startup + start now\n } else if (process.platform === 'linux') {\n run('systemctl', ['--user', 'enable', '--now', label])\n } else if (process.platform === 'darwin') {\n run('launchctl', ['load', '-w', launchdPlistPath(label)])\n } else {\n throw new Error(`not supported on ${process.platform}`)\n }\n log.info(`daemon ${label} enabled — always-on while this machine is up`)\n}\n"],"mappings":"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","names":["resolve","fs","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentchatme/agent-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "Shared host-agnostic engine for AgentChat coding-agent integrations \u2014 wire protocol, identity storage, session digest, hook state machine, always-on daemon internals. A library, not a CLI: every function takes an identity home, none resolves one.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|