@agentconnect.md/setup 1.37.0-rc.3 → 1.37.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +83 -1142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36023,20 +36023,6 @@ const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def) => {
|
|
|
36023
36023
|
inst.keyType = def.keyType;
|
|
36024
36024
|
inst.valueType = def.valueType;
|
|
36025
36025
|
});
|
|
36026
|
-
function record$1(keyType, valueType, params) {
|
|
36027
|
-
if (!valueType || !valueType._zod) return new ZodRecord({
|
|
36028
|
-
type: "record",
|
|
36029
|
-
keyType: string(),
|
|
36030
|
-
valueType: keyType,
|
|
36031
|
-
...normalizeParams(valueType)
|
|
36032
|
-
});
|
|
36033
|
-
return new ZodRecord({
|
|
36034
|
-
type: "record",
|
|
36035
|
-
keyType,
|
|
36036
|
-
valueType,
|
|
36037
|
-
...normalizeParams(params)
|
|
36038
|
-
});
|
|
36039
|
-
}
|
|
36040
36026
|
function partialRecord(keyType, valueType, params) {
|
|
36041
36027
|
const k = clone(keyType);
|
|
36042
36028
|
k._zod.values = void 0;
|
|
@@ -36293,1115 +36279,16 @@ function number(params) {
|
|
|
36293
36279
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
36294
36280
|
}
|
|
36295
36281
|
//#endregion
|
|
36296
|
-
//#region ../protocol/dist/
|
|
36282
|
+
//#region ../protocol/dist/slack-app-manifest.js
|
|
36297
36283
|
var import_fastify = /* @__PURE__ */ __toESM(require_fastify(), 1);
|
|
36298
|
-
object({
|
|
36299
|
-
v: literal(1),
|
|
36300
|
-
id: string().uuid(),
|
|
36301
|
-
ts: string().datetime(),
|
|
36302
|
-
type: string(),
|
|
36303
|
-
corr: string().uuid().optional(),
|
|
36304
|
-
payload: unknown()
|
|
36305
|
-
});
|
|
36306
|
-
object({
|
|
36307
|
-
epoch: number$1().int(),
|
|
36308
|
-
agentId: string().uuid().optional(),
|
|
36309
|
-
launchId: string().uuid().optional()
|
|
36310
|
-
});
|
|
36311
|
-
/**
|
|
36312
|
-
* Builds the envelope schema for one frame `type` — the `type` literal plus the
|
|
36313
|
-
* typed payload — so a wire's discriminated union infers `payload` precisely.
|
|
36314
|
-
* Shared by the daemon↔CP union (`frame.ts`) and the relay wire unions
|
|
36315
|
-
* (`frames/relay-cp.ts` / `frames/relay-daemon.ts`), which are deliberately
|
|
36316
|
-
* SEPARATE unions over the same envelope shape (shared-bot-relay.md §8).
|
|
36317
|
-
*/
|
|
36318
|
-
function frameSchema(type, payload) {
|
|
36319
|
-
return object({
|
|
36320
|
-
v: literal(1),
|
|
36321
|
-
id: string().uuid(),
|
|
36322
|
-
ts: string().datetime(),
|
|
36323
|
-
type: literal(type),
|
|
36324
|
-
corr: string().uuid().optional(),
|
|
36325
|
-
payload
|
|
36326
|
-
});
|
|
36327
|
-
}
|
|
36328
|
-
const ErrorFrame = object({
|
|
36329
|
-
code: _enum([
|
|
36330
|
-
"UNKNOWN_FRAME",
|
|
36331
|
-
"FRAME_TOO_LARGE",
|
|
36332
|
-
"PROTOCOL_STATE",
|
|
36333
|
-
"BAD_PAYLOAD",
|
|
36334
|
-
"AUTH_FAILED",
|
|
36335
|
-
"ATTESTATION_INVALID",
|
|
36336
|
-
"STALE_EPOCH",
|
|
36337
|
-
"STALE_LAUNCH",
|
|
36338
|
-
"NO_SESSION",
|
|
36339
|
-
"SCOPE_DENIED",
|
|
36340
|
-
"DELEGATION_DENIED",
|
|
36341
|
-
"INVOCATION_CONFLICT",
|
|
36342
|
-
"LEASE_EXPIRED",
|
|
36343
|
-
"LEASE_DENIED",
|
|
36344
|
-
"RATE_LIMITED",
|
|
36345
|
-
"CONFLICT",
|
|
36346
|
-
"INTERNAL"
|
|
36347
|
-
]),
|
|
36348
|
-
message: string(),
|
|
36349
|
-
retryable: boolean(),
|
|
36350
|
-
details: record$1(string(), unknown()).optional()
|
|
36351
|
-
});
|
|
36352
|
-
//#endregion
|
|
36353
|
-
//#region ../protocol/dist/frames/route.js
|
|
36354
|
-
const Platform = string().min(1);
|
|
36355
|
-
const SessionKey = object({
|
|
36356
|
-
platform: Platform,
|
|
36357
|
-
channel: string(),
|
|
36358
|
-
thread: string().optional()
|
|
36359
|
-
});
|
|
36360
|
-
/** Trigger-matching rule for a binding (protocol §5.1). */
|
|
36361
|
-
const BindRule = object({ match: discriminatedUnion("kind", [
|
|
36362
|
-
object({ kind: literal("mention") }),
|
|
36363
|
-
object({ kind: literal("dm") }),
|
|
36364
|
-
object({
|
|
36365
|
-
kind: literal("keyword"),
|
|
36366
|
-
value: string()
|
|
36367
|
-
}),
|
|
36368
|
-
object({ kind: literal("auto") })
|
|
36369
|
-
]) });
|
|
36370
|
-
object({
|
|
36371
|
-
sessionKey: SessionKey,
|
|
36372
|
-
agentId: string().uuid(),
|
|
36373
|
-
workspaceId: string().uuid(),
|
|
36374
|
-
bindRules: array(BindRule).default([])
|
|
36375
|
-
});
|
|
36376
|
-
object({
|
|
36377
|
-
ok: boolean(),
|
|
36378
|
-
sessionKey: SessionKey,
|
|
36379
|
-
reason: string().optional()
|
|
36380
|
-
});
|
|
36381
|
-
object({
|
|
36382
|
-
routingEpoch: number$1().int(),
|
|
36383
|
-
rules: array(object({
|
|
36384
|
-
match: unknown(),
|
|
36385
|
-
agentId: string().uuid()
|
|
36386
|
-
}))
|
|
36387
|
-
});
|
|
36388
|
-
object({
|
|
36389
|
-
scope: union([
|
|
36390
|
-
object({
|
|
36391
|
-
kind: literal("agent"),
|
|
36392
|
-
agentId: string().uuid()
|
|
36393
|
-
}),
|
|
36394
|
-
object({ kind: literal("daemon") }),
|
|
36395
|
-
object({
|
|
36396
|
-
kind: literal("session"),
|
|
36397
|
-
sessionKey: SessionKey
|
|
36398
|
-
})
|
|
36399
|
-
]),
|
|
36400
|
-
deadline: string().datetime()
|
|
36401
|
-
});
|
|
36402
|
-
object({
|
|
36403
|
-
remaining: number$1().int(),
|
|
36404
|
-
drained: array(SessionKey)
|
|
36405
|
-
});
|
|
36406
|
-
object({ released: array(SessionKey) });
|
|
36407
|
-
//#endregion
|
|
36408
|
-
//#region ../protocol/dist/frames/integration.js
|
|
36409
|
-
/**
|
|
36410
|
-
* Platform integration distribution (C→D) — the Slack "install" flow.
|
|
36411
|
-
*
|
|
36412
|
-
* The Control Plane is the source of truth for platform integrations and pushes
|
|
36413
|
-
* them to the daemon that owns the integration's agent (`integration/upsert`, and
|
|
36414
|
-
* the reconcile snapshot `RegisterOk.integrations[]`). The daemon opens the Socket
|
|
36415
|
-
* Mode connection from the delivered config (see slack/connection.ts).
|
|
36416
|
-
*
|
|
36417
|
-
* SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
|
|
36418
|
-
* platform tokens (botToken/appToken/appSecret). These payloads MUST NEVER be
|
|
36419
|
-
* logged — no body dump on decode error, no register/ok snapshot debug dump.
|
|
36420
|
-
* CP-owned integrations live only in daemon MEMORY and are re-converged on
|
|
36421
|
-
* reconnect; hand-authored agents keep tokens in their own `agent.json` (the
|
|
36422
|
-
* same trust boundary as the daemon process).
|
|
36423
|
-
*
|
|
36424
|
-
* `signingSecret` is intentionally absent: Socket Mode authenticates with the
|
|
36425
|
-
* app-level token, so the daemon never needs a signing secret.
|
|
36426
|
-
*/
|
|
36427
|
-
/** Trigger match — mirrors the daemon BindRuleConfig.match (agents/agent-schema.ts). */
|
|
36428
|
-
const BindMatch = discriminatedUnion("kind", [
|
|
36429
|
-
object({ kind: literal("mention") }),
|
|
36430
|
-
object({ kind: literal("dm") }),
|
|
36431
|
-
object({
|
|
36432
|
-
kind: literal("keyword"),
|
|
36433
|
-
value: string()
|
|
36434
|
-
}),
|
|
36435
|
-
object({ kind: literal("auto") })
|
|
36436
|
-
]);
|
|
36437
|
-
/** One channel/thread trigger binding — mirrors the daemon BindRuleConfig. */
|
|
36438
|
-
const IntegrationBindRule = object({
|
|
36439
|
-
channel: string().optional(),
|
|
36440
|
-
thread: string().optional(),
|
|
36441
|
-
match: BindMatch
|
|
36442
|
-
});
|
|
36443
|
-
object({
|
|
36444
|
-
botToken: string(),
|
|
36445
|
-
appToken: string().optional(),
|
|
36446
|
-
appId: string().optional(),
|
|
36447
|
-
shareable: boolean().default(false),
|
|
36448
|
-
botUserId: string().optional()
|
|
36449
|
-
});
|
|
36450
|
-
object({ botToken: string() });
|
|
36451
|
-
object({
|
|
36452
|
-
botToken: string(),
|
|
36453
|
-
applicationId: string().optional()
|
|
36454
|
-
});
|
|
36455
|
-
/**
|
|
36456
|
-
* The Feishu / Lark config payload — the long-connection WebSocket
|
|
36457
|
-
* (`@larksuiteoapi/node-sdk` `WSClient`) + REST client. A Feishu self-built app
|
|
36458
|
-
* authenticates with an `appId` + `appSecret` PAIR — the SDK exchanges them for a
|
|
36459
|
-
* short-lived `tenant_access_token` internally (no Slack-style app-level token, no
|
|
36460
|
-
* signing secret). `appId` is a semi-public identifier (`cli_…`); `appSecret` is
|
|
36461
|
-
* plaintext secret material — NEVER log it. `botOpenId` is the bot's own open_id
|
|
36462
|
-
* for @-mention routing; lazily resolved by the daemon via `bot/info` if absent.
|
|
36463
|
-
*
|
|
36464
|
-
* The envelope's `mode` decides the transport: `direct` opens the SDK long
|
|
36465
|
-
* connection on the daemon; `shared` keeps only the authenticated REST client
|
|
36466
|
-
* (callbacks arrive through the relay, pre-addressed over rd/*).
|
|
36467
|
-
*
|
|
36468
|
-
* `region` selects the open-platform gateway the daemon SDK (and CP verifier)
|
|
36469
|
-
* talk to — `'feishu'` = mainland China (`open.feishu.cn`, the SDK default) vs
|
|
36470
|
-
* `'lark'` = international (`open.larksuite.com`). Same app model, different host;
|
|
36471
|
-
* an app is registered in exactly one region. Defaults to `'feishu'` so existing
|
|
36472
|
-
* installs are unaffected.
|
|
36473
|
-
*/
|
|
36474
|
-
const FeishuRegion = _enum(["feishu", "lark"]);
|
|
36475
|
-
object({
|
|
36476
|
-
appId: string(),
|
|
36477
|
-
appSecret: string(),
|
|
36478
|
-
botOpenId: string().optional(),
|
|
36479
|
-
region: FeishuRegion.default("feishu")
|
|
36480
|
-
});
|
|
36481
|
-
/**
|
|
36482
|
-
* §6.4 core routing ENVELOPE (integration-plugin-architecture.md D4): the knobs CORE
|
|
36483
|
-
* reads — routing, gating, ingress mode — platform-independent. This is the ONLY
|
|
36484
|
-
* carrier of these knobs on the wire: the opaque per-platform `config` payload
|
|
36485
|
-
* never duplicates them (the daemon reads routing exclusively from here).
|
|
36486
|
-
*/
|
|
36487
|
-
const IntegrationCoreEnvelope = object({
|
|
36488
|
-
mode: _enum(["direct", "shared"]).default("direct"),
|
|
36489
|
-
bindRules: array(IntegrationBindRule).default([]),
|
|
36490
|
-
mutedChannels: array(string()).default([]),
|
|
36491
|
-
gated: boolean().default(false)
|
|
36492
|
-
});
|
|
36493
|
-
object({
|
|
36494
|
-
integrationId: string().uuid(),
|
|
36495
|
-
agentId: string().uuid(),
|
|
36496
|
-
platform: Platform,
|
|
36497
|
-
core: IntegrationCoreEnvelope,
|
|
36498
|
-
config: unknown().optional()
|
|
36499
|
-
});
|
|
36500
|
-
object({ integrationId: string().uuid() });
|
|
36501
|
-
/**
|
|
36502
|
-
* One conversation the bot participates in (metadata only — no messages).
|
|
36503
|
-
* `kind` distinguishes member channels from direct conversations (resource-
|
|
36504
|
-
* visibility.md §14.3): absent = 'channel' for wire compatibility. DM rows
|
|
36505
|
-
* (`kind: 'im'`, Slack "D…" ids) are reported for every integration on first
|
|
36506
|
-
* inbound DM; their `name` is the counterpart's display name. Group DMs
|
|
36507
|
-
* (`kind: 'mpim'`, Slack multi-person DMs) are reported on observation the same
|
|
36508
|
-
* way — never enumerated, because Slack does not list them as bot membership —
|
|
36509
|
-
* but they behave like a channel: several humans share the room, so the agent
|
|
36510
|
-
* stays mention-gated there rather than answering every message.
|
|
36511
|
-
*
|
|
36512
|
-
* `spaceId`/`space` identify the container the conversation lives in — a Discord
|
|
36513
|
-
* GUILD, which a bot in several servers needs for the channel to be identifiable at
|
|
36514
|
-
* all (every server has a "#general"). The ID is the identity: two distinct guilds
|
|
36515
|
-
* may carry the SAME name, so grouping on the name alone would merge them and hide
|
|
36516
|
-
* the ambiguity it was meant to resolve. `space` is the display label only. Both are
|
|
36517
|
-
* absent on platforms with one implicit container per bot (Slack workspace, Telegram,
|
|
36518
|
-
* Feishu tenant) and on DM rows.
|
|
36519
|
-
*/
|
|
36520
|
-
const IntegrationChannel = object({
|
|
36521
|
-
id: string(),
|
|
36522
|
-
name: string().optional(),
|
|
36523
|
-
spaceId: string().optional(),
|
|
36524
|
-
space: string().optional(),
|
|
36525
|
-
isPrivate: boolean().optional(),
|
|
36526
|
-
kind: _enum([
|
|
36527
|
-
"channel",
|
|
36528
|
-
"im",
|
|
36529
|
-
"mpim"
|
|
36530
|
-
]).optional()
|
|
36531
|
-
});
|
|
36532
|
-
object({
|
|
36533
|
-
integrationId: string().uuid(),
|
|
36534
|
-
channels: array(IntegrationChannel),
|
|
36535
|
-
authoritative: boolean().optional(),
|
|
36536
|
-
removed: array(string()).optional()
|
|
36537
|
-
});
|
|
36538
|
-
/**
|
|
36539
|
-
* What a leave targets. Platforms disagree about what a bot can withdraw from, and
|
|
36540
|
-
* the difference is not cosmetic — so the caller has to say which it means rather
|
|
36541
|
-
* than the daemon guessing from an id:
|
|
36542
|
-
*
|
|
36543
|
-
* - `conversation` — one channel/group the bot is a member of (Slack
|
|
36544
|
-
* `conversations.leave`, Telegram `leaveChat`).
|
|
36545
|
-
* - `space` — the whole container. Discord has no per-channel membership for a
|
|
36546
|
-
* bot at all: it is in a GUILD and sees that guild's channels through
|
|
36547
|
-
* permissions, so the only thing it can leave is the entire server. That is a
|
|
36548
|
-
* much larger action than leaving one channel and must be requested as such.
|
|
36549
|
-
*/
|
|
36550
|
-
const IntegrationLeaveTarget = discriminatedUnion("kind", [object({
|
|
36551
|
-
kind: literal("conversation"),
|
|
36552
|
-
channel: string().min(1)
|
|
36553
|
-
}), object({
|
|
36554
|
-
kind: literal("space"),
|
|
36555
|
-
spaceId: string().min(1)
|
|
36556
|
-
})]);
|
|
36557
|
-
object({
|
|
36558
|
-
integrationId: string().uuid(),
|
|
36559
|
-
target: IntegrationLeaveTarget
|
|
36560
|
-
});
|
|
36561
|
-
object({
|
|
36562
|
-
integrationId: string().uuid(),
|
|
36563
|
-
channels: array(string()).min(1)
|
|
36564
|
-
});
|
|
36565
|
-
object({
|
|
36566
|
-
ok: boolean(),
|
|
36567
|
-
error: string().optional()
|
|
36568
|
-
});
|
|
36569
|
-
//#endregion
|
|
36570
|
-
//#region ../protocol/dist/frames/cron.js
|
|
36571
|
-
/**
|
|
36572
|
-
* Cron sinks to the daemon (D5) — protocol §5.4.
|
|
36573
|
-
*
|
|
36574
|
-
* A cron periodically triggers ONE AGENT with a synthetic prompt (`trigger`) to
|
|
36575
|
-
* carry out some work. The CP owns the definition; the daemon owns firing +
|
|
36576
|
-
* last-run persistence, so crons fire even when the CP is down. On receipt the
|
|
36577
|
-
* daemon persists the def into the owning agent's `agent.json` `crons[]` (the
|
|
36578
|
-
* single source of truth, same model as integrations) — surviving a restart
|
|
36579
|
-
* with the CP down.
|
|
36580
|
-
*
|
|
36581
|
-
* `target` is OPTIONAL output routing: when present, the daemon posts the
|
|
36582
|
-
* trigger as a real message in that channel and the agent's session replies in
|
|
36583
|
-
* its thread; when absent the fire is headless (the agent works with no
|
|
36584
|
-
* platform output).
|
|
36585
|
-
*/
|
|
36586
|
-
const CronTarget = object({
|
|
36587
|
-
platform: Platform.default("slack"),
|
|
36588
|
-
channel: string(),
|
|
36589
|
-
integrationId: string().uuid().optional()
|
|
36590
|
-
});
|
|
36591
|
-
object({
|
|
36592
|
-
cronId: string().uuid(),
|
|
36593
|
-
agentId: string().uuid(),
|
|
36594
|
-
schedule: string(),
|
|
36595
|
-
timezone: string().min(1),
|
|
36596
|
-
target: CronTarget.optional(),
|
|
36597
|
-
trigger: string(),
|
|
36598
|
-
enabled: boolean().default(true)
|
|
36599
|
-
});
|
|
36600
|
-
object({ cronId: string().uuid() });
|
|
36601
|
-
/**
|
|
36602
|
-
* `cron/report` (D→C EVT, fire-and-forget) — one CP-owned cron fired. The
|
|
36603
|
-
* daemon stamps the fire into its local store first (it stays authoritative,
|
|
36604
|
-
* §5.4) and reports it here so the console's `lastRunAt` converges; the CP
|
|
36605
|
-
* upsert is latest-wins, so the daemon re-asserting its stored stamps on
|
|
36606
|
-
* reconnect (fires while the CP was unreachable) is idempotent and can never
|
|
36607
|
-
* regress the value. Hand-authored (no-origin) crons are never reported.
|
|
36608
|
-
*
|
|
36609
|
-
* Reports are keyed by `(cronId, firedAt)`: the FIRE report (no `status`)
|
|
36610
|
-
* opens the run, an optional SESSION report attaches the ACP session as soon
|
|
36611
|
-
* as it is initialized, and the COMPLETION report (with `status` + outcome
|
|
36612
|
-
* fields) closes the run once the dispatched turn ends. A completion without
|
|
36613
|
-
* a prior fire report (CP was down at fire time) still creates the run row.
|
|
36614
|
-
*/
|
|
36615
|
-
const CronRunStatus = _enum(["success", "failed"]);
|
|
36616
|
-
object({
|
|
36617
|
-
cronId: string().uuid(),
|
|
36618
|
-
agentId: string().uuid(),
|
|
36619
|
-
firedAt: string().datetime(),
|
|
36620
|
-
status: CronRunStatus.optional(),
|
|
36621
|
-
durationMs: number$1().int().nonnegative().optional(),
|
|
36622
|
-
sessionId: string().optional(),
|
|
36623
|
-
reason: string().optional()
|
|
36624
|
-
});
|
|
36625
|
-
object({ cronId: string().uuid() });
|
|
36626
|
-
//#endregion
|
|
36627
|
-
//#region ../protocol/dist/frames/collab.js
|
|
36628
|
-
/**
|
|
36629
|
-
* Bot-AGNOSTIC agent-collaboration routing snapshot (agent-collaboration §2.3 / §6.2 / §6.5).
|
|
36630
|
-
*
|
|
36631
|
-
* The existing shared-bot `members` table is keyed by botId (`BotAssignment`) and
|
|
36632
|
-
* CANNOT address an agent on a DIFFERENT bot / arbitrary channel. This snapshot is
|
|
36633
|
-
* the fix: it maps a channel — `(orgId, platform, channelId)` — to the per-agent
|
|
36634
|
-
* placement + call policy the relay needs to route a cross-daemon `rd/agentmsg` and
|
|
36635
|
-
* the target daemon needs to terminal-verify a remote caller.
|
|
36636
|
-
*
|
|
36637
|
-
* It carries NO message body — pure routing/policy metadata, like the rest of the
|
|
36638
|
-
* control plane. The SAME shape is distributed two ways (§6.5):
|
|
36639
|
-
* - CP→relay over the `rc/*` wire (`rc/collab-routes`) — the relay routes
|
|
36640
|
-
* `toAgentId` → owning `daemonId` and authorizes the caller/target policy.
|
|
36641
|
-
* - CP→daemon over the daemon↔CP wire — as a `register/ok` field (reconnect
|
|
36642
|
-
* baseline) + a `collaboration/routes` EVT (hot push) — so the OWNING daemon of
|
|
36643
|
-
* the target can terminal-verify (defense in depth, §2.5 #4) the remote caller's
|
|
36644
|
-
* org/channel/placement against its OWN copy, never trusting the relay's claim
|
|
36645
|
-
* blindly.
|
|
36646
|
-
*
|
|
36647
|
-
* FOLLOW-UP (scoped down in P2, see PR description): the full versioned lifecycle
|
|
36648
|
-
* of §6.5 — per-entry tombstones, TTL/expiry after a CP disconnect, and
|
|
36649
|
-
* fail-closed-on-stale — is NOT fully implemented here. `generation` is present as
|
|
36650
|
-
* the version hook and the snapshot is FULL-REPLACE (converge-don't-diff, same as
|
|
36651
|
-
* `register/ok`), which is enough to route + authorize on a live CP. TTL-expiry and
|
|
36652
|
-
* tombstone semantics are a follow-up within this phase.
|
|
36653
|
-
*/
|
|
36654
|
-
/** One agent's placement + call policy within a channel. `daemonId` is the owning
|
|
36655
|
-
* daemon the relay forwards to; `integrationId` is the DEFINITE reply integration
|
|
36656
|
-
* (§6.2 — no fallback to "first connection"). */
|
|
36657
|
-
const CollabAgentPlacement = object({
|
|
36658
|
-
agentId: string().uuid(),
|
|
36659
|
-
daemonId: string().uuid(),
|
|
36660
|
-
integrationId: string().uuid().optional(),
|
|
36661
|
-
/** Public Slack app id (`A…`) for this agent's bot. Receivers use it to recognize
|
|
36662
|
-
* AgentConnect-authored platform messages — the first half of verifying an
|
|
36663
|
-
* authorship claim (send-message-routing-rework.md §4). */
|
|
36664
|
-
botAppId: string().optional(),
|
|
36665
|
-
/** Public platform member id this agent's bot posts as (Slack `U…`) — the id a
|
|
36666
|
-
* `<@…>` mention resolves to. With {@link botShared} and `name` it is the complete
|
|
36667
|
-
* input for the conversation-specific mention ADDRESS
|
|
36668
|
-
* (send-message-routing-rework.md §8.5), which both directions need: rendering an
|
|
36669
|
-
* exact address to post, and resolving an inbound mention back to an agent.
|
|
36670
|
-
* Public metadata, never a credential. */
|
|
36671
|
-
botUserId: string().optional(),
|
|
36672
|
-
/** True when {@link botUserId} backs MORE THAN ONE agent (a shared bot). The bot user
|
|
36673
|
-
* id alone then cannot identify an agent, so its address carries the agent slug too
|
|
36674
|
-
* (`<@U_SHARED> reviewer`) and a BARE mention of it addresses no specific agent —
|
|
36675
|
-
* which is why a shared bot with no exact author claim fails closed at ingress (§4)
|
|
36676
|
-
* and why a bare shared-bot mention from an agent must not fall back to the channel
|
|
36677
|
-
* default (§6). */
|
|
36678
|
-
botShared: boolean().optional(),
|
|
36679
|
-
callPolicy: _enum(["all", "selected"]).default("all"),
|
|
36680
|
-
allowedCallerAgentIds: array(string()).default([]),
|
|
36681
|
-
/** Caller-side authorization. Effective A→B access requires A's outbound
|
|
36682
|
-
* policy to admit B and B's inbound call policy to admit A. */
|
|
36683
|
-
outboundPolicy: _enum(["all", "selected"]).default("all"),
|
|
36684
|
-
allowedTargetAgentIds: array(string()).default([]),
|
|
36685
|
-
name: string().optional(),
|
|
36686
|
-
displayName: string().optional()
|
|
36687
|
-
});
|
|
36688
|
-
/**
|
|
36689
|
-
* One agent's placement + call policy carried OUTSIDE any channel — the org-scoped
|
|
36690
|
-
* peer directory entry.
|
|
36691
|
-
*
|
|
36692
|
-
* Every structure on the CP→daemon and CP→relay wires is channel-keyed, so an agent
|
|
36693
|
-
* with NO IM integration (webchat, hook, dream, memory-only) never appears in any
|
|
36694
|
-
* `channels[]` entry at all. The channel-keyed snapshot structurally cannot express
|
|
36695
|
-
* "which agents exist in this org", which is precisely the input channel-free
|
|
36696
|
-
* authorization needs: discovery and A2A authorization depend only on the directional
|
|
36697
|
-
* call policy (`outboundPolicy`/`allowedTargetAgentIds` on the caller,
|
|
36698
|
-
* `callPolicy`/`allowedCallerAgentIds` on the target), org-scoped, with channel
|
|
36699
|
-
* demoted to an optional filter. Hence the flat list below.
|
|
36700
|
-
*/
|
|
36701
|
-
const CollabOrgAgent = CollabAgentPlacement.extend({ orgId: string().min(1) });
|
|
36702
|
-
/** All agents present in one channel, across daemons. `orgId` scopes routing +
|
|
36703
|
-
* authorization: a cross-org caller/target pair never resolves (§2.5 — cross-org
|
|
36704
|
-
* rejected). */
|
|
36705
|
-
const CollabChannelRoute = object({
|
|
36706
|
-
orgId: string().min(1),
|
|
36707
|
-
platform: Platform,
|
|
36708
|
-
channelId: string().min(1),
|
|
36709
|
-
agents: array(CollabAgentPlacement)
|
|
36710
|
-
});
|
|
36711
|
-
/**
|
|
36712
|
-
* The full collaboration snapshot — FULL-REPLACE (converge-don't-diff): the
|
|
36713
|
-
* recipient replaces its whole table with `channels`. `generation` monotonically
|
|
36714
|
-
* increases per source so a recipient can ignore a stale re-order (version hook for
|
|
36715
|
-
* the §6.5 lifecycle follow-up).
|
|
36716
|
-
*/
|
|
36717
|
-
const CollabRoutesSnapshot = object({
|
|
36718
|
-
generation: number$1().int().nonnegative().default(0),
|
|
36719
|
-
channels: array(CollabChannelRoute).default([]),
|
|
36720
|
-
/**
|
|
36721
|
-
* FLAT org-scoped directory, alongside (not instead of) `channels`. It is the only
|
|
36722
|
-
* place an integration-less agent can appear — see `CollabOrgAgent` — and therefore
|
|
36723
|
-
* the authorization input for channel-free A2A. `default([])` keeps a snapshot from
|
|
36724
|
-
* an older CP (which advertises no `agent-directory-org-scope-v1`) decodable.
|
|
36725
|
-
*/
|
|
36726
|
-
agents: array(CollabOrgAgent).default([]),
|
|
36727
|
-
/**
|
|
36728
|
-
* §6.1 origin-kind classification for the platform ids the EMITTER knows — how an
|
|
36729
|
-
* older peer classifies an id a newer CP introduces (its own built-in seed only
|
|
36730
|
-
* covers ids its build shipped with). Consumers overlay these entries on their
|
|
36731
|
-
* seed; an id classified by neither defaults to `'chat'`, the fail-closed answer
|
|
36732
|
-
* wherever placements matter (`coordsDecision`). Both fields read as open strings
|
|
36733
|
-
* per the S1a rule. `default([])` keeps a pre-S1b snapshot decodable.
|
|
36734
|
-
*/
|
|
36735
|
-
platformKinds: array(object({
|
|
36736
|
-
platformId: string().min(1),
|
|
36737
|
-
originKind: string().min(1)
|
|
36738
|
-
})).default([])
|
|
36739
|
-
});
|
|
36740
|
-
//#endregion
|
|
36741
|
-
//#region ../protocol/dist/frames/hook.js
|
|
36742
|
-
/** Decimal wire form for Prisma/GitHub bigint values. */
|
|
36743
|
-
const HookBigIntString = string().regex(/^(?:0|[1-9]\d*)$/);
|
|
36744
|
-
/** The highest formal-review event one hook permits. */
|
|
36745
|
-
const HookReviewPolicy = _enum([
|
|
36746
|
-
"off",
|
|
36747
|
-
"comment",
|
|
36748
|
-
"request_changes",
|
|
36749
|
-
"full"
|
|
36750
|
-
]);
|
|
36751
|
-
/** External run-reporting transport. `status` is reserved for the later R3. */
|
|
36752
|
-
const HookReportingMode = _enum([
|
|
36753
|
-
"off",
|
|
36754
|
-
"check",
|
|
36755
|
-
"status"
|
|
36756
|
-
]);
|
|
36757
|
-
/** R2a only admits informational; `required` remains a fail-closed future mode. */
|
|
36758
|
-
const HookGateMode = _enum(["informational", "required"]);
|
|
36759
|
-
/**
|
|
36760
|
-
* Exact config + dispatch identity captured when the CP compiles a hook fire.
|
|
36761
|
-
*
|
|
36762
|
-
* The relay-facing frames expose these fields individually at top level so an
|
|
36763
|
-
* older CP/relay can omit them without making the whole fire undecodable. A
|
|
36764
|
-
* consumer MUST have every field before enabling review/reporting; absence is
|
|
36765
|
-
* not filled with a revision and therefore fails closed. Action-time RPCs use
|
|
36766
|
-
* this complete object so a partially rolled-out tuple cannot authorize an
|
|
36767
|
-
* effect.
|
|
36768
|
-
*/
|
|
36769
|
-
const HookConfigSnapshot = object({
|
|
36770
|
-
configRevision: HookBigIntString,
|
|
36771
|
-
dispatchRevision: HookBigIntString,
|
|
36772
|
-
dispatchDaemonId: string().uuid(),
|
|
36773
|
-
reviewPolicy: HookReviewPolicy,
|
|
36774
|
-
reportingMode: HookReportingMode,
|
|
36775
|
-
gateMode: HookGateMode
|
|
36776
|
-
});
|
|
36777
|
-
/** Optional top-level form used only on rolling-compatible delivery frames. */
|
|
36778
|
-
const OptionalHookConfigSnapshot = HookConfigSnapshot.partial();
|
|
36779
|
-
/**
|
|
36780
|
-
* Trusted, body-free GitHub subject/revision metadata. It is deliberately
|
|
36781
|
-
* separate from HookContext: HookContext contains attacker-authored prompt
|
|
36782
|
-
* excerpts, whereas this object is cut from a signature-verified payload and
|
|
36783
|
-
* fenced to the CP-compiled numeric repo/installation rule.
|
|
36784
|
-
*
|
|
36785
|
-
* `headSha` is absent for PR issue_comment deliveries because GitHub omits the
|
|
36786
|
-
* revision there; the daemon resolves it before `hook/start`. R2a informational
|
|
36787
|
-
* checks use `reportSha=headSha` once known. Review-comment deliveries may also
|
|
36788
|
-
* identify the triggering comment and its normalized thread root; both remain
|
|
36789
|
-
* optional so mixed-version relay/daemon rollouts continue to decode.
|
|
36790
|
-
*/
|
|
36791
|
-
const GithubHookMetadata = object({
|
|
36792
|
-
repoId: HookBigIntString,
|
|
36793
|
-
repoFullName: string().min(1),
|
|
36794
|
-
sourceInstallationId: HookBigIntString,
|
|
36795
|
-
subjectKind: _enum(["issue", "pull_request"]),
|
|
36796
|
-
pullNumber: number$1().int().positive().optional(),
|
|
36797
|
-
headSha: string().min(1).optional(),
|
|
36798
|
-
baseSha: string().min(1).optional(),
|
|
36799
|
-
reportSha: string().min(1).optional(),
|
|
36800
|
-
headRepoFullName: string().min(1).optional(),
|
|
36801
|
-
mergeCommitSha: string().min(1).optional(),
|
|
36802
|
-
isDraft: boolean().optional(),
|
|
36803
|
-
baseChanged: boolean().optional(),
|
|
36804
|
-
explicitReviewRequest: boolean().optional(),
|
|
36805
|
-
reviewCommentId: HookBigIntString.optional(),
|
|
36806
|
-
reviewThreadRootCommentId: HookBigIntString.optional()
|
|
36807
|
-
}).superRefine((value, ctx) => {
|
|
36808
|
-
if (value.subjectKind === "pull_request" && value.pullNumber === void 0) ctx.addIssue({
|
|
36809
|
-
code: "custom",
|
|
36810
|
-
path: ["pullNumber"],
|
|
36811
|
-
message: "pullNumber is required for a pull_request subject"
|
|
36812
|
-
});
|
|
36813
|
-
if (value.reportSha !== void 0 && value.headSha === void 0) ctx.addIssue({
|
|
36814
|
-
code: "custom",
|
|
36815
|
-
path: ["reportSha"],
|
|
36816
|
-
message: "reportSha requires an authoritative headSha"
|
|
36817
|
-
});
|
|
36818
|
-
if (value.reportSha !== void 0 && value.headSha !== void 0 && value.reportSha !== value.headSha) ctx.addIssue({
|
|
36819
|
-
code: "custom",
|
|
36820
|
-
path: ["reportSha"],
|
|
36821
|
-
message: "R2a reports only the authoritative pull head SHA"
|
|
36822
|
-
});
|
|
36823
|
-
});
|
|
36824
|
-
const HookReviewEvent = _enum([
|
|
36825
|
-
"COMMENT",
|
|
36826
|
-
"REQUEST_CHANGES",
|
|
36827
|
-
"APPROVE"
|
|
36828
|
-
]);
|
|
36829
|
-
const HookReviewVerdict = _enum([
|
|
36830
|
-
"pass",
|
|
36831
|
-
"fail",
|
|
36832
|
-
"neutral"
|
|
36833
|
-
]);
|
|
36834
|
-
/** Short machine code only; raw GitHub/runtime exception text never crosses to reporting. */
|
|
36835
|
-
const HookReviewResultCode = string().min(1).max(100).regex(/^[a-z0-9_:-]+$/);
|
|
36836
|
-
/**
|
|
36837
|
-
* Body-free outcome of one reserved formal-review attempt.
|
|
36838
|
-
*
|
|
36839
|
-
* `not_submitted` is only legal after a deterministic no-effect failure and
|
|
36840
|
-
* lets the CP release the reservation. An uncertain POST remains `ambiguous`
|
|
36841
|
-
* until marker reconciliation proves whether GitHub created it.
|
|
36842
|
-
*/
|
|
36843
|
-
const HookReviewResult = discriminatedUnion("state", [
|
|
36844
|
-
object({
|
|
36845
|
-
state: literal("submitted"),
|
|
36846
|
-
reviewId: string().min(1),
|
|
36847
|
-
event: HookReviewEvent,
|
|
36848
|
-
verdict: HookReviewVerdict,
|
|
36849
|
-
commitId: string().min(1)
|
|
36850
|
-
}),
|
|
36851
|
-
object({
|
|
36852
|
-
state: literal("not_submitted"),
|
|
36853
|
-
code: HookReviewResultCode
|
|
36854
|
-
}),
|
|
36855
|
-
object({
|
|
36856
|
-
state: literal("ambiguous"),
|
|
36857
|
-
code: HookReviewResultCode
|
|
36858
|
-
})
|
|
36859
|
-
]);
|
|
36860
|
-
/** Body-free identity of the daemon's one fallback GitHub comment. */
|
|
36861
|
-
const GithubPublishedComment = object({
|
|
36862
|
-
kind: _enum(["issue_comment", "review_comment"]),
|
|
36863
|
-
commentId: HookBigIntString
|
|
36864
|
-
});
|
|
36865
|
-
object({
|
|
36866
|
-
source: _enum(["webhook", "github"]),
|
|
36867
|
-
event: string().optional(),
|
|
36868
|
-
action: string().optional(),
|
|
36869
|
-
repo: string().optional(),
|
|
36870
|
-
number: number$1().int().optional(),
|
|
36871
|
-
title: string().optional(),
|
|
36872
|
-
senderLogin: string().optional(),
|
|
36873
|
-
senderAvatarUrl: string().url().optional(),
|
|
36874
|
-
authorAssociation: string().optional(),
|
|
36875
|
-
labels: array(string()).optional(),
|
|
36876
|
-
htmlUrl: string().optional(),
|
|
36877
|
-
bodyExcerpt: string().optional(),
|
|
36878
|
-
body: string().optional(),
|
|
36879
|
-
truncated: boolean().optional()
|
|
36880
|
-
});
|
|
36881
|
-
object({
|
|
36882
|
-
hookId: string().uuid(),
|
|
36883
|
-
agentId: string().uuid(),
|
|
36884
|
-
deliveryKey: string().min(1),
|
|
36885
|
-
...OptionalHookConfigSnapshot.shape,
|
|
36886
|
-
event: string().min(1).optional(),
|
|
36887
|
-
github: GithubHookMetadata.optional(),
|
|
36888
|
-
status: _enum(["success", "failed"]),
|
|
36889
|
-
durationMs: number$1().int().nonnegative().optional(),
|
|
36890
|
-
sessionId: string().optional(),
|
|
36891
|
-
reason: string().optional(),
|
|
36892
|
-
reviewAttemptId: string().uuid().optional(),
|
|
36893
|
-
reviewResult: HookReviewResult.optional(),
|
|
36894
|
-
publishedComment: GithubPublishedComment.optional()
|
|
36895
|
-
}).superRefine((report, ctx) => {
|
|
36896
|
-
if (report.reviewAttemptId === void 0 !== (report.reviewResult === void 0)) ctx.addIssue({
|
|
36897
|
-
code: "custom",
|
|
36898
|
-
path: report.reviewAttemptId === void 0 ? ["reviewAttemptId"] : ["reviewResult"],
|
|
36899
|
-
message: "reviewAttemptId and reviewResult must be reported together"
|
|
36900
|
-
});
|
|
36901
|
-
if (report.publishedComment && report.reviewResult?.state === "submitted") ctx.addIssue({
|
|
36902
|
-
code: "custom",
|
|
36903
|
-
path: ["publishedComment"],
|
|
36904
|
-
message: "a submitted formal review and fallback comment are mutually exclusive"
|
|
36905
|
-
});
|
|
36906
|
-
});
|
|
36907
|
-
object({
|
|
36908
|
-
hookId: string().uuid(),
|
|
36909
|
-
agentId: string().uuid(),
|
|
36910
|
-
deliveryKey: string().min(1),
|
|
36911
|
-
sessionId: string().min(1).optional(),
|
|
36912
|
-
event: string().min(1).optional(),
|
|
36913
|
-
github: GithubHookMetadata,
|
|
36914
|
-
...HookConfigSnapshot.shape
|
|
36915
|
-
});
|
|
36916
|
-
object({ accepted: literal(true) });
|
|
36917
|
-
object({
|
|
36918
|
-
hookId: string().uuid(),
|
|
36919
|
-
deliveryKey: string().min(1),
|
|
36920
|
-
attemptId: string().uuid(),
|
|
36921
|
-
requestedEvent: HookReviewEvent,
|
|
36922
|
-
requestedVerdict: HookReviewVerdict,
|
|
36923
|
-
snapshot: HookConfigSnapshot
|
|
36924
|
-
});
|
|
36925
|
-
object({
|
|
36926
|
-
attemptId: string().uuid(),
|
|
36927
|
-
token: string().min(1),
|
|
36928
|
-
ttlSec: number$1().int().positive(),
|
|
36929
|
-
expiresAt: string().datetime(),
|
|
36930
|
-
repoId: HookBigIntString,
|
|
36931
|
-
repoFullName: string().min(1),
|
|
36932
|
-
pullNumber: number$1().int().positive(),
|
|
36933
|
-
expectedHeadSha: string().min(1),
|
|
36934
|
-
expectedBaseSha: string().min(1)
|
|
36935
|
-
});
|
|
36936
|
-
object({
|
|
36937
|
-
hookId: string().uuid(),
|
|
36938
|
-
deliveryKey: string().min(1),
|
|
36939
|
-
attemptId: string().uuid(),
|
|
36940
|
-
snapshot: HookConfigSnapshot,
|
|
36941
|
-
result: HookReviewResult
|
|
36942
|
-
});
|
|
36943
|
-
object({ accepted: literal(true) });
|
|
36944
|
-
//#endregion
|
|
36945
|
-
//#region ../protocol/dist/frames/remote-mcp.js
|
|
36946
|
-
/** Non-secret entitlement carried CP → relay → daemon after webchat verification. */
|
|
36947
|
-
const WebchatRemoteMcpEntitlement = object({
|
|
36948
|
-
authorityId: string().uuid(),
|
|
36949
|
-
authorityGeneration: number$1().int().positive(),
|
|
36950
|
-
expiresAt: string().datetime()
|
|
36951
|
-
}).strict();
|
|
36952
|
-
const DescriptorFence = object({
|
|
36953
|
-
authorityId: string().uuid(),
|
|
36954
|
-
authorityGeneration: number$1().int().positive(),
|
|
36955
|
-
conversationId: string().uuid(),
|
|
36956
|
-
descriptorInstanceId: string().uuid(),
|
|
36957
|
-
grantRevision: number$1().int().positive()
|
|
36958
|
-
}).strict();
|
|
36959
|
-
object({
|
|
36960
|
-
authorityId: string().uuid(),
|
|
36961
|
-
authorityGeneration: number$1().int().positive(),
|
|
36962
|
-
conversationId: string().uuid(),
|
|
36963
|
-
descriptorInstanceId: string().uuid()
|
|
36964
|
-
}).strict();
|
|
36965
|
-
DescriptorFence.extend({
|
|
36966
|
-
grantId: string().uuid(),
|
|
36967
|
-
token: string().min(32),
|
|
36968
|
-
expiresAt: string().datetime(),
|
|
36969
|
-
mcpUrl: string().url()
|
|
36970
|
-
}).strict();
|
|
36971
|
-
DescriptorFence.extend({ grantId: string().uuid() }).strict().extend({ activated: boolean() }).strict();
|
|
36972
|
-
object({
|
|
36973
|
-
authorityId: string().uuid(),
|
|
36974
|
-
authorityGeneration: number$1().int().positive(),
|
|
36975
|
-
conversationId: string().uuid(),
|
|
36976
|
-
reason: _enum([
|
|
36977
|
-
"session_closed",
|
|
36978
|
-
"session_expired",
|
|
36979
|
-
"agent_detached",
|
|
36980
|
-
"feature_disabled",
|
|
36981
|
-
"security"
|
|
36982
|
-
])
|
|
36983
|
-
}).strict().pick({
|
|
36984
|
-
authorityId: true,
|
|
36985
|
-
authorityGeneration: true,
|
|
36986
|
-
conversationId: true
|
|
36987
|
-
}).extend({ revoked: boolean() }).strict();
|
|
36988
|
-
//#endregion
|
|
36989
|
-
//#region ../protocol/dist/frames/relay-cp.js
|
|
36990
|
-
const RcAuth = object({
|
|
36991
|
-
method: _enum(["token", "apikey"]),
|
|
36992
|
-
credential: string().min(1)
|
|
36993
|
-
});
|
|
36994
|
-
const RcDeploymentConfig = object({
|
|
36995
|
-
revision: number$1().int().nonnegative(),
|
|
36996
|
-
githubWebhookSecret: string().min(1).optional()
|
|
36997
|
-
});
|
|
36998
|
-
const RcAuthOk = object({
|
|
36999
|
-
heartbeatSec: number$1().int(),
|
|
37000
|
-
serverTime: string().datetime(),
|
|
37001
|
-
deploymentConfig: RcDeploymentConfig.optional()
|
|
37002
|
-
});
|
|
37003
|
-
const RcRegister = object({
|
|
37004
|
-
name: string().min(1),
|
|
37005
|
-
daemonUrl: string().min(1)
|
|
37006
|
-
});
|
|
37007
|
-
const RcRegistered = object({ relayId: string().uuid() });
|
|
37008
|
-
const RcHeartbeat = object({});
|
|
37009
|
-
const RcVerify = discriminatedUnion("kind", [object({
|
|
37010
|
-
kind: literal("daemon-key"),
|
|
37011
|
-
credential: string().min(1)
|
|
37012
|
-
}), object({
|
|
37013
|
-
kind: literal("webchat-token"),
|
|
37014
|
-
credential: string().min(1),
|
|
37015
|
-
conversationBinding: literal("v1").optional()
|
|
37016
|
-
})]);
|
|
37017
|
-
const RcWebchatParticipant = object({
|
|
37018
|
-
agentId: string().uuid(),
|
|
37019
|
-
daemonId: string().uuid().optional(),
|
|
37020
|
-
primary: boolean().optional()
|
|
37021
|
-
});
|
|
37022
|
-
const RcVerifyResult = object({
|
|
37023
|
-
ok: boolean(),
|
|
37024
|
-
reason: string().optional(),
|
|
37025
|
-
orgId: string().optional(),
|
|
37026
|
-
daemonId: string().uuid().optional(),
|
|
37027
|
-
userId: string().optional(),
|
|
37028
|
-
user: string().optional(),
|
|
37029
|
-
agentId: string().uuid().optional(),
|
|
37030
|
-
conversationId: string().uuid().optional(),
|
|
37031
|
-
participants: array(RcWebchatParticipant).max(16).optional(),
|
|
37032
|
-
remoteMcp: WebchatRemoteMcpEntitlement.optional()
|
|
37033
|
-
});
|
|
37034
|
-
const RcGithubHookFence = object({
|
|
37035
|
-
hookId: string().uuid(),
|
|
37036
|
-
configRevision: HookBigIntString,
|
|
37037
|
-
dispatchRevision: HookBigIntString
|
|
37038
|
-
}).strict();
|
|
37039
|
-
const RcGithubCommentAuthz = object({
|
|
37040
|
-
hookId: string().uuid(),
|
|
37041
|
-
installationId: string().regex(/^[1-9]\d*$/),
|
|
37042
|
-
repoId: string().regex(/^[1-9]\d*$/),
|
|
37043
|
-
repoFullName: string().regex(/^[^/\s]+\/[^/\s]+$/),
|
|
37044
|
-
senderLogin: string().min(1),
|
|
37045
|
-
subjectAuthorLogin: string().min(1).optional(),
|
|
37046
|
-
configRevision: HookBigIntString,
|
|
37047
|
-
dispatchRevision: HookBigIntString,
|
|
37048
|
-
siblingFences: array(RcGithubHookFence).min(1).optional()
|
|
37049
|
-
}).strict();
|
|
37050
|
-
const RcGithubCommentAuthzResult = object({ allowed: boolean() }).strict();
|
|
37051
|
-
const GithubNumericId = string().regex(/^[1-9]\d*$/);
|
|
37052
|
-
const RcGithubRerequest = union([object({
|
|
37053
|
-
checkRunId: GithubNumericId,
|
|
37054
|
-
repoId: GithubNumericId,
|
|
37055
|
-
headSha: string().min(1),
|
|
37056
|
-
deliveryKey: string().min(1).max(200),
|
|
37057
|
-
includeBaseSha: literal(true).optional()
|
|
37058
|
-
}).strict(), object({
|
|
37059
|
-
scope: literal("suite"),
|
|
37060
|
-
appId: GithubNumericId,
|
|
37061
|
-
installationId: GithubNumericId,
|
|
37062
|
-
repoId: GithubNumericId,
|
|
37063
|
-
headSha: string().min(1),
|
|
37064
|
-
deliveryKey: string().min(1).max(200)
|
|
37065
|
-
}).strict()]);
|
|
37066
|
-
const RcGithubRerequestTarget = object({
|
|
37067
|
-
hookId: string().uuid(),
|
|
37068
|
-
pullNumber: number$1().int().positive(),
|
|
37069
|
-
baseSha: string().min(1),
|
|
37070
|
-
configRevision: HookBigIntString,
|
|
37071
|
-
dispatchRevision: HookBigIntString
|
|
37072
|
-
}).strict();
|
|
37073
|
-
const RcGithubRerequestResult = union([
|
|
37074
|
-
object({ allowed: literal(false) }).strict(),
|
|
37075
|
-
object({
|
|
37076
|
-
allowed: literal(true),
|
|
37077
|
-
hookId: string().uuid(),
|
|
37078
|
-
pullNumber: number$1().int().positive(),
|
|
37079
|
-
baseSha: string().min(1).optional(),
|
|
37080
|
-
configRevision: HookBigIntString,
|
|
37081
|
-
dispatchRevision: HookBigIntString
|
|
37082
|
-
}).strict(),
|
|
37083
|
-
object({
|
|
37084
|
-
allowed: literal(true),
|
|
37085
|
-
targets: array(RcGithubRerequestTarget).min(1)
|
|
37086
|
-
}).strict()
|
|
37087
|
-
]);
|
|
37088
|
-
const RcHookAssign = object({
|
|
37089
|
-
hookId: string().uuid(),
|
|
37090
|
-
kind: _enum(["webhook", "github"]),
|
|
37091
|
-
agentId: string().uuid(),
|
|
37092
|
-
daemonId: string().uuid(),
|
|
37093
|
-
...OptionalHookConfigSnapshot.shape,
|
|
37094
|
-
sessionMode: _enum([
|
|
37095
|
-
"perDelivery",
|
|
37096
|
-
"perThread",
|
|
37097
|
-
"shared"
|
|
37098
|
-
]),
|
|
37099
|
-
target: CronTarget.optional(),
|
|
37100
|
-
webhook: object({
|
|
37101
|
-
urlToken: string().min(1),
|
|
37102
|
-
hmacSecret: string().optional()
|
|
37103
|
-
}).optional(),
|
|
37104
|
-
github: object({
|
|
37105
|
-
repoId: string(),
|
|
37106
|
-
repoFullName: string(),
|
|
37107
|
-
sessionKeyPrefix: string().min(1).optional(),
|
|
37108
|
-
events: array(string()),
|
|
37109
|
-
labelFilter: array(string()),
|
|
37110
|
-
commentFamilies: array(_enum(["issues", "pull_request"])).optional(),
|
|
37111
|
-
mentionOnly: boolean(),
|
|
37112
|
-
appSlug: string().optional(),
|
|
37113
|
-
agentName: string().optional(),
|
|
37114
|
-
installationIds: array(string())
|
|
37115
|
-
}).optional()
|
|
37116
|
-
}).superRefine((rule, ctx) => {
|
|
37117
|
-
if (rule.dispatchDaemonId !== void 0 && rule.dispatchDaemonId !== rule.daemonId) ctx.addIssue({
|
|
37118
|
-
code: "custom",
|
|
37119
|
-
path: ["dispatchDaemonId"],
|
|
37120
|
-
message: "dispatchDaemonId must equal daemonId on a compiled rule"
|
|
37121
|
-
});
|
|
37122
|
-
});
|
|
37123
|
-
const RcHookRemove = object({ hookId: string().uuid() });
|
|
37124
|
-
const RcRunReport = object({
|
|
37125
|
-
hookId: string().uuid(),
|
|
37126
|
-
deliveryKey: string().min(1),
|
|
37127
|
-
firedAt: string().datetime(),
|
|
37128
|
-
agentId: string().uuid(),
|
|
37129
|
-
daemonId: string().uuid().optional(),
|
|
37130
|
-
...OptionalHookConfigSnapshot.shape,
|
|
37131
|
-
event: string().min(1).optional(),
|
|
37132
|
-
github: GithubHookMetadata.optional(),
|
|
37133
|
-
status: _enum(["accepted", "failed"]),
|
|
37134
|
-
reason: string().optional()
|
|
37135
|
-
}).superRefine((report, ctx) => {
|
|
37136
|
-
if (report.daemonId !== void 0 && report.dispatchDaemonId !== void 0 && report.daemonId !== report.dispatchDaemonId) ctx.addIssue({
|
|
37137
|
-
code: "custom",
|
|
37138
|
-
path: ["dispatchDaemonId"],
|
|
37139
|
-
message: "dispatchDaemonId must equal daemonId on a delivery report"
|
|
37140
|
-
});
|
|
37141
|
-
});
|
|
37142
|
-
const RcGithubInstallation = object({
|
|
37143
|
-
installationId: string().min(1),
|
|
37144
|
-
action: string().min(1)
|
|
37145
|
-
});
|
|
37146
|
-
const RcDaemonRevoke = object({ daemonId: string().uuid() });
|
|
37147
36284
|
/** App-level Slack message shortcut for opening the controls of the session that
|
|
37148
36285
|
* owns the selected message's conversation. Direct apps receive it over Socket
|
|
37149
|
-
* Mode; shared apps receive the same callback through the relay HTTP edge.
|
|
36286
|
+
* Mode; shared apps receive the same callback through the relay HTTP edge.
|
|
36287
|
+
*
|
|
36288
|
+
* Declared to Slack by {@link buildSlackAppManifest} below, which is why it lives
|
|
36289
|
+
* here rather than beside the runtime Block Kit action ids in `frames/relay-cp.ts`:
|
|
36290
|
+
* those name controls on messages we post, this one names a manifest feature. */
|
|
37150
36291
|
const SLACK_MANAGE_SESSION_SHORTCUT_CALLBACK_ID = "ac_manage_session";
|
|
37151
|
-
/** One short choice from the compact Slack status overflow. Slack caps option
|
|
37152
|
-
* values at 150 characters; the longer session target rides in `block_id`. */
|
|
37153
|
-
const SlackStatusOverflowAction = _enum([
|
|
37154
|
-
"switch-agent",
|
|
37155
|
-
"manage",
|
|
37156
|
-
"cancel"
|
|
37157
|
-
]);
|
|
37158
|
-
object({
|
|
37159
|
-
v: literal(1),
|
|
37160
|
-
action: SlackStatusOverflowAction,
|
|
37161
|
-
target: string().min(1)
|
|
37162
|
-
}).strict();
|
|
37163
|
-
object({
|
|
37164
|
-
v: literal(1),
|
|
37165
|
-
agentId: string().min(1),
|
|
37166
|
-
integrationId: string().min(1),
|
|
37167
|
-
sessionKey: string().min(1)
|
|
37168
|
-
}).strict();
|
|
37169
|
-
const AttributedRoute = object({
|
|
37170
|
-
agentId: string().uuid(),
|
|
37171
|
-
daemonId: string().uuid(),
|
|
37172
|
-
integrationId: string().uuid(),
|
|
37173
|
-
scope: object({
|
|
37174
|
-
channel: string().optional(),
|
|
37175
|
-
thread: string().optional()
|
|
37176
|
-
}).optional(),
|
|
37177
|
-
match: BindMatch
|
|
37178
|
-
});
|
|
37179
|
-
const RcBotSecrets = union([
|
|
37180
|
-
object({
|
|
37181
|
-
botToken: string(),
|
|
37182
|
-
signingSecret: string()
|
|
37183
|
-
}).catchall(unknown()),
|
|
37184
|
-
object({
|
|
37185
|
-
verificationToken: string(),
|
|
37186
|
-
encryptKey: string().optional()
|
|
37187
|
-
}).catchall(unknown()),
|
|
37188
|
-
record$1(string(), unknown())
|
|
37189
|
-
]);
|
|
37190
|
-
const RcAgentDirEntry = object({
|
|
37191
|
-
agentId: string().uuid(),
|
|
37192
|
-
name: string(),
|
|
37193
|
-
daemonId: string().uuid(),
|
|
37194
|
-
integrationId: string().uuid().optional()
|
|
37195
|
-
});
|
|
37196
|
-
const RcBotAssign = object({
|
|
37197
|
-
botId: string().uuid(),
|
|
37198
|
-
platform: Platform,
|
|
37199
|
-
originKind: string().min(1).optional(),
|
|
37200
|
-
credentialRevision: number$1().int().nonnegative().optional(),
|
|
37201
|
-
secrets: RcBotSecrets,
|
|
37202
|
-
ingress: unknown().optional(),
|
|
37203
|
-
members: array(object({
|
|
37204
|
-
daemonId: string().uuid(),
|
|
37205
|
-
agentIds: array(string().uuid())
|
|
37206
|
-
})),
|
|
37207
|
-
agents: array(RcAgentDirEntry).default([]),
|
|
37208
|
-
routes: array(AttributedRoute),
|
|
37209
|
-
defaultAgentId: string().uuid().optional(),
|
|
37210
|
-
defaultDaemonId: string().uuid().optional(),
|
|
37211
|
-
gatedAgentIds: array(string().uuid()).default([]),
|
|
37212
|
-
mutedChannels: array(string()).default([]),
|
|
37213
|
-
gatedOffChannels: array(string()).default([]),
|
|
37214
|
-
noticeAuthority: string().uuid().optional(),
|
|
37215
|
-
noticedDmConversations: array(string()).default([])
|
|
37216
|
-
});
|
|
37217
|
-
const RcBotUnassign = object({
|
|
37218
|
-
botId: string().uuid(),
|
|
37219
|
-
credentialRevision: number$1().int().nonnegative().optional()
|
|
37220
|
-
});
|
|
37221
|
-
const RcRoutes = object({
|
|
37222
|
-
botId: string().uuid(),
|
|
37223
|
-
members: array(object({
|
|
37224
|
-
daemonId: string().uuid(),
|
|
37225
|
-
agentIds: array(string().uuid())
|
|
37226
|
-
})),
|
|
37227
|
-
agents: array(RcAgentDirEntry).default([]),
|
|
37228
|
-
routes: array(AttributedRoute),
|
|
37229
|
-
defaultAgentId: string().uuid().optional(),
|
|
37230
|
-
defaultDaemonId: string().uuid().optional(),
|
|
37231
|
-
gatedAgentIds: array(string().uuid()).default([]),
|
|
37232
|
-
mutedChannels: array(string()).default([]),
|
|
37233
|
-
gatedOffChannels: array(string()).default([]),
|
|
37234
|
-
noticeAuthority: string().uuid().optional(),
|
|
37235
|
-
noticedDmConversations: array(string()).default([])
|
|
37236
|
-
});
|
|
37237
|
-
const RcAssign = object({
|
|
37238
|
-
botId: string().uuid(),
|
|
37239
|
-
sessionKey: string().min(1),
|
|
37240
|
-
agentId: string().uuid(),
|
|
37241
|
-
daemonId: string().uuid()
|
|
37242
|
-
});
|
|
37243
|
-
const RcParticipantAssign = object({
|
|
37244
|
-
botId: string().uuid(),
|
|
37245
|
-
sessionKey: string().min(1),
|
|
37246
|
-
agentId: string().uuid(),
|
|
37247
|
-
daemonId: string().uuid()
|
|
37248
|
-
});
|
|
37249
|
-
const RcSetChannelAgent = object({
|
|
37250
|
-
botId: string().uuid(),
|
|
37251
|
-
channelId: string().min(1),
|
|
37252
|
-
agentId: string().uuid()
|
|
37253
|
-
});
|
|
37254
|
-
const RcBotChannels = object({
|
|
37255
|
-
botId: string().uuid(),
|
|
37256
|
-
channels: array(IntegrationChannel)
|
|
37257
|
-
});
|
|
37258
|
-
const RcNoticePosted = object({
|
|
37259
|
-
botId: string().uuid(),
|
|
37260
|
-
channel: string().min(1)
|
|
37261
|
-
});
|
|
37262
|
-
const RcBotRevoked = object({
|
|
37263
|
-
botId: string().uuid(),
|
|
37264
|
-
reason: _enum(["app_uninstalled", "tokens_revoked"]),
|
|
37265
|
-
credentialRevision: number$1().int().nonnegative().optional(),
|
|
37266
|
-
eventAtMs: number$1().int().nonnegative().optional()
|
|
37267
|
-
});
|
|
37268
|
-
const RcBotRevokedOk = object({
|
|
37269
|
-
botId: string().uuid(),
|
|
37270
|
-
applied: boolean()
|
|
37271
|
-
});
|
|
37272
|
-
/** `type` string → payload schema for the relay↔CP wire. */
|
|
37273
|
-
const RELAY_CP_SCHEMAS = {
|
|
37274
|
-
"rc/auth": RcAuth,
|
|
37275
|
-
"rc/auth/ok": RcAuthOk,
|
|
37276
|
-
"rc/register": RcRegister,
|
|
37277
|
-
"rc/registered": RcRegistered,
|
|
37278
|
-
"rc/heartbeat": RcHeartbeat,
|
|
37279
|
-
"rc/verify": RcVerify,
|
|
37280
|
-
"rc/verify/ok": RcVerifyResult,
|
|
37281
|
-
"rc/github-comment-authz": RcGithubCommentAuthz,
|
|
37282
|
-
"rc/github-comment-authz/ok": RcGithubCommentAuthzResult,
|
|
37283
|
-
"rc/github-rerequest": RcGithubRerequest,
|
|
37284
|
-
"rc/github-rerequest/ok": RcGithubRerequestResult,
|
|
37285
|
-
"rc/hook-assign": RcHookAssign,
|
|
37286
|
-
"rc/hook-remove": RcHookRemove,
|
|
37287
|
-
"rc/run-report": RcRunReport,
|
|
37288
|
-
"rc/github-installation": RcGithubInstallation,
|
|
37289
|
-
"rc/daemon-revoke": RcDaemonRevoke,
|
|
37290
|
-
"rc/bot-assign": RcBotAssign,
|
|
37291
|
-
"rc/bot-unassign": RcBotUnassign,
|
|
37292
|
-
"rc/routes": RcRoutes,
|
|
37293
|
-
"rc/assign": RcAssign,
|
|
37294
|
-
"rc/participant-assign": RcParticipantAssign,
|
|
37295
|
-
"rc/set-channel-agent": RcSetChannelAgent,
|
|
37296
|
-
"rc/bot-channels": RcBotChannels,
|
|
37297
|
-
"rc/bot-conversation": object({
|
|
37298
|
-
botId: string().uuid(),
|
|
37299
|
-
conversation: IntegrationChannel
|
|
37300
|
-
}),
|
|
37301
|
-
"rc/bot-revoked": RcBotRevoked,
|
|
37302
|
-
"rc/bot-revoked/ok": RcBotRevokedOk,
|
|
37303
|
-
"rc/notice-posted": RcNoticePosted,
|
|
37304
|
-
"rc/thread-assign": object({
|
|
37305
|
-
botId: string().uuid(),
|
|
37306
|
-
sessionKey: string().min(1),
|
|
37307
|
-
agentId: string().uuid(),
|
|
37308
|
-
daemonId: string().uuid()
|
|
37309
|
-
}),
|
|
37310
|
-
"rc/thread-participant": object({
|
|
37311
|
-
botId: string().uuid(),
|
|
37312
|
-
sessionKey: string().min(1),
|
|
37313
|
-
agentId: string().uuid(),
|
|
37314
|
-
daemonId: string().uuid()
|
|
37315
|
-
}),
|
|
37316
|
-
"rc/thread-lookup": object({
|
|
37317
|
-
botId: string().uuid(),
|
|
37318
|
-
sessionKey: string().min(1)
|
|
37319
|
-
}),
|
|
37320
|
-
"rc/thread-lookup/ok": object({
|
|
37321
|
-
botId: string().uuid(),
|
|
37322
|
-
sessionKey: string().min(1),
|
|
37323
|
-
target: object({
|
|
37324
|
-
agentId: string().uuid(),
|
|
37325
|
-
daemonId: string().uuid()
|
|
37326
|
-
}).nullable(),
|
|
37327
|
-
participants: array(object({
|
|
37328
|
-
agentId: string().uuid(),
|
|
37329
|
-
daemonId: string().uuid()
|
|
37330
|
-
})).default([])
|
|
37331
|
-
}),
|
|
37332
|
-
"rc/collab-routes": CollabRoutesSnapshot,
|
|
37333
|
-
"rc/mcp-assign": object({
|
|
37334
|
-
providerId: string().uuid(),
|
|
37335
|
-
upstreamUrl: string(),
|
|
37336
|
-
headers: array(object({
|
|
37337
|
-
name: string(),
|
|
37338
|
-
value: string()
|
|
37339
|
-
})).default([]),
|
|
37340
|
-
grantKeyHashes: array(string().min(1)).min(1)
|
|
37341
|
-
}),
|
|
37342
|
-
"rc/mcp-unassign": object({
|
|
37343
|
-
providerId: string().uuid(),
|
|
37344
|
-
grantKeyHash: string().optional()
|
|
37345
|
-
}),
|
|
37346
|
-
"rc/memoryconnection-assign": object({
|
|
37347
|
-
connectionId: string().uuid(),
|
|
37348
|
-
revision: number$1().int().positive(),
|
|
37349
|
-
upstreamUrl: string().url().max(2048),
|
|
37350
|
-
headers: array(object({
|
|
37351
|
-
name: string().min(1).max(128),
|
|
37352
|
-
value: string().max(16 * 1024)
|
|
37353
|
-
})).max(64).default([]),
|
|
37354
|
-
grantKeyHashes: array(string().regex(/^[a-f0-9]{64}$/)).min(1).max(64)
|
|
37355
|
-
}),
|
|
37356
|
-
"rc/memoryconnection-unassign": object({
|
|
37357
|
-
connectionId: string().uuid(),
|
|
37358
|
-
revision: number$1().int().positive(),
|
|
37359
|
-
grantKeyHash: string().regex(/^[a-f0-9]{64}$/).optional()
|
|
37360
|
-
}),
|
|
37361
|
-
error: ErrorFrame
|
|
37362
|
-
};
|
|
37363
|
-
Object.keys(RELAY_CP_SCHEMAS);
|
|
37364
|
-
discriminatedUnion("type", [
|
|
37365
|
-
frameSchema("rc/auth", RELAY_CP_SCHEMAS["rc/auth"]),
|
|
37366
|
-
frameSchema("rc/auth/ok", RELAY_CP_SCHEMAS["rc/auth/ok"]),
|
|
37367
|
-
frameSchema("rc/register", RELAY_CP_SCHEMAS["rc/register"]),
|
|
37368
|
-
frameSchema("rc/registered", RELAY_CP_SCHEMAS["rc/registered"]),
|
|
37369
|
-
frameSchema("rc/heartbeat", RELAY_CP_SCHEMAS["rc/heartbeat"]),
|
|
37370
|
-
frameSchema("rc/verify", RELAY_CP_SCHEMAS["rc/verify"]),
|
|
37371
|
-
frameSchema("rc/verify/ok", RELAY_CP_SCHEMAS["rc/verify/ok"]),
|
|
37372
|
-
frameSchema("rc/github-comment-authz", RELAY_CP_SCHEMAS["rc/github-comment-authz"]),
|
|
37373
|
-
frameSchema("rc/github-comment-authz/ok", RELAY_CP_SCHEMAS["rc/github-comment-authz/ok"]),
|
|
37374
|
-
frameSchema("rc/github-rerequest", RELAY_CP_SCHEMAS["rc/github-rerequest"]),
|
|
37375
|
-
frameSchema("rc/github-rerequest/ok", RELAY_CP_SCHEMAS["rc/github-rerequest/ok"]),
|
|
37376
|
-
frameSchema("rc/hook-assign", RELAY_CP_SCHEMAS["rc/hook-assign"]),
|
|
37377
|
-
frameSchema("rc/hook-remove", RELAY_CP_SCHEMAS["rc/hook-remove"]),
|
|
37378
|
-
frameSchema("rc/run-report", RELAY_CP_SCHEMAS["rc/run-report"]),
|
|
37379
|
-
frameSchema("rc/github-installation", RELAY_CP_SCHEMAS["rc/github-installation"]),
|
|
37380
|
-
frameSchema("rc/daemon-revoke", RELAY_CP_SCHEMAS["rc/daemon-revoke"]),
|
|
37381
|
-
frameSchema("rc/bot-assign", RELAY_CP_SCHEMAS["rc/bot-assign"]),
|
|
37382
|
-
frameSchema("rc/bot-unassign", RELAY_CP_SCHEMAS["rc/bot-unassign"]),
|
|
37383
|
-
frameSchema("rc/routes", RELAY_CP_SCHEMAS["rc/routes"]),
|
|
37384
|
-
frameSchema("rc/assign", RELAY_CP_SCHEMAS["rc/assign"]),
|
|
37385
|
-
frameSchema("rc/participant-assign", RELAY_CP_SCHEMAS["rc/participant-assign"]),
|
|
37386
|
-
frameSchema("rc/set-channel-agent", RELAY_CP_SCHEMAS["rc/set-channel-agent"]),
|
|
37387
|
-
frameSchema("rc/bot-channels", RELAY_CP_SCHEMAS["rc/bot-channels"]),
|
|
37388
|
-
frameSchema("rc/bot-conversation", RELAY_CP_SCHEMAS["rc/bot-conversation"]),
|
|
37389
|
-
frameSchema("rc/bot-revoked", RELAY_CP_SCHEMAS["rc/bot-revoked"]),
|
|
37390
|
-
frameSchema("rc/bot-revoked/ok", RELAY_CP_SCHEMAS["rc/bot-revoked/ok"]),
|
|
37391
|
-
frameSchema("rc/notice-posted", RELAY_CP_SCHEMAS["rc/notice-posted"]),
|
|
37392
|
-
frameSchema("rc/thread-assign", RELAY_CP_SCHEMAS["rc/thread-assign"]),
|
|
37393
|
-
frameSchema("rc/thread-participant", RELAY_CP_SCHEMAS["rc/thread-participant"]),
|
|
37394
|
-
frameSchema("rc/thread-lookup", RELAY_CP_SCHEMAS["rc/thread-lookup"]),
|
|
37395
|
-
frameSchema("rc/thread-lookup/ok", RELAY_CP_SCHEMAS["rc/thread-lookup/ok"]),
|
|
37396
|
-
frameSchema("rc/collab-routes", RELAY_CP_SCHEMAS["rc/collab-routes"]),
|
|
37397
|
-
frameSchema("rc/mcp-assign", RELAY_CP_SCHEMAS["rc/mcp-assign"]),
|
|
37398
|
-
frameSchema("rc/mcp-unassign", RELAY_CP_SCHEMAS["rc/mcp-unassign"]),
|
|
37399
|
-
frameSchema("rc/memoryconnection-assign", RELAY_CP_SCHEMAS["rc/memoryconnection-assign"]),
|
|
37400
|
-
frameSchema("rc/memoryconnection-unassign", RELAY_CP_SCHEMAS["rc/memoryconnection-unassign"]),
|
|
37401
|
-
frameSchema("error", RELAY_CP_SCHEMAS["error"])
|
|
37402
|
-
]);
|
|
37403
|
-
//#endregion
|
|
37404
|
-
//#region ../protocol/dist/slack-app-manifest.js
|
|
37405
36292
|
/** Public platform profile copy. Never derive this from an Agent description. */
|
|
37406
36293
|
const PLATFORM_APP_DESCRIPTION = "AI agent powered by AgentConnect.";
|
|
37407
36294
|
const SLACK_BOT_SCOPES = [
|
|
@@ -57634,6 +56521,26 @@ async function disconnectPrisma() {
|
|
|
57634
56521
|
await singleton?.$disconnect();
|
|
57635
56522
|
singleton = void 0;
|
|
57636
56523
|
}
|
|
56524
|
+
//#endregion
|
|
56525
|
+
//#region ../control-plane/dist/secrets/scope.js
|
|
56526
|
+
/** Deployment-owned material (the deployment config document's secrets). */
|
|
56527
|
+
const DEPLOYMENT_SCOPE = { kind: "deployment" };
|
|
56528
|
+
/**
|
|
56529
|
+
* Envelope marker for values sealed under a SCOPED key. It carries a format
|
|
56530
|
+
* version and nothing else — deliberately not the tenant id (see above). Its
|
|
56531
|
+
* one job is to let `open` tell a scoped value apart from a pre-scoping one, so
|
|
56532
|
+
* the rollout needs no backfill.
|
|
56533
|
+
*/
|
|
56534
|
+
const SECRET_ENVELOPE_PREFIX = "acv1:";
|
|
56535
|
+
/**
|
|
56536
|
+
* Org key names default to `<deployment key>-org-`, so they inherit whatever
|
|
56537
|
+
* namespace the deployment key already occupies. Deployments commonly share one
|
|
56538
|
+
* transit mount and rely on key naming alone to stay separated; a fixed prefix
|
|
56539
|
+
* would collide their org keys, a derived one cannot.
|
|
56540
|
+
*/
|
|
56541
|
+
function effectiveOrgKeyPrefix(transitKey, configured) {
|
|
56542
|
+
return configured ?? `${transitKey}-org-`;
|
|
56543
|
+
}
|
|
57637
56544
|
const HttpUrlSchema = string().url().superRefine((value, ctx) => {
|
|
57638
56545
|
const url = new URL(value);
|
|
57639
56546
|
if (url.protocol !== "http:" && url.protocol !== "https:") ctx.addIssue({
|
|
@@ -57888,7 +56795,7 @@ var DeploymentConfigService = class {
|
|
|
57888
56795
|
for (const [rawKey, value] of Object.entries(secretPatch)) {
|
|
57889
56796
|
const key = DeploymentSecretKeySchema.parse(rawKey);
|
|
57890
56797
|
prepared[key] = value === null ? null : {
|
|
57891
|
-
sealedValue: await this.cipher.seal(value),
|
|
56798
|
+
sealedValue: await this.cipher.seal(value, DEPLOYMENT_SCOPE),
|
|
57892
56799
|
fingerprint: fingerprint(value)
|
|
57893
56800
|
};
|
|
57894
56801
|
}
|
|
@@ -57909,7 +56816,7 @@ var DeploymentConfigService = class {
|
|
|
57909
56816
|
const secrets = {};
|
|
57910
56817
|
for (const secret of row.secrets) {
|
|
57911
56818
|
const parsed = DeploymentSecretKeySchema.safeParse(secret.key);
|
|
57912
|
-
if (parsed.success && openedKeys.has(parsed.data)) secrets[parsed.data] = await this.cipher.open(secret.sealedValue);
|
|
56819
|
+
if (parsed.success && openedKeys.has(parsed.data)) secrets[parsed.data] = await this.cipher.open(secret.sealedValue, DEPLOYMENT_SCOPE);
|
|
57913
56820
|
}
|
|
57914
56821
|
return {
|
|
57915
56822
|
schemaVersion: 1,
|
|
@@ -58105,12 +57012,19 @@ var PgDeploymentConfigStore = class extends DeploymentConfigService {
|
|
|
58105
57012
|
*
|
|
58106
57013
|
* Envelope encryption as a service: the data key never leaves Vault; the CP
|
|
58107
57014
|
* sends base64 plaintext to `transit/encrypt/<key>` and stores the returned
|
|
58108
|
-
*
|
|
57015
|
+
* ciphertext, behind this deployment's envelope tag, in the existing text
|
|
57016
|
+
* columns. WHICH key is chosen comes from the {@link SecretScope} the caller
|
|
57017
|
+
* passes — the deployment's key, or `<orgKeyPrefix><orgId>` — so an
|
|
57018
|
+
* organization's material can be destroyed with its key
|
|
57019
|
+
* (docs/designs/per-org-secret-encryption.md).
|
|
58109
57020
|
*
|
|
58110
57021
|
* Contract (pinned on the port):
|
|
58111
|
-
* - `open` PASSES THROUGH values it did not seal (
|
|
58112
|
-
* return as-is): existing plaintext rows keep
|
|
58113
|
-
* next write re-seals them — the rollout is
|
|
57022
|
+
* - `open` PASSES THROUGH values it did not seal (neither the envelope tag nor
|
|
57023
|
+
* a bare `vault:vN:` prefix ⇒ return as-is): existing plaintext rows keep
|
|
57024
|
+
* reading after the flip, and the next write re-seals them — the rollout is
|
|
57025
|
+
* online, no backfill required.
|
|
57026
|
+
* - A value sealed under one scope does NOT open under another: Transit rejects
|
|
57027
|
+
* a ciphertext presented to a different key, which is the cross-tenant fence.
|
|
58114
57028
|
* - No argument or response body is ever logged; errors carry only the HTTP
|
|
58115
57029
|
* status and Vault's `errors[]` strings.
|
|
58116
57030
|
*
|
|
@@ -58121,11 +57035,13 @@ var PgDeploymentConfigStore = class extends DeploymentConfigService {
|
|
|
58121
57035
|
* that exact login wire shape ({role, jwt}), so nothing here is bound to
|
|
58122
57036
|
* Kubernetes — a k8s ServiceAccount token is just the common jwtPath.
|
|
58123
57037
|
*
|
|
58124
|
-
* `open` results are cached in-process keyed by
|
|
58125
|
-
* order eviction): reconcile opens every owned agent's
|
|
58126
|
-
* Transit ciphertexts are stable until re-sealed, so
|
|
58127
|
-
* one network call per distinct value.
|
|
58128
|
-
*
|
|
57038
|
+
* `open` results are cached in-process keyed by key name AND ciphertext
|
|
57039
|
+
* (bounded, insertion-order eviction): reconcile opens every owned agent's
|
|
57040
|
+
* secrets per register, and Transit ciphertexts are stable until re-sealed, so
|
|
57041
|
+
* the cache turns that into one network call per distinct value. The key name
|
|
57042
|
+
* belongs in the cache key because the stored string alone no longer implies
|
|
57043
|
+
* one. `seal` is never cached — Transit returns fresh ciphertext per call by
|
|
57044
|
+
* design.
|
|
58129
57045
|
*/
|
|
58130
57046
|
/** Transit ciphertext is self-describing: `vault:v<key-version>:<base64>`. */
|
|
58131
57047
|
const CIPHERTEXT_RE = /^vault:v\d+:/;
|
|
@@ -58134,6 +57050,7 @@ const RENEW_FRACTION = .8;
|
|
|
58134
57050
|
var VaultTransitSecretCipher = class {
|
|
58135
57051
|
base;
|
|
58136
57052
|
key;
|
|
57053
|
+
orgKeyPrefix;
|
|
58137
57054
|
mount;
|
|
58138
57055
|
namespace;
|
|
58139
57056
|
auth;
|
|
@@ -58146,6 +57063,7 @@ var VaultTransitSecretCipher = class {
|
|
|
58146
57063
|
constructor(opts) {
|
|
58147
57064
|
this.base = `${opts.addr.replace(/\/+$/, "")}/v1`;
|
|
58148
57065
|
this.key = opts.key;
|
|
57066
|
+
this.orgKeyPrefix = opts.orgKeyPrefix;
|
|
58149
57067
|
this.mount = opts.mount ?? "transit";
|
|
58150
57068
|
this.namespace = opts.namespace;
|
|
58151
57069
|
this.auth = opts.auth;
|
|
@@ -58153,19 +57071,31 @@ var VaultTransitSecretCipher = class {
|
|
|
58153
57071
|
this.now = opts.now ?? Date.now;
|
|
58154
57072
|
this.openCacheMax = opts.openCacheMax ?? 5e3;
|
|
58155
57073
|
}
|
|
58156
|
-
|
|
58157
|
-
|
|
58158
|
-
|
|
58159
|
-
|
|
57074
|
+
/**
|
|
57075
|
+
* The key a scope resolves to. Org keys are created lazily by Transit on the
|
|
57076
|
+
* first encrypt (the policy must permit creation on that path); nothing here
|
|
57077
|
+
* is coupled to organization creation.
|
|
57078
|
+
*/
|
|
57079
|
+
keyFor(scope) {
|
|
57080
|
+
return scope.kind === "deployment" ? this.key : `${this.orgKeyPrefix}${scope.orgId}`;
|
|
58160
57081
|
}
|
|
58161
|
-
async
|
|
58162
|
-
|
|
58163
|
-
|
|
57082
|
+
async seal(plaintext, scope) {
|
|
57083
|
+
const data = await this.transit("encrypt", this.keyFor(scope), { plaintext: Buffer.from(plaintext, "utf8").toString("base64") });
|
|
57084
|
+
if (typeof data.ciphertext !== "string") throw new Error("vault transit encrypt: no ciphertext in response");
|
|
57085
|
+
return `${SECRET_ENVELOPE_PREFIX}${data.ciphertext}`;
|
|
57086
|
+
}
|
|
57087
|
+
async open(stored, scope) {
|
|
57088
|
+
const scoped = stored.startsWith(SECRET_ENVELOPE_PREFIX);
|
|
57089
|
+
if (!scoped && !CIPHERTEXT_RE.test(stored)) return stored;
|
|
57090
|
+
const key = scoped ? this.keyFor(scope) : this.key;
|
|
57091
|
+
const ciphertext = scoped ? stored.slice(5) : stored;
|
|
57092
|
+
const cacheKey = `${key}\u0000${ciphertext}`;
|
|
57093
|
+
const hit = this.openCache.get(cacheKey);
|
|
58164
57094
|
if (hit !== void 0) return hit;
|
|
58165
|
-
const data = await this.transit("decrypt", { ciphertext
|
|
57095
|
+
const data = await this.transit("decrypt", key, { ciphertext });
|
|
58166
57096
|
if (typeof data.plaintext !== "string") throw new Error("vault transit decrypt: no plaintext in response");
|
|
58167
57097
|
const value = Buffer.from(data.plaintext, "base64").toString("utf8");
|
|
58168
|
-
this.cacheOpen(
|
|
57098
|
+
this.cacheOpen(cacheKey, value);
|
|
58169
57099
|
return value;
|
|
58170
57100
|
}
|
|
58171
57101
|
cacheOpen(ciphertext, plaintext) {
|
|
@@ -58175,8 +57105,8 @@ var VaultTransitSecretCipher = class {
|
|
|
58175
57105
|
}
|
|
58176
57106
|
this.openCache.set(ciphertext, plaintext);
|
|
58177
57107
|
}
|
|
58178
|
-
async transit(op, body) {
|
|
58179
|
-
const path = `${this.mount}/${op}/${
|
|
57108
|
+
async transit(op, key, body) {
|
|
57109
|
+
const path = `${this.mount}/${op}/${key}`;
|
|
58180
57110
|
let res = await this.post(path, body, await this.token());
|
|
58181
57111
|
if (res.status === 403 && this.auth.method === "jwt") {
|
|
58182
57112
|
this.clientToken = void 0;
|
|
@@ -58252,11 +57182,21 @@ async function describeError(res) {
|
|
|
58252
57182
|
* as Vault Transit stores ciphertext. The composition root selects one provider,
|
|
58253
57183
|
* so every store switches together.
|
|
58254
57184
|
*
|
|
57185
|
+
* Every call carries a {@link SecretScope} naming WHOSE key to use — the
|
|
57186
|
+
* deployment's, or one organization's (docs/designs/per-org-secret-encryption.md).
|
|
57187
|
+
* The scope comes from the caller, never from the stored value.
|
|
57188
|
+
*
|
|
58255
57189
|
* Contract for an encrypting implementation (e.g. Vault Transit):
|
|
58256
|
-
* - `seal` returns a self-describing value
|
|
57190
|
+
* - `seal` returns a self-describing value carrying the envelope version
|
|
57191
|
+
* (`acv1:` + Transit's own `vault:v1:…`).
|
|
58257
57192
|
* - `open` MUST pass through values it did not seal (no recognizable prefix ⇒
|
|
58258
57193
|
* return as-is): existing plaintext rows keep working, and re-sealing on the
|
|
58259
57194
|
* next write migrates them lazily — the flip is online, no backfill required.
|
|
57195
|
+
* Values sealed before scoping existed (a bare `vault:vN:`) open under the
|
|
57196
|
+
* deployment key whatever `scope` says; only the envelope-tagged arm is
|
|
57197
|
+
* scoped.
|
|
57198
|
+
* - A value sealed under one scope MUST NOT open under another. Failing closed
|
|
57199
|
+
* there is the point of passing the scope in.
|
|
58260
57200
|
* - Neither side ever logs its argument.
|
|
58261
57201
|
*
|
|
58262
57202
|
* This is NOT the C5 `SecretsProvider` lease broker (`providers/provider.ts`),
|
|
@@ -58297,6 +57237,7 @@ function makeSecretCipher(config) {
|
|
|
58297
57237
|
return new VaultTransitSecretCipher({
|
|
58298
57238
|
addr: config.VAULT_ADDR,
|
|
58299
57239
|
key: config.VAULT_TRANSIT_KEY,
|
|
57240
|
+
orgKeyPrefix: effectiveOrgKeyPrefix(config.VAULT_TRANSIT_KEY, config.VAULT_TRANSIT_ORG_KEY_PREFIX),
|
|
58300
57241
|
mount: config.VAULT_TRANSIT_MOUNT,
|
|
58301
57242
|
...config.VAULT_NAMESPACE ? { namespace: config.VAULT_NAMESPACE } : {},
|
|
58302
57243
|
auth
|