@aryaminus/controlkeel-opencode 0.2.32 → 0.2.34

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.
@@ -9,8 +9,9 @@ Recommended flow:
9
9
  2. Ensure `controlkeel version` reports `>= 0.1.26`
10
10
  3. Run `controlkeel review plan submit --body-file .opencode/review-plan.md --submitted-by opencode --task-id <task_id> --json` (or use `--session-id <session_id>`)
11
11
  4. Read the returned `review.id` and `browser_url`
12
- 5. Wait with `controlkeel review plan wait --id <review_id> --timeout 30 --json`
13
- 6. Do not execute until the review is approved
12
+ 5. If `browser_url` is available, wait with `controlkeel review plan wait --id <review_id> --timeout 30 --json`
13
+ 6. If `browser_url` is missing/unreachable, do **not** loop on wait; ask for explicit user approval in chat and record it with `controlkeel review plan respond --id <review_id> --decision approved --feedback-notes "User approved in chat; browser unavailable" --json` (or `ck_review_feedback`)
14
+ 7. Do not execute until the review is approved
14
15
 
15
16
  Fallback when the `submit_plan` tool is stale in a long-running OpenCode session:
16
17
  - If the tool returns an error like `ControlKeel CLI [object Object] is too old`, run the CLI flow above directly.
