ework-web 0.10.45 → 0.10.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.45",
3
+ "version": "0.10.47",
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/config.ts CHANGED
@@ -82,7 +82,6 @@ export const configSchema = z.object({
82
82
  internalAuthHook: z.string().default(""),
83
83
  userAuthHook: z.string().default(""),
84
84
  loginPage: z.string().default(""),
85
- statusHook: z.string().default(""),
86
85
  // Default "provider/model" string passed to `opencode run --model <X>`.
87
86
  // Empty = let opencode pick per its own opencode.json + env. ework-daemon
88
87
  // pushes this (or the per-project override) on every spawn to defend
@@ -193,7 +192,6 @@ export async function loadConfig(): Promise<Config> {
193
192
  internalAuthHook: process.env.WORK_INTERNAL_AUTH_HOOK ?? "",
194
193
  userAuthHook: process.env.WORK_USER_AUTH_HOOK ?? "",
195
194
  loginPage: process.env.WORK_LOGIN_PAGE ?? "",
196
- statusHook: process.env.WORK_STATUS_HOOK ?? "",
197
195
  defaultModel: db.defaultModel ?? process.env.WORK_DEFAULT_MODEL,
198
196
  autowireActive: process.env.WORK_AUTOWIRE_ACTIVE !== "false",
199
197
  webhookMaxConcurrent: Number(process.env.WORK_WEBHOOK_MAX_CONCURRENT ?? "6"),
package/src/index.ts CHANGED
@@ -89,6 +89,7 @@ import {
89
89
  emitCommentEvent,
90
90
  emitStatusChanged,
91
91
  emitPingEvent,
92
+ migrateWebhookEvents,
92
93
  listWebhooks,
93
94
  createWebhook,
94
95
  deleteWebhook,
@@ -156,7 +157,7 @@ async function autoWireDaemon(projectId: number, origin: string): Promise<void>
156
157
  project_id: projectId,
157
158
  url: target,
158
159
  secret: cfg.daemonWebhookSecret,
159
- events: ["issues", "issue_comment"],
160
+ events: ["issues", "issue_comment", "status_changed"],
160
161
  });
161
162
  void emitPingEvent(projectId, origin);
162
163
  }
@@ -185,6 +186,7 @@ async function autoWireAllProjects(origin: string): Promise<void> {
185
186
  }
186
187
 
187
188
  void autoWireAllProjects(`http://${cfg.host}:${cfg.port}`);
189
+ void migrateWebhookEvents().catch((e) => log.warn("migrateWebhookEvents failed", { err: e as Error }));
188
190
 
189
191
  const SEC_HEADERS: Record<string, string> = {
190
192
  "content-security-policy": buildCsp(cfg),
@@ -1729,8 +1731,8 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
1729
1731
  const url_ = String(form.get("url") ?? "").trim();
1730
1732
  const secret = String(form.get("secret") ?? "");
1731
1733
  const events = form.getAll("events") as string[];
1732
- const validEvents = (events.length > 0 ? events : ["issues", "issue_comment"])
1733
- .filter((e): e is WebhookEventName => e === "issues" || e === "issue_comment");
1734
+ const validEvents = (events.length > 0 ? events : ["issues", "issue_comment", "status_changed"])
1735
+ .filter((e): e is WebhookEventName => e === "issues" || e === "issue_comment" || e === "status_changed");
1734
1736
  const wh = await createWebhook({
1735
1737
  project_id: project.id,
1736
1738
  url: url_,
@@ -17,7 +17,6 @@ export interface LayoutProps {
17
17
  labels?: { id: number; name: string; color: string }[];
18
18
  canEditLabels?: boolean;
19
19
  aiStatus?: string;
20
- statusHook?: string;
21
20
  }
22
21
 
23
22
  export const THEME_CSS = `
@@ -169,7 +168,7 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
169
168
  if (!m) return "";
170
169
  return `<span class="ai-badge ${m.cls}">${m.label}</span>`;
171
170
  })();
172
- const haltBtnHtml = props.writesEnabled !== false && props.aiStatus === "processing"
171
+ const haltBtnHtml = props.writesEnabled !== false && props.aiStatus !== "halted"
173
172
  ? `<button type="button" id="haltBtn" class="halt-btn" title="停止 AI 处理">⏹ 停止</button>`
174
173
  : "";
175
174
  return `<!doctype html>
@@ -131,7 +131,6 @@ export async function buildIssueThread(
131
131
  labels: labels.map((l) => ({ id: l.id, name: l.name, color: l.color })),
132
132
  canEditLabels: cfg.writesEnabled !== false,
133
133
  aiStatus: issue.ai_status ?? "",
134
- statusHook: cfg.statusHook ?? "",
135
134
  },
136
135
  safeJsonEmbed(payload),
137
136
  displayViews.map((v) => renderCommentCard(v, cfg)).join("")
package/src/webhooks.ts CHANGED
@@ -60,7 +60,7 @@ export interface WebhookDeliveryRow {
60
60
  created_at: string;
61
61
  }
62
62
 
63
- const DEFAULT_EVENTS: WebhookEventName[] = ["issues", "issue_comment"];
63
+ const DEFAULT_EVENTS: WebhookEventName[] = ["issues", "issue_comment", "status_changed"];
64
64
  const MAX_RESPONSE_LOG_BYTES = 8192;
65
65
  const HTTP_TIMEOUT_MS = 10_000;
66
66
  const RETRY_DELAYS_MS = [0, 2_000, 8_000];
@@ -112,7 +112,7 @@ function parseEvents(events: unknown): WebhookEventName[] {
112
112
  try {
113
113
  const arr = JSON.parse(events) as unknown[];
114
114
  const valid = arr.filter(
115
- (v): v is WebhookEventName => v === "issues" || v === "issue_comment"
115
+ (v): v is WebhookEventName => v === "issues" || v === "issue_comment" || v === "status_changed"
116
116
  );
117
117
  return valid.length > 0 ? valid : DEFAULT_EVENTS;
118
118
  } catch {
@@ -122,6 +122,20 @@ function parseEvents(events: unknown): WebhookEventName[] {
122
122
 
123
123
  // ─── CRUD ────────────────────────────────────────────────────
124
124
 
125
+ export async function migrateWebhookEvents(): Promise<void> {
126
+ const rows = await getDB().all<WebhookRow>("SELECT * FROM {{webhooks}}");
127
+ for (const wh of rows) {
128
+ const current = parseEvents(wh.events);
129
+ if (!current.includes("status_changed")) {
130
+ const updated = [...current, "status_changed"];
131
+ await getDB().run("UPDATE {{webhooks}} SET events = ? WHERE id = ?", [
132
+ JSON.stringify(updated),
133
+ wh.id,
134
+ ]);
135
+ }
136
+ }
137
+ }
138
+
125
139
  export async function listWebhooks(projectId: number): Promise<WebhookRow[]> {
126
140
  return await getDB().all<WebhookRow>("SELECT * FROM {{webhooks}} WHERE project_id = ? ORDER BY id", [projectId]);
127
141
  }
@@ -273,6 +287,7 @@ interface PayloadIssue {
273
287
  pull_request: null;
274
288
  repository: PayloadRepository;
275
289
  user: PayloadUser;
290
+ ai_status?: string;
276
291
  }
277
292
 
278
293
  interface PayloadComment {
@@ -404,6 +419,7 @@ function buildIssue(
404
419
  pull_request: null,
405
420
  repository: buildRepository(project, origin, model),
406
421
  user: buildUser(issue.author, origin),
422
+ ai_status: issue.ai_status ?? "",
407
423
  };
408
424
  }
409
425