@bondsmith/bondsmithd 0.1.8 → 0.1.9
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/README.md +15 -0
- package/dist/index.js +92 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,21 @@ The daemon reads it on its 5-second tick and shows it on the session. Anything e
|
|
|
81
81
|
a report dated in the future. A `waiting` or `done` report is dropped once the pane writes output more
|
|
82
82
|
than 2 seconds after it, since that means the agent moved on; a `working` report is not.
|
|
83
83
|
|
|
84
|
+
An agent can also share the plan it is working against, which Bondsmith shows above the session's
|
|
85
|
+
changes. Run this from inside the session's pane once the plan is agreed, and again whenever it changes:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
tmux set-option -t "$TMUX_PANE" -q @bondsmith-plan "$(cat <<'EOF'
|
|
89
|
+
# What the session is doing
|
|
90
|
+
- step one
|
|
91
|
+
- step two
|
|
92
|
+
EOF
|
|
93
|
+
)"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The value is markdown, cut at 32,768 characters. The daemon reads it only when the browser asks, so
|
|
97
|
+
unlike the state report it has no timestamp and is not dropped when the pane writes output.
|
|
98
|
+
|
|
84
99
|
An agent with no hook can still get attention by ringing the terminal bell (`printf '\a'`). A bell
|
|
85
100
|
rung in a session nobody has looked at marks it as waiting, whatever its last report said, until tmux
|
|
86
101
|
clears the flag when someone attaches to the session. Agents that can ring the bell for approvals or
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var REPLAYED_COMMAND_MESSAGE = "command already used (replay rejected)";
|
|
|
16
16
|
var INVALID_MESSAGE_ERROR = "invalid message";
|
|
17
17
|
|
|
18
18
|
// ../../packages/local-agent-protocol/src/protocol-version.ts
|
|
19
|
-
var PROTOCOL_VERSION =
|
|
19
|
+
var PROTOCOL_VERSION = 8;
|
|
20
20
|
|
|
21
21
|
// ../../packages/local-agent-protocol/src/project-path.ts
|
|
22
22
|
var PROJECT_PATH_INVALID_REASONS = [
|
|
@@ -62,6 +62,7 @@ var MAX_INPUT_CHARS = 16 * 1024;
|
|
|
62
62
|
var MAX_WIRE_MESSAGE_BYTES = 256 * 1024;
|
|
63
63
|
var MAX_DAEMON_REPLY_BYTES = 200 * 1024;
|
|
64
64
|
var MAX_RANGE_LINES = 500;
|
|
65
|
+
var MAX_PLAN_CHARS = 32 * 1024;
|
|
65
66
|
var MAX_TERMINAL_DIMENSION = 1e3;
|
|
66
67
|
var terminalSizeSchema = z2.object({
|
|
67
68
|
cols: z2.number().int().min(1).max(MAX_TERMINAL_DIMENSION),
|
|
@@ -202,6 +203,11 @@ var browserMessageSchema = z2.discriminatedUnion("type", [
|
|
|
202
203
|
staged: z2.boolean(),
|
|
203
204
|
requestId: z2.string().min(1).max(200)
|
|
204
205
|
}),
|
|
206
|
+
z2.object({
|
|
207
|
+
type: z2.literal("workspace.getPlan"),
|
|
208
|
+
sessionId: z2.string(),
|
|
209
|
+
requestId: z2.string().min(1).max(200)
|
|
210
|
+
}),
|
|
205
211
|
z2.object({
|
|
206
212
|
type: z2.literal("project.validatePath"),
|
|
207
213
|
path: z2.string().min(1).max(4096),
|
|
@@ -291,6 +297,12 @@ var daemonMessageSchema = z2.discriminatedUnion("type", [
|
|
|
291
297
|
requestId: z2.string(),
|
|
292
298
|
diff: workspaceFileDiffSchema
|
|
293
299
|
}),
|
|
300
|
+
z2.object({
|
|
301
|
+
type: z2.literal("workspace.plan"),
|
|
302
|
+
sessionId: z2.string(),
|
|
303
|
+
requestId: z2.string(),
|
|
304
|
+
plan: z2.string().max(MAX_PLAN_CHARS).nullable()
|
|
305
|
+
}),
|
|
294
306
|
z2.object({
|
|
295
307
|
type: z2.literal("project.pathValidated"),
|
|
296
308
|
requestId: z2.string(),
|
|
@@ -406,6 +418,7 @@ var SIGNABLE_ACTIONS = [
|
|
|
406
418
|
"workspace.getStatus",
|
|
407
419
|
"workspace.getDiff",
|
|
408
420
|
"workspace.readFileRange",
|
|
421
|
+
"workspace.getPlan",
|
|
409
422
|
"project.validatePath"
|
|
410
423
|
];
|
|
411
424
|
var envelopeSchema = z3.object({
|
|
@@ -891,6 +904,9 @@ import { promisify } from "util";
|
|
|
891
904
|
var execFile = promisify(execFileCb);
|
|
892
905
|
var MAX_GIT_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
893
906
|
var SAFE_DIFF_FLAGS = ["--no-ext-diff", "--no-textconv"];
|
|
907
|
+
function splitNulList(output) {
|
|
908
|
+
return output.split("\0").filter((entry) => entry.length > 0);
|
|
909
|
+
}
|
|
894
910
|
var GitNotARepoError = class extends Error {
|
|
895
911
|
constructor() {
|
|
896
912
|
super("not a git repository");
|
|
@@ -990,7 +1006,7 @@ import { sign } from "crypto";
|
|
|
990
1006
|
import WebSocket from "ws";
|
|
991
1007
|
|
|
992
1008
|
// src/version.ts
|
|
993
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
1009
|
+
var DAEMON_VERSION = true ? "0.1.9" : "dev";
|
|
994
1010
|
var VERSION_FLAGS = ["--version", "-v"];
|
|
995
1011
|
function versionLine() {
|
|
996
1012
|
return `bondsmithd ${DAEMON_VERSION} (protocol ${PROTOCOL_VERSION})`;
|
|
@@ -1061,6 +1077,16 @@ async function getSessionActivity(tmuxSessionName) {
|
|
|
1061
1077
|
return null;
|
|
1062
1078
|
}
|
|
1063
1079
|
}
|
|
1080
|
+
var PLAN_OPTION = "@bondsmith-plan";
|
|
1081
|
+
async function getSessionPlan(tmuxSessionName) {
|
|
1082
|
+
try {
|
|
1083
|
+
const plan = (await runTmux(["show-options", "-qv", "-t", tmuxSessionName, PLAN_OPTION])).trim();
|
|
1084
|
+
return plan ? plan.slice(0, MAX_PLAN_CHARS) : null;
|
|
1085
|
+
} catch (error2) {
|
|
1086
|
+
if (error2 instanceof TmuxNotFoundError) throw error2;
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1064
1090
|
async function listTmuxSessions(formatString) {
|
|
1065
1091
|
try {
|
|
1066
1092
|
return await runTmux(["list-sessions", "-F", formatString]);
|
|
@@ -1709,6 +1735,22 @@ async function dispatch(conn, message, {
|
|
|
1709
1735
|
}
|
|
1710
1736
|
return;
|
|
1711
1737
|
}
|
|
1738
|
+
case "workspace.getPlan": {
|
|
1739
|
+
const session = getSessionOrError(sessionManager, conn, message.sessionId, message.requestId);
|
|
1740
|
+
if (!session) return;
|
|
1741
|
+
try {
|
|
1742
|
+
const plan = await getSessionPlan(session.tmuxSessionName);
|
|
1743
|
+
conn.send({
|
|
1744
|
+
type: "workspace.plan",
|
|
1745
|
+
sessionId: message.sessionId,
|
|
1746
|
+
requestId: message.requestId,
|
|
1747
|
+
plan
|
|
1748
|
+
});
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
conn.send(error(errorMessageOf(err, "failed to read the session plan"), message.requestId));
|
|
1751
|
+
}
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1712
1754
|
case "project.validatePath": {
|
|
1713
1755
|
try {
|
|
1714
1756
|
const result = await validateProjectPath2(message.path);
|
|
@@ -2796,7 +2838,7 @@ async function listChangedPaths(cwd, baseSha) {
|
|
|
2796
2838
|
runGitDiff(["--relative", "--name-only", "-z", baseSha], cwd),
|
|
2797
2839
|
runGit(["ls-files", "--others", "--exclude-standard", "-z"], cwd)
|
|
2798
2840
|
]);
|
|
2799
|
-
const paths = [...tracked
|
|
2841
|
+
const paths = [...splitNulList(tracked), ...splitNulList(untracked)];
|
|
2800
2842
|
return [...new Set(paths)];
|
|
2801
2843
|
}
|
|
2802
2844
|
async function captureBaseline(cwd, options) {
|
|
@@ -2908,7 +2950,7 @@ async function isGitRepo(cwd) {
|
|
|
2908
2950
|
}
|
|
2909
2951
|
function parseNumstat(output) {
|
|
2910
2952
|
const map = /* @__PURE__ */ new Map();
|
|
2911
|
-
const fields = output
|
|
2953
|
+
const fields = splitNulList(output);
|
|
2912
2954
|
let i = 0;
|
|
2913
2955
|
while (i < fields.length) {
|
|
2914
2956
|
const line = fields[i];
|
|
@@ -2949,7 +2991,7 @@ function isStatusLetter(letter) {
|
|
|
2949
2991
|
return letter in STATUS_BY_LETTER;
|
|
2950
2992
|
}
|
|
2951
2993
|
function parseNameStatus(output) {
|
|
2952
|
-
const fields = output
|
|
2994
|
+
const fields = splitNulList(output);
|
|
2953
2995
|
const changed = [];
|
|
2954
2996
|
let i = 0;
|
|
2955
2997
|
while (i < fields.length) {
|
|
@@ -2968,16 +3010,57 @@ function parseNameStatus(output) {
|
|
|
2968
3010
|
}
|
|
2969
3011
|
return changed;
|
|
2970
3012
|
}
|
|
3013
|
+
var reportedFailures = /* @__PURE__ */ new Map();
|
|
3014
|
+
async function loadDefaultBranchView(cwd, baseSha) {
|
|
3015
|
+
const defaultRef = await findDefaultBranchRef(cwd);
|
|
3016
|
+
if (!defaultRef) {
|
|
3017
|
+
reportedFailures.delete(cwd);
|
|
3018
|
+
return null;
|
|
3019
|
+
}
|
|
3020
|
+
try {
|
|
3021
|
+
const defaultSha = (await runGit(["rev-parse", "--verify", "--quiet", `${defaultRef}^{commit}`], cwd)).trim();
|
|
3022
|
+
const forkSha = (await runGit(["merge-base", baseSha, defaultSha], cwd)).trim();
|
|
3023
|
+
const [differing, deletedSinceFork] = await Promise.all([
|
|
3024
|
+
runGitDiff(["--relative", "--name-only", "-z", defaultSha], cwd),
|
|
3025
|
+
runGitDiff(["--relative", "--name-only", "--diff-filter=D", "-z", forkSha, defaultSha], cwd)
|
|
3026
|
+
]);
|
|
3027
|
+
reportedFailures.delete(cwd);
|
|
3028
|
+
return {
|
|
3029
|
+
differing: new Set(splitNulList(differing)),
|
|
3030
|
+
deletedSinceFork: new Set(splitNulList(deletedSinceFork))
|
|
3031
|
+
};
|
|
3032
|
+
} catch (error2) {
|
|
3033
|
+
const signature = `${defaultRef}: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
3034
|
+
if (reportedFailures.get(cwd) !== signature) {
|
|
3035
|
+
reportedFailures.set(cwd, signature);
|
|
3036
|
+
console.error("Failed to compare with the default branch; listing files unfiltered", {
|
|
3037
|
+
cwd,
|
|
3038
|
+
defaultRef,
|
|
3039
|
+
error: error2
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
return null;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
function dropAlreadyOnDefaultBranch(files, view) {
|
|
3046
|
+
if (!view) return files;
|
|
3047
|
+
return files.filter((file) => {
|
|
3048
|
+
if (file.status === "untracked") return true;
|
|
3049
|
+
if (file.status === "deleted") return !view.deletedSinceFork.has(file.path);
|
|
3050
|
+
return view.differing.has(file.path) || file.previousPath !== void 0 && view.differing.has(file.previousPath);
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
2971
3053
|
async function getWorkspaceStatus(cwd, baseline) {
|
|
2972
3054
|
const branch = await getCurrentBranch(cwd);
|
|
2973
3055
|
if (branch === null && !await isGitRepo(cwd)) {
|
|
2974
3056
|
return { isGitRepo: false, branch: null, files: [] };
|
|
2975
3057
|
}
|
|
2976
3058
|
const base = await resolveBaseFor(cwd, baseline);
|
|
2977
|
-
const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
|
|
3059
|
+
const [nameStatusRaw, numstatRaw, untrackedRaw, defaultBranchView] = await Promise.all([
|
|
2978
3060
|
runGitDiff(["--relative", "--name-status", "-z", "-M", base.sha], cwd),
|
|
2979
3061
|
runGitDiff(["--relative", "--numstat", "-z", "-M", base.sha], cwd),
|
|
2980
|
-
runGit(["ls-files", "--others", "--exclude-standard", "-z"], cwd)
|
|
3062
|
+
runGit(["ls-files", "--others", "--exclude-standard", "-z"], cwd),
|
|
3063
|
+
loadDefaultBranchView(cwd, base.sha)
|
|
2981
3064
|
]);
|
|
2982
3065
|
const numstat = parseNumstat(numstatRaw);
|
|
2983
3066
|
const tracked = parseNameStatus(nameStatusRaw).map((changed) => {
|
|
@@ -2992,7 +3075,7 @@ async function getWorkspaceStatus(cwd, baseline) {
|
|
|
2992
3075
|
binary: counts?.binary ?? false
|
|
2993
3076
|
};
|
|
2994
3077
|
});
|
|
2995
|
-
const untracked = untrackedRaw
|
|
3078
|
+
const untracked = splitNulList(untrackedRaw).map((path) => ({
|
|
2996
3079
|
path,
|
|
2997
3080
|
status: "untracked",
|
|
2998
3081
|
staged: false,
|
|
@@ -3000,7 +3083,7 @@ async function getWorkspaceStatus(cwd, baseline) {
|
|
|
3000
3083
|
deletions: null,
|
|
3001
3084
|
binary: false
|
|
3002
3085
|
}));
|
|
3003
|
-
const files = [...tracked, ...untracked];
|
|
3086
|
+
const files = dropAlreadyOnDefaultBranch([...tracked, ...untracked], defaultBranchView);
|
|
3004
3087
|
return {
|
|
3005
3088
|
isGitRepo: true,
|
|
3006
3089
|
branch,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bondsmith/bondsmithd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local daemon for Bondsmith agent sessions — runs on the user's own machine, pairs with a Bondsmith account, and exposes tmux-backed terminal sessions to the Bondsmith web app over a signed WebSocket protocol.",
|
|
6
6
|
"homepage": "https://bondsmith.dev",
|