@spinabot/brigade 1.13.0 → 1.14.0

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.
Files changed (59) hide show
  1. package/convex/logs.d.ts +3 -3
  2. package/convex/memory.d.ts +21 -21
  3. package/convex/schema.d.ts +9 -9
  4. package/convex/skills.d.ts +3 -3
  5. package/dist/buildstamp.json +1 -1
  6. package/dist/cli/commands/config-cmd.d.ts +12 -19
  7. package/dist/cli/commands/config-cmd.d.ts.map +1 -1
  8. package/dist/cli/commands/config-cmd.js +14 -197
  9. package/dist/cli/commands/config-cmd.js.map +1 -1
  10. package/dist/core/agents-crud-ops.d.ts +15 -0
  11. package/dist/core/agents-crud-ops.d.ts.map +1 -0
  12. package/dist/core/agents-crud-ops.js +27 -0
  13. package/dist/core/agents-crud-ops.js.map +1 -0
  14. package/dist/core/agents-ops.d.ts +43 -0
  15. package/dist/core/agents-ops.d.ts.map +1 -0
  16. package/dist/core/agents-ops.js +117 -0
  17. package/dist/core/agents-ops.js.map +1 -0
  18. package/dist/core/channels-ops.d.ts +30 -0
  19. package/dist/core/channels-ops.d.ts.map +1 -0
  20. package/dist/core/channels-ops.js +52 -0
  21. package/dist/core/channels-ops.js.map +1 -0
  22. package/dist/core/config-ops.d.ts +77 -0
  23. package/dist/core/config-ops.d.ts.map +1 -0
  24. package/dist/core/config-ops.js +241 -0
  25. package/dist/core/config-ops.js.map +1 -0
  26. package/dist/core/exec-ops.d.ts +48 -0
  27. package/dist/core/exec-ops.d.ts.map +1 -0
  28. package/dist/core/exec-ops.js +101 -0
  29. package/dist/core/exec-ops.js.map +1 -0
  30. package/dist/core/integrations-ops.d.ts +25 -0
  31. package/dist/core/integrations-ops.d.ts.map +1 -0
  32. package/dist/core/integrations-ops.js +40 -0
  33. package/dist/core/integrations-ops.js.map +1 -0
  34. package/dist/core/memory-ops.d.ts +20 -0
  35. package/dist/core/memory-ops.d.ts.map +1 -0
  36. package/dist/core/memory-ops.js +40 -0
  37. package/dist/core/memory-ops.js.map +1 -0
  38. package/dist/core/pairing-ops.d.ts +33 -0
  39. package/dist/core/pairing-ops.d.ts.map +1 -0
  40. package/dist/core/pairing-ops.js +78 -0
  41. package/dist/core/pairing-ops.js.map +1 -0
  42. package/dist/core/provider-ops.d.ts +17 -0
  43. package/dist/core/provider-ops.d.ts.map +1 -0
  44. package/dist/core/provider-ops.js +29 -0
  45. package/dist/core/provider-ops.js.map +1 -0
  46. package/dist/core/server.d.ts.map +1 -1
  47. package/dist/core/server.js +91 -0
  48. package/dist/core/server.js.map +1 -1
  49. package/dist/core/sessions-ops.d.ts +25 -0
  50. package/dist/core/sessions-ops.d.ts.map +1 -0
  51. package/dist/core/sessions-ops.js +77 -0
  52. package/dist/core/sessions-ops.js.map +1 -0
  53. package/dist/core/skills-ops.d.ts +14 -0
  54. package/dist/core/skills-ops.d.ts.map +1 -0
  55. package/dist/core/skills-ops.js +28 -0
  56. package/dist/core/skills-ops.js.map +1 -0
  57. package/dist/protocol/methods.d.ts +478 -0
  58. package/dist/protocol/methods.d.ts.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Channel pairing operations behind the `pairing.*` gateway RPCs — the
