ework-web 0.10.67 → 0.10.69

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 +30 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.67",
3
+ "version": "0.10.69",
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
@@ -28,6 +28,7 @@ import {
28
28
  resolveModel,
29
29
  listLabelsForIssue,
30
30
  getUserByLogin,
31
+ type UserRow,
31
32
  type IssueRow,
32
33
  type ProjectRow,
33
34
  type CommentRow,
@@ -229,6 +230,9 @@ interface PayloadUser {
229
230
  html_url: string;
230
231
  is_admin: boolean;
231
232
  language: string;
233
+ kind?: string;
234
+ display_name?: string | null;
235
+ is_active?: boolean;
232
236
  }
233
237
 
234
238
  interface PayloadLabel {
@@ -348,6 +352,18 @@ function buildUser(login: string, origin: string): PayloadUser {
348
352
  };
349
353
  }
350
354
 
355
+ function buildUserFromRow(user: UserRow, origin: string): PayloadUser {
356
+ return {
357
+ ...buildUser(user.login, origin),
358
+ full_name: user.display_name ?? user.login,
359
+ email: user.email ?? `${user.login}@${new URL(origin).host ?? "localhost"}.noreply.ework`,
360
+ is_admin: user.is_admin === 1,
361
+ kind: user.kind,
362
+ display_name: user.display_name,
363
+ is_active: user.is_active === 1,
364
+ };
365
+ }
366
+
351
367
  function buildRepository(project: ProjectRow, origin: string, model?: string): PayloadRepository {
352
368
  const fullName = `${project.owner}/${project.name}`;
353
369
  const htmlUrl = `${origin}/${encodeURIComponent(project.owner)}/${encodeURIComponent(project.name)}`;
@@ -639,11 +655,6 @@ export async function emitIssueEvent(
639
655
  const issue = await getIssueById(issueId);
640
656
  if (!issue) { log.warn(`emitIssueEvent: issue ${issueId} not found — silent skip prevented`); return; }
641
657
 
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
658
  const cfg = await getConfigAll();
648
659
  const scopeKey = `${project.owner}/${project.name}`;
649
660
  const globalOff = cfg["dispatchEnabled"] === "false";
@@ -651,11 +662,6 @@ export async function emitIssueEvent(
651
662
  const issueOff = issue.ai_status === "dispatch_off" || issue.ai_status === "halted";
652
663
  const dispatchOff = globalOff || projectOff || issueOff;
653
664
 
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
-
659
665
  const commentCount = await countCommentsSafe(issueId);
660
666
  // Resolve model: project override > global default. Empty string = no
661
667
  // override (daemon omits --model, lets opencode pick). globalDefault
@@ -666,6 +672,11 @@ export async function emitIssueEvent(
666
672
  const payload = buildIssuePayload(issue, project, commentCount, action, origin, model, labels);
667
673
  (payload as IssueEventPayload).event_id = randomUUID();
668
674
  (payload as IssueEventPayload).dispatch_off = dispatchOff;
675
+ const authorUser = await getUserByLogin(issue.author);
676
+ if (authorUser) {
677
+ payload.sender = buildUserFromRow(authorUser, origin);
678
+ payload.issue.user = buildUserFromRow(authorUser, origin);
679
+ }
669
680
  const rawBody = JSON.stringify(payload);
670
681
  await fanOut(projectId, "issues", rawBody);
671
682
  } catch (e) {
@@ -700,7 +711,13 @@ export async function emitCommentEvent(
700
711
  const labels = await toPayloadLabels(issueId);
701
712
  const payload = buildCommentPayload(issue, comment, project, commentCount, origin, model, labels);
702
713
  (payload as CommentEventPayload).event_id = randomUUID();
703
- (payload as CommentEventPayload).comment.author_kind = authorUser?.kind ?? "human";
714
+ if (authorUser) {
715
+ payload.sender = buildUserFromRow(authorUser, origin);
716
+ payload.comment.user = buildUserFromRow(authorUser, origin);
717
+ (payload as CommentEventPayload).comment.author_kind = authorUser.kind;
718
+ } else {
719
+ (payload as CommentEventPayload).comment.author_kind = "human";
720
+ }
704
721
  const rawBody = JSON.stringify(payload);
705
722
  await fanOut(projectId, "issue_comment", rawBody);
706
723
  } catch (e) {
@@ -728,8 +745,9 @@ export async function emitStatusChanged(
728
745
  const issue = await getIssueById(issueId);
729
746
  if (!issue) { log.warn(`emitStatusChanged: issue ${issueId} not found (project=${projectId}) — silent skip prevented`); return; }
730
747
  const commentCount = await countCommentsSafe(issueId);
748
+ const actorUser = await getUserByLogin(actor);
749
+ const sender = actorUser ? buildUserFromRow(actorUser, origin) : buildUser(actor, origin);
731
750
  const repo = buildRepository(project, origin);
732
- const sender = buildUser(actor, origin);
733
751
  const issuePayload = buildIssue(issue, project, commentCount, origin);
734
752
  const payload: StatusChangedPayload = {
735
753
  event_id: randomUUID(),