ework-web 0.10.66 → 0.10.67

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/webhooks.ts +20 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.66",
3
+ "version": "0.10.67",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
package/src/webhooks.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  // the full retry trail rather than an opaque final status.
17
17
 
18
18
  import { createHmac, randomUUID } from "node:crypto";
19
- import { getDB } from "./db";
19
+ import { getDB, getConfigAll } from "./db";
20
20
  import { log } from "./logger";
21
21
  import { loadConfig } from "./config";
22
22
  import {
@@ -312,6 +312,7 @@ interface IssueEventPayload {
312
312
  commit_id?: string;
313
313
  commit_url?: string;
314
314
  url?: string;
315
+ dispatch_off?: boolean;
315
316
  }
316
317
 
317
318
  interface CommentEventPayload {
@@ -638,9 +639,23 @@ export async function emitIssueEvent(
638
639
  const issue = await getIssueById(issueId);
639
640
  if (!issue) { log.warn(`emitIssueEvent: issue ${issueId} not found — silent skip prevented`); return; }
640
641
 
641
- // Web is a dumb message bus — always fan out. Wake policy is enforced
642
- // downstream (daemon handleEvent). Web only suppresses for subscription-
643
- // level concerns (none currently).
642
+ // Web is a dumb message bus for comments — always fan out.
643
+ // BUT for issue_opened, dispatch state is a subscription-level switch:
644
+ // if dispatch is off (global/project/issue), don't emit at all.
645
+ // This is the "webhook switch" — downstream never sees the event.
646
+ // We still include dispatch_off in payload for defense-in-depth.
647
+ const cfg = await getConfigAll();
648
+ const scopeKey = `${project.owner}/${project.name}`;
649
+ const globalOff = cfg["dispatchEnabled"] === "false";
650
+ const projectOff = cfg[`dispatchOff:${scopeKey}`] === "1";
651
+ const issueOff = issue.ai_status === "dispatch_off" || issue.ai_status === "halted";
652
+ const dispatchOff = globalOff || projectOff || issueOff;
653
+
654
+ if (action === "opened" && dispatchOff) {
655
+ log.info(`webhook: issue_opened suppressed (dispatch off: global=${globalOff} project=${projectOff} issue=${issueOff}) for ${scopeKey}#${issue.number}`);
656
+ return;
657
+ }
658
+
644
659
  const commentCount = await countCommentsSafe(issueId);
645
660
  // Resolve model: project override > global default. Empty string = no
646
661
  // override (daemon omits --model, lets opencode pick). globalDefault
@@ -650,6 +665,7 @@ export async function emitIssueEvent(
650
665
  const labels = await toPayloadLabels(issueId);
651
666
  const payload = buildIssuePayload(issue, project, commentCount, action, origin, model, labels);
652
667
  (payload as IssueEventPayload).event_id = randomUUID();
668
+ (payload as IssueEventPayload).dispatch_off = dispatchOff;
653
669
  const rawBody = JSON.stringify(payload);
654
670
  await fanOut(projectId, "issues", rawBody);
655
671
  } catch (e) {