@bondsmith/bondsmithd 0.1.14 → 0.1.15
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 +11 -6
- package/dist/index.js +94 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,20 +82,20 @@ that pane belongs to:
|
|
|
82
82
|
tmux set-option -t "$TMUX_PANE" -q @bondsmith-agent-state "waiting:$(date +%s)"
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
The value is `<state>:<epoch seconds>`, where state is `working`, `waiting` (needs the user) or `done
|
|
85
|
+
The value is `<state>:<epoch seconds>`, where state is `working`, `waiting` (needs the user) or `done` (finished a turn, shown until the user attaches, types or resizes after it).
|
|
86
86
|
The daemon reads it on its 5-second tick and shows it on the session. Anything else is ignored, as is
|
|
87
87
|
a report dated in the future. A `waiting` or `done` report is dropped once the pane writes output more
|
|
88
88
|
than 2 seconds after it, since that means the agent moved on. A `working` report is dropped once the
|
|
89
89
|
pane has been silent for 2 minutes, since an interrupted or crashed agent never reports again.
|
|
90
90
|
|
|
91
91
|
Claude Code sessions that Bondsmith starts as plain `claude` report this on their own (see the end of the
|
|
92
|
-
plan section below): `working` when a prompt is sent, `waiting` when it asks for permission
|
|
93
|
-
|
|
92
|
+
plan section below): `working` when a prompt is sent, `waiting` when it asks for permission or fails with
|
|
93
|
+
an API error, `done` when it finishes a turn. Nothing needs installing for those.
|
|
94
94
|
|
|
95
95
|
For an agent the daemon does not set up (a wrapper, `npx claude`, another agent), note that every session
|
|
96
96
|
Bondsmith starts has `BONDSMITH_SESSION` set to its id, so a hook installed once for all your projects can
|
|
97
97
|
stay silent everywhere else. This entry in `~/.claude/settings.json` reports a Claude Code session as
|
|
98
|
-
waiting the moment it asks for permission
|
|
98
|
+
waiting the moment it asks for permission, and as done when it finishes a turn:
|
|
99
99
|
|
|
100
100
|
```json
|
|
101
101
|
{
|
|
@@ -116,7 +116,7 @@ waiting the moment it asks for permission or finishes a turn:
|
|
|
116
116
|
"hooks": [
|
|
117
117
|
{
|
|
118
118
|
"type": "command",
|
|
119
|
-
"command": "[ -n \"$BONDSMITH_SESSION\" ] && tmux set-option -t \"$TMUX_PANE\" -q @bondsmith-agent-state \"
|
|
119
|
+
"command": "[ -n \"$BONDSMITH_SESSION\" ] && tmux set-option -t \"$TMUX_PANE\" -q @bondsmith-agent-state \"done:$(date +%s)\"; exit 0"
|
|
120
120
|
}
|
|
121
121
|
]
|
|
122
122
|
}
|
|
@@ -127,7 +127,7 @@ waiting the moment it asks for permission or finishes a turn:
|
|
|
127
127
|
|
|
128
128
|
This is optional, and nothing edits your settings for you. The command does nothing outside a Bondsmith
|
|
129
129
|
session, and sessions started before the daemon set the variable are skipped by it. It reports only
|
|
130
|
-
`waiting`. A `working` report is dropped after the pane has been silent for 2 minutes, so one that an
|
|
130
|
+
`waiting` and `done`. A `working` report is dropped after the pane has been silent for 2 minutes, so one that an
|
|
131
131
|
interrupted Claude Code never clears (it does not run `Stop` then, and has no hook for Escape) expires on
|
|
132
132
|
its own.
|
|
133
133
|
|
|
@@ -157,6 +157,11 @@ that only answers the agent (`yes`, `continue`, `ok`, `thanks`, and a few like t
|
|
|
157
157
|
characters leaves the earlier headline in place; a built-in slash command such as `/help` sends no prompt to
|
|
158
158
|
the hook, so it does too. The prompt is stored as typed, so a secret in a prompt is in the headline.
|
|
159
159
|
|
|
160
|
+
A sixth hook watches Bash calls for `gh pr create`. When one succeeds and prints a pull request URL, the URL is
|
|
161
|
+
appended to the tmux option `@bondsmith-prs` (space separated, each once, the newest 20 kept), and the daemon
|
|
162
|
+
reports it on the session as `pullRequestUrls`. It matches `gh pr create` only as a command of its own, so
|
|
163
|
+
`echo "gh pr create"` or `gh pr view` record nothing, and a failed command never reaches it.
|
|
164
|
+
|
|
160
165
|
The daemon leaves the command alone if it already has `--settings`, if the installed `claude` does not list
|
|
161
166
|
`--settings` in `claude --help` (asked once per daemon run; if `claude` cannot be run at all, that is logged
|
|
162
167
|
and asked again after a minute), or if `BONDSMITH_AGENT_HOOKS` is set to `off`, `0`, `false` or `no` where
|
package/dist/index.js
CHANGED
|
@@ -11,13 +11,14 @@ import { fileURLToPath } from "url";
|
|
|
11
11
|
var SESSION_STATUSES = ["running", "exited"];
|
|
12
12
|
var AGENT_STATES = ["working", "waiting", "done"];
|
|
13
13
|
var MAX_SKIPPED_FILES_REPORTED = 20;
|
|
14
|
+
var MAX_SESSION_PULL_REQUESTS = 20;
|
|
14
15
|
|
|
15
16
|
// ../../packages/local-agent-protocol/src/messages.ts
|
|
16
17
|
var REPLAYED_COMMAND_MESSAGE = "command already used (replay rejected)";
|
|
17
18
|
var INVALID_MESSAGE_ERROR = "invalid message";
|
|
18
19
|
|
|
19
20
|
// ../../packages/local-agent-protocol/src/protocol-version.ts
|
|
20
|
-
var PROTOCOL_VERSION =
|
|
21
|
+
var PROTOCOL_VERSION = 19;
|
|
21
22
|
|
|
22
23
|
// ../../packages/local-agent-protocol/src/branch-name.ts
|
|
23
24
|
var MAX_BRANCH_NAME_LENGTH = 250;
|
|
@@ -206,7 +207,8 @@ var localSessionSchema = z3.object({
|
|
|
206
207
|
agentState: z3.enum(AGENT_STATES).nullable().optional().catch(void 0),
|
|
207
208
|
worktreeSetupFailed: z3.boolean().optional().catch(void 0),
|
|
208
209
|
worktreeSetupSkipped: z3.array(z3.string().max(300)).max(MAX_SKIPPED_FILES_REPORTED).optional().catch(void 0),
|
|
209
|
-
headline: z3.string().nullable().optional().catch(void 0)
|
|
210
|
+
headline: z3.string().nullable().optional().catch(void 0),
|
|
211
|
+
pullRequestUrls: z3.array(z3.string().max(300)).max(MAX_SESSION_PULL_REQUESTS).optional().catch(void 0)
|
|
210
212
|
});
|
|
211
213
|
var workspaceFileEntrySchema = z3.object({
|
|
212
214
|
path: z3.string(),
|
|
@@ -317,6 +319,11 @@ var browserMessageSchema = z3.discriminatedUnion("type", [
|
|
|
317
319
|
sessionId: z3.string(),
|
|
318
320
|
...terminalSizeSchema.shape
|
|
319
321
|
}),
|
|
322
|
+
z3.object({
|
|
323
|
+
type: z3.literal("session.setMouseMode"),
|
|
324
|
+
sessionId: z3.string(),
|
|
325
|
+
enabled: z3.boolean()
|
|
326
|
+
}),
|
|
320
327
|
z3.object({ type: z3.literal("session.stop"), sessionId: z3.string() }),
|
|
321
328
|
z3.object({
|
|
322
329
|
type: z3.literal("workspace.getStatus"),
|
|
@@ -527,6 +534,7 @@ var SIGNABLE_ACTIONS = [
|
|
|
527
534
|
"session.input",
|
|
528
535
|
"session.insertText",
|
|
529
536
|
"session.resize",
|
|
537
|
+
"session.setMouseMode",
|
|
530
538
|
"session.stop",
|
|
531
539
|
"session.list",
|
|
532
540
|
"workspace.getStatus",
|
|
@@ -1457,6 +1465,12 @@ async function runTmux(args, options) {
|
|
|
1457
1465
|
async function sendKeysLiteral(tmuxSessionName, text) {
|
|
1458
1466
|
await runTmux(["send-keys", "-t", tmuxSessionName, "-l", text]);
|
|
1459
1467
|
}
|
|
1468
|
+
async function setMouseMode(tmuxSessionName, enabled) {
|
|
1469
|
+
await runTmux(["set-option", "-t", tmuxSessionName, "mouse", enabled ? "on" : "off"]);
|
|
1470
|
+
}
|
|
1471
|
+
async function cancelCopyMode(tmuxSessionName) {
|
|
1472
|
+
await runTmux(["copy-mode", "-q", "-t", tmuxSessionName]);
|
|
1473
|
+
}
|
|
1460
1474
|
async function capturePane(tmuxSessionName) {
|
|
1461
1475
|
return runTmux(["capture-pane", "-t", tmuxSessionName, "-p", "-e"]);
|
|
1462
1476
|
}
|
|
@@ -2268,7 +2282,7 @@ function listInstalledAgents(env = process.env) {
|
|
|
2268
2282
|
}
|
|
2269
2283
|
|
|
2270
2284
|
// src/version.ts
|
|
2271
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
2285
|
+
var DAEMON_VERSION = true ? "0.1.15" : "dev";
|
|
2272
2286
|
var VERSION_FLAGS = ["--version", "-v"];
|
|
2273
2287
|
function versionLine() {
|
|
2274
2288
|
return `bondsmithd ${DAEMON_VERSION} (protocol ${PROTOCOL_VERSION})`;
|
|
@@ -2445,6 +2459,7 @@ function verifyRejectionMessage(reason) {
|
|
|
2445
2459
|
var REVOKED_REJECTION_MESSAGE = "connection has not completed its handshake";
|
|
2446
2460
|
var BRACKETED_PASTE_START = "\x1B[200~";
|
|
2447
2461
|
var BRACKETED_PASTE_END = "\x1B[201~";
|
|
2462
|
+
var WHEEL_ONLY_INPUT = /^(?:\x1b\[<6[45];\d+;\d+M)+$/;
|
|
2448
2463
|
function releaseSession(conn, ptyAttachments, sessionId) {
|
|
2449
2464
|
const listener = conn.getOutputListener(sessionId);
|
|
2450
2465
|
if (listener) ptyAttachments.release(sessionId, listener);
|
|
@@ -2458,6 +2473,7 @@ function createRouterConnection(connectionAuth, send, markControllerId, ptyAttac
|
|
|
2458
2473
|
let admissionChain = Promise.resolve();
|
|
2459
2474
|
const conn = {
|
|
2460
2475
|
subscriptions: /* @__PURE__ */ new Set(),
|
|
2476
|
+
mouseModeSessions: /* @__PURE__ */ new Map(),
|
|
2461
2477
|
send,
|
|
2462
2478
|
bufferedBytes,
|
|
2463
2479
|
isClosed: () => closed,
|
|
@@ -2496,6 +2512,11 @@ function createRouterConnection(connectionAuth, send, markControllerId, ptyAttac
|
|
|
2496
2512
|
for (const sessionId of [...conn.subscriptions]) {
|
|
2497
2513
|
releaseSession(conn, ptyAttachments, sessionId);
|
|
2498
2514
|
}
|
|
2515
|
+
for (const tmuxSessionName of conn.mouseModeSessions.values()) {
|
|
2516
|
+
setMouseMode(tmuxSessionName, false).catch(() => {
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
conn.mouseModeSessions.clear();
|
|
2499
2520
|
}
|
|
2500
2521
|
return { conn, releaseAllSubscriptions };
|
|
2501
2522
|
}
|
|
@@ -2729,6 +2750,11 @@ async function dispatch(conn, message, {
|
|
|
2729
2750
|
return;
|
|
2730
2751
|
}
|
|
2731
2752
|
sessionManager.acknowledge(message.sessionId);
|
|
2753
|
+
const tmuxSessionName = conn.mouseModeSessions.get(message.sessionId);
|
|
2754
|
+
if (tmuxSessionName && !WHEEL_ONLY_INPUT.test(message.data)) {
|
|
2755
|
+
await cancelCopyMode(tmuxSessionName).catch(() => {
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2732
2758
|
ptyAttachments.write(message.sessionId, message.data);
|
|
2733
2759
|
return;
|
|
2734
2760
|
}
|
|
@@ -2759,6 +2785,20 @@ async function dispatch(conn, message, {
|
|
|
2759
2785
|
ptyAttachments.resize(message.sessionId, message.cols, message.rows);
|
|
2760
2786
|
return;
|
|
2761
2787
|
}
|
|
2788
|
+
case "session.setMouseMode": {
|
|
2789
|
+
const session = sessionManager.get(message.sessionId);
|
|
2790
|
+
if (!session) return;
|
|
2791
|
+
if (message.enabled) conn.mouseModeSessions.set(session.id, session.tmuxSessionName);
|
|
2792
|
+
else conn.mouseModeSessions.delete(session.id);
|
|
2793
|
+
try {
|
|
2794
|
+
await setMouseMode(session.tmuxSessionName, message.enabled);
|
|
2795
|
+
if (message.enabled && conn.isClosed()) await setMouseMode(session.tmuxSessionName, false);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
if (message.enabled) conn.mouseModeSessions.delete(session.id);
|
|
2798
|
+
console.warn(`bondsmithd: ${errorMessageOf(err, "failed to set mouse mode")}`);
|
|
2799
|
+
}
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2762
2802
|
case "session.stop": {
|
|
2763
2803
|
try {
|
|
2764
2804
|
if (!await sessionManager.stop(message.sessionId)) {
|
|
@@ -3990,8 +4030,10 @@ function decodeSkippedFiles(raw) {
|
|
|
3990
4030
|
// src/sessions/tmux/agent-state.ts
|
|
3991
4031
|
var AGENT_STATE_OPTION = "@bondsmith-agent-state";
|
|
3992
4032
|
var HEADLINE_OPTION = "@bondsmith-headline";
|
|
4033
|
+
var PULL_REQUESTS_OPTION = "@bondsmith-prs";
|
|
4034
|
+
var PULL_REQUEST_URL = /https?:\/\/[^\s/]+\/[^\s/]+\/[^\s/]+\/pull\/\d+/g;
|
|
3993
4035
|
var MAX_HEADLINE_CHARS = 200;
|
|
3994
|
-
var LIVE_SESSION_REPORTS_FORMAT = `#{session_name} #{${AGENT_STATE_OPTION}} #{window_bell_flag} #{${SETUP_FAILED_OPTION}} #{${SETUP_SKIPPED_OPTION}} #{${HEADLINE_OPTION}}`;
|
|
4036
|
+
var LIVE_SESSION_REPORTS_FORMAT = `#{session_name} #{${AGENT_STATE_OPTION}} #{window_bell_flag} #{${SETUP_FAILED_OPTION}} #{${SETUP_SKIPPED_OPTION}} #{${PULL_REQUESTS_OPTION}} #{${HEADLINE_OPTION}}`;
|
|
3995
4037
|
var AGENT_STATE_OUTPUT_GRACE_MS = 2e3;
|
|
3996
4038
|
var FUTURE_REPORT_ALLOWANCE_MS = 2e3;
|
|
3997
4039
|
var WORKING_SILENCE_MS = 12e4;
|
|
@@ -4003,6 +4045,10 @@ function cleanHeadline(raw) {
|
|
|
4003
4045
|
if (!line) return null;
|
|
4004
4046
|
return line.length > MAX_HEADLINE_CHARS ? `${line.slice(0, MAX_HEADLINE_CHARS - 1)}\u2026` : line;
|
|
4005
4047
|
}
|
|
4048
|
+
function parsePullRequestUrls(raw) {
|
|
4049
|
+
const urls = [...new Set(raw.match(PULL_REQUEST_URL) ?? [])];
|
|
4050
|
+
return urls.slice(-MAX_SESSION_PULL_REQUESTS);
|
|
4051
|
+
}
|
|
4006
4052
|
function isAgentState(value) {
|
|
4007
4053
|
return AGENT_STATES.includes(value);
|
|
4008
4054
|
}
|
|
@@ -4015,6 +4061,7 @@ function parseLiveSessionReports(output) {
|
|
|
4015
4061
|
bell = "",
|
|
4016
4062
|
setupFailed = "",
|
|
4017
4063
|
setupSkipped = "",
|
|
4064
|
+
pullRequests = "",
|
|
4018
4065
|
...headline
|
|
4019
4066
|
] = line.split(" ");
|
|
4020
4067
|
if (sessionName) {
|
|
@@ -4023,17 +4070,23 @@ function parseLiveSessionReports(output) {
|
|
|
4023
4070
|
bell: bell === "1",
|
|
4024
4071
|
setupFailed: setupFailed === "1",
|
|
4025
4072
|
setupSkipped: decodeSkippedFiles(setupSkipped),
|
|
4073
|
+
pullRequestUrls: parsePullRequestUrls(pullRequests),
|
|
4026
4074
|
headline: cleanHeadline(headline.join(" "))
|
|
4027
4075
|
});
|
|
4028
4076
|
}
|
|
4029
4077
|
}
|
|
4030
4078
|
return reports;
|
|
4031
4079
|
}
|
|
4032
|
-
function
|
|
4080
|
+
function parseReport(raw) {
|
|
4033
4081
|
if (!raw) return null;
|
|
4034
4082
|
const [state, epoch] = raw.split(":");
|
|
4035
4083
|
if (!state || !isAgentState(state) || !epoch || !/^\d+$/.test(epoch)) return null;
|
|
4036
|
-
|
|
4084
|
+
return { state, reportedAtMs: Number(epoch) * 1e3 };
|
|
4085
|
+
}
|
|
4086
|
+
function resolveAgentState(raw, lastActivityAt, nowMs = Date.now()) {
|
|
4087
|
+
const parsed = parseReport(raw);
|
|
4088
|
+
if (!parsed) return null;
|
|
4089
|
+
const { state, reportedAtMs } = parsed;
|
|
4037
4090
|
if (reportedAtMs > nowMs + FUTURE_REPORT_ALLOWANCE_MS) return null;
|
|
4038
4091
|
if (SETTLED_STATES.has(state) && lastActivityAt) {
|
|
4039
4092
|
if (Date.parse(lastActivityAt) > reportedAtMs + AGENT_STATE_OUTPUT_GRACE_MS) return null;
|
|
@@ -4053,9 +4106,16 @@ function hasSettled(lastActivityAt, acknowledgedAtMs, nowMs = Date.now()) {
|
|
|
4053
4106
|
}
|
|
4054
4107
|
return nowMs - lastActivityMs >= SETTLE_QUIET_MS;
|
|
4055
4108
|
}
|
|
4109
|
+
var REPORT_ROUNDING_MS = 1e3;
|
|
4110
|
+
function isDoneAcknowledged(raw, acknowledgedAtMs) {
|
|
4111
|
+
const reportedAtMs = parseReport(raw)?.reportedAtMs;
|
|
4112
|
+
if (acknowledgedAtMs === null || reportedAtMs === void 0) return false;
|
|
4113
|
+
return acknowledgedAtMs >= reportedAtMs + REPORT_ROUNDING_MS;
|
|
4114
|
+
}
|
|
4056
4115
|
function resolveSessionAttention(report, lastActivityAt, { nowMs, acknowledgedAtMs = null } = {}) {
|
|
4057
4116
|
if (report?.bell) return "waiting";
|
|
4058
4117
|
const reported = resolveAgentState(report?.agentState, lastActivityAt, nowMs);
|
|
4118
|
+
if (reported === "done" && isDoneAcknowledged(report?.agentState, acknowledgedAtMs)) return null;
|
|
4059
4119
|
if (reported) return reported;
|
|
4060
4120
|
return hasSettled(lastActivityAt, acknowledgedAtMs, nowMs) ? "waiting" : null;
|
|
4061
4121
|
}
|
|
@@ -4106,6 +4166,21 @@ function headlineHookScript() {
|
|
|
4106
4166
|
`}catch{}})`
|
|
4107
4167
|
].join("");
|
|
4108
4168
|
}
|
|
4169
|
+
var PR_CREATE_COMMAND = /(?:^|[;&|\n(])\s*(?:\w+=\S*\s+)*gh\s+pr\s+create\b/;
|
|
4170
|
+
function pullRequestHookScript() {
|
|
4171
|
+
return [
|
|
4172
|
+
`let d="";`,
|
|
4173
|
+
`process.stdin.on("data",c=>d+=c).on("end",()=>{try{`,
|
|
4174
|
+
`const j=JSON.parse(d),c=j.tool_input?.command,o=j.tool_response?.stdout;`,
|
|
4175
|
+
`if(typeof c!=="string"||typeof o!=="string"||!${PR_CREATE_COMMAND.toString()}.test(c))return;`,
|
|
4176
|
+
`const m=o.match(${PULL_REQUEST_URL.toString()});if(!m)return;const u=m[m.length-1];`,
|
|
4177
|
+
`const cp=require("child_process"),p=process.env.TMUX_PANE;`,
|
|
4178
|
+
`const cur=cp.execFileSync("tmux",["show-options","-qv","-t",p,"${PULL_REQUESTS_OPTION}"],{encoding:"utf8"}).split(/\\s+/).filter(Boolean);`,
|
|
4179
|
+
`if(cur.includes(u))return;`,
|
|
4180
|
+
`cp.execFileSync("tmux",["set-option","-t",p,"-q","${PULL_REQUESTS_OPTION}",cur.concat(u).slice(-${MAX_SESSION_PULL_REQUESTS}).join(" ")])`,
|
|
4181
|
+
`}catch{}})`
|
|
4182
|
+
].join("");
|
|
4183
|
+
}
|
|
4109
4184
|
|
|
4110
4185
|
// src/sessions/agents/claude-settings.ts
|
|
4111
4186
|
function shellQuote3(value) {
|
|
@@ -4120,12 +4195,17 @@ function planHookCommand(nodePath = process.execPath, plansDir = HOOK_PLANS_DIR)
|
|
|
4120
4195
|
function headlineHookCommand(nodePath = process.execPath) {
|
|
4121
4196
|
return nodeHookCommand(headlineHookScript(), nodePath);
|
|
4122
4197
|
}
|
|
4198
|
+
function pullRequestHookCommand(nodePath = process.execPath) {
|
|
4199
|
+
const run = `${shellQuote3(nodePath)} -e ${shellQuote3(pullRequestHookScript())}`;
|
|
4200
|
+
return `[ -n "$${SESSION_ENV_VAR}" ] && { input=$(cat); case "$input" in *"gh pr create"*) printf '%s' "$input" | ${run};; esac; }; exit 0`;
|
|
4201
|
+
}
|
|
4123
4202
|
function stateHookCommand(state) {
|
|
4124
4203
|
return `[ -n "$${SESSION_ENV_VAR}" ] && tmux set-option -t "$TMUX_PANE" -q ${AGENT_STATE_OPTION} "${state}:$(date +%s)"; exit 0`;
|
|
4125
4204
|
}
|
|
4126
4205
|
var STATE_HOOK_TIMEOUT_SECONDS = 5;
|
|
4127
4206
|
var PLAN_HOOK_TIMEOUT_SECONDS = 10;
|
|
4128
4207
|
var HEADLINE_HOOK_TIMEOUT_SECONDS = 5;
|
|
4208
|
+
var PULL_REQUEST_HOOK_TIMEOUT_SECONDS = 5;
|
|
4129
4209
|
function claudeSettings() {
|
|
4130
4210
|
const run = (command, timeout) => ({ type: "command", command, timeout });
|
|
4131
4211
|
const report = (state) => run(stateHookCommand(state), STATE_HOOK_TIMEOUT_SECONDS);
|
|
@@ -4135,16 +4215,20 @@ function claudeSettings() {
|
|
|
4135
4215
|
{
|
|
4136
4216
|
matcher: "ExitPlanMode",
|
|
4137
4217
|
hooks: [run(planHookCommand(), PLAN_HOOK_TIMEOUT_SECONDS)]
|
|
4218
|
+
},
|
|
4219
|
+
{
|
|
4220
|
+
matcher: "Bash",
|
|
4221
|
+
hooks: [run(pullRequestHookCommand(), PULL_REQUEST_HOOK_TIMEOUT_SECONDS)]
|
|
4138
4222
|
}
|
|
4139
4223
|
],
|
|
4140
|
-
// Working from the moment a prompt is sent, which is also the task the session is on; waiting
|
|
4224
|
+
// Working from the moment a prompt is sent, which is also the task the session is on; waiting when it needs the user; done when a turn ends.
|
|
4141
4225
|
UserPromptSubmit: [
|
|
4142
4226
|
{
|
|
4143
4227
|
hooks: [report("working"), run(headlineHookCommand(), HEADLINE_HOOK_TIMEOUT_SECONDS)]
|
|
4144
4228
|
}
|
|
4145
4229
|
],
|
|
4146
4230
|
PermissionRequest: [{ matcher: "*", hooks: [report("waiting")] }],
|
|
4147
|
-
Stop: [{ hooks: [report("
|
|
4231
|
+
Stop: [{ hooks: [report("done")] }],
|
|
4148
4232
|
StopFailure: [{ hooks: [report("waiting")] }]
|
|
4149
4233
|
}
|
|
4150
4234
|
};
|
|
@@ -4661,6 +4745,7 @@ ${error2.message}
|
|
|
4661
4745
|
acknowledgedAtMs: this.acknowledgements.get(session.id) ?? null
|
|
4662
4746
|
}),
|
|
4663
4747
|
headline: report?.headline ?? null,
|
|
4748
|
+
...report?.pullRequestUrls?.length && { pullRequestUrls: report.pullRequestUrls },
|
|
4664
4749
|
...report?.setupFailed && { worktreeSetupFailed: true },
|
|
4665
4750
|
...report?.setupSkipped?.length && { worktreeSetupSkipped: report.setupSkipped }
|
|
4666
4751
|
};
|
|
@@ -4687,7 +4772,7 @@ ${error2.message}
|
|
|
4687
4772
|
}
|
|
4688
4773
|
};
|
|
4689
4774
|
function snapshotEquals(a, b) {
|
|
4690
|
-
return a.branch === b.branch && a.repositoryFullName === b.repositoryFullName && a.changedFileCount === b.changedFileCount && a.lastActivityAt === b.lastActivityAt && (a.agentState ?? null) === (b.agentState ?? null) && (a.headline ?? null) === (b.headline ?? null) && (a.worktreeSetupFailed ?? false) === (b.worktreeSetupFailed ?? false) && JSON.stringify(a.worktreeSetupSkipped ?? []) === JSON.stringify(b.worktreeSetupSkipped ?? []);
|
|
4775
|
+
return a.branch === b.branch && a.repositoryFullName === b.repositoryFullName && a.changedFileCount === b.changedFileCount && a.lastActivityAt === b.lastActivityAt && (a.agentState ?? null) === (b.agentState ?? null) && (a.headline ?? null) === (b.headline ?? null) && JSON.stringify(a.pullRequestUrls ?? []) === JSON.stringify(b.pullRequestUrls ?? []) && (a.worktreeSetupFailed ?? false) === (b.worktreeSetupFailed ?? false) && JSON.stringify(a.worktreeSetupSkipped ?? []) === JSON.stringify(b.worktreeSetupSkipped ?? []);
|
|
4691
4776
|
}
|
|
4692
4777
|
|
|
4693
4778
|
// src/ws/server.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bondsmith/bondsmithd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
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",
|