3
+ * `brigade pairing <list|approve|revoke>` surface, reachable from a remote
4
+ * client.
5
+ *
6
+ * OPERATOR-SCOPED channel access control (approve/deny strangers who DM the
7
+ * bot). Per-channel, never a per-session target, so no per-session guard. The
8
+ * RPCs REQUIRE an explicit `channel` (no CLI-style auto-pick) — a client knows
9
+ * the channel from `system.capabilities`. Reuses the SAME access-control
10
+ * primitives the CLI calls, including the owner-bootstrap + in-channel notify
11
+ * on approve.
12
+ */
13
+ import { approvePairingCode, readChannelOwner, readPendingPairings, revokePairingCode, setChannelOwner, } from "../agents/channels/access-control/index.js";
14
+ import { BUNDLED_MODULES, loadModules } from "../agents/extensions/index.js";
15
+ import { DEFAULT_AGENT_ID, resolveAgentWorkspaceDir } from "../config/paths.js";
16
+ import { loadConfig } from "./config.js";
17
+ /** Best-effort adapter lookup — only `approve` needs it (owner-bootstrap + notify). */
18
+ async function resolveAdapter(channel) {
19
+ try {
20
+ const config = loadConfig();
21
+ const workspaceDir = resolveAgentWorkspaceDir(DEFAULT_AGENT_ID);
22
+ const registry = await loadModules({
23
+ modules: BUNDLED_MODULES,
24
+ meta: { agentId: DEFAULT_AGENT_ID, workspaceDir, cwd: workspaceDir, config: config },
25
+ });
26
+ return registry.channels.find((c) => c.id === channel);
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ export function handlePairingList(params) {
33
+ const p = (params ?? {});
34
+ const channel = (p.channel ?? "").trim();
35
+ if (!channel)
36
+ throw new Error("pairing.list: missing 'channel'");
37
+ return { channel, pending: readPendingPairings(channel) };
38
+ }
39
+ export async function handlePairingApprove(params) {
40
+ const p = (params ?? {});
41
+ const channel = (p.channel ?? "").trim();
42
+ const code = (p.code ?? "").trim();
43
+ if (!channel || !code)
44
+ return { ok: false, channel, reason: "missing 'channel' or 'code'" };
45
+ const approved = approvePairingCode(channel, code);
46
+ if (!approved)
47
+ return { ok: false, channel, reason: "unknown or expired pairing code" };
48
+ // Owner bootstrap (bot-separate channels like Telegram): the first approved
49
+ // sender becomes the recorded owner so they can run admin commands. Reaching
50
+ // this RPC already proves operator access. Never overwrites an existing owner.
51
+ let becameOwner = false;
52
+ const adapter = await resolveAdapter(channel);
53
+ if (adapter?.pairing?.botIsSeparateFromOperator && !readChannelOwner(channel)) {
54
+ becameOwner = setChannelOwner(channel, approved.senderId);
55
+ }
56
+ // Best-effort in-channel "you're approved" reply when the adapter wires it.
57
+ const notify = adapter?.pairing?.notifyApproval;
58
+ if (notify) {
59
+ try {
60
+ await notify({ senderId: approved.senderId, senderName: approved.senderName });
61
+ }
62
+ catch {
63
+ /* non-fatal — the approval already landed in the allow-list */
64
+ }
65
+ }
66
+ return { ok: true, channel, sender: approved.senderId, owner: becameOwner };
67
+ }
68
+ export function handlePairingRevoke(params) {
69
+ const p = (params ?? {});
70
+ const channel = (p.channel ?? "").trim();
71
+ const code = (p.code ?? "").trim();
72
+ if (!channel || !code)
73
+ return { ok: false, channel, reason: "missing 'channel' or 'code'" };
74
+ return revokePairingCode(channel, code)
75
+ ? { ok: true, channel }
76
+ : { ok: false, channel, reason: "no matching pending code" };
77
+ }
78
+ //# sourceMappingURL=pairing-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-ops.js","sourceRoot":"","sources":["../../src/core/pairing-ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Provider-key removal behind the `provider.remove` gateway RPC.
3
+ *
4
+ * The genuine gap: `add-provider` adds a key, but there was NO way to REMOVE one
5
+ * over the gateway — keys live in `auth-profiles.json` (not config, so config.set
6
+ * can't reach them) and no tool exposes removal. This closes it. Operator-scoped
7
+ * per-agent (no per-session guard — allowlisted).
8
+ */
9
+ export interface ProviderRemoveResult {
10
+ ok: boolean;
11
+ providerId: string;
12
+ agentId: string;
13
+ removed: number;
14
+ reason?: string;
15
+ }
16
+ export declare function handleProviderRemove(params: unknown): ProviderRemoveResult;
17
+ //# sourceMappingURL=provider-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-ops.d.ts","sourceRoot":"","sources":["../../src/core/provider-ops.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,oBAAoB,CAe1E"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Provider-key removal behind the `provider.remove` gateway RPC.
3
+ *
4
+ * The genuine gap: `add-provider` adds a key, but there was NO way to REMOVE one
5
+ * over the gateway — keys live in `auth-profiles.json` (not config, so config.set
6
+ * can't reach them) and no tool exposes removal. This closes it. Operator-scoped
7
+ * per-agent (no per-session guard — allowlisted).
8
+ */
9
+ import { readProfiles, writeProfiles } from "../auth/profiles.js";
10
+ import { DEFAULT_AGENT_ID } from "../config/paths.js";
11
+ export function handleProviderRemove(params) {
12
+ const p = (params ?? {});
13
+ const providerId = (p.providerId ?? "").trim().toLowerCase();
14
+ const agentId = (p.agentId ?? "").trim() || DEFAULT_AGENT_ID;
15
+ if (!providerId)
16
+ return { ok: false, providerId, agentId, removed: 0, reason: "missing 'providerId'" };
17
+ const file = readProfiles(agentId);
18
+ let removed = 0;
19
+ for (const [key, profile] of Object.entries(file.profiles)) {
20
+ if ((profile.provider ?? "").trim().toLowerCase() === providerId) {
21
+ delete file.profiles[key];
22
+ removed++;
23
+ }
24
+ }
25
+ if (removed > 0)
26
+ writeProfiles(agentId, file);
27
+ return { ok: removed > 0, providerId, agentId, removed, ...(removed === 0 ? { reason: "no key found for that provider" } : {}) };
28
+ }
29
+ //# sourceMappingURL=provider-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-ops.js","sourceRoot":"","sources":["../../src/core/provider-ops.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAStD,MAAM,UAAU,oBAAoB,CAAC,MAAe;IACnD,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAA8C,CAAC;IACtE,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAC;IAC7D,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACvG,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,UAAU,EAAE,CAAC;YAClE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IACD,IAAI,OAAO,GAAG,CAAC;QAAE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,gCAAgC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AAClI,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA2QH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AA8FD,wBAAsB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwKjF"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAySH,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AA8FD,wBAAsB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAwKjF"}
@@ -135,6 +135,17 @@ import { mutateConfigAtomic } from "../config/io.js";
135
135
  import { acquireGatewayLock } from "./gateway-lock.js";
136
136
  import { clearHeartbeatFile, clearPidFile, writeHeartbeatFile, writePidFile } from "./gateway-probe.js";
137
137
  import { extractToken, matchesAnyToken, resolveGatewayAuth } from "./gateway-auth.js";
138
+ import { handleConfigGet, handleConfigList, handleConfigSchema, handleConfigSet, handleConfigUnset, handleConfigValidate, } from "./config-ops.js";
139
+ import { handleExecAllow, handleExecAllowPattern, handleExecDenyTest, handleExecList, handleExecRemove, } from "./exec-ops.js";
140
+ import { handleAgentsBind, handleAgentsBindings, handleAgentsUnbind } from "./agents-ops.js";
141
+ import { handlePairingApprove, handlePairingList, handlePairingRevoke } from "./pairing-ops.js";
142
+ import { handleSessionsCleanup } from "./sessions-ops.js";
143
+ import { handleMemoryManage, handleMemoryWrite } from "./memory-ops.js";
144
+ import { handleAgentsAdd, handleAgentsDelete, handleAgentsSetIdentity } from "./agents-crud-ops.js";
145
+ import { handleSkillsCreate, handleSkillsDelete, handleSkillsWriteFile } from "./skills-ops.js";
146
+ import { handleChannelsAllowAdd, handleChannelsAllowList, handleChannelsAllowRemove, handleChannelsConnect, handleChannelsDisconnect, } from "./channels-ops.js";
147
+ import { handleProviderRemove } from "./provider-ops.js";
148
+ import { handleComposio, handleOauth } from "./integrations-ops.js";
138
149
  // Persist a model selection to brigade.json's new wizard-shape (the lifted
139
150
  // code expected the older flat `defaultProvider`/`defaultModelId` fields).
140
151
  // Writes through the same `agents.defaults.{provider, model.primary}` path
@@ -4093,6 +4104,86 @@ async function continueBoot(args) {
4093
4104
  disposeHandlers.push(registerGatewayHandler("org.snapshot", (_params) => handleOrgSnapshot(undefined, {
4094
4105
  loadConfig: () => loadConfig(),
4095
4106
  })));
4107
+ // `config.*` — operator-level config CRUD over the wire (the `brigade
4108
+ // config` CLI, reachable from a remote client). Path/value/redact shape:
4109
+ // never session-targeted, so the guard-sweep correctly needs no per-session
4110
+ // access check. Reads/writes go through the mode-aware loadConfig/saveConfig,
4111
+ // so this works in filesystem AND Convex mode.
4112
+ disposeHandlers.push(registerGatewayHandler("config.get", handleConfigGet));
4113
+ disposeHandlers.push(registerGatewayHandler("config.set", handleConfigSet));
4114
+ disposeHandlers.push(registerGatewayHandler("config.unset", handleConfigUnset));
4115
+ disposeHandlers.push(registerGatewayHandler("config.list", handleConfigList));
4116
+ disposeHandlers.push(registerGatewayHandler("config.schema", handleConfigSchema));
4117
+ disposeHandlers.push(registerGatewayHandler("config.validate", handleConfigValidate));
4118
+ // `exec.*` — operator-level exec-approval allowlist CRUD (the `brigade exec`
4119
+ // CLI over the wire). Per-agent + operator-scoped (the operator manages
4120
+ // their OWN agents' bash-approval allowlist), the same posture as the
4121
+ // allowlisted exec-allow-all / exec-grant-skill RPCs — no per-session guard
4122
+ // (see ALLOWLIST_NO_GUARD_NEEDED in server.guard-sweep.test.ts). The
4123
+ // hard-deny safety net in exec-approvals.ts still applies on every allow.
4124
+ disposeHandlers.push(registerGatewayHandler("exec.list", handleExecList));
4125
+ disposeHandlers.push(registerGatewayHandler("exec.allow", handleExecAllow));
4126
+ disposeHandlers.push(registerGatewayHandler("exec.allow-pattern", handleExecAllowPattern));
4127
+ disposeHandlers.push(registerGatewayHandler("exec.remove", handleExecRemove));
4128
+ disposeHandlers.push(registerGatewayHandler("exec.deny-test", handleExecDenyTest));
4129
+ // `agents.*` — operator-level routing-binding management (which agent owns
4130
+ // which channel/account). The genuine no-other-path gap: agent add/delete/
4131
+ // set-identity are already reachable via the `manage_agent` tool, but
4132
+ // bindings had no remote path. Operator-scoped config mutation, no per-
4133
+ // session guard (allowlisted in server.guard-sweep.test.ts).
4134
+ disposeHandlers.push(registerGatewayHandler("agents.bindings", handleAgentsBindings));
4135
+ disposeHandlers.push(registerGatewayHandler("agents.bind", handleAgentsBind));
4136
+ disposeHandlers.push(registerGatewayHandler("agents.unbind", handleAgentsUnbind));
4137
+ // `pairing.*` — operator-level channel pairing (approve/revoke strangers who
4138
+ // DM the bot). Per-channel + operator-scoped, no per-session guard. The RPCs
4139
+ // require an explicit channel (a client gets the channel list from
4140
+ // system.capabilities), unlike the CLI's single-channel auto-pick.
4141
+ disposeHandlers.push(registerGatewayHandler("pairing.list", handlePairingList));
4142
+ disposeHandlers.push(registerGatewayHandler("pairing.approve", handlePairingApprove));
4143
+ disposeHandlers.push(registerGatewayHandler("pairing.revoke", handlePairingRevoke));
4144
+ // `sessions.cleanup` — operator maintenance: delete an agent's stale idle
4145
+ // transcript files (the gateway regenerates the store entry on next access).
4146
+ // NOT session-content access (unlike sessions.list/history), so no per-
4147
+ // session guard (allowlisted in server.guard-sweep.test.ts).
4148
+ disposeHandlers.push(registerGatewayHandler("sessions.cleanup", handleSessionsCleanup));
4149
+ // `memory.*` — Tideline write + governance (write_memory / manage_memory).
4150
+ // Memory lives in facts.jsonl (NOT config), so config.set can't reach it;
4151
+ // these are the only typed remote path to MUTATE memory (read is covered by
4152
+ // memory-query / memory-graph). Operator-scoped owner origin, no per-session
4153
+ // guard (allowlisted in server.guard-sweep.test.ts).
4154
+ disposeHandlers.push(registerGatewayHandler("memory.write", handleMemoryWrite));
4155
+ disposeHandlers.push(registerGatewayHandler("memory.manage", handleMemoryManage));
4156
+ // agents.add/delete/set-identity — agent CRUD (reuses the manage_agent tool,
4157
+ // which wraps `brigade agents add/delete/set-identity`). Seeds/soft-deletes a
4158
+ // workspace, so config.set alone can't do it. Operator-scoped (allowlisted).
4159
+ disposeHandlers.push(registerGatewayHandler("agents.add", handleAgentsAdd));
4160
+ disposeHandlers.push(registerGatewayHandler("agents.delete", handleAgentsDelete));
4161
+ disposeHandlers.push(registerGatewayHandler("agents.set-identity", handleAgentsSetIdentity));
4162
+ // skills.create/delete/write-file — skill authoring (reuses the manage_skill
4163
+ // tool). SKILL.md files on disk, not config. (status/install/update already
4164
+ // cover read/install/enable.) Operator-scoped (allowlisted).
4165
+ disposeHandlers.push(registerGatewayHandler("skills.create", handleSkillsCreate));
4166
+ disposeHandlers.push(registerGatewayHandler("skills.delete", handleSkillsDelete));
4167
+ disposeHandlers.push(registerGatewayHandler("skills.write-file", handleSkillsWriteFile));
4168
+ // channels.* — LIVE connect/disconnect (runtime adapter via the global
4169
+ // channel manager) + DM allow-from (a per-channel file store, not config).
4170
+ // Channel enable/disable/policy are already config.set-reachable. Operator-
4171
+ // scoped (allowlisted). connect reuses the owner-scoped connect_channel tool.
4172
+ disposeHandlers.push(registerGatewayHandler("channels.connect", handleChannelsConnect));
4173
+ disposeHandlers.push(registerGatewayHandler("channels.disconnect", handleChannelsDisconnect));
4174
+ disposeHandlers.push(registerGatewayHandler("channels.allow-add", handleChannelsAllowAdd));
4175
+ disposeHandlers.push(registerGatewayHandler("channels.allow-remove", handleChannelsAllowRemove));
4176
+ disposeHandlers.push(registerGatewayHandler("channels.allow-list", handleChannelsAllowList));
4177
+ // provider.remove — delete a provider key (auth-profiles.json, not config;
4178
+ // add-provider exists, removal had no gateway path). Operator-scoped.
4179
+ disposeHandlers.push(registerGatewayHandler("provider.remove", handleProviderRemove));
4180
+ // composio + oauth — integrations. `composio` is remote-clean (Composio
4181
+ // hosts the OAuth callback; the gateway hands over a click-link + polls).
4182
+ // `oauth` is the DIY loopback flow (callback on the gateway host — completes
4183
+ // only for a local/tunneled operator; status/token work remotely). Both
4184
+ // reuse the owner-scoped tools. Operator-scoped (allowlisted).
4185
+ disposeHandlers.push(registerGatewayHandler("composio", handleComposio));
4186
+ disposeHandlers.push(registerGatewayHandler("oauth", handleOauth));
4096
4187
  // Wave O0.8 GAP 11 — opt the session inbox into JSONL persistence at
4097
4188
  // gateway boot. The disk write surface defaults off so the existing
4098
4189
  // unit-test fleet (which doesn't tempdir-isolate ~/.brigade) keeps