@lmzhen/dsh-evolution-merge-ui 0.30.16 → 0.30.18

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/lib/client.js CHANGED
@@ -5,14 +5,13 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- //#region src/client/routes.generated.ts
8
+ //#endregion
9
+ //#region src/client/api.ts
9
10
  /**
10
- * Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
11
- * A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
12
- * external), so the table is mirrored here mechanically; a rename in core regenerates all three
13
- * client copies, and the generator's --check makes a stale copy red.
11
+ * The two read routes this panel uses. Parses the frozen wireVersion=1 envelopes
12
+ * and maps them to the panel view types; the panel never sees a path or a raw host body.
14
13
  */
15
- const ROUTES = {
14
+ const HOST_ROUTES = {
16
15
  approval: {
17
16
  pending: "/api/dsh-evolution/approval/pending",
18
17
  preview: "/api/dsh-evolution/approval/preview",
@@ -31,56 +30,53 @@ window.__ModuleLoader__.load({
31
30
  merge: {
32
31
  view: "/api/dsh-evolution/merge/view",
33
32
  rows: "/api/dsh-evolution/merge/rows",
33
+ run: "/api/dsh-evolution/merge/run",
34
+ runs: "/api/dsh-evolution/merge/runs",
34
35
  body: "/api/dsh-evolution/merge/body",
35
36
  history: "/api/dsh-evolution/merge/history"
36
37
  },
37
38
  diagnostics: { view: "/api/dsh-evolution/diagnostics" }
38
- };
39
- //#endregion
40
- //#region src/client/route-client.generated.ts
41
- /** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
42
- function classify(status) {
43
- if (status === 401) return {
44
- kind: "unauthenticated",
45
- status: 401
46
- };
47
- if (status === 403) return {
48
- kind: "forbidden",
49
- status: 403
50
- };
51
- if (status === 404) return {
52
- kind: "unmounted",
53
- status: 404
54
- };
55
- return {
56
- kind: "server",
57
- status
58
- };
59
- }
60
- /**
61
- * One request, no retries and no body parsing for the platform's admission answers.
62
- * @param doFetch - the fetch in use (the browser's, or a spec's stub).
63
- * @param path - the exact route path from the generated ROUTES table.
64
- * @param init - standard RequestInit.
65
- * @returns the data, or the classified failure (network/parse failures read as 'server').
66
- */
67
- async function requestJson(doFetch, path, init) {
39
+ }.merge;
40
+ async function requestWire(path) {
68
41
  let response;
69
42
  try {
70
- response = await doFetch(path, init);
71
- } catch (error) {
43
+ response = await fetch(path, { headers: { accept: "application/json" } });
44
+ } catch {
72
45
  return {
73
46
  ok: false,
74
47
  failure: {
75
48
  kind: "server",
76
- status: 0,
77
- detail: error instanceof Error ? error.message : String(error)
49
+ status: 0
78
50
  }
79
51
  };
80
52
  }
53
+ if (response.status === 401) return {
54
+ ok: false,
55
+ failure: {
56
+ kind: "unauthenticated",
57
+ status: 401
58
+ }
59
+ };
60
+ if (response.status === 403) return {
61
+ ok: false,
62
+ failure: {
63
+ kind: "forbidden",
64
+ status: 403
65
+ }
66
+ };
67
+ if (response.status === 404) return {
68
+ ok: false,
69
+ failure: {
70
+ kind: "unmounted",
71
+ status: 404
72
+ }
73
+ };
81
74
  if (!response.ok) return {
82
75
  ok: false,
83
- failure: classify(response.status)
76
+ failure: {
77
+ kind: "server",
78
+ status: response.status
79
+ }
84
80
  };
85
81
  let body;
86
82
  try {
@@ -94,81 +90,158 @@ window.__ModuleLoader__.load({
94
90
  }
95
91
  };
96
92
  }
97
- if (typeof body === "object" && body !== null) {
98
- const record = body;
99
- if (record.ok === true) return {
100
- ok: true,
101
- data: record.data
102
- };
103
- const nested = record.error;
104
- const errorRecord = typeof nested === "object" && nested !== null ? nested : void 0;
105
- const code = typeof record.code === "string" && record.code !== "" ? record.code : typeof errorRecord?.code === "string" ? errorRecord.code : void 0;
106
- const message = typeof record.message === "string" && record.message !== "" ? record.message : typeof errorRecord?.message === "string" ? errorRecord.message : void 0;
107
- if (record.ok === false && code !== void 0 && message !== void 0 && message !== "") return {
108
- ok: false,
109
- failure: {
110
- kind: "refused",
111
- code,
112
- message
113
- }
114
- };
115
- }
116
- return {
93
+ const record = body;
94
+ if (record.wireVersion !== 1) return {
117
95
  ok: false,
118
96
  failure: {
119
97
  kind: "server",
120
98
  status: response.status
121
99
  }
122
100
  };
101
+ if (typeof record.code === "string") return {
102
+ ok: false,
103
+ failure: {
104
+ kind: "refused",
105
+ code: record.code,
106
+ message: typeof record.message === "string" ? record.message : record.code
107
+ }
108
+ };
109
+ return {
110
+ ok: true,
111
+ payload: body
112
+ };
123
113
  }
124
- //#endregion
125
- //#region src/client/api.ts
126
- /**
127
- * The two read routes this panel uses, through the generated client route table and the
128
- * shared route client. A refusal keeps the host's own sentence.
129
- */
130
- const HOST_ROUTES = ROUTES.merge;
114
+ const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
115
+ const toRun = (run) => ({
116
+ id: run.id,
117
+ state: run.state,
118
+ ...run.command === void 0 ? {} : { command: run.command },
119
+ ...run.stage === void 0 ? {} : { stage: run.stage },
120
+ ...run.progress === null || run.progress === void 0 ? {} : { progress: {
121
+ done: run.progress.done,
122
+ total: run.progress.total
123
+ } },
124
+ startedAt: run.startedAt
125
+ });
126
+ const toRow = (row) => ({
127
+ id: row.id,
128
+ pairKey: row.id,
129
+ a: row.a,
130
+ b: row.b,
131
+ state: row.state,
132
+ queue: row.queue,
133
+ stage: null,
134
+ label: row.a + " + " + row.b,
135
+ updatedAt: new Date(row.updatedAt).toISOString(),
136
+ createdAt: new Date(row.createdAt).toISOString(),
137
+ ...row.target === void 0 ? {} : {
138
+ target: row.target.requested,
139
+ ...row.target.actual === void 0 ? {} : { targetActual: row.target.actual },
140
+ ...row.target.created === void 0 ? {} : { targetCreated: row.target.created }
141
+ },
142
+ ...row.staging?.state === void 0 ? {} : { stagingState: row.staging.state },
143
+ ...row.verifySubState === void 0 ? {} : { verifySubState: row.verifySubState },
144
+ ...row.skipKind === void 0 ? {} : { skipKind: row.skipKind },
145
+ ...row.bodies === void 0 ? {} : { bodies: row.bodies.map((body) => ({
146
+ name: body.name,
147
+ side: body.side
148
+ })) },
149
+ ...row.confidence === void 0 ? {} : { confidence: row.confidence },
150
+ ...row.expiresAt === void 0 ? {} : { expiresAt: row.expiresAt },
151
+ ...row.summary === void 0 ? {} : { summary: [
152
+ row.summary.a,
153
+ row.summary.b,
154
+ row.summary.rationale
155
+ ].filter((value) => value !== null) },
156
+ ...row.reason === void 0 ? {} : { reason: row.reason },
157
+ ...row.origin === void 0 ? {} : { origin: row.origin === "system" ? "agent" : row.origin },
158
+ ...row.failure === void 0 ? {} : { failure: {
159
+ stage: row.failure.stage,
160
+ code: row.failure.code,
161
+ message: row.failure.detail ?? row.failure.code,
162
+ retryable: row.failure.retryable
163
+ } },
164
+ ...row.staging?.expiresAt === void 0 ? {} : { stagingExpiresAt: row.staging.expiresAt },
165
+ ...row.expired === void 0 ? {} : { expired: row.expired.kind },
166
+ ...row.attempts === void 0 ? {} : { attempts: row.attempts },
167
+ actions: row.actions.map((action) => ({
168
+ id: action.id,
169
+ command: action.command,
170
+ confirm: action.confirm,
171
+ enabled: action.enabled,
172
+ ...action.disabledReason === void 0 ? {} : { disabledReason: action.disabledReason }
173
+ }))
174
+ });
131
175
  async function view() {
132
- const answer = await requestJson(fetch, HOST_ROUTES.view, { headers: { accept: "application/json" } });
133
- return answer.ok ? {
176
+ const answer = await requestWire(HOST_ROUTES.view);
177
+ if (!answer.ok) return answer;
178
+ const facts = answer.payload;
179
+ return {
134
180
  ok: true,
135
- payload: answer.data
136
- } : {
137
- ok: false,
138
- failure: answer.failure
181
+ payload: {
182
+ counts: facts.counts.active,
183
+ expired: facts.counts.expired,
184
+ tabs: [
185
+ "verify",
186
+ "pending",
187
+ "fuse",
188
+ "skipped"
189
+ ].map((queue) => ({
190
+ queue,
191
+ count: facts.counts.active[queue]
192
+ })),
193
+ commands: facts.fallback.map((item) => item.command),
194
+ startCommands: facts.startCommands,
195
+ notes: [...facts.notes],
196
+ degraded: facts.degraded,
197
+ ...facts.last === null && facts.active === null ? {} : { latestRun: toRun(facts.last ?? facts.active) },
198
+ approval: facts.approval.mergeApproval
199
+ }
139
200
  };
140
201
  }
141
- async function rows(queue, cursor = 0) {
142
- const url = HOST_ROUTES.rows + "?queue=" + encodeURIComponent(queue) + "&cursor=" + String(cursor);
143
- const answer = await requestJson(fetch, url, { headers: { accept: "application/json" } });
144
- return answer.ok ? {
145
- ok: true,
146
- payload: answer.data
147
- } : {
202
+ async function rows(queue, cursor, expiredOnly = false) {
203
+ const answer = await requestWire(HOST_ROUTES.rows + "?queue=" + encodeURIComponent(queue) + (cursor === void 0 ? "" : "&cursor=" + encodeURIComponent(cursor)) + (expiredOnly ? "&expired=1" : ""));
204
+ if (!answer.ok) return answer;
205
+ if (answer.payload.queue !== queue || answer.payload.expiredOnly !== expiredOnly) return {
148
206
  ok: false,
149
- failure: answer.failure
207
+ failure: {
208
+ kind: "server",
209
+ status: 200
210
+ }
211
+ };
212
+ return {
213
+ ok: true,
214
+ payload: {
215
+ queue: answer.payload.queue,
216
+ items: answer.payload.rows.map(toRow),
217
+ ...answer.payload.nextCursor === void 0 ? {} : { nextCursor: answer.payload.nextCursor }
218
+ }
150
219
  };
151
220
  }
152
221
  async function body(id, side) {
153
- const url = HOST_ROUTES.body + "?id=" + encodeURIComponent(id) + "&side=" + side;
154
- const answer = await requestJson(fetch, url, { headers: { accept: "application/json" } });
222
+ const answer = await requestWire(HOST_ROUTES.body + "?id=" + encodeURIComponent(id) + "&side=" + side);
155
223
  return answer.ok ? {
156
224
  ok: true,
157
- payload: answer.data
158
- } : {
159
- ok: false,
160
- failure: answer.failure
161
- };
225
+ payload: {
226
+ content: answer.payload.text,
227
+ markdown: answer.payload.markdown,
228
+ html: escapeHtml(answer.payload.text)
229
+ }
230
+ } : answer;
162
231
  }
163
232
  async function history() {
164
- const answer = await requestJson(fetch, HOST_ROUTES.history, { headers: { accept: "application/json" } });
233
+ const answer = await requestWire(HOST_ROUTES.history);
165
234
  return answer.ok ? {
166
235
  ok: true,
167
- payload: answer.data
168
- } : {
169
- ok: false,
170
- failure: answer.failure
171
- };
236
+ payload: { items: answer.payload.items.map((item) => ({
237
+ name: item.name,
238
+ kind: item.kind,
239
+ reason: item.reason,
240
+ lastVersion: item.lastVersion,
241
+ archivedAt: new Date(item.archivedAt).toISOString(),
242
+ ...item.sourceRunId === void 0 ? {} : { sourceRunId: item.sourceRunId }
243
+ })) }
244
+ } : answer;
172
245
  }
173
246
  //#endregion
174
247
  //#region src/client/seam.ts
@@ -178,6 +251,13 @@ window.__ModuleLoader__.load({
178
251
  const NS = "evolution-merge";
179
252
  const zh = {
180
253
  "entry.label": "技能合并",
254
+ "title": "技能合并",
255
+ "start.scan": "复制扫描命令",
256
+ "start.pair": "复制配对命令",
257
+ "degraded": "读模型降级:",
258
+ "copied": "已复制:",
259
+ "row.noSummary": "无摘要",
260
+ "overlay.body": "正文",
181
261
  "hint": "手动触发;后台逐对判定。这里只读,不写技能。",
182
262
  "history": "历史",
183
263
  "close": "关闭",
@@ -209,6 +289,55 @@ window.__ModuleLoader__.load({
209
289
  "row.failure": "失败",
210
290
  "row.noRetry": "不可重试",
211
291
  "row.copy": "复制命令",
292
+ "tab.expired": "已过期",
293
+ "row.targetCreated": "新建",
294
+ "row.verify": "判定状态",
295
+ "row.staging": "暂存",
296
+ "row.skipKind": "跳过类型",
297
+ "row.bodies": "正文",
298
+ "verify.queued": "排队",
299
+ "verify.running": "进行中",
300
+ "verify.failed": "失败",
301
+ "verify.cancelled": "已取消",
302
+ "staging.none": "无",
303
+ "staging.kept": "保留",
304
+ "staging.cleared-source-ok": "已清理(源完好)",
305
+ "staging.cleared-source-gone": "已清理(源已缺失)",
306
+ "skipKind.agent-keep": "自动保留",
307
+ "skipKind.human-reject": "人工拒绝",
308
+ "skipKind.over-limit": "超限",
309
+ "skipKind.forced": "强制",
310
+ "action.approve": "复制批准",
311
+ "action.reject": "复制拒绝",
312
+ "action.retry-candidates": "重试候选",
313
+ "action.retry-resume": "重试续跑",
314
+ "action.discard": "复制丢弃",
315
+ "action.restore": "恢复",
316
+ "action.pair": "复制配对",
317
+ "action.status": "状态",
318
+ "action.report": "报告",
319
+ "action.cancel": "取消",
320
+ "action.confirm": "确认复制",
321
+ "disabled.expired": "已过期,不可用",
322
+ "disabled.invalid": "状态不允许",
323
+ "disabled.not-retryable": "不可重试",
324
+ "disabled.source-gone": "源已缺失",
325
+ "disabled.owned": "已有运行",
326
+ "disabled.unavailable": "暂不可用",
327
+ "history.empty": "没有历史。",
328
+ "failureServer": "读取失败",
329
+ "failureUnauthorized": "未登录",
330
+ "failureForbidden": "无权访问",
331
+ "failureUnmounted": "宿主未挂载",
332
+ "fallbackHint": "可复制命令见下方",
333
+ "body.missing": "正文不存在或不可读。",
334
+ "expired.target-taken": "目标已被占用",
335
+ "expired.source-changed": "源已变更",
336
+ "expired.skipped-ttl": "跳过已到期",
337
+ "history.kind.merged": "已合并",
338
+ "history.kind.deleted": "已删除",
339
+ "history.kind.split": "已拆分",
340
+ "history.kind.other": "其他",
212
341
  "origin.agent": "自动",
213
342
  "origin.human": "人工",
214
343
  "state.verify": "待判定",
@@ -219,6 +348,13 @@ window.__ModuleLoader__.load({
219
348
  };
220
349
  const en = {
221
350
  "entry.label": "Skill merge",
351
+ "title": "Skill merge",
352
+ "start.scan": "Copy scan command",
353
+ "start.pair": "Copy pair command",
354
+ "degraded": "Degraded read model:",
355
+ "copied": "Copied:",
356
+ "row.noSummary": "No summary",
357
+ "overlay.body": "Body",
222
358
  "hint": "Manual only; pairs are judged in the background. Read-only here.",
223
359
  "history": "History",
224
360
  "close": "Close",
@@ -250,6 +386,55 @@ window.__ModuleLoader__.load({
250
386
  "row.failure": "Failure",
251
387
  "row.noRetry": "not retryable",
252
388
  "row.copy": "Copy command",
389
+ "tab.expired": "Expired",
390
+ "row.targetCreated": "created",
391
+ "row.verify": "Verify state",
392
+ "row.staging": "Staging",
393
+ "row.skipKind": "Skip kind",
394
+ "row.bodies": "Bodies",
395
+ "verify.queued": "queued",
396
+ "verify.running": "running",
397
+ "verify.failed": "failed",
398
+ "verify.cancelled": "cancelled",
399
+ "staging.none": "none",
400
+ "staging.kept": "kept",
401
+ "staging.cleared-source-ok": "cleared (source ok)",
402
+ "staging.cleared-source-gone": "cleared (source gone)",
403
+ "skipKind.agent-keep": "agent keep",
404
+ "skipKind.human-reject": "human reject",
405
+ "skipKind.over-limit": "over limit",
406
+ "skipKind.forced": "forced",
407
+ "action.approve": "Copy approve",
408
+ "action.reject": "Copy reject",
409
+ "action.retry-candidates": "Retry candidates",
410
+ "action.retry-resume": "Retry resume",
411
+ "action.discard": "Copy discard",
412
+ "action.restore": "Restore",
413
+ "action.pair": "Copy pair",
414
+ "action.status": "Status",
415
+ "action.report": "Report",
416
+ "action.cancel": "Cancel",
417
+ "action.confirm": "Copy confirmation",
418
+ "disabled.expired": "expired",
419
+ "disabled.invalid": "invalid state",
420
+ "disabled.not-retryable": "not retryable",
421
+ "disabled.source-gone": "source gone",
422
+ "disabled.owned": "already running",
423
+ "disabled.unavailable": "unavailable",
424
+ "history.empty": "No history.",
425
+ "failureServer": "Read failed",
426
+ "failureUnauthorized": "Not signed in",
427
+ "failureForbidden": "Forbidden",
428
+ "failureUnmounted": "Host not mounted",
429
+ "fallbackHint": "Copy the command below",
430
+ "body.missing": "Body missing or unreadable.",
431
+ "expired.target-taken": "target taken",
432
+ "expired.source-changed": "source changed",
433
+ "expired.skipped-ttl": "skip expired",
434
+ "history.kind.merged": "Merged",
435
+ "history.kind.deleted": "Deleted",
436
+ "history.kind.split": "Split",
437
+ "history.kind.other": "Other",
253
438
  "origin.agent": "auto",
254
439
  "origin.human": "human",
255
440
  "state.verify": "Unverified",
@@ -272,6 +457,16 @@ window.__ModuleLoader__.load({
272
457
  ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; gap:var(--evo-space-6); border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
273
458
  ".evolution-merge-panel [data-role=\"tab\"] { background:none; border:0; border-bottom:var(--evo-hairline-width) solid transparent; padding:var(--evo-space-6) var(--evo-space-8); cursor:pointer; color:inherit; }",
274
459
  ".evolution-merge-panel [data-role=\"tab\"][data-active=\"true\"] { border-bottom-color:var(--dsw-alias-brand-primary); }",
460
+ ".evolution-merge-panel button:focus-visible { outline:2px solid currentColor !important; outline-offset:2px; box-shadow:0 0 0 2px currentColor !important; }",
461
+ ".evolution-merge-panel [data-role=\"title\"] { margin:0; font-size:var(--evo-type-lg); font-weight:600; }",
462
+ ".evolution-merge-panel [data-role=\"start\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
463
+ ".evolution-merge-panel [data-role=\"degraded\"] { margin:0; color:var(--dsw-alias-state-warning-primary); font-size:var(--evo-type-xs); }",
464
+ ".evolution-merge-panel [data-role=\"expired-toggle\"][data-active=\"true\"] { background:var(--dsw-alias-brand-primary); color:var(--dsw-alias-bg-base); }",
465
+ ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
466
+ ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
467
+ ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
468
+ ".evolution-merge-panel [data-role=\"overlay\"] { position:relative; display:flex; flex-direction:column; gap:var(--evo-space-8); padding:var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); }",
469
+ ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
275
470
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
276
471
  ".evolution-merge-panel [data-role=\"row\"] { display:grid; grid-template-columns:1fr auto; gap:var(--evo-space-8); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); }",
277
472
  ".evolution-merge-panel [data-role=\"row-label\"] { font-weight:600; }",
@@ -338,6 +533,14 @@ window.__ModuleLoader__.load({
338
533
  "skipped"
339
534
  ];
340
535
  function failure(t, failure) {
536
+ if (failure.kind === "refused" && failure.code === "merge-body-missing") return FailureBlock({
537
+ failure: {
538
+ ...failure,
539
+ message: t("body.missing")
540
+ },
541
+ t,
542
+ fallback: FALLBACKS["merge"]
543
+ });
341
544
  return FailureBlock({
342
545
  failure,
343
546
  t,
@@ -348,22 +551,28 @@ window.__ModuleLoader__.load({
348
551
  const t = face.t;
349
552
  const [view, setView] = (0, react.useState)(face.view);
350
553
  const [tab, setTab] = (0, react.useState)("pending");
554
+ const [expiredOnly, setExpiredOnly] = (0, react.useState)(false);
351
555
  const [rows, setRows] = (0, react.useState)(face.rows);
352
556
  const [loading, setLoading] = (0, react.useState)(face.loadRows !== void 0 || face.loadView !== void 0);
353
557
  const [failed, setFailed] = (0, react.useState)(void 0);
354
558
  const [nextCursor, setNextCursor] = (0, react.useState)(void 0);
559
+ const [copied, setCopied] = (0, react.useState)(void 0);
355
560
  const [overlay, setOverlay] = (0, react.useState)(void 0);
356
561
  const request = (0, react.useRef)(0);
357
- const loadRows = (queue, cursor = 0) => {
562
+ const copyCommand = (command) => {
563
+ navigator.clipboard.writeText(command);
564
+ setCopied(command);
565
+ };
566
+ const loadRows = (queue, cursor, expired = false) => {
358
567
  const load = face.loadRows;
359
568
  if (load === void 0) return;
360
569
  const token = request.current + 1;
361
570
  request.current = token;
362
571
  setLoading(true);
363
- load(queue, cursor).then((outcome) => {
572
+ load(queue, cursor, queue === "pending" && expired).then((outcome) => {
364
573
  if (token !== request.current) return;
365
574
  if (outcome.ok) {
366
- setRows((current) => [...cursor === 0 ? [] : current, ...outcome.payload.items].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)));
575
+ setRows((current) => [...cursor === void 0 ? [] : current, ...outcome.payload.items].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)));
367
576
  setNextCursor(outcome.payload.nextCursor);
368
577
  setFailed(void 0);
369
578
  } else setFailed(outcome.failure);
@@ -384,13 +593,6 @@ window.__ModuleLoader__.load({
384
593
  setFailed(outcome.failure);
385
594
  return;
386
595
  }
387
- if (outcome.payload.schemaVersion !== void 0 && outcome.payload.schemaVersion !== 2) {
388
- setFailed({
389
- kind: "server",
390
- status: 0
391
- });
392
- return;
393
- }
394
596
  setView(outcome.payload);
395
597
  }).catch(() => {
396
598
  setFailed({
@@ -403,12 +605,14 @@ window.__ModuleLoader__.load({
403
605
  const load = face.loadBody;
404
606
  if (load === void 0) return;
405
607
  load(id, side).then((outcome) => {
406
- if (outcome.ok) setOverlay({
407
- kind: "body",
408
- html: outcome.payload.html,
409
- label: id + ":" + side
410
- });
411
- else setFailed(outcome.failure);
608
+ if (outcome.ok) {
609
+ setFailed(void 0);
610
+ setOverlay({
611
+ kind: "body",
612
+ html: outcome.payload.html,
613
+ label: id + ":" + side
614
+ });
615
+ } else setFailed(outcome.failure);
412
616
  }).catch(() => {
413
617
  setFailed({
414
618
  kind: "server",
@@ -420,11 +624,13 @@ window.__ModuleLoader__.load({
420
624
  const load = face.loadHistory;
421
625
  if (load === void 0) return;
422
626
  load().then((outcome) => {
423
- if (outcome.ok) setOverlay({
424
- kind: "history",
425
- items: outcome.payload.items
426
- });
427
- else setFailed(outcome.failure);
627
+ if (outcome.ok) {
628
+ setFailed(void 0);
629
+ setOverlay({
630
+ kind: "history",
631
+ items: outcome.payload.items
632
+ });
633
+ } else setFailed(outcome.failure);
428
634
  }).catch(() => {
429
635
  setFailed({
430
636
  kind: "server",
@@ -437,15 +643,11 @@ window.__ModuleLoader__.load({
437
643
  loadRows("pending");
438
644
  }, [face]);
439
645
  (0, react.useEffect)(() => {
646
+ if (tab !== "pending" && expiredOnly) setExpiredOnly(false);
440
647
  setNextCursor(void 0);
441
- loadRows(tab, 0);
442
- }, [tab]);
648
+ loadRows(tab, void 0, tab === "pending" && expiredOnly);
649
+ }, [tab, expiredOnly]);
443
650
  const counts = view.counts;
444
- const stats = (0, react.createElement)("div", { "data-role": "stats" }, QUEUES.map((queue) => (0, react.createElement)("div", {
445
- key: queue,
446
- "data-role": "stat",
447
- "data-queue": queue
448
- }, (0, react.createElement)("div", { "data-role": "stat-value" }, String(counts[queue])), (0, react.createElement)("div", { "data-role": "stat-label" }, t("tab." + queue)))));
449
651
  const latest = view.latestRun;
450
652
  const runStrip = latest === void 0 ? null : (0, react.createElement)("div", { "data-role": "run-strip" }, (0, react.createElement)("span", { "data-role": "run-id" }, latest.id), (0, react.createElement)("span", { "data-role": "run-state" }, latest.state), latest.stage === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-stage" }, t("stage." + latest.stage)), latest.progress === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-progress" }, String(latest.progress.done) + "/" + String(latest.progress.total)));
451
653
  const tabs = (0, react.createElement)("div", {
@@ -462,57 +664,102 @@ window.__ModuleLoader__.load({
462
664
  setTab(queue);
463
665
  }
464
666
  }, t("tab." + queue) + " " + String(counts[queue]))));
667
+ const expiredToggle = tab === "pending" ? (0, react.createElement)("button", {
668
+ type: "button",
669
+ role: "switch",
670
+ "aria-checked": expiredOnly ? "true" : "false",
671
+ "data-role": "expired-toggle",
672
+ "data-active": expiredOnly ? "true" : "false",
673
+ onClick: () => {
674
+ setExpiredOnly((value) => !value);
675
+ }
676
+ }, t("tab.expired") + " " + String(view.expired)) : null;
465
677
  const list = rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty." + tab)) : (0, react.createElement)("div", { "data-role": "rows" }, rows.map((row) => {
466
678
  const facts = [
467
- row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
679
+ row.target === void 0 ? void 0 : t("row.target") + " " + row.target + (row.targetActual === void 0 ? "" : " → " + row.targetActual) + (row.targetCreated === true ? " (" + t("row.targetCreated") + ")" : ""),
680
+ row.verifySubState === void 0 ? void 0 : t("row.verify") + " " + t("verify." + row.verifySubState),
681
+ row.stagingState === void 0 || row.stagingState === "none" ? void 0 : t("row.staging") + " " + t("staging." + row.stagingState),
682
+ row.skipKind === void 0 ? void 0 : t("row.skipKind") + " " + t("skipKind." + row.skipKind),
683
+ row.bodies === void 0 ? void 0 : t("row.bodies") + " " + row.bodies.map((body) => body.name + "(" + body.side + ")").join(", "),
468
684
  row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2),
469
685
  row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
470
686
  row.origin === void 0 ? void 0 : t("row.origin") + " " + t("origin." + row.origin),
471
687
  row.stagingExpiresAt === void 0 ? void 0 : t("row.expires") + " " + new Date(row.stagingExpiresAt).toLocaleString(),
472
- row.expired === void 0 ? void 0 : t("row.expired") + " " + row.expired,
688
+ row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
473
689
  row.reason === void 0 ? void 0 : t("row.reason") + " " + row.reason,
474
- row.summary === void 0 ? void 0 : row.summary.filter(Boolean).join(" · "),
690
+ row.summary === void 0 ? void 0 : row.summary.filter(Boolean).join(" · ") || t("row.noSummary"),
475
691
  row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.message + (row.failure.retryable ? "" : " · " + t("row.noRetry"))
476
692
  ].filter((value) => value !== void 0).join(" · ");
477
693
  return (0, react.createElement)("div", {
478
694
  key: row.id,
479
695
  "data-role": "row"
480
- }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage)) + " · " + row.id), facts === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, facts)), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, row.updatedAt), (0, react.createElement)("div", { "data-role": "row-actions" }, face.loadBody === void 0 ? null : (0, react.createElement)("button", {
696
+ }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), facts === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, facts)), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, face.loadBody === void 0 || row.bodies === void 0 ? null : (0, react.createElement)("button", {
481
697
  type: "button",
482
698
  "data-role": "body-a",
483
699
  onClick: () => {
484
700
  openBody(row.id, "a");
485
701
  }
486
- }, "A"), face.loadBody === void 0 ? null : (0, react.createElement)("button", {
702
+ }, "A"), face.loadBody === void 0 || row.bodies === void 0 ? null : (0, react.createElement)("button", {
487
703
  type: "button",
488
704
  "data-role": "body-b",
489
705
  onClick: () => {
490
706
  openBody(row.id, "b");
491
707
  }
492
- }, "B"), (row.actions ?? []).filter((action) => view.approval !== false || !action.includes("/evolution merge approve")).map((action) => (0, react.createElement)("button", {
493
- key: action,
708
+ }, "B"), (row.actions ?? []).map((action) => (0, react.createElement)("button", {
709
+ key: action.id,
494
710
  type: "button",
495
711
  "data-role": "row-action",
496
- title: action,
712
+ "data-action": action.id,
713
+ title: action.disabledReason === void 0 ? action.command : t("disabled." + action.disabledReason),
714
+ disabled: !action.enabled,
497
715
  onClick: () => {
498
- navigator.clipboard.writeText(action);
716
+ if (!action.enabled) return;
717
+ if (action.confirm && globalThis.confirm !== void 0 && !globalThis.confirm(t("action.confirm") + " " + t("action." + action.id))) return;
718
+ copyCommand(action.command);
499
719
  }
500
- }, t("row.copy"))))));
720
+ }, t("action." + action.id))))));
501
721
  }));
502
722
  return (0, react.createElement)("section", {
503
723
  className: CSS_TAG_ID,
504
724
  "data-state": loading ? "loading" : failed !== void 0 ? "failed" : rows.length === 0 ? "empty" : "ready"
505
- }, (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), view.approval === void 0 ? null : (0, react.createElement)("p", { "data-role": "approval" }, t(view.approval ? "approval.on" : "approval.off")), runStrip, stats, tabs, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "note" }, failure(t, failed)), list, nextCursor === void 0 ? null : (0, react.createElement)("button", {
725
+ }, (0, react.createElement)("h2", { "data-role": "title" }, t("title")), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
726
+ type: "button",
727
+ "data-role": "start-scan",
728
+ onClick: () => {
729
+ copyCommand(view.startCommands.scan);
730
+ }
731
+ }, t("start.scan")), (0, react.createElement)("button", {
732
+ type: "button",
733
+ "data-role": "start-pair",
734
+ onClick: () => {
735
+ copyCommand(view.startCommands.pair);
736
+ }
737
+ }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, view.approval === void 0 ? null : (0, react.createElement)("p", { "data-role": "approval" }, t(view.approval ? "approval.on" : "approval.off")), runStrip, tabs, expiredToggle, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "failure" }, failure(t, failed), (0, react.createElement)("div", { "data-role": "fallback" }, view.commands.map((command) => (0, react.createElement)("button", {
738
+ key: command,
739
+ type: "button",
740
+ "data-role": "fallback-command",
741
+ onClick: () => {
742
+ copyCommand(command);
743
+ }
744
+ }, command)))), copied === void 0 ? null : (0, react.createElement)("p", {
745
+ "data-role": "copied",
746
+ role: "status"
747
+ }, t("copied") + " " + copied), list, nextCursor === void 0 ? null : (0, react.createElement)("button", {
506
748
  type: "button",
507
749
  "data-role": "load-more",
508
750
  onClick: () => {
509
- loadRows(tab, Number(nextCursor));
751
+ loadRows(tab, nextCursor, expiredOnly);
510
752
  }
511
753
  }, t("loadMore")), face.loadHistory === void 0 ? null : (0, react.createElement)("button", {
512
754
  type: "button",
513
755
  "data-role": "history",
514
756
  onClick: openHistory
515
- }, t("history")), overlay === void 0 ? null : (0, react.createElement)("section", { "data-role": "overlay" }, (0, react.createElement)("button", {
757
+ }, t("history")), overlay === void 0 ? null : (0, react.createElement)("section", {
758
+ "data-role": "overlay",
759
+ role: "dialog",
760
+ "aria-modal": "true",
761
+ "aria-label": overlay.kind === "body" ? t("overlay.body") : t("history")
762
+ }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, overlay.kind === "body" ? t("overlay.body") : t("history")), (0, react.createElement)("button", {
516
763
  type: "button",
517
764
  "data-role": "overlay-close",
518
765
  onClick: () => {
@@ -522,12 +769,12 @@ window.__ModuleLoader__.load({
522
769
  "data-role": "body",
523
770
  "data-label": overlay.label,
524
771
  dangerouslySetInnerHTML: { __html: overlay.html }
525
- }) : (0, react.createElement)("ul", { "data-role": "history-list" }, overlay.items.map((item) => (0, react.createElement)("li", { key: item.name }, item.name + " · " + item.kind + " · " + item.reason + " · v" + String(item.lastVersion))))), (0, react.createElement)("button", {
772
+ }) : overlay.items.length === 0 ? (0, react.createElement)("p", { "data-role": "history-empty" }, t("history.empty")) : (0, react.createElement)("ul", { "data-role": "history-list" }, overlay.items.map((item) => (0, react.createElement)("li", { key: item.name }, item.name + " · " + t("history.kind." + item.kind) + " · " + item.reason + " · v" + String(item.lastVersion))))), (0, react.createElement)("button", {
526
773
  type: "button",
527
774
  "data-role": "refresh",
528
775
  onClick: () => {
529
776
  loadView();
530
- loadRows(tab, 0);
777
+ loadRows(tab, void 0, tab === "pending" && expiredOnly);
531
778
  }
532
779
  }, t("refresh")));
533
780
  }
@@ -556,6 +803,7 @@ window.__ModuleLoader__.load({
556
803
  fuse: 0,
557
804
  skipped: 0
558
805
  },
806
+ expired: 0,
559
807
  tabs: [
560
808
  {
561
809
  queue: "verify",
@@ -574,7 +822,23 @@ window.__ModuleLoader__.load({
574
822
  count: 0
575
823
  }
576
824
  ],
577
- commands: []
825
+ commands: [
826
+ "/evolution merge scan",
827
+ "/evolution merge pair <a> <b>",
828
+ "/evolution merge candidates",
829
+ "/evolution merge pending",
830
+ "/evolution merge resume",
831
+ "/evolution merge skipped",
832
+ "/evolution merge status",
833
+ "/evolution merge report",
834
+ "/evolution merge doctor"
835
+ ],
836
+ startCommands: {
837
+ scan: "/evolution merge scan",
838
+ pair: "/evolution merge pair <a> <b>"
839
+ },
840
+ notes: [],
841
+ degraded: false
578
842
  };
579
843
  }
580
844
  function apply(ctx) {
@@ -8,7 +8,7 @@ export interface MergeFace {
8
8
  view: MergeViewPayload;
9
9
  rows: MergeRowView[];
10
10
  loadView?: () => Promise<MergeOutcome<MergeViewPayload>>;
11
- loadRows?: (queue: MergeQueue, cursor?: number) => Promise<MergeOutcome<QueuePage>>;
11
+ loadRows?: (queue: MergeQueue, cursor?: string, expiredOnly?: boolean) => Promise<MergeOutcome<QueuePage>>;
12
12
  loadBody?: (id: string, side: 'a' | 'b') => Promise<MergeOutcome<MergeBody>>;
13
13
  loadHistory?: () => Promise<MergeOutcome<{
14
14
  items: MergeHistoryItem[];
@@ -1,7 +1,9 @@
1
- import { type RouteFailure } from './route-client.generated.ts';
1
+ import type { RouteFailure } from './route-client.generated.ts';
2
2
  export declare const HOST_ROUTES: {
3
3
  readonly view: "/api/dsh-evolution/merge/view";
4
4
  readonly rows: "/api/dsh-evolution/merge/rows";
5
+ readonly run: "/api/dsh-evolution/merge/run";
6
+ readonly runs: "/api/dsh-evolution/merge/runs";
5
7
  readonly body: "/api/dsh-evolution/merge/body";
6
8
  readonly history: "/api/dsh-evolution/merge/history";
7
9
  };
@@ -24,16 +26,29 @@ export interface MergeRunView {
24
26
  startedAt: number;
25
27
  }
26
28
  export interface MergeViewPayload {
27
- schemaVersion?: number;
28
29
  counts: MergeCounts;
30
+ expired: number;
29
31
  tabs: {
30
32
  queue: MergeQueue;
31
33
  count: number;
32
34
  }[];
33
35
  commands: string[];
36
+ startCommands: {
37
+ scan: string;
38
+ pair: string;
39
+ };
40
+ notes: string[];
41
+ degraded: boolean;
34
42
  latestRun?: MergeRunView;
35
43
  approval?: boolean;
36
44
  }
45
+ export interface RowActionView {
46
+ id: string;
47
+ command: string;
48
+ confirm: boolean;
49
+ enabled: boolean;
50
+ disabledReason?: string;
51
+ }
37
52
  export interface MergeRowView {
38
53
  id: string;
39
54
  pairKey: string;
@@ -59,10 +74,19 @@ export interface MergeRowView {
59
74
  attempts?: number;
60
75
  origin?: 'agent' | 'human';
61
76
  stagingExpiresAt?: number;
62
- expired?: 'target-taken' | 'source-changed';
77
+ expired?: 'target-taken' | 'source-changed' | 'skipped-ttl';
63
78
  reason?: string;
64
79
  summary?: string[];
65
- actions?: string[];
80
+ actions: RowActionView[];
81
+ targetActual?: string;
82
+ targetCreated?: boolean;
83
+ stagingState?: 'none' | 'kept' | 'cleared-source-ok' | 'cleared-source-gone';
84
+ verifySubState?: 'queued' | 'running' | 'failed' | 'cancelled';
85
+ skipKind?: 'agent-keep' | 'human-reject' | 'over-limit' | 'forced';
86
+ bodies?: {
87
+ name: string;
88
+ side: 'live' | 'archive';
89
+ }[];
66
90
  }
67
91
  export interface QueuePage {
68
92
  queue: MergeQueue;
@@ -90,7 +114,7 @@ export type MergeOutcome<T> = {
90
114
  failure: RouteFailure;
91
115
  };
92
116
  export declare function view(): Promise<MergeOutcome<MergeViewPayload>>;
93
- export declare function rows(queue: MergeQueue, cursor?: number): Promise<MergeOutcome<QueuePage>>;
117
+ export declare function rows(queue: MergeQueue, cursor?: string, expiredOnly?: boolean): Promise<MergeOutcome<QueuePage>>;
94
118
  export declare function body(id: string, side: 'a' | 'b'): Promise<MergeOutcome<MergeBody>>;
95
119
  export declare function history(): Promise<MergeOutcome<{
96
120
  items: MergeHistoryItem[];
@@ -27,6 +27,8 @@ export declare const ROUTES: {
27
27
  readonly merge: {
28
28
  readonly view: "/api/dsh-evolution/merge/view";
29
29
  readonly rows: "/api/dsh-evolution/merge/rows";
30
+ readonly run: "/api/dsh-evolution/merge/run";
31
+ readonly runs: "/api/dsh-evolution/merge/runs";
30
32
  readonly body: "/api/dsh-evolution/merge/body";
31
33
  readonly history: "/api/dsh-evolution/merge/history";
32
34
  };
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.30.16",
14
+ "version": "0.30.18",
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },