patchwork-os 1.2.0-beta.2.canary.660 → 1.2.0-beta.2.canary.662
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/bridge.js +20 -5
- package/dist/bridge.js.map +1 -1
- package/dist/commands/approve.d.ts +61 -0
- package/dist/commands/approve.js +228 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patchwork approve <callId>` / `patchwork reject <callId>`.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The dashboard has two buttons that copy `patchwork approve <callId>` and
|
|
7
|
+
* `patchwork approve --edit <callId>` to the clipboard
|
|
8
|
+
* (dashboard/src/app/approvals/page.tsx). Neither command existed: running the
|
|
9
|
+
* copied string printed `Unknown command: 'approve'. Did you mean: approvals?`
|
|
10
|
+
* — `approvals` being an unrelated read-only KPI report. So the product told
|
|
11
|
+
* the operator to run something that has never worked.
|
|
12
|
+
*
|
|
13
|
+
* It also matters beyond the broken button. Every approval today is decided
|
|
14
|
+
* through the dashboard or a phone notification. If either becomes unavailable
|
|
15
|
+
* — the dashboard is down, the push relay is unreachable, or a future change
|
|
16
|
+
* starts REFUSING approvals — there is no other way to decide a queued action,
|
|
17
|
+
* and a queued action blocks the recipe waiting on it. A terminal path is the
|
|
18
|
+
* fallback that has to exist before anything can ever refuse.
|
|
19
|
+
*
|
|
20
|
+
* ## A shim, and nothing more
|
|
21
|
+
*
|
|
22
|
+
* No new approval logic lives here. Each verb maps to one route the bridge
|
|
23
|
+
* already serves (src/approvalHttp.ts):
|
|
24
|
+
* - approve → POST /approve/:callId
|
|
25
|
+
* - reject → POST /reject/:callId (optional `{ reason }` body)
|
|
26
|
+
* - --review reads GET /approvals first, purely to show the operator what
|
|
27
|
+
* they are about to decide.
|
|
28
|
+
*
|
|
29
|
+
* Bridge discovery and Bearer auth mirror src/commands/connect.ts. Every side
|
|
30
|
+
* effect is injected so this is testable without a live bridge.
|
|
31
|
+
*
|
|
32
|
+
* ## `--edit` cannot do what its name promises
|
|
33
|
+
*
|
|
34
|
+
* The dashboard advertises "Edit & approve". There is NO param-editing
|
|
35
|
+
* capability anywhere in the bridge: `POST /approve/:callId` ignores the
|
|
36
|
+
* request body entirely, and nothing in the tree patches a pending approval's
|
|
37
|
+
* params. The flag was aspirational.
|
|
38
|
+
*
|
|
39
|
+
* It is accepted here anyway, because the copied command must work rather than
|
|
40
|
+
* error. But it does NOT silently pretend: it prints that parameters cannot be
|
|
41
|
+
* modified and then behaves as `--review`. Quietly treating `--edit` as a
|
|
42
|
+
* successful edit would be the worse failure — the operator would believe they
|
|
43
|
+
* had changed an action's parameters and approve the original.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* The route patterns are `/^\/(approve|reject)\/([A-Za-z0-9-]+)$/`. An id
|
|
47
|
+
* outside that set cannot match, so the bridge would answer 404 — technically
|
|
48
|
+
* correct and useless to read, because "unknown callId" and "you pasted
|
|
49
|
+
* something that is not a callId" are different problems with different fixes.
|
|
50
|
+
* Checked here so the message names the real one.
|
|
51
|
+
*/
|
|
52
|
+
const CALL_ID_RE = /^[A-Za-z0-9-]+$/;
|
|
53
|
+
function usage(action) {
|
|
54
|
+
const extra = action === "reject" ? "\n --reason <text> record why (audit trail)" : "";
|
|
55
|
+
return (`usage: patchwork ${action} <callId> [--review] [--json]${extra}\n\n` +
|
|
56
|
+
` --review print the queued action and confirm before deciding\n` +
|
|
57
|
+
` --json machine-readable result\n\n` +
|
|
58
|
+
`List what is pending with: patchwork approvals\n`);
|
|
59
|
+
}
|
|
60
|
+
function flagValue(argv, flag) {
|
|
61
|
+
const i = argv.indexOf(flag);
|
|
62
|
+
if (i === -1)
|
|
63
|
+
return undefined;
|
|
64
|
+
const v = argv[i + 1];
|
|
65
|
+
// A flag whose value is missing or is itself a flag was almost certainly a
|
|
66
|
+
// typo. Returning undefined would silently drop it.
|
|
67
|
+
if (v === undefined || v.startsWith("--"))
|
|
68
|
+
return undefined;
|
|
69
|
+
return v;
|
|
70
|
+
}
|
|
71
|
+
/** Render one pending entry for the review prompt. */
|
|
72
|
+
function describeEntry(e) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(` tool: ${e.toolName ?? "(unknown)"}`);
|
|
75
|
+
if (e.tier)
|
|
76
|
+
lines.push(` tier: ${e.tier}`);
|
|
77
|
+
if (e.recipeName)
|
|
78
|
+
lines.push(` recipe: ${e.recipeName}`);
|
|
79
|
+
if (e.summary)
|
|
80
|
+
lines.push(` summary: ${e.summary}`);
|
|
81
|
+
if (e.params !== undefined) {
|
|
82
|
+
let rendered;
|
|
83
|
+
try {
|
|
84
|
+
rendered = JSON.stringify(e.params, null, 2) ?? String(e.params);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
rendered = "(unserialisable)";
|
|
88
|
+
}
|
|
89
|
+
// Indent every line so the params block cannot be mistaken for the
|
|
90
|
+
// surrounding prose when it spans many lines.
|
|
91
|
+
lines.push(" params:");
|
|
92
|
+
for (const l of rendered.split("\n"))
|
|
93
|
+
lines.push(` ${l}`);
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
export async function runApproveCommand(action, argv, deps) {
|
|
98
|
+
const wantJson = argv.includes("--json");
|
|
99
|
+
const wantEdit = argv.includes("--edit");
|
|
100
|
+
const wantReview = argv.includes("--review") || wantEdit;
|
|
101
|
+
const reason = action === "reject" ? flagValue(argv, "--reason") : undefined;
|
|
102
|
+
const positional = argv.filter((a, i) => {
|
|
103
|
+
if (a.startsWith("--"))
|
|
104
|
+
return false;
|
|
105
|
+
// Drop the value that belongs to --reason.
|
|
106
|
+
const prev = argv[i - 1];
|
|
107
|
+
if (prev === "--reason")
|
|
108
|
+
return false;
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
const callId = positional[0];
|
|
112
|
+
if (!callId) {
|
|
113
|
+
deps.writeErr(usage(action));
|
|
114
|
+
deps.exit(1);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!CALL_ID_RE.test(callId)) {
|
|
118
|
+
deps.writeErr(`[${action}] '${callId}' is not a callId (expected letters, digits and hyphens).\n` +
|
|
119
|
+
` Copy one from the dashboard, or run: patchwork approvals\n`);
|
|
120
|
+
deps.exit(1);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const lock = deps.findBridgeLock();
|
|
124
|
+
if (!lock) {
|
|
125
|
+
deps.writeErr(`[${action}] no running bridge found in ~/.claude/ide/.\n` +
|
|
126
|
+
` An approval is held in the bridge's memory, so there is nothing to decide\n` +
|
|
127
|
+
` while it is stopped. Start it with: patchwork start\n`);
|
|
128
|
+
deps.exit(1);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const base = `http://127.0.0.1:${lock.port}`;
|
|
132
|
+
const auth = { Authorization: `Bearer ${lock.authToken}` };
|
|
133
|
+
if (wantEdit) {
|
|
134
|
+
// Say it plainly rather than letting the flag imply a capability. See the
|
|
135
|
+
// module header: the bridge ignores the body on /approve entirely.
|
|
136
|
+
deps.writeErr(`[${action}] --edit cannot modify parameters — the bridge has no such capability.\n` +
|
|
137
|
+
` Showing the queued action for review instead; approving sends it unchanged.\n`);
|
|
138
|
+
}
|
|
139
|
+
if (wantReview) {
|
|
140
|
+
let entry;
|
|
141
|
+
try {
|
|
142
|
+
const res = await deps.fetchFn(`${base}/approvals`, { headers: auth });
|
|
143
|
+
if (res.ok) {
|
|
144
|
+
const list = (await res.json());
|
|
145
|
+
if (Array.isArray(list)) {
|
|
146
|
+
entry = list.find((e) => e?.callId === callId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* fall through — review is a courtesy, not a gate */
|
|
152
|
+
}
|
|
153
|
+
if (!entry) {
|
|
154
|
+
deps.writeErr(`[${action}] ${callId} is not in the pending list. It may have been decided\n` +
|
|
155
|
+
` already, or expired. Continuing would 404 or 409.\n`);
|
|
156
|
+
deps.exit(1);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
deps.write(`About to ${action}:\n${describeEntry(entry)}\n`);
|
|
160
|
+
if (!deps.isTTY) {
|
|
161
|
+
// Same discipline as `members set-password`: a confirmation that cannot
|
|
162
|
+
// be given must not be assumed. Refusing a pipe is the safe default for
|
|
163
|
+
// a command whose whole purpose is a deliberate human decision.
|
|
164
|
+
deps.writeErr(`[${action}] --review needs an interactive terminal to confirm.\n` +
|
|
165
|
+
` Drop --review to ${action} without the prompt.\n`);
|
|
166
|
+
deps.exit(1);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const ok = await deps.confirm(`${action} this action? [y/N] `);
|
|
170
|
+
if (!ok) {
|
|
171
|
+
deps.write(`Cancelled — nothing was ${action}d.\n`);
|
|
172
|
+
deps.exit(0);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const url = `${base}/${action}/${callId}`;
|
|
177
|
+
let res;
|
|
178
|
+
try {
|
|
179
|
+
res = await deps.fetchFn(url, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { ...auth, "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify(reason !== undefined ? { reason } : {}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
deps.writeErr(`[${action}] could not reach the bridge at ${base}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
187
|
+
deps.exit(1);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let body = {};
|
|
191
|
+
try {
|
|
192
|
+
body = (await res.json());
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* a non-JSON body is reported via status alone below */
|
|
196
|
+
}
|
|
197
|
+
if (wantJson) {
|
|
198
|
+
deps.write(`${JSON.stringify({ status: res.status, ...body }, null, 2)}\n`);
|
|
199
|
+
deps.exit(res.ok ? 0 : 1);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (res.ok) {
|
|
203
|
+
const decision = typeof body.decision === "string" ? body.decision : action;
|
|
204
|
+
deps.write(`${decision === "allow" ? "Approved" : "Rejected"} ${callId}\n`);
|
|
205
|
+
deps.exit(0);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Distinguish the three failures an operator can actually act on. A generic
|
|
209
|
+
// "request failed" would send them to check the bridge when the real answer
|
|
210
|
+
// is that a colleague already decided it.
|
|
211
|
+
if (res.status === 409 && body.error === "already_decided") {
|
|
212
|
+
deps.writeErr(`[${action}] ${callId} was already decided (${String(body.decision)}).\n` +
|
|
213
|
+
` Nothing changed. A concurrent dashboard or phone action got there first.\n`);
|
|
214
|
+
}
|
|
215
|
+
else if (res.status === 404) {
|
|
216
|
+
deps.writeErr(`[${action}] ${callId} is not pending — it may have expired or never existed.\n` +
|
|
217
|
+
` Run: patchwork approvals\n`);
|
|
218
|
+
}
|
|
219
|
+
else if (res.status === 401) {
|
|
220
|
+
deps.writeErr(`[${action}] the bridge rejected this credential (401). The lock file's token\n` +
|
|
221
|
+
` may be stale — restart the bridge, or check ~/.claude/ide/.\n`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
deps.writeErr(`[${action}] bridge returned ${res.status}: ${typeof body.error === "string" ? body.error : "(no error field)"}\n`);
|
|
225
|
+
}
|
|
226
|
+
deps.exit(1);
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=approve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.js","sourceRoot":"","sources":["../../src/commands/approve.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -213,6 +213,12 @@ const KNOWN_SUBCOMMANDS = [
|
|
|
213
213
|
"shadow-scan",
|
|
214
214
|
"workers",
|
|
215
215
|
"approvals",
|
|
216
|
+
// `approve`/`reject` decide ONE queued action; `approvals` is the read-only
|
|
217
|
+
// KPI report. The near-identical spelling is why the unknown-command
|
|
218
|
+
// suggester used to answer "Did you mean: approvals?" to someone running the
|
|
219
|
+
// exact string the dashboard told them to copy.
|
|
220
|
+
"approve",
|
|
221
|
+
"reject",
|
|
216
222
|
"gate",
|
|
217
223
|
"outcomes",
|
|
218
224
|
"butler",
|
|
@@ -2172,6 +2178,41 @@ if (process.argv[2] === "connect") {
|
|
|
2172
2178
|
});
|
|
2173
2179
|
})();
|
|
2174
2180
|
}
|
|
2181
|
+
// `patchwork approve <callId>` / `patchwork reject <callId>` — decide a queued
|
|
2182
|
+
// approval from the terminal. The dashboard has copied these strings to the
|
|
2183
|
+
// clipboard for a long time while the subcommands did not exist; they are also
|
|
2184
|
+
// the fallback path for when the dashboard or the phone relay is unavailable.
|
|
2185
|
+
if (process.argv[2] === "approve" || process.argv[2] === "reject") {
|
|
2186
|
+
const action = process.argv[2];
|
|
2187
|
+
(async () => {
|
|
2188
|
+
const { runApproveCommand } = await import("./commands/approve.js");
|
|
2189
|
+
const { findBridgeLockForTask } = await import("./commands/task.js");
|
|
2190
|
+
await runApproveCommand(action, process.argv.slice(3), {
|
|
2191
|
+
findBridgeLock: (port) => findBridgeLockForTask(port),
|
|
2192
|
+
fetchFn: fetch,
|
|
2193
|
+
write: (s) => process.stdout.write(s),
|
|
2194
|
+
writeErr: (s) => process.stderr.write(s),
|
|
2195
|
+
exit: (code) => process.exit(code),
|
|
2196
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
2197
|
+
confirm: async (question) => {
|
|
2198
|
+
const { createInterface } = await import("node:readline");
|
|
2199
|
+
const rl = createInterface({
|
|
2200
|
+
input: process.stdin,
|
|
2201
|
+
output: process.stdout,
|
|
2202
|
+
});
|
|
2203
|
+
try {
|
|
2204
|
+
const answer = await new Promise((resolve) => {
|
|
2205
|
+
rl.question(question, resolve);
|
|
2206
|
+
});
|
|
2207
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
2208
|
+
}
|
|
2209
|
+
finally {
|
|
2210
|
+
rl.close();
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
});
|
|
2214
|
+
})();
|
|
2215
|
+
}
|
|
2175
2216
|
// `patchwork analytics` — manage the self-hosted telemetry collector config.
|
|
2176
2217
|
// Replaces the brittle "endpoint+secret in launchd plist" pattern with a
|
|
2177
2218
|
// proper config file the bridge reads at startup.
|