@agentconnect.md/daemon 1.0.0-rc.323 → 1.0.0-rc.324
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-BEjpql6l.js → acp-host-DKkoWiWe.js} +70 -4
- package/dist/acp-host-DKkoWiWe.js.map +1 -0
- package/dist/{agent-list-Ckqv2x26.js → agent-list-CE6JNwIB.js} +2 -2
- package/dist/{agent-list-Ckqv2x26.js.map → agent-list-CE6JNwIB.js.map} +1 -1
- package/dist/{chat-MTttQBSF.js → chat-lv_zfcAK.js} +3 -3
- package/dist/{chat-MTttQBSF.js.map → chat-lv_zfcAK.js.map} +1 -1
- package/dist/{dist-D8I6ZfGL.js → dist-D97dgR66.js} +212 -8
- package/dist/dist-D97dgR66.js.map +1 -0
- package/dist/{gh-token-CeVzSIMp.js → gh-token-CYlbctQZ.js} +2 -2
- package/dist/{gh-token-CeVzSIMp.js.map → gh-token-CYlbctQZ.js.map} +1 -1
- package/dist/index.js +5 -5
- package/dist/{load-agents-C2pFahVp.js → load-agents-CuXy_Ei1.js} +2 -2
- package/dist/{load-agents-C2pFahVp.js.map → load-agents-CuXy_Ei1.js.map} +1 -1
- package/dist/{login-D6xoGjQX.js → login-BMPemxkh.js} +4 -4
- package/dist/{login-D6xoGjQX.js.map → login-BMPemxkh.js.map} +1 -1
- package/dist/{run-foreground-B5Kfk1KO.js → run-foreground-BLhVJZlp.js} +1102 -89
- package/dist/{run-foreground-B5Kfk1KO.js.map → run-foreground-BLhVJZlp.js.map} +1 -1
- package/dist/run-foreground-DhYjpWCl.js +2 -0
- package/package.json +1 -1
- package/dist/acp-host-BEjpql6l.js.map +0 -1
- package/dist/dist-D8I6ZfGL.js.map +0 -1
- package/dist/run-foreground-D0Q64Gar.js +0 -2
|
@@ -3,10 +3,10 @@ import { n as require_ms, t as require_src$3 } from "./src-DDtlVutj.js";
|
|
|
3
3
|
import { n as init_esm_shims, t as __dirname } from "./esm-shims-DxGcm4Hw.js";
|
|
4
4
|
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
|
-
import { a as RELAY_DAEMON_WS_PATH, c as SHARED_CONFIG_ACTION_ID, d as encode, f as MAX_FRAME_BYTES, g as
|
|
7
|
-
import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-
|
|
6
|
+
import { _ as ZodIssueCode, a as RELAY_DAEMON_WS_PATH, c as SHARED_CONFIG_ACTION_ID, d as encode, f as MAX_FRAME_BYTES, g as RESERVED_MCP_SERVER_NAME, i as RELAY_DAEMON_SUBPROTOCOL, l as buildEnvelope, m as HookReport, n as gitRepoLabel, o as buildRelayDaemonFrame, r as normalizeGitUrl, s as decodeRelayDaemonFrame, u as decodeEnvelope } from "./dist-D97dgR66.js";
|
|
7
|
+
import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-CuXy_Ei1.js";
|
|
8
8
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
9
|
-
import { C as
|
|
9
|
+
import { C as cachedRuntimeNames, S as toolsForIntegrations, T as resolveRuntimes, _ as resolveInMemoryDir, a as prepareWorkspace, b as GITHUB_REVIEW_TOOLS, c as sessionGitEnv, d as memoryKindOf, f as memoryProviderFor, g as listMemory, h as MemoryTooLargeError, i as prefetchWorkspace, l as simpleGit, m as MemoryPathError, n as turnFailureReason, o as initGitInjection, p as MemoryConflictError, r as installedRuntimes, s as probeGitVersion, t as AcpHost, u as createMemoryProvider, v as writeMemoryFile, w as cachedRuntimeVersions, x as MEMORY_TOOL_NAMES, y as ALL_TOOL_NAMES } from "./acp-host-DKkoWiWe.js";
|
|
10
10
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Bu-Svl7L.js";
|
|
11
11
|
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";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
@@ -4229,6 +4229,7 @@ var CpClient = class {
|
|
|
4229
4229
|
correlator;
|
|
4230
4230
|
stopped = false;
|
|
4231
4231
|
fatal = false;
|
|
4232
|
+
hookReportAckV1 = false;
|
|
4232
4233
|
attempt = 0;
|
|
4233
4234
|
reconnectTimer;
|
|
4234
4235
|
lastAuthedEpoch = 0;
|
|
@@ -4306,12 +4307,14 @@ var CpClient = class {
|
|
|
4306
4307
|
this.deps.onWebAppUrl?.(ok.webAppUrl);
|
|
4307
4308
|
this.deps.onOrgSlug?.(ok.orgSlug);
|
|
4308
4309
|
this.state = "REGISTERING";
|
|
4309
|
-
const
|
|
4310
|
+
const regOk = await this.request("register", {
|
|
4310
4311
|
host: this.deps.host,
|
|
4311
4312
|
capabilities: this.deps.capabilities(),
|
|
4312
4313
|
maxAgents: this.deps.maxAgents,
|
|
4313
4314
|
localState: this.deps.localState()
|
|
4314
|
-
})
|
|
4315
|
+
});
|
|
4316
|
+
const snap = regOk.payload;
|
|
4317
|
+
this.hookReportAckV1 = regOk.payload.serverFeatures?.includes("hook-report-ack-v1") ?? false;
|
|
4315
4318
|
this.routingEpoch = snap.routingEpoch;
|
|
4316
4319
|
this.deps.configApply.applyReconcileSnapshot(snap);
|
|
4317
4320
|
this.state = "READY";
|
|
@@ -4380,15 +4383,45 @@ var CpClient = class {
|
|
|
4380
4383
|
this.transport?.send(encode(buildEnvelope("cron/report", report)));
|
|
4381
4384
|
}
|
|
4382
4385
|
/**
|
|
4383
|
-
*
|
|
4384
|
-
*
|
|
4385
|
-
*
|
|
4386
|
-
*
|
|
4387
|
-
* No-op unless READY/DRAINING.
|
|
4386
|
+
* Durably converge a hook turn outcome (D→C `hook/report` REQ). The daemon
|
|
4387
|
+
* retains its metadata-only outbox row until the CP replies after persistence
|
|
4388
|
+
* and projection convergence, so a long CP outage delays GC instead of losing
|
|
4389
|
+
* a terminal result.
|
|
4388
4390
|
*/
|
|
4389
|
-
emitHookReport(report) {
|
|
4390
|
-
|
|
4391
|
-
this.
|
|
4391
|
+
async emitHookReport(report) {
|
|
4392
|
+
this.requireReady("hook/report");
|
|
4393
|
+
if (!this.hookReportAckV1) {
|
|
4394
|
+
this.transport.send(encode(buildEnvelope("hook/report", report)));
|
|
4395
|
+
return "legacy-sent";
|
|
4396
|
+
}
|
|
4397
|
+
const rep = await this.request("hook/report", report);
|
|
4398
|
+
if (rep.type !== "ack" || rep.payload.ok !== true) throw new WireError("INTERNAL", `expected hook/report ack, got ${rep.type}`, false);
|
|
4399
|
+
return "acknowledged";
|
|
4400
|
+
}
|
|
4401
|
+
/** Durable start barrier for an accepted GitHub hook turn. Formal review is
|
|
4402
|
+
* not exposed until this correlated request succeeds. */
|
|
4403
|
+
async startHook(payload) {
|
|
4404
|
+
this.requireReady("hook/start");
|
|
4405
|
+
const rep = await this.request("hook/start", payload);
|
|
4406
|
+
if (rep.type !== "hook/start/ok") throw new WireError("INTERNAL", `expected hook/start/ok, got ${rep.type}`, false);
|
|
4407
|
+
return rep.payload;
|
|
4408
|
+
}
|
|
4409
|
+
/** One-attempt, action-time formal-review purpose token. */
|
|
4410
|
+
async authorizeGithubReview(payload) {
|
|
4411
|
+
this.requireReady("github/review-authorize");
|
|
4412
|
+
const rep = await this.request("github/review-authorize", payload);
|
|
4413
|
+
if (rep.type !== "github/review-authorized") throw new WireError("INTERNAL", `expected github/review-authorized, got ${rep.type}`, false);
|
|
4414
|
+
return rep.payload;
|
|
4415
|
+
}
|
|
4416
|
+
/** Immediate body-free outcome; HookReport repeats it for lost-reply recovery. */
|
|
4417
|
+
async reportGithubReviewResult(payload) {
|
|
4418
|
+
this.requireReady("github/review-result");
|
|
4419
|
+
const rep = await this.request("github/review-result", payload);
|
|
4420
|
+
if (rep.type !== "github/review-result/ok") throw new WireError("INTERNAL", `expected github/review-result/ok, got ${rep.type}`, false);
|
|
4421
|
+
return rep.payload;
|
|
4422
|
+
}
|
|
4423
|
+
requireReady(op) {
|
|
4424
|
+
if (this.state !== "READY" && this.state !== "DRAINING" || !this.transport) throw new WireError("INTERNAL", `control plane unreachable for ${op} (client ${this.state})`, true);
|
|
4392
4425
|
}
|
|
4393
4426
|
request(type, payload) {
|
|
4394
4427
|
const frame = buildEnvelope(type, payload);
|
|
@@ -6416,6 +6449,11 @@ var LocalStore = class {
|
|
|
6416
6449
|
msg TEXT NOT NULL,
|
|
6417
6450
|
integrationId TEXT,
|
|
6418
6451
|
callMeta TEXT,
|
|
6452
|
+
hookContext TEXT,
|
|
6453
|
+
posterPublishState TEXT,
|
|
6454
|
+
terminalReport TEXT,
|
|
6455
|
+
legacyReportConnection TEXT,
|
|
6456
|
+
completedAt INTEGER,
|
|
6419
6457
|
isQueueCmd INTEGER,
|
|
6420
6458
|
loopGuardCounted INTEGER NOT NULL DEFAULT 0,
|
|
6421
6459
|
enqueuedAt TEXT NOT NULL
|
|
@@ -6481,6 +6519,7 @@ var LocalStore = class {
|
|
|
6481
6519
|
this.migrateInboxLoopGuardCounted();
|
|
6482
6520
|
this.migrateTranscriptToolBody();
|
|
6483
6521
|
this.migrateTranscriptRecipient();
|
|
6522
|
+
this.migrateInboxHookContext();
|
|
6484
6523
|
}
|
|
6485
6524
|
/** Add the `transcript.recipient` column (the agent a row was delivered to) to a
|
|
6486
6525
|
* pre-existing DB. Legacy rows have NULL recipient, so they surface in a session view
|
|
@@ -6488,6 +6527,16 @@ var LocalStore = class {
|
|
|
6488
6527
|
migrateTranscriptRecipient() {
|
|
6489
6528
|
if (!this.db.prepare("PRAGMA table_info(transcript)").all().some((c) => c.name === "recipient")) this.db.exec("ALTER TABLE transcript ADD COLUMN recipient TEXT");
|
|
6490
6529
|
}
|
|
6530
|
+
/** R1 hook turns must retain their trusted dispatch fence and the poster's
|
|
6531
|
+
* single-attempt state across restart. Upgrade existing local stores in place. */
|
|
6532
|
+
migrateInboxHookContext() {
|
|
6533
|
+
const cols = this.db.prepare("PRAGMA table_info(inbox)").all();
|
|
6534
|
+
if (!cols.some((c) => c.name === "hookContext")) this.db.exec("ALTER TABLE inbox ADD COLUMN hookContext TEXT");
|
|
6535
|
+
if (!cols.some((c) => c.name === "posterPublishState")) this.db.exec("ALTER TABLE inbox ADD COLUMN posterPublishState TEXT");
|
|
6536
|
+
if (!cols.some((c) => c.name === "terminalReport")) this.db.exec("ALTER TABLE inbox ADD COLUMN terminalReport TEXT");
|
|
6537
|
+
if (!cols.some((c) => c.name === "legacyReportConnection")) this.db.exec("ALTER TABLE inbox ADD COLUMN legacyReportConnection TEXT");
|
|
6538
|
+
if (!cols.some((c) => c.name === "completedAt")) this.db.exec("ALTER TABLE inbox ADD COLUMN completedAt INTEGER");
|
|
6539
|
+
}
|
|
6491
6540
|
/** Backfill the durable mute tombstones from the legacy sessions.muted column.
|
|
6492
6541
|
* Both writes in setSessionMuted stay transactional, so this idempotent sync cannot
|
|
6493
6542
|
* resurrect a mute that was explicitly cleared. */
|
|
@@ -6930,28 +6979,96 @@ var LocalStore = class {
|
|
|
6930
6979
|
return this.db.prepare("SELECT lastRunAt FROM cron_runs WHERE key = ?").get(key)?.lastRunAt;
|
|
6931
6980
|
}
|
|
6932
6981
|
/** §6.9 #353 durable inbox: persist an admitted message BEFORE its admission ACK. Keyed
|
|
6933
|
-
* by the stable id (deliveryId/msgId)
|
|
6982
|
+
* by the stable id (deliveryId/msgId). A re-append preserves the original payload/FIFO
|
|
6934
6983
|
* position and may only advance the durable loop-accounting marker from 0 to 1. */
|
|
6935
6984
|
appendInbox(row) {
|
|
6936
|
-
this.db.prepare(`INSERT INTO inbox
|
|
6937
|
-
|
|
6985
|
+
const inserted = this.db.prepare(`INSERT OR IGNORE INTO inbox
|
|
6986
|
+
(id, sessionKey, agentId, msg, integrationId, callMeta, hookContext, posterPublishState,
|
|
6987
|
+
terminalReport, legacyReportConnection, completedAt, isQueueCmd, loopGuardCounted, enqueuedAt)
|
|
6938
6988
|
VALUES
|
|
6939
|
-
(@id, @sessionKey, @agentId, @msg, @integrationId, @callMeta, @
|
|
6940
|
-
|
|
6941
|
-
loopGuardCounted=MAX(inbox.loopGuardCounted, excluded.loopGuardCounted)`).run({
|
|
6989
|
+
(@id, @sessionKey, @agentId, @msg, @integrationId, @callMeta, @hookContext, @posterPublishState,
|
|
6990
|
+
@terminalReport, @legacyReportConnection, @completedAt, @isQueueCmd, @loopGuardCounted, @enqueuedAt)`).run({
|
|
6942
6991
|
id: row.id,
|
|
6943
6992
|
sessionKey: row.sessionKey,
|
|
6944
6993
|
agentId: row.agentId,
|
|
6945
6994
|
msg: row.msg,
|
|
6946
6995
|
integrationId: row.integrationId ?? null,
|
|
6947
6996
|
callMeta: row.callMeta ?? null,
|
|
6997
|
+
hookContext: row.hookContext ?? null,
|
|
6998
|
+
posterPublishState: row.posterPublishState ?? null,
|
|
6999
|
+
terminalReport: row.terminalReport ?? null,
|
|
7000
|
+
legacyReportConnection: row.legacyReportConnection ?? null,
|
|
7001
|
+
completedAt: row.completedAt ?? null,
|
|
6948
7002
|
isQueueCmd: row.isQueueCmd ?? null,
|
|
6949
7003
|
loopGuardCounted: row.loopGuardCounted ?? 0,
|
|
6950
7004
|
enqueuedAt: row.enqueuedAt
|
|
6951
7005
|
});
|
|
7006
|
+
if (inserted.changes === 0 && row.loopGuardCounted === 1) this.db.prepare("UPDATE inbox SET loopGuardCounted = MAX(loopGuardCounted, 1) WHERE id = ?").run(row.id);
|
|
7007
|
+
return inserted.changes === 1;
|
|
6952
7008
|
}
|
|
6953
|
-
/**
|
|
6954
|
-
*
|
|
7009
|
+
/** Stable-id admission probe used before any hook anchoring side effect. A
|
|
7010
|
+
* live row will be replayed (or is already running); a terminal row is the
|
|
7011
|
+
* durable receipt. Either way, redelivery must not post another anchor. */
|
|
7012
|
+
hasInbox(id) {
|
|
7013
|
+
return this.db.prepare("SELECT 1 FROM inbox WHERE id = ?").get(id) !== void 0;
|
|
7014
|
+
}
|
|
7015
|
+
updateInboxHookState(id, hookContext, posterPublishState) {
|
|
7016
|
+
return this.db.prepare(`UPDATE inbox
|
|
7017
|
+
SET hookContext = @hookContext,
|
|
7018
|
+
posterPublishState = COALESCE(@posterPublishState, posterPublishState)
|
|
7019
|
+
WHERE id = @id`).run({
|
|
7020
|
+
id,
|
|
7021
|
+
hookContext,
|
|
7022
|
+
posterPublishState: posterPublishState ?? null
|
|
7023
|
+
}).changes === 1;
|
|
7024
|
+
}
|
|
7025
|
+
/** Atomically turn a live hook inbox row into a redacted terminal receipt.
|
|
7026
|
+
* The stable id remains present to absorb relay redelivery after restart;
|
|
7027
|
+
* startup re-emits only the metadata report, never the model prompt. The CAS
|
|
7028
|
+
* result identifies the sole writer so a later terminal path cannot replace
|
|
7029
|
+
* the winning outbox body. */
|
|
7030
|
+
completeHookInbox(id, terminalReport, completedAt) {
|
|
7031
|
+
if (this.db.prepare(`UPDATE inbox
|
|
7032
|
+
SET msg = '{}', integrationId = NULL, callMeta = NULL, hookContext = NULL,
|
|
7033
|
+
posterPublishState = 'settled', terminalReport = @terminalReport,
|
|
7034
|
+
legacyReportConnection = NULL, completedAt = @completedAt, isQueueCmd = NULL
|
|
7035
|
+
WHERE id = @id AND hookContext IS NOT NULL AND completedAt IS NULL`).run({
|
|
7036
|
+
id,
|
|
7037
|
+
terminalReport,
|
|
7038
|
+
completedAt
|
|
7039
|
+
}).changes === 1) return "completed";
|
|
7040
|
+
const row = this.db.prepare("SELECT completedAt FROM inbox WHERE id = ?").get(id);
|
|
7041
|
+
return row?.completedAt !== null && row?.completedAt !== void 0 ? "already-terminal" : "missing";
|
|
7042
|
+
}
|
|
7043
|
+
/** Remember a best-effort legacy EVT on this exact CP connection so an
|
|
7044
|
+
* outbox pump can move past it without repeatedly re-sending. The report body
|
|
7045
|
+
* remains retained until a future ACK-capable connection durably converges it. */
|
|
7046
|
+
markLegacyHookReportSent(id, connectionId) {
|
|
7047
|
+
return this.db.prepare(`UPDATE inbox
|
|
7048
|
+
SET legacyReportConnection = @connectionId
|
|
7049
|
+
WHERE id = @id AND terminalReport IS NOT NULL`).run({
|
|
7050
|
+
id,
|
|
7051
|
+
connectionId
|
|
7052
|
+
}).changes === 1;
|
|
7053
|
+
}
|
|
7054
|
+
/** A CP-correlated ACK releases only the report payload. Keep a bounded
|
|
7055
|
+
* metadata-only stable-id receipt so relay redelivery still cannot rerun the
|
|
7056
|
+
* model; unacknowledged reports are never capacity-evicted. */
|
|
7057
|
+
acknowledgeHookInbox(id, maxAcknowledgedReceipts = 1e4) {
|
|
7058
|
+
const result = this.db.prepare(`UPDATE inbox
|
|
7059
|
+
SET terminalReport = NULL, legacyReportConnection = NULL
|
|
7060
|
+
WHERE id = @id AND completedAt IS NOT NULL AND terminalReport IS NOT NULL`).run({ id });
|
|
7061
|
+
if (result.changes === 1) this.db.prepare(`DELETE FROM inbox
|
|
7062
|
+
WHERE id IN (
|
|
7063
|
+
SELECT id FROM inbox
|
|
7064
|
+
WHERE completedAt IS NOT NULL AND terminalReport IS NULL
|
|
7065
|
+
ORDER BY completedAt DESC, id DESC
|
|
7066
|
+
LIMIT -1 OFFSET @maxAcknowledgedReceipts
|
|
7067
|
+
)`).run({ maxAcknowledgedReceipts });
|
|
7068
|
+
return result.changes === 1;
|
|
7069
|
+
}
|
|
7070
|
+
/** Remove an ordinary inbox row once its turn reaches a terminal state. Hook
|
|
7071
|
+
* rows are converted to bounded redacted receipts by completeHookInbox. */
|
|
6955
7072
|
removeInbox(id) {
|
|
6956
7073
|
this.db.prepare("DELETE FROM inbox WHERE id = ?").run(id);
|
|
6957
7074
|
}
|
|
@@ -6960,15 +7077,15 @@ var LocalStore = class {
|
|
|
6960
7077
|
listInboxBySessionKeyFifo() {
|
|
6961
7078
|
return this.db.prepare("SELECT * FROM inbox ORDER BY sessionKey ASC, enqueuedAt ASC").all();
|
|
6962
7079
|
}
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
*
|
|
6968
|
-
* restart. Returns the removed ids so the daemon can clear its live-id cache. */
|
|
7080
|
+
/** Remove ordinary durable turns owned by an agent. Live hook rows have their
|
|
7081
|
+
* own single completion owner and must survive until that owner atomically
|
|
7082
|
+
* redacts them into a terminal report. Unacknowledged terminal hook reports
|
|
7083
|
+
* are an outbox and likewise must survive lifecycle purges. ACKed receipts
|
|
7084
|
+
* may be discarded here because CP already durably converged them. */
|
|
6969
7085
|
removeInboxByAgentId(agentId) {
|
|
6970
|
-
const
|
|
6971
|
-
this.db.prepare(
|
|
7086
|
+
const removable = "agentId = ? AND hookContext IS NULL AND terminalReport IS NULL";
|
|
7087
|
+
const rows = this.db.prepare(`SELECT id FROM inbox WHERE ${removable}`).all(agentId);
|
|
7088
|
+
this.db.prepare(`DELETE FROM inbox WHERE ${removable}`).run(agentId);
|
|
6972
7089
|
return rows.map((row) => row.id);
|
|
6973
7090
|
}
|
|
6974
7091
|
/** Record one turn admission against a conversation-wide fixed window. A trusted
|
|
@@ -7692,6 +7809,31 @@ async function executeTool(ctx, name, args, deps) {
|
|
|
7692
7809
|
cancelled: true
|
|
7693
7810
|
};
|
|
7694
7811
|
}
|
|
7812
|
+
if (name === "submitGithubReview") {
|
|
7813
|
+
if (!deps.submitGithubReview) throw new Error("formal GitHub reviews are unavailable on this daemon");
|
|
7814
|
+
const event = requireEnum(args, "event", [
|
|
7815
|
+
"COMMENT",
|
|
7816
|
+
"REQUEST_CHANGES",
|
|
7817
|
+
"APPROVE"
|
|
7818
|
+
]);
|
|
7819
|
+
const verdict = requireEnum(args, "verdict", [
|
|
7820
|
+
"pass",
|
|
7821
|
+
"fail",
|
|
7822
|
+
"neutral"
|
|
7823
|
+
]);
|
|
7824
|
+
const body = requireStringAllowEmpty(args, "body");
|
|
7825
|
+
const comments = parseReviewComments(args.comments);
|
|
7826
|
+
return deps.submitGithubReview({
|
|
7827
|
+
agentId: ctx.agentId,
|
|
7828
|
+
platform: ctx.platform,
|
|
7829
|
+
channel: ctx.channel,
|
|
7830
|
+
thread: ctx.thread,
|
|
7831
|
+
event,
|
|
7832
|
+
verdict,
|
|
7833
|
+
body,
|
|
7834
|
+
...comments ? { comments } : {}
|
|
7835
|
+
});
|
|
7836
|
+
}
|
|
7695
7837
|
const gw = ctx.integrationId ? deps.gatewayFor(ctx.integrationId) : void 0;
|
|
7696
7838
|
if (!gw) throw new Error(`no live platform connection for integration ${ctx.integrationId ?? "(none)"}`);
|
|
7697
7839
|
switch (name) {
|
|
@@ -7778,6 +7920,35 @@ function optionalNumber(args, key) {
|
|
|
7778
7920
|
if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`argument ${key} must be a finite number`);
|
|
7779
7921
|
return v;
|
|
7780
7922
|
}
|
|
7923
|
+
function requireEnum(args, key, values) {
|
|
7924
|
+
const value = args[key];
|
|
7925
|
+
if (typeof value !== "string" || !values.includes(value)) throw new Error(`argument ${key} must be one of: ${values.join(", ")}`);
|
|
7926
|
+
return value;
|
|
7927
|
+
}
|
|
7928
|
+
function requirePositiveInt(args, key) {
|
|
7929
|
+
const value = args[key];
|
|
7930
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`argument ${key} must be a positive integer`);
|
|
7931
|
+
return value;
|
|
7932
|
+
}
|
|
7933
|
+
function parseReviewComments(value) {
|
|
7934
|
+
if (value === void 0 || value === null) return void 0;
|
|
7935
|
+
if (!Array.isArray(value)) throw new Error("argument comments must be an array");
|
|
7936
|
+
if (value.length > 100) throw new Error("argument comments may contain at most 100 entries");
|
|
7937
|
+
return value.map((item, index) => {
|
|
7938
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) throw new Error(`comments[${index}] must be an object`);
|
|
7939
|
+
const row = item;
|
|
7940
|
+
const startLine = row.startLine === void 0 ? void 0 : requirePositiveInt(row, "startLine");
|
|
7941
|
+
const startSide = row.startSide === void 0 ? void 0 : requireEnum(row, "startSide", ["LEFT", "RIGHT"]);
|
|
7942
|
+
return {
|
|
7943
|
+
path: requireString(row, "path"),
|
|
7944
|
+
body: requireString(row, "body"),
|
|
7945
|
+
line: requirePositiveInt(row, "line"),
|
|
7946
|
+
side: requireEnum(row, "side", ["LEFT", "RIGHT"]),
|
|
7947
|
+
...startLine !== void 0 ? { startLine } : {},
|
|
7948
|
+
...startSide !== void 0 ? { startSide } : {}
|
|
7949
|
+
};
|
|
7950
|
+
});
|
|
7951
|
+
}
|
|
7781
7952
|
//#endregion
|
|
7782
7953
|
//#region src/mcp/control-server.ts
|
|
7783
7954
|
/**
|
|
@@ -11251,7 +11422,7 @@ var SSEClientTransport = class {
|
|
|
11251
11422
|
};
|
|
11252
11423
|
//#endregion
|
|
11253
11424
|
//#region src/mcp/mcp-prober.ts
|
|
11254
|
-
const DEFAULT_TIMEOUT_MS$
|
|
11425
|
+
const DEFAULT_TIMEOUT_MS$2 = 15e3;
|
|
11255
11426
|
const DEFAULT_CONCURRENCY$1 = 3;
|
|
11256
11427
|
function envRecord(pairs) {
|
|
11257
11428
|
return Object.fromEntries(pairs.map((e) => [e.name, e.value]));
|
|
@@ -11290,7 +11461,7 @@ async function connectReal(name, def) {
|
|
|
11290
11461
|
* not a failure.
|
|
11291
11462
|
*/
|
|
11292
11463
|
async function probeMcpServer(name, def, opts = {}) {
|
|
11293
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$
|
|
11464
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
|
|
11294
11465
|
let timer;
|
|
11295
11466
|
let timedOut = false;
|
|
11296
11467
|
const timeout = new Promise((_, reject) => {
|
|
@@ -197666,7 +197837,10 @@ const UNTRUSTED_CONTENT_BEGIN = "----- BEGIN UNTRUSTED EXTERNAL CONTENT (GitHub
|
|
|
197666
197837
|
const UNTRUSTED_CONTENT_END = "----- END UNTRUSTED EXTERNAL CONTENT -----";
|
|
197667
197838
|
/** channel/thread from the affinity key — see the sessionKey grammar on {@link RdMsgHook}. */
|
|
197668
197839
|
function splitSessionKey(msg) {
|
|
197669
|
-
if (msg.sessionKey === msg.hookId) return {
|
|
197840
|
+
if (msg.sessionKey === msg.hookId) return {
|
|
197841
|
+
channel: msg.hookId,
|
|
197842
|
+
thread: msg.hookId
|
|
197843
|
+
};
|
|
197670
197844
|
if (msg.sessionKey === `${msg.hookId}:${msg.deliveryKey}`) return {
|
|
197671
197845
|
channel: msg.hookId,
|
|
197672
197846
|
thread: msg.deliveryKey
|
|
@@ -197731,7 +197905,7 @@ function neutralizeDelimiters(body) {
|
|
|
197731
197905
|
* Only emitted when the poster will actually run (a thread number exists); push
|
|
197732
197906
|
* fires have no thread, so they never see this. */
|
|
197733
197907
|
function githubReplyHint(c) {
|
|
197734
|
-
return ["", `Your final reply is posted back to ${`${c.repo ?? "this thread"}#${c.number}`} automatically as
|
|
197908
|
+
return ["", `Your final reply is posted back to ${`${c.repo ?? "this thread"}#${c.number}`} automatically as an ordinary GitHub comment. Keep it self-contained; the daemon exclusively owns that reply comment. If this active PR hook permits a formal review, use only the structured \`submitGithubReview\` tool for COMMENT / REQUEST_CHANGES / APPROVE and inline review comments. Do NOT create, update, or delete GitHub comments or formal reviews through \`gh\`, another CLI, a connector, or a direct API call — those paths would race or double-post. Other GitHub tools are for READ-only inspection (thread, diff, files), then return the ordinary comment content in your final reply.`].join("\n");
|
|
197735
197909
|
}
|
|
197736
197910
|
/** The github-kind turn text: a trusted metadata header + the FENCED excerpt.
|
|
197737
197911
|
* The title rides the header (relay-sanitized to one capped line) — it is
|
|
@@ -198125,6 +198299,227 @@ var GithubFinalPoster = class {
|
|
|
198125
198299
|
}
|
|
198126
198300
|
};
|
|
198127
198301
|
//#endregion
|
|
198302
|
+
//#region src/github/review.ts
|
|
198303
|
+
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
198304
|
+
const MAX_REVIEW_PAGES = 10;
|
|
198305
|
+
const REVIEWS_PER_PAGE = 100;
|
|
198306
|
+
var GithubHttpError = class extends Error {
|
|
198307
|
+
status;
|
|
198308
|
+
constructor(status, message) {
|
|
198309
|
+
super(message);
|
|
198310
|
+
this.status = status;
|
|
198311
|
+
this.name = "GithubHttpError";
|
|
198312
|
+
}
|
|
198313
|
+
};
|
|
198314
|
+
function markerFor(target) {
|
|
198315
|
+
return `<!-- agentconnect-review:${Buffer.from(JSON.stringify({
|
|
198316
|
+
v: 1,
|
|
198317
|
+
hookId: target.hookId,
|
|
198318
|
+
deliveryKey: target.deliveryKey,
|
|
198319
|
+
attemptId: target.attemptId,
|
|
198320
|
+
headSha: target.expectedHeadSha
|
|
198321
|
+
}), "utf8").toString("base64url")} -->`;
|
|
198322
|
+
}
|
|
198323
|
+
function validateInput(input) {
|
|
198324
|
+
if (input.event === "APPROVE" && input.verdict !== "pass") return "APPROVE requires verdict=pass";
|
|
198325
|
+
if (input.event === "REQUEST_CHANGES" && input.verdict !== "fail") return "REQUEST_CHANGES requires verdict=fail";
|
|
198326
|
+
if ((input.event === "COMMENT" || input.event === "REQUEST_CHANGES") && !input.body.trim()) return `${input.event} requires a non-empty body`;
|
|
198327
|
+
for (const [index, comment] of (input.comments ?? []).entries()) {
|
|
198328
|
+
if (!comment.path.trim()) return `comments[${index}].path is required`;
|
|
198329
|
+
if (!comment.body.trim()) return `comments[${index}].body is required`;
|
|
198330
|
+
if (!Number.isInteger(comment.line) || comment.line <= 0) return `comments[${index}].line must be positive`;
|
|
198331
|
+
if (comment.startLine !== void 0) {
|
|
198332
|
+
if (!Number.isInteger(comment.startLine) || comment.startLine <= 0 || comment.startLine > comment.line) return `comments[${index}].startLine must be positive and no greater than line`;
|
|
198333
|
+
if (!comment.startSide) return `comments[${index}].startSide is required with startLine`;
|
|
198334
|
+
}
|
|
198335
|
+
}
|
|
198336
|
+
}
|
|
198337
|
+
function safeJson(text) {
|
|
198338
|
+
if (!text) return void 0;
|
|
198339
|
+
return JSON.parse(text.replace(/"id"\s*:\s*(\d{15,})/g, "\"id\":\"$1\""));
|
|
198340
|
+
}
|
|
198341
|
+
/** One lexical-scope client; it never caches tokens or targets. */
|
|
198342
|
+
var GithubReviewClient = class {
|
|
198343
|
+
fetchImpl;
|
|
198344
|
+
baseUrl;
|
|
198345
|
+
timeoutMs;
|
|
198346
|
+
constructor(deps = {}) {
|
|
198347
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
198348
|
+
this.baseUrl = (deps.baseUrl ?? "https://api.github.com").replace(/\/+$/, "");
|
|
198349
|
+
this.timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
198350
|
+
}
|
|
198351
|
+
async getPull(token, repoFullName, pullNumber) {
|
|
198352
|
+
const pull = await this.request(`/repos/${repoFullName}/pulls/${pullNumber}`, token, "GET");
|
|
198353
|
+
const headSha = pull.head?.sha;
|
|
198354
|
+
const baseSha = pull.base?.sha;
|
|
198355
|
+
if (!headSha || !baseSha) throw new GithubHttpError(502, "GitHub pull response omitted head/base SHA");
|
|
198356
|
+
return {
|
|
198357
|
+
headSha,
|
|
198358
|
+
baseSha,
|
|
198359
|
+
draft: pull.draft === true,
|
|
198360
|
+
state: pull.state ?? "unknown",
|
|
198361
|
+
merged: pull.merged === true
|
|
198362
|
+
};
|
|
198363
|
+
}
|
|
198364
|
+
async submit(target, input) {
|
|
198365
|
+
const invalid = validateInput(input);
|
|
198366
|
+
if (invalid) return {
|
|
198367
|
+
state: "not_submitted",
|
|
198368
|
+
code: "invalid_input",
|
|
198369
|
+
message: invalid
|
|
198370
|
+
};
|
|
198371
|
+
const marker = markerFor(target);
|
|
198372
|
+
try {
|
|
198373
|
+
let existing;
|
|
198374
|
+
try {
|
|
198375
|
+
existing = await this.findByMarker(target, marker);
|
|
198376
|
+
} catch (err) {
|
|
198377
|
+
if (target.recovering) return {
|
|
198378
|
+
state: "ambiguous",
|
|
198379
|
+
code: "ambiguous_write",
|
|
198380
|
+
message: "cannot reconcile the prior formal review attempt; automatic retry is blocked"
|
|
198381
|
+
};
|
|
198382
|
+
throw err;
|
|
198383
|
+
}
|
|
198384
|
+
if (existing) return this.submitted(existing, target, input);
|
|
198385
|
+
const pull = await this.getPull(target.token, target.repoFullName, target.pullNumber);
|
|
198386
|
+
if (pull.state !== "open" || pull.merged || pull.draft) return {
|
|
198387
|
+
state: "not_submitted",
|
|
198388
|
+
code: "pull_unavailable",
|
|
198389
|
+
message: `pull request is ${pull.merged ? "merged" : pull.draft ? "draft" : pull.state}`
|
|
198390
|
+
};
|
|
198391
|
+
if (pull.headSha !== target.expectedHeadSha || pull.baseSha !== target.expectedBaseSha) return {
|
|
198392
|
+
state: "not_submitted",
|
|
198393
|
+
code: "revision_changed",
|
|
198394
|
+
message: "pull request head/base changed while the review was running"
|
|
198395
|
+
};
|
|
198396
|
+
const body = input.body.trim() ? `${input.body}\n\n${marker}` : marker;
|
|
198397
|
+
try {
|
|
198398
|
+
const created = await this.request(`/repos/${target.repoFullName}/pulls/${target.pullNumber}/reviews`, target.token, "POST", {
|
|
198399
|
+
commit_id: target.expectedHeadSha,
|
|
198400
|
+
event: input.event,
|
|
198401
|
+
body,
|
|
198402
|
+
...input.comments?.length ? { comments: input.comments.map((comment) => ({
|
|
198403
|
+
path: comment.path,
|
|
198404
|
+
body: comment.body,
|
|
198405
|
+
line: comment.line,
|
|
198406
|
+
side: comment.side,
|
|
198407
|
+
...comment.startLine !== void 0 ? { start_line: comment.startLine } : {},
|
|
198408
|
+
...comment.startSide !== void 0 ? { start_side: comment.startSide } : {}
|
|
198409
|
+
})) } : {}
|
|
198410
|
+
});
|
|
198411
|
+
return this.submitted(created, target, input);
|
|
198412
|
+
} catch (err) {
|
|
198413
|
+
if (err instanceof GithubHttpError && err.status >= 400 && err.status < 500) return {
|
|
198414
|
+
state: "not_submitted",
|
|
198415
|
+
code: "github_rejected",
|
|
198416
|
+
message: err.message
|
|
198417
|
+
};
|
|
198418
|
+
const recovered = await this.findByMarker(target, marker).catch(() => void 0);
|
|
198419
|
+
if (recovered) return this.submitted(recovered, target, input);
|
|
198420
|
+
return {
|
|
198421
|
+
state: "ambiguous",
|
|
198422
|
+
code: "ambiguous_write",
|
|
198423
|
+
message: "GitHub review outcome is unknown; automatic retry is blocked"
|
|
198424
|
+
};
|
|
198425
|
+
}
|
|
198426
|
+
} catch (err) {
|
|
198427
|
+
return {
|
|
198428
|
+
state: "not_submitted",
|
|
198429
|
+
code: "github_rejected",
|
|
198430
|
+
message: err instanceof Error ? err.message : String(err)
|
|
198431
|
+
};
|
|
198432
|
+
}
|
|
198433
|
+
}
|
|
198434
|
+
/** Read-only recovery for an attempt whose POST outcome was ambiguous. This
|
|
198435
|
+
* is safe to run automatically before a replayed model turn: it can converge
|
|
198436
|
+
* a now-visible marker, but never issues a second review mutation. */
|
|
198437
|
+
async reconcile(target, event, verdict) {
|
|
198438
|
+
const marker = markerFor(target);
|
|
198439
|
+
try {
|
|
198440
|
+
const existing = await this.findByMarker(target, marker);
|
|
198441
|
+
if (existing) return this.submitted(existing, target, {
|
|
198442
|
+
event,
|
|
198443
|
+
verdict,
|
|
198444
|
+
body: ""
|
|
198445
|
+
});
|
|
198446
|
+
return {
|
|
198447
|
+
state: "ambiguous",
|
|
198448
|
+
code: "ambiguous_write",
|
|
198449
|
+
message: "the prior formal review marker is not visible yet; automatic mutation retry is blocked"
|
|
198450
|
+
};
|
|
198451
|
+
} catch {
|
|
198452
|
+
return {
|
|
198453
|
+
state: "ambiguous",
|
|
198454
|
+
code: "ambiguous_write",
|
|
198455
|
+
message: "cannot reconcile the prior formal review attempt; automatic mutation retry is blocked"
|
|
198456
|
+
};
|
|
198457
|
+
}
|
|
198458
|
+
}
|
|
198459
|
+
submitted(review, target, input) {
|
|
198460
|
+
if (review.id === void 0 || review.id === null) return {
|
|
198461
|
+
state: "ambiguous",
|
|
198462
|
+
code: "ambiguous_write",
|
|
198463
|
+
message: "GitHub created/recovered a review without an id"
|
|
198464
|
+
};
|
|
198465
|
+
if (!review.commit_id) return {
|
|
198466
|
+
state: "ambiguous",
|
|
198467
|
+
code: "ambiguous_write",
|
|
198468
|
+
message: "GitHub created/recovered a review without its commit id"
|
|
198469
|
+
};
|
|
198470
|
+
const commitId = review.commit_id;
|
|
198471
|
+
if (commitId !== target.expectedHeadSha) return {
|
|
198472
|
+
state: "ambiguous",
|
|
198473
|
+
code: "ambiguous_write",
|
|
198474
|
+
message: "recovered review is anchored to an unexpected commit"
|
|
198475
|
+
};
|
|
198476
|
+
return {
|
|
198477
|
+
state: "submitted",
|
|
198478
|
+
reviewId: String(review.id),
|
|
198479
|
+
event: input.event,
|
|
198480
|
+
verdict: input.verdict,
|
|
198481
|
+
commitId
|
|
198482
|
+
};
|
|
198483
|
+
}
|
|
198484
|
+
async findByMarker(target, marker) {
|
|
198485
|
+
for (let page = 1; page <= MAX_REVIEW_PAGES; page += 1) {
|
|
198486
|
+
const rows = await this.request(`/repos/${target.repoFullName}/pulls/${target.pullNumber}/reviews?per_page=${REVIEWS_PER_PAGE}&page=${page}`, target.token, "GET");
|
|
198487
|
+
const hit = rows.find((review) => review.body?.includes(marker));
|
|
198488
|
+
if (hit) return hit;
|
|
198489
|
+
if (rows.length < REVIEWS_PER_PAGE) return void 0;
|
|
198490
|
+
}
|
|
198491
|
+
throw new Error("review marker reconciliation exceeded pagination bound");
|
|
198492
|
+
}
|
|
198493
|
+
async request(path, token, method, body) {
|
|
198494
|
+
let response;
|
|
198495
|
+
try {
|
|
198496
|
+
response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
198497
|
+
method,
|
|
198498
|
+
headers: {
|
|
198499
|
+
authorization: `Bearer ${token}`,
|
|
198500
|
+
accept: "application/vnd.github+json",
|
|
198501
|
+
"x-github-api-version": "2022-11-28",
|
|
198502
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
198503
|
+
},
|
|
198504
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
198505
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
198506
|
+
});
|
|
198507
|
+
} catch (err) {
|
|
198508
|
+
throw new Error(`GitHub request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198509
|
+
}
|
|
198510
|
+
const text = await response.text();
|
|
198511
|
+
if (!response.ok) {
|
|
198512
|
+
let detail = "";
|
|
198513
|
+
try {
|
|
198514
|
+
const parsed = safeJson(text);
|
|
198515
|
+
detail = parsed?.message ? `: ${parsed.message}` : "";
|
|
198516
|
+
} catch {}
|
|
198517
|
+
throw new GithubHttpError(response.status, `GitHub ${method} ${response.status}${detail}`);
|
|
198518
|
+
}
|
|
198519
|
+
return safeJson(text);
|
|
198520
|
+
}
|
|
198521
|
+
};
|
|
198522
|
+
//#endregion
|
|
198128
198523
|
//#region src/runtimes/runtime-prober.ts
|
|
198129
198524
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
198130
198525
|
const DEFAULT_CONCURRENCY = 3;
|
|
@@ -198358,7 +198753,7 @@ var RelayClient = class {
|
|
|
198358
198753
|
const frame = decoded.frame;
|
|
198359
198754
|
if (frame.corr && this.correlator.settle(frame)) return;
|
|
198360
198755
|
if (frame.type === "rd/msg") {
|
|
198361
|
-
this.handleMsg(frame.id, frame.payload);
|
|
198756
|
+
await this.handleMsg(frame.id, frame.payload);
|
|
198362
198757
|
return;
|
|
198363
198758
|
}
|
|
198364
198759
|
if (frame.type === "rd/agentmsg/fwd") {
|
|
@@ -198370,9 +198765,9 @@ var RelayClient = class {
|
|
|
198370
198765
|
/** An inbound rd/msg: dispatch it, reply `rd/ack`; only a webchat op streams
|
|
198371
198766
|
* `rd/chat` back over this socket. A shared-bot `im` replies out-of-band via the
|
|
198372
198767
|
* daemon's own Slack send, and a hook fire has no reply stream — both get a noop. */
|
|
198373
|
-
handleMsg(reqId, msg) {
|
|
198768
|
+
async handleMsg(reqId, msg) {
|
|
198374
198769
|
const chat = msg.source === "webchat" ? (event) => this.sendChat(msg.chatId, event) : () => void 0;
|
|
198375
|
-
const ack = this.deps.onRelayMsg(msg, chat);
|
|
198770
|
+
const ack = await this.deps.onRelayMsg(msg, chat);
|
|
198376
198771
|
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
|
|
198377
198772
|
}
|
|
198378
198773
|
/** A forwarded cross-daemon agent-call: terminal-verify + dispatch, reply the typed
|
|
@@ -215511,10 +215906,35 @@ function isBuiltinSystemTool(params) {
|
|
|
215511
215906
|
tc?.toolCallId
|
|
215512
215907
|
].some((id) => typeof id === "string" && [...BUILTIN_TOOL_FQNS].some((fqn) => id.includes(fqn)));
|
|
215513
215908
|
}
|
|
215909
|
+
function hookSnapshot(msg) {
|
|
215910
|
+
if (msg.configRevision === void 0 || msg.dispatchRevision === void 0 || msg.dispatchDaemonId === void 0 || msg.reviewPolicy === void 0 || msg.reportingMode === void 0 || msg.gateMode === void 0) return void 0;
|
|
215911
|
+
return {
|
|
215912
|
+
configRevision: msg.configRevision,
|
|
215913
|
+
dispatchRevision: msg.dispatchRevision,
|
|
215914
|
+
dispatchDaemonId: msg.dispatchDaemonId,
|
|
215915
|
+
reviewPolicy: msg.reviewPolicy,
|
|
215916
|
+
reportingMode: msg.reportingMode,
|
|
215917
|
+
gateMode: msg.gateMode
|
|
215918
|
+
};
|
|
215919
|
+
}
|
|
215920
|
+
function reviewPolicyAllows(policy, event) {
|
|
215921
|
+
if (policy === "full") return true;
|
|
215922
|
+
if (policy === "request_changes") return event === "COMMENT" || event === "REQUEST_CHANGES";
|
|
215923
|
+
if (policy === "comment") return event === "COMMENT";
|
|
215924
|
+
return false;
|
|
215925
|
+
}
|
|
215926
|
+
function reviewResultForWire(effect) {
|
|
215927
|
+
if (effect.state === "submitted") return effect;
|
|
215928
|
+
return {
|
|
215929
|
+
state: effect.state,
|
|
215930
|
+
code: effect.code
|
|
215931
|
+
};
|
|
215932
|
+
}
|
|
215514
215933
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
215515
215934
|
const LOOP_GUARD_WINDOW_MS = 6e4;
|
|
215516
215935
|
const MAX_AUTOMATIC_TURNS_PER_WINDOW = 8;
|
|
215517
215936
|
const MAX_TOTAL_TURNS_PER_WINDOW = 60;
|
|
215937
|
+
const MAX_HOOK_REPORT_INFLIGHT = 100;
|
|
215518
215938
|
/** Thrown to a `dispatch()` caller when the per-session admission queue is at its depth
|
|
215519
215939
|
* cap (§4.4 backpressure): the message is fast-failed, not buffered. Carries a stable
|
|
215520
215940
|
* `reason` so an agent-call source (P1/P2) can surface a typed `queue_full`. */
|
|
@@ -215610,6 +216030,22 @@ function usesLoopGuard(msg) {
|
|
|
215610
216030
|
function isMalformedPlatformTurn(msg) {
|
|
215611
216031
|
return msg.platform === "slack" && msg.source === "user" && !msg.sender.isBot && msg.sender.id === "unknown" && msg.text.trim() === "" && (msg.attachments?.length ?? 0) === 0;
|
|
215612
216032
|
}
|
|
216033
|
+
function authorizedReviewTargetMatches(active, attemptId, authorized) {
|
|
216034
|
+
return authorized.attemptId === attemptId && authorized.repoId === active.repoId && authorized.repoFullName.toLowerCase() === active.repoFullName.toLowerCase() && authorized.pullNumber === active.pullNumber && authorized.expectedHeadSha === active.expectedHeadSha && authorized.expectedBaseSha === active.expectedBaseSha;
|
|
216035
|
+
}
|
|
216036
|
+
function authorizedReviewTarget(active, attemptId, authorized, recovering = false) {
|
|
216037
|
+
return {
|
|
216038
|
+
token: authorized.token,
|
|
216039
|
+
repoFullName: authorized.repoFullName,
|
|
216040
|
+
pullNumber: authorized.pullNumber,
|
|
216041
|
+
expectedHeadSha: authorized.expectedHeadSha,
|
|
216042
|
+
expectedBaseSha: authorized.expectedBaseSha,
|
|
216043
|
+
hookId: active.hook.hookId,
|
|
216044
|
+
deliveryKey: active.hook.deliveryKey,
|
|
216045
|
+
attemptId,
|
|
216046
|
+
...recovering ? { recovering: true } : {}
|
|
216047
|
+
};
|
|
216048
|
+
}
|
|
215613
216049
|
function isSlackStatusBarText(text) {
|
|
215614
216050
|
return text.startsWith(":bar_chart:") || text.startsWith("📊");
|
|
215615
216051
|
}
|
|
@@ -215694,6 +216130,10 @@ var Daemon = class Daemon {
|
|
|
215694
216130
|
botUserIds = {};
|
|
215695
216131
|
cpRouting;
|
|
215696
216132
|
cpCollab = new CpCollabRoutes();
|
|
216133
|
+
/** One active PR hook turn per logical session key. Long-lived ACP sessions
|
|
216134
|
+
* may span deliveries, so authorization must never live in SessionContext. */
|
|
216135
|
+
activeGithubTurnMeta = /* @__PURE__ */ new Map();
|
|
216136
|
+
githubReviewClient = new GithubReviewClient();
|
|
215697
216137
|
clock;
|
|
215698
216138
|
requestExit;
|
|
215699
216139
|
draining = false;
|
|
@@ -215717,6 +216157,9 @@ var Daemon = class Daemon {
|
|
|
215717
216157
|
cancelTimers = /* @__PURE__ */ new Map();
|
|
215718
216158
|
coldCancelTimers = /* @__PURE__ */ new Map();
|
|
215719
216159
|
slackRetryTimers = /* @__PURE__ */ new Map();
|
|
216160
|
+
hookReportRetryTimer;
|
|
216161
|
+
hookReportInflight = /* @__PURE__ */ new Set();
|
|
216162
|
+
hookReportConnectionId = randomUUID();
|
|
215720
216163
|
slackRetryRuns = /* @__PURE__ */ new Map();
|
|
215721
216164
|
constructor(opts = {}) {
|
|
215722
216165
|
this.opts = opts;
|
|
@@ -215846,6 +216289,7 @@ var Daemon = class Daemon {
|
|
|
215846
216289
|
startOrchestration: (req) => this.startOrchestration(req),
|
|
215847
216290
|
getOrchestration: (req) => Promise.resolve(this.getOrchestrationForOwner(req)),
|
|
215848
216291
|
cancelOrchestration: (req) => Promise.resolve(this.cancelOrchestrationForOwner(req)),
|
|
216292
|
+
submitGithubReview: (req) => this.submitGithubReview(req),
|
|
215849
216293
|
memory: this.memory,
|
|
215850
216294
|
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
215851
216295
|
channel,
|
|
@@ -215867,6 +216311,7 @@ var Daemon = class Daemon {
|
|
|
215867
216311
|
mcpServersFor: ({ agent, platform, channel, thread, integrationId, isDm }) => {
|
|
215868
216312
|
const servers = [];
|
|
215869
216313
|
let tools = toolsForIntegrations(agent.integrations);
|
|
216314
|
+
tools = [...tools, ...GITHUB_REVIEW_TOOLS];
|
|
215870
216315
|
if (memoryKindOf(agent) === "native") tools = tools.filter((t) => !MEMORY_TOOL_NAMES.has(t.name));
|
|
215871
216316
|
if (tools.length > 0) {
|
|
215872
216317
|
const token = this.mcp.register({
|
|
@@ -216874,6 +217319,10 @@ var Daemon = class Daemon {
|
|
|
216874
217319
|
this.log.debug(`webchat cancel: no in-flight turn for conversation ${conversationId}`);
|
|
216875
217320
|
}
|
|
216876
217321
|
relayMsgAcks = /* @__PURE__ */ new Map();
|
|
217322
|
+
/** Hook admission crosses an async anchor + durable-inbox barrier. Coalesce a
|
|
217323
|
+
* retransmit that arrives before that barrier settles instead of dispatching
|
|
217324
|
+
* the same delivery twice. */
|
|
217325
|
+
pendingRelayMsgAcks = /* @__PURE__ */ new Map();
|
|
216877
217326
|
agentCallDeliveries = /* @__PURE__ */ new Map();
|
|
216878
217327
|
orchestrationDeadlines = /* @__PURE__ */ new Map();
|
|
216879
217328
|
/**
|
|
@@ -216895,7 +217344,26 @@ var Daemon = class Daemon {
|
|
|
216895
217344
|
this.log.debug(`relay: duplicate rd/msg ${dedupKey} — replaying ack (no re-dispatch)`);
|
|
216896
217345
|
return prior;
|
|
216897
217346
|
}
|
|
216898
|
-
const
|
|
217347
|
+
const pending = this.pendingRelayMsgAcks.get(dedupKey);
|
|
217348
|
+
if (pending) return pending;
|
|
217349
|
+
if (msg.source === "hook") {
|
|
217350
|
+
const task = this.dispatchRelayHook(msg).catch((err) => {
|
|
217351
|
+
this.log.error(`hook admission failed for ${dedupKey}: ${formatErr(err)}`);
|
|
217352
|
+
return {
|
|
217353
|
+
msgId: msg.msgId,
|
|
217354
|
+
accepted: false,
|
|
217355
|
+
reason: "durability"
|
|
217356
|
+
};
|
|
217357
|
+
}).then((ack) => {
|
|
217358
|
+
this.pendingRelayMsgAcks.delete(dedupKey);
|
|
217359
|
+
if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
|
|
217360
|
+
this.relayMsgAcks.set(dedupKey, ack);
|
|
217361
|
+
return ack;
|
|
217362
|
+
});
|
|
217363
|
+
this.pendingRelayMsgAcks.set(dedupKey, task);
|
|
217364
|
+
return task;
|
|
217365
|
+
}
|
|
217366
|
+
const ack = msg.source === "webchat" ? this.dispatchRelayOp(msg, chat) : this.handleRelayIm(msg);
|
|
216899
217367
|
if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
|
|
216900
217368
|
this.relayMsgAcks.set(dedupKey, ack);
|
|
216901
217369
|
return ack;
|
|
@@ -217397,9 +217865,10 @@ var Daemon = class Daemon {
|
|
|
217397
217865
|
* Ack-verdict gate for one hook fire (`rd/msg` hook member) — the mirror of
|
|
217398
217866
|
* {@link dispatchWebchatTurn}'s synchronous gates: the relay's rc/run-report
|
|
217399
217867
|
* needs a REASONED rejection now, not a silently dropped fire-and-forget
|
|
217400
|
-
* dispatch. Accepted
|
|
217868
|
+
* dispatch. Accepted is returned only after {@link onHookFire} has crossed
|
|
217869
|
+
* the durable-inbox admission barrier; the model turn itself remains async.
|
|
217401
217870
|
*/
|
|
217402
|
-
dispatchRelayHook(msg) {
|
|
217871
|
+
async dispatchRelayHook(msg) {
|
|
217403
217872
|
if (!this.agents.has(msg.agentId)) {
|
|
217404
217873
|
this.log.warn(`hook: no agent "${msg.agentId}" on this daemon — rejecting fire ${msg.msgId}`);
|
|
217405
217874
|
return {
|
|
@@ -217432,10 +217901,11 @@ var Daemon = class Daemon {
|
|
|
217432
217901
|
reason: "draining"
|
|
217433
217902
|
};
|
|
217434
217903
|
}
|
|
217435
|
-
|
|
217904
|
+
const admission = await this.onHookFire(msg);
|
|
217436
217905
|
return {
|
|
217437
217906
|
msgId: msg.msgId,
|
|
217438
|
-
accepted:
|
|
217907
|
+
accepted: admission.accepted,
|
|
217908
|
+
...admission.reason ? { reason: admission.reason } : {}
|
|
217439
217909
|
};
|
|
217440
217910
|
}
|
|
217441
217911
|
/**
|
|
@@ -217448,25 +217918,36 @@ var Daemon = class Daemon {
|
|
|
217448
217918
|
* without one the fire runs headless.
|
|
217449
217919
|
*/
|
|
217450
217920
|
async onHookFire(msg) {
|
|
217451
|
-
const firedAt = this.clock.now();
|
|
217452
|
-
const report = (status, extra = {}) => {
|
|
217453
|
-
this.cpClient?.emitHookReport({
|
|
217454
|
-
hookId: msg.hookId,
|
|
217455
|
-
agentId: msg.agentId,
|
|
217456
|
-
deliveryKey: msg.deliveryKey,
|
|
217457
|
-
status,
|
|
217458
|
-
durationMs: Math.max(0, this.clock.now() - firedAt),
|
|
217459
|
-
...extra
|
|
217460
|
-
});
|
|
217461
|
-
};
|
|
217462
217921
|
const nmsg = buildHookMessage(msg, randomUUID());
|
|
217922
|
+
if (this.store.hasInbox(nmsg.msgId)) {
|
|
217923
|
+
this.log.debug(`hook: durable duplicate ${nmsg.msgId} — replaying accepted admission`);
|
|
217924
|
+
return { accepted: true };
|
|
217925
|
+
}
|
|
217926
|
+
const snapshot = hookSnapshot(msg);
|
|
217927
|
+
const hookContext = {
|
|
217928
|
+
hookId: msg.hookId,
|
|
217929
|
+
agentId: msg.agentId,
|
|
217930
|
+
deliveryKey: msg.deliveryKey,
|
|
217931
|
+
firedAt: msg.firedAt,
|
|
217932
|
+
...msg.event ? { event: msg.event } : {},
|
|
217933
|
+
...snapshot ? { snapshot } : {},
|
|
217934
|
+
...msg.github ? { github: msg.github } : {}
|
|
217935
|
+
};
|
|
217463
217936
|
const c = msg.context;
|
|
217464
217937
|
if (c?.source === "github" && c.action === "deleted" && !msg.target) {
|
|
217465
217938
|
const key = sessionKey(nmsg.platform, nmsg.channel, nmsg.thread ?? nmsg.msgId, msg.agentId);
|
|
217466
217939
|
if (!this.store.getSession(key)?.acpSessionId) {
|
|
217467
217940
|
this.log.info(`hook: skipping github ${c.event ?? "event"}:deleted fire ${msg.msgId} — no session for ${key}`);
|
|
217468
|
-
|
|
217469
|
-
|
|
217941
|
+
const entry = {
|
|
217942
|
+
agentId: msg.agentId,
|
|
217943
|
+
msg: nmsg,
|
|
217944
|
+
initAbort: new AbortController(),
|
|
217945
|
+
hookContext,
|
|
217946
|
+
resolve: () => {},
|
|
217947
|
+
reject: () => {}
|
|
217948
|
+
};
|
|
217949
|
+
if (this.persistInbox(entry, key, { required: true }) !== "existing") this.emitHookCompletion(hookContext, "success", { reason: "deleted: no existing session" }, entry);
|
|
217950
|
+
return { accepted: true };
|
|
217470
217951
|
}
|
|
217471
217952
|
}
|
|
217472
217953
|
const githubReply = c?.source === "github" && c.repo && c.number !== void 0 ? {
|
|
@@ -217474,14 +217955,223 @@ var Daemon = class Daemon {
|
|
|
217474
217955
|
repo: c.repo,
|
|
217475
217956
|
number: c.number
|
|
217476
217957
|
} : void 0;
|
|
217958
|
+
if (githubReply) hookContext.githubReply = githubReply;
|
|
217959
|
+
const anchored = await this.anchorTrigger(msg.agentId, nmsg, msg.target, hookAnchorText(msg), `hook "${msg.hookId}"`);
|
|
217960
|
+
if (!anchored) return {
|
|
217961
|
+
accepted: false,
|
|
217962
|
+
reason: "dropped"
|
|
217963
|
+
};
|
|
217964
|
+
let admission;
|
|
217965
|
+
this.dispatch(msg.agentId, anchored, msg.target?.integrationId, void 0, void 0, {
|
|
217966
|
+
requireDurable: true,
|
|
217967
|
+
onAdmission: (result) => {
|
|
217968
|
+
admission = result;
|
|
217969
|
+
}
|
|
217970
|
+
}, githubReply, hookContext).catch((err) => this.log.error(`hook turn failed for agent "${msg.agentId}": ${formatErr(err)}`));
|
|
217971
|
+
if (!admission) throw new Error("hook admission barrier did not settle synchronously");
|
|
217972
|
+
if (!admission.accepted) return {
|
|
217973
|
+
accepted: false,
|
|
217974
|
+
reason: admission.reason ?? "durability"
|
|
217975
|
+
};
|
|
217976
|
+
return { accepted: true };
|
|
217977
|
+
}
|
|
217978
|
+
/** Resolve a PR revision if the webhook omitted it (notably
|
|
217979
|
+
* issue_comment), cross the CP hook/start barrier, then return the active
|
|
217980
|
+
* review authority. Failure only disables structured effects; the ordinary
|
|
217981
|
+
* agent turn and final poster continue. */
|
|
217982
|
+
async prepareGithubTurn(entry) {
|
|
217983
|
+
const hook = entry.hookContext;
|
|
217984
|
+
const snapshot = hook?.snapshot;
|
|
217985
|
+
const github = hook?.github;
|
|
217986
|
+
if (!hook || !snapshot || !github || github.subjectKind !== "pull_request" || github.pullNumber === void 0 || snapshot.reviewPolicy === "off" && snapshot.reportingMode === "off" || snapshot.gateMode !== "informational" || snapshot.reportingMode === "status") return void 0;
|
|
217987
|
+
if (this.cfg.daemonId && snapshot.dispatchDaemonId !== this.cfg.daemonId) {
|
|
217988
|
+
this.log.warn(`github review: stale dispatch daemon for ${hook.hookId}:${hook.deliveryKey}`);
|
|
217989
|
+
return;
|
|
217990
|
+
}
|
|
217991
|
+
if (!github.headSha || !github.baseSha) try {
|
|
217992
|
+
const postToken = await this.gitCreds.getPostToken(hook.agentId, github.repoFullName, hook.hookId);
|
|
217993
|
+
const revision = await this.githubReviewClient.getPull(postToken.token, github.repoFullName, github.pullNumber);
|
|
217994
|
+
hook.github = {
|
|
217995
|
+
...github,
|
|
217996
|
+
headSha: revision.headSha,
|
|
217997
|
+
baseSha: revision.baseSha,
|
|
217998
|
+
reportSha: revision.headSha,
|
|
217999
|
+
isDraft: revision.draft
|
|
218000
|
+
};
|
|
218001
|
+
this.persistHookState(entry, void 0, true);
|
|
218002
|
+
} catch (err) {
|
|
218003
|
+
this.log.warn(`github review: unable to resolve PR revision (${formatErr(err)})`);
|
|
218004
|
+
return;
|
|
218005
|
+
}
|
|
218006
|
+
const trusted = hook.github;
|
|
218007
|
+
if (!trusted.headSha || !trusted.baseSha || trusted.pullNumber === void 0) return void 0;
|
|
218008
|
+
const client = this.cpClient;
|
|
218009
|
+
if (!client) return void 0;
|
|
218010
|
+
const payload = {
|
|
218011
|
+
hookId: hook.hookId,
|
|
218012
|
+
agentId: hook.agentId,
|
|
218013
|
+
deliveryKey: hook.deliveryKey,
|
|
218014
|
+
...hook.event ? { event: hook.event } : {},
|
|
218015
|
+
github: {
|
|
218016
|
+
...trusted,
|
|
218017
|
+
reportSha: trusted.reportSha ?? trusted.headSha
|
|
218018
|
+
},
|
|
218019
|
+
...snapshot
|
|
218020
|
+
};
|
|
218021
|
+
let started = false;
|
|
218022
|
+
for (let attempt = 0; attempt < 3 && !started; attempt += 1) try {
|
|
218023
|
+
await client.startHook(payload);
|
|
218024
|
+
started = true;
|
|
218025
|
+
} catch (err) {
|
|
218026
|
+
if (attempt === 2) {
|
|
218027
|
+
this.log.warn(`github review: hook/start rejected (${formatErr(err)})`);
|
|
218028
|
+
return;
|
|
218029
|
+
}
|
|
218030
|
+
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
218031
|
+
}
|
|
218032
|
+
if (snapshot.reviewPolicy === "off") return void 0;
|
|
218033
|
+
const recoverableAttempt = hook.reviewAttemptId !== void 0 && hook.reviewRequestedEvent !== void 0 && hook.reviewRequestedVerdict !== void 0 && (hook.reviewResult === void 0 || hook.reviewResult.state === "ambiguous");
|
|
218034
|
+
const active = {
|
|
218035
|
+
entry,
|
|
218036
|
+
hook,
|
|
218037
|
+
snapshot,
|
|
218038
|
+
repoId: trusted.repoId,
|
|
218039
|
+
repoFullName: trusted.repoFullName,
|
|
218040
|
+
pullNumber: trusted.pullNumber,
|
|
218041
|
+
expectedHeadSha: trusted.headSha,
|
|
218042
|
+
expectedBaseSha: trusted.baseSha,
|
|
218043
|
+
reportSha: trusted.reportSha ?? trusted.headSha,
|
|
218044
|
+
reviewState: hook.reviewAttemptId === void 0 || recoverableAttempt ? "idle" : "done"
|
|
218045
|
+
};
|
|
218046
|
+
if (recoverableAttempt) await this.reconcileGithubReviewAttempt(active);
|
|
218047
|
+
return active;
|
|
218048
|
+
}
|
|
218049
|
+
/** Store the immediate and terminal-report copies together so they cannot
|
|
218050
|
+
* drift across submit and restart-recovery paths. */
|
|
218051
|
+
persistGithubReviewEffect(active, attemptId, effect, required = false) {
|
|
218052
|
+
const result = reviewResultForWire(effect);
|
|
218053
|
+
active.hook.reviewResult = result;
|
|
218054
|
+
active.hook.reviewReportAttemptId = attemptId;
|
|
218055
|
+
active.hook.reviewReportResult = result;
|
|
218056
|
+
this.persistHookState(active.entry, void 0, required);
|
|
218057
|
+
return result;
|
|
218058
|
+
}
|
|
218059
|
+
/** A daemon restart may replay an attempt after an ambiguous POST/list race.
|
|
218060
|
+
* Before prompting the model, perform a GET-only marker reconciliation. A
|
|
218061
|
+
* still-missing marker remains blocked; only an explicit tool invocation can
|
|
218062
|
+
* cross the full reauthorization + revision fence and retry the mutation. */
|
|
218063
|
+
async reconcileGithubReviewAttempt(active) {
|
|
218064
|
+
const cp = this.cpClient;
|
|
218065
|
+
const attemptId = active.hook.reviewAttemptId;
|
|
218066
|
+
const requestedEvent = active.hook.reviewRequestedEvent;
|
|
218067
|
+
const requestedVerdict = active.hook.reviewRequestedVerdict;
|
|
218068
|
+
if (!cp || !attemptId || !requestedEvent || !requestedVerdict) return;
|
|
218069
|
+
try {
|
|
218070
|
+
const authorized = await cp.authorizeGithubReview({
|
|
218071
|
+
hookId: active.hook.hookId,
|
|
218072
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218073
|
+
attemptId,
|
|
218074
|
+
requestedEvent,
|
|
218075
|
+
requestedVerdict,
|
|
218076
|
+
snapshot: active.snapshot
|
|
218077
|
+
});
|
|
218078
|
+
if (!authorizedReviewTargetMatches(active, attemptId, authorized)) {
|
|
218079
|
+
this.log.warn("github review: recovery authorization returned a mismatched target");
|
|
218080
|
+
return;
|
|
218081
|
+
}
|
|
218082
|
+
const effect = await this.githubReviewClient.reconcile(authorizedReviewTarget(active, attemptId, authorized, true), requestedEvent, requestedVerdict);
|
|
218083
|
+
if (effect.state !== "submitted") return;
|
|
218084
|
+
const result = this.persistGithubReviewEffect(active, attemptId, effect, true);
|
|
218085
|
+
await cp.reportGithubReviewResult({
|
|
218086
|
+
hookId: active.hook.hookId,
|
|
218087
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218088
|
+
attemptId,
|
|
218089
|
+
snapshot: active.snapshot,
|
|
218090
|
+
result
|
|
218091
|
+
});
|
|
218092
|
+
active.reviewState = "done";
|
|
218093
|
+
} catch (err) {
|
|
218094
|
+
this.log.warn(`github review: replay reconciliation deferred (${formatErr(err)})`);
|
|
218095
|
+
}
|
|
218096
|
+
}
|
|
218097
|
+
async submitGithubReview(req) {
|
|
218098
|
+
const key = sessionKey(req.platform, req.channel, req.thread, req.agentId);
|
|
218099
|
+
const active = this.activeGithubTurnMeta.get(key);
|
|
218100
|
+
if (!active || active.hook.agentId !== req.agentId) throw new Error("formal GitHub review is only available during the active PR hook turn");
|
|
218101
|
+
if (!reviewPolicyAllows(active.snapshot.reviewPolicy, req.event)) throw new Error(`${req.event} exceeds this hook's ${active.snapshot.reviewPolicy} review policy`);
|
|
218102
|
+
if (active.reviewState !== "idle") throw new Error("this PR hook turn already has a formal review attempt");
|
|
218103
|
+
active.reviewState = "submitting";
|
|
218104
|
+
const recovering = active.hook.reviewAttemptId !== void 0;
|
|
218105
|
+
if (recovering) {
|
|
218106
|
+
if (active.hook.reviewRequestedEvent === void 0 || active.hook.reviewRequestedVerdict === void 0 || active.hook.reviewRequestedEvent !== req.event || active.hook.reviewRequestedVerdict !== req.verdict) {
|
|
218107
|
+
active.reviewState = "idle";
|
|
218108
|
+
throw new Error("a recovered formal-review attempt must keep its original event and verdict");
|
|
218109
|
+
}
|
|
218110
|
+
} else {
|
|
218111
|
+
active.hook.reviewRequestedEvent = req.event;
|
|
218112
|
+
active.hook.reviewRequestedVerdict = req.verdict;
|
|
218113
|
+
}
|
|
218114
|
+
const attemptId = active.hook.reviewAttemptId ?? randomUUID();
|
|
218115
|
+
active.hook.reviewAttemptId = attemptId;
|
|
218116
|
+
const entry = active.entry;
|
|
217477
218117
|
try {
|
|
217478
|
-
|
|
217479
|
-
if (sessionId !== null) report("success", { sessionId });
|
|
217480
|
-
else report("failed", { reason: "dropped" });
|
|
218118
|
+
this.persistHookState(entry, void 0, true);
|
|
217481
218119
|
} catch (err) {
|
|
217482
|
-
|
|
218120
|
+
if (!recovering) {
|
|
218121
|
+
delete active.hook.reviewAttemptId;
|
|
218122
|
+
delete active.hook.reviewRequestedEvent;
|
|
218123
|
+
delete active.hook.reviewRequestedVerdict;
|
|
218124
|
+
}
|
|
218125
|
+
active.reviewState = "idle";
|
|
218126
|
+
throw new Error(`formal review durability barrier failed: ${formatErr(err)}`);
|
|
218127
|
+
}
|
|
218128
|
+
const cp = this.cpClient;
|
|
218129
|
+
if (!cp) {
|
|
218130
|
+
active.reviewState = "done";
|
|
218131
|
+
throw new Error("control plane is not connected; formal review denied");
|
|
218132
|
+
}
|
|
218133
|
+
let authorized;
|
|
218134
|
+
try {
|
|
218135
|
+
authorized = await cp.authorizeGithubReview({
|
|
218136
|
+
hookId: active.hook.hookId,
|
|
218137
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218138
|
+
attemptId,
|
|
218139
|
+
requestedEvent: req.event,
|
|
218140
|
+
requestedVerdict: req.verdict,
|
|
218141
|
+
snapshot: active.snapshot
|
|
218142
|
+
});
|
|
218143
|
+
} catch (err) {
|
|
218144
|
+
active.reviewState = "done";
|
|
217483
218145
|
throw err;
|
|
217484
218146
|
}
|
|
218147
|
+
if (!authorizedReviewTargetMatches(active, attemptId, authorized)) {
|
|
218148
|
+
active.reviewState = "done";
|
|
218149
|
+
throw new Error("control plane returned a mismatched formal-review target");
|
|
218150
|
+
}
|
|
218151
|
+
const effect = await this.githubReviewClient.submit(authorizedReviewTarget(active, attemptId, authorized, recovering), req);
|
|
218152
|
+
const result = this.persistGithubReviewEffect(active, attemptId, effect);
|
|
218153
|
+
try {
|
|
218154
|
+
await cp.reportGithubReviewResult({
|
|
218155
|
+
hookId: active.hook.hookId,
|
|
218156
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218157
|
+
attemptId,
|
|
218158
|
+
snapshot: active.snapshot,
|
|
218159
|
+
result
|
|
218160
|
+
});
|
|
218161
|
+
if (effect.state === "not_submitted") {
|
|
218162
|
+
delete active.hook.reviewAttemptId;
|
|
218163
|
+
delete active.hook.reviewRequestedEvent;
|
|
218164
|
+
delete active.hook.reviewRequestedVerdict;
|
|
218165
|
+
delete active.hook.reviewResult;
|
|
218166
|
+
active.reviewState = "idle";
|
|
218167
|
+
this.persistHookState(entry);
|
|
218168
|
+
} else if (effect.state === "ambiguous") active.reviewState = "idle";
|
|
218169
|
+
else active.reviewState = "done";
|
|
218170
|
+
} catch (err) {
|
|
218171
|
+
active.reviewState = effect.state === "ambiguous" ? "idle" : "done";
|
|
218172
|
+
this.log.warn(`github review: immediate result report failed (${formatErr(err)})`);
|
|
218173
|
+
}
|
|
218174
|
+
return effect;
|
|
217485
218175
|
}
|
|
217486
218176
|
/** The op-switch behind {@link handleRelayMsg} (dedup handled by the caller). */
|
|
217487
218177
|
dispatchRelayOp(msg, chat) {
|
|
@@ -218075,26 +218765,142 @@ var Daemon = class Daemon {
|
|
|
218075
218765
|
* durable row would be un-replayable. Sets `entry.inboxId` so every terminal path can delete
|
|
218076
218766
|
* the row. The row id is the message's stable deliveryId/msgId (§6.3) — idempotent re-append.
|
|
218077
218767
|
*/
|
|
218078
|
-
persistInbox(entry, key) {
|
|
218079
|
-
if (entry.webchat) return;
|
|
218768
|
+
persistInbox(entry, key, options = {}) {
|
|
218769
|
+
if (entry.webchat) return "skipped";
|
|
218080
218770
|
const id = entry.callMeta?.deliveryId ?? entry.msg.msgId;
|
|
218081
218771
|
try {
|
|
218082
|
-
this.store.appendInbox({
|
|
218772
|
+
const inserted = this.store.appendInbox({
|
|
218083
218773
|
id,
|
|
218084
218774
|
sessionKey: key,
|
|
218085
218775
|
agentId: entry.agentId,
|
|
218086
218776
|
msg: JSON.stringify(entry.msg),
|
|
218087
218777
|
integrationId: entry.integrationId ?? null,
|
|
218088
218778
|
callMeta: entry.callMeta ? JSON.stringify(entry.callMeta) : null,
|
|
218779
|
+
hookContext: entry.hookContext ? JSON.stringify(entry.hookContext) : null,
|
|
218780
|
+
posterPublishState: entry.posterPublishState ?? null,
|
|
218089
218781
|
isQueueCmd: entry.isQueueCmd ? 1 : null,
|
|
218090
218782
|
loopGuardCounted: 1,
|
|
218091
218783
|
enqueuedAt: monotonicTs()
|
|
218092
218784
|
});
|
|
218785
|
+
if (!inserted && !options.adoptExisting) return "existing";
|
|
218093
218786
|
entry.inboxId = id;
|
|
218094
218787
|
this.liveInboxIds.add(id);
|
|
218788
|
+
return inserted ? "inserted" : "adopted";
|
|
218095
218789
|
} catch (err) {
|
|
218790
|
+
if (options.required) throw err;
|
|
218096
218791
|
this.log.warn(`durable inbox: append failed for ${id}: ${err.message}`);
|
|
218792
|
+
return "failed";
|
|
218793
|
+
}
|
|
218794
|
+
}
|
|
218795
|
+
persistHookState(entry, posterPublishState, required = false) {
|
|
218796
|
+
if (!entry.inboxId || !entry.hookContext) {
|
|
218797
|
+
if (required) throw new Error("hook state has no durable inbox row");
|
|
218798
|
+
return;
|
|
218799
|
+
}
|
|
218800
|
+
if (posterPublishState) entry.posterPublishState = posterPublishState;
|
|
218801
|
+
try {
|
|
218802
|
+
if (!this.store.updateInboxHookState(entry.inboxId, JSON.stringify(entry.hookContext), posterPublishState)) throw new Error("durable inbox row is missing");
|
|
218803
|
+
} catch (err) {
|
|
218804
|
+
if (required) throw err;
|
|
218805
|
+
this.log.warn(`durable inbox: hook state update failed for ${entry.inboxId}: ${err.message}`);
|
|
218806
|
+
}
|
|
218807
|
+
}
|
|
218808
|
+
emitHookCompletion(hook, status, extra = {}, owner) {
|
|
218809
|
+
if (owner?.hookTerminalReceipt) return;
|
|
218810
|
+
const start = Date.parse(hook.turnStartedAt ?? hook.firedAt);
|
|
218811
|
+
const reviewAttemptId = hook.reviewReportAttemptId ?? hook.reviewAttemptId;
|
|
218812
|
+
const reviewResult = hook.reviewReportResult ?? hook.reviewResult;
|
|
218813
|
+
const report = {
|
|
218814
|
+
hookId: hook.hookId,
|
|
218815
|
+
agentId: hook.agentId,
|
|
218816
|
+
deliveryKey: hook.deliveryKey,
|
|
218817
|
+
...hook.snapshot ?? {},
|
|
218818
|
+
...hook.event ? { event: hook.event } : {},
|
|
218819
|
+
...hook.github ? { github: hook.github } : {},
|
|
218820
|
+
status,
|
|
218821
|
+
durationMs: Number.isFinite(start) ? Math.max(0, this.clock.now() - start) : 0,
|
|
218822
|
+
...extra,
|
|
218823
|
+
...reviewAttemptId && reviewResult ? {
|
|
218824
|
+
reviewAttemptId,
|
|
218825
|
+
reviewResult
|
|
218826
|
+
} : {}
|
|
218827
|
+
};
|
|
218828
|
+
let reportInboxId;
|
|
218829
|
+
if (owner?.inboxId) try {
|
|
218830
|
+
const completed = this.store.completeHookInbox(owner.inboxId, JSON.stringify(report), this.clock.now());
|
|
218831
|
+
if (completed === "already-terminal") {
|
|
218832
|
+
owner.hookTerminalReceipt = true;
|
|
218833
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218834
|
+
return;
|
|
218835
|
+
}
|
|
218836
|
+
if (completed === "missing") {
|
|
218837
|
+
this.log.warn(`durable inbox: hook terminal receipt owner is missing for ${owner.inboxId}`);
|
|
218838
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218839
|
+
} else {
|
|
218840
|
+
owner.hookTerminalReceipt = true;
|
|
218841
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218842
|
+
reportInboxId = owner.inboxId;
|
|
218843
|
+
}
|
|
218844
|
+
} catch (err) {
|
|
218845
|
+
this.log.warn(`durable inbox: hook terminal receipt failed for ${owner.inboxId}: ${formatErr(err)}`);
|
|
218097
218846
|
}
|
|
218847
|
+
this.sendHookReport(report, reportInboxId);
|
|
218848
|
+
}
|
|
218849
|
+
/** Send one durable terminal report and release only its outbox payload after
|
|
218850
|
+
* the CP's correlated persistence/projection ACK. The stable id receipt stays
|
|
218851
|
+
* bounded locally to absorb later relay redelivery. */
|
|
218852
|
+
sendHookReport(report, inboxId) {
|
|
218853
|
+
if (!this.cpClient) return;
|
|
218854
|
+
if (inboxId && this.hookReportInflight.has(inboxId)) return;
|
|
218855
|
+
if (inboxId && this.hookReportInflight.size >= MAX_HOOK_REPORT_INFLIGHT) {
|
|
218856
|
+
this.scheduleHookReportRetry();
|
|
218857
|
+
return;
|
|
218858
|
+
}
|
|
218859
|
+
if (inboxId) this.hookReportInflight.add(inboxId);
|
|
218860
|
+
const connectionId = this.hookReportConnectionId;
|
|
218861
|
+
let refillDrainSlot = false;
|
|
218862
|
+
Promise.resolve(this.cpClient.emitHookReport(report)).then((result) => {
|
|
218863
|
+
if (!inboxId) return;
|
|
218864
|
+
if (result === "legacy-sent") {
|
|
218865
|
+
try {
|
|
218866
|
+
if (!this.store.markLegacyHookReportSent(inboxId, connectionId)) throw new Error("durable inbox row is missing");
|
|
218867
|
+
refillDrainSlot = true;
|
|
218868
|
+
} catch (err) {
|
|
218869
|
+
this.log.warn(`durable inbox: legacy report stamp failed for ${inboxId}: ${formatErr(err)}`);
|
|
218870
|
+
this.scheduleHookReportRetry();
|
|
218871
|
+
}
|
|
218872
|
+
return;
|
|
218873
|
+
}
|
|
218874
|
+
refillDrainSlot = true;
|
|
218875
|
+
try {
|
|
218876
|
+
this.store.acknowledgeHookInbox(inboxId);
|
|
218877
|
+
} catch (err) {
|
|
218878
|
+
this.log.warn(`durable inbox: hook report ACK cleanup failed for ${inboxId}: ${formatErr(err)}`);
|
|
218879
|
+
}
|
|
218880
|
+
}).catch((err) => {
|
|
218881
|
+
if (typeof err === "object" && err !== null && "retryable" in err && err.retryable === false && inboxId) {
|
|
218882
|
+
refillDrainSlot = true;
|
|
218883
|
+
try {
|
|
218884
|
+
this.store.acknowledgeHookInbox(inboxId);
|
|
218885
|
+
} catch (cleanupError) {
|
|
218886
|
+
this.log.warn(`durable inbox: hook report dead-letter failed for ${inboxId}: ${formatErr(cleanupError)}`);
|
|
218887
|
+
}
|
|
218888
|
+
this.log.warn(`hook report permanently rejected for ${inboxId}: ${formatErr(err)}`);
|
|
218889
|
+
return;
|
|
218890
|
+
}
|
|
218891
|
+
this.log.debug(`hook report retained for retry: ${formatErr(err)}`);
|
|
218892
|
+
this.scheduleHookReportRetry();
|
|
218893
|
+
}).finally(() => {
|
|
218894
|
+
if (inboxId) this.hookReportInflight.delete(inboxId);
|
|
218895
|
+
if (refillDrainSlot) this.scheduleHookReportRetry(250);
|
|
218896
|
+
});
|
|
218897
|
+
}
|
|
218898
|
+
scheduleHookReportRetry(delayMs = 5e3) {
|
|
218899
|
+
if (this.draining || this.hookReportRetryTimer !== void 0) return;
|
|
218900
|
+
this.hookReportRetryTimer = this.clock.setTimeout(() => {
|
|
218901
|
+
this.hookReportRetryTimer = void 0;
|
|
218902
|
+
this.replayHookTerminalReports();
|
|
218903
|
+
}, delayMs);
|
|
218098
218904
|
}
|
|
218099
218905
|
/** Remove an entry's durable inbox row once its turn reaches ANY terminal state (success,
|
|
218100
218906
|
* reject/fail-stop, cancel, gate-drop) so it is not replayed on the next startup. No-op
|
|
@@ -218103,6 +218909,7 @@ var Daemon = class Daemon {
|
|
|
218103
218909
|
removeInbox(entry) {
|
|
218104
218910
|
if (!entry.inboxId) return;
|
|
218105
218911
|
this.liveInboxIds.delete(entry.inboxId);
|
|
218912
|
+
if (entry.hookContext) return;
|
|
218106
218913
|
try {
|
|
218107
218914
|
this.store.removeInbox(entry.inboxId);
|
|
218108
218915
|
} catch (err) {
|
|
@@ -218130,6 +218937,7 @@ var Daemon = class Daemon {
|
|
|
218130
218937
|
let removed = 0;
|
|
218131
218938
|
try {
|
|
218132
218939
|
for (const row of this.store.listInboxBySessionKeyFifo()) {
|
|
218940
|
+
if (row.hookContext || row.terminalReport) continue;
|
|
218133
218941
|
let msg;
|
|
218134
218942
|
try {
|
|
218135
218943
|
msg = JSON.parse(row.msg);
|
|
@@ -218146,8 +218954,8 @@ var Daemon = class Daemon {
|
|
|
218146
218954
|
}
|
|
218147
218955
|
return removed;
|
|
218148
218956
|
}
|
|
218149
|
-
isLoopGuardOpen(msg) {
|
|
218150
|
-
return usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopGuardScope(msg));
|
|
218957
|
+
isLoopGuardOpen(msg, includeHook = false) {
|
|
218958
|
+
return (usesLoopGuard(msg) || includeHook) && this.store.isLoopGuardOpen(loopGuardScope(msg));
|
|
218151
218959
|
}
|
|
218152
218960
|
/** First-open side effects for a durable conversation circuit: purge restart work,
|
|
218153
218961
|
* drop every matching serial queue, cancel live ACP turns across ALL agents sharing
|
|
@@ -218214,41 +219022,71 @@ var Daemon = class Daemon {
|
|
|
218214
219022
|
...error ? { error } : {}
|
|
218215
219023
|
});
|
|
218216
219024
|
}
|
|
218217
|
-
dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply) {
|
|
219025
|
+
dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply, hookContext, posterPublishState = githubReply ? "not_started" : void 0) {
|
|
218218
219026
|
return new Promise((resolve, reject) => {
|
|
219027
|
+
let admissionSettled = false;
|
|
219028
|
+
const settleAdmission = (result) => {
|
|
219029
|
+
if (admissionSettled) return;
|
|
219030
|
+
admissionSettled = true;
|
|
219031
|
+
opts?.onAdmission?.(result);
|
|
219032
|
+
};
|
|
218219
219033
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
218220
219034
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
219035
|
+
settleAdmission({
|
|
219036
|
+
accepted: false,
|
|
219037
|
+
reason: "draining"
|
|
219038
|
+
});
|
|
218221
219039
|
resolve(null);
|
|
218222
219040
|
return;
|
|
218223
219041
|
}
|
|
218224
219042
|
if (this.paused(agentId)) {
|
|
218225
219043
|
this.log.info(`dispatch: skipped for agent "${agentId}" (paused)`);
|
|
219044
|
+
settleAdmission({
|
|
219045
|
+
accepted: false,
|
|
219046
|
+
reason: "paused"
|
|
219047
|
+
});
|
|
218226
219048
|
resolve(null);
|
|
218227
219049
|
return;
|
|
218228
219050
|
}
|
|
218229
219051
|
if (this.safetyDrainingAgents.has(agentId)) {
|
|
218230
219052
|
if (opts?.fromInboxReplay) this.waitForSafetyDrain(agentId).then(() => {
|
|
218231
219053
|
if (this.draining || !this.agents.has(agentId)) {
|
|
219054
|
+
settleAdmission({
|
|
219055
|
+
accepted: false,
|
|
219056
|
+
reason: "draining"
|
|
219057
|
+
});
|
|
218232
219058
|
resolve(null);
|
|
218233
219059
|
return;
|
|
218234
219060
|
}
|
|
218235
219061
|
if (opts.inboxReplayId !== void 0 && !this.store.hasInbox(opts.inboxReplayId)) {
|
|
219062
|
+
settleAdmission({
|
|
219063
|
+
accepted: false,
|
|
219064
|
+
reason: "dropped"
|
|
219065
|
+
});
|
|
218236
219066
|
resolve(null);
|
|
218237
219067
|
return;
|
|
218238
219068
|
}
|
|
218239
|
-
this.dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply).then(resolve, reject);
|
|
219069
|
+
this.dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply, hookContext, posterPublishState).then(resolve, reject);
|
|
218240
219070
|
}).catch(reject);
|
|
218241
219071
|
else {
|
|
218242
219072
|
this.log.info(`dispatch: skipped for agent "${agentId}" (stopping interrupted work)`);
|
|
219073
|
+
settleAdmission({
|
|
219074
|
+
accepted: false,
|
|
219075
|
+
reason: "busy"
|
|
219076
|
+
});
|
|
218243
219077
|
resolve(null);
|
|
218244
219078
|
}
|
|
218245
219079
|
return;
|
|
218246
219080
|
}
|
|
218247
219081
|
const key = sessionKey(msg.platform, msg.channel, msg.thread ?? msg.msgId, agentId);
|
|
218248
219082
|
const loopScope = loopGuardScope(msg);
|
|
218249
|
-
if (usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopScope)) {
|
|
219083
|
+
if ((usesLoopGuard(msg) || hookContext !== void 0) && this.store.isLoopGuardOpen(loopScope)) {
|
|
218250
219084
|
if (opts?.fromInboxReplay) this.purgeLoopScopeInbox(loopScope);
|
|
218251
219085
|
this.log.warn(`dispatch: skipped ${msg.msgId}; loop guard is open for ${loopScope}`);
|
|
219086
|
+
settleAdmission({
|
|
219087
|
+
accepted: false,
|
|
219088
|
+
reason: "loop_protection"
|
|
219089
|
+
});
|
|
218252
219090
|
resolve(null);
|
|
218253
219091
|
return;
|
|
218254
219092
|
}
|
|
@@ -218262,6 +219100,10 @@ var Daemon = class Daemon {
|
|
|
218262
219100
|
});
|
|
218263
219101
|
else this.purgeLoopScopeInbox(loopScope);
|
|
218264
219102
|
} else this.log.warn(`dispatch: dropped malformed Slack platform event ${msg.msgId}`);
|
|
219103
|
+
settleAdmission({
|
|
219104
|
+
accepted: false,
|
|
219105
|
+
reason: "malformed_platform_event"
|
|
219106
|
+
});
|
|
218265
219107
|
resolve(null);
|
|
218266
219108
|
return;
|
|
218267
219109
|
}
|
|
@@ -218272,8 +219114,10 @@ var Daemon = class Daemon {
|
|
|
218272
219114
|
...integrationId !== void 0 ? { integrationId } : {},
|
|
218273
219115
|
...webchat ? { webchat } : {},
|
|
218274
219116
|
...callMeta ? { callMeta } : {},
|
|
219117
|
+
...hookContext ? { hookContext } : {},
|
|
218275
219118
|
...opts?.isQueueCmd ? { isQueueCmd: true } : {},
|
|
218276
219119
|
...githubReply ? { githubReply } : {},
|
|
219120
|
+
...posterPublishState ? { posterPublishState } : {},
|
|
218277
219121
|
resolve,
|
|
218278
219122
|
reject
|
|
218279
219123
|
};
|
|
@@ -218281,25 +219125,81 @@ var Daemon = class Daemon {
|
|
|
218281
219125
|
const q = this.serialQueue.get(key) ?? [];
|
|
218282
219126
|
if (q.length >= MAX_QUEUED_PER_SESSION) {
|
|
218283
219127
|
this.log.warn(`dispatch: queue full for session ${key} (${q.length}) — rejecting (queue_full)`);
|
|
219128
|
+
settleAdmission({
|
|
219129
|
+
accepted: false,
|
|
219130
|
+
reason: "queue_full"
|
|
219131
|
+
});
|
|
218284
219132
|
reject(new QueueFullError(key));
|
|
218285
219133
|
return;
|
|
218286
219134
|
}
|
|
218287
219135
|
if (!opts?.replay && !this.admitLoopGuardTurn(agentId, msg, integrationId, opts?.inboxReplayId)) {
|
|
219136
|
+
settleAdmission({
|
|
219137
|
+
accepted: false,
|
|
219138
|
+
reason: "loop_protection"
|
|
219139
|
+
});
|
|
219140
|
+
resolve(null);
|
|
219141
|
+
return;
|
|
219142
|
+
}
|
|
219143
|
+
let persistence;
|
|
219144
|
+
try {
|
|
219145
|
+
persistence = this.persistInbox(entry, key, {
|
|
219146
|
+
required: opts?.requireDurable,
|
|
219147
|
+
adoptExisting: opts?.adoptExistingInbox
|
|
219148
|
+
});
|
|
219149
|
+
} catch (err) {
|
|
219150
|
+
settleAdmission({
|
|
219151
|
+
accepted: false,
|
|
219152
|
+
reason: "durability"
|
|
219153
|
+
});
|
|
219154
|
+
reject(err);
|
|
219155
|
+
return;
|
|
219156
|
+
}
|
|
219157
|
+
if (persistence === "existing") {
|
|
219158
|
+
settleAdmission({
|
|
219159
|
+
accepted: true,
|
|
219160
|
+
duplicate: true
|
|
219161
|
+
});
|
|
218288
219162
|
resolve(null);
|
|
218289
219163
|
return;
|
|
218290
219164
|
}
|
|
218291
219165
|
q.push(entry);
|
|
218292
219166
|
this.serialQueue.set(key, q);
|
|
218293
|
-
|
|
219167
|
+
settleAdmission({ accepted: true });
|
|
218294
219168
|
this.log.debug(`dispatch: queued behind in-flight turn for session ${key} (depth ${q.length})`);
|
|
218295
219169
|
return;
|
|
218296
219170
|
}
|
|
218297
219171
|
if (!opts?.replay && !this.admitLoopGuardTurn(agentId, msg, integrationId, opts?.inboxReplayId)) {
|
|
219172
|
+
settleAdmission({
|
|
219173
|
+
accepted: false,
|
|
219174
|
+
reason: "loop_protection"
|
|
219175
|
+
});
|
|
219176
|
+
resolve(null);
|
|
219177
|
+
return;
|
|
219178
|
+
}
|
|
219179
|
+
let persistence;
|
|
219180
|
+
try {
|
|
219181
|
+
persistence = this.persistInbox(entry, key, {
|
|
219182
|
+
required: opts?.requireDurable,
|
|
219183
|
+
adoptExisting: opts?.adoptExistingInbox
|
|
219184
|
+
});
|
|
219185
|
+
} catch (err) {
|
|
219186
|
+
settleAdmission({
|
|
219187
|
+
accepted: false,
|
|
219188
|
+
reason: "durability"
|
|
219189
|
+
});
|
|
219190
|
+
reject(err);
|
|
219191
|
+
return;
|
|
219192
|
+
}
|
|
219193
|
+
if (persistence === "existing") {
|
|
219194
|
+
settleAdmission({
|
|
219195
|
+
accepted: true,
|
|
219196
|
+
duplicate: true
|
|
219197
|
+
});
|
|
218298
219198
|
resolve(null);
|
|
218299
219199
|
return;
|
|
218300
219200
|
}
|
|
218301
|
-
this.persistInbox(entry, key);
|
|
218302
219201
|
this.inflight.add(key);
|
|
219202
|
+
settleAdmission({ accepted: true });
|
|
218303
219203
|
this.runLoop(key, entry);
|
|
218304
219204
|
});
|
|
218305
219205
|
}
|
|
@@ -218355,7 +219255,7 @@ var Daemon = class Daemon {
|
|
|
218355
219255
|
dispatchGateReason(entry) {
|
|
218356
219256
|
if (entry.cancelledReason) return entry.cancelledReason;
|
|
218357
219257
|
if (this.paused(entry.agentId)) return "pause";
|
|
218358
|
-
if (this.isLoopGuardOpen(entry.msg)) return "loop protection";
|
|
219258
|
+
if (this.isLoopGuardOpen(entry.msg, entry.hookContext !== void 0)) return "loop protection";
|
|
218359
219259
|
}
|
|
218360
219260
|
beginActiveDispatch(agentId, key) {
|
|
218361
219261
|
let resolveDone;
|
|
@@ -218406,13 +219306,19 @@ var Daemon = class Daemon {
|
|
|
218406
219306
|
this.activeGateEntries.set(key, entry);
|
|
218407
219307
|
if (entry !== firstEntry && (this.draining || this.drainingAgents.has(entry.agentId) || this.paused(entry.agentId))) {
|
|
218408
219308
|
this.terminateQueuedSink(entry);
|
|
218409
|
-
if (!this.draining)
|
|
219309
|
+
if (!this.draining) {
|
|
219310
|
+
if (entry.hookContext) this.emitHookCompletion(entry.hookContext, "failed", { reason: "dropped" }, entry);
|
|
219311
|
+
this.removeInbox(entry);
|
|
219312
|
+
}
|
|
218410
219313
|
entry.resolve(null);
|
|
218411
219314
|
const rest = this.serialQueue.get(key) ?? [];
|
|
218412
219315
|
this.serialQueue.delete(key);
|
|
218413
219316
|
for (const e of rest) {
|
|
218414
219317
|
this.terminateQueuedSink(e);
|
|
218415
|
-
if (!this.draining)
|
|
219318
|
+
if (!this.draining) {
|
|
219319
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: "dropped" }, e);
|
|
219320
|
+
this.removeInbox(e);
|
|
219321
|
+
}
|
|
218416
219322
|
e.resolve(null);
|
|
218417
219323
|
}
|
|
218418
219324
|
this.inflight.delete(key);
|
|
@@ -218436,7 +219342,10 @@ var Daemon = class Daemon {
|
|
|
218436
219342
|
for (const e of rest) {
|
|
218437
219343
|
const failStop = new FailStopError(key);
|
|
218438
219344
|
this.terminateQueuedSink(e, failStop.message);
|
|
218439
|
-
if (!this.draining)
|
|
219345
|
+
if (!this.draining) {
|
|
219346
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: "fail_stop" }, e);
|
|
219347
|
+
this.removeInbox(e);
|
|
219348
|
+
}
|
|
218440
219349
|
e.reject(failStop);
|
|
218441
219350
|
}
|
|
218442
219351
|
this.inflight.delete(key);
|
|
@@ -218452,13 +219361,23 @@ var Daemon = class Daemon {
|
|
|
218452
219361
|
}
|
|
218453
219362
|
}
|
|
218454
219363
|
async dispatchOne(entry, key) {
|
|
218455
|
-
const { agentId, msg, integrationId, webchat, callMeta, githubReply } = entry;
|
|
219364
|
+
const { agentId, msg, integrationId, webchat, callMeta, githubReply, hookContext } = entry;
|
|
218456
219365
|
const initialGate = this.dispatchGateReason(entry);
|
|
218457
219366
|
if (initialGate) {
|
|
218458
219367
|
this.terminateQueuedSink(entry);
|
|
219368
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: initialGate }, entry);
|
|
218459
219369
|
this.log.info(`dispatch: skipped admitted turn ${msg.msgId} (${initialGate})`);
|
|
218460
219370
|
return null;
|
|
218461
219371
|
}
|
|
219372
|
+
if (hookContext && !hookContext.turnStartedAt) {
|
|
219373
|
+
hookContext.turnStartedAt = new Date(this.clock.now()).toISOString();
|
|
219374
|
+
try {
|
|
219375
|
+
this.persistHookState(entry, void 0, true);
|
|
219376
|
+
} catch (err) {
|
|
219377
|
+
this.emitHookCompletion(hookContext, "failed", { reason: "durability" }, entry);
|
|
219378
|
+
throw err;
|
|
219379
|
+
}
|
|
219380
|
+
}
|
|
218462
219381
|
if (callMeta?.correlationId !== void 0) this.recordWorkerReport(key, callMeta, msg.text);
|
|
218463
219382
|
const agent = this.agents.get(agentId);
|
|
218464
219383
|
const agentName = agent.displayName?.trim() || agent.name;
|
|
@@ -218510,7 +219429,11 @@ var Daemon = class Daemon {
|
|
|
218510
219429
|
} finally {
|
|
218511
219430
|
releaseReplyConn();
|
|
218512
219431
|
}
|
|
218513
|
-
if (interrupted)
|
|
219432
|
+
if (interrupted) {
|
|
219433
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: interrupted }, entry);
|
|
219434
|
+
return null;
|
|
219435
|
+
}
|
|
219436
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: "session_start_failed" }, entry);
|
|
218514
219437
|
throw err;
|
|
218515
219438
|
}
|
|
218516
219439
|
const { sessionId, blocks, created } = handled;
|
|
@@ -218530,6 +219453,7 @@ var Daemon = class Daemon {
|
|
|
218530
219453
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218531
219454
|
this.terminateQueuedSink(entry);
|
|
218532
219455
|
releaseReplyConn();
|
|
219456
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: initializedGate }, entry);
|
|
218533
219457
|
this.log.info(`dispatch: skipped initialized turn ${msg.msgId} (${initializedGate})`);
|
|
218534
219458
|
return null;
|
|
218535
219459
|
}
|
|
@@ -218570,13 +219494,17 @@ var Daemon = class Daemon {
|
|
|
218570
219494
|
resolveDone,
|
|
218571
219495
|
...callMeta ? { callMeta } : {},
|
|
218572
219496
|
...pendingWebchat ? { webchat: pendingWebchat } : {},
|
|
218573
|
-
...githubReply ? { github: {
|
|
219497
|
+
...githubReply && entry.posterPublishState !== "in_flight" && entry.posterPublishState !== "settled" ? { github: {
|
|
218574
219498
|
...this.makeGithubReply(agentId, githubReply, sessionId),
|
|
218575
219499
|
deferredFinalTranscript: false
|
|
218576
219500
|
} } : {}
|
|
218577
219501
|
};
|
|
218578
219502
|
this.pending.set(pendingTurnKey(agentId, sessionId), p);
|
|
218579
219503
|
if (callMeta) this.activeTurnCallMeta.set(key, callMeta);
|
|
219504
|
+
const activeGithub = await this.prepareGithubTurn(entry).catch((err) => {
|
|
219505
|
+
this.log.warn(`github review: turn setup failed (${formatErr(err)})`);
|
|
219506
|
+
});
|
|
219507
|
+
if (activeGithub) this.activeGithubTurnMeta.set(key, activeGithub);
|
|
218580
219508
|
let finalPhase = "end";
|
|
218581
219509
|
try {
|
|
218582
219510
|
const host = await this.ensureHostAsync(agentId);
|
|
@@ -218593,6 +219521,10 @@ var Daemon = class Daemon {
|
|
|
218593
219521
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218594
219522
|
this.terminateQueuedSink(entry);
|
|
218595
219523
|
if (readyGate === "loop protection") finalPhase = "problem";
|
|
219524
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219525
|
+
sessionId,
|
|
219526
|
+
reason: readyGate
|
|
219527
|
+
}, entry);
|
|
218596
219528
|
this.log.info(`dispatch: skipped ready turn ${msg.msgId} (${readyGate})`);
|
|
218597
219529
|
return null;
|
|
218598
219530
|
}
|
|
@@ -218604,6 +219536,10 @@ var Daemon = class Daemon {
|
|
|
218604
219536
|
const { stopReason, usage } = await host.prompt(sessionId, blocks);
|
|
218605
219537
|
if (p.outputSuppressed) {
|
|
218606
219538
|
if (p.outputSuppressed === "loop protection") finalPhase = "problem";
|
|
219539
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219540
|
+
sessionId,
|
|
219541
|
+
reason: p.outputSuppressed
|
|
219542
|
+
}, entry);
|
|
218607
219543
|
return null;
|
|
218608
219544
|
}
|
|
218609
219545
|
if (usage) {
|
|
@@ -218658,6 +219594,10 @@ var Daemon = class Daemon {
|
|
|
218658
219594
|
this.clearIdle(p);
|
|
218659
219595
|
if (p.outputSuppressed) {
|
|
218660
219596
|
if (p.outputSuppressed === "loop protection") finalPhase = "problem";
|
|
219597
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219598
|
+
sessionId,
|
|
219599
|
+
reason: p.outputSuppressed
|
|
219600
|
+
}, entry);
|
|
218661
219601
|
return null;
|
|
218662
219602
|
}
|
|
218663
219603
|
finalPhase = "problem";
|
|
@@ -218695,6 +219635,10 @@ var Daemon = class Daemon {
|
|
|
218695
219635
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218696
219636
|
await p.applyChain;
|
|
218697
219637
|
}
|
|
219638
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219639
|
+
sessionId,
|
|
219640
|
+
reason: "turn_failed"
|
|
219641
|
+
}, entry);
|
|
218698
219642
|
throw err;
|
|
218699
219643
|
} finally {
|
|
218700
219644
|
this.clearCancelBackstop(agentId, sessionId);
|
|
@@ -218704,6 +219648,7 @@ var Daemon = class Daemon {
|
|
|
218704
219648
|
this.releasePermissions(agentId, sessionId);
|
|
218705
219649
|
this.releaseElicits(agentId, sessionId);
|
|
218706
219650
|
if (callMeta) this.activeTurnCallMeta.delete(key);
|
|
219651
|
+
if (activeGithub && this.activeGithubTurnMeta.get(key) === activeGithub) this.activeGithubTurnMeta.delete(key);
|
|
218707
219652
|
const githubFinal = p.github && !p.outputSuppressed && finalPhase === "end" ? p.github.collector.finalText(true) : void 0;
|
|
218708
219653
|
if (p.github?.deferredFinalTranscript && githubFinal?.trim()) this.store.appendTranscript({
|
|
218709
219654
|
channel: p.channel,
|
|
@@ -218713,7 +219658,19 @@ var Daemon = class Daemon {
|
|
|
218713
219658
|
kind: "text",
|
|
218714
219659
|
text: githubFinal
|
|
218715
219660
|
});
|
|
218716
|
-
if (p.github && !p.outputSuppressed)
|
|
219661
|
+
if (p.github && !p.outputSuppressed) {
|
|
219662
|
+
let recordedInFlight = false;
|
|
219663
|
+
try {
|
|
219664
|
+
this.persistHookState(entry, "in_flight", true);
|
|
219665
|
+
recordedInFlight = true;
|
|
219666
|
+
} catch (err) {
|
|
219667
|
+
this.log.warn(`github poster: durability barrier failed; final publish skipped (${formatErr(err)})`);
|
|
219668
|
+
}
|
|
219669
|
+
if (recordedInFlight) {
|
|
219670
|
+
await p.github.poster.publish(githubFinal).catch((err) => this.log.warn(`github poster: final publish failed (${formatErr(err)})`));
|
|
219671
|
+
this.persistHookState(entry, "settled");
|
|
219672
|
+
}
|
|
219673
|
+
}
|
|
218717
219674
|
this.pending.delete(pendingTurnKey(agentId, sessionId));
|
|
218718
219675
|
this.store.setSessionState(key, "idle", this.clock.now());
|
|
218719
219676
|
this.emitSessionMetadataSnapshot({
|
|
@@ -218726,6 +219683,7 @@ var Daemon = class Daemon {
|
|
|
218726
219683
|
});
|
|
218727
219684
|
p.resolveDone();
|
|
218728
219685
|
}
|
|
219686
|
+
if (hookContext) this.emitHookCompletion(hookContext, "success", { sessionId }, entry);
|
|
218729
219687
|
return sessionId;
|
|
218730
219688
|
}
|
|
218731
219689
|
/** Interrupt a session by its LOGICAL sessionKey (shared by pause, `!stop`, `!cancel`,
|
|
@@ -218747,6 +219705,7 @@ var Daemon = class Daemon {
|
|
|
218747
219705
|
this.serialQueue.delete(key);
|
|
218748
219706
|
for (const e of queued) {
|
|
218749
219707
|
this.terminateQueuedSink(e, opts.dropQueued ? void 0 : reason);
|
|
219708
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: opts.dropQueued ? "dropped" : "cancelled" }, e);
|
|
218750
219709
|
this.removeInbox(e);
|
|
218751
219710
|
if (opts.dropQueued) e.resolve(null);
|
|
218752
219711
|
else e.reject(new FailStopError(key));
|
|
@@ -220139,49 +221098,92 @@ var Daemon = class Daemon {
|
|
|
220139
221098
|
const purgedPausedAgents = /* @__PURE__ */ new Set();
|
|
220140
221099
|
const purgedLoopScopes = /* @__PURE__ */ new Set();
|
|
220141
221100
|
for (const row of rows) {
|
|
221101
|
+
if (row.completedAt !== null && row.completedAt !== void 0) continue;
|
|
220142
221102
|
if (this.liveInboxIds.has(row.id)) continue;
|
|
220143
221103
|
if (!this.agents.has(row.agentId)) {
|
|
220144
221104
|
this.log.warn(`durable inbox: skipping replay of ${row.id} — unknown agent "${row.agentId}"`);
|
|
220145
221105
|
continue;
|
|
220146
221106
|
}
|
|
220147
|
-
if (this.paused(row.agentId)) {
|
|
220148
|
-
if (!purgedPausedAgents.has(row.agentId)) {
|
|
220149
|
-
purgedPausedAgents.add(row.agentId);
|
|
220150
|
-
this.purgeAgentInbox(row.agentId);
|
|
220151
|
-
}
|
|
220152
|
-
continue;
|
|
220153
|
-
}
|
|
220154
221107
|
let msg;
|
|
220155
221108
|
let callMeta;
|
|
221109
|
+
let hookContext;
|
|
220156
221110
|
try {
|
|
220157
221111
|
msg = JSON.parse(row.msg);
|
|
220158
221112
|
callMeta = row.callMeta ? JSON.parse(row.callMeta) : void 0;
|
|
221113
|
+
hookContext = row.hookContext ? JSON.parse(row.hookContext) : void 0;
|
|
220159
221114
|
} catch (err) {
|
|
220160
221115
|
this.log.warn(`durable inbox: skipping corrupt row ${row.id}: ${err.message}`);
|
|
220161
221116
|
continue;
|
|
220162
221117
|
}
|
|
221118
|
+
if (msg.source === "hook" && !hookContext) {
|
|
221119
|
+
this.log.warn(`durable inbox: tombstoning legacy hook row ${row.id} without trusted context`);
|
|
221120
|
+
this.store.removeInbox(row.id);
|
|
221121
|
+
continue;
|
|
221122
|
+
}
|
|
221123
|
+
if (this.paused(row.agentId)) {
|
|
221124
|
+
if (!purgedPausedAgents.has(row.agentId)) {
|
|
221125
|
+
purgedPausedAgents.add(row.agentId);
|
|
221126
|
+
this.purgeAgentInbox(row.agentId);
|
|
221127
|
+
}
|
|
221128
|
+
if (hookContext) this.emitHookCompletion(hookContext, "failed", { reason: "pause" }, { inboxId: row.id });
|
|
221129
|
+
continue;
|
|
221130
|
+
}
|
|
220163
221131
|
if (isMalformedPlatformTurn(msg) && !msg.isDm) {
|
|
220164
221132
|
this.store.removeInbox(row.id);
|
|
220165
221133
|
continue;
|
|
220166
221134
|
}
|
|
220167
221135
|
const loopScope = loopGuardScope(msg);
|
|
220168
|
-
if (usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopScope)) {
|
|
221136
|
+
if ((usesLoopGuard(msg) || hookContext !== void 0) && this.store.isLoopGuardOpen(loopScope)) {
|
|
220169
221137
|
if (!purgedLoopScopes.has(loopScope)) {
|
|
220170
221138
|
purgedLoopScopes.add(loopScope);
|
|
220171
221139
|
this.purgeLoopScopeInbox(loopScope);
|
|
220172
221140
|
}
|
|
221141
|
+
if (hookContext) this.emitHookCompletion(hookContext, "failed", { reason: "loop protection" }, { inboxId: row.id });
|
|
220173
221142
|
continue;
|
|
220174
221143
|
}
|
|
221144
|
+
const posterPublishState = row.posterPublishState === "in_flight" || row.posterPublishState === "settled" ? row.posterPublishState : hookContext?.githubReply ? "not_started" : void 0;
|
|
220175
221145
|
this.dispatch(row.agentId, msg, row.integrationId ?? void 0, void 0, callMeta, {
|
|
220176
221146
|
...row.isQueueCmd ? { isQueueCmd: true } : {},
|
|
220177
221147
|
fromInboxReplay: true,
|
|
220178
221148
|
inboxReplayId: row.id,
|
|
220179
|
-
replay: row.loopGuardCounted === 1
|
|
220180
|
-
|
|
221149
|
+
replay: row.loopGuardCounted === 1,
|
|
221150
|
+
adoptExistingInbox: true,
|
|
221151
|
+
...hookContext ? { requireDurable: true } : {}
|
|
221152
|
+
}, hookContext?.githubReply, hookContext, posterPublishState).catch(() => {});
|
|
220181
221153
|
replayed++;
|
|
220182
221154
|
}
|
|
220183
221155
|
if (replayed) this.log.info(`durable inbox: replayed ${replayed} admitted message(s) through the serial gate`);
|
|
220184
221156
|
}
|
|
221157
|
+
/** Re-assert retained metadata-only hook completions whenever the CP socket
|
|
221158
|
+
* becomes ready. HookRepo applies them idempotently; keeping the receipt also
|
|
221159
|
+
* closes the completed-before-restart relay redelivery window. */
|
|
221160
|
+
replayHookTerminalReports() {
|
|
221161
|
+
let rows;
|
|
221162
|
+
try {
|
|
221163
|
+
rows = this.store.listInboxBySessionKeyFifo();
|
|
221164
|
+
} catch (err) {
|
|
221165
|
+
this.log.warn(`durable inbox: terminal report read failed: ${formatErr(err)}`);
|
|
221166
|
+
this.scheduleHookReportRetry();
|
|
221167
|
+
return;
|
|
221168
|
+
}
|
|
221169
|
+
const pending = rows.filter((row) => row.terminalReport && row.legacyReportConnection !== this.hookReportConnectionId && !this.hookReportInflight.has(row.id));
|
|
221170
|
+
const available = Math.max(0, MAX_HOOK_REPORT_INFLIGHT - this.hookReportInflight.size);
|
|
221171
|
+
const batch = pending.slice(0, available);
|
|
221172
|
+
let emitted = 0;
|
|
221173
|
+
for (const row of batch) try {
|
|
221174
|
+
const decoded = HookReport.parse(JSON.parse(row.terminalReport));
|
|
221175
|
+
this.sendHookReport(decoded, row.id);
|
|
221176
|
+
emitted += 1;
|
|
221177
|
+
} catch (err) {
|
|
221178
|
+
this.log.warn(`durable inbox: corrupt terminal hook receipt ${row.id}: ${formatErr(err)}`);
|
|
221179
|
+
try {
|
|
221180
|
+
this.store.acknowledgeHookInbox(row.id);
|
|
221181
|
+
} catch (cleanupError) {
|
|
221182
|
+
this.log.warn(`durable inbox: corrupt receipt cleanup failed for ${row.id}: ${formatErr(cleanupError)}`);
|
|
221183
|
+
}
|
|
221184
|
+
}
|
|
221185
|
+
if (emitted) this.log.info(`durable inbox: re-emitted ${emitted} terminal hook report(s)`);
|
|
221186
|
+
}
|
|
220185
221187
|
/** daemon/restart + daemon/upgrade (§8.3): ack now, then drain + stop + exit so
|
|
220186
221188
|
* the supervisor relaunches (the new binary, for upgrade). */
|
|
220187
221189
|
scheduleFleetExit(kind, targetVersion) {
|
|
@@ -220438,7 +221440,7 @@ var Daemon = class Daemon {
|
|
|
220438
221440
|
* still runs; a `headless` msg additionally suppresses all platform output
|
|
220439
221441
|
* in dispatch. Resolves with dispatch's sessionId (null = gate-dropped).
|
|
220440
221442
|
*/
|
|
220441
|
-
async
|
|
221443
|
+
async anchorTrigger(agentId, msg, target, anchorText, label) {
|
|
220442
221444
|
if (this.draining || this.drainingAgents.has(agentId) || this.paused(agentId) || this.safetyDrainingAgents.has(agentId)) {
|
|
220443
221445
|
this.log.info(`${label}: skipped for agent "${agentId}" (paused or draining)`);
|
|
220444
221446
|
return null;
|
|
@@ -220456,7 +221458,12 @@ var Daemon = class Daemon {
|
|
|
220456
221458
|
this.log.warn(`${label}: failed to post trigger to ${target.channel} (${formatErr(err)}) — running without anchor`);
|
|
220457
221459
|
}
|
|
220458
221460
|
}
|
|
220459
|
-
return
|
|
221461
|
+
return msg;
|
|
221462
|
+
}
|
|
221463
|
+
async fireTrigger(agentId, msg, target, anchorText, label, githubReply, hookContext) {
|
|
221464
|
+
const anchored = await this.anchorTrigger(agentId, msg, target, anchorText, label);
|
|
221465
|
+
if (!anchored) return null;
|
|
221466
|
+
return this.dispatch(agentId, anchored, target?.integrationId, void 0, void 0, void 0, githubReply, hookContext);
|
|
220460
221467
|
}
|
|
220461
221468
|
/** Build the per-turn GitHub final-answer selector and poster, tokened
|
|
220462
221469
|
* via the repo-targeted gitcred mint (issues/PR write, no contents — never
|
|
@@ -220590,6 +221597,8 @@ var Daemon = class Daemon {
|
|
|
220590
221597
|
mcpServerFacts: () => this.mcpServerFacts,
|
|
220591
221598
|
onReady: () => {
|
|
220592
221599
|
this.probeRuntimesAndEmit();
|
|
221600
|
+
this.hookReportConnectionId = randomUUID();
|
|
221601
|
+
this.replayHookTerminalReports();
|
|
220593
221602
|
for (const [integrationId, channels] of this.channelSnapshots) this.cpClient?.emitIntegrationChannels({
|
|
220594
221603
|
integrationId,
|
|
220595
221604
|
channels
|
|
@@ -220711,6 +221720,10 @@ var Daemon = class Daemon {
|
|
|
220711
221720
|
this.coldCancelTimers.clear();
|
|
220712
221721
|
for (const t of this.slackRetryTimers.values()) this.clock.clearTimeout(t);
|
|
220713
221722
|
this.slackRetryTimers.clear();
|
|
221723
|
+
if (this.hookReportRetryTimer !== void 0) {
|
|
221724
|
+
this.clock.clearTimeout(this.hookReportRetryTimer);
|
|
221725
|
+
this.hookReportRetryTimer = void 0;
|
|
221726
|
+
}
|
|
220714
221727
|
for (const t of this.orchestrationDeadlines.values()) this.clock.clearTimeout(t);
|
|
220715
221728
|
this.orchestrationDeadlines.clear();
|
|
220716
221729
|
this.metrics?.stop();
|
|
@@ -220772,4 +221785,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
220772
221785
|
//#endregion
|
|
220773
221786
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
220774
221787
|
|
|
220775
|
-
//# sourceMappingURL=run-foreground-
|
|
221788
|
+
//# sourceMappingURL=run-foreground-BLhVJZlp.js.map
|