@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.
Files changed (2) hide show
  1. package/dist/bin.js +81 -27
  2. 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 digest(facts, { from = 0, maxChars = 6e4, toolResultChars = 600 } = {}) {
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
- ${truncate(t.text, maxChars)}`);
2147
+ ${bounded(text, maxChars)}`);
2127
2148
  else if (t.role === "assistant") parts.push(`[ASSISTANT]
2128
- ${truncate(t.text, maxChars)}`);
2149
+ ${bounded(text, maxChars)}`);
2129
2150
  else if (t.tool === "result") parts.push(`[TOOL RESULT${t.isError ? " \u2014 ERROR" : ""}]
2130
- ${truncate(t.text, toolResultChars)}`);
2131
- else parts.push(`[TOOL ${t.tool}] ${truncate(t.text, 400)}`);
2151
+ ${bounded(text, toolResultChars)}`);
2152
+ else parts.push(`[TOOL ${t.tool}] ${truncate(text, 400)}`);
2132
2153
  }
2133
- return redact(truncate(parts.join("\n\n"), maxChars));
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 remote = await mothershipRecall(config, {
2180
- task,
2181
- userId: config.userId,
2182
- role: config.role,
2183
- orgUnit: config.orgUnit,
2184
- limit: 8,
2185
- recentContext: history,
2186
- dryRun: opts.dryRun
2187
- }, remaining(8e3));
2188
- if (remote && (!remote.profile || remote.profile.orgId === config.orgId && remote.profile.userId === config.userId)) {
2189
- if (remote.profile) profile = remote.profile;
2190
- for (const lesson of remote.lessons) {
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 (remote.lessons.length) store.rebuildIndex();
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", { session: opts.sessionId, title: redact(raw.title), reason: rejection });
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 || evidence.some((quote) => {
2470
- if (typeof quote !== "string") return true;
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
- return !text || !visible.includes(text) || !results.some((result) => result.includes(text));
2473
- })) return "workflow requires matching verification evidence from successful tool results";
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunshin-cc/bunshin",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Shared memory for company AI agents. Connect Codex, Claude Code, or Pi to Bunshin.",
5
5
  "type": "module",
6
6
  "bin": {