@agentconnect.md/daemon 1.0.0-rc.282 → 1.0.0-rc.284
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/{acp-host-C-4zFhF8.js → acp-host-CDuL4q1C.js} +78 -31
- package/dist/acp-host-CDuL4q1C.js.map +1 -0
- package/dist/{chat-B8VEWuix.js → chat-lDAUzWEJ.js} +2 -2
- package/dist/{chat-B8VEWuix.js.map → chat-lDAUzWEJ.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/{login-OT3DwN02.js → login-1aQsRYZ9.js} +2 -2
- package/dist/{login-OT3DwN02.js.map → login-1aQsRYZ9.js.map} +1 -1
- package/dist/{run-foreground-BV2scayf.js → run-foreground-D_sn_AMV.js} +595 -4
- package/dist/{run-foreground-BV2scayf.js.map → run-foreground-D_sn_AMV.js.map} +1 -1
- package/dist/run-foreground-bNQ9crEV.js +2 -0
- package/package.json +1 -1
- package/dist/acp-host-C-4zFhF8.js.map +0 -1
- package/dist/run-foreground-Cz_WXUfg.js +0 -2
|
@@ -5,7 +5,7 @@ import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
|
5
5
|
import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
|
|
6
6
|
import { C as ZodIssueCode, S as RESERVED_MCP_SERVER_NAME, _ as buildEnvelope, a as agentChildEnv, b as MAX_FRAME_BYTES, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as SHARED_CONFIG_ACTION_ID, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as decodeEnvelope, y as encode } from "./load-agents-DzCnR0cj.js";
|
|
7
7
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
8
|
-
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-
|
|
8
|
+
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-CDuL4q1C.js";
|
|
9
9
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-CMC80O_1.js";
|
|
10
10
|
import { B as ToolListChangedNotificationSchema, D as ListPromptsResultSchema, E as ListChangedOptionsBaseSchema, F as PromptListChangedNotificationSchema, G as isZ4Schema, H as isJSONRPCRequest, I as ReadResourceResultSchema, J as encodeFrame, K as safeParse, L as ResourceListChangedNotificationSchema, M as ListToolsResultSchema, O as ListResourceTemplatesResultSchema, P as McpError, R as SUPPORTED_PROTOCOL_VERSIONS, S as InitializeResultSchema, T as LATEST_PROTOCOL_VERSION, U as isJSONRPCResultResponse, V as isInitializedNotification, W as getObjectShape, _ as ElicitResultSchema, a as AjvJsonSchemaValidator, b as GetPromptResultSchema, c as mergeCapabilities, d as CompleteResultSchema, f as CreateMessageRequestSchema, g as ElicitRequestSchema, h as CreateTaskResultSchema, i as assertToolsCallTaskCapability, k as ListResourcesResultSchema, m as CreateMessageResultWithToolsSchema, n as serializeMessage, o as require_fast_deep_equal, p as CreateMessageResultSchema, q as decodeFrames, r as assertClientRequestTaskCapability, s as Protocol, t as ReadBuffer, u as CallToolResultSchema, v as EmptyResultSchema, w as JSONRPCMessageSchema, y as ErrorCode } from "./stdio-DnS8WcXi.js";
|
|
11
11
|
import { URL as URL$1 } from "url";
|
|
@@ -6373,6 +6373,68 @@ var LocalStore = class {
|
|
|
6373
6373
|
CREATE TABLE IF NOT EXISTS cron_runs (
|
|
6374
6374
|
key TEXT PRIMARY KEY, lastRunAt INTEGER NOT NULL
|
|
6375
6375
|
);
|
|
6376
|
+
-- §6.9 #353 durable inbox: an ADMITTED-but-QUEUED message persisted BEFORE the
|
|
6377
|
+
-- admission ACK, so a hard kill / agent move can't lose a message the caller was
|
|
6378
|
+
-- already told delivered:true. Replayed FIFO-by-sessionKey on startup and removed
|
|
6379
|
+
-- on every terminal path (success / reject / cancel / gate-drop). The id column is the
|
|
6380
|
+
-- message's STABLE deliveryId/msgId (§6.3) so re-append and replay are idempotent
|
|
6381
|
+
-- against the existing admission-idempotency maps. Webchat turns are NOT persisted
|
|
6382
|
+
-- here (their live sink can't be restored — see §6.9 #367). enqueuedAt is a
|
|
6383
|
+
-- monotonic decimal string (fixed-width ⇒ string order == numeric order) driving
|
|
6384
|
+
-- FIFO within a sessionKey.
|
|
6385
|
+
CREATE TABLE IF NOT EXISTS inbox (
|
|
6386
|
+
id TEXT PRIMARY KEY,
|
|
6387
|
+
sessionKey TEXT NOT NULL,
|
|
6388
|
+
agentId TEXT NOT NULL,
|
|
6389
|
+
msg TEXT NOT NULL,
|
|
6390
|
+
integrationId TEXT,
|
|
6391
|
+
callMeta TEXT,
|
|
6392
|
+
isQueueCmd INTEGER,
|
|
6393
|
+
enqueuedAt TEXT NOT NULL
|
|
6394
|
+
);
|
|
6395
|
+
CREATE INDEX IF NOT EXISTS inbox_fifo ON inbox (sessionKey, enqueuedAt);
|
|
6396
|
+
-- §3.4/§6.8 main-agent orchestration: a main agent fans out N subtasks to
|
|
6397
|
+
-- worker agents, then waits asynchronously and summarizes. The record is
|
|
6398
|
+
-- persisted BEFORE any delivery (record-first — a fast worker's reply must
|
|
6399
|
+
-- never arrive before the record exists, else §3.3 correlation drops it).
|
|
6400
|
+
-- daemon-local (never on the CP hot path); the deadline epoch is the durable
|
|
6401
|
+
-- SoT for the one-shot cron re-armed on startup. status: active|done|cancelled.
|
|
6402
|
+
CREATE TABLE IF NOT EXISTS orchestration (
|
|
6403
|
+
orchestrationId TEXT PRIMARY KEY,
|
|
6404
|
+
mainSessionKey TEXT NOT NULL,
|
|
6405
|
+
mainAgentId TEXT NOT NULL,
|
|
6406
|
+
-- The main's session coords, stored explicitly (not parsed from mainSessionKey,
|
|
6407
|
+
-- whose channel/thread may contain ':') so the deadline fire wakes the exact session.
|
|
6408
|
+
platform TEXT NOT NULL,
|
|
6409
|
+
channel TEXT NOT NULL,
|
|
6410
|
+
thread TEXT NOT NULL,
|
|
6411
|
+
integrationId TEXT,
|
|
6412
|
+
replyTarget TEXT,
|
|
6413
|
+
deadline INTEGER,
|
|
6414
|
+
status TEXT NOT NULL,
|
|
6415
|
+
createdAt INTEGER NOT NULL,
|
|
6416
|
+
updatedAt INTEGER NOT NULL
|
|
6417
|
+
);
|
|
6418
|
+
CREATE INDEX IF NOT EXISTS orchestration_active ON orchestration (status);
|
|
6419
|
+
-- One row per subtask. Stable correlationId = "<orchestrationId>.<idx>" is the
|
|
6420
|
+
-- delivery/report correlation key (§3.3). State machine (§6.8):
|
|
6421
|
+
-- pending → sending → delivered → succeeded | worker_error | timed_out
|
|
6422
|
+
-- busy/offline are RETRYABLE attempt states folded back to a delivery failure.
|
|
6423
|
+
-- Transitions are CAS + idempotent on (orchestrationId, correlationId).
|
|
6424
|
+
CREATE TABLE IF NOT EXISTS orchestration_subtask (
|
|
6425
|
+
orchestrationId TEXT NOT NULL,
|
|
6426
|
+
correlationId TEXT NOT NULL,
|
|
6427
|
+
idx INTEGER NOT NULL,
|
|
6428
|
+
toAgentId TEXT NOT NULL,
|
|
6429
|
+
text TEXT NOT NULL,
|
|
6430
|
+
status TEXT NOT NULL,
|
|
6431
|
+
result TEXT,
|
|
6432
|
+
deliveryReason TEXT,
|
|
6433
|
+
updatedAt TEXT NOT NULL,
|
|
6434
|
+
PRIMARY KEY (orchestrationId, correlationId)
|
|
6435
|
+
);
|
|
6436
|
+
CREATE INDEX IF NOT EXISTS orchestration_subtask_by_orch
|
|
6437
|
+
ON orchestration_subtask (orchestrationId, idx);
|
|
6376
6438
|
`);
|
|
6377
6439
|
this.migrateSessionUsage();
|
|
6378
6440
|
this.migrateTranscriptToolBody();
|
|
@@ -6727,6 +6789,125 @@ var LocalStore = class {
|
|
|
6727
6789
|
getCronLastRun(key) {
|
|
6728
6790
|
return this.db.prepare("SELECT lastRunAt FROM cron_runs WHERE key = ?").get(key)?.lastRunAt;
|
|
6729
6791
|
}
|
|
6792
|
+
/** §6.9 #353 durable inbox: persist an admitted message BEFORE its admission ACK. Keyed
|
|
6793
|
+
* by the stable id (deliveryId/msgId) with INSERT OR IGNORE, so a re-append of the same
|
|
6794
|
+
* delivery (P2 retry / replay) is a no-op — idempotent against the admission maps. */
|
|
6795
|
+
appendInbox(row) {
|
|
6796
|
+
this.db.prepare(`INSERT OR IGNORE INTO inbox (id, sessionKey, agentId, msg, integrationId, callMeta, isQueueCmd, enqueuedAt)
|
|
6797
|
+
VALUES (@id, @sessionKey, @agentId, @msg, @integrationId, @callMeta, @isQueueCmd, @enqueuedAt)`).run({
|
|
6798
|
+
id: row.id,
|
|
6799
|
+
sessionKey: row.sessionKey,
|
|
6800
|
+
agentId: row.agentId,
|
|
6801
|
+
msg: row.msg,
|
|
6802
|
+
integrationId: row.integrationId ?? null,
|
|
6803
|
+
callMeta: row.callMeta ?? null,
|
|
6804
|
+
isQueueCmd: row.isQueueCmd ?? null,
|
|
6805
|
+
enqueuedAt: row.enqueuedAt
|
|
6806
|
+
});
|
|
6807
|
+
}
|
|
6808
|
+
/** Remove an inbox row once its turn reaches a terminal state (success / reject / cancel /
|
|
6809
|
+
* gate-drop) so it is never replayed. No-op on an unknown id. */
|
|
6810
|
+
removeInbox(id) {
|
|
6811
|
+
this.db.prepare("DELETE FROM inbox WHERE id = ?").run(id);
|
|
6812
|
+
}
|
|
6813
|
+
/** All pending inbox rows, ordered FIFO-by-sessionKey (sessionKey, then enqueuedAt) for
|
|
6814
|
+
* startup replay (§6.9 #353). Order within a sessionKey is preserved by `enqueuedAt`. */
|
|
6815
|
+
listInboxBySessionKeyFifo() {
|
|
6816
|
+
return this.db.prepare("SELECT * FROM inbox ORDER BY sessionKey ASC, enqueuedAt ASC").all();
|
|
6817
|
+
}
|
|
6818
|
+
/**
|
|
6819
|
+
* RECORD-FIRST (§3.4): persist the orchestration header + all subtask rows (status
|
|
6820
|
+
* 'pending') in ONE transaction, BEFORE any delivery. A fast worker's reply that
|
|
6821
|
+
* arrives before this returns has nothing to correlate against and is dropped — so
|
|
6822
|
+
* this MUST complete before startOrchestration delivers anything. Idempotent via
|
|
6823
|
+
* INSERT OR IGNORE on the stable ids (replay-safe).
|
|
6824
|
+
*/
|
|
6825
|
+
createOrchestration(orch, subtasks) {
|
|
6826
|
+
const insertOrch = this.db.prepare(`INSERT OR IGNORE INTO orchestration
|
|
6827
|
+
(orchestrationId, mainSessionKey, mainAgentId, platform, channel, thread,
|
|
6828
|
+
integrationId, replyTarget, deadline, status, createdAt, updatedAt)
|
|
6829
|
+
VALUES (@orchestrationId, @mainSessionKey, @mainAgentId, @platform, @channel, @thread,
|
|
6830
|
+
@integrationId, @replyTarget, @deadline, @status, @createdAt, @updatedAt)`);
|
|
6831
|
+
const insertSub = this.db.prepare(`INSERT OR IGNORE INTO orchestration_subtask
|
|
6832
|
+
(orchestrationId, correlationId, idx, toAgentId, text, status, result, deliveryReason, updatedAt)
|
|
6833
|
+
VALUES (@orchestrationId, @correlationId, @idx, @toAgentId, @text, @status, @result, @deliveryReason, @updatedAt)`);
|
|
6834
|
+
this.db.exec("BEGIN");
|
|
6835
|
+
try {
|
|
6836
|
+
insertOrch.run({
|
|
6837
|
+
orchestrationId: orch.orchestrationId,
|
|
6838
|
+
mainSessionKey: orch.mainSessionKey,
|
|
6839
|
+
mainAgentId: orch.mainAgentId,
|
|
6840
|
+
platform: orch.platform,
|
|
6841
|
+
channel: orch.channel,
|
|
6842
|
+
thread: orch.thread,
|
|
6843
|
+
integrationId: orch.integrationId ?? null,
|
|
6844
|
+
replyTarget: orch.replyTarget ?? null,
|
|
6845
|
+
deadline: orch.deadline ?? null,
|
|
6846
|
+
status: orch.status,
|
|
6847
|
+
createdAt: orch.createdAt,
|
|
6848
|
+
updatedAt: orch.updatedAt
|
|
6849
|
+
});
|
|
6850
|
+
for (const s of subtasks) insertSub.run({
|
|
6851
|
+
orchestrationId: s.orchestrationId,
|
|
6852
|
+
correlationId: s.correlationId,
|
|
6853
|
+
idx: s.idx,
|
|
6854
|
+
toAgentId: s.toAgentId,
|
|
6855
|
+
text: s.text,
|
|
6856
|
+
status: s.status,
|
|
6857
|
+
result: s.result ?? null,
|
|
6858
|
+
deliveryReason: s.deliveryReason ?? null,
|
|
6859
|
+
updatedAt: s.updatedAt
|
|
6860
|
+
});
|
|
6861
|
+
this.db.exec("COMMIT");
|
|
6862
|
+
} catch (err) {
|
|
6863
|
+
this.db.exec("ROLLBACK");
|
|
6864
|
+
throw err;
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
getOrchestration(orchestrationId) {
|
|
6868
|
+
return this.db.prepare("SELECT * FROM orchestration WHERE orchestrationId = ?").get(orchestrationId);
|
|
6869
|
+
}
|
|
6870
|
+
getSubtasks(orchestrationId) {
|
|
6871
|
+
return this.db.prepare("SELECT * FROM orchestration_subtask WHERE orchestrationId = ? ORDER BY idx ASC").all(orchestrationId);
|
|
6872
|
+
}
|
|
6873
|
+
getSubtaskByCorrelation(orchestrationId, correlationId) {
|
|
6874
|
+
return this.db.prepare("SELECT * FROM orchestration_subtask WHERE orchestrationId = ? AND correlationId = ?").get(orchestrationId, correlationId);
|
|
6875
|
+
}
|
|
6876
|
+
/** All still-`active` orchestrations — for startup re-arm of deadlines + re-drive
|
|
6877
|
+
* of non-terminal subtasks (§6.8). */
|
|
6878
|
+
listActiveOrchestrations() {
|
|
6879
|
+
return this.db.prepare("SELECT * FROM orchestration WHERE status = 'active' ORDER BY createdAt ASC").all();
|
|
6880
|
+
}
|
|
6881
|
+
/** CAS subtask status — only advances when the current status is one of `from`.
|
|
6882
|
+
* Idempotent + monotonic: a stale/duplicate transition (current status not in
|
|
6883
|
+
* `from`) is a no-op and returns false. */
|
|
6884
|
+
setSubtaskStatus(orchestrationId, correlationId, from, to, updatedAt, extra) {
|
|
6885
|
+
const placeholders = from.map((_, i) => `@from${i}`).join(", ");
|
|
6886
|
+
const params = {
|
|
6887
|
+
orchestrationId,
|
|
6888
|
+
correlationId,
|
|
6889
|
+
to,
|
|
6890
|
+
updatedAt
|
|
6891
|
+
};
|
|
6892
|
+
from.forEach((f, i) => params[`from${i}`] = f);
|
|
6893
|
+
let setResult = "";
|
|
6894
|
+
if (extra && "result" in extra) {
|
|
6895
|
+
setResult += ", result=@result";
|
|
6896
|
+
params.result = extra.result ?? null;
|
|
6897
|
+
}
|
|
6898
|
+
if (extra && "deliveryReason" in extra) {
|
|
6899
|
+
setResult += ", deliveryReason=@deliveryReason";
|
|
6900
|
+
params.deliveryReason = extra.deliveryReason ?? null;
|
|
6901
|
+
}
|
|
6902
|
+
return this.db.prepare(`UPDATE orchestration_subtask SET status=@to, updatedAt=@updatedAt${setResult}
|
|
6903
|
+
WHERE orchestrationId=@orchestrationId AND correlationId=@correlationId AND status IN (${placeholders})`).run(params).changes > 0;
|
|
6904
|
+
}
|
|
6905
|
+
setOrchestrationStatus(orchestrationId, status, updatedAt) {
|
|
6906
|
+
this.db.prepare("UPDATE orchestration SET status=?, updatedAt=? WHERE orchestrationId=?").run(status, updatedAt, orchestrationId);
|
|
6907
|
+
}
|
|
6908
|
+
setOrchestrationDeadline(orchestrationId, deadline, updatedAt) {
|
|
6909
|
+
this.db.prepare("UPDATE orchestration SET deadline=?, updatedAt=? WHERE orchestrationId=?").run(deadline, updatedAt, orchestrationId);
|
|
6910
|
+
}
|
|
6730
6911
|
close() {
|
|
6731
6912
|
this.db.close();
|
|
6732
6913
|
}
|
|
@@ -7128,6 +7309,8 @@ async function executeTool(ctx, name, args, deps) {
|
|
|
7128
7309
|
return await deps.messageAgent({
|
|
7129
7310
|
callerAgentId: ctx.agentId,
|
|
7130
7311
|
platform: ctx.platform,
|
|
7312
|
+
callerChannel: ctx.channel,
|
|
7313
|
+
callerThread: ctx.thread,
|
|
7131
7314
|
toAgentId,
|
|
7132
7315
|
text,
|
|
7133
7316
|
channel,
|
|
@@ -7135,6 +7318,50 @@ async function executeTool(ctx, name, args, deps) {
|
|
|
7135
7318
|
...correlationId !== void 0 ? { correlationId } : {}
|
|
7136
7319
|
});
|
|
7137
7320
|
}
|
|
7321
|
+
if (name === "startOrchestration") {
|
|
7322
|
+
const rawSubtasks = args.subtasks;
|
|
7323
|
+
if (!Array.isArray(rawSubtasks) || rawSubtasks.length === 0) throw new Error("missing required argument: subtasks (non-empty array)");
|
|
7324
|
+
const subtasks = rawSubtasks.map((s, i) => {
|
|
7325
|
+
if (typeof s !== "object" || s === null) throw new Error(`subtasks[${i}] must be an object`);
|
|
7326
|
+
const so = s;
|
|
7327
|
+
return {
|
|
7328
|
+
toAgentId: requireString(so, "toAgentId"),
|
|
7329
|
+
text: requireString(so, "text")
|
|
7330
|
+
};
|
|
7331
|
+
});
|
|
7332
|
+
const deadlineMs = optionalNumber(args, "deadlineMs");
|
|
7333
|
+
const replyTarget = optionalString(args, "replyTarget");
|
|
7334
|
+
return await deps.startOrchestration({
|
|
7335
|
+
mainAgentId: ctx.agentId,
|
|
7336
|
+
platform: ctx.platform,
|
|
7337
|
+
channel: ctx.channel,
|
|
7338
|
+
thread: ctx.thread,
|
|
7339
|
+
...ctx.integrationId !== void 0 ? { integrationId: ctx.integrationId } : {},
|
|
7340
|
+
subtasks,
|
|
7341
|
+
...deadlineMs !== void 0 ? { deadlineMs } : {},
|
|
7342
|
+
...replyTarget !== void 0 ? { replyTarget } : {}
|
|
7343
|
+
});
|
|
7344
|
+
}
|
|
7345
|
+
if (name === "getOrchestration" || name === "cancelOrchestration") {
|
|
7346
|
+
const orchestrationId = requireString(args, "orchestrationId");
|
|
7347
|
+
const req = {
|
|
7348
|
+
mainAgentId: ctx.agentId,
|
|
7349
|
+
platform: ctx.platform,
|
|
7350
|
+
channel: ctx.channel,
|
|
7351
|
+
thread: ctx.thread,
|
|
7352
|
+
orchestrationId
|
|
7353
|
+
};
|
|
7354
|
+
if (name === "getOrchestration") {
|
|
7355
|
+
const rec = await deps.getOrchestration(req);
|
|
7356
|
+
if (rec === null) throw new Error(`no orchestration ${orchestrationId} owned by this session`);
|
|
7357
|
+
return rec;
|
|
7358
|
+
}
|
|
7359
|
+
if (!await deps.cancelOrchestration(req)) throw new Error(`no orchestration ${orchestrationId} owned by this session`);
|
|
7360
|
+
return {
|
|
7361
|
+
orchestrationId,
|
|
7362
|
+
cancelled: true
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7138
7365
|
const gw = ctx.integrationId ? deps.gatewayFor(ctx.integrationId) : void 0;
|
|
7139
7366
|
if (!gw) throw new Error(`no live platform connection for integration ${ctx.integrationId ?? "(none)"}`);
|
|
7140
7367
|
switch (name) {
|
|
@@ -7215,6 +7442,12 @@ function optionalString(args, key) {
|
|
|
7215
7442
|
if (typeof v !== "string") throw new Error(`argument ${key} must be a string`);
|
|
7216
7443
|
return v;
|
|
7217
7444
|
}
|
|
7445
|
+
function optionalNumber(args, key) {
|
|
7446
|
+
const v = args[key];
|
|
7447
|
+
if (v === void 0 || v === null) return void 0;
|
|
7448
|
+
if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`argument ${key} must be a finite number`);
|
|
7449
|
+
return v;
|
|
7450
|
+
}
|
|
7218
7451
|
//#endregion
|
|
7219
7452
|
//#region src/mcp/control-server.ts
|
|
7220
7453
|
/**
|
|
@@ -214181,6 +214414,9 @@ var Daemon = class Daemon {
|
|
|
214181
214414
|
return client.channelAgents(req);
|
|
214182
214415
|
},
|
|
214183
214416
|
messageAgent: (req) => this.messageAgent(req),
|
|
214417
|
+
startOrchestration: (req) => this.startOrchestration(req),
|
|
214418
|
+
getOrchestration: (req) => Promise.resolve(this.getOrchestrationForOwner(req)),
|
|
214419
|
+
cancelOrchestration: (req) => Promise.resolve(this.cancelOrchestrationForOwner(req)),
|
|
214184
214420
|
memory: this.memory,
|
|
214185
214421
|
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
214186
214422
|
channel,
|
|
@@ -214295,6 +214531,8 @@ var Daemon = class Daemon {
|
|
|
214295
214531
|
};
|
|
214296
214532
|
this.watcher.on("add", debounced).on("change", debounced).on("unlink", debounced);
|
|
214297
214533
|
this.log.info(`watching ${this.agentsDir} for agent changes`);
|
|
214534
|
+
this.replayInbox();
|
|
214535
|
+
this.rearmOrchestrationDeadlines();
|
|
214298
214536
|
this.startCpClient(root);
|
|
214299
214537
|
this.armIdleSweep();
|
|
214300
214538
|
this.log.info("daemon ready");
|
|
@@ -215058,6 +215296,7 @@ var Daemon = class Daemon {
|
|
|
215058
215296
|
}
|
|
215059
215297
|
relayMsgAcks = /* @__PURE__ */ new Map();
|
|
215060
215298
|
agentCallDeliveries = /* @__PURE__ */ new Map();
|
|
215299
|
+
orchestrationDeadlines = /* @__PURE__ */ new Map();
|
|
215061
215300
|
/**
|
|
215062
215301
|
* Dispatch one inbound relay item (`rd/msg` — a webchat op or a hook fire) and
|
|
215063
215302
|
* return the `rd/ack` verdict — the relay-path entry, reusing the SAME turn
|
|
@@ -215225,10 +215464,19 @@ var Daemon = class Daemon {
|
|
|
215225
215464
|
reason: "not_allowed"
|
|
215226
215465
|
});
|
|
215227
215466
|
}
|
|
215228
|
-
const
|
|
215467
|
+
const callerKey = sessionKey(platform, req.callerChannel, req.callerThread, req.callerAgentId);
|
|
215468
|
+
const inbound = this.activeTurnCallMeta.get(callerKey);
|
|
215469
|
+
const hopCount = inbound ? inbound.hopCount + 1 : 0;
|
|
215470
|
+
if (hopCount > MAX_AGENT_CALL_HOPS) return record({
|
|
215471
|
+
delivered: false,
|
|
215472
|
+
targetSession,
|
|
215473
|
+
reason: "hop_limit"
|
|
215474
|
+
});
|
|
215475
|
+
const isReply = inbound !== void 0 && req.toAgentId === inbound.callFrom;
|
|
215476
|
+
const correlationId = req.correlationId !== void 0 ? req.correlationId : isReply ? inbound.correlationId : void 0;
|
|
215229
215477
|
const callMeta = {
|
|
215230
215478
|
callFrom: req.callerAgentId,
|
|
215231
|
-
...
|
|
215479
|
+
...correlationId !== void 0 ? { correlationId } : {},
|
|
215232
215480
|
hopCount,
|
|
215233
215481
|
deliveryId
|
|
215234
215482
|
};
|
|
@@ -215311,6 +215559,235 @@ var Daemon = class Daemon {
|
|
|
215311
215559
|
return p === "telegram" || p === "webchat" || p === "discord" || p === "hook" ? p : "slack";
|
|
215312
215560
|
}
|
|
215313
215561
|
/**
|
|
215562
|
+
* Start an orchestration (§3.4/§6.8). ATOMIC ordering per §3.4:
|
|
215563
|
+
* (a) RECORD-FIRST — persist the orchestration header + every subtask (status
|
|
215564
|
+
* 'pending') in one transaction BEFORE any delivery, so a fast worker's reply can
|
|
215565
|
+
* never arrive before the record exists (§3.3 would otherwise drop it);
|
|
215566
|
+
* (b) deliver each subtask via the existing `messageAgent` path, CAS-recording
|
|
215567
|
+
* pending→sending→delivered | pending→sending→(delivered rollback)failed per subtask;
|
|
215568
|
+
* (c) schedule the one-shot, session-anchored, cancelable cron deadline (if requested)
|
|
215569
|
+
* — but only when at least one subtask actually delivered (all-failed ⇒ no wait).
|
|
215570
|
+
* The main identity + coords are the TRUSTED SessionContext (never tool input).
|
|
215571
|
+
*/
|
|
215572
|
+
async startOrchestration(req) {
|
|
215573
|
+
const orchestrationId = randomUUID();
|
|
215574
|
+
const platform = this.narrowPlatform(req.platform);
|
|
215575
|
+
const mainSessionKey = sessionKey(platform, req.channel, req.thread, req.mainAgentId);
|
|
215576
|
+
const now = this.clock.now();
|
|
215577
|
+
const deadline = req.deadlineMs !== void 0 && req.deadlineMs > 0 ? now + Math.min(req.deadlineMs, 2147483647) : null;
|
|
215578
|
+
const subtaskRows = req.subtasks.map((s, idx) => ({
|
|
215579
|
+
orchestrationId,
|
|
215580
|
+
correlationId: `${orchestrationId}.${idx}`,
|
|
215581
|
+
idx,
|
|
215582
|
+
toAgentId: s.toAgentId,
|
|
215583
|
+
text: s.text,
|
|
215584
|
+
status: "pending",
|
|
215585
|
+
updatedAt: monotonicTs()
|
|
215586
|
+
}));
|
|
215587
|
+
const orch = {
|
|
215588
|
+
orchestrationId,
|
|
215589
|
+
mainSessionKey,
|
|
215590
|
+
mainAgentId: req.mainAgentId,
|
|
215591
|
+
platform,
|
|
215592
|
+
channel: req.channel,
|
|
215593
|
+
thread: req.thread,
|
|
215594
|
+
integrationId: req.integrationId ?? null,
|
|
215595
|
+
replyTarget: req.replyTarget ?? null,
|
|
215596
|
+
deadline,
|
|
215597
|
+
status: "active",
|
|
215598
|
+
createdAt: now,
|
|
215599
|
+
updatedAt: now
|
|
215600
|
+
};
|
|
215601
|
+
this.store.createOrchestration(orch, subtaskRows);
|
|
215602
|
+
const delivered = [];
|
|
215603
|
+
const failed = [];
|
|
215604
|
+
for (const s of subtaskRows) {
|
|
215605
|
+
this.store.setSubtaskStatus(orchestrationId, s.correlationId, ["pending"], "sending", monotonicTs());
|
|
215606
|
+
let result;
|
|
215607
|
+
try {
|
|
215608
|
+
result = await this.messageAgent({
|
|
215609
|
+
callerAgentId: req.mainAgentId,
|
|
215610
|
+
platform: req.platform,
|
|
215611
|
+
callerChannel: req.channel,
|
|
215612
|
+
callerThread: req.thread,
|
|
215613
|
+
toAgentId: s.toAgentId,
|
|
215614
|
+
text: s.text,
|
|
215615
|
+
channel: req.channel,
|
|
215616
|
+
thread: req.thread,
|
|
215617
|
+
correlationId: s.correlationId
|
|
215618
|
+
});
|
|
215619
|
+
} catch (err) {
|
|
215620
|
+
result = {
|
|
215621
|
+
delivered: false,
|
|
215622
|
+
targetSession: "",
|
|
215623
|
+
reason: "error"
|
|
215624
|
+
};
|
|
215625
|
+
this.log.warn(`orchestration ${orchestrationId}: delivery of ${s.correlationId} threw: ${formatErr(err)}`);
|
|
215626
|
+
}
|
|
215627
|
+
if (result.delivered) {
|
|
215628
|
+
this.store.setSubtaskStatus(orchestrationId, s.correlationId, ["sending"], "delivered", monotonicTs());
|
|
215629
|
+
delivered.push(s.correlationId);
|
|
215630
|
+
} else {
|
|
215631
|
+
const reason = result.reason ?? "undeliverable";
|
|
215632
|
+
this.store.setSubtaskStatus(orchestrationId, s.correlationId, ["sending"], "worker_error", monotonicTs(), { deliveryReason: reason });
|
|
215633
|
+
failed.push({
|
|
215634
|
+
correlationId: s.correlationId,
|
|
215635
|
+
reason
|
|
215636
|
+
});
|
|
215637
|
+
}
|
|
215638
|
+
}
|
|
215639
|
+
if (deadline !== null && delivered.length > 0) this.armOrchestrationDeadline(orchestrationId, deadline);
|
|
215640
|
+
else if (deadline !== null) this.store.setOrchestrationDeadline(orchestrationId, null, this.clock.now());
|
|
215641
|
+
this.log.info(`orchestration ${orchestrationId}: ${delivered.length} delivered, ${failed.length} failed` + (deadline !== null && delivered.length > 0 ? `, deadline in ${deadline - now}ms` : ""));
|
|
215642
|
+
return {
|
|
215643
|
+
orchestrationId,
|
|
215644
|
+
delivered,
|
|
215645
|
+
failed
|
|
215646
|
+
};
|
|
215647
|
+
}
|
|
215648
|
+
/** Arm (or re-arm) the one-shot, session-anchored deadline for an orchestration. On fire
|
|
215649
|
+
* it wakes the main's session DIRECTLY (dispatch to mainSessionKey) — NOT via fireTrigger
|
|
215650
|
+
* (which would post a channel anchor / open a new thread). Idempotent: replaces any
|
|
215651
|
+
* existing timer for this id. Clamped to setTimeout's 32-bit ceiling. */
|
|
215652
|
+
armOrchestrationDeadline(orchestrationId, deadlineEpoch) {
|
|
215653
|
+
const existing = this.orchestrationDeadlines.get(orchestrationId);
|
|
215654
|
+
if (existing !== void 0) this.clock.clearTimeout(existing);
|
|
215655
|
+
const delay = Math.min(Math.max(0, deadlineEpoch - this.clock.now()), 2147483647);
|
|
215656
|
+
const handle = this.clock.setTimeout(() => {
|
|
215657
|
+
this.orchestrationDeadlines.delete(orchestrationId);
|
|
215658
|
+
this.fireOrchestrationDeadline(orchestrationId);
|
|
215659
|
+
}, delay);
|
|
215660
|
+
this.orchestrationDeadlines.set(orchestrationId, handle);
|
|
215661
|
+
}
|
|
215662
|
+
/** Deadline fired (§3.5): mark every still-open subtask timed_out, then WAKE the main's
|
|
215663
|
+
* session so it re-reads getOrchestration and summarizes the partial result. The wake is
|
|
215664
|
+
* a direct dispatch to the stored session coords — no platform post, no new thread. */
|
|
215665
|
+
fireOrchestrationDeadline(orchestrationId) {
|
|
215666
|
+
const orch = this.store.getOrchestration(orchestrationId);
|
|
215667
|
+
if (!orch || orch.status !== "active") return;
|
|
215668
|
+
for (const s of this.store.getSubtasks(orchestrationId)) this.store.setSubtaskStatus(orchestrationId, s.correlationId, [
|
|
215669
|
+
"pending",
|
|
215670
|
+
"sending",
|
|
215671
|
+
"delivered"
|
|
215672
|
+
], "timed_out", monotonicTs());
|
|
215673
|
+
this.store.setOrchestrationDeadline(orchestrationId, null, this.clock.now());
|
|
215674
|
+
this.wakeOrchestrationMain(orch, `orchestration ${orchestrationId} deadline reached — summarize what you have`);
|
|
215675
|
+
}
|
|
215676
|
+
/** Wake the orchestration's owning main session with a synthetic agent-source turn keyed
|
|
215677
|
+
* to the exact stored coords (so it lands in the same session that started it). Headless
|
|
215678
|
+
* is NOT set — the main needs its reply transport to post the summary. */
|
|
215679
|
+
wakeOrchestrationMain(orch, text) {
|
|
215680
|
+
const platform = this.narrowPlatform(orch.platform);
|
|
215681
|
+
const msg = {
|
|
215682
|
+
msgId: `orchestration:${orch.orchestrationId}:${monotonicTs()}`,
|
|
215683
|
+
traceId: orch.orchestrationId,
|
|
215684
|
+
source: "agent",
|
|
215685
|
+
platform,
|
|
215686
|
+
channel: orch.channel,
|
|
215687
|
+
thread: orch.thread,
|
|
215688
|
+
sender: {
|
|
215689
|
+
id: `orchestration:${orch.orchestrationId}`,
|
|
215690
|
+
isBot: true
|
|
215691
|
+
},
|
|
215692
|
+
text,
|
|
215693
|
+
mentionedBots: [],
|
|
215694
|
+
isDm: false
|
|
215695
|
+
};
|
|
215696
|
+
this.dispatch(orch.mainAgentId, msg, orch.integrationId ?? void 0).catch((err) => this.log.error(`orchestration ${orch.orchestrationId}: deadline wake dispatch failed: ${formatErr(err)}`));
|
|
215697
|
+
}
|
|
215698
|
+
/** §3.3 correlation-recording hook, called from dispatchOne when the MAIN receives a
|
|
215699
|
+
* messageAgent turn carrying a correlationId. Records the worker's result into the
|
|
215700
|
+
* orchestration ONLY if ALL four safety checks hold; any failure drops the report
|
|
215701
|
+
* (debug log) and never corrupts completion. Uses ONLY trusted values (callMeta.callFrom,
|
|
215702
|
+
* the receiving session key) — never a frame/prompt field. */
|
|
215703
|
+
recordWorkerReport(receivingSessionKey, callMeta, reportText) {
|
|
215704
|
+
const correlationId = callMeta.correlationId;
|
|
215705
|
+
if (correlationId === void 0) return;
|
|
215706
|
+
const dot = correlationId.lastIndexOf(".");
|
|
215707
|
+
if (dot <= 0) return;
|
|
215708
|
+
const orchestrationId = correlationId.slice(0, dot);
|
|
215709
|
+
const orch = this.store.getOrchestration(orchestrationId);
|
|
215710
|
+
if (!orch) return;
|
|
215711
|
+
if (orch.mainSessionKey !== receivingSessionKey) {
|
|
215712
|
+
this.log.debug(`orchestration ${orchestrationId}: report dropped — session mismatch`);
|
|
215713
|
+
return;
|
|
215714
|
+
}
|
|
215715
|
+
if (orch.status !== "active") {
|
|
215716
|
+
this.log.debug(`orchestration ${orchestrationId}: report dropped — orchestration ${orch.status}`);
|
|
215717
|
+
return;
|
|
215718
|
+
}
|
|
215719
|
+
const sub = this.store.getSubtaskByCorrelation(orchestrationId, correlationId);
|
|
215720
|
+
if (!sub) return;
|
|
215721
|
+
if (callMeta.callFrom !== sub.toAgentId) {
|
|
215722
|
+
this.log.debug(`orchestration ${orchestrationId}: report for ${correlationId} dropped — reporter "${callMeta.callFrom}" ≠ tasked worker "${sub.toAgentId}"`);
|
|
215723
|
+
return;
|
|
215724
|
+
}
|
|
215725
|
+
if (this.store.setSubtaskStatus(orchestrationId, correlationId, [
|
|
215726
|
+
"sending",
|
|
215727
|
+
"delivered",
|
|
215728
|
+
"timed_out"
|
|
215729
|
+
], "succeeded", monotonicTs(), { result: reportText })) this.log.info(`orchestration ${orchestrationId}: recorded result for ${correlationId} from ${callMeta.callFrom}`);
|
|
215730
|
+
else this.log.debug(`orchestration ${orchestrationId}: duplicate/late report for ${correlationId} — no-op`);
|
|
215731
|
+
}
|
|
215732
|
+
getOrchestrationForOwner(req) {
|
|
215733
|
+
const orch = this.ownedOrchestration(req);
|
|
215734
|
+
if (!orch) return null;
|
|
215735
|
+
return {
|
|
215736
|
+
orchestrationId: orch.orchestrationId,
|
|
215737
|
+
status: orch.status,
|
|
215738
|
+
deadline: orch.deadline ?? null,
|
|
215739
|
+
replyTarget: orch.replyTarget ?? null,
|
|
215740
|
+
createdAt: orch.createdAt,
|
|
215741
|
+
subtasks: this.store.getSubtasks(orch.orchestrationId).map((s) => ({
|
|
215742
|
+
correlationId: s.correlationId,
|
|
215743
|
+
toAgentId: s.toAgentId,
|
|
215744
|
+
status: s.status,
|
|
215745
|
+
result: s.result ?? null,
|
|
215746
|
+
...s.deliveryReason ? { deliveryReason: s.deliveryReason } : {}
|
|
215747
|
+
}))
|
|
215748
|
+
};
|
|
215749
|
+
}
|
|
215750
|
+
cancelOrchestrationForOwner(req) {
|
|
215751
|
+
const orch = this.ownedOrchestration(req);
|
|
215752
|
+
if (!orch) return false;
|
|
215753
|
+
const handle = this.orchestrationDeadlines.get(orch.orchestrationId);
|
|
215754
|
+
if (handle !== void 0) {
|
|
215755
|
+
this.clock.clearTimeout(handle);
|
|
215756
|
+
this.orchestrationDeadlines.delete(orch.orchestrationId);
|
|
215757
|
+
}
|
|
215758
|
+
this.store.setOrchestrationDeadline(orch.orchestrationId, null, this.clock.now());
|
|
215759
|
+
this.store.setOrchestrationStatus(orch.orchestrationId, "cancelled", this.clock.now());
|
|
215760
|
+
return true;
|
|
215761
|
+
}
|
|
215762
|
+
/** Resolve an orchestration IFF the requesting main+session OWNS it (§3.5a owner check).
|
|
215763
|
+
* Returns undefined on unknown id or any owner mismatch. */
|
|
215764
|
+
ownedOrchestration(req) {
|
|
215765
|
+
const orch = this.store.getOrchestration(req.orchestrationId);
|
|
215766
|
+
if (!orch) return void 0;
|
|
215767
|
+
const requesterKey = sessionKey(this.narrowPlatform(req.platform), req.channel, req.thread, req.mainAgentId);
|
|
215768
|
+
if (orch.mainSessionKey !== requesterKey || orch.mainAgentId !== req.mainAgentId) return void 0;
|
|
215769
|
+
return orch;
|
|
215770
|
+
}
|
|
215771
|
+
/** Startup re-arm (§3.5): re-arm the one-shot deadline for every still-active orchestration
|
|
215772
|
+
* from the durable `deadline` epoch. A deadline already in the past fires ~immediately.
|
|
215773
|
+
* Mirrors replayInbox's read-active-rows/re-schedule pattern; idempotent. */
|
|
215774
|
+
rearmOrchestrationDeadlines() {
|
|
215775
|
+
let active;
|
|
215776
|
+
try {
|
|
215777
|
+
active = this.store.listActiveOrchestrations();
|
|
215778
|
+
} catch (err) {
|
|
215779
|
+
this.log.warn(`orchestration: deadline re-arm read failed: ${err.message}`);
|
|
215780
|
+
return;
|
|
215781
|
+
}
|
|
215782
|
+
let armed = 0;
|
|
215783
|
+
for (const orch of active) {
|
|
215784
|
+
if (orch.deadline == null) continue;
|
|
215785
|
+
this.armOrchestrationDeadline(orch.orchestrationId, orch.deadline);
|
|
215786
|
+
armed++;
|
|
215787
|
+
}
|
|
215788
|
+
if (armed) this.log.info(`orchestration: re-armed ${armed} deadline(s) on startup`);
|
|
215789
|
+
}
|
|
215790
|
+
/**
|
|
215314
215791
|
* Ack-verdict gate for one hook fire (`rd/msg` hook member) — the mirror of
|
|
215315
215792
|
* {@link dispatchWebchatTurn}'s synchronous gates: the relay's rc/run-report
|
|
215316
215793
|
* needs a REASONED rejection now, not a silently dropped fire-and-forget
|
|
@@ -215537,6 +216014,10 @@ var Daemon = class Daemon {
|
|
|
215537
216014
|
}
|
|
215538
216015
|
inflight = /* @__PURE__ */ new Set();
|
|
215539
216016
|
serialQueue = /* @__PURE__ */ new Map();
|
|
216017
|
+
/** §6.9 #353: stable ids of messages currently backed by a durable inbox row (admitted,
|
|
216018
|
+
* not yet terminal). Guards startup replay from re-admitting a row whose entry is already
|
|
216019
|
+
* live in the gate (idempotency — a duplicate/in-flight id is not double-processed). */
|
|
216020
|
+
liveInboxIds = /* @__PURE__ */ new Set();
|
|
215540
216021
|
/**
|
|
215541
216022
|
* Resolve a command's target from the channel's latest session when the routing ladder
|
|
215542
216023
|
* couldn't (no mention entity / thread / dm rule matched — e.g. a group `/status@bot`).
|
|
@@ -215868,6 +216349,49 @@ var Daemon = class Daemon {
|
|
|
215868
216349
|
}
|
|
215869
216350
|
}
|
|
215870
216351
|
/**
|
|
216352
|
+
* §6.9 #353 durable inbox: persist an ADMITTED entry BEFORE its admission ACK/return, so a
|
|
216353
|
+
* hard kill / agent move can't lose a message the caller was already told delivered:true.
|
|
216354
|
+
* Called ONLY on the two genuine admission branches in dispatch() (claim + enqueue), never
|
|
216355
|
+
* on a rejected/gate-dropped admission — a queue-full or paused/draining drop persists
|
|
216356
|
+
* nothing. WEBCHAT turns are skipped (§6.9 #367): their `sink` is a live in-memory transport
|
|
216357
|
+
* that can't be restored across a restart and a dead browser socket can't be resumed, so a
|
|
216358
|
+
* durable row would be un-replayable. Sets `entry.inboxId` so every terminal path can delete
|
|
216359
|
+
* the row. The row id is the message's stable deliveryId/msgId (§6.3) — idempotent re-append.
|
|
216360
|
+
*/
|
|
216361
|
+
persistInbox(entry, key) {
|
|
216362
|
+
if (entry.webchat) return;
|
|
216363
|
+
const id = entry.callMeta?.deliveryId ?? entry.msg.msgId;
|
|
216364
|
+
try {
|
|
216365
|
+
this.store.appendInbox({
|
|
216366
|
+
id,
|
|
216367
|
+
sessionKey: key,
|
|
216368
|
+
agentId: entry.agentId,
|
|
216369
|
+
msg: JSON.stringify(entry.msg),
|
|
216370
|
+
integrationId: entry.integrationId ?? null,
|
|
216371
|
+
callMeta: entry.callMeta ? JSON.stringify(entry.callMeta) : null,
|
|
216372
|
+
isQueueCmd: entry.isQueueCmd ? 1 : null,
|
|
216373
|
+
enqueuedAt: monotonicTs()
|
|
216374
|
+
});
|
|
216375
|
+
entry.inboxId = id;
|
|
216376
|
+
this.liveInboxIds.add(id);
|
|
216377
|
+
} catch (err) {
|
|
216378
|
+
this.log.warn(`durable inbox: append failed for ${id}: ${err.message}`);
|
|
216379
|
+
}
|
|
216380
|
+
}
|
|
216381
|
+
/** Remove an entry's durable inbox row once its turn reaches ANY terminal state (success,
|
|
216382
|
+
* reject/fail-stop, cancel, gate-drop) so it is not replayed on the next startup. No-op
|
|
216383
|
+
* for a non-persisted entry (webchat / never-admitted). NOT called on graceful-shutdown
|
|
216384
|
+
* settle: those admitted-but-unrun rows are intentionally LEFT for startup replay. */
|
|
216385
|
+
removeInbox(entry) {
|
|
216386
|
+
if (!entry.inboxId) return;
|
|
216387
|
+
this.liveInboxIds.delete(entry.inboxId);
|
|
216388
|
+
try {
|
|
216389
|
+
this.store.removeInbox(entry.inboxId);
|
|
216390
|
+
} catch (err) {
|
|
216391
|
+
this.log.warn(`durable inbox: remove failed for ${entry.inboxId}: ${err.message}`);
|
|
216392
|
+
}
|
|
216393
|
+
}
|
|
216394
|
+
/**
|
|
215871
216395
|
* Terminate a QUEUED entry's webchat sink when it is gate-dropped/rejected without ever
|
|
215872
216396
|
* entering dispatchOne (drain/pause gate-drop, fail-stop of the queued rest, `!cancel`/`!stop`
|
|
215873
216397
|
* queue drop, shutdown). A webchat turn signals its client via the WebchatSink terminal
|
|
@@ -215915,9 +216439,11 @@ var Daemon = class Daemon {
|
|
|
215915
216439
|
}
|
|
215916
216440
|
q.push(entry);
|
|
215917
216441
|
this.serialQueue.set(key, q);
|
|
216442
|
+
this.persistInbox(entry, key);
|
|
215918
216443
|
this.log.debug(`dispatch: queued behind in-flight turn for session ${key} (depth ${q.length})`);
|
|
215919
216444
|
return;
|
|
215920
216445
|
}
|
|
216446
|
+
this.persistInbox(entry, key);
|
|
215921
216447
|
this.inflight.add(key);
|
|
215922
216448
|
this.runLoop(key, entry);
|
|
215923
216449
|
});
|
|
@@ -215939,11 +216465,13 @@ var Daemon = class Daemon {
|
|
|
215939
216465
|
while (entry) {
|
|
215940
216466
|
if (entry !== firstEntry && (this.draining || this.drainingAgents.has(entry.agentId) || this.paused(entry.agentId))) {
|
|
215941
216467
|
this.terminateQueuedSink(entry);
|
|
216468
|
+
if (!this.draining) this.removeInbox(entry);
|
|
215942
216469
|
entry.resolve(null);
|
|
215943
216470
|
const rest = this.serialQueue.get(key) ?? [];
|
|
215944
216471
|
this.serialQueue.delete(key);
|
|
215945
216472
|
for (const e of rest) {
|
|
215946
216473
|
this.terminateQueuedSink(e);
|
|
216474
|
+
if (!this.draining) this.removeInbox(e);
|
|
215947
216475
|
e.resolve(null);
|
|
215948
216476
|
}
|
|
215949
216477
|
this.inflight.delete(key);
|
|
@@ -215951,14 +216479,17 @@ var Daemon = class Daemon {
|
|
|
215951
216479
|
}
|
|
215952
216480
|
try {
|
|
215953
216481
|
const sessionId = await this.dispatchOne(entry, key);
|
|
216482
|
+
this.removeInbox(entry);
|
|
215954
216483
|
entry.resolve(sessionId);
|
|
215955
216484
|
} catch (err) {
|
|
216485
|
+
if (!this.draining) this.removeInbox(entry);
|
|
215956
216486
|
entry.reject(err);
|
|
215957
216487
|
const rest = this.serialQueue.get(key) ?? [];
|
|
215958
216488
|
this.serialQueue.delete(key);
|
|
215959
216489
|
for (const e of rest) {
|
|
215960
216490
|
const failStop = new FailStopError(key);
|
|
215961
216491
|
this.terminateQueuedSink(e, failStop.message);
|
|
216492
|
+
if (!this.draining) this.removeInbox(e);
|
|
215962
216493
|
e.reject(failStop);
|
|
215963
216494
|
}
|
|
215964
216495
|
this.inflight.delete(key);
|
|
@@ -215972,6 +216503,7 @@ var Daemon = class Daemon {
|
|
|
215972
216503
|
}
|
|
215973
216504
|
async dispatchOne(entry, key) {
|
|
215974
216505
|
const { agentId, msg, integrationId, webchat, callMeta } = entry;
|
|
216506
|
+
if (callMeta?.correlationId !== void 0) this.recordWorkerReport(key, callMeta, msg.text);
|
|
215975
216507
|
const agent = this.agents.get(agentId);
|
|
215976
216508
|
const rec = new TranscriptRecorder();
|
|
215977
216509
|
const replyConn = msg.headless || webchat ? void 0 : this.replyConnFor(agentId, integrationId);
|
|
@@ -216028,6 +216560,7 @@ var Daemon = class Daemon {
|
|
|
216028
216560
|
} } : {}
|
|
216029
216561
|
};
|
|
216030
216562
|
this.pending.set(sessionId, p);
|
|
216563
|
+
if (callMeta) this.activeTurnCallMeta.set(key, callMeta);
|
|
216031
216564
|
let finalPhase = "end";
|
|
216032
216565
|
try {
|
|
216033
216566
|
const host = await this.ensureHostAsync(agentId);
|
|
@@ -216103,6 +216636,7 @@ var Daemon = class Daemon {
|
|
|
216103
216636
|
this.releasePermissions(sessionId);
|
|
216104
216637
|
this.releaseElicits(sessionId);
|
|
216105
216638
|
this.pending.delete(sessionId);
|
|
216639
|
+
if (callMeta) this.activeTurnCallMeta.delete(key);
|
|
216106
216640
|
this.store.setSessionState(key, "idle", this.clock.now());
|
|
216107
216641
|
this.emitSessionMetadataSnapshot({
|
|
216108
216642
|
sessionId,
|
|
@@ -216130,6 +216664,7 @@ var Daemon = class Daemon {
|
|
|
216130
216664
|
this.serialQueue.delete(key);
|
|
216131
216665
|
for (const e of queued) {
|
|
216132
216666
|
this.terminateQueuedSink(e, reason);
|
|
216667
|
+
this.removeInbox(e);
|
|
216133
216668
|
e.reject(new FailStopError(key));
|
|
216134
216669
|
}
|
|
216135
216670
|
}
|
|
@@ -216591,6 +217126,17 @@ var Daemon = class Daemon {
|
|
|
216591
217126
|
p.idleTimer = void 0;
|
|
216592
217127
|
}
|
|
216593
217128
|
pending = /* @__PURE__ */ new Map();
|
|
217129
|
+
/**
|
|
217130
|
+
* DAEMON-PRIVATE trusted call metadata of the CURRENTLY in-flight turn, keyed by the
|
|
217131
|
+
* turn's LOGICAL sessionKey (§6.7). Installed at the start of a `dispatchOne` turn that
|
|
217132
|
+
* carries `callMeta` and cleared in its `finally`; the tool is keyed by the static
|
|
217133
|
+
* session-level MCP token, so a nested `messageAgent` resolves the caller's sessionKey
|
|
217134
|
+
* (from its trusted SessionContext coords + agentId) and reads THIS map to auto-inherit
|
|
217135
|
+
* hop/origin (all child calls) and correlationId (replies only) — never trusting the
|
|
217136
|
+
* agent to pass them. A plain human/platform turn has no entry, so a `messageAgent` it
|
|
217137
|
+
* makes is a fresh call (hopCount 0, no inherited correlation).
|
|
217138
|
+
*/
|
|
217139
|
+
activeTurnCallMeta = /* @__PURE__ */ new Map();
|
|
216594
217140
|
permSeq = 0;
|
|
216595
217141
|
pendingPerms = /* @__PURE__ */ new Map();
|
|
216596
217142
|
/**
|
|
@@ -217068,6 +217614,49 @@ var Daemon = class Daemon {
|
|
|
217068
217614
|
}
|
|
217069
217615
|
this.serialQueue.clear();
|
|
217070
217616
|
}
|
|
217617
|
+
/**
|
|
217618
|
+
* §6.9 #353 startup replay: re-admit durably-persisted admitted-but-not-completed inbox rows
|
|
217619
|
+
* through the SAME serial gate (`dispatch`), FIFO-by-sessionKey (rows come back ordered by
|
|
217620
|
+
* (sessionKey, enqueuedAt), so order within a sessionKey is preserved and the gate keeps them
|
|
217621
|
+
* serial). Runs once on start() after the store is open, agents are loaded into `this.agents`,
|
|
217622
|
+
* and platform connections/crons are up (so a re-admitted turn has its reply transport), as
|
|
217623
|
+
* normal inbound begins. Idempotency: a row whose id is already live in the gate (`liveInboxIds`)
|
|
217624
|
+
* is skipped — dispatch re-appends the same id with INSERT OR IGNORE so no double row is written,
|
|
217625
|
+
* and the re-admitted entry adopts the existing row for later removal. Rows for an agent that no
|
|
217626
|
+
* longer exists on this daemon are SKIPPED + logged (the simplest choice — another owner/daemon
|
|
217627
|
+
* may hold that agent; we neither drop the row nor block on it). Webchat turns were never
|
|
217628
|
+
* persisted, so none appear here.
|
|
217629
|
+
*/
|
|
217630
|
+
replayInbox() {
|
|
217631
|
+
let rows;
|
|
217632
|
+
try {
|
|
217633
|
+
rows = this.store.listInboxBySessionKeyFifo();
|
|
217634
|
+
} catch (err) {
|
|
217635
|
+
this.log.warn(`durable inbox: replay read failed: ${err.message}`);
|
|
217636
|
+
return;
|
|
217637
|
+
}
|
|
217638
|
+
if (rows.length === 0) return;
|
|
217639
|
+
let replayed = 0;
|
|
217640
|
+
for (const row of rows) {
|
|
217641
|
+
if (this.liveInboxIds.has(row.id)) continue;
|
|
217642
|
+
if (!this.agents.has(row.agentId)) {
|
|
217643
|
+
this.log.warn(`durable inbox: skipping replay of ${row.id} — unknown agent "${row.agentId}"`);
|
|
217644
|
+
continue;
|
|
217645
|
+
}
|
|
217646
|
+
let msg;
|
|
217647
|
+
let callMeta;
|
|
217648
|
+
try {
|
|
217649
|
+
msg = JSON.parse(row.msg);
|
|
217650
|
+
callMeta = row.callMeta ? JSON.parse(row.callMeta) : void 0;
|
|
217651
|
+
} catch (err) {
|
|
217652
|
+
this.log.warn(`durable inbox: skipping corrupt row ${row.id}: ${err.message}`);
|
|
217653
|
+
continue;
|
|
217654
|
+
}
|
|
217655
|
+
this.dispatch(row.agentId, msg, row.integrationId ?? void 0, void 0, callMeta, row.isQueueCmd ? { isQueueCmd: true } : void 0).catch(() => {});
|
|
217656
|
+
replayed++;
|
|
217657
|
+
}
|
|
217658
|
+
if (replayed) this.log.info(`durable inbox: replayed ${replayed} admitted message(s) through the serial gate`);
|
|
217659
|
+
}
|
|
217071
217660
|
/** daemon/restart + daemon/upgrade (§8.3): ack now, then drain + stop + exit so
|
|
217072
217661
|
* the supervisor relaunches (the new binary, for upgrade). */
|
|
217073
217662
|
scheduleFleetExit(kind, targetVersion) {
|
|
@@ -217423,6 +218012,8 @@ var Daemon = class Daemon {
|
|
|
217423
218012
|
this.cancelTimers.clear();
|
|
217424
218013
|
for (const t of this.slackRetryTimers.values()) this.clock.clearTimeout(t);
|
|
217425
218014
|
this.slackRetryTimers.clear();
|
|
218015
|
+
for (const t of this.orchestrationDeadlines.values()) this.clock.clearTimeout(t);
|
|
218016
|
+
this.orchestrationDeadlines.clear();
|
|
217426
218017
|
this.metrics?.stop();
|
|
217427
218018
|
await this.watcher?.close();
|
|
217428
218019
|
await this.drainForShutdown();
|
|
@@ -217473,4 +218064,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
217473
218064
|
//#endregion
|
|
217474
218065
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
217475
218066
|
|
|
217476
|
-
//# sourceMappingURL=run-foreground-
|
|
218067
|
+
//# sourceMappingURL=run-foreground-D_sn_AMV.js.map
|