@misterhuydo/sentinel 1.0.82 → 1.0.84
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/.cairn/.hint-lock +1 -1
- package/.cairn/minify-map.json +0 -6
- package/.cairn/session.json +2 -2
- package/lib/generate.js +15 -1
- package/lib/init.js +36 -12
- package/lib/upgrade.js +1 -2
- package/package.json +1 -1
- package/python/scripts/patch_notify.js +200 -0
- package/python/sentinel/config_loader.py +6 -0
- package/python/sentinel/fix_engine.py +177 -160
- package/python/sentinel/main.py +35 -0
- package/python/sentinel/notify.py +88 -0
- package/python/sentinel/sentinel_boss.py +53 -21
- package/templates/sentinel.properties +3 -1
- package/templates/workspace-sentinel.properties +33 -0
- package/.cairn/views/2a85cc_init.js +0 -275
package/.cairn/.hint-lock
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-23T05:01:48.297Z
|
package/.cairn/minify-map.json
CHANGED
|
@@ -4,11 +4,5 @@
|
|
|
4
4
|
"state": "compressed",
|
|
5
5
|
"minifiedAt": 1774128147034.2527,
|
|
6
6
|
"readCount": 1
|
|
7
|
-
},
|
|
8
|
-
"J:\\Projects\\Sentinel\\cli\\lib\\init.js": {
|
|
9
|
-
"tempPath": "J:\\Projects\\Sentinel\\cli\\.cairn\\views\\2a85cc_init.js",
|
|
10
|
-
"state": "compressed",
|
|
11
|
-
"minifiedAt": 1774189651415.41,
|
|
12
|
-
"readCount": 1
|
|
13
7
|
}
|
|
14
8
|
}
|
package/.cairn/session.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"message": "Auto-checkpoint at 2026-03-
|
|
3
|
-
"checkpoint_at": "2026-03-
|
|
2
|
+
"message": "Auto-checkpoint at 2026-03-23T05:23:48.069Z",
|
|
3
|
+
"checkpoint_at": "2026-03-23T05:23:48.071Z",
|
|
4
4
|
"active_files": [],
|
|
5
5
|
"notes": [],
|
|
6
6
|
"mtime_snapshot": {}
|
package/lib/generate.js
CHANGED
|
@@ -96,7 +96,7 @@ rm -f "$PID_FILE"
|
|
|
96
96
|
|
|
97
97
|
// ── Workspace-level startAll / stopAll ────────────────────────────────────────
|
|
98
98
|
|
|
99
|
-
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}) {
|
|
99
|
+
function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {}, authConfig = {}) {
|
|
100
100
|
// Write shared sentinel.properties once (never overwrite existing)
|
|
101
101
|
const workspaceProps = path.join(workspace, 'sentinel.properties');
|
|
102
102
|
if (!fs.existsSync(workspaceProps)) {
|
|
@@ -108,6 +108,20 @@ function generateWorkspaceScripts(workspace, smtpConfig = {}, slackConfig = {})
|
|
|
108
108
|
if (smtpConfig.password) tpl = tpl.replace('SMTP_PASSWORD=<app-password>', 'SMTP_PASSWORD=' + smtpConfig.password);
|
|
109
109
|
fs.writeFileSync(workspaceProps, tpl);
|
|
110
110
|
}
|
|
111
|
+
// Always upsert auth config so re-runs persist it
|
|
112
|
+
if (authConfig.apiKey || authConfig.claudeProForTasks !== undefined) {
|
|
113
|
+
let props = fs.readFileSync(workspaceProps, 'utf8');
|
|
114
|
+
if (authConfig.apiKey) {
|
|
115
|
+
const replaced = props.replace(/^#?\s*ANTHROPIC_API_KEY=.*/m, 'ANTHROPIC_API_KEY=' + authConfig.apiKey);
|
|
116
|
+
props = replaced !== props ? replaced : props.trimEnd() + '\nANTHROPIC_API_KEY=' + authConfig.apiKey + '\n';
|
|
117
|
+
}
|
|
118
|
+
if (authConfig.claudeProForTasks !== undefined) {
|
|
119
|
+
const val = authConfig.claudeProForTasks ? 'true' : 'false';
|
|
120
|
+
const replaced = props.replace(/^CLAUDE_PRO_FOR_TASKS=.*/m, 'CLAUDE_PRO_FOR_TASKS=' + val);
|
|
121
|
+
props = replaced !== props ? replaced : props.trimEnd() + '\nCLAUDE_PRO_FOR_TASKS=' + val + '\n';
|
|
122
|
+
}
|
|
123
|
+
fs.writeFileSync(workspaceProps, props);
|
|
124
|
+
}
|
|
111
125
|
// Always upsert Slack tokens so re-runs persist them
|
|
112
126
|
if (slackConfig.botToken || slackConfig.appToken) {
|
|
113
127
|
let props = fs.readFileSync(workspaceProps, 'utf8');
|
package/lib/init.js
CHANGED
|
@@ -33,15 +33,26 @@ module.exports = async function init() {
|
|
|
33
33
|
{
|
|
34
34
|
type: 'select',
|
|
35
35
|
name: 'authMode',
|
|
36
|
-
message: '
|
|
36
|
+
message: 'Claude authentication strategy',
|
|
37
|
+
hint: 'Boss = Slack AI; Fix Engine = autonomous code repair',
|
|
37
38
|
choices: [
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
{
|
|
40
|
+
title: 'Both (RECOMMENDED) — API key for Boss, Claude Pro for Fix Engine',
|
|
41
|
+
value: 'both',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: 'API key only — full Boss tools; Fix Engine billed per token',
|
|
45
|
+
value: 'apikey',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: 'Claude Pro / OAuth only — run `claude login`; Boss has limited tools',
|
|
49
|
+
value: 'oauth',
|
|
50
|
+
},
|
|
51
|
+
{ title: 'Skip (configure manually in sentinel.properties)', value: 'skip' },
|
|
41
52
|
],
|
|
42
53
|
},
|
|
43
54
|
{
|
|
44
|
-
type: prev => prev === 'apikey' ? 'password' : null,
|
|
55
|
+
type: prev => (prev === 'apikey' || prev === 'both') ? 'password' : null,
|
|
45
56
|
name: 'anthropicKey',
|
|
46
57
|
message: 'Anthropic API key (sk-ant-...)',
|
|
47
58
|
validate: v => v.startsWith('sk-ant-') ? true : 'Key should start with sk-ant-',
|
|
@@ -147,12 +158,22 @@ module.exports = async function init() {
|
|
|
147
158
|
|
|
148
159
|
// ── Claude Code auth ─────────────────────────────────────────────────────────
|
|
149
160
|
step('Claude Code authentication…');
|
|
150
|
-
if (authMode === '
|
|
151
|
-
ok('API key
|
|
161
|
+
if (authMode === 'both' && anthropicKey) {
|
|
162
|
+
ok('API key → Sentinel Boss (full tools, structured responses)');
|
|
163
|
+
ok('Claude Pro → Fix Engine + Ask Codebase (heavy coding, Pro subscription)');
|
|
164
|
+
info('Run `claude login` on this server now (or before starting projects)');
|
|
165
|
+
info('CLAUDE_PRO_FOR_TASKS=true written to workspace sentinel.properties');
|
|
166
|
+
} else if (authMode === 'apikey' && anthropicKey) {
|
|
167
|
+
ok('API key → all Claude usage (Boss + Fix Engine billed to your API quota)');
|
|
168
|
+
info('CLAUDE_PRO_FOR_TASKS=false written — Fix Engine will use API key');
|
|
169
|
+
warn('Heavy fix tasks will consume API tokens. Claude Pro is cheaper for those.');
|
|
152
170
|
} else if (authMode === 'oauth') {
|
|
153
|
-
|
|
171
|
+
ok('Claude Pro / OAuth → Fix Engine + Ask Codebase');
|
|
172
|
+
warn('Boss will use CLI fallback — some tools unavailable without an API key');
|
|
173
|
+
info('Run `claude login` on this server to authenticate');
|
|
154
174
|
} else {
|
|
155
|
-
|
|
175
|
+
warn('No auth configured — add ANTHROPIC_API_KEY or run `claude login` before starting');
|
|
176
|
+
info('See: workspace sentinel.properties for full auth documentation');
|
|
156
177
|
}
|
|
157
178
|
|
|
158
179
|
// ── Slack Bot ────────────────────────────────────────────────────────────────
|
|
@@ -179,7 +200,11 @@ module.exports = async function init() {
|
|
|
179
200
|
|
|
180
201
|
// ── Workspace start/stop scripts ─────────────────────────────────────────────
|
|
181
202
|
step('Generating scripts…');
|
|
182
|
-
|
|
203
|
+
const authConfig = {};
|
|
204
|
+
if (anthropicKey) authConfig.apiKey = anthropicKey;
|
|
205
|
+
if (authMode === 'both' || authMode === 'oauth') authConfig.claudeProForTasks = true;
|
|
206
|
+
if (authMode === 'apikey') authConfig.claudeProForTasks = false;
|
|
207
|
+
generateWorkspaceScripts(workspace, { host: smtpHost, user: smtpUser, password: effectiveSmtpPassword }, { botToken: effectiveSlackBotToken, appToken: effectiveSlackAppToken }, authConfig);
|
|
183
208
|
ok(`${workspace}/startAll.sh`);
|
|
184
209
|
ok(`${workspace}/stopAll.sh`);
|
|
185
210
|
|
|
@@ -316,8 +341,7 @@ function ensureClaudePermissions() {
|
|
|
316
341
|
}
|
|
317
342
|
try {
|
|
318
343
|
fs.ensureDirSync(require('path').dirname(settingsPath));
|
|
319
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '
|
|
320
|
-
');
|
|
344
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
321
345
|
ok('Claude Code permissions patched: ' + added.join(', '));
|
|
322
346
|
} catch (e) {
|
|
323
347
|
warn('Could not write ' + settingsPath + ': ' + e.message);
|
package/lib/upgrade.js
CHANGED
|
@@ -28,8 +28,7 @@ function ensureClaudePermissions() {
|
|
|
28
28
|
try {
|
|
29
29
|
const path = require('path');
|
|
30
30
|
fs.ensureDirSync(path.dirname(settingsPath));
|
|
31
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '
|
|
32
|
-
');
|
|
31
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
33
32
|
ok('Claude Code permissions patched: ' + added.join(', '));
|
|
34
33
|
} catch (e) {
|
|
35
34
|
warn('Could not write ' + settingsPath + ': ' + e.message);
|
package/package.json
CHANGED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
// ── fix_engine.py ─────────────────────────────────────────────────────────────
|
|
5
|
+
{
|
|
6
|
+
const f = 'sentinel/fix_engine.py';
|
|
7
|
+
let c = fs.readFileSync(f, 'utf8').replace(/\r\n/g, '\n');
|
|
8
|
+
|
|
9
|
+
// 1. Add notify import
|
|
10
|
+
const OLD_IMPORT = 'from .config_loader import RepoConfig, SentinelConfig\nfrom .log_parser import ErrorEvent';
|
|
11
|
+
const NEW_IMPORT = 'from .config_loader import RepoConfig, SentinelConfig\nfrom .log_parser import ErrorEvent\nfrom .notify import alert_if_rate_limited, slack_alert';
|
|
12
|
+
if (!c.includes(OLD_IMPORT)) { console.error('fix_engine import not found'); process.exit(1); }
|
|
13
|
+
c = c.replace(OLD_IMPORT, NEW_IMPORT);
|
|
14
|
+
|
|
15
|
+
// 2. Only inject ANTHROPIC_API_KEY when claude_pro_for_tasks is False
|
|
16
|
+
// (so when both are configured, heavy tasks use Claude Pro OAuth)
|
|
17
|
+
const OLD_ENV = ` import os as _os
|
|
18
|
+
env = _os.environ.copy()
|
|
19
|
+
if cfg.anthropic_api_key:
|
|
20
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key`;
|
|
21
|
+
const NEW_ENV = ` import os as _os
|
|
22
|
+
env = _os.environ.copy()
|
|
23
|
+
# Inject API key only when Claude Pro is NOT preferred for tasks
|
|
24
|
+
# (when claude_pro_for_tasks=True and API key is set, let claude CLI use OAuth/Pro)
|
|
25
|
+
if cfg.anthropic_api_key and not cfg.claude_pro_for_tasks:
|
|
26
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key`;
|
|
27
|
+
if (!c.includes(OLD_ENV)) { console.error('fix_engine env block not found'); process.exit(1); }
|
|
28
|
+
c = c.replace(OLD_ENV, NEW_ENV);
|
|
29
|
+
|
|
30
|
+
// 3. After getting output, check for rate limits and alert Slack
|
|
31
|
+
const OLD_OUTPUT = ` output = (result.stdout or "") + (result.stderr or "")
|
|
32
|
+
|
|
33
|
+
if output.strip().upper().startswith("SKIP:"):`;
|
|
34
|
+
const NEW_OUTPUT = ` output = (result.stdout or "") + (result.stderr or "")
|
|
35
|
+
|
|
36
|
+
# Alert Slack immediately on rate-limit / auth failure — never stay silent
|
|
37
|
+
alert_if_rate_limited(
|
|
38
|
+
cfg.slack_bot_token,
|
|
39
|
+
cfg.slack_channel,
|
|
40
|
+
source=f"fix_engine/{event.fingerprint}",
|
|
41
|
+
output=output,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if output.strip().upper().startswith("SKIP:"):`;
|
|
45
|
+
if (!c.includes(OLD_OUTPUT)) { console.error('fix_engine output block not found'); process.exit(1); }
|
|
46
|
+
c = c.replace(OLD_OUTPUT, NEW_OUTPUT);
|
|
47
|
+
|
|
48
|
+
// 4. Also alert on FileNotFoundError (claude CLI missing)
|
|
49
|
+
const OLD_NOTFOUND = ` except FileNotFoundError:
|
|
50
|
+
logger.error("Claude Code binary not found at '%s'", cfg.claude_code_bin)
|
|
51
|
+
return "error", None, ""`;
|
|
52
|
+
const NEW_NOTFOUND = ' except FileNotFoundError:\n'
|
|
53
|
+
+ ' msg = (\n'
|
|
54
|
+
+ ' f":warning: *Sentinel \u2014 Claude CLI not found*\\n"\n'
|
|
55
|
+
+ ' f"`{cfg.claude_code_bin}` not found. Run: `npm install -g @anthropic-ai/claude-code`\\n"\n'
|
|
56
|
+
+ ' f"Fix engine is disabled until this is resolved."\n'
|
|
57
|
+
+ ' )\n'
|
|
58
|
+
+ ' logger.error("Claude Code binary not found at \'%s\'", cfg.claude_code_bin)\n'
|
|
59
|
+
+ ' slack_alert(cfg.slack_bot_token, cfg.slack_channel, msg)\n'
|
|
60
|
+
+ ' return "error", None, ""';
|
|
61
|
+
if (!c.includes(OLD_NOTFOUND)) { console.error('fix_engine notfound block not found'); process.exit(1); }
|
|
62
|
+
c = c.replace(OLD_NOTFOUND, NEW_NOTFOUND);
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(f, c, 'utf8');
|
|
65
|
+
console.log('fix_engine.py done. Lines:', c.split('\n').length);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── sentinel_boss.py ──────────────────────────────────────────────────────────
|
|
69
|
+
{
|
|
70
|
+
const f = 'sentinel/sentinel_boss.py';
|
|
71
|
+
let c = fs.readFileSync(f, 'utf8').replace(/\r\n/g, '\n');
|
|
72
|
+
|
|
73
|
+
// 1. Add notify import
|
|
74
|
+
const OLD_IMPORT = 'logger = logging.getLogger(__name__)';
|
|
75
|
+
const NEW_IMPORT = 'from .notify import alert_if_rate_limited, slack_alert, is_rate_limited\n\nlogger = logging.getLogger(__name__)';
|
|
76
|
+
if (!c.includes(OLD_IMPORT)) { console.error('boss import anchor not found'); process.exit(1); }
|
|
77
|
+
c = c.replace(OLD_IMPORT, NEW_IMPORT);
|
|
78
|
+
|
|
79
|
+
// 2. Revert auth priority: API key first, CLI fallback
|
|
80
|
+
// Replace current handle_message body
|
|
81
|
+
const OLD_HM_BODY =
|
|
82
|
+
` # 1st priority: Claude Pro / OAuth via CLI
|
|
83
|
+
cli_reply, cli_done = await _handle_with_cli(
|
|
84
|
+
message, history, cfg_loader, store, slack_client=slack_client, user_name=user_name,
|
|
85
|
+
user_id=user_id, attachments=attachments,
|
|
86
|
+
)
|
|
87
|
+
if not cli_reply.startswith(":warning:"):
|
|
88
|
+
return cli_reply, cli_done
|
|
89
|
+
|
|
90
|
+
# CLI failed — try ANTHROPIC_API_KEY fallback
|
|
91
|
+
try:
|
|
92
|
+
import anthropic # noqa: F401
|
|
93
|
+
except ImportError:
|
|
94
|
+
return (
|
|
95
|
+
":warning: \`anthropic\` package not installed. Run: \`pip install anthropic\`",
|
|
96
|
+
True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
api_key = cfg_loader.sentinel.anthropic_api_key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
100
|
+
if not api_key:
|
|
101
|
+
return cli_reply, cli_done # No fallback available
|
|
102
|
+
|
|
103
|
+
logger.info("Boss: CLI path failed (%s…), falling back to ANTHROPIC_API_KEY", cli_reply[:60])
|
|
104
|
+
return await _handle_with_api(
|
|
105
|
+
message, history, cfg_loader, store, slack_client=slack_client, user_name=user_name,
|
|
106
|
+
user_id=user_id, attachments=attachments,
|
|
107
|
+
)`;
|
|
108
|
+
|
|
109
|
+
const NEW_HM_BODY =
|
|
110
|
+
` api_key = cfg_loader.sentinel.anthropic_api_key or os.environ.get("ANTHROPIC_API_KEY", "")
|
|
111
|
+
|
|
112
|
+
# 1st priority: ANTHROPIC_API_KEY — full structured tools, cheap per-token for Boss queries
|
|
113
|
+
if api_key:
|
|
114
|
+
try:
|
|
115
|
+
import anthropic # noqa: F401
|
|
116
|
+
return await _handle_with_api(
|
|
117
|
+
message, history, cfg_loader, store, slack_client=slack_client,
|
|
118
|
+
user_name=user_name, user_id=user_id, attachments=attachments,
|
|
119
|
+
)
|
|
120
|
+
except Exception as api_err:
|
|
121
|
+
err_str = str(api_err)
|
|
122
|
+
# Detect rate-limit / auth failure and alert Slack before falling through
|
|
123
|
+
cfg = cfg_loader.sentinel
|
|
124
|
+
if is_rate_limited(err_str):
|
|
125
|
+
from .notify import rate_limit_message
|
|
126
|
+
alert_if_rate_limited(cfg.slack_bot_token, cfg.slack_channel,
|
|
127
|
+
"sentinel_boss/api", err_str)
|
|
128
|
+
logger.warning("Boss: API key path failed (%s), trying CLI fallback", err_str[:80])
|
|
129
|
+
|
|
130
|
+
# 2nd priority: Claude Pro / OAuth via CLI (limited tools but no API key needed)
|
|
131
|
+
cli_reply, cli_done = await _handle_with_cli(
|
|
132
|
+
message, history, cfg_loader, store, slack_client=slack_client, user_name=user_name,
|
|
133
|
+
user_id=user_id, attachments=attachments,
|
|
134
|
+
)
|
|
135
|
+
if not cli_reply.startswith(":warning:"):
|
|
136
|
+
return cli_reply, cli_done
|
|
137
|
+
|
|
138
|
+
# Both paths failed — alert Slack and return error
|
|
139
|
+
cfg = cfg_loader.sentinel
|
|
140
|
+
err_output = cli_reply
|
|
141
|
+
alert_if_rate_limited(cfg.slack_bot_token, cfg.slack_channel,
|
|
142
|
+
"sentinel_boss/cli", err_output)
|
|
143
|
+
if not api_key:
|
|
144
|
+
# No auth at all configured
|
|
145
|
+
no_auth_msg = (
|
|
146
|
+
":warning: *Sentinel Boss — no Claude auth configured*\\n"
|
|
147
|
+
"Configure at least one of:\\n"
|
|
148
|
+
"\u2022 \`ANTHROPIC_API_KEY\` in \`sentinel.properties\` \u2014 full features\\n"
|
|
149
|
+
"\u2022 Claude Pro OAuth: run \`claude login\` on the server \u2014 required for fix_engine\\n"
|
|
150
|
+
"See: https://github.com/misterhuydo/Sentinel#authentication"
|
|
151
|
+
)
|
|
152
|
+
slack_alert(cfg.slack_bot_token, cfg.slack_channel, no_auth_msg)
|
|
153
|
+
return ":warning: No Claude authentication configured. See Slack for details.", True
|
|
154
|
+
return cli_reply, cli_done`;
|
|
155
|
+
|
|
156
|
+
if (!c.includes(OLD_HM_BODY)) { console.error('handle_message body not found'); process.exit(1); }
|
|
157
|
+
c = c.replace(OLD_HM_BODY, NEW_HM_BODY);
|
|
158
|
+
|
|
159
|
+
// 3. In _handle_with_cli, alert Slack on rate-limit detected in CLI output
|
|
160
|
+
const OLD_CLI_FAIL =
|
|
161
|
+
` if result.returncode != 0 and not output:
|
|
162
|
+
return f":warning: \`claude --print\` failed (exit {result.returncode}): {(result.stderr or '').strip()[:300]}", True`;
|
|
163
|
+
const NEW_CLI_FAIL =
|
|
164
|
+
` raw_err = (result.stderr or "").strip()
|
|
165
|
+
if result.returncode != 0 and not output:
|
|
166
|
+
full_err = f"exit {result.returncode}: {raw_err[:300]}"
|
|
167
|
+
cfg = cfg_loader.sentinel
|
|
168
|
+
alert_if_rate_limited(cfg.slack_bot_token, cfg.slack_channel,
|
|
169
|
+
"sentinel_boss/cli", raw_err or full_err)
|
|
170
|
+
return f":warning: \`claude --print\` failed ({full_err})", True`;
|
|
171
|
+
if (!c.includes(OLD_CLI_FAIL)) { console.error('cli fail block not found'); process.exit(1); }
|
|
172
|
+
c = c.replace(OLD_CLI_FAIL, NEW_CLI_FAIL);
|
|
173
|
+
|
|
174
|
+
// 4. Also alert in ask_codebase when claude --print fails
|
|
175
|
+
const OLD_ASK_FAIL = ` if r.returncode != 0 and not output:
|
|
176
|
+
return {"repo": repo_name, "error": f"claude --print failed (rc={r.returncode}): {(r.stderr or '')[:200]}"}`;
|
|
177
|
+
const NEW_ASK_FAIL = ` if r.returncode != 0 and not output:
|
|
178
|
+
raw_err = (r.stderr or "")
|
|
179
|
+
alert_if_rate_limited(
|
|
180
|
+
cfg.slack_bot_token, cfg.slack_channel,
|
|
181
|
+
f"ask_codebase/{repo_name}", raw_err,
|
|
182
|
+
)
|
|
183
|
+
return {"repo": repo_name, "error": f"claude --print failed (rc={r.returncode}): {raw_err[:200]}"}`;
|
|
184
|
+
if (!c.includes(OLD_ASK_FAIL)) { console.error('ask_codebase fail block not found'); process.exit(1); }
|
|
185
|
+
c = c.replace(OLD_ASK_FAIL, NEW_ASK_FAIL);
|
|
186
|
+
|
|
187
|
+
// 5. ask_codebase: respect claude_pro_for_tasks (don't inject API key when Pro preferred)
|
|
188
|
+
const OLD_ASK_ENV = ` env = os.environ.copy()
|
|
189
|
+
if cfg.anthropic_api_key:
|
|
190
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key`;
|
|
191
|
+
const NEW_ASK_ENV = ` env = os.environ.copy()
|
|
192
|
+
# Only inject API key when Claude Pro is NOT preferred for heavy tasks
|
|
193
|
+
if cfg.anthropic_api_key and not cfg.claude_pro_for_tasks:
|
|
194
|
+
env["ANTHROPIC_API_KEY"] = cfg.anthropic_api_key`;
|
|
195
|
+
if (!c.includes(OLD_ASK_ENV)) { console.error('ask_codebase env not found'); process.exit(1); }
|
|
196
|
+
c = c.replace(OLD_ASK_ENV, NEW_ASK_ENV);
|
|
197
|
+
|
|
198
|
+
fs.writeFileSync(f, c, 'utf8');
|
|
199
|
+
console.log('sentinel_boss.py done. Lines:', c.split('\n').length);
|
|
200
|
+
}
|
|
@@ -63,6 +63,11 @@ class SentinelConfig:
|
|
|
63
63
|
slack_watch_bot_ids: list[str] = field(default_factory=list) # pre-configured bot IDs to watch passively
|
|
64
64
|
slack_allowed_users: list[str] = field(default_factory=list) # if set, only these Slack user IDs can talk to Boss
|
|
65
65
|
project_name: str = "" # optional: friendly name used by Sentinel Boss (e.g. "1881")
|
|
66
|
+
# Auth strategy:
|
|
67
|
+
# ANTHROPIC_API_KEY — used by Sentinel Boss conversation (structured tools, cheap per-token)
|
|
68
|
+
# Claude Pro / OAuth — used by fix_engine + ask_codebase when CLAUDE_PRO_FOR_TASKS=true
|
|
69
|
+
# At least one must be configured. Both = ideal split (Boss=API key, heavy tasks=Pro).
|
|
70
|
+
claude_pro_for_tasks: bool = True # when True + API key set, fix_engine/ask_codebase use claude CLI (Pro billing)
|
|
66
71
|
|
|
67
72
|
|
|
68
73
|
@dataclass
|
|
@@ -158,6 +163,7 @@ class ConfigLoader:
|
|
|
158
163
|
c.slack_watch_bot_ids = _csv(d.get("SLACK_WATCH_BOT_IDS", ""))
|
|
159
164
|
c.slack_allowed_users = _csv(d.get("SLACK_ALLOWED_USERS", ""))
|
|
160
165
|
c.project_name = d.get("PROJECT_NAME", "")
|
|
166
|
+
c.claude_pro_for_tasks = d.get("CLAUDE_PRO_FOR_TASKS", "true").lower() != "false"
|
|
161
167
|
self.sentinel = c
|
|
162
168
|
|
|
163
169
|
def _load_log_sources(self):
|