@@ -309,6 +309,65 @@ export const ControlKeelGovernance: Plugin = async ({ project, client, $, direct
309
309
  throw new Error("ControlKeel did not return a review id")
310
310
  }
311
311
 
312
+ const openEnv = process.env.LOGGER_LEVEL
313
+ ? process.env
314
+ : { ...process.env, LOGGER_LEVEL: "warning" }
315
+
316
+ let openPayload = null
317
+
318
+ try {
319
+ const openProc = Bun.spawn(["controlkeel", "review", "plan", "open", "--id", String(reviewId), "--json"], {
320
+ stdout: "pipe",
321
+ stderr: "pipe",
322
+ env: openEnv,
323
+ })
324
+ const openOut = await new Response(openProc.stdout).text()
325
+ const openErr = await new Response(openProc.stderr).text()
326
+ const openExit = await openProc.exited
327
+
328
+ if (openExit === 0) {
329
+ openPayload = parseJson([openOut, openErr].filter(Boolean).join("\n"))
330
+ } else {
331
+ openPayload = {
332
+ error: `controlkeel review plan open failed with exit code ${openExit}${openErr.trim() ? `: ${openErr.trim()}` : ""}`,
333
+ }
334
+ }
335
+ } catch (error) {
336
+ openPayload = { error: error instanceof Error ? error.message : String(error) }
337
+ }
338
+
339
+ const browserUrl =
340
+ openPayload?.browser_url ??
341
+ submitPayload?.browser_url ??
342
+ submitPayload?.url ??
343
+ submitPayload?.review?.browser_url ??
344
+ null
345
+
346
+ const openError = typeof openPayload?.open_error === "string" ? openPayload.open_error.trim() : ""
347
+ const openFailure = typeof openPayload?.error === "string" ? openPayload.error.trim() : ""
348
+
349
+ const remoteLocalhostMismatch =
350
+ typeof browserUrl === "string" &&
351
+ browserUrl.includes("localhost") &&
352
+ openPayload?.remote === true
353
+
354
+ if (!browserUrl || openError || openFailure || remoteLocalhostMismatch) {
355
+ return {
356
+ reviewId,
357
+ submitPayload,
358
+ openPayload,
359
+ browserUrl,
360
+ status: submitPayload?.review?.status ?? "pending",
361
+ feedbackNotes: submitPayload?.review?.feedback_notes ?? null,
362
+ timedOut: false,
363
+ waitSkipped: true,
364
+ manualApprovalRequired: true,
365
+ reason: !browserUrl ? "browser_url_unavailable" : "browser_unreachable",
366
+ guidance:
367
+ "Browser review is unavailable from this environment. Ask the user for explicit approval in chat, then record it with `controlkeel review plan respond --id <review_id> --decision approved --feedback-notes \"User approved in chat; browser unavailable\" --json` or `ck_review_feedback`.",
368
+ }
369
+ }
370
+
312
371
  const waitEnv = process.env.LOGGER_LEVEL
313
372
  ? process.env
314
373
  : { ...process.env, LOGGER_LEVEL: "warning" }
@@ -348,10 +407,13 @@ export const ControlKeelGovernance: Plugin = async ({ project, client, $, direct
348
407
  return {
349
408
  reviewId,
350
409
  submitPayload,
410
+ openPayload,
351
411
  waitPayload,
352
- browserUrl: waitPayload?.browser_url ?? submitPayload?.browser_url,
412
+ browserUrl: waitPayload?.browser_url ?? browserUrl,
353
413
  status: waitPayload?.review?.status,
354
414
  feedbackNotes: waitPayload?.review?.feedback_notes ?? null,
415
+ waitSkipped: false,
416
+ manualApprovalRequired: false,
355
417
  }
356
418
  } finally {
357
419
  // Clean up temp file
package/index.js CHANGED
@@ -292,6 +292,65 @@ export const ControlKeelGovernance = async ({ $, directory }) => {
292
292
  throw new Error("ControlKeel did not return a review id")
293
293
  }
294
294
 
295
+ const openEnv = process.env.LOGGER_LEVEL
296
+ ? process.env
297
+ : { ...process.env, LOGGER_LEVEL: "warning" }
298
+
299
+ let openPayload = null
300
+
301
+ try {
302
+ const openProc = Bun.spawn(["controlkeel", "review", "plan", "open", "--id", String(reviewId), "--json"], {
303
+ stdout: "pipe",
304
+ stderr: "pipe",
305
+ env: openEnv,
306
+ })
307
+ const openOut = await new Response(openProc.stdout).text()
308
+ const openErr = await new Response(openProc.stderr).text()
309
+ const openExit = await openProc.exited
310
+
311
+ if (openExit === 0) {
312
+ openPayload = parseJson([openOut, openErr].filter(Boolean).join("\n"))
313
+ } else {
314
+ openPayload = {
315
+ error: `controlkeel review plan open failed with exit code ${openExit}${openErr.trim() ? `: ${openErr.trim()}` : ""}`,
316
+ }
317
+ }
318
+ } catch (error) {
319
+ openPayload = { error: error instanceof Error ? error.message : String(error) }
320
+ }
321
+
322
+ const browserUrl =
323
+ openPayload?.browser_url ??
324
+ submitPayload?.browser_url ??
325
+ submitPayload?.url ??
326
+ submitPayload?.review?.browser_url ??
327
+ null
328
+
329
+ const openError = typeof openPayload?.open_error === "string" ? openPayload.open_error.trim() : ""
330
+ const openFailure = typeof openPayload?.error === "string" ? openPayload.error.trim() : ""
331
+
332
+ const remoteLocalhostMismatch =
333
+ typeof browserUrl === "string" &&
334
+ browserUrl.includes("localhost") &&
335
+ openPayload?.remote === true
336
+
337
+ if (!browserUrl || openError || openFailure || remoteLocalhostMismatch) {
338
+ return {
339
+ reviewId,
340
+ submitPayload,
341
+ openPayload,
342
+ browserUrl,
343
+ status: submitPayload?.review?.status ?? "pending",
344
+ feedbackNotes: submitPayload?.review?.feedback_notes ?? null,
345
+ timedOut: false,
346
+ waitSkipped: true,
347
+ manualApprovalRequired: true,
348
+ reason: !browserUrl ? "browser_url_unavailable" : "browser_unreachable",
349
+ guidance:
350
+ "Browser review is unavailable from this environment. Ask the user for explicit approval in chat, then record it with `controlkeel review plan respond --id <review_id> --decision approved --feedback-notes \"User approved in chat; browser unavailable\" --json` or `ck_review_feedback`.",
351
+ }
352
+ }
353
+
295
354
  const waitEnv = process.env.LOGGER_LEVEL
296
355
  ? process.env
297
356
  : { ...process.env, LOGGER_LEVEL: "warning" }
@@ -331,10 +390,13 @@ export const ControlKeelGovernance = async ({ $, directory }) => {
331
390
  return {
332
391
  reviewId,
333
392
  submitPayload,
393
+ openPayload,
334
394
  waitPayload,
335
- browserUrl: waitPayload?.browser_url ?? submitPayload?.browser_url,
395
+ browserUrl: waitPayload?.browser_url ?? browserUrl,
336
396
  status: waitPayload?.review?.status,
337
397
  feedbackNotes: waitPayload?.review?.feedback_notes ?? null,
398
+ waitSkipped: false,
399
+ manualApprovalRequired: false,
338
400
  }
339
401
  } finally {
340
402
  // Clean up temp file
package/package.json CHANGED
@@ -35,5 +35,5 @@
35
35
  "url": "git+https://github.com/aryaminus/controlkeel.git"
36
36
  },
37
37
  "type": "module",
38
- "version": "0.2.32"
38
+ "version": "0.2.34"
39
39
  }