bingocode 1.1.155 → 1.1.156
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/package.json
CHANGED
|
@@ -100,6 +100,39 @@ function writeGlobalClaudeConfig(updates: Record<string, unknown>): void {
|
|
|
100
100
|
fs.renameSync(tmp, configPath);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function readClaudeSettings(): Record<string, unknown> {
|
|
104
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
105
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
106
|
+
try {
|
|
107
|
+
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
108
|
+
return JSON.parse(raw) as Record<string, unknown>;
|
|
109
|
+
} catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function writeClaudeSettings(updates: Record<string, unknown>): void {
|
|
115
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
116
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
117
|
+
const dir = path.dirname(settingsPath);
|
|
118
|
+
if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); }
|
|
119
|
+
|
|
120
|
+
let current: Record<string, unknown> = {};
|
|
121
|
+
try {
|
|
122
|
+
if (fs.existsSync(settingsPath)) {
|
|
123
|
+
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
124
|
+
current = JSON.parse(raw) as Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
|
|
128
|
+
const merged = { ...current, ...updates };
|
|
129
|
+
|
|
130
|
+
// atomic write via temp + rename
|
|
131
|
+
const tmp = `${settingsPath}.tmp.${Date.now()}`;
|
|
132
|
+
fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
133
|
+
fs.renameSync(tmp, settingsPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
103
136
|
/**
|
|
104
137
|
* Determine if in "official" mode (no custom provider active).
|
|
105
138
|
* Logic matches ConversationService.shouldMarkManagedOAuth().
|
|
@@ -193,6 +226,9 @@ const i18nMap = {
|
|
|
193
226
|
autoModeLabel: 'Auto Mode',
|
|
194
227
|
autoModeOn: '已开启',
|
|
195
228
|
autoModeOff: '已关闭',
|
|
229
|
+
bypassPermsLabel: 'Bypass',
|
|
230
|
+
bypassPermsOn: '已开启',
|
|
231
|
+
bypassPermsOff: '已关闭',
|
|
196
232
|
},
|
|
197
233
|
en: {
|
|
198
234
|
menu: {
|
|
@@ -229,6 +265,9 @@ const i18nMap = {
|
|
|
229
265
|
autoModeLabel: 'Auto Mode',
|
|
230
266
|
autoModeOn: 'Enabled',
|
|
231
267
|
autoModeOff: 'Disabled',
|
|
268
|
+
bypassPermsLabel: 'Bypass',
|
|
269
|
+
bypassPermsOn: 'Enabled',
|
|
270
|
+
bypassPermsOff: 'Disabled',
|
|
232
271
|
},
|
|
233
272
|
ja: {
|
|
234
273
|
menu: {
|
|
@@ -265,6 +304,9 @@ const i18nMap = {
|
|
|
265
304
|
autoModeLabel: 'Auto Mode',
|
|
266
305
|
autoModeOn: '有効',
|
|
267
306
|
autoModeOff: '無効',
|
|
307
|
+
bypassPermsLabel: 'Bypass',
|
|
308
|
+
bypassPermsOn: '有効',
|
|
309
|
+
bypassPermsOff: '無効',
|
|
268
310
|
},
|
|
269
311
|
};
|
|
270
312
|
|
|
@@ -384,6 +426,9 @@ export const CliMenuManager: React.FC = () => {
|
|
|
384
426
|
if (typeof bSettings.autoModeEnabled === 'boolean') {
|
|
385
427
|
setAutoModeEnabled(bSettings.autoModeEnabled);
|
|
386
428
|
}
|
|
429
|
+
if (typeof bSettings.bypassPermsEnabled === 'boolean') {
|
|
430
|
+
setBypassPermsEnabled(bSettings.bypassPermsEnabled);
|
|
431
|
+
}
|
|
387
432
|
} catch {}
|
|
388
433
|
}, []);
|
|
389
434
|
|
|
@@ -442,6 +487,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
442
487
|
const [settingsStage, setSettingsStage] = useState<'list' | 'langPicker'>('list');
|
|
443
488
|
const [settingsCursor, setSettingsCursor] = useState(0);
|
|
444
489
|
const [autoModeEnabled, setAutoModeEnabled] = useState(false);
|
|
490
|
+
const [bypassPermsEnabled, setBypassPermsEnabled] = useState(false);
|
|
445
491
|
|
|
446
492
|
// Top toolbar state
|
|
447
493
|
const [animEnabled, setAnimEnabled] = useState(true);
|
|
@@ -829,7 +875,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
829
875
|
if (!showHelp && page === 'settings') {
|
|
830
876
|
if (settingsStage === 'list') {
|
|
831
877
|
// +1 for the fixed Language row prepended before settingData entries
|
|
832
|
-
const totalRows =
|
|
878
|
+
const totalRows = 3 + (settingData && typeof settingData === 'object' ? Object.keys(settingData).length : 0);
|
|
833
879
|
const visible = Math.max(1, MID_H - 2);
|
|
834
880
|
if (key.downArrow || input === 'j') {
|
|
835
881
|
setSettingsCursor(c => Math.min(totalRows - 1, c + 1));
|
|
@@ -862,12 +908,27 @@ export const CliMenuManager: React.FC = () => {
|
|
|
862
908
|
}
|
|
863
909
|
return next;
|
|
864
910
|
});
|
|
911
|
+
} else if (settingsCursor === 2) {
|
|
912
|
+
// Row 2: toggle Bypass Permissions
|
|
913
|
+
setBypassPermsEnabled(prev => {
|
|
914
|
+
const next = !prev;
|
|
915
|
+
try {
|
|
916
|
+
writeBingoSettings({ bypassPermsEnabled: next });
|
|
917
|
+
const safeSettings = next
|
|
918
|
+
? { permissions: { defaultMode: 'bypassPermissions', skipDangerousModePermissionPrompt: true } }
|
|
919
|
+
: { permissions: { defaultMode: 'default' } };
|
|
920
|
+
writeClaudeSettings(safeSettings);
|
|
921
|
+
} catch {
|
|
922
|
+
return prev; // write failed — keep old state
|
|
923
|
+
}
|
|
924
|
+
return next;
|
|
925
|
+
});
|
|
865
926
|
}
|
|
866
927
|
}
|
|
867
928
|
}
|
|
868
929
|
// langPicker stage: ESC handled above; selection via SelectInput onSelect
|
|
869
930
|
}
|
|
870
|
-
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme, settingsStage, settingsCursor, autoModeEnabled]);
|
|
931
|
+
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme, settingsStage, settingsCursor, autoModeEnabled, bypassPermsEnabled]);
|
|
871
932
|
|
|
872
933
|
function cleanText(text: string): string {
|
|
873
934
|
return String(text ?? '').replace(/[\n\r]+/g, ' ').replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
@@ -1350,6 +1411,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1350
1411
|
const fixedRows: SettingRow[] = [
|
|
1351
1412
|
{ key: '__lang', label: tS.langLabel, value: currentLangLabel, interactive: true },
|
|
1352
1413
|
{ key: '__autoMode', label: tS.autoModeLabel, value: autoModeEnabled ? tS.autoModeOn : tS.autoModeOff, interactive: true },
|
|
1414
|
+
{ key: '__bypassPerms', label: tS.bypassPermsLabel, value: bypassPermsEnabled ? tS.bypassPermsOn : tS.bypassPermsOff, interactive: true },
|
|
1353
1415
|
];
|
|
1354
1416
|
const dataEntries = settingData && typeof settingData === 'object' ? Object.entries(settingData) : [];
|
|
1355
1417
|
const dataRows: SettingRow[] = dataEntries.map(([k, v]) => ({
|
|
@@ -71,11 +71,14 @@ export function stripTrailingWhitespace(str: string): string {
|
|
|
71
71
|
* @returns The actual string found in the file, or null if not found
|
|
72
72
|
*/
|
|
73
73
|
|
|
74
|
-
/** Normalizes Unicode dashes
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
/** Normalizes Unicode dashes to ASCII, indent whitespace to spaces.
|
|
75
|
+
* Fills gaps where models emit ASCII dashes instead of Unicode dashes,
|
|
76
|
+
* or provide different tab/space indentation than the file has. */
|
|
77
77
|
export function normalizeDashes(str: string): string {
|
|
78
|
-
return str.replaceAll('
|
|
78
|
+
return str.replaceAll('\u2014', '-').replaceAll('\u2013', '-').replaceAll('\u2015', '-')
|
|
79
|
+
}
|
|
80
|
+
export function normalizeIndentation(str: string): string {
|
|
81
|
+
return str.split('\n').map(line => line.trimStart()).join('\n')
|
|
79
82
|
}
|
|
80
83
|
export function findActualString(
|
|
81
84
|
fileContent: string,
|
|
@@ -9,21 +9,61 @@ export type GoalEvalResult = {
|
|
|
9
9
|
gap: string | null
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// --- EVAL Block parser for structured evaluation ---
|
|
13
|
+
|
|
14
|
+
type EvalBlock = {
|
|
15
|
+
metric: string
|
|
16
|
+
valueTarget: string
|
|
17
|
+
passed: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse markdown text for structured statement > EVAL: lines.
|
|
22
|
+
*
|
|
23
|
+
* Expected actor format:
|
|
24
|
+
* > EVAL: <metric>: <value> / <target> → ✓ or ✗
|
|
25
|
+
*/
|
|
26
|
+
function parseEvalBlocks(text: string): EvalBlock[] {
|
|
27
|
+
const blocks: EvalBlock[] = []
|
|
28
|
+
const regex = />\s*EVAL:\s*(.+?):\s*(.+?)\s*→\s*(✓|✗)/g
|
|
29
|
+
let match
|
|
30
|
+
while ((match = regex.exec(text)) !== null) {
|
|
31
|
+
const [, metric, valueTarget, result] = match
|
|
32
|
+
const passed = result === '✓' || result === '✔'
|
|
33
|
+
blocks.push({ metric: metric.trim(), valueTarget: valueTarget.trim(), passed })
|
|
34
|
+
}
|
|
35
|
+
return blocks
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Determine if all metrics pass — enabling early termination. */
|
|
39
|
+
function allMetricsPassing(blocks: EvalBlock[]): boolean {
|
|
40
|
+
return blocks.length > 0 && blocks.every(b => b.passed)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Extract structured EVAL summary from parsed blocks for consumption by evaluator model. */
|
|
44
|
+
function evalSummary(blocks: EvalBlock[]): string {
|
|
45
|
+
if (blocks.length === 0) return '(no EVAL blocks found)'
|
|
46
|
+
const passed = blocks.filter(b => b.passed).length
|
|
47
|
+
return [
|
|
48
|
+
`Pre-parsed EVAL metrics (${passed}/${blocks.length} passed):`,
|
|
49
|
+
...blocks.map(b => `- ${b.metric}: ${b.valueTarget} → ${b.passed ? '✓' : '✗'}`),
|
|
50
|
+
].join('\n')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Core evaluator ---
|
|
54
|
+
|
|
12
55
|
/**
|
|
13
|
-
*
|
|
56
|
+
* Optimized goal evaluator.
|
|
14
57
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
58
|
+
* Strategy:
|
|
59
|
+
* 1. Regex-parse EVAL blocks from recent assistant text. If all metrics
|
|
60
|
+
* pass → short-circuit satisfied without calling evaluator model.
|
|
61
|
+
* 2. Feed pre-parsed EVAL summary to Haiku-4.5 for fallback evaluation.
|
|
17
62
|
*/
|
|
18
63
|
export async function evaluateGoal(
|
|
19
64
|
goalCondition: string,
|
|
20
65
|
messages: MessageType[],
|
|
21
66
|
): Promise<GoalEvalResult> {
|
|
22
|
-
const client = new Anthropic({
|
|
23
|
-
baseURL: process.env.ANTHROPIC_BASE_URL ?? undefined,
|
|
24
|
-
apiKey: process.env.ANTHROPIC_API_KEY ?? 'dummy',
|
|
25
|
-
})
|
|
26
|
-
|
|
27
67
|
const recentAssistantTexts = messages
|
|
28
68
|
.filter(m => m.type === 'assistant' || m.role === 'assistant')
|
|
29
69
|
.slice(-5)
|
|
@@ -40,22 +80,37 @@ export async function evaluateGoal(
|
|
|
40
80
|
.filter(Boolean)
|
|
41
81
|
.join('\n---\n')
|
|
42
82
|
|
|
43
|
-
|
|
83
|
+
// Phase 1: regex-parse EVAL blocks from recent output (fast, no model call)
|
|
84
|
+
const evalBlocks = parseEvalBlocks(recentAssistantTexts)
|
|
44
85
|
|
|
45
|
-
|
|
86
|
+
// If ALL named metrics pass, the agent itself confirms goal completion.
|
|
87
|
+
if (evalBlocks.length > 0 && allMetricsPassing(evalBlocks)) {
|
|
88
|
+
return {
|
|
89
|
+
satisfied: true,
|
|
90
|
+
reason: `all ${evalBlocks.length} EVAL metrics satisfied`,
|
|
91
|
+
gap: null,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Phase 2: Fallback to Haiku evaluator with pre-parsed summary
|
|
96
|
+
const evalInput = [
|
|
97
|
+
evalSummary(evalBlocks),
|
|
98
|
+
'(note: EVAL blocks already pre-parsed above — use to guide your evaluation)',
|
|
99
|
+
'',
|
|
100
|
+
recentAssistantTexts.slice(-4000), // trim long messages to fit context
|
|
101
|
+
].join('\n')
|
|
46
102
|
|
|
47
|
-
|
|
103
|
+
const client = new Anthropic({
|
|
104
|
+
baseURL: process.env.ANTHROPIC_BASE_URL ?? undefined,
|
|
105
|
+
apiKey: process.env.ANTHROPIC_API_KEY ?? 'dummy',
|
|
106
|
+
})
|
|
48
107
|
|
|
49
|
-
|
|
50
|
-
${recentAssistantTexts || '(none yet)'}
|
|
108
|
+
const prompt = `You are a goal completion evaluator. Determine if ${goalCondition} is fulfilled.
|
|
51
109
|
|
|
52
|
-
|
|
53
|
-
1. Did the agent produce a final EVAL block? If so, use those values directly.
|
|
54
|
-
2. If no EVAL blocks found, infer based on any explicit declarations (e.g. "✓", "100%", "fixed", "complete").
|
|
55
|
-
3. Output ONLY valid JSON — no explanation or markdown.
|
|
110
|
+
${evalInput.slice(0, 6000)}
|
|
56
111
|
|
|
57
|
-
|
|
58
|
-
{"satisfied": true|false, "reason": "<one sentence>", "gap": "<missing item or null>"}`
|
|
112
|
+
Evaluate and respond ONLY in valid JSON:
|
|
113
|
+
{"satisfied": true|false, "reason": "<one sentence>", "gap": "<specific missing item, or null if satisfied>"}`
|
|
59
114
|
|
|
60
115
|
let text = ''
|
|
61
116
|
try {
|
|
@@ -66,6 +121,7 @@ Respond in:
|
|
|
66
121
|
})
|
|
67
122
|
text = response.content.find((b: any) => b.type === 'text')?.text || ''
|
|
68
123
|
} catch (e) {
|
|
124
|
+
// Short-circuit on API error — parse what we can
|
|
69
125
|
return {
|
|
70
126
|
satisfied: false,
|
|
71
127
|
reason: 'Evaluator API error',
|
|
@@ -73,8 +129,8 @@ Respond in:
|
|
|
73
129
|
}
|
|
74
130
|
}
|
|
75
131
|
|
|
132
|
+
// Phase 3: Parse evaluator output back to JSON
|
|
76
133
|
try {
|
|
77
|
-
// Strip markdown code fences and find JSON object bounds
|
|
78
134
|
let cleaned = text
|
|
79
135
|
.replace(/```(?:json)?\s*/gi, '')
|
|
80
136
|
.replace(/```/g, '')
|