@agentchatme/agent-core 0.0.13131 → 0.0.13131111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -11
- package/dist/{chunk-M2X5WY7Q.js → chunk-Y4REWBSV.js} +2 -2
- package/dist/chunk-Y4REWBSV.js.map +1 -0
- package/dist/daemon-entry.js +40 -2
- package/dist/daemon-entry.js.map +1 -1
- package/dist/index.d.ts +41 -17
- package/dist/index.js +113 -53
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-M2X5WY7Q.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
3
|
|
|
3
4
|
/** Low-cardinality identity attached to every coding-agent API operation. */
|
|
4
5
|
declare const CODING_AGENTS_CLIENT_IDENTITY: {
|
|
5
6
|
readonly name: "coding_agents";
|
|
6
|
-
readonly version: "0.0.
|
|
7
|
+
readonly version: "0.0.13131111";
|
|
7
8
|
};
|
|
8
9
|
/** Headers for raw HTTP and WebSocket transports that bypass the SDK. */
|
|
9
10
|
declare const CODING_AGENTS_CLIENT_HEADERS: Readonly<Record<string, string>>;
|
|
10
11
|
|
|
11
12
|
/** Published package version, kept in lockstep with package.json by tests. */
|
|
12
|
-
declare const VERSION = "0.0.
|
|
13
|
+
declare const VERSION = "0.0.13131111";
|
|
13
14
|
|
|
14
15
|
declare const SyncRowSchema: z.ZodObject<{
|
|
15
16
|
id: z.ZodString;
|
|
@@ -193,14 +194,17 @@ declare const StateSchema: z.ZodObject<{
|
|
|
193
194
|
continuations: z.ZodNumber;
|
|
194
195
|
updated_at: z.ZodString;
|
|
195
196
|
pending_ack: z.ZodOptional<z.ZodString>;
|
|
197
|
+
pending_ack_requires_continuation: z.ZodOptional<z.ZodBoolean>;
|
|
196
198
|
}, "strip", z.ZodTypeAny, {
|
|
197
199
|
continuations: number;
|
|
198
200
|
updated_at: string;
|
|
199
201
|
pending_ack?: string | undefined;
|
|
202
|
+
pending_ack_requires_continuation?: boolean | undefined;
|
|
200
203
|
}, {
|
|
201
204
|
continuations: number;
|
|
202
205
|
updated_at: string;
|
|
203
206
|
pending_ack?: string | undefined;
|
|
207
|
+
pending_ack_requires_continuation?: boolean | undefined;
|
|
204
208
|
}>>>;
|
|
205
209
|
last_offer_at: z.ZodOptional<z.ZodString>;
|
|
206
210
|
offer_declined_at: z.ZodOptional<z.ZodString>;
|
|
@@ -209,6 +213,7 @@ declare const StateSchema: z.ZodObject<{
|
|
|
209
213
|
continuations: number;
|
|
210
214
|
updated_at: string;
|
|
211
215
|
pending_ack?: string | undefined;
|
|
216
|
+
pending_ack_requires_continuation?: boolean | undefined;
|
|
212
217
|
}>;
|
|
213
218
|
last_offer_at?: string | undefined;
|
|
214
219
|
offer_declined_at?: string | undefined;
|
|
@@ -217,6 +222,7 @@ declare const StateSchema: z.ZodObject<{
|
|
|
217
222
|
continuations: number;
|
|
218
223
|
updated_at: string;
|
|
219
224
|
pending_ack?: string | undefined;
|
|
225
|
+
pending_ack_requires_continuation?: boolean | undefined;
|
|
220
226
|
}> | undefined;
|
|
221
227
|
last_offer_at?: string | undefined;
|
|
222
228
|
offer_declined_at?: string | undefined;
|
|
@@ -232,8 +238,8 @@ declare function recordContinuation(home: string, sessionKey: string, now?: Date
|
|
|
232
238
|
* should be allowed to pick messages up again.
|
|
233
239
|
*/
|
|
234
240
|
declare function resetSession(home: string, sessionKey: string): void;
|
|
235
|
-
declare function setPendingAck(home: string, sessionKey: string, cursor: string, now?: Date): void;
|
|
236
|
-
/** Read-and-clear the pending cursor for a session (
|
|
241
|
+
declare function setPendingAck(home: string, sessionKey: string, cursor: string, now?: Date, requiresContinuation?: boolean): void;
|
|
242
|
+
/** Read-and-clear the pending cursor for a session (completed-turn boundary). */
|
|
237
243
|
declare function takePendingAck(home: string, sessionKey: string, now?: Date): string | null;
|
|
238
244
|
declare function shouldOfferRegistration(home: string, now?: Date): boolean;
|
|
239
245
|
declare function recordRegistrationOffer(home: string, now?: Date): void;
|
|
@@ -309,8 +315,9 @@ declare function formatStopPickup(handle: string | null, rows: SyncRow[]): strin
|
|
|
309
315
|
* Injected at session start when always-on was set up but the daemon isn't
|
|
310
316
|
* beating (its heartbeat is stale — see alwaysOnHealth). Written in the FIRST
|
|
311
317
|
* person because the agent relays it to its user, and deliberately careful not
|
|
312
|
-
* to imply
|
|
313
|
-
*
|
|
318
|
+
* to imply that stored messages disappear: they remain in conversation
|
|
319
|
+
* history and their delivery envelopes queue within the normal retention
|
|
320
|
+
* window. The one-line fix is inline so the agent can act on it.
|
|
314
321
|
*/
|
|
315
322
|
declare function formatAlwaysOnDown(copy: HostCopy): string;
|
|
316
323
|
/**
|
|
@@ -402,6 +409,11 @@ interface HookInput {
|
|
|
402
409
|
/** Claude Code SessionStart source: startup | resume | clear | compact | fork.
|
|
403
410
|
* Undefined on other hosts/events. */
|
|
404
411
|
source: string | undefined;
|
|
412
|
+
/**
|
|
413
|
+
* Whether this Stop belongs to the continuation created by a prior Stop
|
|
414
|
+
* decision. Both harnesses expose this as `stop_hook_active`.
|
|
415
|
+
*/
|
|
416
|
+
stopHookActive?: boolean;
|
|
405
417
|
}
|
|
406
418
|
declare function readHookInput(stream?: NodeJS.ReadStream): Promise<HookInput>;
|
|
407
419
|
|
|
@@ -415,23 +427,31 @@ interface SessionStartResult {
|
|
|
415
427
|
/** Text to inject into the session, or null for "say nothing". */
|
|
416
428
|
context: string | null;
|
|
417
429
|
}
|
|
430
|
+
interface UserPromptResult {
|
|
431
|
+
/** Text to add to this prompt, or null when the inbox has nothing new. */
|
|
432
|
+
context: string | null;
|
|
433
|
+
/**
|
|
434
|
+
* Persist the surfaced cursor locally. Call only after the host has accepted
|
|
435
|
+
* `context`; the following Stop is the first boundary allowed to ACK it.
|
|
436
|
+
*/
|
|
437
|
+
stage: () => void;
|
|
438
|
+
}
|
|
418
439
|
interface StopResult {
|
|
419
440
|
/** Text to continue the session with, or null to let it stop. */
|
|
420
441
|
reason: string | null;
|
|
421
442
|
/**
|
|
422
|
-
*
|
|
423
|
-
* actually been given `reason
|
|
424
|
-
* null — it is a no-op.
|
|
443
|
+
* Persist the surfaced cursor locally. Call this ONLY after the host has
|
|
444
|
+
* actually been given `reason`. The following Stop commits it remotely.
|
|
425
445
|
*/
|
|
426
|
-
|
|
446
|
+
stage: () => void;
|
|
427
447
|
}
|
|
428
448
|
declare function hooksDisabled(): boolean;
|
|
429
449
|
declare function sessionStart(ctx: HookContext, input: HookInput): Promise<SessionStartResult>;
|
|
430
450
|
/**
|
|
431
|
-
* A prompt is
|
|
432
|
-
*
|
|
451
|
+
* A prompt is about to run. Claim and inject the inbox at this real turn
|
|
452
|
+
* boundary, then stage its cursor only after the host accepts our output.
|
|
433
453
|
*/
|
|
434
|
-
declare function userPrompt(ctx: HookContext, input: HookInput): Promise<
|
|
454
|
+
declare function userPrompt(ctx: HookContext, input: HookInput): Promise<UserPromptResult>;
|
|
435
455
|
declare function stop(ctx: HookContext, input: HookInput): Promise<StopResult>;
|
|
436
456
|
/** A host session is closing. Release only its own foreground lease. */
|
|
437
457
|
declare function sessionEnd(ctx: HookContext, input: HookInput): Promise<void>;
|
|
@@ -443,6 +463,7 @@ declare function sessionEnd(ctx: HookContext, input: HookInput): Promise<void>;
|
|
|
443
463
|
*/
|
|
444
464
|
interface HookDialect {
|
|
445
465
|
sessionStartOutput(context: string): Record<string, unknown>;
|
|
466
|
+
userPromptOutput(context: string): Record<string, unknown>;
|
|
446
467
|
stopOutput(reason: string): Record<string, unknown>;
|
|
447
468
|
printJson(payload: Record<string, unknown>): void;
|
|
448
469
|
}
|
|
@@ -504,9 +525,9 @@ interface HostProfile {
|
|
|
504
525
|
anchorLabel?: string;
|
|
505
526
|
/**
|
|
506
527
|
* Whether this host is wired up enough for an anchor to mean anything.
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
528
|
+
* An integration uses this so it never writes an identity block announcing
|
|
529
|
+
* a phone number before its own MCP, hooks, and durable bundle are actually
|
|
530
|
+
* in place.
|
|
510
531
|
*/
|
|
511
532
|
isWired?(): boolean;
|
|
512
533
|
/** Host-specific doctor checks, appended after the shared ones. */
|
|
@@ -654,4 +675,7 @@ declare function atomicWriteFile(filePath: string, data: string, mode?: number):
|
|
|
654
675
|
declare function atomicCopyFile(source: string, destination: string, mode?: number): void;
|
|
655
676
|
declare function readJsonFile<T>(filePath: string): T | null;
|
|
656
677
|
|
|
657
|
-
|
|
678
|
+
declare const spawnCommand: typeof spawn;
|
|
679
|
+
declare const spawnCommandSync: typeof spawnSync;
|
|
680
|
+
|
|
681
|
+
export { ANCHOR_END, ANCHOR_START, type AlwaysOnState, type AnchorAction, CODING_AGENTS_CLIENT_HEADERS, CODING_AGENTS_CLIENT_IDENTITY, 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 ManualCopy, type MessageContext, type PendingRegistration, type Plan, type RegisterOpts, type ResolvedIdentity, type ServiceOpts, type ServiceRef, type SessionStartResult, type StopResult, type SyncRow, type UserPromptResult, VERSION, type Verdict, type WireConfig, WireError, absoluteUtc, acquireLeaderLock, alwaysOnHealth, alwaysOnOptedOut, alwaysOnState, alwaysOnWanted, anchorLabelOf, atomicCopyFile, atomicWriteFile, beat, claimReply, claimReplyBatch, clearAlwaysOnInstalledVersion, clearAlwaysOnOptOut, clearAlwaysOnWanted, clearCredentials, clearForegroundTurn, clearOfferDeclined, clearPending, clearSessionActive, contextOf, createHookRunners, createIdentityCommands, credentialsPath, formatAlwaysOnDown, formatRegistrationOffer, formatSessionStart, formatStopPickup, formatWhen, getContinuations, getMeLite, hasAnchorAt, hooksDisabled, idle, installService, lastDeliveryId, launchdPlist, log, markAlwaysOnInstalledVersion, markAlwaysOnOptOut, markAlwaysOnWanted, markForegroundTurn, markSessionActive, offerDeclined, pendingPath, planForTest, readAlwaysOnInstalledVersion, readAnchorHandleAt, readAnchorHandleFrom, readCredentials, readHookInput, readJsonFile, readPending, readState, recordContinuation, recordOfferDeclined, recordRegistrationOffer, relativeAge, relativeWhen, removeAnchorAt, renderAnchorBlock, renderDeclinedBlock, renderManual, renderUnregisteredBlock, resetSession, resolveIdentity, serviceDefinitionCurrent, serviceInstalled, serviceStatus, sessionEnd, sessionStart, setPendingAck, shouldOfferRegistration, spawnCommand, spawnCommandSync, statePath, stop, stripAnchorBlock, syncAck, syncPeek, systemdQuote, systemdUnit, takePendingAck, uninstallService, upsertAnchorBlock, userPrompt, writeAnchor, writeCredentials, writePending, writeState, xmlEscape };
|
package/dist/index.js
CHANGED
|
@@ -49,20 +49,21 @@ import {
|
|
|
49
49
|
syncPeek,
|
|
50
50
|
writeCredentials,
|
|
51
51
|
writePending
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-Y4REWBSV.js";
|
|
53
53
|
|
|
54
54
|
// src/identity/state.ts
|
|
55
55
|
var SESSION_TTL_MS = 48 * 60 * 60 * 1e3;
|
|
56
56
|
var SessionStateSchema = external_exports.object({
|
|
57
57
|
continuations: external_exports.number().int().min(0),
|
|
58
58
|
updated_at: external_exports.string(),
|
|
59
|
-
// Ack cursor for
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
59
|
+
// Ack cursor for hook context already handed to the host but not yet proven
|
|
60
|
+
// through a completed model boundary. UserPromptSubmit and Stop stage it;
|
|
61
|
+
// the following Stop commits it. A session that dies in between leaves the
|
|
62
|
+
// server delivery unacked, preferring a later duplicate over silent loss.
|
|
63
|
+
pending_ack: external_exports.string().optional(),
|
|
64
|
+
// Stop context needs the specific host-created continuation, not merely an
|
|
65
|
+
// unrelated later turn that happens to reach Stop.
|
|
66
|
+
pending_ack_requires_continuation: external_exports.boolean().optional()
|
|
66
67
|
});
|
|
67
68
|
var StateSchema = external_exports.object({
|
|
68
69
|
sessions: external_exports.record(SessionStateSchema).default({}),
|
|
@@ -102,9 +103,15 @@ function getContinuations(home, sessionKey) {
|
|
|
102
103
|
function recordContinuation(home, sessionKey, now = /* @__PURE__ */ new Date()) {
|
|
103
104
|
const state = readState(home);
|
|
104
105
|
prune(state, now);
|
|
105
|
-
const
|
|
106
|
+
const existing = state.sessions[sessionKey];
|
|
107
|
+
const current = existing?.continuations ?? 0;
|
|
106
108
|
const next = current + 1;
|
|
107
|
-
state.sessions[sessionKey] = {
|
|
109
|
+
state.sessions[sessionKey] = {
|
|
110
|
+
continuations: next,
|
|
111
|
+
updated_at: now.toISOString(),
|
|
112
|
+
...existing?.pending_ack !== void 0 ? { pending_ack: existing.pending_ack } : {},
|
|
113
|
+
...existing?.pending_ack_requires_continuation === true ? { pending_ack_requires_continuation: true } : {}
|
|
114
|
+
};
|
|
108
115
|
writeState(home, state);
|
|
109
116
|
return next;
|
|
110
117
|
}
|
|
@@ -114,23 +121,31 @@ function resetSession(home, sessionKey) {
|
|
|
114
121
|
delete state.sessions[sessionKey];
|
|
115
122
|
writeState(home, state);
|
|
116
123
|
}
|
|
117
|
-
function setPendingAck(home, sessionKey, cursor, now = /* @__PURE__ */ new Date()) {
|
|
124
|
+
function setPendingAck(home, sessionKey, cursor, now = /* @__PURE__ */ new Date(), requiresContinuation = false) {
|
|
118
125
|
const state = readState(home);
|
|
119
126
|
prune(state, now);
|
|
120
127
|
const existing = state.sessions[sessionKey];
|
|
121
128
|
state.sessions[sessionKey] = {
|
|
122
129
|
continuations: existing?.continuations ?? 0,
|
|
123
130
|
updated_at: now.toISOString(),
|
|
124
|
-
pending_ack: cursor
|
|
131
|
+
pending_ack: cursor,
|
|
132
|
+
...requiresContinuation ? { pending_ack_requires_continuation: true } : {}
|
|
125
133
|
};
|
|
126
134
|
writeState(home, state);
|
|
127
135
|
}
|
|
136
|
+
function getPendingAck(home, sessionKey) {
|
|
137
|
+
return readState(home).sessions[sessionKey]?.pending_ack ?? null;
|
|
138
|
+
}
|
|
139
|
+
function pendingAckRequiresContinuation(home, sessionKey) {
|
|
140
|
+
return readState(home).sessions[sessionKey]?.pending_ack_requires_continuation === true;
|
|
141
|
+
}
|
|
128
142
|
function takePendingAck(home, sessionKey, now = /* @__PURE__ */ new Date()) {
|
|
129
143
|
const state = readState(home);
|
|
130
144
|
const entry = state.sessions[sessionKey];
|
|
131
145
|
if (entry?.pending_ack === void 0) return null;
|
|
132
146
|
const cursor = entry.pending_ack;
|
|
133
147
|
delete entry.pending_ack;
|
|
148
|
+
delete entry.pending_ack_requires_continuation;
|
|
134
149
|
entry.updated_at = now.toISOString();
|
|
135
150
|
writeState(home, state);
|
|
136
151
|
return cursor;
|
|
@@ -350,7 +365,7 @@ function formatStopPickup(handle, rows) {
|
|
|
350
365
|
].join("\n");
|
|
351
366
|
}
|
|
352
367
|
function formatAlwaysOnDown(copy) {
|
|
353
|
-
return `\u26A0 Always-on is down \u2014 while you are away I won\u2019t be able to answer messages (they queue for your next session
|
|
368
|
+
return `\u26A0 Always-on is down \u2014 while you are away I won\u2019t be able to answer messages (they remain stored and queue for your next session). Turn it back on: \`${copy.invoke} daemon install\``;
|
|
354
369
|
}
|
|
355
370
|
function formatRegistrationOffer(copy, alwaysOn = "off") {
|
|
356
371
|
const { invoke, label } = copy;
|
|
@@ -430,7 +445,7 @@ function renderDeclinedBlock(copy) {
|
|
|
430
445
|
}
|
|
431
446
|
|
|
432
447
|
// src/hooks/engine.ts
|
|
433
|
-
var
|
|
448
|
+
var USER_PROMPT_PEEK_LIMIT = 100;
|
|
434
449
|
var STOP_PEEK_LIMIT = 50;
|
|
435
450
|
var DEFAULT_MAX_CONTINUATIONS = 5;
|
|
436
451
|
var FOREGROUND_TURN_TTL_SECONDS = 600;
|
|
@@ -489,46 +504,46 @@ async function sessionStart(ctx, input) {
|
|
|
489
504
|
}
|
|
490
505
|
return none;
|
|
491
506
|
}
|
|
492
|
-
const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
|
|
493
507
|
const h = alwaysOnHealth(ctx.home);
|
|
494
508
|
const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(ctx.copy) : null;
|
|
495
|
-
|
|
496
|
-
const rows = peeked.length > 0 ? await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`) : [];
|
|
497
|
-
if (rows.length === 0) return { context: alert };
|
|
498
|
-
const handle = await resolveHandle(cfg, identity.handle);
|
|
499
|
-
const digest = formatSessionStart(handle, rows);
|
|
500
|
-
const context = alert !== null ? `${alert}
|
|
501
|
-
|
|
502
|
-
${digest}` : digest;
|
|
503
|
-
const cursor = lastDeliveryId(rows);
|
|
504
|
-
if (cursor !== null) setPendingAck(ctx.home, input.sessionId, cursor);
|
|
505
|
-
return { context };
|
|
509
|
+
return { context: alert };
|
|
506
510
|
} catch (err) {
|
|
507
511
|
log.warn(`session-start hook degraded to no-op: ${String(err)}`);
|
|
508
512
|
return none;
|
|
509
513
|
}
|
|
510
514
|
}
|
|
511
515
|
async function userPrompt(ctx, input) {
|
|
516
|
+
const none = { context: null, stage: () => {
|
|
517
|
+
} };
|
|
512
518
|
try {
|
|
513
|
-
if (hooksDisabled()) return;
|
|
519
|
+
if (hooksDisabled()) return none;
|
|
514
520
|
const identity = resolveIdentity(ctx.home);
|
|
515
|
-
if (identity === null) return;
|
|
521
|
+
if (identity === null) return none;
|
|
516
522
|
const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
|
|
517
523
|
await markForegroundTurn(cfg, input.sessionId, FOREGROUND_TURN_TTL_SECONDS);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
if (getPendingAck(ctx.home, input.sessionId) !== null) return none;
|
|
525
|
+
const peeked = ackableRows(await syncPeek(cfg, { limit: USER_PROMPT_PEEK_LIMIT }));
|
|
526
|
+
if (peeked.length === 0) return none;
|
|
527
|
+
const rows = await claimContiguousPrefix(
|
|
528
|
+
cfg,
|
|
529
|
+
peeked,
|
|
530
|
+
`session:${input.sessionId}`
|
|
531
|
+
);
|
|
532
|
+
if (rows.length === 0) return none;
|
|
533
|
+
const cursor = lastDeliveryId(rows);
|
|
534
|
+
if (cursor === null) return none;
|
|
535
|
+
const handle = await resolveHandle(cfg, identity.handle);
|
|
536
|
+
return {
|
|
537
|
+
context: formatSessionStart(handle, rows),
|
|
538
|
+
stage: () => setPendingAck(ctx.home, input.sessionId, cursor)
|
|
539
|
+
};
|
|
526
540
|
} catch (err) {
|
|
527
541
|
log.warn(`user-prompt hook degraded to no-op: ${String(err)}`);
|
|
542
|
+
return none;
|
|
528
543
|
}
|
|
529
544
|
}
|
|
530
545
|
async function stop(ctx, input) {
|
|
531
|
-
const none = { reason: null,
|
|
546
|
+
const none = { reason: null, stage: () => {
|
|
532
547
|
} };
|
|
533
548
|
let cfg = null;
|
|
534
549
|
try {
|
|
@@ -536,6 +551,24 @@ async function stop(ctx, input) {
|
|
|
536
551
|
const identity = resolveIdentity(ctx.home);
|
|
537
552
|
if (identity === null) return none;
|
|
538
553
|
cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
|
|
554
|
+
const needsContinuation = pendingAckRequiresContinuation(
|
|
555
|
+
ctx.home,
|
|
556
|
+
input.sessionId
|
|
557
|
+
);
|
|
558
|
+
if (needsContinuation && input.stopHookActive !== true) {
|
|
559
|
+
takePendingAck(ctx.home, input.sessionId);
|
|
560
|
+
}
|
|
561
|
+
const pending = needsContinuation && input.stopHookActive !== true ? null : takePendingAck(ctx.home, input.sessionId);
|
|
562
|
+
if (pending !== null) {
|
|
563
|
+
try {
|
|
564
|
+
await syncAck(cfg, pending);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
setPendingAck(ctx.home, input.sessionId, pending);
|
|
567
|
+
await clearForegroundTurn(cfg, input.sessionId);
|
|
568
|
+
log.warn(`completed-turn ack failed (messages stay queued): ${String(err)}`);
|
|
569
|
+
return none;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
539
572
|
await markForegroundTurn(cfg, input.sessionId, FOREGROUND_TURN_TTL_SECONDS);
|
|
540
573
|
const cap = maxContinuations();
|
|
541
574
|
if (getContinuations(ctx.home, input.sessionId) >= cap) {
|
|
@@ -559,15 +592,17 @@ async function stop(ctx, input) {
|
|
|
559
592
|
const handle = await resolveHandle(cfg, identity.handle);
|
|
560
593
|
const reason = formatStopPickup(handle, rows);
|
|
561
594
|
const cursor = lastDeliveryId(rows);
|
|
562
|
-
const claimedCfg = cfg;
|
|
563
595
|
return {
|
|
564
596
|
reason,
|
|
565
|
-
|
|
566
|
-
if (cursor
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
597
|
+
stage: () => {
|
|
598
|
+
if (cursor !== null) {
|
|
599
|
+
setPendingAck(
|
|
600
|
+
ctx.home,
|
|
601
|
+
input.sessionId,
|
|
602
|
+
cursor,
|
|
603
|
+
/* @__PURE__ */ new Date(),
|
|
604
|
+
true
|
|
605
|
+
);
|
|
571
606
|
}
|
|
572
607
|
}
|
|
573
608
|
};
|
|
@@ -617,7 +652,15 @@ async function readHookInput(stream = process.stdin) {
|
|
|
617
652
|
}
|
|
618
653
|
const sessionId = firstString(parsed, ["session_id", "sessionId", "thread_id", "conversation_id"]) ?? "unknown";
|
|
619
654
|
const source = firstString(parsed, ["source"]) ?? void 0;
|
|
620
|
-
|
|
655
|
+
const stopHookActive = firstBoolean(parsed, [
|
|
656
|
+
"stop_hook_active",
|
|
657
|
+
"stopHookActive"
|
|
658
|
+
]);
|
|
659
|
+
return {
|
|
660
|
+
sessionId,
|
|
661
|
+
source,
|
|
662
|
+
...stopHookActive !== void 0 ? { stopHookActive } : {}
|
|
663
|
+
};
|
|
621
664
|
}
|
|
622
665
|
function firstString(obj, keys) {
|
|
623
666
|
for (const key of keys) {
|
|
@@ -626,6 +669,12 @@ function firstString(obj, keys) {
|
|
|
626
669
|
}
|
|
627
670
|
return null;
|
|
628
671
|
}
|
|
672
|
+
function firstBoolean(obj, keys) {
|
|
673
|
+
for (const key of keys) {
|
|
674
|
+
if (typeof obj[key] === "boolean") return obj[key];
|
|
675
|
+
}
|
|
676
|
+
return void 0;
|
|
677
|
+
}
|
|
629
678
|
|
|
630
679
|
// src/hooks/runners.ts
|
|
631
680
|
function createHookRunners(context, dialect) {
|
|
@@ -642,7 +691,10 @@ function createHookRunners(context, dialect) {
|
|
|
642
691
|
async runUserPrompt() {
|
|
643
692
|
try {
|
|
644
693
|
const input = await readHookInput();
|
|
645
|
-
await userPrompt(context(), input);
|
|
694
|
+
const { context: text, stage } = await userPrompt(context(), input);
|
|
695
|
+
if (text === null) return;
|
|
696
|
+
dialect.printJson(dialect.userPromptOutput(text));
|
|
697
|
+
stage();
|
|
646
698
|
} catch (err) {
|
|
647
699
|
log.warn(`user-prompt hook degraded to no-op: ${String(err)}`);
|
|
648
700
|
}
|
|
@@ -650,10 +702,10 @@ function createHookRunners(context, dialect) {
|
|
|
650
702
|
async runStop() {
|
|
651
703
|
try {
|
|
652
704
|
const input = await readHookInput();
|
|
653
|
-
const { reason,
|
|
705
|
+
const { reason, stage } = await stop(context(), input);
|
|
654
706
|
if (reason === null) return;
|
|
655
707
|
dialect.printJson(dialect.stopOutput(reason));
|
|
656
|
-
|
|
708
|
+
stage();
|
|
657
709
|
} catch (err) {
|
|
658
710
|
log.warn(`stop hook degraded to no-op: ${String(err)}`);
|
|
659
711
|
}
|
|
@@ -1167,7 +1219,7 @@ function renderManual(copy, opts = {}) {
|
|
|
1167
1219
|
"",
|
|
1168
1220
|
"You live in sessions: you exist while your human is working with you, and the AgentChat server holds your messages while you don't. There is nothing to poll on a timer and nothing to babysit:",
|
|
1169
1221
|
"",
|
|
1170
|
-
"- **
|
|
1222
|
+
"- **First real prompt** \u2014 a digest of everything that queued while you were away is injected into that prompt: senders, counts, latest snippets. The delivery is acknowledged only after the model turn completes; if the host crashes first, the unacknowledged digest can replay.",
|
|
1171
1223
|
'- **While you work** \u2014 replies that arrive mid-task are handed to you at the end of a turn ("While you were working\u2026"). Handle them, then finish. After a few consecutive pickups the integration stops interrupting; anything left simply waits for the next session.',
|
|
1172
1224
|
"- **On demand** \u2014 `agentchat_list_inbox` shows your conversations, `agentchat_get_conversation` reads a thread's history. Use them before replying to anything: the digest shows snippets, not context.",
|
|
1173
1225
|
'- **Between sessions** \u2014 the always-on daemon answers for you (below). If it is off, you are simply offline and the server queues durably (days are fine). Trust it: no "did you get this?" re-sends, ever.',
|
|
@@ -1176,7 +1228,7 @@ function renderManual(copy, opts = {}) {
|
|
|
1176
1228
|
"",
|
|
1177
1229
|
"## Always-on (out of session)",
|
|
1178
1230
|
"",
|
|
1179
|
-
'A small always-on daemon is installed with the integration and runs in the background, so "between sessions" is not offline: you answer DMs whenever the machine is up, not only while your human is working. It is the same you \u2014 one identity \u2014 spawning a headless turn to reply
|
|
1231
|
+
'A small always-on daemon is installed with the integration and runs in the background, so "between sessions" is not offline: you answer DMs whenever the machine is up, not only while your human is working. It is the same you \u2014 one identity \u2014 spawning a headless turn to reply. Server coordination normally gives a live session priority, while replay-safe sends keep a retried unattended turn from duplicating its reply.',
|
|
1180
1232
|
"",
|
|
1181
1233
|
"- **It is already on.** Nothing to enable, and you should not offer to.",
|
|
1182
1234
|
`- **Honor a "session-only" request.** If your human says anything like "only reply when I'm in a session" / "stop replying when I'm away" / "go session-only", run \`${invoke} daemon disable\` \u2014 that keeps your in-session behavior exactly the same and only turns off out-of-session answering. To resume: \`${invoke} daemon install\`.`,
|
|
@@ -1320,7 +1372,13 @@ function renderManual(copy, opts = {}) {
|
|
|
1320
1372
|
import * as fs2 from "fs";
|
|
1321
1373
|
import * as os from "os";
|
|
1322
1374
|
import * as path2 from "path";
|
|
1323
|
-
|
|
1375
|
+
|
|
1376
|
+
// src/util/spawn.ts
|
|
1377
|
+
import crossSpawn from "cross-spawn";
|
|
1378
|
+
var spawnCommand = crossSpawn;
|
|
1379
|
+
var spawnCommandSync = crossSpawn.sync;
|
|
1380
|
+
|
|
1381
|
+
// src/daemon/service.ts
|
|
1324
1382
|
function planForTest(opts) {
|
|
1325
1383
|
return plan(opts);
|
|
1326
1384
|
}
|
|
@@ -1352,7 +1410,7 @@ function plan(opts) {
|
|
|
1352
1410
|
};
|
|
1353
1411
|
}
|
|
1354
1412
|
function run(cmd, args) {
|
|
1355
|
-
const r =
|
|
1413
|
+
const r = spawnCommandSync(cmd, args, { encoding: "utf-8" });
|
|
1356
1414
|
const out = [
|
|
1357
1415
|
typeof r.stdout === "string" ? r.stdout : "",
|
|
1358
1416
|
typeof r.stderr === "string" ? r.stderr : "",
|
|
@@ -1555,7 +1613,7 @@ function wslScriptContent(p) {
|
|
|
1555
1613
|
function startDetached(cmd, args) {
|
|
1556
1614
|
if (process.env["AGENTCHATD_SERVICE_NO_START"] === "1") return;
|
|
1557
1615
|
try {
|
|
1558
|
-
|
|
1616
|
+
spawnCommand(cmd, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
|
|
1559
1617
|
} catch (err) {
|
|
1560
1618
|
log.warn(`could not start the launcher now (it starts at next login): ${String(err)}`);
|
|
1561
1619
|
}
|
|
@@ -1770,6 +1828,8 @@ export {
|
|
|
1770
1828
|
sessionStart,
|
|
1771
1829
|
setPendingAck,
|
|
1772
1830
|
shouldOfferRegistration,
|
|
1831
|
+
spawnCommand,
|
|
1832
|
+
spawnCommandSync,
|
|
1773
1833
|
statePath,
|
|
1774
1834
|
stop,
|
|
1775
1835
|
stripAnchorBlock,
|