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.
@@ -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.