@bunshin-cc/bunshin 0.8.0 → 0.9.0
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 +143 -57
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1127,6 +1127,23 @@ function scopePath(tree, source) {
|
|
|
1127
1127
|
function inBoundary(section, reader) {
|
|
1128
1128
|
return !isScopeLayer(section.layer) || reader.layers.includes(section.layer);
|
|
1129
1129
|
}
|
|
1130
|
+
function parentScope(tree, layer) {
|
|
1131
|
+
const k = layerKind(layer);
|
|
1132
|
+
if (k === "source") {
|
|
1133
|
+
const g = tree.sources[layerValue(layer)]?.group ?? null;
|
|
1134
|
+
return g && tree.groups.some((x) => x.id === g) ? groupLayer(g) : "org";
|
|
1135
|
+
}
|
|
1136
|
+
if (k === "group") {
|
|
1137
|
+
const g = tree.groups.find((x) => x.id === layerValue(layer));
|
|
1138
|
+
return g?.parent && tree.groups.some((x) => x.id === g.parent) ? groupLayer(g.parent) : "org";
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
function ancestorScopes(tree, layer) {
|
|
1143
|
+
const out = [];
|
|
1144
|
+
for (let cur = parentScope(tree, layer); cur && !out.includes(cur); cur = parentScope(tree, cur)) out.push(cur);
|
|
1145
|
+
return out;
|
|
1146
|
+
}
|
|
1130
1147
|
function sourcesIn(tree, layer) {
|
|
1131
1148
|
if (layer === "org") return Object.keys(tree.sources);
|
|
1132
1149
|
if (layerKind(layer) === "source") return [layerValue(layer)];
|
|
@@ -1172,13 +1189,11 @@ function layerValue(layer) {
|
|
|
1172
1189
|
const i = layer.indexOf(":");
|
|
1173
1190
|
return i < 0 ? "" : layer.slice(i + 1);
|
|
1174
1191
|
}
|
|
1192
|
+
function privateLayer(ctx) {
|
|
1193
|
+
return ctx.kind === "robot" ? `robot:${ctx.id}` : `user:${ctx.userId}`;
|
|
1194
|
+
}
|
|
1175
1195
|
function layersFor(ctx) {
|
|
1176
|
-
|
|
1177
|
-
return ["fleet", `arm:${ctx.robot.armModel}`, `mount:${ctx.robot.mount}`, `robot:${ctx.id}`];
|
|
1178
|
-
}
|
|
1179
|
-
const team = ctx.person?.team ?? "";
|
|
1180
|
-
const dept = department(team);
|
|
1181
|
-
return ["org", ...dept ? [`dept:${dept}`] : [], ...team ? [`team:${team}`] : [], `user:${ctx.userId}`];
|
|
1196
|
+
return ["org", privateLayer(ctx)];
|
|
1182
1197
|
}
|
|
1183
1198
|
function isLocalLayer(layer) {
|
|
1184
1199
|
const kind = layerKind(layer);
|
|
@@ -1191,6 +1206,7 @@ function withScope(layers, scope) {
|
|
|
1191
1206
|
return i < 0 ? [...layers, ...path28] : [...layers.slice(0, i), ...path28, ...layers.slice(i)];
|
|
1192
1207
|
}
|
|
1193
1208
|
function readerFor(ctx, ownerClearance, scope) {
|
|
1209
|
+
if (ctx.kind === "robot" && !scope?.source) scope = { ...scope, source: `robot:${ctx.id}` };
|
|
1194
1210
|
const source = scope?.source ? { source: scope.source } : {};
|
|
1195
1211
|
if (ctx.kind === "robot") {
|
|
1196
1212
|
const rank = (c) => c === "private" ? 0 : CLEARANCE_RANK[c];
|
|
@@ -1213,9 +1229,6 @@ function explainSectionAccess(section, reader) {
|
|
|
1213
1229
|
const kind = layerKind(section.layer);
|
|
1214
1230
|
if (kind === "user" && section.layer !== `user:${reader.userId}`) return { allowed: false, reason: "other-user" };
|
|
1215
1231
|
if (kind === "robot" && section.layer !== `robot:${reader.contextId}`) return { allowed: false, reason: "other-robot" };
|
|
1216
|
-
if ((kind === "arm" || kind === "mount") && reader.role === "robot" && !reader.layers.includes(section.layer)) {
|
|
1217
|
-
return { allowed: false, reason: "other-hardware" };
|
|
1218
|
-
}
|
|
1219
1232
|
const access = explainAccess(
|
|
1220
1233
|
{
|
|
1221
1234
|
clearance: section.clearance,
|
|
@@ -1236,42 +1249,25 @@ function scopeAffinity(section, reader) {
|
|
|
1236
1249
|
function baseAffinity(section, reader) {
|
|
1237
1250
|
const kind = layerKind(section.layer);
|
|
1238
1251
|
if (kind === "group" || kind === "source") {
|
|
1239
|
-
|
|
1240
|
-
if (i < 0) return 0.05;
|
|
1252
|
+
if (!reader.layers.includes(section.layer)) return 0.05;
|
|
1241
1253
|
const path28 = reader.layers.filter((l) => {
|
|
1242
1254
|
const k = layerKind(l);
|
|
1243
1255
|
return k === "group" || k === "source";
|
|
1244
1256
|
});
|
|
1245
1257
|
return 0.6 + 0.4 * ((path28.indexOf(section.layer) + 1) / path28.length);
|
|
1246
1258
|
}
|
|
1247
|
-
if (reader.layers.includes(section.layer)
|
|
1248
|
-
|
|
1249
|
-
return 0.5 + 0.5 * (order.indexOf(kind) / Math.max(1, order.length - 1));
|
|
1250
|
-
}
|
|
1251
|
-
if (kind === "team") {
|
|
1252
|
-
const team = layerValue(section.layer);
|
|
1253
|
-
if (department(team) === department(reader.team)) return 0.3;
|
|
1254
|
-
return 0.05;
|
|
1255
|
-
}
|
|
1256
|
-
if (kind === "dept") return 0.1;
|
|
1257
|
-
return 0.2;
|
|
1259
|
+
if (isLocalLayer(section.layer)) return reader.layers.includes(section.layer) ? 1 : 0;
|
|
1260
|
+
return 0.5;
|
|
1258
1261
|
}
|
|
1259
1262
|
function reachesReader(section, reader) {
|
|
1260
|
-
|
|
1261
|
-
const kind = layerKind(section.layer);
|
|
1262
|
-
if (kind === "org" || kind === "dept" || FLEET_LAYER_ORDER.includes(kind)) return canRead(section, reader);
|
|
1263
|
-
if (kind !== "team") return false;
|
|
1264
|
-
return [layerValue(section.layer), ...section.allowedTeams ?? []].some((t) => !!t && orgUnitUnder(reader.team, t));
|
|
1263
|
+
return reader.layers.includes(section.layer) && canRead(section, reader);
|
|
1265
1264
|
}
|
|
1266
|
-
var COMPANY_LAYER_ORDER, FLEET_LAYER_ORDER;
|
|
1267
1265
|
var init_layers = __esm({
|
|
1268
1266
|
"../core/src/v2/layers.ts"() {
|
|
1269
1267
|
"use strict";
|
|
1270
1268
|
init_permissions();
|
|
1271
1269
|
init_text();
|
|
1272
1270
|
init_scopes();
|
|
1273
|
-
COMPANY_LAYER_ORDER = ["org", "dept", "team", "user"];
|
|
1274
|
-
FLEET_LAYER_ORDER = ["fleet", "arm", "mount", "robot"];
|
|
1275
1271
|
}
|
|
1276
1272
|
});
|
|
1277
1273
|
|
|
@@ -1455,6 +1451,9 @@ var init_document = __esm({
|
|
|
1455
1451
|
});
|
|
1456
1452
|
|
|
1457
1453
|
// ../core/src/v2/sections/markdown.ts
|
|
1454
|
+
function repoLayer(repo) {
|
|
1455
|
+
return `source:${repo.includes("/") ? "repo" : "folder"}:${repo}`;
|
|
1456
|
+
}
|
|
1458
1457
|
function repoSectionId(repo, path28, anchor = "") {
|
|
1459
1458
|
return anchor ? `repo:${repo}/${path28}#${anchor}` : `repo:${repo}/${path28}`;
|
|
1460
1459
|
}
|
|
@@ -1487,7 +1486,7 @@ function parseRepoDoc(doc, opts) {
|
|
|
1487
1486
|
id: meta.id ? repoSectionId(doc.repo, doc.path, meta.id) : repoSectionId(doc.repo, doc.path),
|
|
1488
1487
|
orgId: opts.orgId,
|
|
1489
1488
|
kind,
|
|
1490
|
-
layer:
|
|
1489
|
+
layer: opts.layer ?? repoLayer(doc.repo),
|
|
1491
1490
|
title,
|
|
1492
1491
|
body: doc.text,
|
|
1493
1492
|
when,
|
|
@@ -2081,8 +2080,9 @@ var init_engine = __esm({
|
|
|
2081
2080
|
|
|
2082
2081
|
// ../core/src/v2/guidelines.ts
|
|
2083
2082
|
function reflectionGuidelines(sections, reader, opts = {}) {
|
|
2083
|
+
const covering = opts.layer ? /* @__PURE__ */ new Set(["org", opts.layer, ...opts.tree ? ancestorScopes(opts.tree, opts.layer) : []]) : void 0;
|
|
2084
2084
|
const pool = reader ? servable(sections, reader) : sections;
|
|
2085
|
-
return pool.filter((s) => s.delivery === "reflect" && s.status === "active" && (!reader || inBoundary(s, reader))).filter((s) => !
|
|
2085
|
+
return pool.filter((s) => s.delivery === "reflect" && s.status === "active" && (!reader || inBoundary(s, reader))).filter((s) => !covering || covering.has(s.layer)).sort((a, b) => generality(a.layer) - generality(b.layer) || a.id.localeCompare(b.id)).map((s) => ({ id: s.id, title: s.title, layer: s.layer, body: s.body }));
|
|
2086
2086
|
}
|
|
2087
2087
|
function guidelineRules(body) {
|
|
2088
2088
|
const at = body.search(/^## /m);
|
|
@@ -2120,7 +2120,7 @@ var init_guidelines = __esm({
|
|
|
2120
2120
|
init_engine();
|
|
2121
2121
|
init_scopes();
|
|
2122
2122
|
GUIDELINES_ANCHOR = "rules";
|
|
2123
|
-
generality = (l) => ({ org: 0,
|
|
2123
|
+
generality = (l) => ({ org: 0, group: 1, source: 2, robot: 3, user: 3 })[layerKind(l)] ?? 4;
|
|
2124
2124
|
}
|
|
2125
2125
|
});
|
|
2126
2126
|
|
|
@@ -2770,8 +2770,17 @@ A lesson is read by agents on other tasks, months from now. Write the rule, not
|
|
|
2770
2770
|
|
|
2771
2771
|
Every entry is a whole document (a skill, a spec, a policy). Prefer editing an existing document
|
|
2772
2772
|
over creating one, and aim the edit: "anchor" names the heading (from its headings list) the text
|
|
2773
|
-
belongs under, so it lands in the right part of the document.
|
|
2774
|
-
|
|
2773
|
+
belongs under, so it lands in the right part of the document.
|
|
2774
|
+
|
|
2775
|
+
Steering and approach lessons (a, e) are the exception: each is its own short lesson. Use op
|
|
2776
|
+
"create" with kind "rule": a title naming the behaviour, a "when" naming the situations it applies
|
|
2777
|
+
in (as the user would phrase a request there, even one that never mentions it), and a body of at
|
|
2778
|
+
most about 120 words: the rule, why, and when it was learned. Do not append such a lesson inside a
|
|
2779
|
+
long skill or procedure: a rule buried in a long document goes unfollowed even when the document is
|
|
2780
|
+
loaded, and the user ends up repeating it. If a short rule in the list below already says it, append
|
|
2781
|
+
to that rule instead. Traps, procedures and facts (b, c, d) still go into the document they belong to.
|
|
2782
|
+
|
|
2783
|
+
Library documents relevant to this session:
|
|
2775
2784
|
${index}
|
|
2776
2785
|
${input.skills?.length ? `
|
|
2777
2786
|
Skills in the library (target any of them by id, listed here or not):
|
|
@@ -2791,9 +2800,9 @@ A fact, command or habit of this project \u2192 this project. True of every proj
|
|
|
2791
2800
|
that group. True anywhere this workspace works \u2192 org. Lessons that come up again in other scopes
|
|
2792
2801
|
are widened by the nightly generalization pass, so a narrow scope is never a loss. Name it as
|
|
2793
2802
|
"layer" in the target of a new document (an edit keeps the document's own scope).
|
|
2794
|
-
` : ""}${input.profileId ? `- How THIS person works
|
|
2795
|
-
|
|
2796
|
-
|
|
2803
|
+
` : ""}${input.profileId ? `- How THIS person works in every task alike (tone, length, how they like to be asked) \u2192
|
|
2804
|
+
op "append" to their profile ${input.profileId}, anchor "how-i-work". A preference that applies in
|
|
2805
|
+
one kind of situation (how they want PRs, reports, deploys) is a short rule lesson in their layer.
|
|
2797
2806
|
` : ""}- Knowledge about one repo that its team owns \u2192 that repo's section (repo:... ids).
|
|
2798
2807
|
- Cross-repo facts, another team's rules, non-code processes \u2192 a Bunshin section at the right layer.
|
|
2799
2808
|
- If it only holds in some contexts, use op "append" with a "condition" (e.g. "objects over 50 g").
|
|
@@ -3129,6 +3138,17 @@ var init_generalize = __esm({
|
|
|
3129
3138
|
}
|
|
3130
3139
|
});
|
|
3131
3140
|
|
|
3141
|
+
// ../core/src/v2/adopt.ts
|
|
3142
|
+
var init_adopt = __esm({
|
|
3143
|
+
"../core/src/v2/adopt.ts"() {
|
|
3144
|
+
"use strict";
|
|
3145
|
+
init_text();
|
|
3146
|
+
init_layers();
|
|
3147
|
+
init_scopes();
|
|
3148
|
+
init_sources();
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
|
|
3132
3152
|
// ../core/src/v2/index.ts
|
|
3133
3153
|
var init_v2 = __esm({
|
|
3134
3154
|
"../core/src/v2/index.ts"() {
|
|
@@ -3153,6 +3173,7 @@ var init_v2 = __esm({
|
|
|
3153
3173
|
init_sources();
|
|
3154
3174
|
init_scopes();
|
|
3155
3175
|
init_generalize();
|
|
3176
|
+
init_adopt();
|
|
3156
3177
|
}
|
|
3157
3178
|
});
|
|
3158
3179
|
|
|
@@ -3329,7 +3350,8 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
|
3329
3350
|
const now = opts.now ?? Date.now();
|
|
3330
3351
|
const cached = v2.readRepoIndex(root);
|
|
3331
3352
|
const ownerTeam = config.ownerTeam || config.orgUnit || "unknown";
|
|
3332
|
-
const
|
|
3353
|
+
const source = sourceFor(root, v2.store.root);
|
|
3354
|
+
const sameRepo = cached && cached.ownerTeam === ownerTeam && cached.format === INDEX_FORMAT;
|
|
3333
3355
|
const repo = sameRepo && cached.repo && !opts.force ? cached.repo : repoName(root);
|
|
3334
3356
|
const fresh = sameRepo && cached.repo === repo && cached.paths && cached.listedAt && now - Date.parse(cached.listedAt) < LIST_TTL_MS && !opts.force;
|
|
3335
3357
|
const paths = fresh ? cached.paths : listDocs(root);
|
|
@@ -3352,11 +3374,12 @@ function indexWorkingTree(v2, config, cwd, opts = {}) {
|
|
|
3352
3374
|
changed = true;
|
|
3353
3375
|
try {
|
|
3354
3376
|
const text2 = fs6.readFileSync(abs, "utf8");
|
|
3355
|
-
files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam }) };
|
|
3377
|
+
files[rel] = { mtimeMs: stat.mtimeMs, size: stat.size, sections: parseRepoDoc({ repo, path: rel, text: text2 }, { orgId: config.orgId, ownerTeam, layer: source ? sourceLayer(source) : "org" }) };
|
|
3356
3378
|
} catch {
|
|
3357
3379
|
}
|
|
3358
3380
|
}
|
|
3359
3381
|
const index = {
|
|
3382
|
+
format: INDEX_FORMAT,
|
|
3360
3383
|
repo,
|
|
3361
3384
|
ownerTeam,
|
|
3362
3385
|
listedAt: fresh ? cached.listedAt : new Date(now).toISOString(),
|
|
@@ -3397,11 +3420,12 @@ function relativeToRoot(root, file, cwd = root) {
|
|
|
3397
3420
|
}
|
|
3398
3421
|
return null;
|
|
3399
3422
|
}
|
|
3400
|
-
var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc, deployedSkill;
|
|
3423
|
+
var DOC_NAMES, SKIP_DIRS, LIST_TTL_MS, rootCache, isDoc, deployedSkill, INDEX_FORMAT;
|
|
3401
3424
|
var init_repo = __esm({
|
|
3402
3425
|
"src/v2/repo.ts"() {
|
|
3403
3426
|
"use strict";
|
|
3404
3427
|
init_v22();
|
|
3428
|
+
init_source();
|
|
3405
3429
|
DOC_NAMES = /* @__PURE__ */ new Set(["spec.md", "testing.md", "skill.md"]);
|
|
3406
3430
|
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".bunshin"]);
|
|
3407
3431
|
LIST_TTL_MS = 6e4;
|
|
@@ -3412,6 +3436,7 @@ var init_repo = __esm({
|
|
|
3412
3436
|
return DOC_NAMES.has(parts[parts.length - 1].toLowerCase());
|
|
3413
3437
|
};
|
|
3414
3438
|
deployedSkill = (root, rel) => /(^|\/)SKILL\.md$/i.test(rel) && fs6.existsSync(path5.join(root, path5.posix.dirname(rel), ".bunshin.json"));
|
|
3439
|
+
INDEX_FORMAT = 2;
|
|
3415
3440
|
}
|
|
3416
3441
|
});
|
|
3417
3442
|
|
|
@@ -4381,6 +4406,11 @@ function childDir(root, name) {
|
|
|
4381
4406
|
const dir = path8.join(root, name);
|
|
4382
4407
|
return path8.dirname(path8.resolve(dir)) === path8.resolve(root) && insideRoot(root, dir) ? dir : null;
|
|
4383
4408
|
}
|
|
4409
|
+
function layerRank(layer, tree) {
|
|
4410
|
+
const kind = layerKind(layer);
|
|
4411
|
+
if (kind === "group") return 1 + (tree ? groupChain(tree, layerValue(layer)).length : 0);
|
|
4412
|
+
return kind === "source" ? 1e3 : kind === "user" || kind === "robot" ? 2e3 : 0;
|
|
4413
|
+
}
|
|
4384
4414
|
function planDeploy(bundle, p, harnesses) {
|
|
4385
4415
|
const active = bundle.sections.filter((s) => s.status === "active" || s.status === "stale");
|
|
4386
4416
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -4391,7 +4421,7 @@ function planDeploy(bundle, p, harnesses) {
|
|
|
4391
4421
|
continue;
|
|
4392
4422
|
}
|
|
4393
4423
|
const prev = byName.get(s.skill.name);
|
|
4394
|
-
if (!prev || (
|
|
4424
|
+
if (!prev || layerRank(s.layer, bundle.scopes) > layerRank(prev.layer, bundle.scopes)) byName.set(s.skill.name, s);
|
|
4395
4425
|
}
|
|
4396
4426
|
const skills = [...byName.values()].sort((a, b) => a.skill.name.localeCompare(b.skill.name));
|
|
4397
4427
|
const instructions = active.filter((s) => s.delivery === "always" && !s.skill && !s.profileOf).sort((a, b) => (a.packPath ?? a.title).localeCompare(b.packPath ?? b.title));
|
|
@@ -5013,7 +5043,7 @@ ${EXCLUDE_END}
|
|
|
5013
5043
|
} catch {
|
|
5014
5044
|
}
|
|
5015
5045
|
}
|
|
5016
|
-
var settingsFile, orgKey, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, safeJoin, SAFE_NAME, neutralizeMarkers,
|
|
5046
|
+
var settingsFile, orgKey, sha, BLOCK_BEGIN, BLOCK_END, MANIFEST, MAX_FILE, isExecutable, safeJoin, SAFE_NAME, neutralizeMarkers, docMarker, BORN_SLACK_MS, LINK_DIRS, EXCLUDE_BEGIN, EXCLUDE_END;
|
|
5017
5047
|
var init_deploy = __esm({
|
|
5018
5048
|
"src/v2/deploy.ts"() {
|
|
5019
5049
|
"use strict";
|
|
@@ -5038,7 +5068,6 @@ var init_deploy = __esm({
|
|
|
5038
5068
|
safeJoin = containedPath;
|
|
5039
5069
|
SAFE_NAME = /^(?!\.\.?$)[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
5040
5070
|
neutralizeMarkers = (text2) => text2.replace(/<!--(?=\s*bunshin:)/gi, "<!-\u200B-");
|
|
5041
|
-
LAYER_RANK = { org: 0, dept: 1, group: 2, team: 2, source: 3, user: 3, fleet: 0, arm: 1, mount: 2, robot: 3 };
|
|
5042
5071
|
docMarker = (id, version2) => `<!-- bunshin:doc id=${id} v=${version2} -->`;
|
|
5043
5072
|
BORN_SLACK_MS = 2e3;
|
|
5044
5073
|
LINK_DIRS = {
|
|
@@ -6129,7 +6158,8 @@ async function uploadIndex(store, cwd, deadline = Date.now() + 3e4) {
|
|
|
6129
6158
|
}
|
|
6130
6159
|
});
|
|
6131
6160
|
const commit = repoCommit(root);
|
|
6132
|
-
const
|
|
6161
|
+
const source = sourceFor(root, store.root);
|
|
6162
|
+
const res = await v2api(config, "POST", "/sections/index", { repo: index.repo, ...commit ? { commit } : {}, ...source ? { source } : {}, ownerTeam: index.ownerTeam, docs }, { deadline, timeoutMs: 3e4 });
|
|
6133
6163
|
return typeof res.indexed === "number" ? res.indexed : docs.length;
|
|
6134
6164
|
}
|
|
6135
6165
|
var isLocalId, carriesTrace;
|
|
@@ -6857,13 +6887,42 @@ function skillHint(sec, others) {
|
|
|
6857
6887
|
const also = others.length ? ` Also possibly relevant: ${others.map((t) => `"${t}"`).join(", ")}.` : "";
|
|
6858
6888
|
return `Bunshin: the \`${sec.skill.name}\` skill may apply: ${sec.skill.description.replace(/\s+/g, " ").trim()} If it does, invoke it (Skill tool, or \`/${sec.skill.name}\`) before doing this by hand.${also}`;
|
|
6859
6889
|
}
|
|
6860
|
-
|
|
6890
|
+
function lastReply(transcriptPath, maxBytes = 256e3) {
|
|
6891
|
+
if (!transcriptPath) return void 0;
|
|
6892
|
+
try {
|
|
6893
|
+
const fd = fs18.openSync(transcriptPath, "r");
|
|
6894
|
+
try {
|
|
6895
|
+
const size = fs18.fstatSync(fd).size;
|
|
6896
|
+
const len = Math.min(size, maxBytes);
|
|
6897
|
+
const buf = Buffer.alloc(len);
|
|
6898
|
+
fs18.readSync(fd, buf, 0, len, size - len);
|
|
6899
|
+
const lines = buf.toString("utf8").split("\n");
|
|
6900
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6901
|
+
let m;
|
|
6902
|
+
try {
|
|
6903
|
+
m = JSON.parse(lines[i]);
|
|
6904
|
+
} catch {
|
|
6905
|
+
continue;
|
|
6906
|
+
}
|
|
6907
|
+
if (m?.type !== "assistant" || m.isSidechain) continue;
|
|
6908
|
+
const c = m.message?.content;
|
|
6909
|
+
const text2 = Array.isArray(c) ? c.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join("\n") : typeof c === "string" ? c : "";
|
|
6910
|
+
if (text2.trim()) return redact2(text2.trim()).slice(0, 1500);
|
|
6911
|
+
}
|
|
6912
|
+
} finally {
|
|
6913
|
+
fs18.closeSync(fd);
|
|
6914
|
+
}
|
|
6915
|
+
} catch {
|
|
6916
|
+
}
|
|
6917
|
+
return void 0;
|
|
6918
|
+
}
|
|
6919
|
+
async function remoteAuto(ctx, prompt, exclude, source, context) {
|
|
6861
6920
|
const config = ctx.store.config();
|
|
6862
6921
|
if (!canCall(config) || !config.recall.useMothership) return null;
|
|
6863
6922
|
const budget = Math.min(2500, ctx.deadline - Date.now() - 600);
|
|
6864
6923
|
if (budget < 300) return null;
|
|
6865
6924
|
try {
|
|
6866
|
-
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude, ...source ? { source } : {} }, { timeoutMs: budget });
|
|
6925
|
+
const res = await v2api(config, "POST", "/recall/auto", { prompt: redact2(prompt).slice(0, 8e3), exclude, ...source ? { source } : {}, ...context ? { context } : {} }, { timeoutMs: budget });
|
|
6867
6926
|
return res.available ? res : null;
|
|
6868
6927
|
} catch {
|
|
6869
6928
|
return null;
|
|
@@ -6887,6 +6946,7 @@ async function onPrompt(ctx) {
|
|
|
6887
6946
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6888
6947
|
const session = touch(v2, ctx, (s) => {
|
|
6889
6948
|
if (!s.task && prompt.trim()) s.task = redact2(prompt.trim()).slice(0, 2e3);
|
|
6949
|
+
if (s.lastPrompt && prompt.trim()) s.recentPrompts = [...s.recentPrompts ?? [], s.lastPrompt.slice(0, 600)].slice(-3);
|
|
6890
6950
|
s.lastPrompt = redact2(prompt).slice(0, 4e3);
|
|
6891
6951
|
s.lastActivityAt = now;
|
|
6892
6952
|
});
|
|
@@ -6918,19 +6978,45 @@ async function onPrompt(ctx) {
|
|
|
6918
6978
|
return withLive(deployed || !fresh2 ? {} : additional("UserPromptSubmit", renderSection(named, why, { deployed: false })));
|
|
6919
6979
|
}
|
|
6920
6980
|
const res = auto(view.sections, view.reader, prompt, { exclude: injected });
|
|
6921
|
-
const
|
|
6981
|
+
const recent = session.recentPrompts ?? [];
|
|
6982
|
+
const context = {
|
|
6983
|
+
...recent.length && session.task ? { first: session.task.slice(0, 1e3) } : {},
|
|
6984
|
+
...recent.length ? { recent } : {}
|
|
6985
|
+
};
|
|
6986
|
+
const reply = recent.length ? lastReply(ctx.input.transcript_path ?? session.transcriptPath) : void 0;
|
|
6987
|
+
if (reply) context.reply = reply;
|
|
6988
|
+
const remote = await remoteAuto(ctx, prompt, injected, session.source, Object.keys(context).length ? context : void 0);
|
|
6989
|
+
const local = (id) => view.sections.find((s) => s.id === id);
|
|
6922
6990
|
if (remote) {
|
|
6923
|
-
const
|
|
6991
|
+
const why = remote.router ? remote.reason ?? `router ${remote.router}` : `dense ${remote.reason ?? ""}`.trim();
|
|
6924
6992
|
if (remote.section) {
|
|
6925
6993
|
res.section = local(remote.section.id) ?? remote.section;
|
|
6926
6994
|
res.anchor = void 0;
|
|
6927
|
-
res.reason =
|
|
6995
|
+
res.reason = why;
|
|
6928
6996
|
} else {
|
|
6929
6997
|
res.section = void 0;
|
|
6930
6998
|
res.hint = (remote.hint ?? []).map((h) => ({ id: h.id, title: local(h.id)?.title ?? h.title }));
|
|
6931
|
-
res.reason =
|
|
6999
|
+
res.reason = why;
|
|
6932
7000
|
}
|
|
6933
7001
|
}
|
|
7002
|
+
const rules = (remote?.rules ?? []).filter((r) => !injected.includes(r.id) && r.id !== res.section?.id).map((r) => local(r.id) ?? r);
|
|
7003
|
+
let lessons = "";
|
|
7004
|
+
if (rules.length) {
|
|
7005
|
+
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
7006
|
+
for (const r of rules) {
|
|
7007
|
+
addUnique(s.served, r.id);
|
|
7008
|
+
addUnique(s.loaded, r.id);
|
|
7009
|
+
markInContext(s, r.id);
|
|
7010
|
+
}
|
|
7011
|
+
});
|
|
7012
|
+
v2.log("v2-auto-rules", { session: session.sessionId, sections: rules.map((r) => r.id), reason: res.reason });
|
|
7013
|
+
updateFeed(v2, session.sessionId, (f) => {
|
|
7014
|
+
for (const r of [...rules].reverse()) addServed(f, { id: r.id, title: r.title, kind: r.kind, how: "auto", why: "a lesson learned before applies here" });
|
|
7015
|
+
addEvent(f, "served", `lessons: ${rules.map((r) => r.title).join(", ")}`);
|
|
7016
|
+
});
|
|
7017
|
+
lessons = rules.map((r) => renderSection(r, "a lesson learned before applies to this work", { deployed: false })).join("\n\n");
|
|
7018
|
+
}
|
|
7019
|
+
const withLessons = (text2) => withLive(additional("UserPromptSubmit", [text2, lessons].filter(Boolean).join("\n\n")));
|
|
6934
7020
|
if (res.section) {
|
|
6935
7021
|
const sec = res.section;
|
|
6936
7022
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
@@ -6944,10 +7030,10 @@ async function onPrompt(ctx) {
|
|
|
6944
7030
|
addServed(f, { id: sec.id, title: sec.title, kind: sec.kind, ...sec.skill ? { skill: sec.skill.name } : {}, how: "auto", why });
|
|
6945
7031
|
addEvent(f, "served", `served ${label({ ...sec.skill ? { skill: sec.skill.name } : {}, title: sec.title, kind: sec.kind })} \u2014 ${why}`);
|
|
6946
7032
|
});
|
|
6947
|
-
return
|
|
7033
|
+
return withLessons(renderSection(sec, "your prompt matches it", { ...res.anchor ? { anchor: res.anchor } : {}, deployed: !!sec.skill && deployEnabled(config, view.ctx) }));
|
|
6948
7034
|
}
|
|
6949
|
-
const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id)).slice(0, 3);
|
|
6950
|
-
if (!fresh.length) return
|
|
7035
|
+
const fresh = res.hint.filter((h) => !session.hinted?.includes(h.id) && !rules.some((r) => r.id === h.id)).slice(0, 3);
|
|
7036
|
+
if (!fresh.length) return withLessons("");
|
|
6951
7037
|
v2.updateSession(session.sessionId, ctx.cwd, (s) => {
|
|
6952
7038
|
s.hinted = s.hinted ?? [];
|
|
6953
7039
|
addUnique(s.hinted, ...fresh.map((h) => h.id));
|
|
@@ -6961,8 +7047,8 @@ async function onPrompt(ctx) {
|
|
|
6961
7047
|
addEvent(f, "hint", `possibly relevant: ${fresh.map((h) => h.title).join(", ")}`);
|
|
6962
7048
|
});
|
|
6963
7049
|
const topSkill = view.sections.find((s) => s.id === fresh[0].id && s.skill);
|
|
6964
|
-
if (topSkill && deployEnabled(config, view.ctx)) return
|
|
6965
|
-
return
|
|
7050
|
+
if (topSkill && deployEnabled(config, view.ctx)) return withLessons(skillHint(topSkill, fresh.slice(1).map((h) => h.title)));
|
|
7051
|
+
return withLessons(hintLine(fresh.map((h) => h.title), config.harness));
|
|
6966
7052
|
}
|
|
6967
7053
|
function toolPaths(input) {
|
|
6968
7054
|
if (!input || typeof input !== "object") return [];
|
|
@@ -8030,7 +8116,7 @@ function cmdSuggest(store, args, cwd, out, err) {
|
|
|
8030
8116
|
orgId: config.orgId,
|
|
8031
8117
|
contextId: view.ctx.id,
|
|
8032
8118
|
authorUserId: config.userId,
|
|
8033
|
-
target: known ? { sectionId: known } : {
|
|
8119
|
+
target: known ? { sectionId: known } : { storage: { type: "harness", path: rel, symbol: symbol ?? "" } },
|
|
8034
8120
|
op: { op: "append", text: rationale },
|
|
8035
8121
|
rationale,
|
|
8036
8122
|
evidenceEpisodeIds: sessionId ? [episodeIdFor(view.ctx.id, sessionId)] : [],
|