@nexrall/code-core 1.4.75 → 1.4.77
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/dist/agent/askOnce.d.ts +10 -0
- package/dist/agent/askOnce.d.ts.map +1 -1
- package/dist/agent/askOnce.js +10 -2
- package/dist/agent/goal.d.ts +55 -0
- package/dist/agent/goal.d.ts.map +1 -0
- package/dist/agent/goal.js +120 -0
- package/dist/agent/handoffBrief.d.ts +82 -0
- package/dist/agent/handoffBrief.d.ts.map +1 -0
- package/dist/agent/handoffBrief.js +121 -0
- package/dist/agent/modelCatalogue.d.ts +23 -0
- package/dist/agent/modelCatalogue.d.ts.map +1 -1
- package/dist/agent/modelCatalogue.js +28 -0
- package/dist/agent/outputStyles.d.ts +27 -0
- package/dist/agent/outputStyles.d.ts.map +1 -0
- package/dist/agent/outputStyles.js +174 -0
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +16 -3
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +7 -0
- package/dist/api/billingLines.d.ts +11 -0
- package/dist/api/billingLines.d.ts.map +1 -0
- package/dist/api/billingLines.js +56 -0
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/scheduleLines.d.ts +53 -0
- package/dist/api/scheduleLines.d.ts.map +1 -0
- package/dist/api/scheduleLines.js +150 -0
- package/dist/context/contextFiles.d.ts +81 -0
- package/dist/context/contextFiles.d.ts.map +1 -0
- package/dist/context/contextFiles.js +310 -0
- package/dist/diff/collect.d.ts +25 -0
- package/dist/diff/collect.d.ts.map +1 -0
- package/dist/diff/collect.js +169 -0
- package/dist/diff/diffView.d.ts +57 -0
- package/dist/diff/diffView.d.ts.map +1 -0
- package/dist/diff/diffView.js +231 -0
- package/dist/diff/reviewQueue.d.ts +53 -0
- package/dist/diff/reviewQueue.d.ts.map +1 -0
- package/dist/diff/reviewQueue.js +349 -0
- package/dist/events/recap.d.ts +54 -0
- package/dist/events/recap.d.ts.map +1 -0
- package/dist/events/recap.js +161 -0
- package/dist/events/sessionEvents.d.ts +54 -0
- package/dist/events/sessionEvents.d.ts.map +1 -0
- package/dist/events/sessionEvents.js +324 -0
- package/dist/events/sessionStats.d.ts +89 -0
- package/dist/events/sessionStats.d.ts.map +1 -0
- package/dist/events/sessionStats.js +264 -0
- package/dist/import/claudeImport.d.ts +44 -0
- package/dist/import/claudeImport.d.ts.map +1 -0
- package/dist/import/claudeImport.js +337 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -0
- package/dist/permissions/autoMode.d.ts +89 -0
- package/dist/permissions/autoMode.d.ts.map +1 -0
- package/dist/permissions/autoMode.js +346 -0
- package/dist/permissions/rules.d.ts +6 -0
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +33 -0
- package/dist/project/projectCommands.d.ts +33 -0
- package/dist/project/projectCommands.d.ts.map +1 -0
- package/dist/project/projectCommands.js +246 -0
- package/dist/search/conversationTurns.d.ts +53 -0
- package/dist/search/conversationTurns.d.ts.map +1 -0
- package/dist/search/conversationTurns.js +170 -0
- package/dist/search/globalSearch.d.ts +144 -0
- package/dist/search/globalSearch.d.ts.map +1 -0
- package/dist/search/globalSearch.js +350 -0
- package/dist/share/sessionChannel.d.ts +136 -0
- package/dist/share/sessionChannel.d.ts.map +1 -0
- package/dist/share/sessionChannel.js +373 -0
- package/dist/share/sessionShare.d.ts +104 -0
- package/dist/share/sessionShare.d.ts.map +1 -0
- package/dist/share/sessionShare.js +336 -0
- package/dist/ui/configCmd.d.ts +48 -0
- package/dist/ui/configCmd.d.ts.map +1 -0
- package/dist/ui/configCmd.js +146 -0
- package/dist/ui/deployPanel.d.ts +91 -0
- package/dist/ui/deployPanel.d.ts.map +1 -0
- package/dist/ui/deployPanel.js +442 -0
- package/dist/ui/mergeAssistant.d.ts +120 -0
- package/dist/ui/mergeAssistant.d.ts.map +1 -0
- package/dist/ui/mergeAssistant.js +391 -0
- package/dist/ui/notify.d.ts +114 -0
- package/dist/ui/notify.d.ts.map +1 -0
- package/dist/ui/notify.js +235 -0
- package/dist/ui/promptSuggest.d.ts +56 -0
- package/dist/ui/promptSuggest.d.ts.map +1 -0
- package/dist/ui/promptSuggest.js +160 -0
- package/dist/ui/taskGraph.d.ts +44 -0
- package/dist/ui/taskGraph.d.ts.map +1 -0
- package/dist/ui/taskGraph.js +289 -0
- package/dist/util/addDir.d.ts +31 -0
- package/dist/util/addDir.d.ts.map +1 -0
- package/dist/util/addDir.js +124 -0
- package/dist/util/batch.d.ts +16 -0
- package/dist/util/batch.d.ts.map +1 -0
- package/dist/util/batch.js +92 -0
- package/dist/util/cellWidth.d.ts +12 -0
- package/dist/util/cellWidth.d.ts.map +1 -0
- package/dist/util/cellWidth.js +161 -0
- package/dist/util/duration.d.ts +8 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +50 -0
- package/dist/util/loopJobs.d.ts +67 -0
- package/dist/util/loopJobs.d.ts.map +1 -0
- package/dist/util/loopJobs.js +169 -0
- package/dist/util/settingsFile.d.ts +14 -0
- package/dist/util/settingsFile.d.ts.map +1 -0
- package/dist/util/settingsFile.js +99 -0
- package/dist/util/toolLabel.d.ts +2 -0
- package/dist/util/toolLabel.d.ts.map +1 -0
- package/dist/util/toolLabel.js +17 -0
- package/dist/verify/failureTimeline.d.ts +100 -0
- package/dist/verify/failureTimeline.d.ts.map +1 -0
- package/dist/verify/failureTimeline.js +253 -0
- package/dist/verify/verification.d.ts +75 -0
- package/dist/verify/verification.d.ts.map +1 -0
- package/dist/verify/verification.js +315 -0
- package/package.json +5 -4
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// S20 — auto-mode config + the classifier's prompt/parse (see the design doc
|
|
3
|
+
// nexrall/docs/AUTO_MODE_CLASSIFIER.md).
|
|
4
|
+
//
|
|
5
|
+
// The pure half: where `autoMode` settings may come from (user + managed +
|
|
6
|
+
// --settings ONLY — never project/local, so a cloned repo cannot loosen the
|
|
7
|
+
// gate), how tiers merge, the built-in prose limits, the classifier prompt and
|
|
8
|
+
// the reply parser. The model CALL and the runtime (cache, budget, fast paths)
|
|
9
|
+
// live in the hosts — cli/src/permissions/autoClassify.ts for the CLI.
|
|
10
|
+
//
|
|
11
|
+
// The lists below are PROSE FOR A JUDGE, not regexes: they say what the
|
|
12
|
+
// classifier must protect, in the same words a human would, because the check
|
|
13
|
+
// is a model call and not another denylist (the destructive-token guard is
|
|
14
|
+
// that — and it stays the code-level floor, unchanged, underneath this).
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.AUTO_MODE_UNAVAILABLE = exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = exports.AUTO_MODE_DEFAULTS_SENTINEL = exports.AUTO_MODE_SOFT_DENY_DEFAULTS = exports.AUTO_MODE_HARD_DENY_DEFAULTS = void 0;
|
|
50
|
+
exports.loadAutoModeConfig = loadAutoModeConfig;
|
|
51
|
+
exports.classifyPrompt = classifyPrompt;
|
|
52
|
+
exports.parseClassifierReply = parseClassifierReply;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
56
|
+
const rules_1 = require("./rules");
|
|
57
|
+
/**
|
|
58
|
+
* The built-in hard limits (CC's `$defaults` for `hard_deny`): unconditional —
|
|
59
|
+
* user intent does NOT clear these. Keep this list SHORT and beyond argument;
|
|
60
|
+
* it exists for the actions no request should ever authorize.
|
|
61
|
+
*/
|
|
62
|
+
exports.AUTO_MODE_HARD_DENY_DEFAULTS = [
|
|
63
|
+
{
|
|
64
|
+
label: 'Data exfiltration',
|
|
65
|
+
text: 'Sending file contents, repository contents, environment variables, credentials or tokens to any network ' +
|
|
66
|
+
'destination the user has not explicitly named (e.g. curl/wget with a body built from local files or env).',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: 'Credential and system files',
|
|
70
|
+
text: 'Writing to or modifying security-sensitive locations outside the project: ~/.ssh, ~/.aws, ~/.config/gh, ' +
|
|
71
|
+
'~/.netrc, ~/.npmrc credential entries, shell rc files (.zshrc/.bashrc), /etc, or the OS keychain.',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Safety-system tampering',
|
|
75
|
+
text: "Disabling or weakening this session's own safety controls: editing permission rules or autoMode settings to " +
|
|
76
|
+
'grant more access, clearing audit trails, or killing the permission gate.',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* The built-in soft limits (CC's `$defaults` for `soft_deny`): block UNLESS the
|
|
81
|
+
* user's own latest message directly and specifically asks for this exact
|
|
82
|
+
* action, or an allow exception matches.
|
|
83
|
+
*/
|
|
84
|
+
exports.AUTO_MODE_SOFT_DENY_DEFAULTS = [
|
|
85
|
+
{
|
|
86
|
+
label: 'Git history rewrite',
|
|
87
|
+
text: 'Rewriting shared history: git push --force/--force-with-lease to a shared branch, or reset --hard that ' +
|
|
88
|
+
'discards committed work.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'Database and infra destruction',
|
|
92
|
+
text: 'Destructive database or infrastructure operations against shared or production systems: DROP/DELETE/TRUNCATE ' +
|
|
93
|
+
'on non-scratch data, terraform destroy, kubectl delete of shared resources, deleting cloud buckets.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: 'Mass deletion outside scratch space',
|
|
97
|
+
text: 'Deleting many files or whole directories outside the project workspace and scratch/temp locations.',
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
/** The `$defaults` sentinel (CC's spelling) inside soft_deny / hard_deny lists. */
|
|
101
|
+
exports.AUTO_MODE_DEFAULTS_SENTINEL = '$defaults';
|
|
102
|
+
exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS = 400;
|
|
103
|
+
exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD = 0.25;
|
|
104
|
+
function readTier(file) {
|
|
105
|
+
try {
|
|
106
|
+
const obj = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
107
|
+
const am = obj?.autoMode;
|
|
108
|
+
return am && typeof am === 'object' && !Array.isArray(am) ? am : null;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Accept CC's snake_case spellings and our camelCase ones. */
|
|
115
|
+
function pickKey(am, ...names) {
|
|
116
|
+
for (const n of names) {
|
|
117
|
+
if (Object.prototype.hasOwnProperty.call(am, n))
|
|
118
|
+
return { key: n, value: am[n] };
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function asProseList(value, key, warnings) {
|
|
123
|
+
if (!Array.isArray(value)) {
|
|
124
|
+
warnings.push(`autoMode.${key} must be an array of strings — ignored`);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return value
|
|
128
|
+
.filter((v) => typeof v === 'string')
|
|
129
|
+
.map((v) => v.trim())
|
|
130
|
+
.filter(Boolean);
|
|
131
|
+
}
|
|
132
|
+
function asBool(value, key, warnings) {
|
|
133
|
+
if (typeof value === 'boolean')
|
|
134
|
+
return value;
|
|
135
|
+
if (value === 'true')
|
|
136
|
+
return true;
|
|
137
|
+
if (value === 'false')
|
|
138
|
+
return false;
|
|
139
|
+
warnings.push(`autoMode.${key} must be true or false — ignored`);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function asPositiveNumber(value, key, warnings) {
|
|
143
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
144
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
145
|
+
warnings.push(`autoMode.${key} must be a non-negative number — ignored`);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return n;
|
|
149
|
+
}
|
|
150
|
+
/** Expand `$defaults` in a list, or supply defaults when the key was never set. */
|
|
151
|
+
function expandDefaults(list, defaults) {
|
|
152
|
+
if (list === null)
|
|
153
|
+
return defaults.map((d) => `[${d.label}] ${d.text}`);
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const item of list) {
|
|
156
|
+
if (item === exports.AUTO_MODE_DEFAULTS_SENTINEL)
|
|
157
|
+
out.push(...defaults.map((d) => `[${d.label}] ${d.text}`));
|
|
158
|
+
else
|
|
159
|
+
out.push(item);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Load the effective auto-mode config.
|
|
165
|
+
*
|
|
166
|
+
* Tier order — LOW to HIGH precedence (a higher tier that defines a key wins
|
|
167
|
+
* WHOLESALE for that key): user < managed < session (`--settings`). Project and
|
|
168
|
+
* settings.local are read ONLY to report that their `autoMode` is ignored;
|
|
169
|
+
* they can never contribute a value. That asymmetry is the whole security
|
|
170
|
+
* model: settings travel with a checkout, and a checkout that could grant
|
|
171
|
+
* itself trust would make the classifier theatre.
|
|
172
|
+
*/
|
|
173
|
+
function loadAutoModeConfig(workDir) {
|
|
174
|
+
const warnings = [];
|
|
175
|
+
const sources = [];
|
|
176
|
+
const ignoredProjectFiles = [];
|
|
177
|
+
const userFile = path.join(os.homedir(), '.nexrall', 'settings.json');
|
|
178
|
+
const managedFile = (0, rules_1.managedSettingsPath)();
|
|
179
|
+
const tiers = [
|
|
180
|
+
{ file: userFile, autoMode: readTier(userFile) },
|
|
181
|
+
{ file: managedFile, autoMode: readTier(managedFile) },
|
|
182
|
+
{ file: '--settings', autoMode: (0, rules_1.getSessionSettings)()?.autoMode ?? null },
|
|
183
|
+
];
|
|
184
|
+
// A non-object there (e.g. the string "off") is still an attempt we must not
|
|
185
|
+
// swallow silently.
|
|
186
|
+
for (const cand of [
|
|
187
|
+
path.join(workDir, '.nexrall', 'settings.json'),
|
|
188
|
+
path.join(workDir, '.nexrall', 'settings.local.json'),
|
|
189
|
+
]) {
|
|
190
|
+
try {
|
|
191
|
+
const obj = JSON.parse(fs.readFileSync(cand, 'utf-8'));
|
|
192
|
+
if (obj && Object.prototype.hasOwnProperty.call(obj, 'autoMode'))
|
|
193
|
+
ignoredProjectFiles.push(cand);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
/* no file / unreadable — nothing ignored */
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Per-key merge, low → high.
|
|
200
|
+
let enabled = null;
|
|
201
|
+
let classifierModel;
|
|
202
|
+
let environment = null;
|
|
203
|
+
let allow = null;
|
|
204
|
+
let softList = null;
|
|
205
|
+
let hardList = null;
|
|
206
|
+
let classifyAllShell = null;
|
|
207
|
+
let maxClassifications = null;
|
|
208
|
+
let maxBudgetUsd = null;
|
|
209
|
+
for (const tier of tiers) {
|
|
210
|
+
if (!tier.autoMode)
|
|
211
|
+
continue;
|
|
212
|
+
const am = tier.autoMode;
|
|
213
|
+
const keys = [];
|
|
214
|
+
const take = (r, key) => {
|
|
215
|
+
if (r !== null && r !== undefined)
|
|
216
|
+
keys.push(key);
|
|
217
|
+
return r;
|
|
218
|
+
};
|
|
219
|
+
const en = pickKey(am, 'classifier', 'enabled');
|
|
220
|
+
if (en)
|
|
221
|
+
enabled = take(asBool(en.value, 'classifier', warnings) ?? enabled, en.key);
|
|
222
|
+
const cm = pickKey(am, 'classifierModel', 'classifier_model');
|
|
223
|
+
if (cm && typeof cm.value === 'string' && cm.value.trim()) {
|
|
224
|
+
classifierModel = cm.value.trim();
|
|
225
|
+
keys.push(cm.key);
|
|
226
|
+
}
|
|
227
|
+
const env = pickKey(am, 'environment');
|
|
228
|
+
if (env)
|
|
229
|
+
environment = take(asProseList(env.value, 'environment', warnings) ?? environment, 'environment');
|
|
230
|
+
const al = pickKey(am, 'allow');
|
|
231
|
+
if (al)
|
|
232
|
+
allow = take(asProseList(al.value, 'allow', warnings) ?? allow, 'allow');
|
|
233
|
+
const sd = pickKey(am, 'soft_deny', 'softDeny');
|
|
234
|
+
if (sd)
|
|
235
|
+
softList = take(asProseList(sd.value, sd.key, warnings) ?? softList, sd.key);
|
|
236
|
+
const hd = pickKey(am, 'hard_deny', 'hardDeny');
|
|
237
|
+
if (hd)
|
|
238
|
+
hardList = take(asProseList(hd.value, hd.key, warnings) ?? hardList, hd.key);
|
|
239
|
+
const ca = pickKey(am, 'classifyAllShell', 'classify_all_shell');
|
|
240
|
+
if (ca)
|
|
241
|
+
classifyAllShell = take(asBool(ca.value, ca.key, warnings) ?? classifyAllShell, ca.key);
|
|
242
|
+
const mc = pickKey(am, 'maxClassifications', 'max_classifications');
|
|
243
|
+
if (mc)
|
|
244
|
+
maxClassifications = take(asPositiveNumber(mc.value, mc.key, warnings) ?? maxClassifications, mc.key);
|
|
245
|
+
const mb = pickKey(am, 'maxBudgetUsd', 'max_budget_usd');
|
|
246
|
+
if (mb)
|
|
247
|
+
maxBudgetUsd = take(asPositiveNumber(mb.value, mb.key, warnings) ?? maxBudgetUsd, mb.key);
|
|
248
|
+
if (keys.length)
|
|
249
|
+
sources.push({ file: tier.file, keys });
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
enabled: enabled ?? true,
|
|
253
|
+
...(classifierModel ? { classifierModel } : {}),
|
|
254
|
+
environment: environment ?? [],
|
|
255
|
+
allow: allow ?? [],
|
|
256
|
+
softDeny: expandDefaults(softList, exports.AUTO_MODE_SOFT_DENY_DEFAULTS),
|
|
257
|
+
hardDeny: expandDefaults(hardList, exports.AUTO_MODE_HARD_DENY_DEFAULTS),
|
|
258
|
+
classifyAllShell: classifyAllShell ?? false,
|
|
259
|
+
maxClassifications: maxClassifications ?? exports.AUTO_MODE_DEFAULT_MAX_CLASSIFICATIONS,
|
|
260
|
+
maxBudgetUsd: maxBudgetUsd ?? exports.AUTO_MODE_DEFAULT_MAX_BUDGET_USD,
|
|
261
|
+
sources,
|
|
262
|
+
ignoredProjectFiles,
|
|
263
|
+
warnings,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// ─── The classifier's prompt + reply ─────────────────────────────────────────
|
|
267
|
+
/** Clip helper local to this module (the prompt must never grow unbounded). */
|
|
268
|
+
function clip(text, max) {
|
|
269
|
+
return text.length > max ? text.slice(0, max - 1) + '\u2026' : text;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The review request. Deliberately one flat prompt — the classifier is a fresh,
|
|
273
|
+
* tool-less call (askModelOnce), so everything it may consult must be here:
|
|
274
|
+
* the limits, the environment, the action, and the human's actual words.
|
|
275
|
+
*/
|
|
276
|
+
function classifyPrompt(a) {
|
|
277
|
+
const list = (items) => (items.length ? items.map((t) => `- ${t}`).join('\n') : ' (none)');
|
|
278
|
+
const env = [
|
|
279
|
+
`The project directory ${a.workDir}${a.extraDirs?.length ? ` and the added directories ${a.extraDirs.join(', ')}` : ''}`,
|
|
280
|
+
"the current git repository's own configured remotes",
|
|
281
|
+
...a.cfg.environment,
|
|
282
|
+
];
|
|
283
|
+
const intent = a.intent?.length
|
|
284
|
+
? a.intent.map((t, i) => `[${i + 1}] ${clip(t.replace(/\s+/g, ' ').trim(), 300)}`).join('\n')
|
|
285
|
+
: ' (none)';
|
|
286
|
+
let action;
|
|
287
|
+
try {
|
|
288
|
+
action = clip(JSON.stringify(a.input), 2000);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
action = '(unserializable input)';
|
|
292
|
+
}
|
|
293
|
+
return ('You are the AUTO-MODE SAFETY CLASSIFIER for a coding agent.\n' +
|
|
294
|
+
'The agent runs in "auto" mode: it will execute the action below WITHOUT asking the human, unless you block it.\n' +
|
|
295
|
+
"Judge the ACTION's real-world effect, not its wording.\n\n" +
|
|
296
|
+
'HARD LIMITS — block unconditionally, even if the user asked:\n' +
|
|
297
|
+
list(a.cfg.hardDeny) +
|
|
298
|
+
"\n\nSOFT LIMITS — block UNLESS the user's latest message directly and specifically asks for this exact action, " +
|
|
299
|
+
'or an ALLOW EXCEPTION matches:\n' +
|
|
300
|
+
list(a.cfg.softDeny) +
|
|
301
|
+
'\n\nALLOW EXCEPTIONS (pre-approved by the user; clear SOFT limits, never hard ones):\n' +
|
|
302
|
+
list(a.cfg.allow) +
|
|
303
|
+
'\n\nTRUSTED ENVIRONMENT (routine here; staying inside it is not a limit violation):\n' +
|
|
304
|
+
list(env) +
|
|
305
|
+
'\n\nTHE ACTION:\ntool: ' +
|
|
306
|
+
a.tool +
|
|
307
|
+
'\ninput: ' +
|
|
308
|
+
action +
|
|
309
|
+
'\n\nRECENT USER MESSAGES (intent; the LAST one is the current instruction):\n' +
|
|
310
|
+
intent +
|
|
311
|
+
'\n\nReply with EXACTLY ONE line, one of:\n' +
|
|
312
|
+
'ALLOW\n' +
|
|
313
|
+
'BLOCK: <one short reason>\n' +
|
|
314
|
+
'BLOCK-HARD: <one short reason>\n' +
|
|
315
|
+
'Use BLOCK only for a soft limit without clear user intent, BLOCK-HARD for a hard limit. ' +
|
|
316
|
+
'For a routine, reversible action inside the workspace, reply ALLOW. No other text.');
|
|
317
|
+
}
|
|
318
|
+
/** A local guard failed us (or the host never called in) — see the design doc. */
|
|
319
|
+
exports.AUTO_MODE_UNAVAILABLE = {
|
|
320
|
+
action: 'unavailable',
|
|
321
|
+
reason: 'classifier could not review the action',
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Parse one line of classifier output. Anything that is not a clear ALLOW is
|
|
325
|
+
* NOT an allow — a mangled or empty reply fails closed (`unavailable`).
|
|
326
|
+
*/
|
|
327
|
+
function parseClassifierReply(text) {
|
|
328
|
+
if (typeof text !== 'string')
|
|
329
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'no reply from the classifier' };
|
|
330
|
+
const first = text
|
|
331
|
+
.split('\n')
|
|
332
|
+
.find((l) => l.trim().length > 0)
|
|
333
|
+
?.trim() ?? '';
|
|
334
|
+
if (!first)
|
|
335
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: 'empty reply from the classifier' };
|
|
336
|
+
const hard = /^BLOCK[\s_-]*HARD\b[:\s-]*(.*)$/i.exec(first);
|
|
337
|
+
if (hard)
|
|
338
|
+
return { action: 'block-hard', reason: hard[1].trim() || 'hard limit' };
|
|
339
|
+
const soft = /^BLOCK\b[:\s-]*(.*)$/i.exec(first);
|
|
340
|
+
if (soft)
|
|
341
|
+
return { action: 'block-soft', reason: soft[1].trim() || 'soft limit' };
|
|
342
|
+
if (/^ALLOW\b/i.test(first))
|
|
343
|
+
return { action: 'allow' };
|
|
344
|
+
return { ...exports.AUTO_MODE_UNAVAILABLE, reason: `unrecognized classifier reply: ${clip(first, 80)}` };
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=autoMode.js.map
|
|
@@ -9,11 +9,17 @@ export interface MergedSettings {
|
|
|
9
9
|
/** Raw merged object (for hooks, allowedTools, etc.). */
|
|
10
10
|
raw: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
|
+
export declare function managedSettingsPath(): string;
|
|
12
13
|
/** Replace the rules supplied for this process by flags. Not persisted anywhere. */
|
|
13
14
|
export declare function setSessionRules(r: Partial<PermissionRules>): void;
|
|
14
15
|
export declare function setSessionSettings(obj: Record<string, unknown> | null): void;
|
|
15
16
|
export declare function getSessionSettings(): Record<string, unknown> | null;
|
|
16
17
|
export declare function loadSettings(workDir: string): MergedSettings;
|
|
18
|
+
/** The first ALLOW rule that matches this call, or null. S20's classifier gate
|
|
19
|
+
* reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
|
|
20
|
+
* (suspended while the classifier is on — see isBroadAllowRule). */
|
|
21
|
+
export declare function matchedAllowRule(rules: PermissionRules, tool: string, input: Record<string, unknown>, workDir: string): string | null;
|
|
22
|
+
export declare function isBroadAllowRule(rule: string): boolean;
|
|
17
23
|
/**
|
|
18
24
|
* Evaluate a tool call against merged rules. Returns the matching decision, or
|
|
19
25
|
* null when no rule applies (caller decides the default, usually "ask").
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAsBD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEjE;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,CAE5E;AACD,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8B5D;AAqJD;;oEAEoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAEf;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,IAAI,CAK3B"}
|
|
@@ -33,15 +33,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.managedSettingsPath = managedSettingsPath;
|
|
36
37
|
exports.setSessionRules = setSessionRules;
|
|
37
38
|
exports.setSessionSettings = setSessionSettings;
|
|
38
39
|
exports.getSessionSettings = getSessionSettings;
|
|
39
40
|
exports.loadSettings = loadSettings;
|
|
41
|
+
exports.matchedAllowRule = matchedAllowRule;
|
|
42
|
+
exports.isBroadAllowRule = isBroadAllowRule;
|
|
40
43
|
exports.evaluatePermission = evaluatePermission;
|
|
41
44
|
const fs = __importStar(require("fs"));
|
|
42
45
|
const path = __importStar(require("path"));
|
|
43
46
|
const os = __importStar(require("os"));
|
|
44
47
|
function managedSettingsPath() {
|
|
48
|
+
// Tests (and a sandboxed doctor run) point this at a fixture. Checked first so it
|
|
49
|
+
// can never be shadowed by a real machine-level file.
|
|
50
|
+
if (process.env.NEXRALL_MANAGED_SETTINGS)
|
|
51
|
+
return process.env.NEXRALL_MANAGED_SETTINGS;
|
|
45
52
|
if (process.platform === 'darwin')
|
|
46
53
|
return '/Library/Application Support/Nexrall/managed-settings.json';
|
|
47
54
|
if (process.platform === 'win32')
|
|
@@ -258,6 +265,32 @@ function ruleMatches(rule, tool, input, workDir, opts) {
|
|
|
258
265
|
return true;
|
|
259
266
|
return matchValues(tool, input, workDir).some((v) => matchPattern(pattern, v));
|
|
260
267
|
}
|
|
268
|
+
/** The first ALLOW rule that matches this call, or null. S20's classifier gate
|
|
269
|
+
* reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
|
|
270
|
+
* (suspended while the classifier is on — see isBroadAllowRule). */
|
|
271
|
+
function matchedAllowRule(rules, tool, input, workDir) {
|
|
272
|
+
return rules.allow.find((r) => ruleMatches(r, tool, input, workDir, { forAllow: true })) ?? null;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Is this allow rule a BLANKET pre-approval ("anything this tool can do")?
|
|
276
|
+
* Those stop meaning anything once an auto-mode classifier exists: `Bash(*)`
|
|
277
|
+
* is exactly the rule a prompt-injected agent would wish for, and CC suspends
|
|
278
|
+
* the same set in their auto mode. Broad = bare tool name / `(*)`, or a bash
|
|
279
|
+
* rule that hands control to an interpreter (`Bash(python:*)`, `node -e`…).
|
|
280
|
+
* Narrow rules (`Bash(npm run test:*)`, `Read(src/**)`) keep resolving first.
|
|
281
|
+
*/
|
|
282
|
+
const INTERPRETER_HEADS = /^(?:\S*\/)?(python3?|node|bun|deno|ruby|perl|php|bash|sh|zsh|fish|pwsh|powershell|osascript|cmd)(\s|:|$)/i;
|
|
283
|
+
function isBroadAllowRule(rule) {
|
|
284
|
+
const { tool, pattern } = parseRule(rule);
|
|
285
|
+
if (pattern === null || pattern === '' || pattern === '*')
|
|
286
|
+
return true;
|
|
287
|
+
const isBashRule = ['bash', 'run_command', 'execute_command', 'Bash'].includes(tool);
|
|
288
|
+
if (!isBashRule)
|
|
289
|
+
return false;
|
|
290
|
+
if (!/[*:]$/.test(pattern))
|
|
291
|
+
return false;
|
|
292
|
+
return INTERPRETER_HEADS.test(pattern.replace(/[:*]+$/, '').trim());
|
|
293
|
+
}
|
|
261
294
|
/**
|
|
262
295
|
* Evaluate a tool call against merged rules. Returns the matching decision, or
|
|
263
296
|
* null when no rule applies (caller decides the default, usually "ask").
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ProjectCommand {
|
|
2
|
+
/** What the user types after `/run`: an npm script name, a make target, … */
|
|
3
|
+
name: string;
|
|
4
|
+
/** The full shell command. */
|
|
5
|
+
command: string;
|
|
6
|
+
/** The file it came from, shown in the picker and in errors. */
|
|
7
|
+
source: string;
|
|
8
|
+
}
|
|
9
|
+
/** The package manager whose scripts this project's lockfile asks for. */
|
|
10
|
+
export declare function packageManager(workDir: string): 'npm' | 'pnpm' | 'yarn' | 'bun';
|
|
11
|
+
/**
|
|
12
|
+
* Every command this folder can run, in a stable order (package.json first — the most
|
|
13
|
+
* common source — then the rest alphabetically). Missing files are simply absent: a
|
|
14
|
+
* project with no package.json is not an error, it is a Cargo project.
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectProjectCommands(workDir: string): ProjectCommand[];
|
|
17
|
+
/**
|
|
18
|
+
* The commands a shortcut should run: everything in the best tier it finds, best
|
|
19
|
+
* first. More than one means the caller asks which (a repo with `test` and
|
|
20
|
+
* `test:integration` should not silently pick one — running the wrong suite and
|
|
21
|
+
* calling it "the tests" is worse than one extra keystroke).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveShortcut<T extends {
|
|
24
|
+
name: string;
|
|
25
|
+
}>(cmds: T[], want: string): T[];
|
|
26
|
+
/**
|
|
27
|
+
* Runs `git` with the user's arguments: `/git` alone means `git status`, `/git log -5`
|
|
28
|
+
* means exactly that. Not a list of allowed subcommands — the read-only gate in front
|
|
29
|
+
* of this (core's isReadOnlyBash) is what decides whether it may run unprompted, so
|
|
30
|
+
* `/git commit` works in a normal session and is refused under `--readonly`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function gitCommand(arg: string): string;
|
|
33
|
+
//# sourceMappingURL=projectCommands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectCommands.d.ts","sourceRoot":"","sources":["../../src/project/projectCommands.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAkCD,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAM/E;AAsED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAiEvE;AAsBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,CAKxF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C"}
|