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,116 @@
|
|
|
1
|
+
const LEVEL_ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
2
|
+
const PLACEHOLDER = '[REDACTED]';
|
|
3
|
+
// Sensitive object keys whose values are replaced wholesale, regardless of shape.
|
|
4
|
+
const SENSITIVE_KEYS = new Set([
|
|
5
|
+
'token',
|
|
6
|
+
'authorization',
|
|
7
|
+
'accesstoken',
|
|
8
|
+
'refreshtoken',
|
|
9
|
+
'apikey',
|
|
10
|
+
'secret',
|
|
11
|
+
'password',
|
|
12
|
+
'credentials',
|
|
13
|
+
]);
|
|
14
|
+
// Value-shape patterns for secrets that may appear inside free-form strings.
|
|
15
|
+
const VALUE_PATTERNS = [
|
|
16
|
+
// Discord bot token: <base64 userId>.<base64 timestamp>.<base64 hmac>. The real
|
|
17
|
+
// shape is tight (~23-28 . ~6-7 . ~27-40 chars) with word boundaries, so benign
|
|
18
|
+
// dotted strings (e.g. file hashes like "deadbeef.sig.0123456789") are not
|
|
19
|
+
// clobbered.
|
|
20
|
+
/\b[A-Za-z0-9_-]{23,28}\.[A-Za-z0-9_-]{6,7}\.[A-Za-z0-9_-]{27,40}\b/g,
|
|
21
|
+
// Anthropic / OAuth style bearer tokens (sk-..., sk-ant-..., long opaque tokens).
|
|
22
|
+
/\bsk-[A-Za-z0-9-]{16,}\b/g,
|
|
23
|
+
// "Authorization: Bearer <token>" / "Bearer <token>" (allow multiple spaces).
|
|
24
|
+
/(?<=Bearer\s{1,4})[A-Za-z0-9._-]{8,}/gi,
|
|
25
|
+
];
|
|
26
|
+
function redactString(input) {
|
|
27
|
+
let out = input;
|
|
28
|
+
for (const pattern of VALUE_PATTERNS) {
|
|
29
|
+
out = out.replace(pattern, PLACEHOLDER);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
// Recursively redact a value: sensitive keys → placeholder, strings → pattern-scrubbed.
|
|
34
|
+
// Cycles are broken via a seen-set. Depth is bounded to avoid pathological payloads.
|
|
35
|
+
export function redact(value, seen = new WeakSet(), depth = 0) {
|
|
36
|
+
if (typeof value === 'string')
|
|
37
|
+
return redactString(value);
|
|
38
|
+
if (value === null || typeof value !== 'object')
|
|
39
|
+
return value;
|
|
40
|
+
if (depth > 6)
|
|
41
|
+
return '[…]';
|
|
42
|
+
if (seen.has(value))
|
|
43
|
+
return '[Circular]';
|
|
44
|
+
seen.add(value);
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map((item) => redact(item, seen, depth + 1));
|
|
47
|
+
}
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const [key, val] of Object.entries(value)) {
|
|
50
|
+
if (SENSITIVE_KEYS.has(key.toLowerCase())) {
|
|
51
|
+
out[key] = PLACEHOLDER;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
out[key] = redact(val, seen, depth + 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
const consoleSink = {
|
|
60
|
+
write(level, line) {
|
|
61
|
+
if (level === 'error')
|
|
62
|
+
console.error(line);
|
|
63
|
+
else if (level === 'warn')
|
|
64
|
+
console.warn(line);
|
|
65
|
+
else
|
|
66
|
+
console.log(line);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
function formatMeta(meta) {
|
|
70
|
+
if (meta.length === 0)
|
|
71
|
+
return '';
|
|
72
|
+
const parts = meta.map((m) => {
|
|
73
|
+
const scrubbed = redact(m);
|
|
74
|
+
if (typeof scrubbed === 'string')
|
|
75
|
+
return scrubbed;
|
|
76
|
+
try {
|
|
77
|
+
return JSON.stringify(scrubbed);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return String(scrubbed);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return ' ' + parts.join(' ');
|
|
84
|
+
}
|
|
85
|
+
class RedactingLogger {
|
|
86
|
+
name;
|
|
87
|
+
threshold;
|
|
88
|
+
sink;
|
|
89
|
+
constructor(name, options) {
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.threshold = LEVEL_ORDER[options.level ?? 'info'];
|
|
92
|
+
this.sink = options.sink ?? consoleSink;
|
|
93
|
+
}
|
|
94
|
+
log(level, message, meta) {
|
|
95
|
+
if (LEVEL_ORDER[level] < this.threshold)
|
|
96
|
+
return;
|
|
97
|
+
const line = `[${level.toUpperCase()}] ${this.name}: ${redactString(message)}${formatMeta(meta)}`;
|
|
98
|
+
this.sink.write(level, line);
|
|
99
|
+
}
|
|
100
|
+
debug(message, ...meta) {
|
|
101
|
+
this.log('debug', message, meta);
|
|
102
|
+
}
|
|
103
|
+
info(message, ...meta) {
|
|
104
|
+
this.log('info', message, meta);
|
|
105
|
+
}
|
|
106
|
+
warn(message, ...meta) {
|
|
107
|
+
this.log('warn', message, meta);
|
|
108
|
+
}
|
|
109
|
+
error(message, ...meta) {
|
|
110
|
+
this.log('error', message, meta);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function createLogger(name, options = {}) {
|
|
114
|
+
return new RedactingLogger(name, options);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,GAA6B,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAE3F,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,kFAAkF;AAClF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,OAAO;IACP,eAAe;IACf,aAAa;IACb,cAAc;IACd,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,aAAa;CACd,CAAC,CAAC;AAEH,6EAA6E;AAC7E,MAAM,cAAc,GAAa;IAC/B,gFAAgF;IAChF,gFAAgF;IAChF,2EAA2E;IAC3E,aAAa;IACb,qEAAqE;IACrE,kFAAkF;IAClF,2BAA2B;IAC3B,8EAA8E;IAC9E,wCAAwC;CACzC,CAAC;AAEF,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,wFAAwF;AACxF,qFAAqF;AACrF,MAAM,UAAU,MAAM,CAAC,KAAc,EAAE,OAAO,IAAI,OAAO,EAAU,EAAE,KAAK,GAAG,CAAC;IAC5E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAe,CAAC;QAAE,OAAO,YAAY,CAAC;IACnD,IAAI,CAAC,GAAG,CAAC,KAAe,CAAC,CAAC;IAE1B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC1E,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAOD,MAAM,WAAW,GAAY;IAC3B,KAAK,CAAC,KAAK,EAAE,IAAI;QACf,IAAI,KAAK,KAAK,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACtC,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YACzC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;CACF,CAAC;AAOF,SAAS,UAAU,CAAC,IAAe;IACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,OAAO,QAAQ,KAAK,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAClD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,eAAe;IACF,IAAI,CAAS;IACb,SAAS,CAAS;IAClB,IAAI,CAAU;IAE/B,YAAY,IAAY,EAAE,OAAsB;QAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IAC1C,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAe;QAC3D,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS;YAAE,OAAO;QAChD,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,OAAO,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAClG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,UAAyB,EAAE;IACpE,OAAO,IAAI,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createLogger, redact } from './logger.js';
|
|
3
|
+
function captureSink() {
|
|
4
|
+
const lines = [];
|
|
5
|
+
return {
|
|
6
|
+
lines,
|
|
7
|
+
sink: {
|
|
8
|
+
write(level, line) {
|
|
9
|
+
lines.push({ level, line });
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
describe('redact()', () => {
|
|
15
|
+
it('redacts sensitive object keys', () => {
|
|
16
|
+
const out = redact({ token: 'abc.def.ghi', clientId: 'safe', authorization: 'Bearer xyz' });
|
|
17
|
+
expect(out.token).toBe('[REDACTED]');
|
|
18
|
+
expect(out.authorization).toBe('[REDACTED]');
|
|
19
|
+
expect(out.clientId).toBe('safe');
|
|
20
|
+
});
|
|
21
|
+
it('scrubs Discord-token-shaped strings in free text', () => {
|
|
22
|
+
// Synthetic, zero-entropy value matching the Discord-token SHAPE
|
|
23
|
+
// (23-28 . 6-7 . 27-40 chars). Assembled at runtime so no realistic
|
|
24
|
+
// token literal lives in source (avoids secret scanners).
|
|
25
|
+
const fake = ['X'.repeat(24), 'Y'.repeat(6), 'Z'.repeat(28)].join('.');
|
|
26
|
+
const out = redact(`connecting with ${fake} now`);
|
|
27
|
+
expect(out).not.toContain(fake);
|
|
28
|
+
expect(out).toContain('[REDACTED]');
|
|
29
|
+
});
|
|
30
|
+
it('scrubs Bearer tokens and sk- keys', () => {
|
|
31
|
+
expect(redact('Authorization: Bearer abcdef123456')).not.toContain('abcdef123456');
|
|
32
|
+
// Synthetic sk--prefixed key (>=16 tail chars) matching the pattern shape.
|
|
33
|
+
const skKey = 'sk-' + 'k'.repeat(20);
|
|
34
|
+
expect(redact(`key ${skKey} here`)).not.toContain(skKey);
|
|
35
|
+
});
|
|
36
|
+
it('does NOT over-redact a benign dotted string that only resembles a token', () => {
|
|
37
|
+
// Short segments (8 . 3 . 10) fall well below the Discord-token shape
|
|
38
|
+
// (23-28 . 6-7 . 27-40), so a file-hash-like value must survive intact.
|
|
39
|
+
const benign = 'deadbeef.sig.0123456789';
|
|
40
|
+
expect(redact(`artifact ${benign} built`)).toBe(`artifact ${benign} built`);
|
|
41
|
+
});
|
|
42
|
+
it('scrubs a Bearer token separated by multiple spaces', () => {
|
|
43
|
+
// The lookbehind allows 1-4 spaces after "Bearer" so a hand-spaced header
|
|
44
|
+
// is still redacted. Assembled at runtime; not a realistic secret.
|
|
45
|
+
const tok = 'sometoken12345';
|
|
46
|
+
const out = redact(`Bearer ${tok}`);
|
|
47
|
+
expect(out).not.toContain(tok);
|
|
48
|
+
expect(out).toContain('[REDACTED]');
|
|
49
|
+
});
|
|
50
|
+
it('handles nested structures and circular references', () => {
|
|
51
|
+
const obj = { a: { token: 'x.y.z' } };
|
|
52
|
+
obj.self = obj;
|
|
53
|
+
const out = redact(obj);
|
|
54
|
+
expect(out.a.token).toBe('[REDACTED]');
|
|
55
|
+
expect(out.self).toBe('[Circular]');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('createLogger()', () => {
|
|
59
|
+
it('redacts a token embedded in a logged payload', () => {
|
|
60
|
+
const { lines, sink } = captureSink();
|
|
61
|
+
const logger = createLogger('test', { level: 'info', sink });
|
|
62
|
+
// Synthetic sentinel under the sensitive 'token' key (redacted by key).
|
|
63
|
+
const secret = 'S'.repeat(40);
|
|
64
|
+
logger.info('bot login', { discord: { token: secret } });
|
|
65
|
+
const joined = lines.map((l) => l.line).join('\n');
|
|
66
|
+
expect(joined).not.toContain(secret);
|
|
67
|
+
expect(joined).toContain('[REDACTED]');
|
|
68
|
+
});
|
|
69
|
+
it('gates messages below the configured level', () => {
|
|
70
|
+
const { lines, sink } = captureSink();
|
|
71
|
+
const logger = createLogger('test', { level: 'warn', sink });
|
|
72
|
+
logger.debug('debug msg');
|
|
73
|
+
logger.info('info msg');
|
|
74
|
+
logger.warn('warn msg');
|
|
75
|
+
logger.error('error msg');
|
|
76
|
+
const levels = lines.map((l) => l.level);
|
|
77
|
+
expect(levels).toEqual(['warn', 'error']);
|
|
78
|
+
});
|
|
79
|
+
it('defaults to info level', () => {
|
|
80
|
+
const { lines, sink } = captureSink();
|
|
81
|
+
const logger = createLogger('test', { sink });
|
|
82
|
+
logger.debug('nope');
|
|
83
|
+
logger.info('yep');
|
|
84
|
+
expect(lines).toHaveLength(1);
|
|
85
|
+
expect(lines[0].level).toBe('info');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=logger.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../../src/core/logger.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// name → AgentMode; the single place modes are registered and looked up (§4, §10).
|
|
2
|
+
// This is the extensibility seam: adding a backend is register(mode) + nothing
|
|
3
|
+
// else in core changes. Modes self-identify via their readonly `name`, so the
|
|
4
|
+
// registry keys on mode.name rather than a separately-passed string.
|
|
5
|
+
export class ModeRegistry {
|
|
6
|
+
modes = new Map();
|
|
7
|
+
// Register a mode instance. Throws on a duplicate name so a mis-wired double
|
|
8
|
+
// registration surfaces at boot rather than silently shadowing.
|
|
9
|
+
register(mode) {
|
|
10
|
+
if (this.modes.has(mode.name)) {
|
|
11
|
+
throw new Error(`Mode '${mode.name}' is already registered.`);
|
|
12
|
+
}
|
|
13
|
+
this.modes.set(mode.name, mode);
|
|
14
|
+
}
|
|
15
|
+
// Look up a registered mode by name; throws if unknown so callers get a clear
|
|
16
|
+
// error instead of an undefined dereference.
|
|
17
|
+
get(name) {
|
|
18
|
+
const mode = this.modes.get(name);
|
|
19
|
+
if (!mode) {
|
|
20
|
+
throw new Error(`Unknown mode '${name}'. Registered: ${this.list().join(', ') || '(none)'}.`);
|
|
21
|
+
}
|
|
22
|
+
return mode;
|
|
23
|
+
}
|
|
24
|
+
// Non-throwing lookup for callers that want to test presence.
|
|
25
|
+
has(name) {
|
|
26
|
+
return this.modes.has(name);
|
|
27
|
+
}
|
|
28
|
+
// Names of all registered modes, in registration order.
|
|
29
|
+
list() {
|
|
30
|
+
return [...this.modes.keys()];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=modeRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modeRegistry.js","sourceRoot":"","sources":["../../src/core/modeRegistry.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,+EAA+E;AAC/E,8EAA8E;AAC9E,qEAAqE;AACrE,MAAM,OAAO,YAAY;IACN,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEtD,6EAA6E;IAC7E,gEAAgE;IAChE,QAAQ,CAAC,IAAe;QACtB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,IAAI,0BAA0B,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,8EAA8E;IAC9E,6CAA6C;IAC7C,GAAG,CAAC,IAAY;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,kBAAkB,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8DAA8D;IAC9D,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,wDAAwD;IACxD,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class PermissionResolver {
|
|
2
|
+
configStore;
|
|
3
|
+
configResolver;
|
|
4
|
+
constructor(configStore, configResolver) {
|
|
5
|
+
this.configStore = configStore;
|
|
6
|
+
this.configResolver = configResolver;
|
|
7
|
+
}
|
|
8
|
+
resolve(guildId, channelId, override) {
|
|
9
|
+
const config = this.configStore.load();
|
|
10
|
+
const layered = this.configResolver.resolve(guildId, channelId);
|
|
11
|
+
// Profile: session override wins, else layered value. `null` explicitly
|
|
12
|
+
// clears the profile; `undefined` falls through.
|
|
13
|
+
const profileName = override && 'profile' in override ? (override.profile ?? null) : layered.permissionProfile;
|
|
14
|
+
// Base permission mode from the layered defaults; the global auto-allow set
|
|
15
|
+
// is the default tool allowlist when no profile narrows it. Widened to
|
|
16
|
+
// SessionPermMode so a per-session override may carry a Codex sandbox mode.
|
|
17
|
+
let permMode = layered.permissionMode;
|
|
18
|
+
let allowedTools = [...config.autoAllowClaudeTools];
|
|
19
|
+
let policyTier;
|
|
20
|
+
// A named profile bundles mode + tools + tier (§7A). Applied over the base.
|
|
21
|
+
if (profileName) {
|
|
22
|
+
const profile = config.profiles[profileName];
|
|
23
|
+
if (!profile) {
|
|
24
|
+
throw new Error(`Unknown permission profile '${profileName}'.`);
|
|
25
|
+
}
|
|
26
|
+
permMode = profile.permissionMode;
|
|
27
|
+
allowedTools = [...profile.allowedTools];
|
|
28
|
+
policyTier = profile.policyTier;
|
|
29
|
+
}
|
|
30
|
+
// A directly-set per-session permission mode wins over everything, including
|
|
31
|
+
// a profile's bundled mode (the operator explicitly overrode it via /mode).
|
|
32
|
+
if (override?.permMode !== undefined) {
|
|
33
|
+
permMode = override.permMode;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
permMode,
|
|
37
|
+
profile: profileName,
|
|
38
|
+
allowedTools,
|
|
39
|
+
...(policyTier ? { policyTier } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=permissionResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissionResolver.js","sourceRoot":"","sources":["../../src/core/permissionResolver.ts"],"names":[],"mappings":"AAmCA,MAAM,OAAO,kBAAkB;IAEV;IACA;IAFnB,YACmB,WAAwB,EACxB,cAA8B;QAD9B,gBAAW,GAAX,WAAW,CAAa;QACxB,mBAAc,GAAd,cAAc,CAAgB;IAC9C,CAAC;IAEJ,OAAO,CACL,OAAe,EACf,SAAiB,EACjB,QAA0B;QAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEhE,wEAAwE;QACxE,iDAAiD;QACjD,MAAM,WAAW,GACf,QAAQ,IAAI,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC;QAE7F,4EAA4E;QAC5E,uEAAuE;QACvE,4EAA4E;QAC5E,IAAI,QAAQ,GAAoB,OAAO,CAAC,cAAc,CAAC;QACvD,IAAI,YAAY,GAAa,CAAC,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC9D,IAAI,UAAyC,CAAC;QAE9C,4EAA4E;QAC5E,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,+BAA+B,WAAW,IAAI,CAAC,CAAC;YAClE,CAAC;YACD,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;YAClC,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;YACzC,UAAU,GAAG,OAAO,CAAC,UAA+B,CAAC;QACvD,CAAC;QAED,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,QAAQ,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;YACrC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAC/B,CAAC;QAED,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,WAAW;YACpB,YAAY;YACZ,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import { ConfigStore } from './config.js';
|
|
6
|
+
import { CONFIG_VERSION } from './configSchema.js';
|
|
7
|
+
import { StateStore } from './state/store.js';
|
|
8
|
+
import { ChannelRegistry } from './channelRegistry.js';
|
|
9
|
+
import { ConfigResolver } from './configResolver.js';
|
|
10
|
+
import { PermissionResolver } from './permissionResolver.js';
|
|
11
|
+
function makeConfig(overrides = {}) {
|
|
12
|
+
return {
|
|
13
|
+
version: CONFIG_VERSION,
|
|
14
|
+
discord: { token: 'bot-token-abc', clientId: '123456789' },
|
|
15
|
+
auth: { adminRoleIds: [], executeRoleIds: [], readOnlyRoleIds: [], dmPolicy: 'deny' },
|
|
16
|
+
defaults: {
|
|
17
|
+
mode: 'claude',
|
|
18
|
+
claudeModel: 'opus',
|
|
19
|
+
codexModel: '',
|
|
20
|
+
permissionMode: 'default',
|
|
21
|
+
permissionProfile: null,
|
|
22
|
+
codexHome: '~/.codex',
|
|
23
|
+
codexCliCommand: 'codex',
|
|
24
|
+
codexCliVersion: null,
|
|
25
|
+
},
|
|
26
|
+
limits: { maxSessionsPerUser: 0, permissionTimeoutSec: 60, codexTimeoutMs: 1_800_000 },
|
|
27
|
+
policy: { unknownCommand: 'confirm', allowExtraCommands: [] },
|
|
28
|
+
autoAllowClaudeTools: ['Read', 'Glob', 'Grep'],
|
|
29
|
+
profiles: {
|
|
30
|
+
readonly: { permissionMode: 'plan', allowedTools: ['Read', 'Glob', 'Grep'], policyTier: 'read-only' },
|
|
31
|
+
edit: { permissionMode: 'acceptEdits', allowedTools: ['Read', 'Edit', 'Write', 'Bash'], policyTier: 'normal' },
|
|
32
|
+
},
|
|
33
|
+
usage: { userAgent: 'claude-code', cacheSec: 180 },
|
|
34
|
+
audit: { channelId: null },
|
|
35
|
+
locale: 'ko',
|
|
36
|
+
logLevel: 'info',
|
|
37
|
+
favorites: [],
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function binding(overrides = {}) {
|
|
42
|
+
return {
|
|
43
|
+
guildId: 'g1',
|
|
44
|
+
channelId: 'c1',
|
|
45
|
+
mode: 'claude',
|
|
46
|
+
sessionId: null,
|
|
47
|
+
cwd: '/abs/workspace',
|
|
48
|
+
ownerId: 'u1',
|
|
49
|
+
permMode: 'default',
|
|
50
|
+
profile: null,
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
describe('PermissionResolver', () => {
|
|
55
|
+
let dir;
|
|
56
|
+
let store;
|
|
57
|
+
function build() {
|
|
58
|
+
const registry = new ChannelRegistry(new StateStore(dir), () => '2026-01-01T00:00:00.000Z');
|
|
59
|
+
const configResolver = new ConfigResolver(store, registry);
|
|
60
|
+
const resolver = new PermissionResolver(store, configResolver);
|
|
61
|
+
return { resolver, registry };
|
|
62
|
+
}
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dab-permres-'));
|
|
65
|
+
store = new ConfigStore(dir);
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
it('default: global permissionMode + global auto-allow tools when nothing overrides', () => {
|
|
71
|
+
store.save(makeConfig());
|
|
72
|
+
const { resolver } = build();
|
|
73
|
+
const r = resolver.resolve('g1', 'c1');
|
|
74
|
+
expect(r.permMode).toBe('default');
|
|
75
|
+
expect(r.profile).toBeNull();
|
|
76
|
+
expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
|
|
77
|
+
expect(r.policyTier).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
it('layers permission mode global→server→project (project wins)', () => {
|
|
80
|
+
store.save(makeConfig());
|
|
81
|
+
store.saveServerConfig({ version: 1, guildId: 'g1', defaults: { permissionMode: 'acceptEdits' } });
|
|
82
|
+
const { resolver, registry } = build();
|
|
83
|
+
// Server-only → acceptEdits.
|
|
84
|
+
expect(resolver.resolve('g1', 'c9').permMode).toBe('acceptEdits');
|
|
85
|
+
// Project binding wins.
|
|
86
|
+
registry.set(binding({ permMode: 'plan' }));
|
|
87
|
+
expect(resolver.resolve('g1', 'c1').permMode).toBe('plan');
|
|
88
|
+
});
|
|
89
|
+
it('a resolved profile bundles its mode, tools, and tier', () => {
|
|
90
|
+
store.save(makeConfig());
|
|
91
|
+
const { resolver, registry } = build();
|
|
92
|
+
registry.set(binding({ profile: 'readonly' }));
|
|
93
|
+
const r = resolver.resolve('g1', 'c1');
|
|
94
|
+
expect(r.profile).toBe('readonly');
|
|
95
|
+
expect(r.permMode).toBe('plan'); // from profile, over binding permMode 'default'
|
|
96
|
+
expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
|
|
97
|
+
expect(r.policyTier).toBe('read-only');
|
|
98
|
+
});
|
|
99
|
+
it('session override permMode wins over layered value and profile', () => {
|
|
100
|
+
store.save(makeConfig());
|
|
101
|
+
const { resolver, registry } = build();
|
|
102
|
+
registry.set(binding({ profile: 'readonly' })); // profile would give 'plan'
|
|
103
|
+
const r = resolver.resolve('g1', 'c1', { permMode: 'bypassPermissions' });
|
|
104
|
+
expect(r.permMode).toBe('bypassPermissions');
|
|
105
|
+
// Profile still supplies the tool allowlist/tier unless the profile itself was overridden.
|
|
106
|
+
expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
|
|
107
|
+
expect(r.profile).toBe('readonly');
|
|
108
|
+
});
|
|
109
|
+
it('session override can switch the profile, changing bundled tools', () => {
|
|
110
|
+
store.save(makeConfig());
|
|
111
|
+
const { resolver, registry } = build();
|
|
112
|
+
registry.set(binding({ profile: 'readonly' }));
|
|
113
|
+
const r = resolver.resolve('g1', 'c1', { profile: 'edit' });
|
|
114
|
+
expect(r.profile).toBe('edit');
|
|
115
|
+
expect(r.permMode).toBe('acceptEdits');
|
|
116
|
+
expect(r.allowedTools).toEqual(['Read', 'Edit', 'Write', 'Bash']);
|
|
117
|
+
expect(r.policyTier).toBe('normal');
|
|
118
|
+
});
|
|
119
|
+
it('session override can clear the profile back to layered/global tools', () => {
|
|
120
|
+
store.save(makeConfig());
|
|
121
|
+
const { resolver, registry } = build();
|
|
122
|
+
registry.set(binding({ profile: 'readonly', permMode: 'acceptEdits' }));
|
|
123
|
+
const r = resolver.resolve('g1', 'c1', { profile: null });
|
|
124
|
+
expect(r.profile).toBeNull();
|
|
125
|
+
// No profile → layered permMode (binding's acceptEdits) + global auto-allow tools.
|
|
126
|
+
expect(r.permMode).toBe('acceptEdits');
|
|
127
|
+
expect(r.allowedTools).toEqual(['Read', 'Glob', 'Grep']);
|
|
128
|
+
});
|
|
129
|
+
it('throws on an unknown profile name', () => {
|
|
130
|
+
store.save(makeConfig());
|
|
131
|
+
const { resolver, registry } = build();
|
|
132
|
+
registry.set(binding({ profile: 'nope' }));
|
|
133
|
+
expect(() => resolver.resolve('g1', 'c1')).toThrow(/unknown permission profile/i);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
//# sourceMappingURL=permissionResolver.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissionResolver.test.js","sourceRoot":"","sources":["../../src/core/permissionResolver.test.ts"],"names":[],"mappings":"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"}
|