discord-agent-bridge 0.1.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/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/app.js +210 -0
- package/dist/app.js.map +1 -0
- package/dist/app.test.js +117 -0
- package/dist/app.test.js.map +1 -0
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.js +91 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/core/auditLog.js +65 -0
- package/dist/core/auditLog.js.map +1 -0
- package/dist/core/auditLog.test.js +124 -0
- package/dist/core/auditLog.test.js.map +1 -0
- package/dist/core/auth.js +106 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/auth.test.js +216 -0
- package/dist/core/auth.test.js.map +1 -0
- package/dist/core/channelRegistry.js +119 -0
- package/dist/core/channelRegistry.js.map +1 -0
- package/dist/core/channelRegistry.test.js +114 -0
- package/dist/core/channelRegistry.test.js.map +1 -0
- package/dist/core/commandPolicy.js +6 -0
- package/dist/core/commandPolicy.js.map +1 -0
- package/dist/core/commandRouter.js +9 -0
- package/dist/core/commandRouter.js.map +1 -0
- package/dist/core/config.js +141 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.js +149 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/configResolver.js +90 -0
- package/dist/core/configResolver.js.map +1 -0
- package/dist/core/configResolver.test.js +138 -0
- package/dist/core/configResolver.test.js.map +1 -0
- package/dist/core/configSchema.js +164 -0
- package/dist/core/configSchema.js.map +1 -0
- package/dist/core/contracts.js +5 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/eventBus.js +42 -0
- package/dist/core/eventBus.js.map +1 -0
- package/dist/core/eventBus.test.js +72 -0
- package/dist/core/eventBus.test.js.map +1 -0
- package/dist/core/hookBridge.js +11 -0
- package/dist/core/hookBridge.js.map +1 -0
- package/dist/core/logger.js +116 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/logger.test.js +88 -0
- package/dist/core/logger.test.js.map +1 -0
- package/dist/core/modeRegistry.js +33 -0
- package/dist/core/modeRegistry.js.map +1 -0
- package/dist/core/permissionResolver.js +43 -0
- package/dist/core/permissionResolver.js.map +1 -0
- package/dist/core/permissionResolver.test.js +136 -0
- package/dist/core/permissionResolver.test.js.map +1 -0
- package/dist/core/providerCatalog.js +240 -0
- package/dist/core/providerCatalog.js.map +1 -0
- package/dist/core/providerCatalog.test.js +230 -0
- package/dist/core/providerCatalog.test.js.map +1 -0
- package/dist/core/sessionOrchestrator.js +463 -0
- package/dist/core/sessionOrchestrator.js.map +1 -0
- package/dist/core/sessionOrchestrator.test.js +344 -0
- package/dist/core/sessionOrchestrator.test.js.map +1 -0
- package/dist/core/state/schema.js +49 -0
- package/dist/core/state/schema.js.map +1 -0
- package/dist/core/state/store.js +86 -0
- package/dist/core/state/store.js.map +1 -0
- package/dist/core/state/store.test.js +99 -0
- package/dist/core/state/store.test.js.map +1 -0
- package/dist/core/usageService.js +310 -0
- package/dist/core/usageService.js.map +1 -0
- package/dist/core/usageService.test.js +234 -0
- package/dist/core/usageService.test.js.map +1 -0
- package/dist/discord/alwaysAllow.test.js +181 -0
- package/dist/discord/alwaysAllow.test.js.map +1 -0
- package/dist/discord/client.js +666 -0
- package/dist/discord/client.js.map +1 -0
- package/dist/discord/client.test.js +256 -0
- package/dist/discord/client.test.js.map +1 -0
- package/dist/discord/configPanel.js +270 -0
- package/dist/discord/configPanel.js.map +1 -0
- package/dist/discord/configPanel.test.js +332 -0
- package/dist/discord/configPanel.test.js.map +1 -0
- package/dist/discord/directoryBrowser.js +151 -0
- package/dist/discord/directoryBrowser.js.map +1 -0
- package/dist/discord/directoryBrowser.test.js +163 -0
- package/dist/discord/directoryBrowser.test.js.map +1 -0
- package/dist/discord/favorites.js +10 -0
- package/dist/discord/favorites.js.map +1 -0
- package/dist/discord/fileDownload.js +94 -0
- package/dist/discord/fileDownload.js.map +1 -0
- package/dist/discord/fileDownload.test.js +59 -0
- package/dist/discord/fileDownload.test.js.map +1 -0
- package/dist/discord/format.js +104 -0
- package/dist/discord/format.js.map +1 -0
- package/dist/discord/format.test.js +40 -0
- package/dist/discord/format.test.js.map +1 -0
- package/dist/discord/guildChannels.js +97 -0
- package/dist/discord/guildChannels.js.map +1 -0
- package/dist/discord/guildChannels.test.js +231 -0
- package/dist/discord/guildChannels.test.js.map +1 -0
- package/dist/discord/i18n.js +207 -0
- package/dist/discord/i18n.js.map +1 -0
- package/dist/discord/i18n.test.js +31 -0
- package/dist/discord/i18n.test.js.map +1 -0
- package/dist/discord/interactionRouter.js +954 -0
- package/dist/discord/interactionRouter.js.map +1 -0
- package/dist/discord/interactionRouter.test.js +1245 -0
- package/dist/discord/interactionRouter.test.js.map +1 -0
- package/dist/discord/messageRouter.js +221 -0
- package/dist/discord/messageRouter.js.map +1 -0
- package/dist/discord/messageRouter.test.js +360 -0
- package/dist/discord/messageRouter.test.js.map +1 -0
- package/dist/discord/ports.js +12 -0
- package/dist/discord/ports.js.map +1 -0
- package/dist/discord/renderers/defaultSet.test.js +97 -0
- package/dist/discord/renderers/defaultSet.test.js.map +1 -0
- package/dist/discord/renderers/diffView.js +73 -0
- package/dist/discord/renderers/diffView.js.map +1 -0
- package/dist/discord/renderers/diffView.test.js +54 -0
- package/dist/discord/renderers/diffView.test.js.map +1 -0
- package/dist/discord/renderers/index.js +143 -0
- package/dist/discord/renderers/index.js.map +1 -0
- package/dist/discord/renderers/index.test.js +124 -0
- package/dist/discord/renderers/index.test.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.js +19 -0
- package/dist/discord/renderers/mentionOnComplete.js.map +1 -0
- package/dist/discord/renderers/mentionOnComplete.test.js +35 -0
- package/dist/discord/renderers/mentionOnComplete.test.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.js +150 -0
- package/dist/discord/renderers/permissionButtons.js.map +1 -0
- package/dist/discord/renderers/permissionButtons.test.js +108 -0
- package/dist/discord/renderers/permissionButtons.test.js.map +1 -0
- package/dist/discord/renderers/resultLine.js +25 -0
- package/dist/discord/renderers/resultLine.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.js +20 -0
- package/dist/discord/renderers/statusEmbed.js.map +1 -0
- package/dist/discord/renderers/statusEmbed.test.js +32 -0
- package/dist/discord/renderers/statusEmbed.test.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.js +134 -0
- package/dist/discord/renderers/streamEmbed.js.map +1 -0
- package/dist/discord/renderers/streamEmbed.test.js +97 -0
- package/dist/discord/renderers/streamEmbed.test.js.map +1 -0
- package/dist/discord/renderers/toolThread.js +65 -0
- package/dist/discord/renderers/toolThread.js.map +1 -0
- package/dist/discord/renderers/toolThread.test.js +54 -0
- package/dist/discord/renderers/toolThread.test.js.map +1 -0
- package/dist/discord/renderers/transcriptFeed.js +33 -0
- package/dist/discord/renderers/transcriptFeed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.js +78 -0
- package/dist/discord/renderers/usageEmbed.js.map +1 -0
- package/dist/discord/renderers/usageEmbed.test.js +42 -0
- package/dist/discord/renderers/usageEmbed.test.js.map +1 -0
- package/dist/discord/wiring.js +214 -0
- package/dist/discord/wiring.js.map +1 -0
- package/dist/discord/wiring.test.js +252 -0
- package/dist/discord/wiring.test.js.map +1 -0
- package/dist/discord/wizard/channelWizard.js +305 -0
- package/dist/discord/wizard/channelWizard.js.map +1 -0
- package/dist/discord/wizard/channelWizard.test.js +298 -0
- package/dist/discord/wizard/channelWizard.test.js.map +1 -0
- package/dist/discord/wizard/resumeWizard.js +123 -0
- package/dist/discord/wizard/resumeWizard.js.map +1 -0
- package/dist/modes/claude/index.js +80 -0
- package/dist/modes/claude/index.js.map +1 -0
- package/dist/modes/claude/listResumable.test.js +72 -0
- package/dist/modes/claude/listResumable.test.js.map +1 -0
- package/dist/modes/claude/mcpFileTool.js +82 -0
- package/dist/modes/claude/mcpFileTool.js.map +1 -0
- package/dist/modes/claude/permissions.js +35 -0
- package/dist/modes/claude/permissions.js.map +1 -0
- package/dist/modes/claude/plugins.js +56 -0
- package/dist/modes/claude/plugins.js.map +1 -0
- package/dist/modes/claude/session.js +324 -0
- package/dist/modes/claude/session.js.map +1 -0
- package/dist/modes/claude/session.test.js +415 -0
- package/dist/modes/claude/session.test.js.map +1 -0
- package/dist/modes/codex/discovery.js +221 -0
- package/dist/modes/codex/discovery.js.map +1 -0
- package/dist/modes/codex/discovery.test.js +213 -0
- package/dist/modes/codex/discovery.test.js.map +1 -0
- package/dist/modes/codex/eventMapper.js +262 -0
- package/dist/modes/codex/eventMapper.js.map +1 -0
- package/dist/modes/codex/index.js +132 -0
- package/dist/modes/codex/index.js.map +1 -0
- package/dist/modes/codex/index.test.js +170 -0
- package/dist/modes/codex/index.test.js.map +1 -0
- package/dist/modes/codex/runner.js +207 -0
- package/dist/modes/codex/runner.js.map +1 -0
- package/dist/modes/codex/runner.test.js +587 -0
- package/dist/modes/codex/runner.test.js.map +1 -0
- package/dist/modes/codex/sqliteReader.js +171 -0
- package/dist/modes/codex/sqliteReader.js.map +1 -0
- package/dist/modes/codex/sqliteReader.test.js +137 -0
- package/dist/modes/codex/sqliteReader.test.js.map +1 -0
- package/dist/setup/wizard.js +115 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/setup/wizard.test.js +216 -0
- package/dist/setup/wizard.test.js.map +1 -0
- package/dist/startBot.test.js +78 -0
- package/dist/startBot.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DiffViewHandler } from './diffView.js';
|
|
3
|
+
function fakeChannel() {
|
|
4
|
+
const sent = [];
|
|
5
|
+
const channel = {
|
|
6
|
+
async send(message) {
|
|
7
|
+
sent.push(message);
|
|
8
|
+
const em = { id: 'm1', async edit() { } };
|
|
9
|
+
return em;
|
|
10
|
+
},
|
|
11
|
+
async startThread() {
|
|
12
|
+
throw new Error('not used');
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
return { channel, sent };
|
|
16
|
+
}
|
|
17
|
+
const use = (e) => e;
|
|
18
|
+
const result = (e) => e;
|
|
19
|
+
describe('DiffViewHandler', () => {
|
|
20
|
+
it('renders an old→new diff for a successful Edit', async () => {
|
|
21
|
+
const { channel, sent } = fakeChannel();
|
|
22
|
+
const h = new DiffViewHandler({ channel });
|
|
23
|
+
h.noteToolUse(use({ kind: 'tool_use', id: 't1', name: 'Edit', input: { file_path: '/ws/a.ts', old_string: 'a', new_string: 'b' } }));
|
|
24
|
+
await h.handleResult(result({ kind: 'tool_result', id: 't1', ok: true, content: 'ok' }));
|
|
25
|
+
expect(sent).toHaveLength(1);
|
|
26
|
+
const body = sent[0].content ?? '';
|
|
27
|
+
expect(body).toContain('```diff');
|
|
28
|
+
expect(body).toContain('- a');
|
|
29
|
+
expect(body).toContain('+ b');
|
|
30
|
+
});
|
|
31
|
+
it('renders additions-only for a Write with content', async () => {
|
|
32
|
+
const { channel, sent } = fakeChannel();
|
|
33
|
+
const h = new DiffViewHandler({ channel });
|
|
34
|
+
h.noteToolUse(use({ kind: 'tool_use', id: 't2', name: 'Write', input: { file_path: '/ws/n.ts', content: 'line1\nline2' } }));
|
|
35
|
+
await h.handleResult(result({ kind: 'tool_result', id: 't2', ok: true, content: 'ok' }));
|
|
36
|
+
expect(sent[0].content).toContain('+ line1');
|
|
37
|
+
expect(sent[0].content).toContain('+ line2');
|
|
38
|
+
});
|
|
39
|
+
it('renders nothing for a non-edit tool', async () => {
|
|
40
|
+
const { channel, sent } = fakeChannel();
|
|
41
|
+
const h = new DiffViewHandler({ channel });
|
|
42
|
+
h.noteToolUse(use({ kind: 'tool_use', id: 't3', name: 'Bash', input: { command: 'ls' } }));
|
|
43
|
+
await h.handleResult(result({ kind: 'tool_result', id: 't3', ok: true, content: 'files' }));
|
|
44
|
+
expect(sent).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
it('renders nothing for a failed edit result', async () => {
|
|
47
|
+
const { channel, sent } = fakeChannel();
|
|
48
|
+
const h = new DiffViewHandler({ channel });
|
|
49
|
+
h.noteToolUse(use({ kind: 'tool_use', id: 't4', name: 'Edit', input: { file_path: '/ws/a.ts', old_string: 'a', new_string: 'b' } }));
|
|
50
|
+
await h.handleResult(result({ kind: 'tool_result', id: 't4', ok: false, content: 'error' }));
|
|
51
|
+
expect(sent).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=diffView.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffView.test.js","sourceRoot":"","sources":["../../../src/discord/renderers/diffView.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { StreamEmbedHandler } from './streamEmbed.js';
|
|
2
|
+
import { ToolThreadHandler } from './toolThread.js';
|
|
3
|
+
import { PermissionButtonsHandler } from './permissionButtons.js';
|
|
4
|
+
import { DiffViewHandler } from './diffView.js';
|
|
5
|
+
import { TranscriptFeedHandler } from './transcriptFeed.js';
|
|
6
|
+
import { MentionOnCompleteHandler } from './mentionOnComplete.js';
|
|
7
|
+
import { buildResultLine } from './resultLine.js';
|
|
8
|
+
import { buildUsageEmbed } from './usageEmbed.js';
|
|
9
|
+
export class RendererDispatcher {
|
|
10
|
+
renderers;
|
|
11
|
+
capabilities;
|
|
12
|
+
constructor(renderers, capabilities) {
|
|
13
|
+
this.renderers = renderers;
|
|
14
|
+
this.capabilities = capabilities;
|
|
15
|
+
}
|
|
16
|
+
// Subscribe this dispatcher to a channel's event stream. Returns the unsubscribe.
|
|
17
|
+
subscribe(bus, guildId, channelId) {
|
|
18
|
+
return bus.on(guildId, channelId, (ev) => this.dispatch(ev));
|
|
19
|
+
}
|
|
20
|
+
// Capability-gated dispatch. This is the ONE place capabilities decide which
|
|
21
|
+
// renderer fires. A backend that lacks a capability simply has that path skipped;
|
|
22
|
+
// for text specifically, a non-streaming backend routes the final text to a plain
|
|
23
|
+
// message and progress to the transcript feed (Codex degraded UX, §5c).
|
|
24
|
+
dispatch(ev) {
|
|
25
|
+
const caps = this.capabilities;
|
|
26
|
+
switch (ev.kind) {
|
|
27
|
+
case 'text':
|
|
28
|
+
if (caps.streaming)
|
|
29
|
+
this.renderers.stream(ev);
|
|
30
|
+
else
|
|
31
|
+
this.renderers.plainText(ev);
|
|
32
|
+
break;
|
|
33
|
+
case 'thinking':
|
|
34
|
+
if (caps.thinking)
|
|
35
|
+
this.renderers.stream(ev);
|
|
36
|
+
break;
|
|
37
|
+
case 'tool_use':
|
|
38
|
+
case 'tool_result':
|
|
39
|
+
if (caps.toolThreads)
|
|
40
|
+
this.renderers.toolThread(ev);
|
|
41
|
+
if (caps.fileDiff)
|
|
42
|
+
this.renderers.diff(ev);
|
|
43
|
+
break;
|
|
44
|
+
case 'permission_request':
|
|
45
|
+
if (caps.permissionPrompts)
|
|
46
|
+
this.renderers.permission(ev);
|
|
47
|
+
break;
|
|
48
|
+
case 'progress':
|
|
49
|
+
if (caps.progress || caps.transcript)
|
|
50
|
+
this.renderers.transcript(ev);
|
|
51
|
+
break;
|
|
52
|
+
case 'result':
|
|
53
|
+
// Non-streaming/transcript backends post their final text via the feed.
|
|
54
|
+
if (!caps.streaming && (caps.transcript || caps.progress))
|
|
55
|
+
this.renderers.transcript(ev);
|
|
56
|
+
this.renderers.result(ev);
|
|
57
|
+
this.renderers.mention(ev);
|
|
58
|
+
break;
|
|
59
|
+
case 'context_usage':
|
|
60
|
+
if (caps.usagePanel)
|
|
61
|
+
this.renderers.usage(ev);
|
|
62
|
+
break;
|
|
63
|
+
case 'error':
|
|
64
|
+
this.renderers.error(ev);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Wire the concrete handlers over one channel. Async handler work is fire-and-forget
|
|
70
|
+
// with a swallowed rejection so a rendering hiccup never breaks the event stream —
|
|
71
|
+
// the orchestrator's error events remain the user-visible failure signal.
|
|
72
|
+
export function createDefaultRendererSet(options) {
|
|
73
|
+
const { channel, ownerId } = options;
|
|
74
|
+
const textStream = new StreamEmbedHandler({ channel, kind: 'text' });
|
|
75
|
+
const thinkingStream = new StreamEmbedHandler({ channel, kind: 'thinking' });
|
|
76
|
+
const toolThread = new ToolThreadHandler({ channel });
|
|
77
|
+
const permission = new PermissionButtonsHandler({ channel });
|
|
78
|
+
const diff = new DiffViewHandler({ channel });
|
|
79
|
+
const transcript = new TranscriptFeedHandler({ channel });
|
|
80
|
+
const mention = new MentionOnCompleteHandler({ channel, ownerId });
|
|
81
|
+
let lastCtxUsage = null;
|
|
82
|
+
const swallow = (p) => {
|
|
83
|
+
void p.catch(() => { });
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
stream(ev) {
|
|
87
|
+
if (ev.kind === 'thinking')
|
|
88
|
+
thinkingStream.push(ev);
|
|
89
|
+
else
|
|
90
|
+
textStream.push(ev);
|
|
91
|
+
},
|
|
92
|
+
plainText(ev) {
|
|
93
|
+
swallow(channel.send({ content: ev.text }));
|
|
94
|
+
},
|
|
95
|
+
toolThread(ev) {
|
|
96
|
+
swallow(toolThread.handle(ev));
|
|
97
|
+
},
|
|
98
|
+
diff(ev) {
|
|
99
|
+
if (ev.kind === 'tool_use')
|
|
100
|
+
diff.noteToolUse(ev);
|
|
101
|
+
else
|
|
102
|
+
swallow(diff.handleResult(ev));
|
|
103
|
+
},
|
|
104
|
+
permission(ev) {
|
|
105
|
+
// The dispatcher path posts the buttons; the returned promise is resolved by
|
|
106
|
+
// the orchestrator's requestPermission hookup (7b). Here we just surface them.
|
|
107
|
+
swallow(permission.request(ev));
|
|
108
|
+
},
|
|
109
|
+
transcript(ev) {
|
|
110
|
+
swallow(transcript.handle(ev));
|
|
111
|
+
},
|
|
112
|
+
result(ev) {
|
|
113
|
+
// Finalize any open text stream before the done-line, so the answer lands first.
|
|
114
|
+
swallow(textStream.finalize().then(() => thinkingStream.finalize()));
|
|
115
|
+
const line = buildResultLine(ev);
|
|
116
|
+
if (line)
|
|
117
|
+
swallow(channel.send({ content: line }));
|
|
118
|
+
},
|
|
119
|
+
usage(ev) {
|
|
120
|
+
lastCtxUsage = ev;
|
|
121
|
+
const usage = options.getUsage?.() ?? null;
|
|
122
|
+
const embed = buildUsageEmbed(usage, lastCtxUsage);
|
|
123
|
+
if (embed)
|
|
124
|
+
swallow(channel.send({ embeds: [embed] }));
|
|
125
|
+
},
|
|
126
|
+
mention(ev) {
|
|
127
|
+
swallow(mention.handle(ev));
|
|
128
|
+
},
|
|
129
|
+
error(ev) {
|
|
130
|
+
swallow(channel.send({ content: `⚠️ ${ev.message}` }));
|
|
131
|
+
},
|
|
132
|
+
dispose() {
|
|
133
|
+
// Cancel the streaming/thinking debounce timers so a detach mid-stream cannot
|
|
134
|
+
// fire a late edit/send against a channel that is being torn down.
|
|
135
|
+
textStream.cancel();
|
|
136
|
+
thinkingStream.cancel();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Re-export the permission handler for 7b to wire orchestrator.requestPermission
|
|
141
|
+
// and button-interaction resolution (the request/resolve round-trip lives there).
|
|
142
|
+
export { PermissionButtonsHandler } from './permissionButtons.js';
|
|
143
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/discord/renderers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { RendererDispatcher } from './index.js';
|
|
3
|
+
// A spy renderer set: every action is a vi.fn() so a test asserts exactly which
|
|
4
|
+
// renderer fired for a given event × capability combination.
|
|
5
|
+
function spySet() {
|
|
6
|
+
return {
|
|
7
|
+
stream: vi.fn(),
|
|
8
|
+
plainText: vi.fn(),
|
|
9
|
+
toolThread: vi.fn(),
|
|
10
|
+
diff: vi.fn(),
|
|
11
|
+
permission: vi.fn(),
|
|
12
|
+
transcript: vi.fn(),
|
|
13
|
+
result: vi.fn(),
|
|
14
|
+
usage: vi.fn(),
|
|
15
|
+
mention: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const claudeCaps = {
|
|
20
|
+
streaming: true,
|
|
21
|
+
thinking: true,
|
|
22
|
+
toolThreads: true,
|
|
23
|
+
permissionPrompts: true,
|
|
24
|
+
progress: false,
|
|
25
|
+
transcript: false,
|
|
26
|
+
sessionResume: true,
|
|
27
|
+
fileAttach: true,
|
|
28
|
+
fileDiff: true,
|
|
29
|
+
usagePanel: true,
|
|
30
|
+
permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
31
|
+
};
|
|
32
|
+
const codexCaps = {
|
|
33
|
+
streaming: false,
|
|
34
|
+
thinking: false,
|
|
35
|
+
toolThreads: false,
|
|
36
|
+
permissionPrompts: false,
|
|
37
|
+
progress: true,
|
|
38
|
+
transcript: true,
|
|
39
|
+
sessionResume: true,
|
|
40
|
+
fileAttach: false,
|
|
41
|
+
fileDiff: false,
|
|
42
|
+
usagePanel: false,
|
|
43
|
+
permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
44
|
+
};
|
|
45
|
+
const toolUse = { kind: 'tool_use', id: 't1', name: 'Edit', input: { file_path: '/ws/a' } };
|
|
46
|
+
const permReq = { kind: 'permission_request', id: 'p1', toolName: 'Bash', input: {} };
|
|
47
|
+
const textEv = { kind: 'text', text: 'hello', delta: true };
|
|
48
|
+
const progressEv = { kind: 'progress', label: 'editing' };
|
|
49
|
+
const ctxUsage = { kind: 'context_usage', totalTokens: 10, maxTokens: 100, percentage: 10 };
|
|
50
|
+
describe('RendererDispatcher capability dispatch', () => {
|
|
51
|
+
it('fires toolThread for tool_use only when toolThreads is set', () => {
|
|
52
|
+
const set = spySet();
|
|
53
|
+
new RendererDispatcher(set, claudeCaps).dispatch(toolUse);
|
|
54
|
+
expect(set.toolThread).toHaveBeenCalledWith(toolUse);
|
|
55
|
+
const set2 = spySet();
|
|
56
|
+
new RendererDispatcher(set2, codexCaps).dispatch(toolUse);
|
|
57
|
+
expect(set2.toolThread).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
it('fires the diff renderer for tool_use only when fileDiff is set', () => {
|
|
60
|
+
const set = spySet();
|
|
61
|
+
new RendererDispatcher(set, claudeCaps).dispatch(toolUse);
|
|
62
|
+
expect(set.diff).toHaveBeenCalledWith(toolUse);
|
|
63
|
+
const set2 = spySet();
|
|
64
|
+
new RendererDispatcher(set2, codexCaps).dispatch(toolUse);
|
|
65
|
+
expect(set2.diff).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
it('fires permission buttons for permission_request only when permissionPrompts is set', () => {
|
|
68
|
+
const set = spySet();
|
|
69
|
+
new RendererDispatcher(set, claudeCaps).dispatch(permReq);
|
|
70
|
+
expect(set.permission).toHaveBeenCalledWith(permReq);
|
|
71
|
+
const set2 = spySet();
|
|
72
|
+
new RendererDispatcher(set2, codexCaps).dispatch(permReq);
|
|
73
|
+
expect(set2.permission).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('routes text to the stream embed when streaming is set', () => {
|
|
76
|
+
const set = spySet();
|
|
77
|
+
new RendererDispatcher(set, claudeCaps).dispatch(textEv);
|
|
78
|
+
expect(set.stream).toHaveBeenCalledWith(textEv);
|
|
79
|
+
expect(set.plainText).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
it('routes text to a plain message when streaming is NOT set (Codex-like)', () => {
|
|
82
|
+
const set = spySet();
|
|
83
|
+
new RendererDispatcher(set, codexCaps).dispatch(textEv);
|
|
84
|
+
expect(set.plainText).toHaveBeenCalledWith(textEv);
|
|
85
|
+
expect(set.stream).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
87
|
+
it('routes progress to the transcript feed for a transcript/progress backend', () => {
|
|
88
|
+
const set = spySet();
|
|
89
|
+
new RendererDispatcher(set, codexCaps).dispatch(progressEv);
|
|
90
|
+
expect(set.transcript).toHaveBeenCalledWith(progressEv);
|
|
91
|
+
// Claude (no progress/transcript) skips it.
|
|
92
|
+
const set2 = spySet();
|
|
93
|
+
new RendererDispatcher(set2, claudeCaps).dispatch(progressEv);
|
|
94
|
+
expect(set2.transcript).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
it('fires the usage panel for context_usage only when usagePanel is set', () => {
|
|
97
|
+
const set = spySet();
|
|
98
|
+
new RendererDispatcher(set, claudeCaps).dispatch(ctxUsage);
|
|
99
|
+
expect(set.usage).toHaveBeenCalledWith(ctxUsage);
|
|
100
|
+
const set2 = spySet();
|
|
101
|
+
new RendererDispatcher(set2, codexCaps).dispatch(ctxUsage);
|
|
102
|
+
expect(set2.usage).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
it('always fires result + mention on result; Codex additionally feeds final text to transcript', () => {
|
|
105
|
+
const resultEv = { kind: 'result', text: 'final', costUsd: 0.01 };
|
|
106
|
+
const claude = spySet();
|
|
107
|
+
new RendererDispatcher(claude, claudeCaps).dispatch(resultEv);
|
|
108
|
+
expect(claude.result).toHaveBeenCalledWith(resultEv);
|
|
109
|
+
expect(claude.mention).toHaveBeenCalledWith(resultEv);
|
|
110
|
+
expect(claude.transcript).not.toHaveBeenCalled();
|
|
111
|
+
const codex = spySet();
|
|
112
|
+
new RendererDispatcher(codex, codexCaps).dispatch(resultEv);
|
|
113
|
+
expect(codex.result).toHaveBeenCalledWith(resultEv);
|
|
114
|
+
expect(codex.mention).toHaveBeenCalledWith(resultEv);
|
|
115
|
+
expect(codex.transcript).toHaveBeenCalledWith(resultEv);
|
|
116
|
+
});
|
|
117
|
+
it('always surfaces errors regardless of capabilities', () => {
|
|
118
|
+
const errEv = { kind: 'error', message: 'boom', retryable: false };
|
|
119
|
+
const set = spySet();
|
|
120
|
+
new RendererDispatcher(set, codexCaps).dispatch(errEv);
|
|
121
|
+
expect(set.error).toHaveBeenCalledWith(errEv);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/discord/renderers/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAoB,MAAM,YAAY,CAAC;AAGlE,gFAAgF;AAChF,6DAA6D;AAC7D,SAAS,MAAM;IACb,OAAO;QACL,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;QACf,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;QAClB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;QACnB,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;QACnB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;QACnB,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;QACf,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;QAChB,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;KACf,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,GAAiB;IAC/B,SAAS,EAAE,IAAI;IACf,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,IAAI;IACjB,iBAAiB,EAAE,IAAI;IACvB,QAAQ,EAAE,KAAK;IACf,UAAU,EAAE,KAAK;IACjB,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,CAAC;CACzE,CAAC;AAEF,MAAM,SAAS,GAAiB;IAC9B,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;IACf,WAAW,EAAE,KAAK;IAClB,iBAAiB,EAAE,KAAK;IACxB,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,KAAK;IACjB,QAAQ,EAAE,KAAK;IACf,UAAU,EAAE,KAAK;IACjB,eAAe,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,CAAC;CACzE,CAAC;AAEF,MAAM,OAAO,GAAe,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC;AACxG,MAAM,OAAO,GAAe,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAClG,MAAM,MAAM,GAAe,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACxE,MAAM,UAAU,GAAe,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACtE,MAAM,QAAQ,GAAe,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AAExG,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAErD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAE/C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC5F,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAErD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAExD,4CAA4C;QAC5C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAEjD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4FAA4F,EAAE,GAAG,EAAE;QACpG,MAAM,QAAQ,GAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC9E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,IAAI,kBAAkB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QAEjD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;QACvB,IAAI,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACrD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAe,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC/E,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,kBAAkB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class MentionOnCompleteHandler {
|
|
2
|
+
channel;
|
|
3
|
+
ownerId;
|
|
4
|
+
constructor(deps) {
|
|
5
|
+
this.channel = deps.channel;
|
|
6
|
+
this.ownerId = deps.ownerId;
|
|
7
|
+
}
|
|
8
|
+
async handle(_ev) {
|
|
9
|
+
// No owner bound (e.g. a resumed binding missing its ownerId) → skip the mention
|
|
10
|
+
// rather than post a broken `<@>` that pings no one.
|
|
11
|
+
if (!this.ownerId)
|
|
12
|
+
return;
|
|
13
|
+
await this.channel.send({
|
|
14
|
+
content: `<@${this.ownerId}>`,
|
|
15
|
+
mentionUserIds: [this.ownerId],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=mentionOnComplete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mentionOnComplete.js","sourceRoot":"","sources":["../../../src/discord/renderers/mentionOnComplete.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,wBAAwB;IAClB,OAAO,CAAiB;IACxB,OAAO,CAAS;IAEjC,YAAY,IAA2B;QACrC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAA4C;QACvD,iFAAiF;QACjF,qDAAqD;QACrD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACtB,OAAO,EAAE,KAAK,IAAI,CAAC,OAAO,GAAG;YAC7B,cAAc,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;SAC/B,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { MentionOnCompleteHandler } from './mentionOnComplete.js';
|
|
3
|
+
// A fake channel recording every sent message.
|
|
4
|
+
function fakeChannel() {
|
|
5
|
+
const sent = [];
|
|
6
|
+
const channel = {
|
|
7
|
+
async send(message) {
|
|
8
|
+
sent.push(message);
|
|
9
|
+
const em = { id: `m${sent.length}`, async edit() { } };
|
|
10
|
+
return em;
|
|
11
|
+
},
|
|
12
|
+
async startThread() {
|
|
13
|
+
throw new Error('not used');
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
return { channel, sent };
|
|
17
|
+
}
|
|
18
|
+
const resultEv = { kind: 'result', text: 'done' };
|
|
19
|
+
describe('MentionOnCompleteHandler', () => {
|
|
20
|
+
it('mentions the owner on completion when an ownerId is bound', async () => {
|
|
21
|
+
const { channel, sent } = fakeChannel();
|
|
22
|
+
const handler = new MentionOnCompleteHandler({ channel, ownerId: 'owner-9' });
|
|
23
|
+
await handler.handle(resultEv);
|
|
24
|
+
expect(sent).toHaveLength(1);
|
|
25
|
+
expect(sent[0].content).toBe('<@owner-9>');
|
|
26
|
+
expect(sent[0].mentionUserIds).toEqual(['owner-9']);
|
|
27
|
+
});
|
|
28
|
+
it('posts NO message when the ownerId is empty (no broken <@> ping)', async () => {
|
|
29
|
+
const { channel, sent } = fakeChannel();
|
|
30
|
+
const handler = new MentionOnCompleteHandler({ channel, ownerId: '' });
|
|
31
|
+
await handler.handle(resultEv);
|
|
32
|
+
expect(sent).toHaveLength(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=mentionOnComplete.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mentionOnComplete.test.js","sourceRoot":"","sources":["../../../src/discord/renderers/mentionOnComplete.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAIlE,+CAA+C;AAC/C,SAAS,WAAW;IAClB,MAAM,IAAI,GAAsB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAmB;QAC9B,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnB,MAAM,EAAE,GAAoB,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,IAAI,KAAI,CAAC,EAAE,CAAC;YACvE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,WAAW;YACf,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,QAAQ,GAA4C,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAE3F,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,wBAAwB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9E,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,wBAAwB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QACvE,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { COLORS, truncate } from '../format.js';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
// Permission buttons (§6, §5a): a permission_request posts Allow / Always-Allow /
|
|
4
|
+
// Deny buttons; the button interaction resolves the pending PermissionDecision.
|
|
5
|
+
// custom_id scheme: `perm:<reqId>:<action>` where action ∈ allow|always|deny.
|
|
6
|
+
//
|
|
7
|
+
// This handler owns the pending map keyed by reqId. 7b wires two directions:
|
|
8
|
+
// - orchestrator.requestPermission → request(reqId, toolName, input) posts the
|
|
9
|
+
// buttons and returns a Promise<PermissionDecision> that resolves when the user
|
|
10
|
+
// clicks; the awaiting orchestrator turn is unblocked by that resolution.
|
|
11
|
+
// - a button interaction → resolve(customId) settles the matching pending promise.
|
|
12
|
+
// No discord.js here: the sink is the MessageChannel port; interactions are parsed
|
|
13
|
+
// by 7b and handed to resolve() as the raw custom_id string.
|
|
14
|
+
const CUSTOM_ID_PREFIX = 'perm';
|
|
15
|
+
export function buildCustomId(reqId, action) {
|
|
16
|
+
return `${CUSTOM_ID_PREFIX}:${reqId}:${action}`;
|
|
17
|
+
}
|
|
18
|
+
// Parse a `perm:<reqId>:<action>` custom_id. Returns null for a non-permission id
|
|
19
|
+
// or an unknown action, so a foreign interaction is safely ignored by the caller.
|
|
20
|
+
export function parseCustomId(customId) {
|
|
21
|
+
const parts = customId.split(':');
|
|
22
|
+
if (parts.length !== 3 || parts[0] !== CUSTOM_ID_PREFIX)
|
|
23
|
+
return null;
|
|
24
|
+
const [, reqId, action] = parts;
|
|
25
|
+
if (action !== 'allow' && action !== 'always' && action !== 'deny')
|
|
26
|
+
return null;
|
|
27
|
+
if (!reqId)
|
|
28
|
+
return null;
|
|
29
|
+
return { reqId, action };
|
|
30
|
+
}
|
|
31
|
+
export class PermissionButtonsHandler {
|
|
32
|
+
channel;
|
|
33
|
+
pending = new Map();
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.channel = deps.channel;
|
|
36
|
+
}
|
|
37
|
+
// The tool name of a still-pending request, or null once it has resolved/expired.
|
|
38
|
+
// Lets the wiring layer read the tool for an always-allow persistence decision
|
|
39
|
+
// before resolve() removes the pending entry.
|
|
40
|
+
peekToolName(reqId) {
|
|
41
|
+
return this.pending.get(reqId)?.toolName ?? null;
|
|
42
|
+
}
|
|
43
|
+
// The approver bound to a still-pending request, or null when unbound/unknown.
|
|
44
|
+
peekApproverId(reqId) {
|
|
45
|
+
return this.pending.get(reqId)?.approverId ?? null;
|
|
46
|
+
}
|
|
47
|
+
// Post the buttons for a permission_request and return a promise that settles when
|
|
48
|
+
// the user decides. The pending resolver is registered SYNCHRONOUSLY (before the
|
|
49
|
+
// send completes) so a decision that races the post still resolves; the message
|
|
50
|
+
// handle used to disable the buttons is filled in once send resolves. `ev.id` is
|
|
51
|
+
// the AgentEvent id (stable across the round-trip). `approverId` binds the prompt
|
|
52
|
+
// to the one actor allowed to resolve it (the session owner/driver); when omitted
|
|
53
|
+
// the prompt is unbound (resolve() enforces no actor).
|
|
54
|
+
request(ev, approverId) {
|
|
55
|
+
const entry = {
|
|
56
|
+
resolve: () => { },
|
|
57
|
+
message: null,
|
|
58
|
+
toolName: ev.toolName,
|
|
59
|
+
approverId: approverId ?? null,
|
|
60
|
+
};
|
|
61
|
+
const decision = new Promise((resolve) => {
|
|
62
|
+
entry.resolve = resolve;
|
|
63
|
+
});
|
|
64
|
+
this.pending.set(ev.id, entry);
|
|
65
|
+
const embed = {
|
|
66
|
+
title: t('perm.request.title'),
|
|
67
|
+
description: t('perm.request.body', {
|
|
68
|
+
tool: ev.toolName,
|
|
69
|
+
input: truncate(formatInput(ev.input), 3000),
|
|
70
|
+
}),
|
|
71
|
+
color: COLORS.permission,
|
|
72
|
+
};
|
|
73
|
+
const buttons = [
|
|
74
|
+
{ type: 'button', customId: buildCustomId(ev.id, 'allow'), label: t('perm.button.allow'), style: 'success' },
|
|
75
|
+
{ type: 'button', customId: buildCustomId(ev.id, 'always'), label: t('perm.button.always'), style: 'primary' },
|
|
76
|
+
{ type: 'button', customId: buildCustomId(ev.id, 'deny'), label: t('perm.button.deny'), style: 'danger' },
|
|
77
|
+
];
|
|
78
|
+
void this.channel
|
|
79
|
+
.send({ embeds: [embed], components: [{ components: buttons }] })
|
|
80
|
+
.then((message) => {
|
|
81
|
+
// If the user already decided (entry removed), leave the message as-is.
|
|
82
|
+
if (this.pending.get(ev.id) === entry)
|
|
83
|
+
entry.message = message;
|
|
84
|
+
})
|
|
85
|
+
.catch(() => { });
|
|
86
|
+
return decision;
|
|
87
|
+
}
|
|
88
|
+
// Settle a pending request from a button custom_id. Returns the decision applied,
|
|
89
|
+
// or null if the id is unknown/foreign/already-resolved (idempotent, safe to call
|
|
90
|
+
// on any interaction). When the prompt is bound to an approver and `actorId` is
|
|
91
|
+
// supplied, an actor other than the approver is IGNORED (returns null, prompt stays
|
|
92
|
+
// pending) — a bystander cannot resolve another driver's prompt (§7.1/§7.5).
|
|
93
|
+
// Disables the buttons and marks the outcome on resolve.
|
|
94
|
+
async resolve(customId, actorId) {
|
|
95
|
+
const parsed = parseCustomId(customId);
|
|
96
|
+
if (!parsed)
|
|
97
|
+
return null;
|
|
98
|
+
const entry = this.pending.get(parsed.reqId);
|
|
99
|
+
if (!entry)
|
|
100
|
+
return null;
|
|
101
|
+
// Enforce the approver binding: if the prompt is bound and the caller passed an
|
|
102
|
+
// actor id, only that actor may resolve it. Leave the entry pending otherwise.
|
|
103
|
+
if (entry.approverId !== null && actorId !== undefined && actorId !== entry.approverId) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
this.pending.delete(parsed.reqId);
|
|
107
|
+
const decision = parsed.action === 'deny'
|
|
108
|
+
? { behavior: 'deny', message: 'User denied via Discord' }
|
|
109
|
+
: { behavior: 'allow' };
|
|
110
|
+
const decidedKey = parsed.action === 'deny'
|
|
111
|
+
? 'perm.decided.deny'
|
|
112
|
+
: parsed.action === 'always'
|
|
113
|
+
? 'perm.decided.always'
|
|
114
|
+
: 'perm.decided.allow';
|
|
115
|
+
// Collapse the prompt: disable buttons and note the outcome so the record is
|
|
116
|
+
// clear and the buttons cannot be re-clicked. If the buttons message has not
|
|
117
|
+
// finished posting yet (a decision that raced the send), skip the edit — the
|
|
118
|
+
// decision still resolves below.
|
|
119
|
+
if (entry.message) {
|
|
120
|
+
await entry.message.edit({
|
|
121
|
+
embeds: [
|
|
122
|
+
{
|
|
123
|
+
title: `${t('perm.request.title')} — ${t(decidedKey)}`,
|
|
124
|
+
description: t('perm.request.body', {
|
|
125
|
+
tool: entry.toolName,
|
|
126
|
+
input: t(decidedKey),
|
|
127
|
+
}),
|
|
128
|
+
color: parsed.action === 'deny' ? COLORS.stopped : COLORS.idle,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
components: [],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
entry.resolve(decision);
|
|
135
|
+
// `always` is surfaced as allow here; 7b/8 persists the always-allow set into
|
|
136
|
+
// the resolved tool allowlist (config-driven auto-allow, §7A) — not this layer's job.
|
|
137
|
+
return decision;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function formatInput(input) {
|
|
141
|
+
if (typeof input === 'string')
|
|
142
|
+
return input;
|
|
143
|
+
try {
|
|
144
|
+
return '```json\n' + JSON.stringify(input, null, 2) + '\n```';
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return String(input);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=permissionButtons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissionButtons.js","sourceRoot":"","sources":["../../../src/discord/renderers/permissionButtons.ts"],"names":[],"mappings":"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"}
|