@bunshin-cc/bunshin 0.1.2 → 0.1.3
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/bin.js +81 -27
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -405,6 +405,7 @@ var init_store = __esm({
|
|
|
405
405
|
var sync_exports = {};
|
|
406
406
|
__export(sync_exports, {
|
|
407
407
|
canSync: () => canSync,
|
|
408
|
+
mothershipLookup: () => mothershipLookup,
|
|
408
409
|
mothershipRecall: () => mothershipRecall,
|
|
409
410
|
pendingConflictQuestion: () => pendingConflictQuestion,
|
|
410
411
|
resolveConflict: () => resolveConflict,
|
|
@@ -440,6 +441,14 @@ async function mothershipRecall(config, req, timeoutMs = 8e3) {
|
|
|
440
441
|
return null;
|
|
441
442
|
}
|
|
442
443
|
}
|
|
444
|
+
async function mothershipLookup(config, ids, timeoutMs = 8e3) {
|
|
445
|
+
if (!canSync(config) || !ids.length) return null;
|
|
446
|
+
try {
|
|
447
|
+
return await api(config, "POST", "/lessons/lookup", { ids }, timeoutMs);
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
443
452
|
async function runSync(store) {
|
|
444
453
|
const config = store.config();
|
|
445
454
|
const result = { pushedLessons: 0, pushedTraces: 0, pulledConflicts: 0, errors: [] };
|
|
@@ -650,6 +659,7 @@ async function askCodexCli(opts) {
|
|
|
650
659
|
const outFile = path4.join(os.tmpdir(), `bunshin-codex-${process.pid}-${Date.now()}.txt`);
|
|
651
660
|
const args = ["exec", "--skip-git-repo-check", "--ephemeral", "-s", "read-only", "-o", outFile];
|
|
652
661
|
if (opts.model) args.push("-m", opts.model);
|
|
662
|
+
if (opts.effort) args.push("-c", `model_reasoning_effort="${opts.effort}"`);
|
|
653
663
|
args.push("-");
|
|
654
664
|
const prompt = `${opts.system}${jsonOnlyInstruction(opts.schema)}
|
|
655
665
|
|
|
@@ -1977,10 +1987,11 @@ function redact(text) {
|
|
|
1977
1987
|
if (name === "email") {
|
|
1978
1988
|
return /^(?:no[._-]?reply|do[._-]?not[._-]?reply)@|@example\./i.test(match) ? match : "[REDACTED:email]";
|
|
1979
1989
|
}
|
|
1980
|
-
if (name === "bearer" || name === "assigned-secret") {
|
|
1990
|
+
if (name === "bearer" || name === "assigned-secret" || name === "json-secret") {
|
|
1981
1991
|
const key = String(groups[0] ?? "");
|
|
1982
1992
|
const value = String(groups[1] ?? "");
|
|
1983
1993
|
if (PLACEHOLDER_RE.test(value)) return match;
|
|
1994
|
+
if (name === "json-secret") return `${JSON.stringify(key)}:"[REDACTED:json-secret]"`;
|
|
1984
1995
|
return `${key}=[REDACTED:${name}]`;
|
|
1985
1996
|
}
|
|
1986
1997
|
return `[REDACTED:${name}]`;
|
|
@@ -1993,6 +2004,7 @@ var init_redact2 = __esm({
|
|
|
1993
2004
|
"src/redact.ts"() {
|
|
1994
2005
|
"use strict";
|
|
1995
2006
|
PATTERNS = [
|
|
2007
|
+
["bunshin-key", /\bbsk_[A-Za-z0-9]{8}_[A-Za-z0-9]{32}\b/g],
|
|
1996
2008
|
["aws-key", /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA)[0-9A-Z]{16}\b/g],
|
|
1997
2009
|
["github-pat", /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g],
|
|
1998
2010
|
["anthropic-key", /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g],
|
|
@@ -2007,6 +2019,7 @@ var init_redact2 = __esm({
|
|
|
2007
2019
|
"assigned-secret",
|
|
2008
2020
|
/\b([A-Za-z0-9_.-]*(?:secret|passwd|password|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|session[_-]?token)[A-Za-z0-9_.-]*)\s*[:=]\s*['"]?([^\s'"#,;]{6,})['"]?/gi
|
|
2009
2021
|
],
|
|
2022
|
+
["json-secret", /"([A-Za-z0-9_.-]*(?:secret|passwd|password|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|session[_-]?token)[A-Za-z0-9_.-]*)"\s*:\s*"([^"\r\n]{6,})"/gi],
|
|
2010
2023
|
["card", /\b(?:\d[ -]?){13,19}\b/g],
|
|
2011
2024
|
["email", /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g]
|
|
2012
2025
|
];
|
|
@@ -2119,18 +2132,26 @@ function summarizeInput(input) {
|
|
|
2119
2132
|
}
|
|
2120
2133
|
return JSON.stringify(obj);
|
|
2121
2134
|
}
|
|
2122
|
-
function
|
|
2135
|
+
function bounded(text, maxChars) {
|
|
2136
|
+
if (text.length <= maxChars) return text;
|
|
2137
|
+
const marker = "\n[... middle omitted ...]\n";
|
|
2138
|
+
if (maxChars <= marker.length) return text.slice(0, Math.max(0, maxChars));
|
|
2139
|
+
const head = Math.floor((maxChars - marker.length) / 2);
|
|
2140
|
+
return text.slice(0, head) + marker + text.slice(-(maxChars - marker.length - head));
|
|
2141
|
+
}
|
|
2142
|
+
function digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 6e3 } = {}) {
|
|
2123
2143
|
const parts = [];
|
|
2124
2144
|
for (const t of facts.turns.slice(from)) {
|
|
2145
|
+
const text = redact(t.text);
|
|
2125
2146
|
if (t.role === "user") parts.push(`[USER]
|
|
2126
|
-
${
|
|
2147
|
+
${bounded(text, maxChars)}`);
|
|
2127
2148
|
else if (t.role === "assistant") parts.push(`[ASSISTANT]
|
|
2128
|
-
${
|
|
2149
|
+
${bounded(text, maxChars)}`);
|
|
2129
2150
|
else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : ""}]
|
|
2130
|
-
${
|
|
2131
|
-
else parts.push(`[TOOL ${t.tool}] ${truncate(
|
|
2151
|
+
${bounded(text, toolResultChars)}`);
|
|
2152
|
+
else parts.push(`[TOOL ${t.tool}] ${truncate(text, 400)}`);
|
|
2132
2153
|
}
|
|
2133
|
-
return
|
|
2154
|
+
return bounded(parts.join("\n\n"), maxChars);
|
|
2134
2155
|
}
|
|
2135
2156
|
function recentContext(file, { turns = 2, maxChars = 6e3 } = {}) {
|
|
2136
2157
|
const facts = parseTranscript(file);
|
|
@@ -2176,27 +2197,53 @@ async function runRecall(store, opts) {
|
|
|
2176
2197
|
const rules = new Map(store.listRecallLessons().map((r) => [r.id, r]));
|
|
2177
2198
|
let degraded;
|
|
2178
2199
|
if (connected && !opts.offline) {
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2200
|
+
const cached = prepareRecall(local.map((lesson) => ({ lesson })), { task, ...profile }, profile).candidates.slice(0, 60).map((candidate) => candidate.lesson);
|
|
2201
|
+
const cachedIds = new Set(cached.map((lesson) => lesson.id));
|
|
2202
|
+
const [remote, lookup] = await Promise.all([
|
|
2203
|
+
mothershipRecall(config, {
|
|
2204
|
+
task,
|
|
2205
|
+
userId: config.userId,
|
|
2206
|
+
role: config.role,
|
|
2207
|
+
orgUnit: config.orgUnit,
|
|
2208
|
+
limit: 8,
|
|
2209
|
+
recentContext: history,
|
|
2210
|
+
dryRun: opts.dryRun
|
|
2211
|
+
}, remaining(8e3)),
|
|
2212
|
+
cached.length ? mothershipLookup(config, [...cachedIds], remaining(8e3)) : null
|
|
2213
|
+
]);
|
|
2214
|
+
const sameIdentity = (p) => p?.orgId === config.orgId && p.userId === config.userId;
|
|
2215
|
+
const remoteOK = remote && (!remote.profile || sameIdentity(remote.profile));
|
|
2216
|
+
const lookupOK = lookup && sameIdentity(lookup.profile);
|
|
2217
|
+
if (remoteOK || lookupOK) {
|
|
2218
|
+
profile = (remoteOK ? remote.profile : void 0) ?? (lookupOK ? lookup.profile : void 0) ?? profile;
|
|
2219
|
+
byId.clear();
|
|
2220
|
+
const refreshed = /* @__PURE__ */ new Map();
|
|
2221
|
+
if (lookupOK) {
|
|
2222
|
+
const unavailable = new Set(lookup.unavailableIds);
|
|
2223
|
+
for (const lesson of cached) {
|
|
2224
|
+
if (unavailable.has(lesson.id) && lesson.version === 1 && (!config.lastSyncAt || lesson.provenance.createdAt > config.lastSyncAt)) byId.set(lesson.id, lesson);
|
|
2225
|
+
}
|
|
2226
|
+
for (const lesson of lookup.lessons) {
|
|
2227
|
+
if (cachedIds.has(lesson.id) && lesson.provenance.orgId === profile.orgId) refreshed.set(lesson.id, lesson);
|
|
2228
|
+
}
|
|
2229
|
+
} else if (cached.length) {
|
|
2230
|
+
degraded = "local cache refresh unavailable; using current hosted knowledge";
|
|
2231
|
+
}
|
|
2232
|
+
for (const lesson of remoteOK ? remote.lessons : []) {
|
|
2191
2233
|
if (lesson.provenance.orgId !== profile.orgId) continue;
|
|
2234
|
+
const checked = refreshed.get(lesson.id);
|
|
2235
|
+
if (!checked || lesson.version > checked.version) refreshed.set(lesson.id, lesson);
|
|
2236
|
+
}
|
|
2237
|
+
for (const lesson of refreshed.values()) {
|
|
2192
2238
|
byId.set(lesson.id, lesson);
|
|
2193
2239
|
if (!opts.dryRun) store.saveLesson(lesson, { reindex: false });
|
|
2194
2240
|
}
|
|
2195
|
-
for (const rule of remote.recallLessons) rules.set(rule.id, rule);
|
|
2241
|
+
for (const rule of remoteOK ? remote.recallLessons : []) rules.set(rule.id, rule);
|
|
2196
2242
|
if (!opts.dryRun) {
|
|
2197
|
-
for (const conflict of remote.pendingConflicts) store.saveConflict(conflict);
|
|
2198
|
-
if (
|
|
2243
|
+
for (const conflict of remoteOK ? remote.pendingConflicts : []) store.saveConflict(conflict);
|
|
2244
|
+
if (refreshed.size) store.rebuildIndex();
|
|
2199
2245
|
}
|
|
2246
|
+
if (!remoteOK) degraded = "hosted search unavailable; using refreshed locally authored knowledge";
|
|
2200
2247
|
} else {
|
|
2201
2248
|
degraded = "mothership unavailable or identity mismatch; using locally authored knowledge";
|
|
2202
2249
|
}
|
|
@@ -2440,7 +2487,12 @@ async function reflectSession(store, opts) {
|
|
|
2440
2487
|
for (const raw of out.lessons) {
|
|
2441
2488
|
const rejection = workflowRejection(raw, facts, excerpt);
|
|
2442
2489
|
if (rejection) {
|
|
2443
|
-
store.log("reflect-workflow-rejected", {
|
|
2490
|
+
store.log("reflect-workflow-rejected", {
|
|
2491
|
+
session: opts.sessionId,
|
|
2492
|
+
title: redact(raw.title),
|
|
2493
|
+
reason: rejection,
|
|
2494
|
+
evidence: Array.isArray(raw.verificationEvidence) ? raw.verificationEvidence.filter((quote) => typeof quote === "string").map((quote) => redact(quote).slice(0, 1e3)) : []
|
|
2495
|
+
});
|
|
2444
2496
|
continue;
|
|
2445
2497
|
}
|
|
2446
2498
|
const lesson = store.saveLesson(buildLesson(raw, store, opts), { reindex: false });
|
|
@@ -2466,11 +2518,13 @@ function workflowRejection(raw, facts, excerpt) {
|
|
|
2466
2518
|
const visible = normalize(excerpt);
|
|
2467
2519
|
const results = facts.turns.filter((turn) => turn.role === "tool" && turn.tool === "result" && !turn.isError).map((turn) => normalize(turn.text));
|
|
2468
2520
|
const evidence = raw.verificationEvidence;
|
|
2469
|
-
if (!Array.isArray(evidence) || !evidence.length
|
|
2470
|
-
|
|
2521
|
+
if (!Array.isArray(evidence) || !evidence.length) return "workflow requires verification quotes from successful tool results";
|
|
2522
|
+
for (const [index, quote] of evidence.entries()) {
|
|
2523
|
+
if (typeof quote !== "string") return `workflow verification quote ${index + 1} is not text`;
|
|
2471
2524
|
const text = normalize(quote);
|
|
2472
|
-
|
|
2473
|
-
|
|
2525
|
+
if (!text || !results.some((result) => result.includes(text))) return `workflow verification quote ${index + 1} does not match a successful tool result`;
|
|
2526
|
+
if (!visible.includes(text)) return `workflow verification quote ${index + 1} is outside the visible excerpt`;
|
|
2527
|
+
}
|
|
2474
2528
|
}
|
|
2475
2529
|
function countFacts(turns) {
|
|
2476
2530
|
return {
|