@agentconnect.md/daemon 1.0.0-rc.323 → 1.0.0-rc.325
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-Diogp5gy.js} +4 -4
- package/dist/{login-D6xoGjQX.js.map → login-Diogp5gy.js.map} +1 -1
- package/dist/run-foreground-BhBLQHcI.js +2 -0
- package/dist/{run-foreground-B5Kfk1KO.js → run-foreground-sX3AL91D.js} +1116 -124
- package/dist/{run-foreground-B5Kfk1KO.js.map → run-foreground-sX3AL91D.js.map} +1 -1
- 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) => {
|
|
@@ -68307,47 +68478,26 @@ function buildElicitationResolvedCard(params, decision) {
|
|
|
68307
68478
|
}
|
|
68308
68479
|
}];
|
|
68309
68480
|
}
|
|
68310
|
-
/** Build the compact in-thread status message
|
|
68311
|
-
*
|
|
68312
|
-
*
|
|
68481
|
+
/** Build the compact in-thread status message as one Slack row: status text with an
|
|
68482
|
+
* inline View Session URL, plus one Configure accessory button. Slack sections allow
|
|
68483
|
+
* only one accessory, and mrkdwn links cannot dispatch block_actions, so Cancel stays in
|
|
68484
|
+
* the Configure modal rather than becoming a non-functional inline link. */
|
|
68313
68485
|
function buildStatusBlocks(info, sessionKey, link) {
|
|
68314
|
-
const elements = [];
|
|
68315
|
-
if (link) elements.push({
|
|
68316
|
-
type: "button",
|
|
68317
|
-
action_id: STATUS_ACTION.view,
|
|
68318
|
-
text: {
|
|
68319
|
-
type: "plain_text",
|
|
68320
|
-
text: "View Session"
|
|
68321
|
-
},
|
|
68322
|
-
url: link
|
|
68323
|
-
});
|
|
68324
|
-
elements.push({
|
|
68325
|
-
type: "button",
|
|
68326
|
-
action_id: STATUS_ACTION.manage,
|
|
68327
|
-
text: {
|
|
68328
|
-
type: "plain_text",
|
|
68329
|
-
text: "Configure"
|
|
68330
|
-
},
|
|
68331
|
-
value: sessionKey
|
|
68332
|
-
}, {
|
|
68333
|
-
type: "button",
|
|
68334
|
-
action_id: STATUS_ACTION.cancel,
|
|
68335
|
-
text: {
|
|
68336
|
-
type: "plain_text",
|
|
68337
|
-
text: "Cancel"
|
|
68338
|
-
},
|
|
68339
|
-
style: "danger",
|
|
68340
|
-
value: sessionKey
|
|
68341
|
-
});
|
|
68342
68486
|
return [{
|
|
68343
68487
|
type: "section",
|
|
68344
68488
|
text: {
|
|
68345
68489
|
type: "mrkdwn",
|
|
68346
|
-
text: renderStatusBar(info)
|
|
68490
|
+
text: link ? `${renderStatusBar(info)} · <${link}|View Session>` : renderStatusBar(info)
|
|
68491
|
+
},
|
|
68492
|
+
accessory: {
|
|
68493
|
+
type: "button",
|
|
68494
|
+
action_id: STATUS_ACTION.manage,
|
|
68495
|
+
text: {
|
|
68496
|
+
type: "plain_text",
|
|
68497
|
+
text: "Configure"
|
|
68498
|
+
},
|
|
68499
|
+
value: sessionKey
|
|
68347
68500
|
}
|
|
68348
|
-
}, {
|
|
68349
|
-
type: "actions",
|
|
68350
|
-
elements
|
|
68351
68501
|
}];
|
|
68352
68502
|
}
|
|
68353
68503
|
/** Format a token count with thousands separators for the modal's usage grid. */
|
|
@@ -197666,7 +197816,10 @@ const UNTRUSTED_CONTENT_BEGIN = "----- BEGIN UNTRUSTED EXTERNAL CONTENT (GitHub
|
|
|
197666
197816
|
const UNTRUSTED_CONTENT_END = "----- END UNTRUSTED EXTERNAL CONTENT -----";
|
|
197667
197817
|
/** channel/thread from the affinity key — see the sessionKey grammar on {@link RdMsgHook}. */
|
|
197668
197818
|
function splitSessionKey(msg) {
|
|
197669
|
-
if (msg.sessionKey === msg.hookId) return {
|
|
197819
|
+
if (msg.sessionKey === msg.hookId) return {
|
|
197820
|
+
channel: msg.hookId,
|
|
197821
|
+
thread: msg.hookId
|
|
197822
|
+
};
|
|
197670
197823
|
if (msg.sessionKey === `${msg.hookId}:${msg.deliveryKey}`) return {
|
|
197671
197824
|
channel: msg.hookId,
|
|
197672
197825
|
thread: msg.deliveryKey
|
|
@@ -197731,7 +197884,7 @@ function neutralizeDelimiters(body) {
|
|
|
197731
197884
|
* Only emitted when the poster will actually run (a thread number exists); push
|
|
197732
197885
|
* fires have no thread, so they never see this. */
|
|
197733
197886
|
function githubReplyHint(c) {
|
|
197734
|
-
return ["", `Your final reply is posted back to ${`${c.repo ?? "this thread"}#${c.number}`} automatically as
|
|
197887
|
+
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
197888
|
}
|
|
197736
197889
|
/** The github-kind turn text: a trusted metadata header + the FENCED excerpt.
|
|
197737
197890
|
* The title rides the header (relay-sanitized to one capped line) — it is
|
|
@@ -198125,6 +198278,227 @@ var GithubFinalPoster = class {
|
|
|
198125
198278
|
}
|
|
198126
198279
|
};
|
|
198127
198280
|
//#endregion
|
|
198281
|
+
//#region src/github/review.ts
|
|
198282
|
+
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
198283
|
+
const MAX_REVIEW_PAGES = 10;
|
|
198284
|
+
const REVIEWS_PER_PAGE = 100;
|
|
198285
|
+
var GithubHttpError = class extends Error {
|
|
198286
|
+
status;
|
|
198287
|
+
constructor(status, message) {
|
|
198288
|
+
super(message);
|
|
198289
|
+
this.status = status;
|
|
198290
|
+
this.name = "GithubHttpError";
|
|
198291
|
+
}
|
|
198292
|
+
};
|
|
198293
|
+
function markerFor(target) {
|
|
198294
|
+
return `<!-- agentconnect-review:${Buffer.from(JSON.stringify({
|
|
198295
|
+
v: 1,
|
|
198296
|
+
hookId: target.hookId,
|
|
198297
|
+
deliveryKey: target.deliveryKey,
|
|
198298
|
+
attemptId: target.attemptId,
|
|
198299
|
+
headSha: target.expectedHeadSha
|
|
198300
|
+
}), "utf8").toString("base64url")} -->`;
|
|
198301
|
+
}
|
|
198302
|
+
function validateInput(input) {
|
|
198303
|
+
if (input.event === "APPROVE" && input.verdict !== "pass") return "APPROVE requires verdict=pass";
|
|
198304
|
+
if (input.event === "REQUEST_CHANGES" && input.verdict !== "fail") return "REQUEST_CHANGES requires verdict=fail";
|
|
198305
|
+
if ((input.event === "COMMENT" || input.event === "REQUEST_CHANGES") && !input.body.trim()) return `${input.event} requires a non-empty body`;
|
|
198306
|
+
for (const [index, comment] of (input.comments ?? []).entries()) {
|
|
198307
|
+
if (!comment.path.trim()) return `comments[${index}].path is required`;
|
|
198308
|
+
if (!comment.body.trim()) return `comments[${index}].body is required`;
|
|
198309
|
+
if (!Number.isInteger(comment.line) || comment.line <= 0) return `comments[${index}].line must be positive`;
|
|
198310
|
+
if (comment.startLine !== void 0) {
|
|
198311
|
+
if (!Number.isInteger(comment.startLine) || comment.startLine <= 0 || comment.startLine > comment.line) return `comments[${index}].startLine must be positive and no greater than line`;
|
|
198312
|
+
if (!comment.startSide) return `comments[${index}].startSide is required with startLine`;
|
|
198313
|
+
}
|
|
198314
|
+
}
|
|
198315
|
+
}
|
|
198316
|
+
function safeJson(text) {
|
|
198317
|
+
if (!text) return void 0;
|
|
198318
|
+
return JSON.parse(text.replace(/"id"\s*:\s*(\d{15,})/g, "\"id\":\"$1\""));
|
|
198319
|
+
}
|
|
198320
|
+
/** One lexical-scope client; it never caches tokens or targets. */
|
|
198321
|
+
var GithubReviewClient = class {
|
|
198322
|
+
fetchImpl;
|
|
198323
|
+
baseUrl;
|
|
198324
|
+
timeoutMs;
|
|
198325
|
+
constructor(deps = {}) {
|
|
198326
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
198327
|
+
this.baseUrl = (deps.baseUrl ?? "https://api.github.com").replace(/\/+$/, "");
|
|
198328
|
+
this.timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
198329
|
+
}
|
|
198330
|
+
async getPull(token, repoFullName, pullNumber) {
|
|
198331
|
+
const pull = await this.request(`/repos/${repoFullName}/pulls/${pullNumber}`, token, "GET");
|
|
198332
|
+
const headSha = pull.head?.sha;
|
|
198333
|
+
const baseSha = pull.base?.sha;
|
|
198334
|
+
if (!headSha || !baseSha) throw new GithubHttpError(502, "GitHub pull response omitted head/base SHA");
|
|
198335
|
+
return {
|
|
198336
|
+
headSha,
|
|
198337
|
+
baseSha,
|
|
198338
|
+
draft: pull.draft === true,
|
|
198339
|
+
state: pull.state ?? "unknown",
|
|
198340
|
+
merged: pull.merged === true
|
|
198341
|
+
};
|
|
198342
|
+
}
|
|
198343
|
+
async submit(target, input) {
|
|
198344
|
+
const invalid = validateInput(input);
|
|
198345
|
+
if (invalid) return {
|
|
198346
|
+
state: "not_submitted",
|
|
198347
|
+
code: "invalid_input",
|
|
198348
|
+
message: invalid
|
|
198349
|
+
};
|
|
198350
|
+
const marker = markerFor(target);
|
|
198351
|
+
try {
|
|
198352
|
+
let existing;
|
|
198353
|
+
try {
|
|
198354
|
+
existing = await this.findByMarker(target, marker);
|
|
198355
|
+
} catch (err) {
|
|
198356
|
+
if (target.recovering) return {
|
|
198357
|
+
state: "ambiguous",
|
|
198358
|
+
code: "ambiguous_write",
|
|
198359
|
+
message: "cannot reconcile the prior formal review attempt; automatic retry is blocked"
|
|
198360
|
+
};
|
|
198361
|
+
throw err;
|
|
198362
|
+
}
|
|
198363
|
+
if (existing) return this.submitted(existing, target, input);
|
|
198364
|
+
const pull = await this.getPull(target.token, target.repoFullName, target.pullNumber);
|
|
198365
|
+
if (pull.state !== "open" || pull.merged || pull.draft) return {
|
|
198366
|
+
state: "not_submitted",
|
|
198367
|
+
code: "pull_unavailable",
|
|
198368
|
+
message: `pull request is ${pull.merged ? "merged" : pull.draft ? "draft" : pull.state}`
|
|
198369
|
+
};
|
|
198370
|
+
if (pull.headSha !== target.expectedHeadSha || pull.baseSha !== target.expectedBaseSha) return {
|
|
198371
|
+
state: "not_submitted",
|
|
198372
|
+
code: "revision_changed",
|
|
198373
|
+
message: "pull request head/base changed while the review was running"
|
|
198374
|
+
};
|
|
198375
|
+
const body = input.body.trim() ? `${input.body}\n\n${marker}` : marker;
|
|
198376
|
+
try {
|
|
198377
|
+
const created = await this.request(`/repos/${target.repoFullName}/pulls/${target.pullNumber}/reviews`, target.token, "POST", {
|
|
198378
|
+
commit_id: target.expectedHeadSha,
|
|
198379
|
+
event: input.event,
|
|
198380
|
+
body,
|
|
198381
|
+
...input.comments?.length ? { comments: input.comments.map((comment) => ({
|
|
198382
|
+
path: comment.path,
|
|
198383
|
+
body: comment.body,
|
|
198384
|
+
line: comment.line,
|
|
198385
|
+
side: comment.side,
|
|
198386
|
+
...comment.startLine !== void 0 ? { start_line: comment.startLine } : {},
|
|
198387
|
+
...comment.startSide !== void 0 ? { start_side: comment.startSide } : {}
|
|
198388
|
+
})) } : {}
|
|
198389
|
+
});
|
|
198390
|
+
return this.submitted(created, target, input);
|
|
198391
|
+
} catch (err) {
|
|
198392
|
+
if (err instanceof GithubHttpError && err.status >= 400 && err.status < 500) return {
|
|
198393
|
+
state: "not_submitted",
|
|
198394
|
+
code: "github_rejected",
|
|
198395
|
+
message: err.message
|
|
198396
|
+
};
|
|
198397
|
+
const recovered = await this.findByMarker(target, marker).catch(() => void 0);
|
|
198398
|
+
if (recovered) return this.submitted(recovered, target, input);
|
|
198399
|
+
return {
|
|
198400
|
+
state: "ambiguous",
|
|
198401
|
+
code: "ambiguous_write",
|
|
198402
|
+
message: "GitHub review outcome is unknown; automatic retry is blocked"
|
|
198403
|
+
};
|
|
198404
|
+
}
|
|
198405
|
+
} catch (err) {
|
|
198406
|
+
return {
|
|
198407
|
+
state: "not_submitted",
|
|
198408
|
+
code: "github_rejected",
|
|
198409
|
+
message: err instanceof Error ? err.message : String(err)
|
|
198410
|
+
};
|
|
198411
|
+
}
|
|
198412
|
+
}
|
|
198413
|
+
/** Read-only recovery for an attempt whose POST outcome was ambiguous. This
|
|
198414
|
+
* is safe to run automatically before a replayed model turn: it can converge
|
|
198415
|
+
* a now-visible marker, but never issues a second review mutation. */
|
|
198416
|
+
async reconcile(target, event, verdict) {
|
|
198417
|
+
const marker = markerFor(target);
|
|
198418
|
+
try {
|
|
198419
|
+
const existing = await this.findByMarker(target, marker);
|
|
198420
|
+
if (existing) return this.submitted(existing, target, {
|
|
198421
|
+
event,
|
|
198422
|
+
verdict,
|
|
198423
|
+
body: ""
|
|
198424
|
+
});
|
|
198425
|
+
return {
|
|
198426
|
+
state: "ambiguous",
|
|
198427
|
+
code: "ambiguous_write",
|
|
198428
|
+
message: "the prior formal review marker is not visible yet; automatic mutation retry is blocked"
|
|
198429
|
+
};
|
|
198430
|
+
} catch {
|
|
198431
|
+
return {
|
|
198432
|
+
state: "ambiguous",
|
|
198433
|
+
code: "ambiguous_write",
|
|
198434
|
+
message: "cannot reconcile the prior formal review attempt; automatic mutation retry is blocked"
|
|
198435
|
+
};
|
|
198436
|
+
}
|
|
198437
|
+
}
|
|
198438
|
+
submitted(review, target, input) {
|
|
198439
|
+
if (review.id === void 0 || review.id === null) return {
|
|
198440
|
+
state: "ambiguous",
|
|
198441
|
+
code: "ambiguous_write",
|
|
198442
|
+
message: "GitHub created/recovered a review without an id"
|
|
198443
|
+
};
|
|
198444
|
+
if (!review.commit_id) return {
|
|
198445
|
+
state: "ambiguous",
|
|
198446
|
+
code: "ambiguous_write",
|
|
198447
|
+
message: "GitHub created/recovered a review without its commit id"
|
|
198448
|
+
};
|
|
198449
|
+
const commitId = review.commit_id;
|
|
198450
|
+
if (commitId !== target.expectedHeadSha) return {
|
|
198451
|
+
state: "ambiguous",
|
|
198452
|
+
code: "ambiguous_write",
|
|
198453
|
+
message: "recovered review is anchored to an unexpected commit"
|
|
198454
|
+
};
|
|
198455
|
+
return {
|
|
198456
|
+
state: "submitted",
|
|
198457
|
+
reviewId: String(review.id),
|
|
198458
|
+
event: input.event,
|
|
198459
|
+
verdict: input.verdict,
|
|
198460
|
+
commitId
|
|
198461
|
+
};
|
|
198462
|
+
}
|
|
198463
|
+
async findByMarker(target, marker) {
|
|
198464
|
+
for (let page = 1; page <= MAX_REVIEW_PAGES; page += 1) {
|
|
198465
|
+
const rows = await this.request(`/repos/${target.repoFullName}/pulls/${target.pullNumber}/reviews?per_page=${REVIEWS_PER_PAGE}&page=${page}`, target.token, "GET");
|
|
198466
|
+
const hit = rows.find((review) => review.body?.includes(marker));
|
|
198467
|
+
if (hit) return hit;
|
|
198468
|
+
if (rows.length < REVIEWS_PER_PAGE) return void 0;
|
|
198469
|
+
}
|
|
198470
|
+
throw new Error("review marker reconciliation exceeded pagination bound");
|
|
198471
|
+
}
|
|
198472
|
+
async request(path, token, method, body) {
|
|
198473
|
+
let response;
|
|
198474
|
+
try {
|
|
198475
|
+
response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
198476
|
+
method,
|
|
198477
|
+
headers: {
|
|
198478
|
+
authorization: `Bearer ${token}`,
|
|
198479
|
+
accept: "application/vnd.github+json",
|
|
198480
|
+
"x-github-api-version": "2022-11-28",
|
|
198481
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
198482
|
+
},
|
|
198483
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
198484
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
198485
|
+
});
|
|
198486
|
+
} catch (err) {
|
|
198487
|
+
throw new Error(`GitHub request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198488
|
+
}
|
|
198489
|
+
const text = await response.text();
|
|
198490
|
+
if (!response.ok) {
|
|
198491
|
+
let detail = "";
|
|
198492
|
+
try {
|
|
198493
|
+
const parsed = safeJson(text);
|
|
198494
|
+
detail = parsed?.message ? `: ${parsed.message}` : "";
|
|
198495
|
+
} catch {}
|
|
198496
|
+
throw new GithubHttpError(response.status, `GitHub ${method} ${response.status}${detail}`);
|
|
198497
|
+
}
|
|
198498
|
+
return safeJson(text);
|
|
198499
|
+
}
|
|
198500
|
+
};
|
|
198501
|
+
//#endregion
|
|
198128
198502
|
//#region src/runtimes/runtime-prober.ts
|
|
198129
198503
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
198130
198504
|
const DEFAULT_CONCURRENCY = 3;
|
|
@@ -198358,7 +198732,7 @@ var RelayClient = class {
|
|
|
198358
198732
|
const frame = decoded.frame;
|
|
198359
198733
|
if (frame.corr && this.correlator.settle(frame)) return;
|
|
198360
198734
|
if (frame.type === "rd/msg") {
|
|
198361
|
-
this.handleMsg(frame.id, frame.payload);
|
|
198735
|
+
await this.handleMsg(frame.id, frame.payload);
|
|
198362
198736
|
return;
|
|
198363
198737
|
}
|
|
198364
198738
|
if (frame.type === "rd/agentmsg/fwd") {
|
|
@@ -198370,9 +198744,9 @@ var RelayClient = class {
|
|
|
198370
198744
|
/** An inbound rd/msg: dispatch it, reply `rd/ack`; only a webchat op streams
|
|
198371
198745
|
* `rd/chat` back over this socket. A shared-bot `im` replies out-of-band via the
|
|
198372
198746
|
* daemon's own Slack send, and a hook fire has no reply stream — both get a noop. */
|
|
198373
|
-
handleMsg(reqId, msg) {
|
|
198747
|
+
async handleMsg(reqId, msg) {
|
|
198374
198748
|
const chat = msg.source === "webchat" ? (event) => this.sendChat(msg.chatId, event) : () => void 0;
|
|
198375
|
-
const ack = this.deps.onRelayMsg(msg, chat);
|
|
198749
|
+
const ack = await this.deps.onRelayMsg(msg, chat);
|
|
198376
198750
|
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
|
|
198377
198751
|
}
|
|
198378
198752
|
/** A forwarded cross-daemon agent-call: terminal-verify + dispatch, reply the typed
|
|
@@ -215511,10 +215885,35 @@ function isBuiltinSystemTool(params) {
|
|
|
215511
215885
|
tc?.toolCallId
|
|
215512
215886
|
].some((id) => typeof id === "string" && [...BUILTIN_TOOL_FQNS].some((fqn) => id.includes(fqn)));
|
|
215513
215887
|
}
|
|
215888
|
+
function hookSnapshot(msg) {
|
|
215889
|
+
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;
|
|
215890
|
+
return {
|
|
215891
|
+
configRevision: msg.configRevision,
|
|
215892
|
+
dispatchRevision: msg.dispatchRevision,
|
|
215893
|
+
dispatchDaemonId: msg.dispatchDaemonId,
|
|
215894
|
+
reviewPolicy: msg.reviewPolicy,
|
|
215895
|
+
reportingMode: msg.reportingMode,
|
|
215896
|
+
gateMode: msg.gateMode
|
|
215897
|
+
};
|
|
215898
|
+
}
|
|
215899
|
+
function reviewPolicyAllows(policy, event) {
|
|
215900
|
+
if (policy === "full") return true;
|
|
215901
|
+
if (policy === "request_changes") return event === "COMMENT" || event === "REQUEST_CHANGES";
|
|
215902
|
+
if (policy === "comment") return event === "COMMENT";
|
|
215903
|
+
return false;
|
|
215904
|
+
}
|
|
215905
|
+
function reviewResultForWire(effect) {
|
|
215906
|
+
if (effect.state === "submitted") return effect;
|
|
215907
|
+
return {
|
|
215908
|
+
state: effect.state,
|
|
215909
|
+
code: effect.code
|
|
215910
|
+
};
|
|
215911
|
+
}
|
|
215514
215912
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
215515
215913
|
const LOOP_GUARD_WINDOW_MS = 6e4;
|
|
215516
215914
|
const MAX_AUTOMATIC_TURNS_PER_WINDOW = 8;
|
|
215517
215915
|
const MAX_TOTAL_TURNS_PER_WINDOW = 60;
|
|
215916
|
+
const MAX_HOOK_REPORT_INFLIGHT = 100;
|
|
215518
215917
|
/** Thrown to a `dispatch()` caller when the per-session admission queue is at its depth
|
|
215519
215918
|
* cap (§4.4 backpressure): the message is fast-failed, not buffered. Carries a stable
|
|
215520
215919
|
* `reason` so an agent-call source (P1/P2) can surface a typed `queue_full`. */
|
|
@@ -215610,6 +216009,22 @@ function usesLoopGuard(msg) {
|
|
|
215610
216009
|
function isMalformedPlatformTurn(msg) {
|
|
215611
216010
|
return msg.platform === "slack" && msg.source === "user" && !msg.sender.isBot && msg.sender.id === "unknown" && msg.text.trim() === "" && (msg.attachments?.length ?? 0) === 0;
|
|
215612
216011
|
}
|
|
216012
|
+
function authorizedReviewTargetMatches(active, attemptId, authorized) {
|
|
216013
|
+
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;
|
|
216014
|
+
}
|
|
216015
|
+
function authorizedReviewTarget(active, attemptId, authorized, recovering = false) {
|
|
216016
|
+
return {
|
|
216017
|
+
token: authorized.token,
|
|
216018
|
+
repoFullName: authorized.repoFullName,
|
|
216019
|
+
pullNumber: authorized.pullNumber,
|
|
216020
|
+
expectedHeadSha: authorized.expectedHeadSha,
|
|
216021
|
+
expectedBaseSha: authorized.expectedBaseSha,
|
|
216022
|
+
hookId: active.hook.hookId,
|
|
216023
|
+
deliveryKey: active.hook.deliveryKey,
|
|
216024
|
+
attemptId,
|
|
216025
|
+
...recovering ? { recovering: true } : {}
|
|
216026
|
+
};
|
|
216027
|
+
}
|
|
215613
216028
|
function isSlackStatusBarText(text) {
|
|
215614
216029
|
return text.startsWith(":bar_chart:") || text.startsWith("📊");
|
|
215615
216030
|
}
|
|
@@ -215694,6 +216109,10 @@ var Daemon = class Daemon {
|
|
|
215694
216109
|
botUserIds = {};
|
|
215695
216110
|
cpRouting;
|
|
215696
216111
|
cpCollab = new CpCollabRoutes();
|
|
216112
|
+
/** One active PR hook turn per logical session key. Long-lived ACP sessions
|
|
216113
|
+
* may span deliveries, so authorization must never live in SessionContext. */
|
|
216114
|
+
activeGithubTurnMeta = /* @__PURE__ */ new Map();
|
|
216115
|
+
githubReviewClient = new GithubReviewClient();
|
|
215697
216116
|
clock;
|
|
215698
216117
|
requestExit;
|
|
215699
216118
|
draining = false;
|
|
@@ -215717,6 +216136,9 @@ var Daemon = class Daemon {
|
|
|
215717
216136
|
cancelTimers = /* @__PURE__ */ new Map();
|
|
215718
216137
|
coldCancelTimers = /* @__PURE__ */ new Map();
|
|
215719
216138
|
slackRetryTimers = /* @__PURE__ */ new Map();
|
|
216139
|
+
hookReportRetryTimer;
|
|
216140
|
+
hookReportInflight = /* @__PURE__ */ new Set();
|
|
216141
|
+
hookReportConnectionId = randomUUID();
|
|
215720
216142
|
slackRetryRuns = /* @__PURE__ */ new Map();
|
|
215721
216143
|
constructor(opts = {}) {
|
|
215722
216144
|
this.opts = opts;
|
|
@@ -215846,6 +216268,7 @@ var Daemon = class Daemon {
|
|
|
215846
216268
|
startOrchestration: (req) => this.startOrchestration(req),
|
|
215847
216269
|
getOrchestration: (req) => Promise.resolve(this.getOrchestrationForOwner(req)),
|
|
215848
216270
|
cancelOrchestration: (req) => Promise.resolve(this.cancelOrchestrationForOwner(req)),
|
|
216271
|
+
submitGithubReview: (req) => this.submitGithubReview(req),
|
|
215849
216272
|
memory: this.memory,
|
|
215850
216273
|
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
215851
216274
|
channel,
|
|
@@ -215867,6 +216290,7 @@ var Daemon = class Daemon {
|
|
|
215867
216290
|
mcpServersFor: ({ agent, platform, channel, thread, integrationId, isDm }) => {
|
|
215868
216291
|
const servers = [];
|
|
215869
216292
|
let tools = toolsForIntegrations(agent.integrations);
|
|
216293
|
+
tools = [...tools, ...GITHUB_REVIEW_TOOLS];
|
|
215870
216294
|
if (memoryKindOf(agent) === "native") tools = tools.filter((t) => !MEMORY_TOOL_NAMES.has(t.name));
|
|
215871
216295
|
if (tools.length > 0) {
|
|
215872
216296
|
const token = this.mcp.register({
|
|
@@ -216874,6 +217298,10 @@ var Daemon = class Daemon {
|
|
|
216874
217298
|
this.log.debug(`webchat cancel: no in-flight turn for conversation ${conversationId}`);
|
|
216875
217299
|
}
|
|
216876
217300
|
relayMsgAcks = /* @__PURE__ */ new Map();
|
|
217301
|
+
/** Hook admission crosses an async anchor + durable-inbox barrier. Coalesce a
|
|
217302
|
+
* retransmit that arrives before that barrier settles instead of dispatching
|
|
217303
|
+
* the same delivery twice. */
|
|
217304
|
+
pendingRelayMsgAcks = /* @__PURE__ */ new Map();
|
|
216877
217305
|
agentCallDeliveries = /* @__PURE__ */ new Map();
|
|
216878
217306
|
orchestrationDeadlines = /* @__PURE__ */ new Map();
|
|
216879
217307
|
/**
|
|
@@ -216895,7 +217323,26 @@ var Daemon = class Daemon {
|
|
|
216895
217323
|
this.log.debug(`relay: duplicate rd/msg ${dedupKey} — replaying ack (no re-dispatch)`);
|
|
216896
217324
|
return prior;
|
|
216897
217325
|
}
|
|
216898
|
-
const
|
|
217326
|
+
const pending = this.pendingRelayMsgAcks.get(dedupKey);
|
|
217327
|
+
if (pending) return pending;
|
|
217328
|
+
if (msg.source === "hook") {
|
|
217329
|
+
const task = this.dispatchRelayHook(msg).catch((err) => {
|
|
217330
|
+
this.log.error(`hook admission failed for ${dedupKey}: ${formatErr(err)}`);
|
|
217331
|
+
return {
|
|
217332
|
+
msgId: msg.msgId,
|
|
217333
|
+
accepted: false,
|
|
217334
|
+
reason: "durability"
|
|
217335
|
+
};
|
|
217336
|
+
}).then((ack) => {
|
|
217337
|
+
this.pendingRelayMsgAcks.delete(dedupKey);
|
|
217338
|
+
if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
|
|
217339
|
+
this.relayMsgAcks.set(dedupKey, ack);
|
|
217340
|
+
return ack;
|
|
217341
|
+
});
|
|
217342
|
+
this.pendingRelayMsgAcks.set(dedupKey, task);
|
|
217343
|
+
return task;
|
|
217344
|
+
}
|
|
217345
|
+
const ack = msg.source === "webchat" ? this.dispatchRelayOp(msg, chat) : this.handleRelayIm(msg);
|
|
216899
217346
|
if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
|
|
216900
217347
|
this.relayMsgAcks.set(dedupKey, ack);
|
|
216901
217348
|
return ack;
|
|
@@ -217397,9 +217844,10 @@ var Daemon = class Daemon {
|
|
|
217397
217844
|
* Ack-verdict gate for one hook fire (`rd/msg` hook member) — the mirror of
|
|
217398
217845
|
* {@link dispatchWebchatTurn}'s synchronous gates: the relay's rc/run-report
|
|
217399
217846
|
* needs a REASONED rejection now, not a silently dropped fire-and-forget
|
|
217400
|
-
* dispatch. Accepted
|
|
217847
|
+
* dispatch. Accepted is returned only after {@link onHookFire} has crossed
|
|
217848
|
+
* the durable-inbox admission barrier; the model turn itself remains async.
|
|
217401
217849
|
*/
|
|
217402
|
-
dispatchRelayHook(msg) {
|
|
217850
|
+
async dispatchRelayHook(msg) {
|
|
217403
217851
|
if (!this.agents.has(msg.agentId)) {
|
|
217404
217852
|
this.log.warn(`hook: no agent "${msg.agentId}" on this daemon — rejecting fire ${msg.msgId}`);
|
|
217405
217853
|
return {
|
|
@@ -217432,10 +217880,11 @@ var Daemon = class Daemon {
|
|
|
217432
217880
|
reason: "draining"
|
|
217433
217881
|
};
|
|
217434
217882
|
}
|
|
217435
|
-
|
|
217883
|
+
const admission = await this.onHookFire(msg);
|
|
217436
217884
|
return {
|
|
217437
217885
|
msgId: msg.msgId,
|
|
217438
|
-
accepted:
|
|
217886
|
+
accepted: admission.accepted,
|
|
217887
|
+
...admission.reason ? { reason: admission.reason } : {}
|
|
217439
217888
|
};
|
|
217440
217889
|
}
|
|
217441
217890
|
/**
|
|
@@ -217448,25 +217897,36 @@ var Daemon = class Daemon {
|
|
|
217448
217897
|
* without one the fire runs headless.
|
|
217449
217898
|
*/
|
|
217450
217899
|
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
217900
|
const nmsg = buildHookMessage(msg, randomUUID());
|
|
217901
|
+
if (this.store.hasInbox(nmsg.msgId)) {
|
|
217902
|
+
this.log.debug(`hook: durable duplicate ${nmsg.msgId} — replaying accepted admission`);
|
|
217903
|
+
return { accepted: true };
|
|
217904
|
+
}
|
|
217905
|
+
const snapshot = hookSnapshot(msg);
|
|
217906
|
+
const hookContext = {
|
|
217907
|
+
hookId: msg.hookId,
|
|
217908
|
+
agentId: msg.agentId,
|
|
217909
|
+
deliveryKey: msg.deliveryKey,
|
|
217910
|
+
firedAt: msg.firedAt,
|
|
217911
|
+
...msg.event ? { event: msg.event } : {},
|
|
217912
|
+
...snapshot ? { snapshot } : {},
|
|
217913
|
+
...msg.github ? { github: msg.github } : {}
|
|
217914
|
+
};
|
|
217463
217915
|
const c = msg.context;
|
|
217464
217916
|
if (c?.source === "github" && c.action === "deleted" && !msg.target) {
|
|
217465
217917
|
const key = sessionKey(nmsg.platform, nmsg.channel, nmsg.thread ?? nmsg.msgId, msg.agentId);
|
|
217466
217918
|
if (!this.store.getSession(key)?.acpSessionId) {
|
|
217467
217919
|
this.log.info(`hook: skipping github ${c.event ?? "event"}:deleted fire ${msg.msgId} — no session for ${key}`);
|
|
217468
|
-
|
|
217469
|
-
|
|
217920
|
+
const entry = {
|
|
217921
|
+
agentId: msg.agentId,
|
|
217922
|
+
msg: nmsg,
|
|
217923
|
+
initAbort: new AbortController(),
|
|
217924
|
+
hookContext,
|
|
217925
|
+
resolve: () => {},
|
|
217926
|
+
reject: () => {}
|
|
217927
|
+
};
|
|
217928
|
+
if (this.persistInbox(entry, key, { required: true }) !== "existing") this.emitHookCompletion(hookContext, "success", { reason: "deleted: no existing session" }, entry);
|
|
217929
|
+
return { accepted: true };
|
|
217470
217930
|
}
|
|
217471
217931
|
}
|
|
217472
217932
|
const githubReply = c?.source === "github" && c.repo && c.number !== void 0 ? {
|
|
@@ -217474,14 +217934,223 @@ var Daemon = class Daemon {
|
|
|
217474
217934
|
repo: c.repo,
|
|
217475
217935
|
number: c.number
|
|
217476
217936
|
} : void 0;
|
|
217937
|
+
if (githubReply) hookContext.githubReply = githubReply;
|
|
217938
|
+
const anchored = await this.anchorTrigger(msg.agentId, nmsg, msg.target, hookAnchorText(msg), `hook "${msg.hookId}"`);
|
|
217939
|
+
if (!anchored) return {
|
|
217940
|
+
accepted: false,
|
|
217941
|
+
reason: "dropped"
|
|
217942
|
+
};
|
|
217943
|
+
let admission;
|
|
217944
|
+
this.dispatch(msg.agentId, anchored, msg.target?.integrationId, void 0, void 0, {
|
|
217945
|
+
requireDurable: true,
|
|
217946
|
+
onAdmission: (result) => {
|
|
217947
|
+
admission = result;
|
|
217948
|
+
}
|
|
217949
|
+
}, githubReply, hookContext).catch((err) => this.log.error(`hook turn failed for agent "${msg.agentId}": ${formatErr(err)}`));
|
|
217950
|
+
if (!admission) throw new Error("hook admission barrier did not settle synchronously");
|
|
217951
|
+
if (!admission.accepted) return {
|
|
217952
|
+
accepted: false,
|
|
217953
|
+
reason: admission.reason ?? "durability"
|
|
217954
|
+
};
|
|
217955
|
+
return { accepted: true };
|
|
217956
|
+
}
|
|
217957
|
+
/** Resolve a PR revision if the webhook omitted it (notably
|
|
217958
|
+
* issue_comment), cross the CP hook/start barrier, then return the active
|
|
217959
|
+
* review authority. Failure only disables structured effects; the ordinary
|
|
217960
|
+
* agent turn and final poster continue. */
|
|
217961
|
+
async prepareGithubTurn(entry) {
|
|
217962
|
+
const hook = entry.hookContext;
|
|
217963
|
+
const snapshot = hook?.snapshot;
|
|
217964
|
+
const github = hook?.github;
|
|
217965
|
+
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;
|
|
217966
|
+
if (this.cfg.daemonId && snapshot.dispatchDaemonId !== this.cfg.daemonId) {
|
|
217967
|
+
this.log.warn(`github review: stale dispatch daemon for ${hook.hookId}:${hook.deliveryKey}`);
|
|
217968
|
+
return;
|
|
217969
|
+
}
|
|
217970
|
+
if (!github.headSha || !github.baseSha) try {
|
|
217971
|
+
const postToken = await this.gitCreds.getPostToken(hook.agentId, github.repoFullName, hook.hookId);
|
|
217972
|
+
const revision = await this.githubReviewClient.getPull(postToken.token, github.repoFullName, github.pullNumber);
|
|
217973
|
+
hook.github = {
|
|
217974
|
+
...github,
|
|
217975
|
+
headSha: revision.headSha,
|
|
217976
|
+
baseSha: revision.baseSha,
|
|
217977
|
+
reportSha: revision.headSha,
|
|
217978
|
+
isDraft: revision.draft
|
|
217979
|
+
};
|
|
217980
|
+
this.persistHookState(entry, void 0, true);
|
|
217981
|
+
} catch (err) {
|
|
217982
|
+
this.log.warn(`github review: unable to resolve PR revision (${formatErr(err)})`);
|
|
217983
|
+
return;
|
|
217984
|
+
}
|
|
217985
|
+
const trusted = hook.github;
|
|
217986
|
+
if (!trusted.headSha || !trusted.baseSha || trusted.pullNumber === void 0) return void 0;
|
|
217987
|
+
const client = this.cpClient;
|
|
217988
|
+
if (!client) return void 0;
|
|
217989
|
+
const payload = {
|
|
217990
|
+
hookId: hook.hookId,
|
|
217991
|
+
agentId: hook.agentId,
|
|
217992
|
+
deliveryKey: hook.deliveryKey,
|
|
217993
|
+
...hook.event ? { event: hook.event } : {},
|
|
217994
|
+
github: {
|
|
217995
|
+
...trusted,
|
|
217996
|
+
reportSha: trusted.reportSha ?? trusted.headSha
|
|
217997
|
+
},
|
|
217998
|
+
...snapshot
|
|
217999
|
+
};
|
|
218000
|
+
let started = false;
|
|
218001
|
+
for (let attempt = 0; attempt < 3 && !started; attempt += 1) try {
|
|
218002
|
+
await client.startHook(payload);
|
|
218003
|
+
started = true;
|
|
218004
|
+
} catch (err) {
|
|
218005
|
+
if (attempt === 2) {
|
|
218006
|
+
this.log.warn(`github review: hook/start rejected (${formatErr(err)})`);
|
|
218007
|
+
return;
|
|
218008
|
+
}
|
|
218009
|
+
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
218010
|
+
}
|
|
218011
|
+
if (snapshot.reviewPolicy === "off") return void 0;
|
|
218012
|
+
const recoverableAttempt = hook.reviewAttemptId !== void 0 && hook.reviewRequestedEvent !== void 0 && hook.reviewRequestedVerdict !== void 0 && (hook.reviewResult === void 0 || hook.reviewResult.state === "ambiguous");
|
|
218013
|
+
const active = {
|
|
218014
|
+
entry,
|
|
218015
|
+
hook,
|
|
218016
|
+
snapshot,
|
|
218017
|
+
repoId: trusted.repoId,
|
|
218018
|
+
repoFullName: trusted.repoFullName,
|
|
218019
|
+
pullNumber: trusted.pullNumber,
|
|
218020
|
+
expectedHeadSha: trusted.headSha,
|
|
218021
|
+
expectedBaseSha: trusted.baseSha,
|
|
218022
|
+
reportSha: trusted.reportSha ?? trusted.headSha,
|
|
218023
|
+
reviewState: hook.reviewAttemptId === void 0 || recoverableAttempt ? "idle" : "done"
|
|
218024
|
+
};
|
|
218025
|
+
if (recoverableAttempt) await this.reconcileGithubReviewAttempt(active);
|
|
218026
|
+
return active;
|
|
218027
|
+
}
|
|
218028
|
+
/** Store the immediate and terminal-report copies together so they cannot
|
|
218029
|
+
* drift across submit and restart-recovery paths. */
|
|
218030
|
+
persistGithubReviewEffect(active, attemptId, effect, required = false) {
|
|
218031
|
+
const result = reviewResultForWire(effect);
|
|
218032
|
+
active.hook.reviewResult = result;
|
|
218033
|
+
active.hook.reviewReportAttemptId = attemptId;
|
|
218034
|
+
active.hook.reviewReportResult = result;
|
|
218035
|
+
this.persistHookState(active.entry, void 0, required);
|
|
218036
|
+
return result;
|
|
218037
|
+
}
|
|
218038
|
+
/** A daemon restart may replay an attempt after an ambiguous POST/list race.
|
|
218039
|
+
* Before prompting the model, perform a GET-only marker reconciliation. A
|
|
218040
|
+
* still-missing marker remains blocked; only an explicit tool invocation can
|
|
218041
|
+
* cross the full reauthorization + revision fence and retry the mutation. */
|
|
218042
|
+
async reconcileGithubReviewAttempt(active) {
|
|
218043
|
+
const cp = this.cpClient;
|
|
218044
|
+
const attemptId = active.hook.reviewAttemptId;
|
|
218045
|
+
const requestedEvent = active.hook.reviewRequestedEvent;
|
|
218046
|
+
const requestedVerdict = active.hook.reviewRequestedVerdict;
|
|
218047
|
+
if (!cp || !attemptId || !requestedEvent || !requestedVerdict) return;
|
|
217477
218048
|
try {
|
|
217478
|
-
const
|
|
217479
|
-
|
|
217480
|
-
|
|
218049
|
+
const authorized = await cp.authorizeGithubReview({
|
|
218050
|
+
hookId: active.hook.hookId,
|
|
218051
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218052
|
+
attemptId,
|
|
218053
|
+
requestedEvent,
|
|
218054
|
+
requestedVerdict,
|
|
218055
|
+
snapshot: active.snapshot
|
|
218056
|
+
});
|
|
218057
|
+
if (!authorizedReviewTargetMatches(active, attemptId, authorized)) {
|
|
218058
|
+
this.log.warn("github review: recovery authorization returned a mismatched target");
|
|
218059
|
+
return;
|
|
218060
|
+
}
|
|
218061
|
+
const effect = await this.githubReviewClient.reconcile(authorizedReviewTarget(active, attemptId, authorized, true), requestedEvent, requestedVerdict);
|
|
218062
|
+
if (effect.state !== "submitted") return;
|
|
218063
|
+
const result = this.persistGithubReviewEffect(active, attemptId, effect, true);
|
|
218064
|
+
await cp.reportGithubReviewResult({
|
|
218065
|
+
hookId: active.hook.hookId,
|
|
218066
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218067
|
+
attemptId,
|
|
218068
|
+
snapshot: active.snapshot,
|
|
218069
|
+
result
|
|
218070
|
+
});
|
|
218071
|
+
active.reviewState = "done";
|
|
218072
|
+
} catch (err) {
|
|
218073
|
+
this.log.warn(`github review: replay reconciliation deferred (${formatErr(err)})`);
|
|
218074
|
+
}
|
|
218075
|
+
}
|
|
218076
|
+
async submitGithubReview(req) {
|
|
218077
|
+
const key = sessionKey(req.platform, req.channel, req.thread, req.agentId);
|
|
218078
|
+
const active = this.activeGithubTurnMeta.get(key);
|
|
218079
|
+
if (!active || active.hook.agentId !== req.agentId) throw new Error("formal GitHub review is only available during the active PR hook turn");
|
|
218080
|
+
if (!reviewPolicyAllows(active.snapshot.reviewPolicy, req.event)) throw new Error(`${req.event} exceeds this hook's ${active.snapshot.reviewPolicy} review policy`);
|
|
218081
|
+
if (active.reviewState !== "idle") throw new Error("this PR hook turn already has a formal review attempt");
|
|
218082
|
+
active.reviewState = "submitting";
|
|
218083
|
+
const recovering = active.hook.reviewAttemptId !== void 0;
|
|
218084
|
+
if (recovering) {
|
|
218085
|
+
if (active.hook.reviewRequestedEvent === void 0 || active.hook.reviewRequestedVerdict === void 0 || active.hook.reviewRequestedEvent !== req.event || active.hook.reviewRequestedVerdict !== req.verdict) {
|
|
218086
|
+
active.reviewState = "idle";
|
|
218087
|
+
throw new Error("a recovered formal-review attempt must keep its original event and verdict");
|
|
218088
|
+
}
|
|
218089
|
+
} else {
|
|
218090
|
+
active.hook.reviewRequestedEvent = req.event;
|
|
218091
|
+
active.hook.reviewRequestedVerdict = req.verdict;
|
|
218092
|
+
}
|
|
218093
|
+
const attemptId = active.hook.reviewAttemptId ?? randomUUID();
|
|
218094
|
+
active.hook.reviewAttemptId = attemptId;
|
|
218095
|
+
const entry = active.entry;
|
|
218096
|
+
try {
|
|
218097
|
+
this.persistHookState(entry, void 0, true);
|
|
218098
|
+
} catch (err) {
|
|
218099
|
+
if (!recovering) {
|
|
218100
|
+
delete active.hook.reviewAttemptId;
|
|
218101
|
+
delete active.hook.reviewRequestedEvent;
|
|
218102
|
+
delete active.hook.reviewRequestedVerdict;
|
|
218103
|
+
}
|
|
218104
|
+
active.reviewState = "idle";
|
|
218105
|
+
throw new Error(`formal review durability barrier failed: ${formatErr(err)}`);
|
|
218106
|
+
}
|
|
218107
|
+
const cp = this.cpClient;
|
|
218108
|
+
if (!cp) {
|
|
218109
|
+
active.reviewState = "done";
|
|
218110
|
+
throw new Error("control plane is not connected; formal review denied");
|
|
218111
|
+
}
|
|
218112
|
+
let authorized;
|
|
218113
|
+
try {
|
|
218114
|
+
authorized = await cp.authorizeGithubReview({
|
|
218115
|
+
hookId: active.hook.hookId,
|
|
218116
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218117
|
+
attemptId,
|
|
218118
|
+
requestedEvent: req.event,
|
|
218119
|
+
requestedVerdict: req.verdict,
|
|
218120
|
+
snapshot: active.snapshot
|
|
218121
|
+
});
|
|
217481
218122
|
} catch (err) {
|
|
217482
|
-
|
|
218123
|
+
active.reviewState = "done";
|
|
217483
218124
|
throw err;
|
|
217484
218125
|
}
|
|
218126
|
+
if (!authorizedReviewTargetMatches(active, attemptId, authorized)) {
|
|
218127
|
+
active.reviewState = "done";
|
|
218128
|
+
throw new Error("control plane returned a mismatched formal-review target");
|
|
218129
|
+
}
|
|
218130
|
+
const effect = await this.githubReviewClient.submit(authorizedReviewTarget(active, attemptId, authorized, recovering), req);
|
|
218131
|
+
const result = this.persistGithubReviewEffect(active, attemptId, effect);
|
|
218132
|
+
try {
|
|
218133
|
+
await cp.reportGithubReviewResult({
|
|
218134
|
+
hookId: active.hook.hookId,
|
|
218135
|
+
deliveryKey: active.hook.deliveryKey,
|
|
218136
|
+
attemptId,
|
|
218137
|
+
snapshot: active.snapshot,
|
|
218138
|
+
result
|
|
218139
|
+
});
|
|
218140
|
+
if (effect.state === "not_submitted") {
|
|
218141
|
+
delete active.hook.reviewAttemptId;
|
|
218142
|
+
delete active.hook.reviewRequestedEvent;
|
|
218143
|
+
delete active.hook.reviewRequestedVerdict;
|
|
218144
|
+
delete active.hook.reviewResult;
|
|
218145
|
+
active.reviewState = "idle";
|
|
218146
|
+
this.persistHookState(entry);
|
|
218147
|
+
} else if (effect.state === "ambiguous") active.reviewState = "idle";
|
|
218148
|
+
else active.reviewState = "done";
|
|
218149
|
+
} catch (err) {
|
|
218150
|
+
active.reviewState = effect.state === "ambiguous" ? "idle" : "done";
|
|
218151
|
+
this.log.warn(`github review: immediate result report failed (${formatErr(err)})`);
|
|
218152
|
+
}
|
|
218153
|
+
return effect;
|
|
217485
218154
|
}
|
|
217486
218155
|
/** The op-switch behind {@link handleRelayMsg} (dedup handled by the caller). */
|
|
217487
218156
|
dispatchRelayOp(msg, chat) {
|
|
@@ -218075,26 +218744,142 @@ var Daemon = class Daemon {
|
|
|
218075
218744
|
* durable row would be un-replayable. Sets `entry.inboxId` so every terminal path can delete
|
|
218076
218745
|
* the row. The row id is the message's stable deliveryId/msgId (§6.3) — idempotent re-append.
|
|
218077
218746
|
*/
|
|
218078
|
-
persistInbox(entry, key) {
|
|
218079
|
-
if (entry.webchat) return;
|
|
218747
|
+
persistInbox(entry, key, options = {}) {
|
|
218748
|
+
if (entry.webchat) return "skipped";
|
|
218080
218749
|
const id = entry.callMeta?.deliveryId ?? entry.msg.msgId;
|
|
218081
218750
|
try {
|
|
218082
|
-
this.store.appendInbox({
|
|
218751
|
+
const inserted = this.store.appendInbox({
|
|
218083
218752
|
id,
|
|
218084
218753
|
sessionKey: key,
|
|
218085
218754
|
agentId: entry.agentId,
|
|
218086
218755
|
msg: JSON.stringify(entry.msg),
|
|
218087
218756
|
integrationId: entry.integrationId ?? null,
|
|
218088
218757
|
callMeta: entry.callMeta ? JSON.stringify(entry.callMeta) : null,
|
|
218758
|
+
hookContext: entry.hookContext ? JSON.stringify(entry.hookContext) : null,
|
|
218759
|
+
posterPublishState: entry.posterPublishState ?? null,
|
|
218089
218760
|
isQueueCmd: entry.isQueueCmd ? 1 : null,
|
|
218090
218761
|
loopGuardCounted: 1,
|
|
218091
218762
|
enqueuedAt: monotonicTs()
|
|
218092
218763
|
});
|
|
218764
|
+
if (!inserted && !options.adoptExisting) return "existing";
|
|
218093
218765
|
entry.inboxId = id;
|
|
218094
218766
|
this.liveInboxIds.add(id);
|
|
218767
|
+
return inserted ? "inserted" : "adopted";
|
|
218095
218768
|
} catch (err) {
|
|
218769
|
+
if (options.required) throw err;
|
|
218096
218770
|
this.log.warn(`durable inbox: append failed for ${id}: ${err.message}`);
|
|
218771
|
+
return "failed";
|
|
218772
|
+
}
|
|
218773
|
+
}
|
|
218774
|
+
persistHookState(entry, posterPublishState, required = false) {
|
|
218775
|
+
if (!entry.inboxId || !entry.hookContext) {
|
|
218776
|
+
if (required) throw new Error("hook state has no durable inbox row");
|
|
218777
|
+
return;
|
|
218778
|
+
}
|
|
218779
|
+
if (posterPublishState) entry.posterPublishState = posterPublishState;
|
|
218780
|
+
try {
|
|
218781
|
+
if (!this.store.updateInboxHookState(entry.inboxId, JSON.stringify(entry.hookContext), posterPublishState)) throw new Error("durable inbox row is missing");
|
|
218782
|
+
} catch (err) {
|
|
218783
|
+
if (required) throw err;
|
|
218784
|
+
this.log.warn(`durable inbox: hook state update failed for ${entry.inboxId}: ${err.message}`);
|
|
218785
|
+
}
|
|
218786
|
+
}
|
|
218787
|
+
emitHookCompletion(hook, status, extra = {}, owner) {
|
|
218788
|
+
if (owner?.hookTerminalReceipt) return;
|
|
218789
|
+
const start = Date.parse(hook.turnStartedAt ?? hook.firedAt);
|
|
218790
|
+
const reviewAttemptId = hook.reviewReportAttemptId ?? hook.reviewAttemptId;
|
|
218791
|
+
const reviewResult = hook.reviewReportResult ?? hook.reviewResult;
|
|
218792
|
+
const report = {
|
|
218793
|
+
hookId: hook.hookId,
|
|
218794
|
+
agentId: hook.agentId,
|
|
218795
|
+
deliveryKey: hook.deliveryKey,
|
|
218796
|
+
...hook.snapshot ?? {},
|
|
218797
|
+
...hook.event ? { event: hook.event } : {},
|
|
218798
|
+
...hook.github ? { github: hook.github } : {},
|
|
218799
|
+
status,
|
|
218800
|
+
durationMs: Number.isFinite(start) ? Math.max(0, this.clock.now() - start) : 0,
|
|
218801
|
+
...extra,
|
|
218802
|
+
...reviewAttemptId && reviewResult ? {
|
|
218803
|
+
reviewAttemptId,
|
|
218804
|
+
reviewResult
|
|
218805
|
+
} : {}
|
|
218806
|
+
};
|
|
218807
|
+
let reportInboxId;
|
|
218808
|
+
if (owner?.inboxId) try {
|
|
218809
|
+
const completed = this.store.completeHookInbox(owner.inboxId, JSON.stringify(report), this.clock.now());
|
|
218810
|
+
if (completed === "already-terminal") {
|
|
218811
|
+
owner.hookTerminalReceipt = true;
|
|
218812
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218813
|
+
return;
|
|
218814
|
+
}
|
|
218815
|
+
if (completed === "missing") {
|
|
218816
|
+
this.log.warn(`durable inbox: hook terminal receipt owner is missing for ${owner.inboxId}`);
|
|
218817
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218818
|
+
} else {
|
|
218819
|
+
owner.hookTerminalReceipt = true;
|
|
218820
|
+
this.liveInboxIds.delete(owner.inboxId);
|
|
218821
|
+
reportInboxId = owner.inboxId;
|
|
218822
|
+
}
|
|
218823
|
+
} catch (err) {
|
|
218824
|
+
this.log.warn(`durable inbox: hook terminal receipt failed for ${owner.inboxId}: ${formatErr(err)}`);
|
|
218097
218825
|
}
|
|
218826
|
+
this.sendHookReport(report, reportInboxId);
|
|
218827
|
+
}
|
|
218828
|
+
/** Send one durable terminal report and release only its outbox payload after
|
|
218829
|
+
* the CP's correlated persistence/projection ACK. The stable id receipt stays
|
|
218830
|
+
* bounded locally to absorb later relay redelivery. */
|
|
218831
|
+
sendHookReport(report, inboxId) {
|
|
218832
|
+
if (!this.cpClient) return;
|
|
218833
|
+
if (inboxId && this.hookReportInflight.has(inboxId)) return;
|
|
218834
|
+
if (inboxId && this.hookReportInflight.size >= MAX_HOOK_REPORT_INFLIGHT) {
|
|
218835
|
+
this.scheduleHookReportRetry();
|
|
218836
|
+
return;
|
|
218837
|
+
}
|
|
218838
|
+
if (inboxId) this.hookReportInflight.add(inboxId);
|
|
218839
|
+
const connectionId = this.hookReportConnectionId;
|
|
218840
|
+
let refillDrainSlot = false;
|
|
218841
|
+
Promise.resolve(this.cpClient.emitHookReport(report)).then((result) => {
|
|
218842
|
+
if (!inboxId) return;
|
|
218843
|
+
if (result === "legacy-sent") {
|
|
218844
|
+
try {
|
|
218845
|
+
if (!this.store.markLegacyHookReportSent(inboxId, connectionId)) throw new Error("durable inbox row is missing");
|
|
218846
|
+
refillDrainSlot = true;
|
|
218847
|
+
} catch (err) {
|
|
218848
|
+
this.log.warn(`durable inbox: legacy report stamp failed for ${inboxId}: ${formatErr(err)}`);
|
|
218849
|
+
this.scheduleHookReportRetry();
|
|
218850
|
+
}
|
|
218851
|
+
return;
|
|
218852
|
+
}
|
|
218853
|
+
refillDrainSlot = true;
|
|
218854
|
+
try {
|
|
218855
|
+
this.store.acknowledgeHookInbox(inboxId);
|
|
218856
|
+
} catch (err) {
|
|
218857
|
+
this.log.warn(`durable inbox: hook report ACK cleanup failed for ${inboxId}: ${formatErr(err)}`);
|
|
218858
|
+
}
|
|
218859
|
+
}).catch((err) => {
|
|
218860
|
+
if (typeof err === "object" && err !== null && "retryable" in err && err.retryable === false && inboxId) {
|
|
218861
|
+
refillDrainSlot = true;
|
|
218862
|
+
try {
|
|
218863
|
+
this.store.acknowledgeHookInbox(inboxId);
|
|
218864
|
+
} catch (cleanupError) {
|
|
218865
|
+
this.log.warn(`durable inbox: hook report dead-letter failed for ${inboxId}: ${formatErr(cleanupError)}`);
|
|
218866
|
+
}
|
|
218867
|
+
this.log.warn(`hook report permanently rejected for ${inboxId}: ${formatErr(err)}`);
|
|
218868
|
+
return;
|
|
218869
|
+
}
|
|
218870
|
+
this.log.debug(`hook report retained for retry: ${formatErr(err)}`);
|
|
218871
|
+
this.scheduleHookReportRetry();
|
|
218872
|
+
}).finally(() => {
|
|
218873
|
+
if (inboxId) this.hookReportInflight.delete(inboxId);
|
|
218874
|
+
if (refillDrainSlot) this.scheduleHookReportRetry(250);
|
|
218875
|
+
});
|
|
218876
|
+
}
|
|
218877
|
+
scheduleHookReportRetry(delayMs = 5e3) {
|
|
218878
|
+
if (this.draining || this.hookReportRetryTimer !== void 0) return;
|
|
218879
|
+
this.hookReportRetryTimer = this.clock.setTimeout(() => {
|
|
218880
|
+
this.hookReportRetryTimer = void 0;
|
|
218881
|
+
this.replayHookTerminalReports();
|
|
218882
|
+
}, delayMs);
|
|
218098
218883
|
}
|
|
218099
218884
|
/** Remove an entry's durable inbox row once its turn reaches ANY terminal state (success,
|
|
218100
218885
|
* reject/fail-stop, cancel, gate-drop) so it is not replayed on the next startup. No-op
|
|
@@ -218103,6 +218888,7 @@ var Daemon = class Daemon {
|
|
|
218103
218888
|
removeInbox(entry) {
|
|
218104
218889
|
if (!entry.inboxId) return;
|
|
218105
218890
|
this.liveInboxIds.delete(entry.inboxId);
|
|
218891
|
+
if (entry.hookContext) return;
|
|
218106
218892
|
try {
|
|
218107
218893
|
this.store.removeInbox(entry.inboxId);
|
|
218108
218894
|
} catch (err) {
|
|
@@ -218130,6 +218916,7 @@ var Daemon = class Daemon {
|
|
|
218130
218916
|
let removed = 0;
|
|
218131
218917
|
try {
|
|
218132
218918
|
for (const row of this.store.listInboxBySessionKeyFifo()) {
|
|
218919
|
+
if (row.hookContext || row.terminalReport) continue;
|
|
218133
218920
|
let msg;
|
|
218134
218921
|
try {
|
|
218135
218922
|
msg = JSON.parse(row.msg);
|
|
@@ -218146,8 +218933,8 @@ var Daemon = class Daemon {
|
|
|
218146
218933
|
}
|
|
218147
218934
|
return removed;
|
|
218148
218935
|
}
|
|
218149
|
-
isLoopGuardOpen(msg) {
|
|
218150
|
-
return usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopGuardScope(msg));
|
|
218936
|
+
isLoopGuardOpen(msg, includeHook = false) {
|
|
218937
|
+
return (usesLoopGuard(msg) || includeHook) && this.store.isLoopGuardOpen(loopGuardScope(msg));
|
|
218151
218938
|
}
|
|
218152
218939
|
/** First-open side effects for a durable conversation circuit: purge restart work,
|
|
218153
218940
|
* drop every matching serial queue, cancel live ACP turns across ALL agents sharing
|
|
@@ -218214,41 +219001,71 @@ var Daemon = class Daemon {
|
|
|
218214
219001
|
...error ? { error } : {}
|
|
218215
219002
|
});
|
|
218216
219003
|
}
|
|
218217
|
-
dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply) {
|
|
219004
|
+
dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply, hookContext, posterPublishState = githubReply ? "not_started" : void 0) {
|
|
218218
219005
|
return new Promise((resolve, reject) => {
|
|
219006
|
+
let admissionSettled = false;
|
|
219007
|
+
const settleAdmission = (result) => {
|
|
219008
|
+
if (admissionSettled) return;
|
|
219009
|
+
admissionSettled = true;
|
|
219010
|
+
opts?.onAdmission?.(result);
|
|
219011
|
+
};
|
|
218219
219012
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
218220
219013
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
219014
|
+
settleAdmission({
|
|
219015
|
+
accepted: false,
|
|
219016
|
+
reason: "draining"
|
|
219017
|
+
});
|
|
218221
219018
|
resolve(null);
|
|
218222
219019
|
return;
|
|
218223
219020
|
}
|
|
218224
219021
|
if (this.paused(agentId)) {
|
|
218225
219022
|
this.log.info(`dispatch: skipped for agent "${agentId}" (paused)`);
|
|
219023
|
+
settleAdmission({
|
|
219024
|
+
accepted: false,
|
|
219025
|
+
reason: "paused"
|
|
219026
|
+
});
|
|
218226
219027
|
resolve(null);
|
|
218227
219028
|
return;
|
|
218228
219029
|
}
|
|
218229
219030
|
if (this.safetyDrainingAgents.has(agentId)) {
|
|
218230
219031
|
if (opts?.fromInboxReplay) this.waitForSafetyDrain(agentId).then(() => {
|
|
218231
219032
|
if (this.draining || !this.agents.has(agentId)) {
|
|
219033
|
+
settleAdmission({
|
|
219034
|
+
accepted: false,
|
|
219035
|
+
reason: "draining"
|
|
219036
|
+
});
|
|
218232
219037
|
resolve(null);
|
|
218233
219038
|
return;
|
|
218234
219039
|
}
|
|
218235
219040
|
if (opts.inboxReplayId !== void 0 && !this.store.hasInbox(opts.inboxReplayId)) {
|
|
219041
|
+
settleAdmission({
|
|
219042
|
+
accepted: false,
|
|
219043
|
+
reason: "dropped"
|
|
219044
|
+
});
|
|
218236
219045
|
resolve(null);
|
|
218237
219046
|
return;
|
|
218238
219047
|
}
|
|
218239
|
-
this.dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply).then(resolve, reject);
|
|
219048
|
+
this.dispatch(agentId, msg, integrationId, webchat, callMeta, opts, githubReply, hookContext, posterPublishState).then(resolve, reject);
|
|
218240
219049
|
}).catch(reject);
|
|
218241
219050
|
else {
|
|
218242
219051
|
this.log.info(`dispatch: skipped for agent "${agentId}" (stopping interrupted work)`);
|
|
219052
|
+
settleAdmission({
|
|
219053
|
+
accepted: false,
|
|
219054
|
+
reason: "busy"
|
|
219055
|
+
});
|
|
218243
219056
|
resolve(null);
|
|
218244
219057
|
}
|
|
218245
219058
|
return;
|
|
218246
219059
|
}
|
|
218247
219060
|
const key = sessionKey(msg.platform, msg.channel, msg.thread ?? msg.msgId, agentId);
|
|
218248
219061
|
const loopScope = loopGuardScope(msg);
|
|
218249
|
-
if (usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopScope)) {
|
|
219062
|
+
if ((usesLoopGuard(msg) || hookContext !== void 0) && this.store.isLoopGuardOpen(loopScope)) {
|
|
218250
219063
|
if (opts?.fromInboxReplay) this.purgeLoopScopeInbox(loopScope);
|
|
218251
219064
|
this.log.warn(`dispatch: skipped ${msg.msgId}; loop guard is open for ${loopScope}`);
|
|
219065
|
+
settleAdmission({
|
|
219066
|
+
accepted: false,
|
|
219067
|
+
reason: "loop_protection"
|
|
219068
|
+
});
|
|
218252
219069
|
resolve(null);
|
|
218253
219070
|
return;
|
|
218254
219071
|
}
|
|
@@ -218262,6 +219079,10 @@ var Daemon = class Daemon {
|
|
|
218262
219079
|
});
|
|
218263
219080
|
else this.purgeLoopScopeInbox(loopScope);
|
|
218264
219081
|
} else this.log.warn(`dispatch: dropped malformed Slack platform event ${msg.msgId}`);
|
|
219082
|
+
settleAdmission({
|
|
219083
|
+
accepted: false,
|
|
219084
|
+
reason: "malformed_platform_event"
|
|
219085
|
+
});
|
|
218265
219086
|
resolve(null);
|
|
218266
219087
|
return;
|
|
218267
219088
|
}
|
|
@@ -218272,8 +219093,10 @@ var Daemon = class Daemon {
|
|
|
218272
219093
|
...integrationId !== void 0 ? { integrationId } : {},
|
|
218273
219094
|
...webchat ? { webchat } : {},
|
|
218274
219095
|
...callMeta ? { callMeta } : {},
|
|
219096
|
+
...hookContext ? { hookContext } : {},
|
|
218275
219097
|
...opts?.isQueueCmd ? { isQueueCmd: true } : {},
|
|
218276
219098
|
...githubReply ? { githubReply } : {},
|
|
219099
|
+
...posterPublishState ? { posterPublishState } : {},
|
|
218277
219100
|
resolve,
|
|
218278
219101
|
reject
|
|
218279
219102
|
};
|
|
@@ -218281,25 +219104,81 @@ var Daemon = class Daemon {
|
|
|
218281
219104
|
const q = this.serialQueue.get(key) ?? [];
|
|
218282
219105
|
if (q.length >= MAX_QUEUED_PER_SESSION) {
|
|
218283
219106
|
this.log.warn(`dispatch: queue full for session ${key} (${q.length}) — rejecting (queue_full)`);
|
|
219107
|
+
settleAdmission({
|
|
219108
|
+
accepted: false,
|
|
219109
|
+
reason: "queue_full"
|
|
219110
|
+
});
|
|
218284
219111
|
reject(new QueueFullError(key));
|
|
218285
219112
|
return;
|
|
218286
219113
|
}
|
|
218287
219114
|
if (!opts?.replay && !this.admitLoopGuardTurn(agentId, msg, integrationId, opts?.inboxReplayId)) {
|
|
219115
|
+
settleAdmission({
|
|
219116
|
+
accepted: false,
|
|
219117
|
+
reason: "loop_protection"
|
|
219118
|
+
});
|
|
219119
|
+
resolve(null);
|
|
219120
|
+
return;
|
|
219121
|
+
}
|
|
219122
|
+
let persistence;
|
|
219123
|
+
try {
|
|
219124
|
+
persistence = this.persistInbox(entry, key, {
|
|
219125
|
+
required: opts?.requireDurable,
|
|
219126
|
+
adoptExisting: opts?.adoptExistingInbox
|
|
219127
|
+
});
|
|
219128
|
+
} catch (err) {
|
|
219129
|
+
settleAdmission({
|
|
219130
|
+
accepted: false,
|
|
219131
|
+
reason: "durability"
|
|
219132
|
+
});
|
|
219133
|
+
reject(err);
|
|
219134
|
+
return;
|
|
219135
|
+
}
|
|
219136
|
+
if (persistence === "existing") {
|
|
219137
|
+
settleAdmission({
|
|
219138
|
+
accepted: true,
|
|
219139
|
+
duplicate: true
|
|
219140
|
+
});
|
|
218288
219141
|
resolve(null);
|
|
218289
219142
|
return;
|
|
218290
219143
|
}
|
|
218291
219144
|
q.push(entry);
|
|
218292
219145
|
this.serialQueue.set(key, q);
|
|
218293
|
-
|
|
219146
|
+
settleAdmission({ accepted: true });
|
|
218294
219147
|
this.log.debug(`dispatch: queued behind in-flight turn for session ${key} (depth ${q.length})`);
|
|
218295
219148
|
return;
|
|
218296
219149
|
}
|
|
218297
219150
|
if (!opts?.replay && !this.admitLoopGuardTurn(agentId, msg, integrationId, opts?.inboxReplayId)) {
|
|
219151
|
+
settleAdmission({
|
|
219152
|
+
accepted: false,
|
|
219153
|
+
reason: "loop_protection"
|
|
219154
|
+
});
|
|
219155
|
+
resolve(null);
|
|
219156
|
+
return;
|
|
219157
|
+
}
|
|
219158
|
+
let persistence;
|
|
219159
|
+
try {
|
|
219160
|
+
persistence = this.persistInbox(entry, key, {
|
|
219161
|
+
required: opts?.requireDurable,
|
|
219162
|
+
adoptExisting: opts?.adoptExistingInbox
|
|
219163
|
+
});
|
|
219164
|
+
} catch (err) {
|
|
219165
|
+
settleAdmission({
|
|
219166
|
+
accepted: false,
|
|
219167
|
+
reason: "durability"
|
|
219168
|
+
});
|
|
219169
|
+
reject(err);
|
|
219170
|
+
return;
|
|
219171
|
+
}
|
|
219172
|
+
if (persistence === "existing") {
|
|
219173
|
+
settleAdmission({
|
|
219174
|
+
accepted: true,
|
|
219175
|
+
duplicate: true
|
|
219176
|
+
});
|
|
218298
219177
|
resolve(null);
|
|
218299
219178
|
return;
|
|
218300
219179
|
}
|
|
218301
|
-
this.persistInbox(entry, key);
|
|
218302
219180
|
this.inflight.add(key);
|
|
219181
|
+
settleAdmission({ accepted: true });
|
|
218303
219182
|
this.runLoop(key, entry);
|
|
218304
219183
|
});
|
|
218305
219184
|
}
|
|
@@ -218355,7 +219234,7 @@ var Daemon = class Daemon {
|
|
|
218355
219234
|
dispatchGateReason(entry) {
|
|
218356
219235
|
if (entry.cancelledReason) return entry.cancelledReason;
|
|
218357
219236
|
if (this.paused(entry.agentId)) return "pause";
|
|
218358
|
-
if (this.isLoopGuardOpen(entry.msg)) return "loop protection";
|
|
219237
|
+
if (this.isLoopGuardOpen(entry.msg, entry.hookContext !== void 0)) return "loop protection";
|
|
218359
219238
|
}
|
|
218360
219239
|
beginActiveDispatch(agentId, key) {
|
|
218361
219240
|
let resolveDone;
|
|
@@ -218406,13 +219285,19 @@ var Daemon = class Daemon {
|
|
|
218406
219285
|
this.activeGateEntries.set(key, entry);
|
|
218407
219286
|
if (entry !== firstEntry && (this.draining || this.drainingAgents.has(entry.agentId) || this.paused(entry.agentId))) {
|
|
218408
219287
|
this.terminateQueuedSink(entry);
|
|
218409
|
-
if (!this.draining)
|
|
219288
|
+
if (!this.draining) {
|
|
219289
|
+
if (entry.hookContext) this.emitHookCompletion(entry.hookContext, "failed", { reason: "dropped" }, entry);
|
|
219290
|
+
this.removeInbox(entry);
|
|
219291
|
+
}
|
|
218410
219292
|
entry.resolve(null);
|
|
218411
219293
|
const rest = this.serialQueue.get(key) ?? [];
|
|
218412
219294
|
this.serialQueue.delete(key);
|
|
218413
219295
|
for (const e of rest) {
|
|
218414
219296
|
this.terminateQueuedSink(e);
|
|
218415
|
-
if (!this.draining)
|
|
219297
|
+
if (!this.draining) {
|
|
219298
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: "dropped" }, e);
|
|
219299
|
+
this.removeInbox(e);
|
|
219300
|
+
}
|
|
218416
219301
|
e.resolve(null);
|
|
218417
219302
|
}
|
|
218418
219303
|
this.inflight.delete(key);
|
|
@@ -218436,7 +219321,10 @@ var Daemon = class Daemon {
|
|
|
218436
219321
|
for (const e of rest) {
|
|
218437
219322
|
const failStop = new FailStopError(key);
|
|
218438
219323
|
this.terminateQueuedSink(e, failStop.message);
|
|
218439
|
-
if (!this.draining)
|
|
219324
|
+
if (!this.draining) {
|
|
219325
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: "fail_stop" }, e);
|
|
219326
|
+
this.removeInbox(e);
|
|
219327
|
+
}
|
|
218440
219328
|
e.reject(failStop);
|
|
218441
219329
|
}
|
|
218442
219330
|
this.inflight.delete(key);
|
|
@@ -218452,13 +219340,23 @@ var Daemon = class Daemon {
|
|
|
218452
219340
|
}
|
|
218453
219341
|
}
|
|
218454
219342
|
async dispatchOne(entry, key) {
|
|
218455
|
-
const { agentId, msg, integrationId, webchat, callMeta, githubReply } = entry;
|
|
219343
|
+
const { agentId, msg, integrationId, webchat, callMeta, githubReply, hookContext } = entry;
|
|
218456
219344
|
const initialGate = this.dispatchGateReason(entry);
|
|
218457
219345
|
if (initialGate) {
|
|
218458
219346
|
this.terminateQueuedSink(entry);
|
|
219347
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: initialGate }, entry);
|
|
218459
219348
|
this.log.info(`dispatch: skipped admitted turn ${msg.msgId} (${initialGate})`);
|
|
218460
219349
|
return null;
|
|
218461
219350
|
}
|
|
219351
|
+
if (hookContext && !hookContext.turnStartedAt) {
|
|
219352
|
+
hookContext.turnStartedAt = new Date(this.clock.now()).toISOString();
|
|
219353
|
+
try {
|
|
219354
|
+
this.persistHookState(entry, void 0, true);
|
|
219355
|
+
} catch (err) {
|
|
219356
|
+
this.emitHookCompletion(hookContext, "failed", { reason: "durability" }, entry);
|
|
219357
|
+
throw err;
|
|
219358
|
+
}
|
|
219359
|
+
}
|
|
218462
219360
|
if (callMeta?.correlationId !== void 0) this.recordWorkerReport(key, callMeta, msg.text);
|
|
218463
219361
|
const agent = this.agents.get(agentId);
|
|
218464
219362
|
const agentName = agent.displayName?.trim() || agent.name;
|
|
@@ -218510,7 +219408,11 @@ var Daemon = class Daemon {
|
|
|
218510
219408
|
} finally {
|
|
218511
219409
|
releaseReplyConn();
|
|
218512
219410
|
}
|
|
218513
|
-
if (interrupted)
|
|
219411
|
+
if (interrupted) {
|
|
219412
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: interrupted }, entry);
|
|
219413
|
+
return null;
|
|
219414
|
+
}
|
|
219415
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: "session_start_failed" }, entry);
|
|
218514
219416
|
throw err;
|
|
218515
219417
|
}
|
|
218516
219418
|
const { sessionId, blocks, created } = handled;
|
|
@@ -218530,6 +219432,7 @@ var Daemon = class Daemon {
|
|
|
218530
219432
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218531
219433
|
this.terminateQueuedSink(entry);
|
|
218532
219434
|
releaseReplyConn();
|
|
219435
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", { reason: initializedGate }, entry);
|
|
218533
219436
|
this.log.info(`dispatch: skipped initialized turn ${msg.msgId} (${initializedGate})`);
|
|
218534
219437
|
return null;
|
|
218535
219438
|
}
|
|
@@ -218570,13 +219473,17 @@ var Daemon = class Daemon {
|
|
|
218570
219473
|
resolveDone,
|
|
218571
219474
|
...callMeta ? { callMeta } : {},
|
|
218572
219475
|
...pendingWebchat ? { webchat: pendingWebchat } : {},
|
|
218573
|
-
...githubReply ? { github: {
|
|
219476
|
+
...githubReply && entry.posterPublishState !== "in_flight" && entry.posterPublishState !== "settled" ? { github: {
|
|
218574
219477
|
...this.makeGithubReply(agentId, githubReply, sessionId),
|
|
218575
219478
|
deferredFinalTranscript: false
|
|
218576
219479
|
} } : {}
|
|
218577
219480
|
};
|
|
218578
219481
|
this.pending.set(pendingTurnKey(agentId, sessionId), p);
|
|
218579
219482
|
if (callMeta) this.activeTurnCallMeta.set(key, callMeta);
|
|
219483
|
+
const activeGithub = await this.prepareGithubTurn(entry).catch((err) => {
|
|
219484
|
+
this.log.warn(`github review: turn setup failed (${formatErr(err)})`);
|
|
219485
|
+
});
|
|
219486
|
+
if (activeGithub) this.activeGithubTurnMeta.set(key, activeGithub);
|
|
218580
219487
|
let finalPhase = "end";
|
|
218581
219488
|
try {
|
|
218582
219489
|
const host = await this.ensureHostAsync(agentId);
|
|
@@ -218593,6 +219500,10 @@ var Daemon = class Daemon {
|
|
|
218593
219500
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218594
219501
|
this.terminateQueuedSink(entry);
|
|
218595
219502
|
if (readyGate === "loop protection") finalPhase = "problem";
|
|
219503
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219504
|
+
sessionId,
|
|
219505
|
+
reason: readyGate
|
|
219506
|
+
}, entry);
|
|
218596
219507
|
this.log.info(`dispatch: skipped ready turn ${msg.msgId} (${readyGate})`);
|
|
218597
219508
|
return null;
|
|
218598
219509
|
}
|
|
@@ -218604,6 +219515,10 @@ var Daemon = class Daemon {
|
|
|
218604
219515
|
const { stopReason, usage } = await host.prompt(sessionId, blocks);
|
|
218605
219516
|
if (p.outputSuppressed) {
|
|
218606
219517
|
if (p.outputSuppressed === "loop protection") finalPhase = "problem";
|
|
219518
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219519
|
+
sessionId,
|
|
219520
|
+
reason: p.outputSuppressed
|
|
219521
|
+
}, entry);
|
|
218607
219522
|
return null;
|
|
218608
219523
|
}
|
|
218609
219524
|
if (usage) {
|
|
@@ -218658,6 +219573,10 @@ var Daemon = class Daemon {
|
|
|
218658
219573
|
this.clearIdle(p);
|
|
218659
219574
|
if (p.outputSuppressed) {
|
|
218660
219575
|
if (p.outputSuppressed === "loop protection") finalPhase = "problem";
|
|
219576
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219577
|
+
sessionId,
|
|
219578
|
+
reason: p.outputSuppressed
|
|
219579
|
+
}, entry);
|
|
218661
219580
|
return null;
|
|
218662
219581
|
}
|
|
218663
219582
|
finalPhase = "problem";
|
|
@@ -218695,6 +219614,10 @@ var Daemon = class Daemon {
|
|
|
218695
219614
|
this.showActivity(replyConn, msg.channel, statusThread, "");
|
|
218696
219615
|
await p.applyChain;
|
|
218697
219616
|
}
|
|
219617
|
+
if (hookContext && !this.draining) this.emitHookCompletion(hookContext, "failed", {
|
|
219618
|
+
sessionId,
|
|
219619
|
+
reason: "turn_failed"
|
|
219620
|
+
}, entry);
|
|
218698
219621
|
throw err;
|
|
218699
219622
|
} finally {
|
|
218700
219623
|
this.clearCancelBackstop(agentId, sessionId);
|
|
@@ -218704,6 +219627,7 @@ var Daemon = class Daemon {
|
|
|
218704
219627
|
this.releasePermissions(agentId, sessionId);
|
|
218705
219628
|
this.releaseElicits(agentId, sessionId);
|
|
218706
219629
|
if (callMeta) this.activeTurnCallMeta.delete(key);
|
|
219630
|
+
if (activeGithub && this.activeGithubTurnMeta.get(key) === activeGithub) this.activeGithubTurnMeta.delete(key);
|
|
218707
219631
|
const githubFinal = p.github && !p.outputSuppressed && finalPhase === "end" ? p.github.collector.finalText(true) : void 0;
|
|
218708
219632
|
if (p.github?.deferredFinalTranscript && githubFinal?.trim()) this.store.appendTranscript({
|
|
218709
219633
|
channel: p.channel,
|
|
@@ -218713,7 +219637,19 @@ var Daemon = class Daemon {
|
|
|
218713
219637
|
kind: "text",
|
|
218714
219638
|
text: githubFinal
|
|
218715
219639
|
});
|
|
218716
|
-
if (p.github && !p.outputSuppressed)
|
|
219640
|
+
if (p.github && !p.outputSuppressed) {
|
|
219641
|
+
let recordedInFlight = false;
|
|
219642
|
+
try {
|
|
219643
|
+
this.persistHookState(entry, "in_flight", true);
|
|
219644
|
+
recordedInFlight = true;
|
|
219645
|
+
} catch (err) {
|
|
219646
|
+
this.log.warn(`github poster: durability barrier failed; final publish skipped (${formatErr(err)})`);
|
|
219647
|
+
}
|
|
219648
|
+
if (recordedInFlight) {
|
|
219649
|
+
await p.github.poster.publish(githubFinal).catch((err) => this.log.warn(`github poster: final publish failed (${formatErr(err)})`));
|
|
219650
|
+
this.persistHookState(entry, "settled");
|
|
219651
|
+
}
|
|
219652
|
+
}
|
|
218717
219653
|
this.pending.delete(pendingTurnKey(agentId, sessionId));
|
|
218718
219654
|
this.store.setSessionState(key, "idle", this.clock.now());
|
|
218719
219655
|
this.emitSessionMetadataSnapshot({
|
|
@@ -218726,6 +219662,7 @@ var Daemon = class Daemon {
|
|
|
218726
219662
|
});
|
|
218727
219663
|
p.resolveDone();
|
|
218728
219664
|
}
|
|
219665
|
+
if (hookContext) this.emitHookCompletion(hookContext, "success", { sessionId }, entry);
|
|
218729
219666
|
return sessionId;
|
|
218730
219667
|
}
|
|
218731
219668
|
/** Interrupt a session by its LOGICAL sessionKey (shared by pause, `!stop`, `!cancel`,
|
|
@@ -218747,6 +219684,7 @@ var Daemon = class Daemon {
|
|
|
218747
219684
|
this.serialQueue.delete(key);
|
|
218748
219685
|
for (const e of queued) {
|
|
218749
219686
|
this.terminateQueuedSink(e, opts.dropQueued ? void 0 : reason);
|
|
219687
|
+
if (e.hookContext) this.emitHookCompletion(e.hookContext, "failed", { reason: opts.dropQueued ? "dropped" : "cancelled" }, e);
|
|
218750
219688
|
this.removeInbox(e);
|
|
218751
219689
|
if (opts.dropQueued) e.resolve(null);
|
|
218752
219690
|
else e.reject(new FailStopError(key));
|
|
@@ -220139,49 +221077,92 @@ var Daemon = class Daemon {
|
|
|
220139
221077
|
const purgedPausedAgents = /* @__PURE__ */ new Set();
|
|
220140
221078
|
const purgedLoopScopes = /* @__PURE__ */ new Set();
|
|
220141
221079
|
for (const row of rows) {
|
|
221080
|
+
if (row.completedAt !== null && row.completedAt !== void 0) continue;
|
|
220142
221081
|
if (this.liveInboxIds.has(row.id)) continue;
|
|
220143
221082
|
if (!this.agents.has(row.agentId)) {
|
|
220144
221083
|
this.log.warn(`durable inbox: skipping replay of ${row.id} — unknown agent "${row.agentId}"`);
|
|
220145
221084
|
continue;
|
|
220146
221085
|
}
|
|
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
221086
|
let msg;
|
|
220155
221087
|
let callMeta;
|
|
221088
|
+
let hookContext;
|
|
220156
221089
|
try {
|
|
220157
221090
|
msg = JSON.parse(row.msg);
|
|
220158
221091
|
callMeta = row.callMeta ? JSON.parse(row.callMeta) : void 0;
|
|
221092
|
+
hookContext = row.hookContext ? JSON.parse(row.hookContext) : void 0;
|
|
220159
221093
|
} catch (err) {
|
|
220160
221094
|
this.log.warn(`durable inbox: skipping corrupt row ${row.id}: ${err.message}`);
|
|
220161
221095
|
continue;
|
|
220162
221096
|
}
|
|
221097
|
+
if (msg.source === "hook" && !hookContext) {
|
|
221098
|
+
this.log.warn(`durable inbox: tombstoning legacy hook row ${row.id} without trusted context`);
|
|
221099
|
+
this.store.removeInbox(row.id);
|
|
221100
|
+
continue;
|
|
221101
|
+
}
|
|
221102
|
+
if (this.paused(row.agentId)) {
|
|
221103
|
+
if (!purgedPausedAgents.has(row.agentId)) {
|
|
221104
|
+
purgedPausedAgents.add(row.agentId);
|
|
221105
|
+
this.purgeAgentInbox(row.agentId);
|
|
221106
|
+
}
|
|
221107
|
+
if (hookContext) this.emitHookCompletion(hookContext, "failed", { reason: "pause" }, { inboxId: row.id });
|
|
221108
|
+
continue;
|
|
221109
|
+
}
|
|
220163
221110
|
if (isMalformedPlatformTurn(msg) && !msg.isDm) {
|
|
220164
221111
|
this.store.removeInbox(row.id);
|
|
220165
221112
|
continue;
|
|
220166
221113
|
}
|
|
220167
221114
|
const loopScope = loopGuardScope(msg);
|
|
220168
|
-
if (usesLoopGuard(msg) && this.store.isLoopGuardOpen(loopScope)) {
|
|
221115
|
+
if ((usesLoopGuard(msg) || hookContext !== void 0) && this.store.isLoopGuardOpen(loopScope)) {
|
|
220169
221116
|
if (!purgedLoopScopes.has(loopScope)) {
|
|
220170
221117
|
purgedLoopScopes.add(loopScope);
|
|
220171
221118
|
this.purgeLoopScopeInbox(loopScope);
|
|
220172
221119
|
}
|
|
221120
|
+
if (hookContext) this.emitHookCompletion(hookContext, "failed", { reason: "loop protection" }, { inboxId: row.id });
|
|
220173
221121
|
continue;
|
|
220174
221122
|
}
|
|
221123
|
+
const posterPublishState = row.posterPublishState === "in_flight" || row.posterPublishState === "settled" ? row.posterPublishState : hookContext?.githubReply ? "not_started" : void 0;
|
|
220175
221124
|
this.dispatch(row.agentId, msg, row.integrationId ?? void 0, void 0, callMeta, {
|
|
220176
221125
|
...row.isQueueCmd ? { isQueueCmd: true } : {},
|
|
220177
221126
|
fromInboxReplay: true,
|
|
220178
221127
|
inboxReplayId: row.id,
|
|
220179
|
-
replay: row.loopGuardCounted === 1
|
|
220180
|
-
|
|
221128
|
+
replay: row.loopGuardCounted === 1,
|
|
221129
|
+
adoptExistingInbox: true,
|
|
221130
|
+
...hookContext ? { requireDurable: true } : {}
|
|
221131
|
+
}, hookContext?.githubReply, hookContext, posterPublishState).catch(() => {});
|
|
220181
221132
|
replayed++;
|
|
220182
221133
|
}
|
|
220183
221134
|
if (replayed) this.log.info(`durable inbox: replayed ${replayed} admitted message(s) through the serial gate`);
|
|
220184
221135
|
}
|
|
221136
|
+
/** Re-assert retained metadata-only hook completions whenever the CP socket
|
|
221137
|
+
* becomes ready. HookRepo applies them idempotently; keeping the receipt also
|
|
221138
|
+
* closes the completed-before-restart relay redelivery window. */
|
|
221139
|
+
replayHookTerminalReports() {
|
|
221140
|
+
let rows;
|
|
221141
|
+
try {
|
|
221142
|
+
rows = this.store.listInboxBySessionKeyFifo();
|
|
221143
|
+
} catch (err) {
|
|
221144
|
+
this.log.warn(`durable inbox: terminal report read failed: ${formatErr(err)}`);
|
|
221145
|
+
this.scheduleHookReportRetry();
|
|
221146
|
+
return;
|
|
221147
|
+
}
|
|
221148
|
+
const pending = rows.filter((row) => row.terminalReport && row.legacyReportConnection !== this.hookReportConnectionId && !this.hookReportInflight.has(row.id));
|
|
221149
|
+
const available = Math.max(0, MAX_HOOK_REPORT_INFLIGHT - this.hookReportInflight.size);
|
|
221150
|
+
const batch = pending.slice(0, available);
|
|
221151
|
+
let emitted = 0;
|
|
221152
|
+
for (const row of batch) try {
|
|
221153
|
+
const decoded = HookReport.parse(JSON.parse(row.terminalReport));
|
|
221154
|
+
this.sendHookReport(decoded, row.id);
|
|
221155
|
+
emitted += 1;
|
|
221156
|
+
} catch (err) {
|
|
221157
|
+
this.log.warn(`durable inbox: corrupt terminal hook receipt ${row.id}: ${formatErr(err)}`);
|
|
221158
|
+
try {
|
|
221159
|
+
this.store.acknowledgeHookInbox(row.id);
|
|
221160
|
+
} catch (cleanupError) {
|
|
221161
|
+
this.log.warn(`durable inbox: corrupt receipt cleanup failed for ${row.id}: ${formatErr(cleanupError)}`);
|
|
221162
|
+
}
|
|
221163
|
+
}
|
|
221164
|
+
if (emitted) this.log.info(`durable inbox: re-emitted ${emitted} terminal hook report(s)`);
|
|
221165
|
+
}
|
|
220185
221166
|
/** daemon/restart + daemon/upgrade (§8.3): ack now, then drain + stop + exit so
|
|
220186
221167
|
* the supervisor relaunches (the new binary, for upgrade). */
|
|
220187
221168
|
scheduleFleetExit(kind, targetVersion) {
|
|
@@ -220438,7 +221419,7 @@ var Daemon = class Daemon {
|
|
|
220438
221419
|
* still runs; a `headless` msg additionally suppresses all platform output
|
|
220439
221420
|
* in dispatch. Resolves with dispatch's sessionId (null = gate-dropped).
|
|
220440
221421
|
*/
|
|
220441
|
-
async
|
|
221422
|
+
async anchorTrigger(agentId, msg, target, anchorText, label) {
|
|
220442
221423
|
if (this.draining || this.drainingAgents.has(agentId) || this.paused(agentId) || this.safetyDrainingAgents.has(agentId)) {
|
|
220443
221424
|
this.log.info(`${label}: skipped for agent "${agentId}" (paused or draining)`);
|
|
220444
221425
|
return null;
|
|
@@ -220456,7 +221437,12 @@ var Daemon = class Daemon {
|
|
|
220456
221437
|
this.log.warn(`${label}: failed to post trigger to ${target.channel} (${formatErr(err)}) — running without anchor`);
|
|
220457
221438
|
}
|
|
220458
221439
|
}
|
|
220459
|
-
return
|
|
221440
|
+
return msg;
|
|
221441
|
+
}
|
|
221442
|
+
async fireTrigger(agentId, msg, target, anchorText, label, githubReply, hookContext) {
|
|
221443
|
+
const anchored = await this.anchorTrigger(agentId, msg, target, anchorText, label);
|
|
221444
|
+
if (!anchored) return null;
|
|
221445
|
+
return this.dispatch(agentId, anchored, target?.integrationId, void 0, void 0, void 0, githubReply, hookContext);
|
|
220460
221446
|
}
|
|
220461
221447
|
/** Build the per-turn GitHub final-answer selector and poster, tokened
|
|
220462
221448
|
* via the repo-targeted gitcred mint (issues/PR write, no contents — never
|
|
@@ -220590,6 +221576,8 @@ var Daemon = class Daemon {
|
|
|
220590
221576
|
mcpServerFacts: () => this.mcpServerFacts,
|
|
220591
221577
|
onReady: () => {
|
|
220592
221578
|
this.probeRuntimesAndEmit();
|
|
221579
|
+
this.hookReportConnectionId = randomUUID();
|
|
221580
|
+
this.replayHookTerminalReports();
|
|
220593
221581
|
for (const [integrationId, channels] of this.channelSnapshots) this.cpClient?.emitIntegrationChannels({
|
|
220594
221582
|
integrationId,
|
|
220595
221583
|
channels
|
|
@@ -220711,6 +221699,10 @@ var Daemon = class Daemon {
|
|
|
220711
221699
|
this.coldCancelTimers.clear();
|
|
220712
221700
|
for (const t of this.slackRetryTimers.values()) this.clock.clearTimeout(t);
|
|
220713
221701
|
this.slackRetryTimers.clear();
|
|
221702
|
+
if (this.hookReportRetryTimer !== void 0) {
|
|
221703
|
+
this.clock.clearTimeout(this.hookReportRetryTimer);
|
|
221704
|
+
this.hookReportRetryTimer = void 0;
|
|
221705
|
+
}
|
|
220714
221706
|
for (const t of this.orchestrationDeadlines.values()) this.clock.clearTimeout(t);
|
|
220715
221707
|
this.orchestrationDeadlines.clear();
|
|
220716
221708
|
this.metrics?.stop();
|
|
@@ -220772,4 +221764,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
220772
221764
|
//#endregion
|
|
220773
221765
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
220774
221766
|
|
|
220775
|
-
//# sourceMappingURL=run-foreground-
|
|
221767
|
+
//# sourceMappingURL=run-foreground-sX3AL91D.js.map
|