balladeer 1.0.16 → 1.0.17
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/cli.d.ts +1 -1
- package/dist/cli.js +19 -6
- package/dist/commands/guidance.d.ts +2 -1
- package/dist/commands/guidance.js +55 -0
- package/dist/commands/judge.d.ts +168 -9
- package/dist/commands/judge.js +594 -51
- package/dist/commands/map.d.ts +46 -2
- package/dist/commands/map.js +242 -5
- package/dist/commands/mcp.js +2 -2
- package/dist/guidance-hook.mjs +278 -94
- package/dist/hook-trust.d.ts +41 -9
- package/dist/hook-trust.js +98 -16
- package/dist/judge-brief.d.ts +15 -3
- package/dist/judge-brief.js +15 -2
- package/dist/judge-hook.d.ts +188 -14
- package/dist/judge-hook.js +919 -61
- package/dist/judge-said.d.ts +52 -0
- package/dist/judge-said.js +181 -0
- package/dist/promise-meaning.d.ts +8 -0
- package/dist/promise-meaning.js +9 -1
- package/dist/relay.d.ts +71 -0
- package/dist/relay.js +193 -0
- package/dist/remove-earlier.js +4 -2
- package/dist/risk/index.d.ts +1 -1
- package/dist/risk/index.js +1 -1
- package/dist/risk/score.d.ts +10 -0
- package/dist/risk/score.js +11 -2
- package/dist/scratch-worktree.d.ts +5 -1
- package/dist/scratch-worktree.js +9 -2
- package/dist/user-scope.d.ts +74 -3
- package/dist/user-scope.js +270 -9
- package/dist/wire.d.ts +3 -3
- package/dist/wire.js +2 -2
- package/package.json +1 -1
package/dist/guidance-hook.mjs
CHANGED
|
@@ -16,9 +16,9 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
16
16
|
};
|
|
17
17
|
var __copyProps = (to, from, except, desc) => {
|
|
18
18
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
-
for (let
|
|
20
|
-
if (!__hasOwnProp.call(to,
|
|
21
|
-
__defProp(to,
|
|
19
|
+
for (let key2 of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
21
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
22
22
|
}
|
|
23
23
|
return to;
|
|
24
24
|
};
|
|
@@ -366,9 +366,9 @@ var require_toml_parser = __commonJS({
|
|
|
366
366
|
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
367
367
|
var defineProperty = Object.defineProperty;
|
|
368
368
|
var descriptor = { configurable: true, enumerable: true, writable: true, value: void 0 };
|
|
369
|
-
function hasKey(obj,
|
|
370
|
-
if (hasOwnProperty.call(obj,
|
|
371
|
-
if (
|
|
369
|
+
function hasKey(obj, key2) {
|
|
370
|
+
if (hasOwnProperty.call(obj, key2)) return true;
|
|
371
|
+
if (key2 === "__proto__") defineProperty(obj, "__proto__", descriptor);
|
|
372
372
|
return false;
|
|
373
373
|
}
|
|
374
374
|
var INLINE_TABLE = Symbol("inline-table");
|
|
@@ -570,11 +570,11 @@ var require_toml_parser = __commonJS({
|
|
|
570
570
|
parseAssign() {
|
|
571
571
|
return this.callNow(this.parseKeyword, this.recordAssignKeyword);
|
|
572
572
|
}
|
|
573
|
-
recordAssignKeyword(
|
|
573
|
+
recordAssignKeyword(key2) {
|
|
574
574
|
if (this.state.resultTable) {
|
|
575
|
-
this.state.resultTable.push(
|
|
575
|
+
this.state.resultTable.push(key2);
|
|
576
576
|
} else {
|
|
577
|
-
this.state.resultTable = [
|
|
577
|
+
this.state.resultTable = [key2];
|
|
578
578
|
}
|
|
579
579
|
return this.goto(this.parseAssignKeywordPreDot);
|
|
580
580
|
}
|
|
@@ -1779,10 +1779,10 @@ var require_stringify = __commonJS({
|
|
|
1779
1779
|
return new Error("Array values can't have mixed types");
|
|
1780
1780
|
}
|
|
1781
1781
|
function getInlineKeys(obj) {
|
|
1782
|
-
return Object.keys(obj).filter((
|
|
1782
|
+
return Object.keys(obj).filter((key2) => isInline(obj[key2]));
|
|
1783
1783
|
}
|
|
1784
1784
|
function getComplexKeys(obj) {
|
|
1785
|
-
return Object.keys(obj).filter((
|
|
1785
|
+
return Object.keys(obj).filter((key2) => !isInline(obj[key2]));
|
|
1786
1786
|
}
|
|
1787
1787
|
function toJSON(obj) {
|
|
1788
1788
|
let nobj = Array.isArray(obj) ? [] : Object.prototype.hasOwnProperty.call(obj, "__proto__") ? { ["__proto__"]: void 0 } : {};
|
|
@@ -1803,16 +1803,16 @@ var require_stringify = __commonJS({
|
|
|
1803
1803
|
complexKeys = getComplexKeys(obj);
|
|
1804
1804
|
var result = [];
|
|
1805
1805
|
var inlineIndent = indent || "";
|
|
1806
|
-
inlineKeys.forEach((
|
|
1807
|
-
var type = tomlType2(obj[
|
|
1806
|
+
inlineKeys.forEach((key2) => {
|
|
1807
|
+
var type = tomlType2(obj[key2]);
|
|
1808
1808
|
if (type !== "undefined" && type !== "null") {
|
|
1809
|
-
result.push(inlineIndent + stringifyKey(
|
|
1809
|
+
result.push(inlineIndent + stringifyKey(key2) + " = " + stringifyAnyInline(obj[key2], true));
|
|
1810
1810
|
}
|
|
1811
1811
|
});
|
|
1812
1812
|
if (result.length > 0) result.push("");
|
|
1813
1813
|
var complexIndent = prefix && inlineKeys.length > 0 ? indent + " " : "";
|
|
1814
|
-
complexKeys.forEach((
|
|
1815
|
-
result.push(stringifyComplex(prefix, complexIndent,
|
|
1814
|
+
complexKeys.forEach((key2) => {
|
|
1815
|
+
result.push(stringifyComplex(prefix, complexIndent, key2, obj[key2]));
|
|
1816
1816
|
});
|
|
1817
1817
|
return result.join("\n");
|
|
1818
1818
|
}
|
|
@@ -1857,8 +1857,8 @@ var require_stringify = __commonJS({
|
|
|
1857
1857
|
return "table";
|
|
1858
1858
|
}
|
|
1859
1859
|
}
|
|
1860
|
-
function stringifyKey(
|
|
1861
|
-
var keyStr = String(
|
|
1860
|
+
function stringifyKey(key2) {
|
|
1861
|
+
var keyStr = String(key2);
|
|
1862
1862
|
if (/^[-A-Za-z0-9_]+$/.test(keyStr)) {
|
|
1863
1863
|
return keyStr;
|
|
1864
1864
|
} else {
|
|
@@ -1977,27 +1977,27 @@ var require_stringify = __commonJS({
|
|
|
1977
1977
|
function stringifyInlineTable(value) {
|
|
1978
1978
|
value = toJSON(value);
|
|
1979
1979
|
var result = [];
|
|
1980
|
-
Object.keys(value).forEach((
|
|
1981
|
-
result.push(stringifyKey(
|
|
1980
|
+
Object.keys(value).forEach((key2) => {
|
|
1981
|
+
result.push(stringifyKey(key2) + " = " + stringifyAnyInline(value[key2], false));
|
|
1982
1982
|
});
|
|
1983
1983
|
return "{ " + result.join(", ") + (result.length > 0 ? " " : "") + "}";
|
|
1984
1984
|
}
|
|
1985
|
-
function stringifyComplex(prefix, indent,
|
|
1985
|
+
function stringifyComplex(prefix, indent, key2, value) {
|
|
1986
1986
|
var valueType = tomlType2(value);
|
|
1987
1987
|
if (valueType === "array") {
|
|
1988
|
-
return stringifyArrayOfTables(prefix, indent,
|
|
1988
|
+
return stringifyArrayOfTables(prefix, indent, key2, value);
|
|
1989
1989
|
} else if (valueType === "table") {
|
|
1990
|
-
return stringifyComplexTable(prefix, indent,
|
|
1990
|
+
return stringifyComplexTable(prefix, indent, key2, value);
|
|
1991
1991
|
} else {
|
|
1992
1992
|
throw typeError(valueType);
|
|
1993
1993
|
}
|
|
1994
1994
|
}
|
|
1995
|
-
function stringifyArrayOfTables(prefix, indent,
|
|
1995
|
+
function stringifyArrayOfTables(prefix, indent, key2, values) {
|
|
1996
1996
|
values = toJSON(values);
|
|
1997
1997
|
validateArray(values);
|
|
1998
1998
|
var firstValueType = tomlType2(values[0]);
|
|
1999
1999
|
if (firstValueType !== "table") throw typeError(firstValueType);
|
|
2000
|
-
var fullKey = prefix + stringifyKey(
|
|
2000
|
+
var fullKey = prefix + stringifyKey(key2);
|
|
2001
2001
|
var result = "";
|
|
2002
2002
|
values.forEach((table) => {
|
|
2003
2003
|
if (result.length > 0) result += "\n";
|
|
@@ -2006,8 +2006,8 @@ var require_stringify = __commonJS({
|
|
|
2006
2006
|
});
|
|
2007
2007
|
return result;
|
|
2008
2008
|
}
|
|
2009
|
-
function stringifyComplexTable(prefix, indent,
|
|
2010
|
-
var fullKey = prefix + stringifyKey(
|
|
2009
|
+
function stringifyComplexTable(prefix, indent, key2, value) {
|
|
2010
|
+
var fullKey = prefix + stringifyKey(key2);
|
|
2011
2011
|
var result = "";
|
|
2012
2012
|
if (getInlineKeys(value).length > 0) {
|
|
2013
2013
|
result += indent + "[" + fullKey + "]\n";
|
|
@@ -2026,8 +2026,133 @@ var require_toml = __commonJS({
|
|
|
2026
2026
|
}
|
|
2027
2027
|
});
|
|
2028
2028
|
|
|
2029
|
+
// src/relay.ts
|
|
2030
|
+
import { createHash } from "node:crypto";
|
|
2031
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2032
|
+
import { join } from "node:path";
|
|
2033
|
+
var RELAY_FIELD = "forThePerson";
|
|
2034
|
+
var MAX_SENTENCE = 400;
|
|
2035
|
+
var MAX_SUBJECT = 200;
|
|
2036
|
+
var MAX_PENDING = 8;
|
|
2037
|
+
function clean(value, limit) {
|
|
2038
|
+
if (typeof value !== "string") return void 0;
|
|
2039
|
+
const text = value.replace(/[\u0000-\u001f\u007f]/g, " ").trim();
|
|
2040
|
+
return text ? text.slice(0, limit) : void 0;
|
|
2041
|
+
}
|
|
2042
|
+
function noticeFrom(value) {
|
|
2043
|
+
if (!value || typeof value !== "object") return void 0;
|
|
2044
|
+
const record = value;
|
|
2045
|
+
const sentence = clean(record.sentence, MAX_SENTENCE);
|
|
2046
|
+
if (!sentence) return void 0;
|
|
2047
|
+
const subjects = (Array.isArray(record.subjects) ? record.subjects : []).map((s) => clean(s, MAX_SUBJECT)).filter((s) => s !== void 0).slice(0, 4);
|
|
2048
|
+
return { sentence, subjects };
|
|
2049
|
+
}
|
|
2050
|
+
function noticeFromToolResponse(response) {
|
|
2051
|
+
const visit = (value, depth) => {
|
|
2052
|
+
if (depth > 4 || value === null || value === void 0) return void 0;
|
|
2053
|
+
if (typeof value === "string") {
|
|
2054
|
+
if (!value.includes(RELAY_FIELD)) return void 0;
|
|
2055
|
+
try {
|
|
2056
|
+
return visit(JSON.parse(value), depth + 1);
|
|
2057
|
+
} catch {
|
|
2058
|
+
return void 0;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (Array.isArray(value)) {
|
|
2062
|
+
for (const item of value) {
|
|
2063
|
+
const found = visit(item, depth + 1);
|
|
2064
|
+
if (found) return found;
|
|
2065
|
+
}
|
|
2066
|
+
return void 0;
|
|
2067
|
+
}
|
|
2068
|
+
if (typeof value !== "object") return void 0;
|
|
2069
|
+
const record = value;
|
|
2070
|
+
if (RELAY_FIELD in record) return noticeFrom(record[RELAY_FIELD]);
|
|
2071
|
+
for (const key2 of ["structuredContent", "content", "text", "result", "tool_response"]) {
|
|
2072
|
+
const found = visit(record[key2], depth + 1);
|
|
2073
|
+
if (found) return found;
|
|
2074
|
+
}
|
|
2075
|
+
return void 0;
|
|
2076
|
+
};
|
|
2077
|
+
return visit(response, 0);
|
|
2078
|
+
}
|
|
2079
|
+
function relayNote(notice2) {
|
|
2080
|
+
return `Balladeer just acted on the person's behalf: ${notice2.sentence} Make sure your reply tells them this, in your own words and in about one line.`;
|
|
2081
|
+
}
|
|
2082
|
+
var WORD = /[a-z0-9]+/g;
|
|
2083
|
+
var QUIET = new Set(
|
|
2084
|
+
"a an the and or of to in on for with is are be by as at from that this it its not no".split(" ")
|
|
2085
|
+
);
|
|
2086
|
+
function words(text) {
|
|
2087
|
+
return (text.toLowerCase().match(WORD) ?? []).filter((w) => w.length > 2 && !QUIET.has(w));
|
|
2088
|
+
}
|
|
2089
|
+
function mentions(reply, subject) {
|
|
2090
|
+
const wanted = [...new Set(words(subject))];
|
|
2091
|
+
if (!wanted.length) return true;
|
|
2092
|
+
const said = new Set(words(reply));
|
|
2093
|
+
const found = wanted.filter((w) => said.has(w) || said.has(w.replace(/s$/, ""))).length;
|
|
2094
|
+
return found / wanted.length >= 0.5;
|
|
2095
|
+
}
|
|
2096
|
+
function relayed(reply, notice2) {
|
|
2097
|
+
return notice2.subjects.length === 0 || notice2.subjects.some((s) => mentions(reply, s));
|
|
2098
|
+
}
|
|
2099
|
+
function reminder(notices) {
|
|
2100
|
+
const told = notices.map((n) => n.sentence).join(" ");
|
|
2101
|
+
return `Your reply does not yet tell the person what Balladeer did: ${told} Add it in your own words, in about one line.`;
|
|
2102
|
+
}
|
|
2103
|
+
function pendingPath(directory, sessionId) {
|
|
2104
|
+
return join(directory, `${createHash("sha256").update(sessionId).digest("hex")}.relay.json`);
|
|
2105
|
+
}
|
|
2106
|
+
function readPending(directory, sessionId) {
|
|
2107
|
+
try {
|
|
2108
|
+
const parsed = JSON.parse(readFileSync(pendingPath(directory, sessionId), "utf8"));
|
|
2109
|
+
return parsed.schemaVersion === 1 && Array.isArray(parsed.notices) ? parsed.notices.map(noticeFrom).filter((n) => n !== void 0) : [];
|
|
2110
|
+
} catch {
|
|
2111
|
+
return [];
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function writePending(directory, sessionId, notices) {
|
|
2115
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2116
|
+
const path = pendingPath(directory, sessionId);
|
|
2117
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
2118
|
+
const body = { schemaVersion: 1, notices: notices.slice(-MAX_PENDING) };
|
|
2119
|
+
writeFileSync(temporary, JSON.stringify(body), { mode: 384 });
|
|
2120
|
+
renameSync(temporary, path);
|
|
2121
|
+
}
|
|
2122
|
+
function isBalladeerTool(name) {
|
|
2123
|
+
return typeof name === "string" && /^mcp__balladeer[a-z0-9_-]*__/i.test(name);
|
|
2124
|
+
}
|
|
2125
|
+
function relayHook(input, directory) {
|
|
2126
|
+
try {
|
|
2127
|
+
const sessionId = typeof input.session_id === "string" ? input.session_id : void 0;
|
|
2128
|
+
if (input.hook_event_name === "PostToolUse") {
|
|
2129
|
+
if (!isBalladeerTool(input.tool_name)) return void 0;
|
|
2130
|
+
const notice2 = noticeFromToolResponse(input.tool_response ?? input.tool_output);
|
|
2131
|
+
if (!notice2) return void 0;
|
|
2132
|
+
if (sessionId && directory)
|
|
2133
|
+
writePending(directory, sessionId, [...readPending(directory, sessionId), notice2]);
|
|
2134
|
+
return JSON.stringify({
|
|
2135
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: relayNote(notice2) }
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
if (input.hook_event_name === "Stop") {
|
|
2139
|
+
if (!sessionId || !directory) return void 0;
|
|
2140
|
+
const pending = readPending(directory, sessionId);
|
|
2141
|
+
if (!pending.length) return void 0;
|
|
2142
|
+
const reply = typeof input.last_assistant_message === "string" ? input.last_assistant_message : "";
|
|
2143
|
+
const missing = pending.filter((n) => !relayed(reply, n));
|
|
2144
|
+
writePending(directory, sessionId, []);
|
|
2145
|
+
if (!missing.length || input.stop_hook_active === true) return void 0;
|
|
2146
|
+
return JSON.stringify({ decision: "block", reason: reminder(missing) });
|
|
2147
|
+
}
|
|
2148
|
+
return void 0;
|
|
2149
|
+
} catch {
|
|
2150
|
+
return void 0;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2029
2154
|
// src/wire.ts
|
|
2030
|
-
var CLI_VERSION = "1.0.
|
|
2155
|
+
var CLI_VERSION = "1.0.17";
|
|
2031
2156
|
var CLIENT_HEADER = "x-balladeer-client";
|
|
2032
2157
|
var UPDATE_STATE_HEADER = "x-balladeer-update-state";
|
|
2033
2158
|
var HOOK_HOST_HEADER = "x-balladeer-hook";
|
|
@@ -2040,15 +2165,15 @@ import {
|
|
|
2040
2165
|
fchmodSync,
|
|
2041
2166
|
fsyncSync,
|
|
2042
2167
|
lstatSync,
|
|
2043
|
-
mkdirSync,
|
|
2168
|
+
mkdirSync as mkdirSync2,
|
|
2044
2169
|
openSync,
|
|
2045
|
-
readFileSync,
|
|
2046
|
-
renameSync,
|
|
2170
|
+
readFileSync as readFileSync2,
|
|
2171
|
+
renameSync as renameSync2,
|
|
2047
2172
|
unlinkSync,
|
|
2048
2173
|
writeSync
|
|
2049
2174
|
} from "node:fs";
|
|
2050
2175
|
import { homedir } from "node:os";
|
|
2051
|
-
import { dirname, join, parse, resolve } from "node:path";
|
|
2176
|
+
import { dirname, join as join2, parse, resolve } from "node:path";
|
|
2052
2177
|
var StoreError = class extends Error {
|
|
2053
2178
|
code;
|
|
2054
2179
|
constructor(code, message, options) {
|
|
@@ -2062,11 +2187,11 @@ function configHome(environment = process.env) {
|
|
|
2062
2187
|
const explicit = environment.BALLADEER_CONFIG_HOME?.trim();
|
|
2063
2188
|
if (explicit) return resolve(explicit);
|
|
2064
2189
|
const xdg = environment.XDG_CONFIG_HOME?.trim();
|
|
2065
|
-
if (xdg) return resolve(
|
|
2066
|
-
return resolve(
|
|
2190
|
+
if (xdg) return resolve(join2(xdg, "balladeer"));
|
|
2191
|
+
return resolve(join2(homedir(), ".config", "balladeer"));
|
|
2067
2192
|
}
|
|
2068
2193
|
function credentialsPath(environment = process.env) {
|
|
2069
|
-
return
|
|
2194
|
+
return join2(configHome(environment), "credentials.json");
|
|
2070
2195
|
}
|
|
2071
2196
|
function assertPrivate(path, kind) {
|
|
2072
2197
|
const stats = lstatSync(path);
|
|
@@ -2098,7 +2223,7 @@ function readCredentials(environment = process.env) {
|
|
|
2098
2223
|
let raw;
|
|
2099
2224
|
try {
|
|
2100
2225
|
assertPrivate(path, "file");
|
|
2101
|
-
raw =
|
|
2226
|
+
raw = readFileSync2(path, "utf8");
|
|
2102
2227
|
} catch (error) {
|
|
2103
2228
|
if (error?.code === "ENOENT") {
|
|
2104
2229
|
return { ...EMPTY, pendingPairings: [], sessions: [], agents: [] };
|
|
@@ -2154,8 +2279,8 @@ function newerVersionPublished() {
|
|
|
2154
2279
|
|
|
2155
2280
|
// src/self-update.ts
|
|
2156
2281
|
import { spawn } from "node:child_process";
|
|
2157
|
-
import { existsSync, mkdirSync as
|
|
2158
|
-
import { delimiter, dirname as dirname2, join as
|
|
2282
|
+
import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2283
|
+
import { delimiter, dirname as dirname2, join as join3 } from "node:path";
|
|
2159
2284
|
|
|
2160
2285
|
// src/release.ts
|
|
2161
2286
|
var HOOK_SPECIFIER = "balladeer@1";
|
|
@@ -2167,24 +2292,24 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
|
2167
2292
|
var SELF_UPDATE_ENVIRONMENT = "BALLADEER_SELF_UPDATE";
|
|
2168
2293
|
var STATE_PATTERN = /^(?:started|unstartable|installed|failed):[A-Za-z0-9._-]{1,40}$/;
|
|
2169
2294
|
function stampPath(environment) {
|
|
2170
|
-
return
|
|
2295
|
+
return join3(configHome(environment), STAMP);
|
|
2171
2296
|
}
|
|
2172
2297
|
function statePath(environment) {
|
|
2173
|
-
return
|
|
2298
|
+
return join3(configHome(environment), STATE);
|
|
2174
2299
|
}
|
|
2175
2300
|
function lastStarted(environment) {
|
|
2176
2301
|
const path = stampPath(environment);
|
|
2177
2302
|
if (!existsSync(path)) return 0;
|
|
2178
2303
|
try {
|
|
2179
|
-
const parsed = JSON.parse(
|
|
2304
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
2180
2305
|
return typeof parsed.startedAt === "number" ? parsed.startedAt : 0;
|
|
2181
2306
|
} catch {
|
|
2182
2307
|
return 0;
|
|
2183
2308
|
}
|
|
2184
2309
|
}
|
|
2185
2310
|
function writePrivate(path, value, environment) {
|
|
2186
|
-
|
|
2187
|
-
|
|
2311
|
+
mkdirSync3(configHome(environment), { recursive: true, mode: 448 });
|
|
2312
|
+
writeFileSync2(path, JSON.stringify(value) + "\n", { mode: 384 });
|
|
2188
2313
|
}
|
|
2189
2314
|
function recordSelfUpdateState(environment, state, at) {
|
|
2190
2315
|
if (!STATE_PATTERN.test(state)) return;
|
|
@@ -2195,7 +2320,7 @@ function recordSelfUpdateState(environment, state, at) {
|
|
|
2195
2320
|
}
|
|
2196
2321
|
function selfUpdateState(environment) {
|
|
2197
2322
|
try {
|
|
2198
|
-
const parsed = JSON.parse(
|
|
2323
|
+
const parsed = JSON.parse(readFileSync3(statePath(environment), "utf8"));
|
|
2199
2324
|
return typeof parsed.state === "string" && STATE_PATTERN.test(parsed.state) ? parsed.state : void 0;
|
|
2200
2325
|
} catch {
|
|
2201
2326
|
return void 0;
|
|
@@ -2209,9 +2334,9 @@ function resolveLaunch(specifier, environment, execPath = process.execPath, exis
|
|
|
2209
2334
|
PATH: [bin, environment.PATH ?? ""].filter((part) => part.length > 0).join(delimiter),
|
|
2210
2335
|
[SELF_UPDATE_ENVIRONMENT]: "1"
|
|
2211
2336
|
};
|
|
2212
|
-
const script =
|
|
2337
|
+
const script = join3(bin, "..", "lib", "node_modules", "npm", "bin", "npx-cli.js");
|
|
2213
2338
|
if (exists(script)) return { command: execPath, args: [script, ...tail], environment: child };
|
|
2214
|
-
const shim =
|
|
2339
|
+
const shim = join3(bin, process.platform === "win32" ? "npx.cmd" : "npx");
|
|
2215
2340
|
if (exists(shim)) return { command: shim, args: tail, environment: child };
|
|
2216
2341
|
return { command: "npx", args: tail, environment: child };
|
|
2217
2342
|
}
|
|
@@ -2249,25 +2374,25 @@ function startSelfUpdateIfDue(newer, deps) {
|
|
|
2249
2374
|
}
|
|
2250
2375
|
|
|
2251
2376
|
// src/guidance.ts
|
|
2252
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2377
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
2253
2378
|
import {
|
|
2254
2379
|
constants,
|
|
2255
2380
|
closeSync as closeSync2,
|
|
2256
2381
|
fstatSync,
|
|
2257
2382
|
lstatSync as lstatSync2,
|
|
2258
|
-
mkdirSync as
|
|
2383
|
+
mkdirSync as mkdirSync4,
|
|
2259
2384
|
openSync as openSync2,
|
|
2260
|
-
readFileSync as
|
|
2261
|
-
renameSync as
|
|
2385
|
+
readFileSync as readFileSync4,
|
|
2386
|
+
renameSync as renameSync3,
|
|
2262
2387
|
unlinkSync as unlinkSync2,
|
|
2263
|
-
writeFileSync as
|
|
2388
|
+
writeFileSync as writeFileSync3
|
|
2264
2389
|
} from "node:fs";
|
|
2265
|
-
import { join as
|
|
2390
|
+
import { join as join4 } from "node:path";
|
|
2266
2391
|
var GUIDANCE_UNAVAILABLE = "Balladeer could not verify the current workspace guidance and capture mode. Continue the user's authorized work, but do not make unsolicited capture offers or file inferred promises. Do not reuse earlier Quiet/Thorough permission or cached instructions as current. An explicit request to record still requires current Balladeer tool checks and named-human agreement to meaning; never invent approval. Retry current guidance at the next hook boundary.";
|
|
2267
2392
|
var HEX = /^[a-f0-9]{64}$/;
|
|
2268
2393
|
var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
2269
2394
|
var MAX_BYTES = 4e4;
|
|
2270
|
-
var sha = (value) =>
|
|
2395
|
+
var sha = (value) => createHash2("sha256").update(value).digest("hex");
|
|
2271
2396
|
function object(value) {
|
|
2272
2397
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2273
2398
|
}
|
|
@@ -2282,13 +2407,13 @@ function guidanceEndpoint(agent) {
|
|
|
2282
2407
|
const expected = new URL(agent.controlPlane);
|
|
2283
2408
|
if (url.origin !== expected.origin || url.protocol !== "https:" || url.pathname !== "/api/mcp" || url.username || url.password || url.hash || !UUID.test(agent.workspaceId) || !UUID.test(agent.repositoryId))
|
|
2284
2409
|
throw new Error("invalid_guidance_scope");
|
|
2285
|
-
for (const [
|
|
2410
|
+
for (const [key2, value] of [
|
|
2286
2411
|
["workspaceId", agent.workspaceId],
|
|
2287
2412
|
["repositoryId", agent.repositoryId]
|
|
2288
2413
|
]) {
|
|
2289
|
-
if (url.searchParams.getAll(
|
|
2414
|
+
if (url.searchParams.getAll(key2).length > 1 || url.searchParams.has(key2) && url.searchParams.get(key2) !== value)
|
|
2290
2415
|
throw new Error("invalid_guidance_scope");
|
|
2291
|
-
url.searchParams.set(
|
|
2416
|
+
url.searchParams.set(key2, value);
|
|
2292
2417
|
}
|
|
2293
2418
|
url.pathname = "/api/agent-guidance";
|
|
2294
2419
|
return url;
|
|
@@ -2299,7 +2424,7 @@ function readJson(path) {
|
|
|
2299
2424
|
fd = openSync2(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
2300
2425
|
const stat = fstatSync(fd);
|
|
2301
2426
|
if (!stat.isFile() || stat.size > MAX_BYTES || (stat.mode & 63) !== 0) return void 0;
|
|
2302
|
-
return JSON.parse(
|
|
2427
|
+
return JSON.parse(readFileSync4(fd, "utf8"));
|
|
2303
2428
|
} catch {
|
|
2304
2429
|
return void 0;
|
|
2305
2430
|
} finally {
|
|
@@ -2308,7 +2433,7 @@ function readJson(path) {
|
|
|
2308
2433
|
}
|
|
2309
2434
|
function privateDirectory(path) {
|
|
2310
2435
|
try {
|
|
2311
|
-
|
|
2436
|
+
mkdirSync4(path, { mode: 448 });
|
|
2312
2437
|
} catch {
|
|
2313
2438
|
}
|
|
2314
2439
|
const stat = lstatSync2(path);
|
|
@@ -2318,9 +2443,9 @@ function privateDirectory(path) {
|
|
|
2318
2443
|
function atomicJson(path, value) {
|
|
2319
2444
|
const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
2320
2445
|
try {
|
|
2321
|
-
|
|
2446
|
+
writeFileSync3(temporary, `${JSON.stringify(value)}
|
|
2322
2447
|
`, { flag: "wx", mode: 384 });
|
|
2323
|
-
|
|
2448
|
+
renameSync3(temporary, path);
|
|
2324
2449
|
} finally {
|
|
2325
2450
|
try {
|
|
2326
2451
|
unlinkSync2(temporary);
|
|
@@ -2342,16 +2467,16 @@ function guidanceScopeKey(agent) {
|
|
|
2342
2467
|
function cacheDirectory(environment) {
|
|
2343
2468
|
const home = configHome(environment);
|
|
2344
2469
|
privateDirectory(home);
|
|
2345
|
-
const directory =
|
|
2470
|
+
const directory = join4(home, "guidance-v1");
|
|
2346
2471
|
privateDirectory(directory);
|
|
2347
2472
|
return directory;
|
|
2348
2473
|
}
|
|
2349
2474
|
function cached(directory, scopeKey, agent) {
|
|
2350
2475
|
try {
|
|
2351
|
-
const pointer = readJson(
|
|
2476
|
+
const pointer = readJson(join4(directory, `${scopeKey}.current.json`));
|
|
2352
2477
|
if (!object(pointer) || typeof pointer.key !== "string" || !HEX.test(pointer.key))
|
|
2353
2478
|
return void 0;
|
|
2354
|
-
const body = readJson(
|
|
2479
|
+
const body = readJson(join4(directory, `${pointer.key}.body.json`));
|
|
2355
2480
|
if (!object(body) || typeof body.etag !== "string" || !/^"[a-f0-9]{64}"$/.test(body.etag))
|
|
2356
2481
|
return void 0;
|
|
2357
2482
|
const document = parseGuidance(body.document, agent);
|
|
@@ -2427,12 +2552,12 @@ async function loadGuidance(input) {
|
|
|
2427
2552
|
if (abort.signal.aborted) throw new Error("guidance_deadline");
|
|
2428
2553
|
if (directory) {
|
|
2429
2554
|
try {
|
|
2430
|
-
const
|
|
2431
|
-
atomicJson(
|
|
2432
|
-
atomicJson(
|
|
2433
|
-
if (previous && previous.key !==
|
|
2555
|
+
const key2 = sha(JSON.stringify([scopeKey, document.revision]));
|
|
2556
|
+
atomicJson(join4(directory, `${key2}.body.json`), { document, etag });
|
|
2557
|
+
atomicJson(join4(directory, `${scopeKey}.current.json`), { key: key2 });
|
|
2558
|
+
if (previous && previous.key !== key2) {
|
|
2434
2559
|
try {
|
|
2435
|
-
unlinkSync2(
|
|
2560
|
+
unlinkSync2(join4(directory, `${previous.key}.body.json`));
|
|
2436
2561
|
} catch {
|
|
2437
2562
|
}
|
|
2438
2563
|
}
|
|
@@ -2474,7 +2599,7 @@ function recordGuidanceContext(input, write2) {
|
|
|
2474
2599
|
let path;
|
|
2475
2600
|
let emitted = true;
|
|
2476
2601
|
try {
|
|
2477
|
-
path =
|
|
2602
|
+
path = join4(cacheDirectory(input.environment), `${contextKey}.context.json`);
|
|
2478
2603
|
const previous = readJson(path);
|
|
2479
2604
|
emitted = always || !object(previous) || previous.status === "unavailable" || previous.digest !== document?.digest || previous.revision !== document?.revision || (previous.runtimeGeneration ?? null) !== (input.runtimeGeneration ?? null);
|
|
2480
2605
|
} catch {
|
|
@@ -2510,14 +2635,14 @@ import {
|
|
|
2510
2635
|
existsSync as existsSync3,
|
|
2511
2636
|
lstatSync as lstatSync4,
|
|
2512
2637
|
linkSync,
|
|
2513
|
-
mkdirSync as
|
|
2514
|
-
readFileSync as
|
|
2515
|
-
renameSync as
|
|
2638
|
+
mkdirSync as mkdirSync6,
|
|
2639
|
+
readFileSync as readFileSync7,
|
|
2640
|
+
renameSync as renameSync6,
|
|
2516
2641
|
unlinkSync as unlinkSync5,
|
|
2517
|
-
writeFileSync as
|
|
2642
|
+
writeFileSync as writeFileSync4
|
|
2518
2643
|
} from "node:fs";
|
|
2519
2644
|
var import_toml2 = __toESM(require_toml(), 1);
|
|
2520
|
-
import { dirname as dirname4, join as
|
|
2645
|
+
import { dirname as dirname4, join as join7, relative } from "node:path";
|
|
2521
2646
|
|
|
2522
2647
|
// src/codex-config.ts
|
|
2523
2648
|
var import_toml = __toESM(require_toml(), 1);
|
|
@@ -2526,26 +2651,26 @@ import {
|
|
|
2526
2651
|
existsSync as existsSync2,
|
|
2527
2652
|
fsyncSync as fsyncSync3,
|
|
2528
2653
|
lstatSync as lstatSync3,
|
|
2529
|
-
mkdirSync as
|
|
2654
|
+
mkdirSync as mkdirSync5,
|
|
2530
2655
|
openSync as openSync4,
|
|
2531
|
-
readFileSync as
|
|
2532
|
-
renameSync as
|
|
2656
|
+
readFileSync as readFileSync6,
|
|
2657
|
+
renameSync as renameSync5,
|
|
2533
2658
|
unlinkSync as unlinkSync4,
|
|
2534
2659
|
writeSync as writeSync3
|
|
2535
2660
|
} from "node:fs";
|
|
2536
|
-
import { join as
|
|
2661
|
+
import { join as join6 } from "node:path";
|
|
2537
2662
|
|
|
2538
2663
|
// src/mcp-config.ts
|
|
2539
2664
|
import {
|
|
2540
2665
|
closeSync as closeSync3,
|
|
2541
2666
|
fsyncSync as fsyncSync2,
|
|
2542
2667
|
openSync as openSync3,
|
|
2543
|
-
readFileSync as
|
|
2544
|
-
renameSync as
|
|
2668
|
+
readFileSync as readFileSync5,
|
|
2669
|
+
renameSync as renameSync4,
|
|
2545
2670
|
unlinkSync as unlinkSync3,
|
|
2546
2671
|
writeSync as writeSync2
|
|
2547
2672
|
} from "node:fs";
|
|
2548
|
-
import { basename, dirname as dirname3, isAbsolute, join as
|
|
2673
|
+
import { basename, dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
2549
2674
|
var MCP_CONFIG_FILE = ".mcp.json";
|
|
2550
2675
|
function sameOrigin(left, right) {
|
|
2551
2676
|
try {
|
|
@@ -2588,7 +2713,7 @@ function currentEntry(parsed) {
|
|
|
2588
2713
|
}
|
|
2589
2714
|
function readMcpConfig(repositoryRoot) {
|
|
2590
2715
|
try {
|
|
2591
|
-
return JSON.parse(
|
|
2716
|
+
return JSON.parse(readFileSync5(join5(repositoryRoot, MCP_CONFIG_FILE), "utf8"));
|
|
2592
2717
|
} catch {
|
|
2593
2718
|
return void 0;
|
|
2594
2719
|
}
|
|
@@ -2598,7 +2723,7 @@ function readMcpConfig(repositoryRoot) {
|
|
|
2598
2723
|
var CODEX_CONFIG_FILE = ".codex/config.toml";
|
|
2599
2724
|
function readCodexEntry(root) {
|
|
2600
2725
|
try {
|
|
2601
|
-
const config = (0, import_toml.parse)(
|
|
2726
|
+
const config = (0, import_toml.parse)(readFileSync6(join6(root, CODEX_CONFIG_FILE), "utf8"));
|
|
2602
2727
|
const servers = config.mcp_servers;
|
|
2603
2728
|
return servers && typeof servers === "object" && !Array.isArray(servers) && !(servers instanceof Date) ? servers.balladeer : void 0;
|
|
2604
2729
|
} catch {
|
|
@@ -2607,7 +2732,7 @@ function readCodexEntry(root) {
|
|
|
2607
2732
|
}
|
|
2608
2733
|
function hasManagedCodexEntry(root, controlPlane) {
|
|
2609
2734
|
try {
|
|
2610
|
-
const text =
|
|
2735
|
+
const text = readFileSync6(join6(root, CODEX_CONFIG_FILE), "utf8");
|
|
2611
2736
|
const starts = [...text.matchAll(/^# balladeer:mcp:start\r?$/gm)];
|
|
2612
2737
|
const ends = [...text.matchAll(/^# balladeer:mcp:end\r?$/gm)];
|
|
2613
2738
|
if (starts.length !== 1 || ends.length !== 1 || !starts[0] || !ends[0] || starts[0].index >= ends[0].index)
|
|
@@ -3032,7 +3157,7 @@ function readRegular(path, allowMissing = false, writable = true) {
|
|
|
3032
3157
|
const stat = lstatSync4(path);
|
|
3033
3158
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE || writable && (stat.mode & 146) === 0)
|
|
3034
3159
|
throw new Error("file_not_safely_writable");
|
|
3035
|
-
return
|
|
3160
|
+
return readFileSync7(path, "utf8");
|
|
3036
3161
|
}
|
|
3037
3162
|
function safeParent(root, path) {
|
|
3038
3163
|
let at = dirname4(path);
|
|
@@ -3054,7 +3179,7 @@ function validateProjectGuidanceScope(options) {
|
|
|
3054
3179
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3055
3180
|
timeout: 1e3
|
|
3056
3181
|
}).trim();
|
|
3057
|
-
const path =
|
|
3182
|
+
const path = join7(root, options.hook === "codex" ? ".codex/config.toml" : ".mcp.json");
|
|
3058
3183
|
safeParent(root, path);
|
|
3059
3184
|
readRegular(path, false, false);
|
|
3060
3185
|
const entry = options.hook === "codex" ? readCodexEntry(root) : currentEntry(readMcpConfig(root));
|
|
@@ -3168,31 +3293,52 @@ function repositoryHints(cwd = process.cwd()) {
|
|
|
3168
3293
|
}
|
|
3169
3294
|
|
|
3170
3295
|
// src/hook-trust.ts
|
|
3171
|
-
import { mkdirSync as
|
|
3172
|
-
import { join as
|
|
3296
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3297
|
+
import { join as join8 } from "node:path";
|
|
3298
|
+
var HOSTS_REQUIRING_TRUST = ["codex"];
|
|
3173
3299
|
var FILE = "hook-trust.json";
|
|
3300
|
+
var key = (host, set) => set === "guidance" ? host : `${host}:${set}`;
|
|
3174
3301
|
function read(environment) {
|
|
3175
3302
|
try {
|
|
3176
|
-
const parsed = JSON.parse(
|
|
3303
|
+
const parsed = JSON.parse(readFileSync8(join8(configHome(environment), FILE), "utf8"));
|
|
3177
3304
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
3178
3305
|
} catch {
|
|
3179
3306
|
return {};
|
|
3180
3307
|
}
|
|
3181
3308
|
}
|
|
3309
|
+
function pushCheckAwaitingFile(environment, host) {
|
|
3310
|
+
return join8(configHome(environment), `${host}-push-check-awaiting-first-run`);
|
|
3311
|
+
}
|
|
3312
|
+
function forget(path) {
|
|
3313
|
+
try {
|
|
3314
|
+
rmSync(path, { force: true });
|
|
3315
|
+
} catch {
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3182
3318
|
function write(environment, marks) {
|
|
3183
3319
|
try {
|
|
3184
|
-
|
|
3185
|
-
|
|
3320
|
+
mkdirSync7(configHome(environment), { recursive: true, mode: 448 });
|
|
3321
|
+
writeFileSync5(join8(configHome(environment), FILE), JSON.stringify(marks) + "\n", {
|
|
3186
3322
|
mode: 384
|
|
3187
3323
|
});
|
|
3324
|
+
for (const host of HOSTS_REQUIRING_TRUST) {
|
|
3325
|
+
const file = pushCheckAwaitingFile(environment, host);
|
|
3326
|
+
if (awaiting(marks[key(host, "push-check")])) writeFileSync5(file, "", { mode: 384 });
|
|
3327
|
+
else forget(file);
|
|
3328
|
+
}
|
|
3188
3329
|
} catch {
|
|
3189
3330
|
}
|
|
3190
3331
|
}
|
|
3191
|
-
function noteHookFired(environment, host, now = Date.now()) {
|
|
3332
|
+
function noteHookFired(environment, host, now = Date.now(), set = "guidance") {
|
|
3333
|
+
if (set === "push-check") forget(pushCheckAwaitingFile(environment, host));
|
|
3192
3334
|
const marks = read(environment);
|
|
3193
|
-
const mark = marks[host];
|
|
3335
|
+
const mark = marks[key(host, set)];
|
|
3194
3336
|
if (mark?.firedAt !== void 0 && mark.firedAt >= (mark.writtenAt ?? 0)) return;
|
|
3195
|
-
write(environment, { ...marks, [host]: { ...mark, firedAt: now } });
|
|
3337
|
+
write(environment, { ...marks, [key(host, set)]: { ...mark, firedAt: now } });
|
|
3338
|
+
}
|
|
3339
|
+
function awaiting(mark) {
|
|
3340
|
+
if (mark?.writtenAt === void 0) return false;
|
|
3341
|
+
return mark.firedAt === void 0 || mark.firedAt < mark.writtenAt;
|
|
3196
3342
|
}
|
|
3197
3343
|
|
|
3198
3344
|
// src/commands/guidance.ts
|
|
@@ -3237,7 +3383,45 @@ function eventFrom(input) {
|
|
|
3237
3383
|
function identifier(input) {
|
|
3238
3384
|
return typeof input === "string" && input.length > 0 && input.length <= 256 ? input : void 0;
|
|
3239
3385
|
}
|
|
3386
|
+
async function runRelayNote(options) {
|
|
3387
|
+
const input = await new Promise((resolve2) => {
|
|
3388
|
+
const chunks = [];
|
|
3389
|
+
let bytes = 0;
|
|
3390
|
+
let settled = false;
|
|
3391
|
+
const done = (value) => {
|
|
3392
|
+
if (settled) return;
|
|
3393
|
+
settled = true;
|
|
3394
|
+
clearTimeout(timer);
|
|
3395
|
+
options.stdin.removeListener("data", data);
|
|
3396
|
+
resolve2(value);
|
|
3397
|
+
};
|
|
3398
|
+
const data = (chunk) => {
|
|
3399
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
3400
|
+
bytes += buffer.length;
|
|
3401
|
+
if (bytes > 1048576) done(void 0);
|
|
3402
|
+
else chunks.push(buffer);
|
|
3403
|
+
};
|
|
3404
|
+
const timer = setTimeout(() => done(void 0), 1e3);
|
|
3405
|
+
options.stdin.on("data", data);
|
|
3406
|
+
options.stdin.once("end", () => {
|
|
3407
|
+
try {
|
|
3408
|
+
done(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3409
|
+
} catch {
|
|
3410
|
+
done(void 0);
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
3413
|
+
options.stdin.once("error", () => done(void 0));
|
|
3414
|
+
});
|
|
3415
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return 0;
|
|
3416
|
+
const record = input;
|
|
3417
|
+
if (record.hook_event_name !== "PostToolUse") return 0;
|
|
3418
|
+
const out = relayHook(record);
|
|
3419
|
+
if (out) options.write(`${out}
|
|
3420
|
+
`);
|
|
3421
|
+
return 0;
|
|
3422
|
+
}
|
|
3240
3423
|
async function runGuidance(options) {
|
|
3424
|
+
if (options.event === "PostToolUse") return runRelayNote(options);
|
|
3241
3425
|
const projectScoped = options.repositoryId !== void 0;
|
|
3242
3426
|
if (projectScoped && !validateProjectGuidanceScope({
|
|
3243
3427
|
cwd: options.cwd,
|