ework-web 0.10.62 → 0.10.64

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.62",
3
+ "version": "0.10.64",
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",
@@ -118,3 +118,39 @@ export async function resolveDaemonEndpoint(daemonId: number): Promise<string |
118
118
  return null;
119
119
  }
120
120
  }
121
+
122
+ export interface RunningSessionInfo {
123
+ issueNumber: string;
124
+ sessionId: string;
125
+ daemonId: number;
126
+ }
127
+
128
+ /**
129
+ * Query daemon DB for sessions with state='running' belonging to a specific project scope.
130
+ * Uses REAL daemon session state instead of the display-only ai_status field.
131
+ */
132
+ export async function getRunningSessionsForProject(scopeKey: string): Promise<RunningSessionInfo[]> {
133
+ try {
134
+ const rows = await getDB().all<{
135
+ tracker_issue_id: string;
136
+ opencode_session_id: string;
137
+ daemon_id: number;
138
+ }>(
139
+ `SELECT i.tracker_issue_id, s.opencode_session_id, i.owner_daemon_id AS daemon_id
140
+ FROM {{d_op_sessions}} s
141
+ JOIN {{d_issues}} i ON i.uid = s.issue_id
142
+ WHERE s.state = 'running'
143
+ AND i.tracker_scope_key = ?
144
+ AND s.opencode_session_id IS NOT NULL`,
145
+ [scopeKey],
146
+ );
147
+ return rows.map((r) => ({
148
+ issueNumber: r.tracker_issue_id,
149
+ sessionId: r.opencode_session_id,
150
+ daemonId: r.daemon_id,
151
+ }));
152
+ } catch (e) {
153
+ log.info(`coordination: running-sessions query failed (${e instanceof Error ? e.message : String(e)})`);
154
+ return [];
155
+ }
156
+ }
package/src/giteaApi.ts CHANGED
@@ -153,7 +153,7 @@ export async function handleGiteaApi(
153
153
  const limitRaw = Number(url.searchParams.get("limit") ?? 50);
154
154
  const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : 50;
155
155
  try {
156
- const rows = await listAllIssues({ q, state, limit, viewerLogin: user.login, viewerIsAdmin: user.is_admin === 1 });
156
+ const rows = await listAllIssues({ q, state, limit, viewerLogin: user.login, viewerIsAdmin: caller.is_admin === 1 && user.is_admin === 1 });
157
157
  const body = [];
158
158
  for (const row of rows) {
159
159
  const project = await getProject(row.project_owner, row.project_name);
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import { homedir } from "os";
6
6
  import { loadConfig, DB_OVERRIDABLE, parseOverride, resolveTtsBackend } from "./config";
7
7
  import type { Config } from "./config";
8
8
  import { setConfig, deleteConfig, getConfigAll, initDB, getDB } from "./db";
9
- import { getActiveDaemons, listAllDaemons, getSessionDaemonMap, resolveDaemonEndpoint } from "./coordination";
9
+ import { getActiveDaemons, listAllDaemons, getSessionDaemonMap, resolveDaemonEndpoint, getRunningSessionsForProject } from "./coordination";
10
10
  import { testMysqlConnection, migrateSqliteToMysql, writeMysqlEnv, migrateMysqlToSqlite, writeSqliteEnv, migrateDaemonSqliteToMysql, generateMysqlDDL } from "./db-admin";
11
11
  import type { MysqlTargetOpts } from "./db-admin";
12
12
  import { checkAuth, makeAuthCookieHeader, clearAuthCookieHeader, loginHTML, sanitizeNext, ensureBootstrapAdmin, ensureBootstrapSystem, isReservedSystemLogin } from "./auth";
@@ -1994,12 +1994,17 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
1994
1994
  return Response.redirect(`${back}?err=${encodeURIComponent("无权限")}`, 303);
1995
1995
  }
1996
1996
  const issues = await listIssues(project.id, { state: "all" });
1997
- const processing = issues.filter((it) => it.ai_status === "processing");
1998
- for (const it of processing) {
1999
- await updateIssueAiStatus(it.id, "halted");
2000
- void emitStatusChanged(project.id, it.number, "processing", "halted", ctx.user!.login, url.origin);
1997
+ const running = await getRunningSessionsForProject(`${owner}/${repo}`);
1998
+ const issueIds = new Set<number>();
1999
+ for (const r of running) {
2000
+ const it = issues.find((i) => String(i.number) === r.issueNumber);
2001
+ if (it) issueIds.add(it.id);
2001
2002
  }
2002
- return Response.redirect(`${back}?ok=1&ok_msg=${encodeURIComponent(`已停止 ${processing.length} 个运行中AI会话`)}`, 303);
2003
+ for (const id of issueIds) {
2004
+ await updateIssueAiStatus(id, "halted");
2005
+ void emitStatusChanged(project.id, id, "processing", "halted", ctx.user!.login, url.origin);
2006
+ }
2007
+ return Response.redirect(`${back}?ok=1&ok_msg=${encodeURIComponent(`已停止 ${issueIds.size} 个运行中AI会话`)}`, 303);
2003
2008
  }
2004
2009
 
2005
2010
  const settingsDispatchMatch = url.pathname.match(SETTINGS_DISPATCH_RE);
@@ -2255,8 +2260,8 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
2255
2260
  const dispatchCfg = await getConfigAll();
2256
2261
  const dispatchOff = dispatchCfg[`dispatchOff:${owner}/${repo}`] === "1";
2257
2262
  const globalDispatchOff = dispatchCfg["dispatchEnabled"] === "false";
2258
- const processingIssues = await listIssues(project.id, { state: "all" });
2259
- const processingCount = processingIssues.filter((it) => it.ai_status === "processing").length;
2263
+ const running = await getRunningSessionsForProject(`${owner}/${repo}`);
2264
+ const processingCount = new Set(running.map((r) => r.issueNumber)).size;
2260
2265
  return html(buildProjectAiPage(project, dispatchOff, globalDispatchOff, processingCount).html);
2261
2266
  }
2262
2267
 
package/src/webhooks.ts CHANGED
@@ -756,7 +756,7 @@ export async function emitStatusChanged(
756
756
  const project = await getProjectById(projectId);
757
757
  if (!project) return;
758
758
  const issue = await getIssueById(issueId);
759
- if (!issue) return;
759
+ if (!issue) { log.warn(`emitStatusChanged: issue ${issueId} not found (project=${projectId}) — silent skip prevented`); return; }
760
760
  const commentCount = await countCommentsSafe(issueId);
761
761
  const repo = buildRepository(project, origin);
762
762
  const sender = buildUser(actor, origin);