@canonmsg/claude-code-plugin 0.21.1 → 0.22.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Canon Plugin for Claude Code
1
+ # Canon Host for Claude Code
2
2
 
3
3
  Connect Claude Code to [Canon](https://github.com/HeyBobChan/canon) — a messaging app where AI agents are first-class citizens. Control Claude Code from your phone.
4
4
 
@@ -67,22 +67,6 @@ canon-register --name "Frontend" --description "React work" --phone "+1..." --pr
67
67
  CANON_AGENT=frontend canon-claude --cwd ~/projects/frontend
68
68
  ```
69
69
 
70
- ## Channel mode (alternative)
71
-
72
- For a lighter integration without session controls, run Canon as a channel plugin inside your own Claude Code session. The `canon-channel-server` binary (installed by this package) is an MCP stdio server that Claude Code launches.
73
-
74
- Register the channel server with Claude Code's MCP config and provide your Canon API key via the `CANON_API_KEY` environment variable:
75
-
76
- ```bash
77
- # Register once per Claude Code project
78
- claude mcp add canon-channel canon-channel-server
79
-
80
- # Then start Claude Code with your Canon key in the environment
81
- CANON_API_KEY=agk_live_... claude
82
- ```
83
-
84
- Channel mode forwards messages between Canon conversations and the embedded Claude Code session without managing session lifecycle. It is shown as non-revivable by `canon-necromance`. Use `canon-claude` host mode when you want phone-controlled runtime sessions with setup and live controls.
85
-
86
70
  ## Development
87
71
 
88
72
  ```bash
package/dist/host.js CHANGED
@@ -36,7 +36,7 @@ import { ApprovalManager, readHostSessionConfig, resolveHostWorkspaceCwd, } from
36
36
  import { runCli } from './cli-entry.js';
37
37
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
38
38
  import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
39
- import { applyClaudeSessionControl, buildClaudeFinalMessageId, buildClaudeInitialSessionState, createClaudeInputEnvelope, formatClaudeControlError, normalizeClaudeEffortLevel, parseClaudePermissionMode, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
39
+ import { applyClaudeSessionControl, buildClaudeFinalMessageId, buildClaudeInitialSessionState, claudeModelInfoToOption, createClaudeInputEnvelope, formatClaudeControlError, isClaudeCustomModelOption, normalizeClaudeEffortLevel, parseClaudePermissionMode, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
40
40
  import { claudeThinkingToConfig, parseClaudeSessionDefaults, resolveEffectiveSessionSettings, } from './session-defaults.js';
41
41
  import { mapClaudeSubagentActivity } from './subagent-activity.js';
42
42
  function parseRuntimeVisibilityPreset(value) {
@@ -331,11 +331,20 @@ function resolveClaudeCliPath() {
331
331
  console.error('[canon-host] claude CLI not on PATH; using SDK-bundled binary');
332
332
  return undefined;
333
333
  }
334
+ const CLAUDE_SUPPLEMENTAL_MODEL_PROBES = ['opus'];
335
+ function mergeModelOptions(...groups) {
336
+ const byValue = new Map();
337
+ for (const group of groups) {
338
+ for (const option of group) {
339
+ if (!byValue.has(option.value)) {
340
+ byValue.set(option.value, option);
341
+ }
342
+ }
343
+ }
344
+ return [...byValue.values()];
345
+ }
334
346
  function toModelOptions(models) {
335
- return models.map((model) => ({
336
- value: model.value,
337
- label: model.displayName,
338
- }));
347
+ return models.map(claudeModelInfoToOption);
339
348
  }
340
349
  async function publishAgentRuntime(port, runtime) {
341
350
  await port.publishHostAgentRuntime({
@@ -375,6 +384,21 @@ function resolveExecutionFallbackReason(environment) {
375
384
  : environment.reason;
376
385
  }
377
386
  async function detectRuntimeModels(cwd) {
387
+ const discovered = await detectRuntimeModelsForSelection(cwd, 'default');
388
+ const supplemental = await Promise.all(CLAUDE_SUPPLEMENTAL_MODEL_PROBES.map(async (model) => {
389
+ try {
390
+ const options = await detectRuntimeModelsForSelection(cwd, model);
391
+ const option = options.find((entry) => entry.value === model);
392
+ return option && !isClaudeCustomModelOption(option) ? [option] : [];
393
+ }
394
+ catch (error) {
395
+ console.error(`[canon-host] Supplemental Claude model probe failed for ${model}:`, error);
396
+ return [];
397
+ }
398
+ }));
399
+ return mergeModelOptions(discovered, ...supplemental);
400
+ }
401
+ async function detectRuntimeModelsForSelection(cwd, model) {
378
402
  const emptyPrompt = {
379
403
  async *[Symbol.asyncIterator]() { },
380
404
  };
@@ -384,7 +408,7 @@ async function detectRuntimeModels(cwd) {
384
408
  options: {
385
409
  systemPrompt: { type: 'preset', preset: 'claude_code' },
386
410
  tools: { type: 'preset', preset: 'claude_code' },
387
- model: 'default',
411
+ model,
388
412
  permissionMode: 'default',
389
413
  cwd,
390
414
  settings: { forceLoginMethod: 'claudeai' },
@@ -1075,7 +1099,6 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1075
1099
  cwd,
1076
1100
  environment,
1077
1101
  query: q,
1078
- controlApplySeq: new Map(),
1079
1102
  sendInput,
1080
1103
  enqueueInbound: (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, artifactRoutingMode = 'disabled') => enqueueInboundMessage(msg, intent, sourceMessageId, markAccepted, isOwnerTurn, artifactRoutingMode),
1081
1104
  state: buildClaudeInitialSessionState({
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { main as serverMain } from './server.js';
2
1
  export { main as hostMain } from './host.js';
3
2
  export { main as registerMain } from './register.js';
4
3
  export { main as setupMain } from './setup.js';
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export { main as serverMain } from './server.js';
2
1
  export { main as hostMain } from './host.js';
3
2
  export { main as registerMain } from './register.js';
4
3
  export { main as setupMain } from './setup.js';
@@ -54,7 +54,6 @@ export declare function buildClaudeInitialSessionState(input: {
54
54
  }): ClaudeHostSessionState;
55
55
  export interface ClaudeSessionControlTarget {
56
56
  state: ClaudeHostSessionState;
57
- controlApplySeq: Map<string, number>;
58
57
  closed: boolean;
59
58
  query: Pick<Query, 'setModel' | 'setPermissionMode' | 'applyFlagSettings'>;
60
59
  writeState: () => void;
@@ -103,6 +102,13 @@ export declare function shouldDeliverClaudeFinal(input: {
103
102
  interruptedTurnKeys: ReadonlySet<string>;
104
103
  }): boolean;
105
104
  export declare function resetClaudeCompletedTurnState(session: ClaudeCompletedTurnState): void;
105
+ export interface ClaudeModelInfoLike {
106
+ value: string;
107
+ displayName: string;
108
+ description?: string;
109
+ }
110
+ export declare function claudeModelInfoToOption(model: ClaudeModelInfoLike): ModelOption;
111
+ export declare function isClaudeCustomModelOption(option: ModelOption | null | undefined): boolean;
106
112
  export declare function resolveClaudeModelOptions(input: {
107
113
  discovered: ReadonlyArray<ModelOption>;
108
114
  selectedModel?: string | null;
@@ -29,21 +29,23 @@ export function buildClaudeInitialSessionState(input) {
29
29
  state: 'idle',
30
30
  };
31
31
  }
32
- function nextControlApplySeq(target, controlId) {
33
- const nextSeq = (target.controlApplySeq.get(controlId) ?? 0) + 1;
34
- target.controlApplySeq.set(controlId, nextSeq);
35
- return nextSeq;
36
- }
37
- function applyOptimisticControl(input) {
38
- const seq = nextControlApplySeq(input.target, input.controlId);
39
- const applyPromise = (async () => input.apply())();
40
- void applyPromise
41
- .catch((error) => {
42
- if (input.target.closed || input.target.controlApplySeq.get(input.controlId) !== seq) {
32
+ /**
33
+ * Apply a control to the Claude SDK. W2-A2 deleted the seq-guarded optimistic
34
+ * revert machinery (commit c356bdf8) that masked the 2–11s control poll: the
35
+ * bridge is event-push now (~100ms), so the state is already published by the
36
+ * caller's writeState() and the SDK apply is fired without a revert dance. A
37
+ * failed apply surfaces as a runtime-control error (the display truth the app
38
+ * reads) — the same shape codex uses. `value` is the requested value for the
39
+ * error record; `controlId` scopes the error.
40
+ */
41
+ function applyControl(input) {
42
+ // Fire the SDK apply SYNCHRONOUSLY (an async setter may still resolve later);
43
+ // wrap so a synchronous throw is caught in the same rejection path.
44
+ void (async () => input.apply())().catch((error) => {
45
+ if (input.target.closed)
43
46
  return;
44
- }
45
- input.revert();
46
47
  input.target.setRuntimeControlError(input.controlId, input.value, error);
48
+ input.target.writeState();
47
49
  });
48
50
  }
49
51
  function setRuntimeControlValue(state, controlId, value) {
@@ -58,20 +60,13 @@ export function applyClaudeSessionControl(target, ctrl) {
58
60
  target.setRuntimeControlError('model', String(ctrl.model), new Error('Unsupported Claude model.'));
59
61
  }
60
62
  else if (nextModel && nextModel !== target.state.model) {
61
- const previous = target.state.model;
62
63
  target.state.model = nextModel;
63
64
  target.clearRuntimeControlError('model');
64
- applyOptimisticControl({
65
+ applyControl({
65
66
  target,
66
67
  controlId: 'model',
67
68
  value: nextModel,
68
69
  apply: () => target.query.setModel(nextModel),
69
- revert: () => {
70
- if (previous === undefined)
71
- delete target.state.model;
72
- else
73
- target.state.model = previous;
74
- },
75
70
  });
76
71
  }
77
72
  const nextPermissionMode = parseClaudePermissionMode(ctrl.permissionMode);
@@ -79,41 +74,27 @@ export function applyClaudeSessionControl(target, ctrl) {
79
74
  target.setRuntimeControlError('permissionMode', ctrl.permissionMode, new Error(`Unsupported Claude permission mode: ${ctrl.permissionMode}`));
80
75
  }
81
76
  else if (nextPermissionMode && nextPermissionMode !== target.state.permissionMode) {
82
- // Claude Code applies permission-mode changes asynchronously. We publish
83
- // the requested value immediately so Canon does not show a stale "requested"
84
- // state while the SDK catches up, then revert if the current apply fails.
85
- const previous = target.state.permissionMode;
77
+ // Claude Code applies permission-mode changes asynchronously; publish the
78
+ // requested value immediately (writeState below) and surface an error if the
79
+ // apply fails.
86
80
  target.state.permissionMode = nextPermissionMode;
87
81
  target.clearRuntimeControlError('permissionMode');
88
- applyOptimisticControl({
82
+ applyControl({
89
83
  target,
90
84
  controlId: 'permissionMode',
91
85
  value: nextPermissionMode,
92
86
  apply: () => target.query.setPermissionMode(nextPermissionMode),
93
- revert: () => {
94
- if (previous === undefined)
95
- delete target.state.permissionMode;
96
- else
97
- target.state.permissionMode = previous;
98
- },
99
87
  });
100
88
  }
101
89
  const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
102
90
  if (nextEffort && nextEffort !== target.state.effort) {
103
- const previous = target.state.effort;
104
91
  target.state.effort = nextEffort;
105
92
  target.clearRuntimeControlError('effort');
106
- applyOptimisticControl({
93
+ applyControl({
107
94
  target,
108
95
  controlId: 'effort',
109
96
  value: nextEffort,
110
97
  apply: () => target.query.applyFlagSettings({ effortLevel: nextEffort }),
111
- revert: () => {
112
- if (previous === undefined)
113
- delete target.state.effort;
114
- else
115
- target.state.effort = previous;
116
- },
117
98
  });
118
99
  }
119
100
  else if (ctrl.effort !== undefined && !nextEffort) {
@@ -126,20 +107,13 @@ export function applyClaudeSessionControl(target, ctrl) {
126
107
  target.setRuntimeControlError('thinking', String(runtimeControlValues.thinking), new Error('Unsupported Claude thinking mode.'));
127
108
  }
128
109
  else if (nextThinking !== target.state.runtimeControlValues?.thinking) {
129
- const previous = target.state.runtimeControlValues?.thinking;
130
110
  setRuntimeControlValue(target.state, 'thinking', nextThinking);
131
111
  target.clearRuntimeControlError('thinking');
132
- applyOptimisticControl({
112
+ applyControl({
133
113
  target,
134
114
  controlId: 'thinking',
135
115
  value: nextThinking,
136
116
  apply: () => target.query.applyFlagSettings({ alwaysThinkingEnabled: claudeThinkingToFlagValue(nextThinking) }),
137
- revert: () => {
138
- if (previous === undefined)
139
- delete target.state.runtimeControlValues?.thinking;
140
- else
141
- setRuntimeControlValue(target.state, 'thinking', previous);
142
- },
143
117
  });
144
118
  }
145
119
  }
@@ -149,20 +123,13 @@ export function applyClaudeSessionControl(target, ctrl) {
149
123
  target.setRuntimeControlError('ultracode', String(runtimeControlValues.ultracode), new Error('Unsupported Claude ultracode mode.'));
150
124
  }
151
125
  else if (nextUltracode !== target.state.runtimeControlValues?.ultracode) {
152
- const previous = target.state.runtimeControlValues?.ultracode;
153
126
  setRuntimeControlValue(target.state, 'ultracode', nextUltracode);
154
127
  target.clearRuntimeControlError('ultracode');
155
- applyOptimisticControl({
128
+ applyControl({
156
129
  target,
157
130
  controlId: 'ultracode',
158
131
  value: nextUltracode,
159
132
  apply: () => target.query.applyFlagSettings({ ultracode: nextUltracode === 'on' }),
160
- revert: () => {
161
- if (previous === undefined)
162
- delete target.state.runtimeControlValues?.ultracode;
163
- else
164
- setRuntimeControlValue(target.state, 'ultracode', previous);
165
- },
166
133
  });
167
134
  }
168
135
  }
@@ -215,6 +182,32 @@ export function resetClaudeCompletedTurnState(session) {
215
182
  session.toolInProgress = false;
216
183
  session.turnState = 'idle';
217
184
  }
185
+ function normalizeModelDescription(value) {
186
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
187
+ }
188
+ function displayLabelWithClaudeVersion(input) {
189
+ const label = input.displayName.trim() || input.value;
190
+ const description = normalizeModelDescription(input.description);
191
+ if (!description || label.includes('(') || /\d/.test(label))
192
+ return label;
193
+ const [summary] = description.split('·');
194
+ const versionedLabel = summary?.trim();
195
+ return versionedLabel
196
+ && versionedLabel.toLowerCase().startsWith(label.toLowerCase())
197
+ && /\d/.test(versionedLabel)
198
+ ? versionedLabel
199
+ : label;
200
+ }
201
+ export function claudeModelInfoToOption(model) {
202
+ return {
203
+ value: model.value,
204
+ label: displayLabelWithClaudeVersion(model),
205
+ ...(normalizeModelDescription(model.description) ? { description: normalizeModelDescription(model.description) } : {}),
206
+ };
207
+ }
208
+ export function isClaudeCustomModelOption(option) {
209
+ return option?.description?.trim().toLowerCase() === 'custom model';
210
+ }
218
211
  export function resolveClaudeModelOptions(input) {
219
212
  const byValue = new Map();
220
213
  for (const option of input.discovered) {
package/dist/setup.d.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  /**
3
3
  * Setup script for the Canon Claude Code plugin.
4
4
  *
5
- * Run after `npm install -g @canonmsg/claude-code-plugin` to:
6
- * 1. Install the /canon-register and /canon-configure skills
7
- * 2. Show the user how to add the MCP server to their .mcp.json
5
+ * Run after `npm install -g @canonmsg/claude-code-plugin` to install the
6
+ * /canon-register and /canon-configure skills.
8
7
  */
9
8
  export declare function main(): void;
package/dist/setup.js CHANGED
@@ -2,15 +2,14 @@
2
2
  /**
3
3
  * Setup script for the Canon Claude Code plugin.
4
4
  *
5
- * Run after `npm install -g @canonmsg/claude-code-plugin` to:
6
- * 1. Install the /canon-register and /canon-configure skills
7
- * 2. Show the user how to add the MCP server to their .mcp.json
5
+ * Run after `npm install -g @canonmsg/claude-code-plugin` to install the
6
+ * /canon-register and /canon-configure skills.
8
7
  */
9
8
  import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
10
9
  import { join, dirname } from 'node:path';
11
10
  import { fileURLToPath } from 'node:url';
12
11
  import { runCli } from './cli-entry.js';
13
- const HELP = `canon-setup — install Claude Code Canon skills and print MCP setup
12
+ const HELP = `canon-setup — install Claude Code Canon skills
14
13
 
15
14
  USAGE
16
15
  canon-setup
@@ -19,8 +18,7 @@ FLAGS
19
18
  --help, -h Show this help
20
19
  --version, -V Show package version
21
20
 
22
- This command writes Canon Claude Code skill files under ~/.claude/skills and
23
- prints the MCP server configuration for Claude Code channel mode.`;
21
+ This command writes Canon Claude Code skill files under ~/.claude/skills.`;
24
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
23
  const home = process.env.HOME || process.env.USERPROFILE || '';
26
24
  const skillsDir = join(home, '.claude', 'skills');
@@ -45,27 +43,6 @@ function installSkills() {
45
43
  console.log(` Installed /canon-${skill}`);
46
44
  }
47
45
  }
48
- // ── Show MCP config instructions ───────────────────────────────────────
49
- function showMcpInstructions() {
50
- // Find the installed server.js path
51
- const serverPath = join(__dirname, 'server.js');
52
- console.log(`
53
- Add this to your project's .mcp.json (or ~/.mcp.json for global):
54
-
55
- {
56
- "mcpServers": {
57
- "canon-channel": {
58
- "command": "node",
59
- "args": ["${serverPath}"]
60
- }
61
- }
62
- }
63
-
64
- Then start Claude Code with:
65
-
66
- claude --dangerously-load-development-channels server:canon-channel
67
- `);
68
- }
69
46
  // ── Main ───────────────────────────────────────────────────────────────
70
47
  export function main() {
71
48
  console.log('Canon Claude Code Plugin Setup');
@@ -75,8 +52,7 @@ export function main() {
75
52
  console.log('\nRegistering your agent...');
76
53
  console.log(' Run /canon-register in Claude Code to register a new agent');
77
54
  console.log(' Run /canon-configure if you already have an API key\n');
78
- console.log('MCP server configuration:');
79
- showMcpInstructions();
55
+ console.log('Run `canon-claude` to connect this agent to Canon.');
80
56
  }
81
57
  runCli(import.meta.url, main, (error) => {
82
58
  console.error('[canon-setup] Fatal error:', error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.21.1",
4
- "description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
3
+ "version": "0.22.0",
4
+ "description": "Canon host for Claude Code — messaging where AI agents are first-class citizens",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -12,15 +12,13 @@
12
12
  }
13
13
  },
14
14
  "bin": {
15
- "canon-channel-server": "dist/server.js",
16
15
  "canon-claude": "dist/host.js",
17
16
  "canon-register": "dist/register.js",
18
17
  "canon-setup": "dist/setup.js"
19
18
  },
20
19
  "files": [
21
20
  "dist",
22
- "skills",
23
- ".claude-plugin"
21
+ "skills"
24
22
  ],
25
23
  "scripts": {
26
24
  "prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../../packages/core ../../packages/agent-sdk ../../packages/framework ../../packages/bridge ../../packages/agent-host",
@@ -31,11 +29,11 @@
31
29
  },
32
30
  "dependencies": {
33
31
  "@anthropic-ai/claude-agent-sdk": "^0.3.200",
34
- "@canonmsg/agent-host": "^0.4.0",
35
- "@canonmsg/agent-sdk": "^3.4.4",
36
- "@canonmsg/bridge": "^0.2.0",
37
- "@canonmsg/core": "^3.2.0",
38
- "@canonmsg/framework": "^0.2.0",
32
+ "@canonmsg/agent-host": "^0.5.0",
33
+ "@canonmsg/agent-sdk": "^4.0.0",
34
+ "@canonmsg/bridge": "^0.3.0",
35
+ "@canonmsg/core": "^3.3.0",
36
+ "@canonmsg/framework": "^0.3.0",
39
37
  "@modelcontextprotocol/sdk": "^1.29.0"
40
38
  },
41
39
  "engines": {
@@ -36,15 +36,9 @@ Add an existing Canon agent API key as a named profile.
36
36
 
37
37
  5. Write the updated profiles to `~/.canon/agents.json`.
38
38
 
39
- 6. Tell the user: **"API key saved as profile '<profile>'. Restart Claude Code to connect."**
39
+ 6. Tell the user: **"API key saved as profile '<profile>'. Run `canon-claude` to connect."**
40
40
 
41
- If they have multiple agents and want to pin one, add to `.mcp.json`:
42
- ```json
43
- {
44
- "mcpServers": {
45
- "canon-channel": {
46
- "env": { "CANON_AGENT": "<profile>" }
47
- }
48
- }
49
- }
41
+ If they have multiple agents and want to pin one, start the host with the profile name:
42
+ ```bash
43
+ CANON_AGENT=<profile> canon-claude
50
44
  ```
@@ -1,25 +0,0 @@
1
- {
2
- "name": "Canon",
3
- "description": "Connect Claude Code to Canon — messaging where AI agents are first-class citizens",
4
- "version": "0.1.0",
5
- "channels": [
6
- {
7
- "server": "canon-channel",
8
- "userConfig": {
9
- "api_key": {
10
- "description": "Canon agent API key (agk_live_...)",
11
- "sensitive": true
12
- }
13
- }
14
- }
15
- ],
16
- "mcpServers": {
17
- "canon-channel": {
18
- "command": "node",
19
- "args": ["${CLAUDE_PLUGIN_ROOT}/dist/server.js"],
20
- "env": {
21
- "CANON_PLUGIN_API_KEY": "${user_config.api_key}"
22
- }
23
- }
24
- }
25
- }
@@ -1,33 +0,0 @@
1
- import type { ApprovalManager } from '@canonmsg/core/host';
2
- /**
3
- * HTTP server that bridges Claude Code's PermissionRequest hook
4
- * to Canon's ApprovalManager.
5
- *
6
- * Listens on localhost for POST /approve from Claude Code hooks.
7
- */
8
- export declare class ApprovalHttpServer {
9
- private server;
10
- private manager;
11
- private port;
12
- private getConversationId;
13
- private getIsOwnerTurn;
14
- private token;
15
- private maxBodyBytes;
16
- private requestTimeoutMs;
17
- constructor(manager: ApprovalManager, getConversationId: () => string | null, opts?: {
18
- port?: number;
19
- token?: string;
20
- maxBodyBytes?: number;
21
- requestTimeoutMs?: number;
22
- getIsOwnerTurn?: () => boolean;
23
- });
24
- private static readonly MAX_PORT_RETRIES;
25
- get authToken(): string;
26
- start(): Promise<number>;
27
- stop(): Promise<void>;
28
- private handleRequest;
29
- private hasValidToken;
30
- private handleApproval;
31
- private sendHookResponse;
32
- private classifyRisk;
33
- }