dsh-run2skill 0.1.0-alpha → 0.2.0

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 (6) hide show
  1. package/README.en.md +137 -0
  2. package/README.md +82 -32
  3. package/lib/client.js +2462 -1610
  4. package/lib/index.d.ts +3956 -265
  5. package/lib/index.js +19055 -5533
  6. package/package.json +20 -6
package/lib/client.js CHANGED
@@ -5,6 +5,7 @@ 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
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
  //#region node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
9
10
  var _a$1;
10
11
  function $constructor(name, initializer, params) {
@@ -4153,11 +4154,200 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4153
4154
  //#endregion
4154
4155
  //#region src/adapters/dsh-connection/observe-summary-rpc.ts
4155
4156
  const RUN2SKILL_RPC_CHANNEL = "/run2skill";
4156
- const OBSERVE_SUMMARY_ENDPOINT = "observe-summary";
4157
4157
  object({ apiVersion: literal(1) }).strict();
4158
4158
  //#endregion
4159
- //#region src/client/observe-summary-poller.ts
4160
- const OBSERVE_SUMMARY_POLL_INTERVAL_MS = 1e4;
4159
+ //#region src/client/dialog-focus.ts
4160
+ const focusableSelector = [
4161
+ "button:not([disabled])",
4162
+ "[href]",
4163
+ "input:not([disabled])",
4164
+ "select:not([disabled])",
4165
+ "textarea:not([disabled])",
4166
+ "[tabindex]:not([tabindex=\"-1\"])"
4167
+ ].join(",");
4168
+ function trapDialogTab(key, backwards, preventDefault, root, activeElement) {
4169
+ if (key !== "Tab") return;
4170
+ const focusable = Array.from(root.querySelectorAll(focusableSelector));
4171
+ if (focusable.length === 0) {
4172
+ preventDefault();
4173
+ return;
4174
+ }
4175
+ const first = focusable[0];
4176
+ const last = focusable.at(-1);
4177
+ if (!focusable.includes(activeElement)) {
4178
+ preventDefault();
4179
+ (backwards ? last : first).focus();
4180
+ return;
4181
+ }
4182
+ if (backwards && activeElement === first) {
4183
+ preventDefault();
4184
+ last.focus();
4185
+ } else if (!backwards && activeElement === last) {
4186
+ preventDefault();
4187
+ first.focus();
4188
+ }
4189
+ }
4190
+ //#endregion
4191
+ //#region src/client/style-lifecycle.ts
4192
+ const RUN2SKILL_STYLE_ID = "dsh-run2skill/native-ui";
4193
+ function styleSelector() {
4194
+ return `style[data-plugin-css="${RUN2SKILL_STYLE_ID}"]`;
4195
+ }
4196
+ function upsertRun2skillStyle(css) {
4197
+ const existing = document.querySelector(styleSelector());
4198
+ const tag = existing ?? document.createElement("style");
4199
+ tag.dataset.plugin = "dsh-run2skill";
4200
+ tag.dataset.pluginCss = RUN2SKILL_STYLE_ID;
4201
+ tag.textContent = css;
4202
+ if (existing === null) document.head.appendChild(tag);
4203
+ return tag;
4204
+ }
4205
+ function acquireRun2skillStyle() {
4206
+ if (typeof document === "undefined") return () => void 0;
4207
+ const tag = document.querySelector(styleSelector());
4208
+ if (tag === null) return () => void 0;
4209
+ const count = Number(tag.dataset.pluginMounts ?? "0");
4210
+ tag.dataset.pluginMounts = String(Number.isSafeInteger(count) && count >= 0 ? count + 1 : 1);
4211
+ let disposed = false;
4212
+ return () => {
4213
+ if (disposed) return;
4214
+ disposed = true;
4215
+ const current = Number(tag.dataset.pluginMounts ?? "1");
4216
+ if (current <= 1) tag.remove();
4217
+ else tag.dataset.pluginMounts = String(current - 1);
4218
+ };
4219
+ }
4220
+ //#endregion
4221
+ //#region \0run2skill-css:run2skill-settings-page.module.css.mjs
4222
+ const css = ".run2skill_page_H3ahsG{width:min(100%,1040px);color:var(--dsw-alias-label-primary);gap:24px;padding:16px 0 48px;display:grid}.run2skill_intro_H3ahsG,.run2skill_muted_H3ahsG{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.5}.run2skill_section_H3ahsG{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);box-shadow:0 1px 2px var(--dsw-alias-border-l1);border-radius:16px;overflow:hidden}.run2skill_sectionHeader_H3ahsG{box-sizing:border-box;height:auto;min-height:56px;padding:14px 20px}.run2skill_sectionTitle_H3ahsG{font-size:15px}.run2skill_sectionBody_H3ahsG{gap:18px;padding:12px 24px 24px 56px;display:grid}.run2skill_empty_H3ahsG{min-height:56px;color:var(--dsw-alias-label-secondary);place-items:center;margin:0;font-size:13px;display:grid}.run2skill_toolbar_H3ahsG,.run2skill_actions_H3ahsG,.run2skill_modeGroup_H3ahsG{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.run2skill_attentionList_H3ahsG,.run2skill_proposalList_H3ahsG,.run2skill_activityList_H3ahsG{gap:8px;margin:0;padding:0;list-style:none;display:grid}.run2skill_activityItem_H3ahsG{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.run2skill_activitySummary_H3ahsG{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.run2skill_proposalLayout_H3ahsG{grid-template-columns:minmax(220px,.8fr) minmax(0,1.6fr);gap:20px;display:grid}.run2skill_proposalListButton_H3ahsG{text-align:start;white-space:normal;justify-content:flex-start;width:100%}.run2skill_detail_H3ahsG{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;min-width:0;max-height:640px;padding:18px;overflow:auto}.run2skill_detail_H3ahsG pre{white-space:pre-wrap;unicode-bidi:isolate;overflow:auto}.run2skill_status_H3ahsG{min-height:20px;margin:0;font-size:13px}.run2skill_danger_H3ahsG{color:var(--dsw-alias-state-error-primary)}.run2skill_backdrop_H3ahsG,.run2skill_rejectOverlay_H3ahsG{background:var(--dsw-alias-bg-mask-1);position:fixed;inset:0}.run2skill_backdrop_H3ahsG{z-index:999}.run2skill_rejectOverlay_H3ahsG{z-index:1001;place-items:center;padding:16px;display:grid}.run2skill_legacyDialog_H3ahsG{z-index:1000;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 16px 48px var(--dsw-alias-bg-mask-1);border-radius:16px;grid-template-columns:minmax(16rem,24rem) minmax(0,1fr);gap:16px;padding:16px;display:grid;position:fixed;inset:5vh 5vw;overflow:hidden}.run2skill_blocked_H3ahsG{pointer-events:none;display:contents}.run2skill_queue_H3ahsG,.run2skill_legacyDetail_H3ahsG{min-width:0;overflow:auto}.run2skill_legacyHeader_H3ahsG{justify-content:space-between;align-items:center;gap:8px;display:flex}.run2skill_legacyList_H3ahsG{margin:0;padding:0;list-style:none}.run2skill_rejectCard_H3ahsG{background:var(--dsw-alias-bg-layer-1);border-radius:16px;max-width:32rem;padding:16px}.run2skill_textBlock_H3ahsG{white-space:pre-wrap;overflow-wrap:anywhere;unicode-bidi:isolate;overflow:auto}.run2skill_previewFacts_H3ahsG{grid-template-columns:max-content 1fr;gap:6px 12px;display:grid}.run2skill_previewFacts_H3ahsG dt,.run2skill_previewFacts_H3ahsG dd{margin:0}@media (width<=720px){.run2skill_proposalLayout_H3ahsG{grid-template-columns:1fr}.run2skill_sectionBody_H3ahsG{padding:8px 18px 20px}.run2skill_activityItem_H3ahsG{flex-direction:column;align-items:flex-start}}";
4223
+ if (typeof document !== "undefined") upsertRun2skillStyle(css);
4224
+ var _run2skill_css_run2skill_settings_page_module_css_default = {
4225
+ "danger": "run2skill_danger_H3ahsG",
4226
+ "rejectOverlay": "run2skill_rejectOverlay_H3ahsG",
4227
+ "backdrop": "run2skill_backdrop_H3ahsG",
4228
+ "sectionBody": "run2skill_sectionBody_H3ahsG",
4229
+ "legacyList": "run2skill_legacyList_H3ahsG",
4230
+ "muted": "run2skill_muted_H3ahsG",
4231
+ "activitySummary": "run2skill_activitySummary_H3ahsG",
4232
+ "detail": "run2skill_detail_H3ahsG",
4233
+ "queue": "run2skill_queue_H3ahsG",
4234
+ "section": "run2skill_section_H3ahsG",
4235
+ "rejectCard": "run2skill_rejectCard_H3ahsG",
4236
+ "page": "run2skill_page_H3ahsG",
4237
+ "activityList": "run2skill_activityList_H3ahsG",
4238
+ "proposalLayout": "run2skill_proposalLayout_H3ahsG",
4239
+ "proposalListButton": "run2skill_proposalListButton_H3ahsG",
4240
+ "sectionHeader": "run2skill_sectionHeader_H3ahsG",
4241
+ "status": "run2skill_status_H3ahsG",
4242
+ "modeGroup": "run2skill_modeGroup_H3ahsG",
4243
+ "intro": "run2skill_intro_H3ahsG",
4244
+ "proposalList": "run2skill_proposalList_H3ahsG",
4245
+ "empty": "run2skill_empty_H3ahsG",
4246
+ "actions": "run2skill_actions_H3ahsG",
4247
+ "legacyDetail": "run2skill_legacyDetail_H3ahsG",
4248
+ "previewFacts": "run2skill_previewFacts_H3ahsG",
4249
+ "toolbar": "run2skill_toolbar_H3ahsG",
4250
+ "blocked": "run2skill_blocked_H3ahsG",
4251
+ "textBlock": "run2skill_textBlock_H3ahsG",
4252
+ "activityItem": "run2skill_activityItem_H3ahsG",
4253
+ "attentionList": "run2skill_attentionList_H3ahsG",
4254
+ "legacyDialog": "run2skill_legacyDialog_H3ahsG",
4255
+ "legacyHeader": "run2skill_legacyHeader_H3ahsG",
4256
+ "sectionTitle": "run2skill_sectionTitle_H3ahsG"
4257
+ };
4258
+ //#endregion
4259
+ //#region src/client/purge-settings.ts
4260
+ const PURGE_IDLE_POLL_INTERVAL_MS = 1e4;
4261
+ const PURGE_ACTIVE_POLL_INTERVAL_MS = 2e3;
4262
+ const previewId = string().regex(/^purv_[a-f0-9]{64}$/);
4263
+ const purgeId = string().regex(/^purge_[a-f0-9]{64}$/);
4264
+ const digest = string().regex(/^[a-f0-9]{64}$/);
4265
+ const isoDateTime = string().datetime({ offset: true });
4266
+ const count = number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
4267
+ const projectBinding = object({
4268
+ scope: literal("PROJECT"),
4269
+ workspaceId: string().min(1).max(256)
4270
+ }).strict();
4271
+ const scopeBinding = discriminatedUnion("scope", [
4272
+ object({ scope: literal("ALL") }).strict(),
4273
+ projectBinding,
4274
+ object({ scope: literal("USER") }).strict()
4275
+ ]);
4276
+ const previewSchema = object({
4277
+ apiVersion: literal(1),
4278
+ previewId,
4279
+ digest,
4280
+ expiresAt: isoDateTime,
4281
+ scopeBinding,
4282
+ hideBefore: isoDateTime,
4283
+ workItemCount: count,
4284
+ lineageCount: count,
4285
+ derivedRecordCount: count,
4286
+ blockedOrUnprovenCount: count,
4287
+ willDelete: array(object({
4288
+ kind: _enum(["WORK_ITEMS", "LINEAGES"]),
4289
+ count
4290
+ }).strict()).length(2),
4291
+ willKeep: array(object({
4292
+ reason: _enum([
4293
+ "KEEP_NEW",
4294
+ "KEEP_SCOPE",
4295
+ "KEEP_UNPROVEN"
4296
+ ]),
4297
+ count
4298
+ }).strict()).length(3),
4299
+ busyPublicationCount: count
4300
+ }).strict();
4301
+ const receiptSchema = object({
4302
+ apiVersion: literal(1),
4303
+ purgeId,
4304
+ state: _enum(["COMPLETED", "IN_PROGRESS"]),
4305
+ phase: _enum([
4306
+ "HIDING",
4307
+ "DELETING_LINEAGES",
4308
+ "DELETING_WORK_ITEMS",
4309
+ "VERIFYING"
4310
+ ]).optional(),
4311
+ deletedWorkItems: count,
4312
+ deletedLineages: count
4313
+ }).strict();
4314
+ const statusSchema = discriminatedUnion("state", [object({
4315
+ apiVersion: literal(1),
4316
+ state: literal("IDLE")
4317
+ }).strict(), object({
4318
+ apiVersion: literal(1),
4319
+ state: literal("IN_PROGRESS"),
4320
+ purgeId,
4321
+ hideBefore: isoDateTime,
4322
+ startedAt: isoDateTime,
4323
+ phase: _enum([
4324
+ "HIDING",
4325
+ "DELETING_LINEAGES",
4326
+ "DELETING_WORK_ITEMS",
4327
+ "VERIFYING"
4328
+ ]),
4329
+ deletedWorkItems: count,
4330
+ deletedLineages: count,
4331
+ lastError: object({
4332
+ code: string().regex(/^[A-Z0-9_]+$/),
4333
+ occurredAt: isoDateTime
4334
+ }).strict().optional()
4335
+ }).strict()]);
4336
+ const errorSchema = object({
4337
+ ok: literal(false),
4338
+ error: object({
4339
+ code: string().min(1).max(128),
4340
+ message: string().max(512),
4341
+ details: object({ busyPublicationCount: count.optional() }).strict().optional()
4342
+ }).strict()
4343
+ }).strict();
4344
+ var PurgeResponseError = class extends Error {
4345
+ facts;
4346
+ constructor(facts) {
4347
+ super(facts.code);
4348
+ this.facts = facts;
4349
+ }
4350
+ };
4161
4351
  function browserEnvironment$2() {
4162
4352
  return {
4163
4353
  isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
@@ -4165,13 +4355,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4165
4355
  clearInterval: (handle) => {
4166
4356
  globalThis.clearInterval(handle);
4167
4357
  },
4168
- onFocus: (listener) => {
4169
- if (typeof window === "undefined") return () => void 0;
4170
- window.addEventListener("focus", listener);
4171
- return () => {
4172
- window.removeEventListener("focus", listener);
4173
- };
4174
- },
4175
4358
  onVisibilityChange: (listener) => {
4176
4359
  if (typeof document === "undefined") return () => void 0;
4177
4360
  document.addEventListener("visibilitychange", listener);
@@ -4181,25 +4364,63 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4181
4364
  }
4182
4365
  };
4183
4366
  }
4184
- function parsedSummary(result) {
4185
- if (result === null || typeof result !== "object" || !("ok" in result) || result.ok !== true) return;
4186
- const parsed = ObserveSummaryV1Schema.safeParse("value" in result ? result.value : void 0);
4187
- return parsed.success ? parsed.data : void 0;
4367
+ function parseValue$1(schema, response) {
4368
+ const success = object({
4369
+ ok: literal(true),
4370
+ value: schema
4371
+ }).strict().safeParse(response);
4372
+ if (success.success) return success.data.value;
4373
+ const failure = errorSchema.safeParse(response);
4374
+ if (failure.success) {
4375
+ const busy = failure.data.error.details?.busyPublicationCount;
4376
+ throw new PurgeResponseError({
4377
+ code: failure.data.error.code,
4378
+ ...typeof busy === "number" && Number.isSafeInteger(busy) && busy >= 0 ? { busyPublicationCount: busy } : {}
4379
+ });
4380
+ }
4381
+ throw new PurgeResponseError({ code: "PURGE_STORAGE_UNAVAILABLE" });
4188
4382
  }
4189
- var ObserveSummaryPoller = class {
4383
+ function completionAnnouncement(scope, receipt) {
4384
+ return `${scope === "ALL" ? "Run2Skill 缓存" : scope === void 0 ? "Run2Skill 数据" : `${scope} Run2Skill 数据`}清理完成:${String(receipt.deletedWorkItems)} 条待处理数据,${String(receipt.deletedLineages)} 条 Skill 关联记录。`;
4385
+ }
4386
+ function polledCompletionAnnouncement(scope) {
4387
+ return scope === "ALL" ? "Run2Skill 缓存清理完成。" : `${scope === void 0 ? "" : `${scope} `}Run2Skill 数据清理完成。`;
4388
+ }
4389
+ function errorAnnouncement(error, durableBoundary = false) {
4390
+ if (error.code === "PURGE_PREVIEW_STALE") return "清理预览已失效,请重新预览后确认。";
4391
+ if (error.code === "PURGE_BUSY") return `当前有 ${String(error.busyPublicationCount ?? 0)} 份技能草稿正在保存;请等待保存完成后重新预览。`;
4392
+ if (error.code === "PURGE_ALREADY_RUNNING") return "已有一项数据清理正在运行,请等待完成。";
4393
+ if (error.code === "PURGE_SCOPE_UNAVAILABLE") return "当前作用域无法可靠确认,未执行清理。";
4394
+ if (error.code === "PURGE_INCOMPATIBLE") return "当前数据版本不兼容,未执行清理。";
4395
+ if (error.code === "PURGE_FENCE_LIMIT") return "缓存清理保护记录已达上限,未执行清理。";
4396
+ return durableBoundary ? "数据清理未完成;已建立的清理边界继续隐藏,可重试。" : "数据清理暂不可用;未确认已建立清理边界,请稍后重试。";
4397
+ }
4398
+ const initialState$1 = {
4399
+ hostDataEpoch: 0,
4400
+ statusPhase: "LOADING",
4401
+ previewPending: false,
4402
+ mutationPending: false,
4403
+ announcement: ""
4404
+ };
4405
+ var PurgeSettingsController = class {
4190
4406
  call;
4407
+ getWorkspaceId;
4191
4408
  environment;
4192
4409
  #listeners = /* @__PURE__ */ new Set();
4193
- #state = { phase: "LOADING" };
4410
+ #state = initialState$1;
4194
4411
  #started = false;
4412
+ #active = true;
4195
4413
  #disposed = false;
4196
- #timer;
4197
4414
  #pending;
4198
4415
  #abort;
4199
- #removeFocus;
4416
+ #statusPending;
4417
+ #statusAbort;
4418
+ #timer;
4419
+ #timerDelay;
4200
4420
  #removeVisibility;
4201
- constructor(call, environment = browserEnvironment$2()) {
4421
+ constructor(call, getWorkspaceId, environment = browserEnvironment$2()) {
4202
4422
  this.call = call;
4423
+ this.getWorkspaceId = getWorkspaceId;
4203
4424
  this.environment = environment;
4204
4425
  }
4205
4426
  snapshot = () => this.#state;
@@ -4212,648 +4433,247 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4212
4433
  start() {
4213
4434
  if (this.#started || this.#disposed) return;
4214
4435
  this.#started = true;
4215
- this.#removeFocus = this.environment.onFocus(() => {
4216
- if (this.environment.isVisible()) this.#refresh();
4217
- });
4218
4436
  this.#removeVisibility = this.environment.onVisibilityChange(() => {
4219
- if (this.environment.isVisible()) {
4437
+ if (this.#active && this.environment.isVisible()) {
4220
4438
  this.#schedule();
4221
- this.#refresh();
4439
+ this.#refreshStatus();
4222
4440
  } else this.#unschedule();
4223
4441
  });
4224
- if (this.environment.isVisible()) {
4442
+ if (this.#active && this.environment.isVisible()) {
4225
4443
  this.#schedule();
4226
- this.#refresh();
4444
+ this.#refreshStatus();
4227
4445
  }
4228
4446
  }
4229
- whenIdle() {
4230
- return this.#pending ?? Promise.resolve();
4231
- }
4232
- dispose() {
4233
- if (this.#disposed) return;
4234
- this.#disposed = true;
4447
+ pause() {
4448
+ if (this.#disposed || !this.#active) return;
4449
+ this.#active = false;
4235
4450
  this.#unschedule();
4236
- this.#removeFocus?.();
4237
- this.#removeVisibility?.();
4238
- this.#removeFocus = void 0;
4239
- this.#removeVisibility = void 0;
4240
- this.#abort?.abort();
4241
- this.#listeners.clear();
4242
- }
4243
- #schedule() {
4244
- if (this.#timer !== void 0 || this.#disposed) return;
4245
- this.#timer = this.environment.setInterval(() => {
4246
- this.#refresh();
4247
- }, OBSERVE_SUMMARY_POLL_INTERVAL_MS);
4248
- }
4249
- #unschedule() {
4250
- if (this.#timer === void 0) return;
4251
- this.environment.clearInterval(this.#timer);
4252
- this.#timer = void 0;
4451
+ this.#statusAbort?.abort();
4253
4452
  }
4254
- #refresh() {
4255
- if (this.#disposed || this.#pending !== void 0 || !this.environment.isVisible()) return this.#pending ?? Promise.resolve();
4256
- const abort = new AbortController();
4257
- this.#abort = abort;
4258
- const pending = (async () => {
4259
- try {
4260
- const summary = parsedSummary(await this.call({ apiVersion: 1 }, abort.signal));
4261
- if (this.#disposed) return;
4262
- if (summary === void 0) this.#markUnavailable();
4263
- else this.#publish({
4264
- phase: "READY",
4265
- summary
4266
- });
4267
- } catch {
4268
- if (!this.#disposed && !abort.signal.aborted) this.#markUnavailable();
4269
- }
4270
- })();
4271
- this.#pending = pending;
4272
- pending.finally(() => {
4273
- if (this.#pending === pending) this.#pending = void 0;
4274
- if (this.#abort === abort) this.#abort = void 0;
4453
+ resume() {
4454
+ if (this.#disposed || this.#active) return;
4455
+ this.#active = true;
4456
+ if (!this.#started || !this.environment.isVisible()) return;
4457
+ this.#schedule();
4458
+ this.whenIdle().then(() => {
4459
+ if (this.#active && !this.#disposed) this.#refreshStatus();
4275
4460
  });
4276
- return pending;
4277
- }
4278
- #markUnavailable() {
4279
- this.#publish("summary" in this.#state ? {
4280
- phase: "STALE",
4281
- summary: this.#state.summary
4282
- } : { phase: "UNAVAILABLE" });
4283
4461
  }
4284
- #publish(state) {
4285
- this.#state = state;
4286
- for (const listener of this.#listeners) listener();
4462
+ whenIdle() {
4463
+ return Promise.all([this.#pending ?? Promise.resolve(), this.#statusPending ?? Promise.resolve()]).then(() => void 0);
4287
4464
  }
4288
- };
4289
- //#endregion
4290
- //#region src/client/proposal-inbox-wire.ts
4291
- const identity = string().min(1).max(256);
4292
- const sha256 = string().regex(/^[a-f0-9]{64}$/);
4293
- const positiveInteger = number().int().safe().positive();
4294
- const nonNegativeInteger = number().int().safe().nonnegative();
4295
- const proposalId = string().regex(/^prop_[a-f0-9]{64}$/);
4296
- const workItemId = string().regex(/^wi_[a-f0-9]{64}$/);
4297
- const proposalRef = object({
4298
- proposalId,
4299
- revision: positiveInteger,
4300
- digest: sha256
4301
- }).strict();
4302
- const processingState = _enum([
4303
- "READY_FOR_REVIEW",
4304
- "PUBLISHING",
4305
- "NEEDS_ATTENTION",
4306
- "TERMINAL"
4307
- ]);
4308
- const publicationOutcome = _enum([
4309
- "PENDING_REVIEW",
4310
- "DISCARDED",
4311
- "NEEDS_ATTENTION",
4312
- "NEEDS_REFRESH",
4313
- "PUBLISHED",
4314
- "PUBLISH_FAILED"
4315
- ]);
4316
- const rootDisplay = object({
4317
- state: _enum(["EXISTING", "ABSENT"]),
4318
- scope: _enum(["PROJECT", "USER"]),
4319
- expectedProvider: identity,
4320
- expectedSource: _enum(["project-dsh", "user-dsh"]),
4321
- resolverVersion: identity,
4322
- rootContractVersion: identity,
4323
- resolutionContractDigest: sha256,
4324
- declaredRootPath: string().min(1).max(8192)
4325
- }).passthrough();
4326
- const targetDisplay = object({
4327
- bundlePath: string().min(1).max(8192),
4328
- skillFilePath: string().min(1).max(8192)
4329
- }).passthrough();
4330
- const actionBinding = discriminatedUnion("kind", [
4331
- object({
4332
- kind: literal("CREATE"),
4333
- rootBinding: rootDisplay,
4334
- targetBinding: targetDisplay,
4335
- expectedAbsence: object({
4336
- observedAt: string().min(1).max(64),
4337
- flatSkillFilePath: string().min(1).max(8192)
4338
- }).passthrough()
4339
- }).passthrough(),
4340
- object({
4341
- kind: literal("MERGE"),
4342
- rootBinding: rootDisplay,
4343
- targetBinding: targetDisplay,
4344
- baseBinding: object({
4345
- candidateKey: string().regex(/^cand_[a-f0-9]{64}$/),
4346
- path: string().min(1).max(8192),
4347
- exactBytes: string().min(1).max(65536),
4348
- bytesDigest: sha256,
4349
- observedAt: string().min(1).max(64)
4350
- }).passthrough()
4351
- }).passthrough(),
4352
- object({
4353
- kind: literal("DISCARD"),
4354
- coveringCandidateBinding: object({
4355
- candidateKey: string().regex(/^cand_[a-f0-9]{64}$/),
4356
- name: string().min(1).max(128),
4357
- source: identity,
4358
- content: string().min(1).max(65536),
4359
- contentDigest: sha256,
4360
- observedAt: string().min(1).max(64),
4361
- path: string().min(1).max(8192).optional()
4362
- }).passthrough()
4363
- }).passthrough()
4364
- ]);
4365
- const proposal = object({
4366
- schemaVersion: literal(1),
4367
- revision: positiveInteger,
4368
- createdAt: string().min(1).max(64),
4369
- sourceLearningProposalId: string().regex(/^lp_[a-f0-9]{64}$/),
4370
- kind: _enum([
4371
- "CREATE",
4372
- "MERGE",
4373
- "DISCARD"
4374
- ]),
4375
- name: string().min(1).max(128),
4376
- description: string().min(1).max(2048),
4377
- whenToUse: string().min(1).max(4096),
4378
- invocation: object({
4379
- modelInvocable: literal(true),
4380
- userInvocable: literal(false)
4381
- }).strict(),
4382
- exactSkillBytes: string().min(1).max(65536),
4383
- skillBytesDigest: sha256,
4384
- rendererVersion: identity,
4385
- persistenceScope: _enum(["PROJECT", "USER"]),
4386
- workspaceBinding: object({
4387
- workspaceId: identity,
4388
- canonicalPath: string().min(1).max(8192),
4389
- observedAt: string().min(1).max(64)
4390
- }).strict().optional(),
4391
- dshHomeBinding: object({
4392
- resolutionKind: _enum([
4393
- "CONFIGURATION",
4394
- "ENVIRONMENT",
4395
- "DEFAULT"
4396
- ]),
4397
- canonicalPath: string().min(1).max(8192),
4398
- identityDigest: sha256,
4399
- observedAt: string().min(1).max(64)
4400
- }).strict().optional(),
4401
- supportingExperienceIds: array(string().regex(/^exp_[a-f0-9]{64}$/)).min(1).max(3),
4402
- catalogObservationDigest: sha256,
4403
- curationRationale: string().min(1).max(4096),
4404
- actionBinding,
4405
- proposalId,
4406
- digest: sha256
4407
- }).strict().refine((value) => value.kind === value.actionBinding.kind, {
4408
- path: ["actionBinding"],
4409
- message: "action binding kind must match proposal kind"
4410
- });
4411
- const evidence = object({
4412
- source: literal("USER_DIRECT"),
4413
- messageSeq: nonNegativeInteger,
4414
- excerpt: string().max(8192),
4415
- excerptDigest: sha256,
4416
- redactionKinds: array(string().min(1).max(64)).max(16),
4417
- truncated: boolean()
4418
- }).strict();
4419
- const experience = object({
4420
- experienceId: string().regex(/^exp_[a-f0-9]{64}$/),
4421
- type: _enum([
4422
- "CORRECTION",
4423
- "CONSTRAINT",
4424
- "WORKFLOW"
4425
- ]),
4426
- lesson: string().min(1).max(4096),
4427
- persistenceScope: _enum(["PROJECT", "USER"]),
4428
- evidenceStrength: literal("HIGH"),
4429
- supportingEvidence: array(object({
4430
- messageSeq: nonNegativeInteger,
4431
- excerptDigest: sha256
4432
- }).strict()).min(1).max(4),
4433
- contextSummary: string().min(1).max(4096).optional()
4434
- }).strict();
4435
- const summary = object({
4436
- apiVersion: literal(1),
4437
- status: _enum([
4438
- "READY",
4439
- "RECOVERING",
4440
- "DEGRADED",
4441
- "INCOMPATIBLE"
4442
- ]),
4443
- recoveryLag: boolean(),
4444
- lastHealthCode: string().min(1).max(128).optional(),
4445
- queue: discriminatedUnion("completeness", [object({ completeness: literal("UNKNOWN") }).strict(), object({
4446
- completeness: literal("KNOWN"),
4447
- pendingReview: nonNegativeInteger,
4448
- publishing: nonNegativeInteger,
4449
- needsAttention: nonNegativeInteger
4450
- }).strict()])
4451
- }).strict();
4452
- const listItem = object({
4453
- workItemId,
4454
- workItemRevision: positiveInteger,
4455
- proposalRef,
4456
- kind: _enum([
4457
- "CREATE",
4458
- "MERGE",
4459
- "DISCARD"
4460
- ]),
4461
- name: string().min(1).max(128),
4462
- description: string().min(1).max(2048),
4463
- persistenceScope: _enum(["PROJECT", "USER"]),
4464
- createdAt: string().min(1).max(64),
4465
- processingState: _enum([
4466
- "READY_FOR_REVIEW",
4467
- "PUBLISHING",
4468
- "NEEDS_ATTENTION"
4469
- ]),
4470
- publicationOutcome: _enum([
4471
- "PENDING_REVIEW",
4472
- "NEEDS_ATTENTION",
4473
- "NEEDS_REFRESH",
4474
- "PUBLISH_FAILED"
4475
- ])
4476
- }).strict();
4477
- const listPage = object({
4478
- apiVersion: literal(1),
4479
- items: array(listItem).max(20),
4480
- nextCursor: string().regex(/^c_[0-9]+$/).optional()
4481
- }).strict();
4482
- const detail = object({
4483
- apiVersion: literal(1),
4484
- workItemId,
4485
- workItemRevision: positiveInteger,
4486
- processingState,
4487
- reviewDecision: _enum([
4488
- "PENDING",
4489
- "APPROVED",
4490
- "REJECTED"
4491
- ]),
4492
- publicationOutcome,
4493
- proposal,
4494
- sessionCoordinate: object({
4495
- rootSessionId: identity,
4496
- sessionCreatedAt: nonNegativeInteger,
4497
- turn: nonNegativeInteger,
4498
- turnEndSeq: nonNegativeInteger
4499
- }).strict(),
4500
- evidenceRefs: array(evidence).max(16),
4501
- experiences: array(experience).max(3)
4502
- }).strict();
4503
- const receipt = object({
4504
- apiVersion: literal(1),
4505
- workItemId,
4506
- workItemRevision: positiveInteger,
4507
- proposalRef,
4508
- changed: boolean(),
4509
- processingState,
4510
- reviewDecision: _enum([
4511
- "PENDING",
4512
- "APPROVED",
4513
- "REJECTED"
4514
- ]),
4515
- publicationOutcome
4516
- }).strict();
4517
- function parseValue$1(schema, value) {
4518
- const parsed = object({
4519
- ok: literal(true),
4520
- value: schema
4521
- }).strict().safeParse(value);
4522
- return parsed.success ? parsed.data.value : void 0;
4523
- }
4524
- function parseProposalSummary(value) {
4525
- return parseValue$1(summary, value);
4526
- }
4527
- function parseProposalList(value) {
4528
- return parseValue$1(listPage, value);
4529
- }
4530
- function parseProposalDetail(value) {
4531
- return parseValue$1(detail, value);
4532
- }
4533
- function parseMutationReceipt(value) {
4534
- return parseValue$1(receipt, value);
4535
- }
4536
- //#endregion
4537
- //#region src/client/proposal-inbox.ts
4538
- const PROPOSAL_POLL_INTERVAL_MS = 1e4;
4539
- const PROPOSAL_ACTIVE_POLL_INTERVAL_MS = 2e3;
4540
- const endpoint = {
4541
- summary: "summary",
4542
- list: "proposals/list",
4543
- get: "proposals/get",
4544
- approve: "proposals/approve",
4545
- reject: "proposals/reject",
4546
- retry: "proposals/retry",
4547
- confirmDiscard: "coverage/confirm-discard"
4548
- };
4549
- function describeProposalOutcome(detail) {
4550
- if (detail.publicationOutcome === "PUBLISHED") return "Skill 已发布并完成 Registry 回读";
4551
- if (detail.publicationOutcome === "DISCARDED") return "Proposal 已离开待处理队列,Skill 未更改";
4552
- if (detail.publicationOutcome === "NEEDS_REFRESH") return "发布绑定已变化,需要生成新的 Proposal";
4553
- if (detail.publicationOutcome === "PUBLISH_FAILED") return "发布失败,可重试";
4554
- if (detail.processingState === "NEEDS_ATTENTION") return "需要处理后才能继续";
4555
- if (detail.processingState === "PUBLISHING") return "已批准,正在发布";
4556
- return "待审核";
4557
- }
4558
- function describeProposalListItem(item) {
4559
- return describeProposalOutcome(item);
4560
- }
4561
- function describeReviewDecision(decision) {
4562
- if (decision === "APPROVED") return "已批准";
4563
- if (decision === "REJECTED") return "已拒绝";
4564
- return "待决定";
4565
- }
4566
- function describeProcessingState(state) {
4567
- if (state === "PUBLISHING") return "正在发布";
4568
- if (state === "NEEDS_ATTENTION") return "需要处理";
4569
- if (state === "TERMINAL") return "已结束";
4570
- return "待审核";
4571
- }
4572
- function describePublicationOutcome(outcome) {
4573
- if (outcome === "PUBLISHED") return "已发布并完成 Registry 回读";
4574
- if (outcome === "DISCARDED") return "未修改 Skill,Proposal 已离开队列";
4575
- if (outcome === "NEEDS_REFRESH") return "发布绑定已变化,需要新 Proposal";
4576
- if (outcome === "PUBLISH_FAILED") return "发布失败,可重试";
4577
- if (outcome === "NEEDS_ATTENTION") return "发布前事实需要处理";
4578
- return "尚未产生发布结果";
4579
- }
4580
- function browserEnvironment$1() {
4581
- return {
4582
- isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
4583
- setInterval: (callback, delayMs) => globalThis.setInterval(callback, delayMs),
4584
- clearInterval: (handle) => {
4585
- globalThis.clearInterval(handle);
4586
- },
4587
- onFocus: (listener) => {
4588
- if (typeof window === "undefined") return () => void 0;
4589
- window.addEventListener("focus", listener);
4590
- return () => {
4591
- window.removeEventListener("focus", listener);
4592
- };
4593
- },
4594
- onVisibilityChange: (listener) => {
4595
- if (typeof document === "undefined") return () => void 0;
4596
- document.addEventListener("visibilitychange", listener);
4597
- return () => {
4598
- document.removeEventListener("visibilitychange", listener);
4599
- };
4600
- },
4601
- onOnline: (listener) => {
4602
- if (typeof window === "undefined") return () => void 0;
4603
- window.addEventListener("online", listener);
4604
- return () => {
4605
- window.removeEventListener("online", listener);
4606
- };
4607
- }
4608
- };
4609
- }
4610
- const initialState$1 = {
4611
- open: false,
4612
- summaryPhase: "LOADING",
4613
- listPhase: "IDLE",
4614
- items: [],
4615
- detailPhase: "IDLE",
4616
- mutationPending: false,
4617
- announcement: ""
4618
- };
4619
- var ProposalInboxController = class {
4620
- workspaceId;
4621
- call;
4622
- environment;
4623
- #listeners = /* @__PURE__ */ new Set();
4624
- #state = initialState$1;
4625
- #started = false;
4626
- #disposed = false;
4627
- #pending;
4628
- #abort;
4629
- #timer;
4630
- #timerDelay;
4631
- #removeFocus;
4632
- #removeVisibility;
4633
- #removeOnline;
4634
- constructor(workspaceId, call, environment = browserEnvironment$1()) {
4635
- this.workspaceId = workspaceId;
4636
- this.call = call;
4637
- this.environment = environment;
4638
- }
4639
- snapshot = () => this.#state;
4640
- subscribe = (listener) => {
4641
- this.#listeners.add(listener);
4642
- return () => {
4643
- this.#listeners.delete(listener);
4644
- };
4645
- };
4646
- start() {
4647
- if (this.#started || this.#disposed) return;
4648
- this.#started = true;
4649
- const refresh = () => {
4650
- if (this.environment.isVisible()) this.#refreshVisible();
4651
- };
4652
- this.#removeFocus = this.environment.onFocus(refresh);
4653
- this.#removeOnline = this.environment.onOnline(refresh);
4654
- this.#removeVisibility = this.environment.onVisibilityChange(() => {
4655
- if (this.environment.isVisible()) {
4656
- this.#schedule();
4657
- this.#refreshVisible();
4658
- } else this.#unschedule();
4659
- });
4660
- if (this.environment.isVisible()) {
4661
- this.#schedule();
4662
- this.#refreshVisible();
4465
+ async preview(scope) {
4466
+ if (this.#disposed || this.#state.previewPending || this.#state.mutationPending) return;
4467
+ const workspaceId = this.getWorkspaceId();
4468
+ if (scope === "PROJECT" && workspaceId === void 0) {
4469
+ const error = { code: "PURGE_SCOPE_UNAVAILABLE" };
4470
+ this.#publish({
4471
+ ...this.#state,
4472
+ error,
4473
+ announcement: errorAnnouncement(error)
4474
+ });
4475
+ return;
4663
4476
  }
4664
- }
4665
- whenIdle() {
4666
- return this.#pending ?? Promise.resolve();
4667
- }
4668
- async open() {
4669
- if (this.#disposed) return;
4670
- this.#publish({
4671
- ...this.#state,
4672
- open: true
4673
- });
4674
- await this.whenIdle();
4675
- if (!this.#disposed && this.#state.open) await this.#loadList();
4676
- }
4677
- close() {
4678
- if (this.#disposed) return;
4679
- this.#publish({
4680
- ...this.#state,
4681
- open: false
4682
- });
4683
- }
4684
- async select(proposalId) {
4685
- if (this.#disposed || !/^prop_[a-f0-9]{64}$/.test(proposalId)) return;
4686
- await this.whenIdle();
4687
- if (this.#disposed || !this.#state.open) return;
4688
4477
  await this.#execute(async (signal) => {
4689
4478
  this.#publish({
4690
4479
  ...this.#state,
4691
- selectedProposalId: proposalId,
4692
- detailPhase: "LOADING",
4693
- detail: void 0,
4480
+ previewPending: true,
4481
+ previewScope: scope,
4482
+ preview: void 0,
4483
+ error: void 0,
4694
4484
  announcement: ""
4695
4485
  });
4696
- const detail = parseProposalDetail(await this.call(endpoint.get, {
4486
+ const preview = parseValue$1(previewSchema, await this.call("purge/preview", scope === "PROJECT" ? {
4487
+ apiVersion: 1,
4488
+ scope,
4489
+ workspaceId
4490
+ } : {
4697
4491
  apiVersion: 1,
4698
- proposalId
4492
+ scope
4699
4493
  }, signal));
4700
- if (detail === void 0) throw new Error("invalid detail response");
4701
4494
  this.#publish({
4702
4495
  ...this.#state,
4703
- detailPhase: "READY",
4704
- detail,
4705
- announcement: describeProposalOutcome(detail)
4496
+ previewPending: false,
4497
+ preview,
4498
+ error: void 0
4706
4499
  });
4707
- }, () => {
4500
+ }, (error) => {
4708
4501
  this.#publish({
4709
4502
  ...this.#state,
4710
- detailPhase: "ERROR",
4711
- detail: void 0
4503
+ previewPending: false,
4504
+ preview: void 0,
4505
+ error,
4506
+ announcement: errorAnnouncement(error)
4712
4507
  });
4713
4508
  });
4714
4509
  }
4715
- async mutate(action) {
4716
- await this.whenIdle();
4717
- const detail = this.#state.detail;
4718
- if (this.#disposed || detail === void 0 || this.#state.mutationPending) return;
4719
- const proposalRef = {
4720
- proposalId: detail.proposal.proposalId,
4721
- revision: detail.proposal.revision,
4722
- digest: detail.proposal.digest
4723
- };
4724
- const targetEndpoint = action === "APPROVE" ? endpoint.approve : action === "REJECT" ? endpoint.reject : action === "RETRY" ? endpoint.retry : endpoint.confirmDiscard;
4725
- const request = {
4726
- apiVersion: 1,
4727
- workItemId: detail.workItemId,
4728
- workItemRevision: detail.workItemRevision,
4729
- proposalRef,
4730
- ...action === "REJECT" ? { confirm: true } : {}
4731
- };
4510
+ cancelPreview() {
4511
+ if (this.#disposed || this.#state.mutationPending) return;
4512
+ this.#publish({
4513
+ ...this.#state,
4514
+ preview: void 0,
4515
+ previewScope: void 0,
4516
+ error: void 0,
4517
+ announcement: ""
4518
+ });
4519
+ }
4520
+ async confirm() {
4521
+ const preview = this.#state.preview;
4522
+ const scope = this.#state.previewScope;
4523
+ if (this.#disposed || preview === void 0 || scope === void 0 || this.#state.mutationPending) return;
4524
+ const currentWorkspaceId = this.getWorkspaceId();
4525
+ if (scope === "PROJECT" && (currentWorkspaceId === void 0 || preview.scopeBinding.scope !== "PROJECT" || preview.scopeBinding.workspaceId !== currentWorkspaceId)) {
4526
+ const error = { code: "PURGE_PREVIEW_STALE" };
4527
+ this.#publish({
4528
+ ...this.#state,
4529
+ preview: void 0,
4530
+ previewScope: void 0,
4531
+ error,
4532
+ announcement: errorAnnouncement(error)
4533
+ });
4534
+ return;
4535
+ }
4732
4536
  await this.#execute(async (signal) => {
4733
4537
  this.#publish({
4734
4538
  ...this.#state,
4539
+ hostDataEpoch: this.#state.hostDataEpoch + 1,
4735
4540
  mutationPending: true,
4541
+ error: void 0,
4736
4542
  announcement: ""
4737
4543
  });
4738
- const receipt = parseMutationReceipt(await this.call(targetEndpoint, request, signal));
4739
- if (receipt === void 0) throw new Error("invalid mutation receipt");
4740
- const announcement = describeProposalOutcome(receipt) || "Proposal 状态已更新";
4544
+ const receipt = parseValue$1(receiptSchema, await this.call("purge/confirm", scope === "PROJECT" ? {
4545
+ apiVersion: 1,
4546
+ scope,
4547
+ workspaceId: currentWorkspaceId,
4548
+ previewId: preview.previewId,
4549
+ digest: preview.digest
4550
+ } : {
4551
+ apiVersion: 1,
4552
+ scope,
4553
+ previewId: preview.previewId,
4554
+ digest: preview.digest
4555
+ }, signal));
4556
+ this.#acceptReceipt(scope, receipt);
4557
+ }, async (error) => {
4741
4558
  this.#publish({
4742
4559
  ...this.#state,
4743
4560
  mutationPending: false,
4744
- announcement,
4745
- detail: {
4746
- ...detail,
4747
- workItemRevision: receipt.workItemRevision,
4748
- processingState: receipt.processingState,
4749
- reviewDecision: receipt.reviewDecision,
4750
- publicationOutcome: receipt.publicationOutcome
4751
- }
4561
+ error,
4562
+ announcement: errorAnnouncement(error),
4563
+ preview: void 0,
4564
+ previewScope: void 0
4565
+ });
4566
+ await this.#readStatusAfterFailure(error);
4567
+ });
4568
+ }
4569
+ async retry() {
4570
+ const status = this.#state.status;
4571
+ if (this.#disposed || status?.state !== "IN_PROGRESS" || status.lastError === void 0 || this.#state.mutationPending) return;
4572
+ await this.#execute(async (signal) => {
4573
+ this.#publish({
4574
+ ...this.#state,
4575
+ hostDataEpoch: this.#state.hostDataEpoch + 1,
4576
+ mutationPending: true,
4577
+ error: void 0,
4578
+ announcement: "正在重试数据清理。"
4752
4579
  });
4753
- try {
4754
- await this.#refreshWithin(signal);
4755
- } catch {
4756
- if (!this.#disposed && !signal.aborted) this.#publish({
4757
- ...this.#state,
4758
- summaryPhase: this.#state.summary === void 0 ? "UNAVAILABLE" : "STALE",
4759
- listPhase: this.#state.listPhase === "LOADING" ? "ERROR" : this.#state.listPhase
4760
- });
4761
- }
4762
- }, () => {
4580
+ const receipt = parseValue$1(receiptSchema, await this.call("purge/retry", {
4581
+ apiVersion: 1,
4582
+ purgeId: status.purgeId
4583
+ }, signal));
4584
+ this.#acceptReceipt(this.#state.previewScope, receipt);
4585
+ }, async (error) => {
4763
4586
  this.#publish({
4764
4587
  ...this.#state,
4765
4588
  mutationPending: false,
4766
- announcement: "操作未完成,请刷新后重试"
4589
+ error,
4590
+ announcement: errorAnnouncement(error, true)
4767
4591
  });
4592
+ await this.#readStatusAfterFailure(error, true);
4768
4593
  });
4769
4594
  }
4770
4595
  dispose() {
4771
4596
  if (this.#disposed) return;
4772
4597
  this.#disposed = true;
4773
4598
  this.#unschedule();
4774
- this.#removeFocus?.();
4775
4599
  this.#removeVisibility?.();
4776
- this.#removeOnline?.();
4777
- this.#removeFocus = void 0;
4778
4600
  this.#removeVisibility = void 0;
4779
- this.#removeOnline = void 0;
4780
4601
  this.#abort?.abort();
4602
+ this.#statusAbort?.abort();
4781
4603
  this.#listeners.clear();
4782
4604
  }
4783
- async #refreshVisible() {
4784
- if (this.#disposed || !this.environment.isVisible()) return;
4785
- await this.#execute(async (signal) => {
4786
- await this.#refreshWithin(signal);
4787
- }, () => {
4605
+ #acceptReceipt(scope, receipt) {
4606
+ if (receipt.state === "COMPLETED") {
4788
4607
  this.#publish({
4789
4608
  ...this.#state,
4790
- summaryPhase: this.#state.summary === void 0 ? "UNAVAILABLE" : "STALE"
4609
+ hostDataEpoch: this.#state.hostDataEpoch + 1,
4610
+ statusPhase: "READY",
4611
+ status: {
4612
+ apiVersion: 1,
4613
+ state: "IDLE"
4614
+ },
4615
+ inProgressReceipt: void 0,
4616
+ preview: void 0,
4617
+ previewScope: void 0,
4618
+ mutationPending: false,
4619
+ error: void 0,
4620
+ announcement: completionAnnouncement(scope, receipt)
4791
4621
  });
4792
- });
4793
- }
4794
- async #refreshWithin(signal) {
4795
- const summary = parseProposalSummary(await this.call(endpoint.summary, {
4796
- apiVersion: 1,
4797
- workspaceId: this.workspaceId
4798
- }, signal));
4799
- if (summary === void 0) throw new Error("invalid summary response");
4622
+ return;
4623
+ }
4800
4624
  this.#publish({
4801
4625
  ...this.#state,
4802
- summaryPhase: "READY",
4803
- summary
4626
+ mutationPending: false,
4627
+ preview: void 0,
4628
+ inProgressReceipt: receipt,
4629
+ announcement: `Run2Skill 数据清理进行中:${receipt.phase ?? "HIDING"}。`
4804
4630
  });
4805
- if (!this.#state.open) return;
4806
- await this.#loadListWithin(signal);
4807
- const selected = this.#state.selectedProposalId;
4808
- if (selected !== void 0 && this.#state.detail?.processingState === "PUBLISHING") {
4809
- const detail = parseProposalDetail(await this.call(endpoint.get, {
4810
- apiVersion: 1,
4811
- proposalId: selected
4812
- }, signal));
4813
- if (detail !== void 0) this.#publish({
4814
- ...this.#state,
4815
- detailPhase: "READY",
4816
- detail,
4817
- announcement: describeProposalOutcome(detail) || this.#state.announcement
4818
- });
4819
- }
4820
4631
  }
4821
- async #loadList() {
4822
- await this.#execute(async (signal) => {
4823
- await this.#loadListWithin(signal);
4824
- }, () => {
4632
+ async #readStatusAfterFailure(error, durableBoundary = false) {
4633
+ try {
4634
+ const status = parseValue$1(statusSchema, await this.call("purge/status", { apiVersion: 1 }, new AbortController().signal));
4825
4635
  this.#publish({
4826
4636
  ...this.#state,
4827
- listPhase: "ERROR"
4637
+ statusPhase: "READY",
4638
+ status,
4639
+ inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
4640
+ announcement: errorAnnouncement(error, durableBoundary || status.state === "IN_PROGRESS")
4828
4641
  });
4829
- });
4642
+ } catch {}
4830
4643
  }
4831
- async #loadListWithin(signal) {
4832
- this.#publish({
4833
- ...this.#state,
4834
- listPhase: "LOADING"
4835
- });
4836
- const items = [];
4837
- let cursor;
4838
- for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
4839
- const page = parseProposalList(await this.call(endpoint.list, {
4840
- apiVersion: 1,
4841
- workspaceId: this.workspaceId,
4842
- ...cursor === void 0 ? {} : { cursor }
4843
- }, signal));
4844
- if (page === void 0) throw new Error("invalid list response");
4845
- items.push(...page.items);
4846
- cursor = page.nextCursor;
4847
- if (cursor === void 0) {
4644
+ async #refreshStatus() {
4645
+ if (this.#disposed || !this.#active || !this.environment.isVisible()) return;
4646
+ if (this.#statusPending !== void 0) return this.#statusPending;
4647
+ const abort = new AbortController();
4648
+ this.#statusAbort = abort;
4649
+ const pending = (async () => {
4650
+ try {
4651
+ const status = parseValue$1(statusSchema, await this.call("purge/status", { apiVersion: 1 }, abort.signal));
4652
+ if (this.#disposed || abort.signal.aborted) return;
4653
+ const completed = status.state === "IDLE" && (this.#state.status?.state === "IN_PROGRESS" || this.#state.inProgressReceipt?.state === "IN_PROGRESS");
4654
+ const started = status.state === "IN_PROGRESS" && this.#state.status?.state !== "IN_PROGRESS" && this.#state.inProgressReceipt?.state !== "IN_PROGRESS" && !this.#state.mutationPending;
4848
4655
  this.#publish({
4849
4656
  ...this.#state,
4850
- listPhase: "READY",
4851
- items
4657
+ hostDataEpoch: started || completed ? this.#state.hostDataEpoch + 1 : this.#state.hostDataEpoch,
4658
+ statusPhase: "READY",
4659
+ status,
4660
+ inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
4661
+ previewScope: completed ? void 0 : this.#state.previewScope,
4662
+ announcement: completed ? polledCompletionAnnouncement(this.#state.previewScope) : this.#state.announcement
4663
+ });
4664
+ } catch {
4665
+ if (this.#disposed || abort.signal.aborted) return;
4666
+ this.#publish({
4667
+ ...this.#state,
4668
+ statusPhase: this.#state.status === void 0 ? "UNAVAILABLE" : "STALE"
4852
4669
  });
4853
- return;
4854
4670
  }
4855
- }
4856
- throw new Error("proposal list page limit exceeded");
4671
+ })();
4672
+ this.#statusPending = pending;
4673
+ await pending.finally(() => {
4674
+ if (this.#statusPending === pending) this.#statusPending = void 0;
4675
+ if (this.#statusAbort === abort) this.#statusAbort = void 0;
4676
+ });
4857
4677
  }
4858
4678
  async #execute(task, onError) {
4859
4679
  if (this.#disposed || this.#pending !== void 0) return this.#pending ?? Promise.resolve();
@@ -4862,8 +4682,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4862
4682
  const pending = (async () => {
4863
4683
  try {
4864
4684
  await task(abort.signal);
4865
- } catch {
4866
- if (!this.#disposed && !abort.signal.aborted) onError();
4685
+ } catch (error) {
4686
+ if (!this.#disposed && !abort.signal.aborted) await onError(error instanceof PurgeResponseError ? error.facts : { code: "PURGE_STORAGE_UNAVAILABLE" });
4867
4687
  }
4868
4688
  })();
4869
4689
  this.#pending = pending;
@@ -4872,776 +4692,594 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4872
4692
  if (this.#abort === abort) this.#abort = void 0;
4873
4693
  });
4874
4694
  }
4875
- #schedule() {
4876
- if (this.#disposed) return;
4877
- const delay = this.#state.open ? PROPOSAL_ACTIVE_POLL_INTERVAL_MS : PROPOSAL_POLL_INTERVAL_MS;
4878
- if (this.#timer !== void 0 && this.#timerDelay === delay) return;
4879
- this.#unschedule();
4880
- this.#timerDelay = delay;
4881
- this.#timer = this.environment.setInterval(() => {
4882
- this.#refreshVisible();
4883
- }, delay);
4884
- }
4885
- #unschedule() {
4886
- if (this.#timer === void 0) return;
4887
- this.environment.clearInterval(this.#timer);
4888
- this.#timer = void 0;
4889
- this.#timerDelay = void 0;
4890
- }
4891
- #publish(state) {
4892
- if (this.#disposed) return;
4893
- this.#state = state;
4894
- if (this.#started && this.environment.isVisible()) this.#schedule();
4895
- for (const listener of this.#listeners) listener();
4896
- }
4897
- };
4898
- const formatCharacters = /* @__PURE__ */ new Map([
4899
- [0, "NULL"],
4900
- [9, "CHARACTER TABULATION"],
4901
- [1564, "ARABIC LETTER MARK"],
4902
- [8203, "ZERO WIDTH SPACE"],
4903
- [8204, "ZERO WIDTH NON-JOINER"],
4904
- [8205, "ZERO WIDTH JOINER"],
4905
- [8206, "LEFT-TO-RIGHT MARK"],
4906
- [8207, "RIGHT-TO-LEFT MARK"],
4907
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
4908
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
4909
- [8236, "POP DIRECTIONAL FORMATTING"],
4910
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
4911
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
4912
- [8288, "WORD JOINER"],
4913
- [8294, "LEFT-TO-RIGHT ISOLATE"],
4914
- [8295, "RIGHT-TO-LEFT ISOLATE"],
4915
- [8296, "FIRST STRONG ISOLATE"],
4916
- [8297, "POP DIRECTIONAL ISOLATE"],
4917
- [65279, "ZERO WIDTH NO-BREAK SPACE"]
4918
- ]);
4919
- function makeSafeText(value) {
4920
- let result = "";
4921
- for (const character of value) {
4922
- const codePoint = character.codePointAt(0);
4923
- if (character === "\n") {
4924
- result += character;
4925
- continue;
4926
- }
4927
- const name = formatCharacters.get(codePoint) ?? (/\p{Cf}/u.test(character) ? "FORMAT" : codePoint < 32 || codePoint >= 127 && codePoint <= 159 ? "CONTROL" : void 0);
4928
- result += name === void 0 ? character : `[U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} ${name}]`;
4929
- }
4930
- return result;
4931
- }
4932
- function ProposalTextView(props) {
4933
- return (0, react.createElement)("pre", {
4934
- "aria-label": props.label,
4935
- "data-run2skill-text-mode": props.mode,
4936
- style: {
4937
- whiteSpace: "pre-wrap",
4938
- overflowWrap: "anywhere",
4939
- maxHeight: "24rem",
4940
- overflow: "auto",
4941
- padding: "0.75rem",
4942
- border: "1px solid currentColor",
4943
- borderRadius: "0.375rem",
4944
- unicodeBidi: "isolate"
4945
- }
4946
- }, props.mode === "SAFE" ? makeSafeText(props.value) : props.value);
4947
- }
4948
- function makeExactLineDiff(before, after) {
4949
- const left = before.split("\n");
4950
- const right = after.split("\n");
4951
- if (left.length * right.length > 25e4) return [...left.map((text) => ({
4952
- kind: "REMOVE",
4953
- text
4954
- })), ...right.map((text) => ({
4955
- kind: "ADD",
4956
- text
4957
- }))];
4958
- const table = Array.from({ length: left.length + 1 }, () => new Uint32Array(right.length + 1));
4959
- for (let leftIndex = left.length - 1; leftIndex >= 0; leftIndex -= 1) for (let rightIndex = right.length - 1; rightIndex >= 0; rightIndex -= 1) table[leftIndex][rightIndex] = left[leftIndex] === right[rightIndex] ? table[leftIndex + 1][rightIndex + 1] + 1 : Math.max(table[leftIndex + 1][rightIndex], table[leftIndex][rightIndex + 1]);
4960
- const result = [];
4961
- let leftIndex = 0;
4962
- let rightIndex = 0;
4963
- while (leftIndex < left.length || rightIndex < right.length) if (leftIndex < left.length && rightIndex < right.length && left[leftIndex] === right[rightIndex]) {
4964
- result.push({
4965
- kind: "CONTEXT",
4966
- text: left[leftIndex]
4967
- });
4968
- leftIndex += 1;
4969
- rightIndex += 1;
4970
- } else if (leftIndex < left.length && (rightIndex >= right.length || table[leftIndex + 1][rightIndex] >= table[leftIndex][rightIndex + 1])) {
4971
- result.push({
4972
- kind: "REMOVE",
4973
- text: left[leftIndex]
4974
- });
4975
- leftIndex += 1;
4976
- } else {
4977
- result.push({
4978
- kind: "ADD",
4979
- text: right[rightIndex]
4980
- });
4981
- rightIndex += 1;
4982
- }
4983
- return result;
4984
- }
4985
- //#endregion
4986
- //#region src/client/dialog-focus.ts
4987
- const focusableSelector = [
4988
- "button:not([disabled])",
4989
- "[href]",
4990
- "input:not([disabled])",
4991
- "select:not([disabled])",
4992
- "textarea:not([disabled])",
4993
- "[tabindex]:not([tabindex=\"-1\"])"
4994
- ].join(",");
4995
- function trapDialogTab(key, backwards, preventDefault, root, activeElement) {
4996
- if (key !== "Tab") return;
4997
- const focusable = Array.from(root.querySelectorAll(focusableSelector));
4998
- if (focusable.length === 0) {
4999
- preventDefault();
5000
- return;
5001
- }
5002
- const first = focusable[0];
5003
- const last = focusable.at(-1);
5004
- if (!focusable.includes(activeElement)) {
5005
- preventDefault();
5006
- (backwards ? last : first).focus();
5007
- return;
4695
+ #schedule() {
4696
+ if (this.#disposed || !this.#active) return;
4697
+ const delay = this.#state.status?.state === "IN_PROGRESS" || this.#state.inProgressReceipt?.state === "IN_PROGRESS" || this.#state.mutationPending ? PURGE_ACTIVE_POLL_INTERVAL_MS : PURGE_IDLE_POLL_INTERVAL_MS;
4698
+ if (this.#timer !== void 0 && this.#timerDelay === delay) return;
4699
+ this.#unschedule();
4700
+ this.#timerDelay = delay;
4701
+ this.#timer = this.environment.setInterval(() => {
4702
+ this.#refreshStatus();
4703
+ }, delay);
5008
4704
  }
5009
- if (backwards && activeElement === first) {
5010
- preventDefault();
5011
- last.focus();
5012
- } else if (!backwards && activeElement === last) {
5013
- preventDefault();
5014
- first.focus();
4705
+ #unschedule() {
4706
+ if (this.#timer === void 0) return;
4707
+ this.environment.clearInterval(this.#timer);
4708
+ this.#timer = void 0;
4709
+ this.#timerDelay = void 0;
5015
4710
  }
4711
+ #publish(state) {
4712
+ if (this.#disposed) return;
4713
+ this.#state = state;
4714
+ if (this.#started && this.#active && this.environment.isVisible()) this.#schedule();
4715
+ for (const listener of this.#listeners) listener();
4716
+ }
4717
+ };
4718
+ const phaseCopy = {
4719
+ HIDING: "正在隐藏命中数据",
4720
+ DELETING_LINEAGES: "正在删除 Skill 关联记录",
4721
+ DELETING_WORK_ITEMS: "正在删除待处理数据",
4722
+ VERIFYING: "正在验证清理结果"
4723
+ };
4724
+ function PreviewSummary({ preview }) {
4725
+ return (0, react.createElement)("dl", null, (0, react.createElement)("dt", null, "将删除"), (0, react.createElement)("dd", null, `${String(preview.workItemCount)} 条待处理数据;${String(preview.lineageCount)} 条 Skill 关联记录;${String(preview.derivedRecordCount)} 条其他中间缓存`), (0, react.createElement)("dt", null, "将保留"), (0, react.createElement)("dd", null, "已发布 Skill、DSH 原始会话记录和非 Run2Skill 设置"), (0, react.createElement)("dt", null, "正在保存的技能草稿"), (0, react.createElement)("dd", null, `${String(preview.busyPublicationCount)} 份;存在时不会开始清理`));
5016
4726
  }
5017
- //#endregion
5018
- //#region src/client/status-copy.ts
5019
- function describeRun2skillHealth(status) {
5020
- if (status === "INCOMPATIBLE") return "run2skill 当前版本不兼容";
5021
- if (status === "DEGRADED") return "run2skill 暂时降级";
5022
- if (status === "RECOVERING") return "run2skill 正在恢复历史观察";
5023
- return "run2skill 已就绪";
5024
- }
5025
- //#endregion
5026
- //#region src/client/proposal-inbox-view.ts
5027
- function proposalInboxContentBlocked(mutationPending, rejectConfirm) {
5028
- return mutationPending || rejectConfirm;
5029
- }
5030
- function useDialogFocus(open, triggerRef, dialogRef, focusTrapSuspended) {
5031
- (0, react.useEffect)(() => {
5032
- if (!open) return;
5033
- if (typeof document === "undefined") return;
5034
- const dialog = dialogRef.current;
5035
- const initial = dialog?.querySelector("[data-initial-focus]") ?? dialog?.querySelector(focusableSelector);
5036
- const recapture = (event) => {
5037
- if (focusTrapSuspended.current) return;
5038
- if (dialog !== null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
5039
- };
5040
- document.addEventListener("focusin", recapture, true);
5041
- initial?.focus();
5042
- return () => {
5043
- document.removeEventListener("focusin", recapture, true);
5044
- triggerRef.current?.focus();
5045
- };
5046
- }, [
5047
- dialogRef,
5048
- focusTrapSuspended,
5049
- open,
5050
- triggerRef
5051
- ]);
5052
- }
5053
- function countLabel(state) {
5054
- const queue = state.summary?.queue;
5055
- if (queue === void 0 || queue.completeness === "UNKNOWN") return "Skill 提案,待处理数量未知";
5056
- const total = queue.pendingReview + queue.publishing + queue.needsAttention;
5057
- const facts = [
5058
- queue.pendingReview > 0 ? `${String(queue.pendingReview)} 条待审核` : void 0,
5059
- queue.publishing > 0 ? `${String(queue.publishing)} 条正在发布` : void 0,
5060
- queue.needsAttention > 0 ? `${String(queue.needsAttention)} 条需要处理` : void 0
5061
- ].filter((fact) => fact !== void 0);
5062
- return facts.length === 0 ? "当前没有待处理 Skill 提案" : `${String(total)} 条 Skill 提案待处理:${facts.join(",")}`;
5063
- }
5064
- function describeProposalSummaryState(state) {
5065
- if (state.summaryPhase === "LOADING") return "Proposal 队列状态加载中";
5066
- if (state.summaryPhase === "UNAVAILABLE") return "Proposal 队列状态暂不可用";
5067
- if (state.summary === void 0) return "Proposal 队列状态未知";
5068
- const health = describeRun2skillHealth(state.summary.status);
5069
- const queue = state.summary.queue.completeness === "UNKNOWN" ? "待处理数量未知" : countLabel(state);
5070
- return state.summaryPhase === "STALE" ? `Proposal 队列状态可能已过期:${health};${queue}` : `${health};${queue}`;
5071
- }
5072
- function ProposalInboxHeaderAction(props) {
5073
- const controller = (0, react.useMemo)(() => new ProposalInboxController(props.workspaceId, props.callReview), [props.callReview, props.workspaceId]);
5074
- (0, react.useEffect)(() => {
5075
- controller.start();
5076
- return () => {
5077
- controller.dispose();
5078
- };
5079
- }, [controller]);
5080
- const state = (0, react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
5081
- const triggerRef = (0, react.useRef)(null);
5082
- const dialogRef = (0, react.useRef)(null);
5083
- const [textMode, setTextMode] = (0, react.useState)("SAFE");
5084
- const [rejectConfirm, setRejectConfirm] = (0, react.useState)(false);
5085
- const focusTrapSuspended = (0, react.useRef)(false);
5086
- focusTrapSuspended.current = rejectConfirm;
5087
- useDialogFocus(state.open, triggerRef, dialogRef, focusTrapSuspended);
5088
- const label = countLabel(state);
5089
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
5090
- ref: triggerRef,
5091
- type: "button",
5092
- "aria-label": label,
5093
- "aria-haspopup": "dialog",
5094
- "aria-expanded": state.open,
5095
- "data-run2skill-proposal-trigger": true,
5096
- onClick: () => {
5097
- controller.open();
5098
- },
5099
- style: { outlineOffset: "0.2rem" }
5100
- }, label), state.open ? (0, react.createElement)(ProposalInboxPanel, {
5101
- controller,
5102
- state,
5103
- dialogRef,
5104
- textMode,
5105
- setTextMode,
5106
- rejectConfirm,
5107
- setRejectConfirm
5108
- }) : null);
5109
- }
5110
- function closeOnKeyboard(event, controller, dialog) {
5111
- if (event.key === "Escape") {
5112
- event.preventDefault();
5113
- controller.close();
5114
- } else if (dialog !== null) trapDialogTab(event.key, event.shiftKey, () => {
5115
- event.preventDefault();
5116
- }, dialog, typeof document === "undefined" ? void 0 : document.activeElement);
5117
- }
5118
- function ProposalInboxPanel(props) {
5119
- const { controller, state } = props;
5120
- const contentBlocked = proposalInboxContentBlocked(state.mutationPending, props.rejectConfirm);
5121
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", {
5122
- "aria-hidden": true,
5123
- "data-run2skill-proposal-backdrop": true,
5124
- style: {
5125
- position: "fixed",
5126
- inset: 0,
5127
- zIndex: 999,
5128
- background: "rgb(0 0 0 / 35%)"
5129
- }
5130
- }), (0, react.createElement)("div", {
5131
- ref: props.dialogRef,
5132
- role: "dialog",
5133
- "aria-modal": true,
5134
- "aria-labelledby": "run2skill-proposal-inbox-title",
5135
- onKeyDown: (event) => {
5136
- closeOnKeyboard(event, controller, props.dialogRef.current);
5137
- },
5138
- style: {
5139
- position: "fixed",
5140
- inset: "5vh 5vw",
5141
- zIndex: 1e3,
5142
- display: "grid",
5143
- gridTemplateColumns: "minmax(16rem, 24rem) minmax(0, 1fr)",
5144
- gap: "1rem",
5145
- padding: "1rem",
5146
- overflow: "hidden",
5147
- background: "Canvas",
5148
- color: "CanvasText",
5149
- border: "2px solid currentColor",
5150
- borderRadius: "0.5rem",
5151
- boxShadow: "0 1rem 3rem rgb(0 0 0 / 35%)"
5152
- }
5153
- }, (0, react.createElement)("div", {
5154
- "aria-hidden": props.rejectConfirm || void 0,
5155
- inert: props.rejectConfirm ? "" : void 0,
5156
- style: {
5157
- display: "contents",
5158
- pointerEvents: props.rejectConfirm ? "none" : void 0
5159
- }
5160
- }, (0, react.createElement)("section", {
5161
- "aria-label": "Proposal 待处理队列",
5162
- style: { overflow: "auto" }
5163
- }, (0, react.createElement)("div", { style: {
5164
- display: "flex",
5165
- alignItems: "center",
5166
- justifyContent: "space-between"
5167
- } }, (0, react.createElement)("h2", { id: "run2skill-proposal-inbox-title" }, "Skill Proposal Inbox"), (0, react.createElement)("button", {
5168
- type: "button",
5169
- "data-initial-focus": true,
5170
- onClick: () => {
5171
- controller.close();
5172
- }
5173
- }, "关闭")), (0, react.createElement)("p", { role: "status" }, countLabel(state)), (0, react.createElement)("p", {
5174
- role: "status",
5175
- "aria-live": "polite"
5176
- }, describeProposalSummaryState(state)), state.listPhase === "LOADING" ? (0, react.createElement)("p", null, "正在加载待处理队列…") : null, state.listPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "待处理队列暂不可用") : null, state.listPhase === "READY" && state.items.length === 0 ? (0, react.createElement)("p", null, "当前没有待处理 Proposal") : null, (0, react.createElement)("ul", { style: {
5177
- listStyle: "none",
5178
- padding: 0
5179
- } }, ...state.items.map((item) => (0, react.createElement)("li", { key: item.proposalRef.proposalId }, (0, react.createElement)("button", {
5180
- type: "button",
5181
- disabled: contentBlocked || state.detailPhase === "LOADING",
5182
- "aria-current": state.selectedProposalId === item.proposalRef.proposalId ? "true" : void 0,
5183
- onClick: () => {
5184
- controller.select(item.proposalRef.proposalId);
5185
- },
5186
- style: {
5187
- display: "block",
5188
- width: "100%",
5189
- padding: "0.75rem",
5190
- marginBlock: "0.25rem",
5191
- textAlign: "start",
5192
- outlineOffset: "0.15rem"
5193
- }
5194
- }, `${item.kind} · ${makeSafeText(item.name)} · ${item.persistenceScope} · ${describeProposalListItem(item)}`))))), (0, react.createElement)("section", {
5195
- "aria-label": "Proposal 详情",
5196
- style: {
5197
- overflow: "auto",
5198
- minWidth: 0
5199
- }
5200
- }, state.detailPhase === "IDLE" ? (0, react.createElement)("p", null, "选择一个 Proposal 查看完整事实") : null, state.detailPhase === "LOADING" ? (0, react.createElement)("p", null, "正在加载 Proposal 详情…") : null, state.detailPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "Proposal 详情暂不可用") : null, state.detail === void 0 ? null : (0, react.createElement)(ProposalDetailView, {
5201
- detail: state.detail,
5202
- textMode: props.textMode,
5203
- setTextMode: props.setTextMode,
5204
- mutationPending: contentBlocked,
5205
- onApprove: () => {
5206
- controller.mutate("APPROVE");
5207
- },
5208
- onReject: () => {
5209
- props.setRejectConfirm(true);
5210
- },
5211
- onRetry: () => {
5212
- controller.mutate("RETRY");
5213
- },
5214
- onConfirmDiscard: () => {
5215
- controller.mutate("CONFIRM_DISCARD");
5216
- }
5217
- }), (0, react.createElement)("div", {
5218
- "aria-live": "polite",
5219
- "aria-atomic": true
5220
- }, state.announcement))), props.rejectConfirm ? (0, react.createElement)(RejectConfirmation, {
5221
- disabled: state.mutationPending,
5222
- onCancel: () => {
5223
- props.setRejectConfirm(false);
5224
- },
5225
- onConfirm: () => {
5226
- props.setRejectConfirm(false);
5227
- controller.mutate("REJECT");
5228
- }
5229
- }) : null));
5230
- }
5231
- function factsFromAction(detail) {
5232
- const action = detail.proposal.actionBinding;
5233
- if (action.kind === "CREATE") return [
5234
- `Root contract: ${action.rootBinding.rootContractVersion}`,
5235
- `Root resolver: ${action.rootBinding.resolverVersion}`,
5236
- `Expected provider/source: ${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5237
- `Resolution digest: ${action.rootBinding.resolutionContractDigest}`,
5238
- `Root state: ${action.rootBinding.state}`,
5239
- `Declared root: ${action.rootBinding.declaredRootPath}`,
5240
- `Bundle target: ${action.targetBinding.bundlePath}`,
5241
- `Skill target: ${action.targetBinding.skillFilePath}`,
5242
- `Flat target checked: ${action.expectedAbsence.flatSkillFilePath}`,
5243
- `Expected absence observed: ${action.expectedAbsence.observedAt}`,
5244
- "Bundle absent: yes",
5245
- "Skill file absent: yes",
5246
- "Flat Skill file absent: yes"
5247
- ].join("\n");
5248
- if (action.kind === "MERGE") return [
5249
- `Root contract: ${action.rootBinding.rootContractVersion}`,
5250
- `Root resolver: ${action.rootBinding.resolverVersion}`,
5251
- `Expected provider/source: ${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5252
- `Resolution digest: ${action.rootBinding.resolutionContractDigest}`,
5253
- `Root state: ${action.rootBinding.state}`,
5254
- `Declared root: ${action.rootBinding.declaredRootPath}`,
5255
- `Target identity: ${action.baseBinding.candidateKey}`,
5256
- `Observed target path: ${action.baseBinding.path}`,
5257
- `Bundle target: ${action.targetBinding.bundlePath}`,
5258
- `Skill target: ${action.targetBinding.skillFilePath}`,
5259
- `Base digest: ${action.baseBinding.bytesDigest}`,
5260
- `Base observed: ${action.baseBinding.observedAt}`
5261
- ].join("\n");
5262
- return [
5263
- `Covering candidate: ${action.coveringCandidateBinding.candidateKey}`,
5264
- `Name: ${action.coveringCandidateBinding.name}`,
5265
- `Source: ${action.coveringCandidateBinding.source}`,
5266
- `Digest: ${action.coveringCandidateBinding.contentDigest}`,
5267
- `Observed: ${action.coveringCandidateBinding.observedAt}`,
5268
- ...action.coveringCandidateBinding.path === void 0 ? [] : [`Path: ${action.coveringCandidateBinding.path}`]
5269
- ].join("\n");
5270
- }
5271
- function ProposalDetailView(props) {
5272
- const { detail, mutationPending } = props;
5273
- const proposal = detail.proposal;
5274
- const actionable = detail.reviewDecision === "PENDING" && detail.processingState === "READY_FOR_REVIEW" && !mutationPending;
5275
- const coordinate = detail.sessionCoordinate;
5276
- const baseBytes = proposal.actionBinding.kind === "MERGE" ? proposal.actionBinding.baseBinding.exactBytes : void 0;
5277
- const coveringBytes = proposal.actionBinding.kind === "DISCARD" ? proposal.actionBinding.coveringCandidateBinding.content : void 0;
5278
- const diff = baseBytes === void 0 ? [] : makeExactLineDiff(baseBytes, proposal.exactSkillBytes);
5279
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h3", null, `${proposal.kind}: ${makeSafeText(proposal.name)}`), (0, react.createElement)("dl", null, (0, react.createElement)("dt", null, "说明"), (0, react.createElement)("dd", null, makeSafeText(proposal.description)), (0, react.createElement)("dt", null, "何时使用"), (0, react.createElement)("dd", null, makeSafeText(proposal.whenToUse)), (0, react.createElement)("dt", null, "Scope"), (0, react.createElement)("dd", null, proposal.persistenceScope), (0, react.createElement)("dt", null, "Proposal revision"), (0, react.createElement)("dd", null, String(proposal.revision)), (0, react.createElement)("dt", null, "Proposal digest"), (0, react.createElement)("dd", null, proposal.digest), (0, react.createElement)("dt", null, "Session / Turn"), (0, react.createElement)("dd", null, `${coordinate.rootSessionId} / ${String(coordinate.turn)} / seq ${String(coordinate.turnEndSeq)}`), proposal.workspaceBinding === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("dt", null, "Workspace"), (0, react.createElement)("dd", null, `${proposal.workspaceBinding.workspaceId} · ${makeSafeText(proposal.workspaceBinding.canonicalPath)}`)), proposal.dshHomeBinding === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("dt", null, "DSH Home"), (0, react.createElement)("dd", null, `${proposal.dshHomeBinding.resolutionKind} · ${makeSafeText(proposal.dshHomeBinding.canonicalPath)}`), (0, react.createElement)("dt", null, "DSH Home identity"), (0, react.createElement)("dd", null, proposal.dshHomeBinding.identityDigest)), (0, react.createElement)("dt", null, "审核决定"), (0, react.createElement)("dd", null, describeReviewDecision(detail.reviewDecision)), (0, react.createElement)("dt", null, "处理状态"), (0, react.createElement)("dd", null, describeProcessingState(detail.processingState)), (0, react.createElement)("dt", null, "发布结果"), (0, react.createElement)("dd", null, describePublicationOutcome(detail.publicationOutcome))), (0, react.createElement)("h4", null, "Why learned"), (0, react.createElement)("p", null, makeSafeText(proposal.curationRationale)), ...detail.experiences.map((experience) => (0, react.createElement)("article", { key: experience.experienceId }, (0, react.createElement)("strong", null, `${experience.type} · ${experience.evidenceStrength}`), (0, react.createElement)("p", null, makeSafeText(experience.lesson)))), (0, react.createElement)("h4", null, "过滤后的 Evidence"), ...detail.evidenceRefs.map((evidence) => (0, react.createElement)("article", { key: `${String(evidence.messageSeq)}:${evidence.excerptDigest}` }, (0, react.createElement)("strong", null, `message seq ${String(evidence.messageSeq)}${evidence.truncated ? " · 已截断" : ""}`), (0, react.createElement)(ProposalTextView, {
5280
- value: evidence.excerpt,
5281
- mode: "SAFE",
5282
- label: `Evidence ${String(evidence.messageSeq)}`
5283
- }))), (0, react.createElement)("h4", null, "绑定事实"), (0, react.createElement)(ProposalTextView, {
5284
- value: factsFromAction(detail),
5285
- mode: "SAFE",
5286
- label: "绑定事实"
5287
- }), (0, react.createElement)("div", {
5288
- role: "group",
5289
- "aria-label": "内容显示方式"
5290
- }, (0, react.createElement)("button", {
5291
- type: "button",
5292
- "aria-pressed": props.textMode === "SAFE",
5293
- onClick: () => {
5294
- props.setTextMode("SAFE");
5295
- }
5296
- }, "安全视图"), (0, react.createElement)("button", {
5297
- type: "button",
5298
- "aria-pressed": props.textMode === "RAW",
5299
- onClick: () => {
5300
- props.setTextMode("RAW");
5301
- }
5302
- }, "原始内容")), (0, react.createElement)("h4", null, "将要批准的完整 SKILL.md"), (0, react.createElement)(ProposalTextView, {
5303
- value: proposal.exactSkillBytes,
5304
- mode: props.textMode,
5305
- label: "将要批准的完整 SKILL.md"
5306
- }), baseBytes === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h4", null, "MERGE Base"), (0, react.createElement)(ProposalTextView, {
5307
- value: baseBytes,
5308
- mode: props.textMode,
5309
- label: "MERGE Base"
5310
- }), (0, react.createElement)("h4", null, "精确 Diff"), (0, react.createElement)("pre", {
5311
- "aria-label": "精确 Diff",
5312
- style: {
5313
- whiteSpace: "pre-wrap",
5314
- unicodeBidi: "isolate"
5315
- }
5316
- }, diff.map((line) => `${line.kind === "ADD" ? "+" : line.kind === "REMOVE" ? "-" : " "} ${makeSafeText(line.text)}`).join("\n"))), coveringBytes === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h4", null, "覆盖已有 Skill 的完整内容"), (0, react.createElement)(ProposalTextView, {
5317
- value: coveringBytes,
5318
- mode: props.textMode,
5319
- label: "覆盖已有 Skill 的完整内容"
5320
- })), (0, react.createElement)("div", {
5321
- role: "group",
5322
- "aria-label": "Proposal 操作"
5323
- }, proposal.kind === "DISCARD" ? (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
5324
- type: "button",
5325
- disabled: !actionable,
5326
- onClick: props.onConfirmDiscard
5327
- }, "确认无需新建 Skill"), (0, react.createElement)("button", {
5328
- type: "button",
5329
- disabled: !actionable,
5330
- onClick: props.onRetry
5331
- }, "不同意,重新分析一次")) : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
5332
- type: "button",
5333
- disabled: !actionable,
5334
- onClick: props.onApprove
5335
- }, detail.processingState === "PUBLISHING" ? "正在发布…" : "批准并发布"), (0, react.createElement)("button", {
5336
- type: "button",
5337
- disabled: !actionable,
5338
- onClick: props.onReject
5339
- }, "拒绝 Proposal"))));
5340
- }
5341
- function RejectConfirmationBody(props) {
5342
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h3", { id: "run2skill-reject-title" }, "确认拒绝 Proposal?"), (0, react.createElement)("p", { id: "run2skill-reject-description" }, "现有 Skill 不会改变;该 Proposal 将离开待处理队列;过滤后的 Evidence 仍按项目策略保留。"), (0, react.createElement)("button", {
5343
- type: "button",
5344
- "data-initial-focus": true,
5345
- disabled: props.disabled,
5346
- onClick: props.onCancel
5347
- }, "取消"), (0, react.createElement)("button", {
5348
- type: "button",
5349
- disabled: props.disabled,
5350
- onClick: props.onConfirm
5351
- }, "确认拒绝"));
5352
- }
5353
- function RejectConfirmation(props) {
4727
+ function PurgeConfirmationDialog(props) {
5354
4728
  const dialogRef = (0, react.useRef)(null);
4729
+ const preview = props.state.preview;
4730
+ const scope = props.state.previewScope;
5355
4731
  (0, react.useEffect)(() => {
5356
- if (typeof document === "undefined") return;
5357
- const previous = document.activeElement;
5358
- const dialog = dialogRef.current;
5359
- const initial = dialog?.querySelector("[data-initial-focus]");
4732
+ if (preview === void 0 || typeof document === "undefined") return;
4733
+ const dialog = dialogRef.current?.closest("[role=\"dialog\"]");
4734
+ const initial = dialog?.querySelector("[autofocus]");
5360
4735
  const recapture = (event) => {
5361
- if (dialog !== null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
4736
+ if (dialog != null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
4737
+ };
4738
+ const captureKeyboard = (event) => {
4739
+ if (dialog == null || event.key !== "Escape" && event.key !== "Tab") return;
4740
+ if (event.key === "Escape") {
4741
+ event.preventDefault();
4742
+ event.stopImmediatePropagation();
4743
+ props.controller.cancelPreview();
4744
+ return;
4745
+ }
4746
+ trapDialogTab(event.key, event.shiftKey, () => {
4747
+ event.preventDefault();
4748
+ }, dialog, document.activeElement);
5362
4749
  };
5363
4750
  document.addEventListener("focusin", recapture, true);
4751
+ document.addEventListener("keydown", captureKeyboard, true);
5364
4752
  initial?.focus();
5365
4753
  return () => {
5366
4754
  document.removeEventListener("focusin", recapture, true);
5367
- previous?.focus();
4755
+ document.removeEventListener("keydown", captureKeyboard, true);
4756
+ props.restoreFocusRef.current?.focus();
5368
4757
  };
5369
- }, []);
5370
- return (0, react.createElement)("div", {
4758
+ }, [preview?.previewId, props.restoreFocusRef]);
4759
+ if (preview === void 0 || scope === void 0) return null;
4760
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4761
+ open: true,
4762
+ title: scope === "ALL" ? "确认清理所有缓存?" : `确认清理 ${scope} Run2Skill 数据?`,
4763
+ description: "此操作只清理 Run2Skill 自己产生的中间缓存数据,不会卸载插件。",
4764
+ closeLabel: "关闭清理确认框",
4765
+ onClose: () => {
4766
+ props.controller.cancelPreview();
4767
+ },
4768
+ footer: (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4769
+ variant: "ghost",
4770
+ autoFocus: true,
4771
+ disabled: props.state.mutationPending,
4772
+ onClick: () => {
4773
+ props.controller.cancelPreview();
4774
+ }
4775
+ }, "取消清理"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4776
+ variant: "primary",
4777
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16),
4778
+ disabled: props.state.mutationPending,
4779
+ onClick: () => {
4780
+ props.controller.confirm();
4781
+ }
4782
+ }, props.state.mutationPending ? "正在清理…" : "确认清理"))
4783
+ }, (0, react.createElement)("div", {
5371
4784
  ref: dialogRef,
5372
- role: "alertdialog",
5373
- "aria-modal": true,
5374
- "aria-labelledby": "run2skill-reject-title",
5375
- "aria-describedby": "run2skill-reject-description",
5376
4785
  onKeyDown: (event) => {
5377
4786
  event.stopPropagation();
5378
4787
  if (event.key === "Escape") {
5379
4788
  event.preventDefault();
5380
- props.onCancel();
4789
+ props.controller.cancelPreview();
5381
4790
  } else if (dialogRef.current !== null) trapDialogTab(event.key, event.shiftKey, () => {
5382
4791
  event.preventDefault();
5383
4792
  }, dialogRef.current, typeof document === "undefined" ? void 0 : document.activeElement);
5384
- },
5385
- style: {
5386
- position: "fixed",
5387
- inset: 0,
5388
- zIndex: 1001,
5389
- display: "grid",
5390
- placeItems: "center",
5391
- padding: "1rem",
5392
- background: "rgb(0 0 0 / 35%)"
5393
4793
  }
5394
- }, (0, react.createElement)("div", { style: {
5395
- maxWidth: "32rem",
5396
- padding: "1rem",
5397
- background: "Canvas",
5398
- color: "CanvasText",
5399
- border: "2px solid currentColor",
5400
- borderRadius: "0.5rem"
5401
- } }, (0, react.createElement)(RejectConfirmationBody, props)));
4794
+ }, (0, react.createElement)("ul", null, (0, react.createElement)("li", null, "删除 Run2Skill 产生的中间缓存、待处理技能草稿、失败与非敏感诊断记录。"), (0, react.createElement)("li", null, "保留 DSH 的原始会话记录。"), (0, react.createElement)("li", null, "保留所有已发布的原生 Skill。"), (0, react.createElement)("li", null, "删除 Skill 关联记录后,保留的 Skill 仍可作为普通 Skill 使用。")), (0, react.createElement)(PreviewSummary, { preview })));
5402
4795
  }
5403
- //#endregion
5404
- //#region src/client/observe-header-action.ts
5405
- function summaryFacts(state) {
5406
- const { summary } = state;
5407
- const facts = [];
5408
- if (summary.status !== "READY") facts.push(describeRun2skillHealth(summary.status));
5409
- facts.push(`已记录 ${summary.capturedCount} 条待处理事项`);
5410
- if (summary.learning !== void 0) {
5411
- if (summary.learning.learned > 0) facts.push(`${summary.learning.learned} 条已学习草案`);
5412
- if (summary.learning.analyzing > 0) facts.push(`${summary.learning.analyzing} 条正在学习`);
5413
- if (summary.learning.needsAttention > 0) facts.push(`${summary.learning.needsAttention} 条学习需处理`);
5414
- }
5415
- if (summary.blockedCaptureCount > 0) facts.push(`另有 ${summary.blockedCaptureCount} 条观察不完整`);
5416
- if (summary.unsaved.completeness === "UNKNOWN") facts.push(summary.unsaved.knownCount > 0 ? `至少有 ${summary.unsaved.knownCount} 条尚未保存,完整数量未知` : "尚未保存数量未知");
5417
- else facts.push(summary.unsaved.knownCount > 0 ? `${summary.unsaved.knownCount} 条尚未保存` : "没有尚未保存的事项");
5418
- return facts;
5419
- }
5420
- function describeObserveState(state) {
5421
- if (state.phase === "LOADING") return "run2skill 状态加载中";
5422
- if (state.phase === "UNAVAILABLE") return "run2skill 状态暂不可用";
5423
- const description = summaryFacts(state).join(";");
5424
- return state.phase === "STALE" ? `run2skill 状态可能已过期:${description}` : description;
5425
- }
5426
- function ObserveStatusPill({ state }) {
5427
- const description = describeObserveState(state);
5428
- const semanticStatus = "summary" in state ? state.summary.status : state.phase;
5429
- return (0, react.createElement)("span", {
4796
+ function PurgeSettingsSection(props) {
4797
+ const surfaceActive = props.active ?? true;
4798
+ (0, react.useEffect)(() => {
4799
+ if (!surfaceActive) props.controller.pause();
4800
+ props.controller.start();
4801
+ }, [props.controller]);
4802
+ (0, react.useEffect)(() => {
4803
+ if (surfaceActive) props.controller.resume();
4804
+ else props.controller.pause();
4805
+ }, [surfaceActive, props.controller]);
4806
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.snapshot, props.controller.snapshot);
4807
+ const lastCompletion = (0, react.useRef)("");
4808
+ (0, react.useEffect)(() => {
4809
+ if (!state.announcement.includes("清理完成") || state.announcement === lastCompletion.current) return;
4810
+ lastCompletion.current = state.announcement;
4811
+ props.onCompleted?.();
4812
+ }, [props.onCompleted, state.announcement]);
4813
+ const restoreRef = (0, react.useMemo)(() => ({ current: null }), []);
4814
+ const active = state.status?.state === "IN_PROGRESS" ? state.status : void 0;
4815
+ const activeReceipt = state.inProgressReceipt?.state === "IN_PROGRESS" ? state.inProgressReceipt : void 0;
4816
+ const activePhase = active?.phase ?? activeReceipt?.phase ?? (activeReceipt === void 0 ? void 0 : "HIDING");
4817
+ const disabled = state.previewPending || state.mutationPending || active !== void 0 || activeReceipt !== void 0;
4818
+ return (0, react.createElement)("section", { "aria-labelledby": "run2skill-purge-heading" }, (0, react.createElement)("h3", { id: "run2skill-purge-heading" }, "缓存清理"), (0, react.createElement)("p", null, "清理 Run2Skill 自己产生的中间缓存数据"), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4819
+ variant: "outline",
4820
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16),
4821
+ disabled,
4822
+ onClick: (event) => {
4823
+ restoreRef.current = event.currentTarget;
4824
+ props.controller.preview("ALL");
4825
+ }
4826
+ }, "清理所有缓存")), state.statusPhase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在读取数据清理状态…") : null, state.statusPhase === "UNAVAILABLE" ? (0, react.createElement)("p", { role: "alert" }, "数据清理状态暂不可用") : null, state.statusPhase === "STALE" ? (0, react.createElement)("p", { role: "status" }, "数据清理状态可能已过期") : null, state.previewPending ? (0, react.createElement)("p", { role: "status" }, "正在生成清理预览…") : null, activePhase === void 0 ? null : (0, react.createElement)("div", {
5430
4827
  role: "status",
5431
- tabIndex: 0,
5432
- "aria-live": "polite",
5433
- "aria-label": description,
5434
- "data-run2skill-status": semanticStatus,
5435
- title: description,
5436
- style: {
5437
- display: "inline-flex",
5438
- alignItems: "center",
5439
- maxWidth: "28rem",
5440
- padding: "0.25rem 0.5rem",
5441
- border: "1px solid currentColor",
5442
- borderRadius: "0.375rem",
5443
- fontSize: "0.75rem",
5444
- lineHeight: 1.25
4828
+ "aria-label": "当前数据清理状态"
4829
+ }, (0, react.createElement)("p", null, phaseCopy[activePhase]), (0, react.createElement)("p", null, `已删除 ${String(active?.deletedWorkItems ?? activeReceipt?.deletedWorkItems ?? 0)} 条待处理数据和 ${String(active?.deletedLineages ?? activeReceipt?.deletedLineages ?? 0)} 条 Skill 关联记录。`), active?.lastError === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("p", { role: "alert" }, `清理失败:${active.lastError.code}`), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4830
+ variant: "outline",
4831
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
4832
+ disabled: state.mutationPending,
4833
+ onClick: () => {
4834
+ props.controller.retry();
5445
4835
  }
5446
- }, description);
4836
+ }, state.mutationPending ? "正在重试…" : "重试清理"))), state.error === void 0 ? null : (0, react.createElement)("p", { role: "alert" }, state.announcement), (0, react.createElement)("div", {
4837
+ role: "status",
4838
+ "aria-label": "数据清理状态播报",
4839
+ "aria-live": "polite",
4840
+ "aria-atomic": true
4841
+ }, state.announcement), (0, react.createElement)(PurgeConfirmationDialog, {
4842
+ controller: props.controller,
4843
+ state,
4844
+ restoreFocusRef: restoreRef
4845
+ }));
5447
4846
  }
5448
- function ObserveHeaderAction({ callSummary }) {
5449
- const poller = (0, react.useMemo)(() => new ObserveSummaryPoller(callSummary), [callSummary]);
5450
- (0, react.useEffect)(() => {
5451
- poller.start();
4847
+ //#endregion
4848
+ //#region src/client/automatic-learning-settings.ts
4849
+ var AutomaticLearningSettingsController = class {
4850
+ scope;
4851
+ #listeners = /* @__PURE__ */ new Set();
4852
+ #snapshot;
4853
+ #saving = false;
4854
+ #error;
4855
+ #unsubscribe;
4856
+ constructor(scope) {
4857
+ this.scope = scope;
4858
+ this.#snapshot = this.#project();
4859
+ this.#unsubscribe = scope.subscribe(() => {
4860
+ this.#publish();
4861
+ });
4862
+ }
4863
+ getSnapshot = () => this.#snapshot;
4864
+ subscribe = (listener) => {
4865
+ this.#listeners.add(listener);
5452
4866
  return () => {
5453
- poller.dispose();
4867
+ this.#listeners.delete(listener);
5454
4868
  };
5455
- }, [poller]);
5456
- const state = (0, react.useSyncExternalStore)(poller.subscribe, poller.snapshot, poller.snapshot);
5457
- return (0, react.createElement)(ObserveStatusPill, { state });
5458
- }
5459
- function Run2skillHeaderAction(props) {
5460
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)(ObserveHeaderAction, { callSummary: props.callSummary }), (0, react.createElement)(ProposalInboxHeaderAction, {
5461
- workspaceId: props.getWorkspaceId(props.sessionId),
5462
- callReview: props.callReview
5463
- }));
4869
+ };
4870
+ async setAutomaticLearning(value) {
4871
+ const before = this.scope.getSnapshot();
4872
+ if (before.status !== "ready" || !before.writable || this.#saving) return;
4873
+ this.#saving = true;
4874
+ this.#error = void 0;
4875
+ this.#publish();
4876
+ try {
4877
+ await this.scope.set("automaticLearning", value);
4878
+ const after = this.scope.getSnapshot();
4879
+ this.#error = after.status === "ready" && after.value?.automaticLearning === value ? void 0 : "SETTINGS_CHANGED";
4880
+ } catch {
4881
+ this.#error = "SETTINGS_CHANGED";
4882
+ } finally {
4883
+ this.#saving = false;
4884
+ this.#publish();
4885
+ }
4886
+ }
4887
+ dispose() {
4888
+ this.#unsubscribe();
4889
+ this.#listeners.clear();
4890
+ }
4891
+ #project() {
4892
+ const snapshot = this.scope.getSnapshot();
4893
+ return Object.freeze({
4894
+ status: snapshot.status,
4895
+ automaticLearning: snapshot.value?.automaticLearning,
4896
+ writable: snapshot.writable,
4897
+ saving: this.#saving,
4898
+ error: this.#error
4899
+ });
4900
+ }
4901
+ #publish() {
4902
+ this.#snapshot = this.#project();
4903
+ for (const listener of this.#listeners) listener();
4904
+ }
4905
+ };
4906
+ function AutomaticLearningSettingsCard(props) {
4907
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot, props.controller.getSnapshot);
4908
+ const [open, setOpen] = (0, react.useState)(false);
4909
+ if (state.status === "unavailable") return null;
4910
+ const disabled = state.status !== "ready" || !state.writable || state.saving;
4911
+ return (0, react.createElement)("li", { "data-run2skill-settings-card": true }, (0, react.createElement)("button", {
4912
+ type: "button",
4913
+ "aria-expanded": open,
4914
+ onClick: () => {
4915
+ setOpen((value) => !value);
4916
+ }
4917
+ }, "Run2Skill", (0, react.createElement)("span", null, "将真实执行中的明确经验整理为待确认的技能草稿")), open ? (0, react.createElement)("div", null, (0, react.createElement)("label", null, (0, react.createElement)("input", {
4918
+ type: "checkbox",
4919
+ checked: state.automaticLearning === true,
4920
+ disabled,
4921
+ onChange: (event) => {
4922
+ props.controller.setAutomaticLearning(event.currentTarget.checked);
4923
+ }
4924
+ }), "自动学习"), (0, react.createElement)("p", null, "关闭后暂停普通自动学习;显式“保存为 Skill”仍会持久保存。已开始的分析继续使用启动时快照。"), (0, react.createElement)("p", null, "分析时沿用发起会话已经选择的模型。"), state.error === void 0 ? null : (0, react.createElement)("p", { role: "status" }, "设置已变化,请刷新后重试。"), (0, react.createElement)(PurgeSettingsSection, { controller: props.purgeController })) : null);
5464
4925
  }
5465
- function applyObserveSummaryClient(ctx) {
5466
- const callSummary = async (payload, signal) => await ctx.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, OBSERVE_SUMMARY_ENDPOINT, payload, signal);
5467
- const callReview = async (endpoint, payload, signal) => await ctx.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
5468
- const getWorkspaceId = (sessionId) => ctx.workspaces.list.getSnapshot().items.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? sessionId;
5469
- ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
5470
- name: "conversation.session.header.actions",
5471
- id: "run2skill-observe-summary",
5472
- order: 30,
4926
+ function applyAutomaticLearningSettingsClient(context) {
4927
+ const controller = new AutomaticLearningSettingsController(context.settingsScope.bind({ namespace: "run2skill" }));
4928
+ const callPurge = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
4929
+ const purgeController = new PurgeSettingsController(callPurge, () => {
4930
+ return context.workspaces.list.getSnapshot().recentWorkspaceId;
4931
+ });
4932
+ purgeController.start();
4933
+ context.effect?.(() => () => {
4934
+ controller.dispose();
4935
+ purgeController.dispose();
4936
+ }, "run2skill: settings and purge");
4937
+ context.slots.inject("settings.plugin.item", () => context.slots.register({
4938
+ name: "settings.plugin.item",
4939
+ key: "run2skill",
5473
4940
  inject: () => ({
5474
- callSummary,
5475
- callReview,
5476
- getWorkspaceId
4941
+ controller,
4942
+ purgeController
5477
4943
  })
5478
- }, Run2skillHeaderAction));
4944
+ }, AutomaticLearningSettingsCard));
5479
4945
  }
5480
4946
  //#endregion
5481
- //#region src/client/purge-settings.ts
5482
- const PURGE_IDLE_POLL_INTERVAL_MS = 1e4;
5483
- const PURGE_ACTIVE_POLL_INTERVAL_MS = 2e3;
5484
- const previewId = string().regex(/^purv_[a-f0-9]{64}$/);
5485
- const purgeId = string().regex(/^purge_[a-f0-9]{64}$/);
5486
- const digest = string().regex(/^[a-f0-9]{64}$/);
5487
- const isoDateTime = string().datetime({ offset: true });
5488
- const count = number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
5489
- const scopeBinding = discriminatedUnion("scope", [object({
5490
- scope: literal("PROJECT"),
5491
- workspaceId: string().min(1).max(256),
5492
- canonicalWorkspacePath: string().min(1).max(32768),
5493
- workspaceObservedAt: isoDateTime,
5494
- canonicalRootPath: string().min(1).max(32768),
5495
- rootContractVersion: literal("stock-dsh-web-default-roots-v1"),
5496
- resolverVersion: literal("stock-root-resolver-v2"),
5497
- resolutionContractDigest: digest
5498
- }).strict(), object({ scope: literal("USER") }).strict()]);
5499
- const previewSchema = object({
5500
- apiVersion: literal(1),
5501
- previewId,
5502
- digest,
5503
- expiresAt: isoDateTime,
5504
- scopeBinding,
5505
- hideBefore: isoDateTime,
5506
- workItemCount: count,
5507
- lineageCount: count,
5508
- blockedOrUnprovenCount: count,
5509
- willDelete: array(object({
5510
- kind: _enum(["WORK_ITEMS", "LINEAGES"]),
5511
- count
5512
- }).strict()).length(2),
5513
- willKeep: array(object({
5514
- reason: _enum([
5515
- "KEEP_NEW",
5516
- "KEEP_SCOPE",
5517
- "KEEP_UNPROVEN"
4947
+ //#region src/client/proposal-inbox-wire.ts
4948
+ const identity = string().min(1).max(256);
4949
+ const sha256 = string().regex(/^[a-f0-9]{64}$/);
4950
+ const positiveInteger = number().int().safe().positive();
4951
+ const nonNegativeInteger = number().int().safe().nonnegative();
4952
+ const proposalId = string().regex(/^prop_[a-f0-9]{64}$/);
4953
+ const workItemId = string().regex(/^wi_[a-f0-9]{64}$/);
4954
+ const proposalRef = object({
4955
+ proposalId,
4956
+ revision: positiveInteger,
4957
+ digest: sha256
4958
+ }).strict();
4959
+ const processingState = _enum([
4960
+ "READY_FOR_REVIEW",
4961
+ "PUBLISHING",
4962
+ "NEEDS_ATTENTION",
4963
+ "TERMINAL"
4964
+ ]);
4965
+ const publicationOutcome = _enum([
4966
+ "PENDING_REVIEW",
4967
+ "DISCARDED",
4968
+ "NEEDS_ATTENTION",
4969
+ "NEEDS_REFRESH",
4970
+ "PUBLISHED",
4971
+ "PUBLISH_FAILED"
4972
+ ]);
4973
+ const rootDisplay = object({
4974
+ state: _enum(["EXISTING", "ABSENT"]),
4975
+ scope: _enum(["PROJECT", "USER"]),
4976
+ expectedProvider: identity,
4977
+ expectedSource: _enum(["project-dsh", "user-dsh"]),
4978
+ resolverVersion: identity,
4979
+ rootContractVersion: identity,
4980
+ resolutionContractDigest: sha256
4981
+ }).strict();
4982
+ const targetDisplay = object({ skillName: identity }).strict();
4983
+ const actionBinding = discriminatedUnion("kind", [
4984
+ object({
4985
+ kind: literal("CREATE"),
4986
+ rootBinding: rootDisplay,
4987
+ targetBinding: targetDisplay,
4988
+ expectedAbsence: object({ observedAt: string().min(1).max(64) }).strict()
4989
+ }).strict(),
4990
+ object({
4991
+ kind: literal("MERGE"),
4992
+ rootBinding: rootDisplay,
4993
+ targetBinding: targetDisplay,
4994
+ baseBinding: object({
4995
+ candidateKey: string().regex(/^cand_[a-f0-9]{64}$/),
4996
+ exactBytes: string().min(1).max(65536),
4997
+ bytesDigest: sha256,
4998
+ observedAt: string().min(1).max(64)
4999
+ }).strict()
5000
+ }).strict(),
5001
+ object({
5002
+ kind: literal("DISCARD"),
5003
+ coveringCandidateBinding: object({
5004
+ candidateKey: string().regex(/^cand_[a-f0-9]{64}$/),
5005
+ name: string().min(1).max(128),
5006
+ source: identity,
5007
+ content: string().min(1).max(65536),
5008
+ contentDigest: sha256,
5009
+ observedAt: string().min(1).max(64)
5010
+ }).strict()
5011
+ }).strict()
5012
+ ]);
5013
+ const proposal = object({
5014
+ schemaVersion: literal(1),
5015
+ revision: positiveInteger,
5016
+ createdAt: string().min(1).max(64),
5017
+ sourceLearningProposalId: string().regex(/^lp_[a-f0-9]{64}$/),
5018
+ kind: _enum([
5019
+ "CREATE",
5020
+ "MERGE",
5021
+ "DISCARD"
5022
+ ]),
5023
+ name: string().min(1).max(128),
5024
+ description: string().min(1).max(2048),
5025
+ whenToUse: string().min(1).max(4096),
5026
+ invocation: object({
5027
+ modelInvocable: literal(true),
5028
+ userInvocable: literal(false)
5029
+ }).strict(),
5030
+ exactSkillBytes: string().min(1).max(65536),
5031
+ skillBytesDigest: sha256,
5032
+ rendererVersion: identity,
5033
+ persistenceScope: _enum(["PROJECT", "USER"]),
5034
+ workspaceBinding: object({ workspaceId: identity }).strict().optional(),
5035
+ dshHomeBinding: object({
5036
+ resolutionKind: _enum([
5037
+ "CONFIGURATION",
5038
+ "ENVIRONMENT",
5039
+ "DEFAULT"
5518
5040
  ]),
5519
- count
5520
- }).strict()).length(3),
5521
- busyPublicationCount: count
5041
+ identityDigest: sha256
5042
+ }).strict().optional(),
5043
+ supportingExperienceIds: array(string().regex(/^exp_[a-f0-9]{64}$/)).min(1).max(3),
5044
+ catalogObservationDigest: sha256,
5045
+ curationRationale: string().min(1).max(4096),
5046
+ actionBinding,
5047
+ proposalId,
5048
+ digest: sha256
5049
+ }).strict().refine((value) => value.kind === value.actionBinding.kind, {
5050
+ path: ["actionBinding"],
5051
+ message: "action binding kind must match proposal kind"
5052
+ });
5053
+ const evidence = object({
5054
+ source: literal("USER_DIRECT"),
5055
+ messageSeq: nonNegativeInteger,
5056
+ excerpt: string().max(8192),
5057
+ excerptDigest: sha256,
5058
+ redactionKinds: array(string().min(1).max(64)).max(16),
5059
+ truncated: boolean()
5522
5060
  }).strict();
5523
- const receiptSchema = object({
5061
+ const experience = object({
5062
+ experienceId: string().regex(/^exp_[a-f0-9]{64}$/),
5063
+ type: _enum([
5064
+ "CORRECTION",
5065
+ "CONSTRAINT",
5066
+ "WORKFLOW"
5067
+ ]),
5068
+ lesson: string().min(1).max(4096),
5069
+ persistenceScope: _enum(["PROJECT", "USER"]),
5070
+ evidenceStrength: literal("HIGH"),
5071
+ supportingEvidence: array(object({
5072
+ messageSeq: nonNegativeInteger,
5073
+ excerptDigest: sha256
5074
+ }).strict()).min(1).max(4),
5075
+ contextSummary: string().min(1).max(4096).optional()
5076
+ }).strict();
5077
+ const summary = object({
5524
5078
  apiVersion: literal(1),
5525
- purgeId,
5526
- state: _enum(["COMPLETED", "IN_PROGRESS"]),
5527
- phase: _enum([
5528
- "HIDING",
5529
- "DELETING_LINEAGES",
5530
- "DELETING_WORK_ITEMS",
5531
- "VERIFYING"
5532
- ]).optional(),
5533
- deletedWorkItems: count,
5534
- deletedLineages: count
5079
+ status: _enum([
5080
+ "READY",
5081
+ "RECOVERING",
5082
+ "DEGRADED",
5083
+ "INCOMPATIBLE"
5084
+ ]),
5085
+ recoveryLag: boolean(),
5086
+ lastHealthCode: string().min(1).max(128).optional(),
5087
+ queue: discriminatedUnion("completeness", [object({ completeness: literal("UNKNOWN") }).strict(), object({
5088
+ completeness: literal("KNOWN"),
5089
+ pendingReview: nonNegativeInteger,
5090
+ publishing: nonNegativeInteger,
5091
+ needsAttention: nonNegativeInteger
5092
+ }).strict()])
5535
5093
  }).strict();
5536
- const statusSchema = discriminatedUnion("state", [object({
5094
+ const listItem = object({
5095
+ workItemId,
5096
+ workItemRevision: positiveInteger,
5097
+ proposalRef,
5098
+ kind: _enum([
5099
+ "CREATE",
5100
+ "MERGE",
5101
+ "DISCARD"
5102
+ ]),
5103
+ name: string().min(1).max(128),
5104
+ description: string().min(1).max(2048),
5105
+ persistenceScope: _enum(["PROJECT", "USER"]),
5106
+ createdAt: string().min(1).max(64),
5107
+ processingState: _enum([
5108
+ "READY_FOR_REVIEW",
5109
+ "PUBLISHING",
5110
+ "NEEDS_ATTENTION"
5111
+ ]),
5112
+ publicationOutcome: _enum([
5113
+ "PENDING_REVIEW",
5114
+ "NEEDS_ATTENTION",
5115
+ "NEEDS_REFRESH",
5116
+ "PUBLISH_FAILED"
5117
+ ])
5118
+ }).strict();
5119
+ const listPage = object({
5537
5120
  apiVersion: literal(1),
5538
- state: literal("IDLE")
5539
- }).strict(), object({
5121
+ items: array(listItem).max(20),
5122
+ nextCursor: string().regex(/^c_[1-9][0-9]*_[1-9][0-9]*_[a-f0-9]{64}$/).optional()
5123
+ }).strict();
5124
+ const detail = object({
5540
5125
  apiVersion: literal(1),
5541
- state: literal("IN_PROGRESS"),
5542
- purgeId,
5543
- hideBefore: isoDateTime,
5544
- startedAt: isoDateTime,
5545
- phase: _enum([
5546
- "HIDING",
5547
- "DELETING_LINEAGES",
5548
- "DELETING_WORK_ITEMS",
5549
- "VERIFYING"
5126
+ workItemId,
5127
+ workItemRevision: positiveInteger,
5128
+ processingState,
5129
+ reviewDecision: _enum([
5130
+ "PENDING",
5131
+ "APPROVED",
5132
+ "REJECTED"
5550
5133
  ]),
5551
- deletedWorkItems: count,
5552
- deletedLineages: count,
5553
- lastError: object({
5554
- code: string().regex(/^[A-Z0-9_]+$/),
5555
- occurredAt: isoDateTime
5556
- }).strict().optional()
5557
- }).strict()]);
5558
- const errorSchema = object({
5559
- ok: literal(false),
5560
- error: object({
5561
- code: string().min(1).max(128),
5562
- message: string().max(512),
5563
- details: object({ busyPublicationCount: count.optional() }).strict().optional()
5564
- }).strict()
5134
+ publicationOutcome,
5135
+ proposal,
5136
+ sessionCoordinate: object({
5137
+ rootSessionId: identity,
5138
+ sessionCreatedAt: nonNegativeInteger,
5139
+ turn: nonNegativeInteger,
5140
+ turnEndSeq: nonNegativeInteger
5141
+ }).strict(),
5142
+ evidenceRefs: array(evidence).max(16),
5143
+ experiences: array(experience).max(3)
5565
5144
  }).strict();
5566
- var PurgeResponseError = class extends Error {
5567
- facts;
5568
- constructor(facts) {
5569
- super(facts.code);
5570
- this.facts = facts;
5571
- }
5145
+ const receipt = object({
5146
+ apiVersion: literal(1),
5147
+ workItemId,
5148
+ workItemRevision: positiveInteger,
5149
+ proposalRef,
5150
+ changed: boolean(),
5151
+ processingState,
5152
+ reviewDecision: _enum([
5153
+ "PENDING",
5154
+ "APPROVED",
5155
+ "REJECTED"
5156
+ ]),
5157
+ publicationOutcome
5158
+ }).strict();
5159
+ function parseValue(schema, value) {
5160
+ const parsed = object({
5161
+ ok: literal(true),
5162
+ value: schema
5163
+ }).strict().safeParse(value);
5164
+ return parsed.success ? parsed.data.value : void 0;
5165
+ }
5166
+ function parseProposalSummary(value) {
5167
+ return parseValue(summary, value);
5168
+ }
5169
+ function parseProposalList(value) {
5170
+ return parseValue(listPage, value);
5171
+ }
5172
+ function parseProposalDetail(value) {
5173
+ return parseValue(detail, value);
5174
+ }
5175
+ function parseMutationReceipt(value) {
5176
+ return parseValue(receipt, value);
5177
+ }
5178
+ //#endregion
5179
+ //#region src/client/proposal-inbox.ts
5180
+ const PROPOSAL_POLL_INTERVAL_MS = 1e4;
5181
+ const PROPOSAL_ACTIVE_POLL_INTERVAL_MS = 2e3;
5182
+ const endpoint = {
5183
+ summary: "summary",
5184
+ list: "proposals/list",
5185
+ get: "proposals/get",
5186
+ approve: "proposals/approve",
5187
+ reject: "proposals/reject",
5188
+ retry: "proposals/retry",
5189
+ confirmDiscard: "coverage/confirm-discard"
5572
5190
  };
5573
- function browserEnvironment() {
5191
+ function describeProposalOutcome(detail) {
5192
+ if (detail.publicationOutcome === "PUBLISHED") return "Skill 已保存,DSH 已确认可以使用";
5193
+ if (detail.publicationOutcome === "DISCARDED") return "技能草稿已放弃,Skill 未更改";
5194
+ if (detail.publicationOutcome === "NEEDS_REFRESH") return "保存目标已变化,需要生成新的技能草稿";
5195
+ if (detail.publicationOutcome === "PUBLISH_FAILED") return "保存失败,可重试";
5196
+ if (detail.processingState === "NEEDS_ATTENTION") return "需要处理后才能继续";
5197
+ if (detail.processingState === "PUBLISHING") return "已确认,正在保存";
5198
+ return "待审核";
5199
+ }
5200
+ function describeProposalListItem(item) {
5201
+ return describeProposalOutcome(item);
5202
+ }
5203
+ function describeReviewDecision(decision) {
5204
+ if (decision === "APPROVED") return "已确认";
5205
+ if (decision === "REJECTED") return "已放弃";
5206
+ return "待决定";
5207
+ }
5208
+ function describeProcessingState(state) {
5209
+ if (state === "PUBLISHING") return "正在保存";
5210
+ if (state === "NEEDS_ATTENTION") return "需要处理";
5211
+ if (state === "TERMINAL") return "已结束";
5212
+ return "待审核";
5213
+ }
5214
+ function describePublicationOutcome(outcome) {
5215
+ if (outcome === "PUBLISHED") return "已保存,DSH 已确认可以使用";
5216
+ if (outcome === "DISCARDED") return "未修改 Skill,技能草稿已放弃";
5217
+ if (outcome === "NEEDS_REFRESH") return "保存目标已变化,需要新的技能草稿";
5218
+ if (outcome === "PUBLISH_FAILED") return "保存失败,可重试";
5219
+ if (outcome === "NEEDS_ATTENTION") return "保存前需要先处理当前问题";
5220
+ return "尚未产生保存结果";
5221
+ }
5222
+ function browserEnvironment$1() {
5574
5223
  return {
5575
5224
  isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
5576
5225
  setInterval: (callback, delayMs) => globalThis.setInterval(callback, delayMs),
5577
5226
  clearInterval: (handle) => {
5578
5227
  globalThis.clearInterval(handle);
5579
5228
  },
5229
+ onFocus: (listener) => {
5230
+ if (typeof window === "undefined") return () => void 0;
5231
+ window.addEventListener("focus", listener);
5232
+ return () => {
5233
+ window.removeEventListener("focus", listener);
5234
+ };
5235
+ },
5580
5236
  onVisibilityChange: (listener) => {
5581
5237
  if (typeof document === "undefined") return () => void 0;
5582
5238
  document.addEventListener("visibilitychange", listener);
5583
5239
  return () => {
5584
5240
  document.removeEventListener("visibilitychange", listener);
5585
5241
  };
5242
+ },
5243
+ onOnline: (listener) => {
5244
+ if (typeof window === "undefined") return () => void 0;
5245
+ window.addEventListener("online", listener);
5246
+ return () => {
5247
+ window.removeEventListener("online", listener);
5248
+ };
5586
5249
  }
5587
- };
5588
- }
5589
- function parseValue(schema, response) {
5590
- const success = object({
5591
- ok: literal(true),
5592
- value: schema
5593
- }).strict().safeParse(response);
5594
- if (success.success) return success.data.value;
5595
- const failure = errorSchema.safeParse(response);
5596
- if (failure.success) {
5597
- const busy = failure.data.error.details?.busyPublicationCount;
5598
- throw new PurgeResponseError({
5599
- code: failure.data.error.code,
5600
- ...typeof busy === "number" && Number.isSafeInteger(busy) && busy >= 0 ? { busyPublicationCount: busy } : {}
5601
- });
5602
- }
5603
- throw new PurgeResponseError({ code: "PURGE_STORAGE_UNAVAILABLE" });
5604
- }
5605
- function completionAnnouncement(scope, receipt) {
5606
- return `${scope === void 0 ? "run2skill 数据" : `${scope} run2skill 数据`}清理完成:${String(receipt.deletedWorkItems)} 条待处理数据,${String(receipt.deletedLineages)} 条发布沿袭记录。`;
5607
- }
5608
- function polledCompletionAnnouncement(scope) {
5609
- return `${scope === void 0 ? "" : `${scope} `}run2skill 数据清理完成。`;
5610
- }
5611
- function errorAnnouncement(error, durableBoundary = false) {
5612
- if (error.code === "PURGE_PREVIEW_STALE") return "清理预览已失效,请重新预览后确认。";
5613
- if (error.code === "PURGE_BUSY") return `当前有 ${String(error.busyPublicationCount ?? 0)} 条正在发布;请等待发布完成后重新预览。`;
5614
- if (error.code === "PURGE_ALREADY_RUNNING") return "已有一项数据清理正在运行,请等待完成。";
5615
- if (error.code === "PURGE_SCOPE_UNAVAILABLE") return "当前作用域无法可靠确认,未执行清理。";
5616
- if (error.code === "PURGE_INCOMPATIBLE") return "当前数据版本不兼容,未执行清理。";
5617
- if (error.code === "PURGE_FENCE_LIMIT") return "PROJECT 清理保护记录已达上限,未执行清理。";
5618
- return durableBoundary ? "数据清理未完成;已建立的清理边界继续隐藏,可重试。" : "数据清理暂不可用;未确认已建立清理边界,请稍后重试。";
5250
+ };
5619
5251
  }
5620
5252
  const initialState = {
5621
- statusPhase: "LOADING",
5622
- previewPending: false,
5253
+ open: false,
5254
+ summaryPhase: "LOADING",
5255
+ listPhase: "IDLE",
5256
+ items: [],
5257
+ detailPhase: "IDLE",
5623
5258
  mutationPending: false,
5624
5259
  announcement: ""
5625
5260
  };
5626
- var PurgeSettingsController = class {
5261
+ var ProposalInboxController = class {
5262
+ workspaceId;
5627
5263
  call;
5628
- getWorkspaceId;
5629
5264
  environment;
5265
+ options;
5630
5266
  #listeners = /* @__PURE__ */ new Set();
5631
5267
  #state = initialState;
5632
5268
  #started = false;
5269
+ #active = true;
5633
5270
  #disposed = false;
5634
5271
  #pending;
5635
5272
  #abort;
5636
- #statusPending;
5637
- #statusAbort;
5638
5273
  #timer;
5639
5274
  #timerDelay;
5275
+ #removeFocus;
5640
5276
  #removeVisibility;
5641
- constructor(call, getWorkspaceId, environment = browserEnvironment()) {
5277
+ #removeOnline;
5278
+ constructor(workspaceId, call, environment = browserEnvironment$1(), options = {}) {
5279
+ this.workspaceId = workspaceId;
5642
5280
  this.call = call;
5643
- this.getWorkspaceId = getWorkspaceId;
5644
5281
  this.environment = environment;
5282
+ this.options = options;
5645
5283
  }
5646
5284
  snapshot = () => this.#state;
5647
5285
  subscribe = (listener) => {
@@ -5653,208 +5291,275 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5653
5291
  start() {
5654
5292
  if (this.#started || this.#disposed) return;
5655
5293
  this.#started = true;
5294
+ const refresh = () => {
5295
+ if (this.#active && this.environment.isVisible()) this.#refreshVisible();
5296
+ };
5297
+ this.#removeFocus = this.environment.onFocus(refresh);
5298
+ this.#removeOnline = this.environment.onOnline(refresh);
5656
5299
  this.#removeVisibility = this.environment.onVisibilityChange(() => {
5657
- if (this.environment.isVisible()) {
5300
+ if (this.#active && this.environment.isVisible()) {
5658
5301
  this.#schedule();
5659
- this.#refreshStatus();
5302
+ this.#refreshVisible();
5660
5303
  } else this.#unschedule();
5661
5304
  });
5662
- if (this.environment.isVisible()) {
5305
+ if (this.#active && this.environment.isVisible()) {
5663
5306
  this.#schedule();
5664
- this.#refreshStatus();
5307
+ this.#refreshVisible();
5665
5308
  }
5666
5309
  }
5310
+ pause() {
5311
+ if (this.#disposed || !this.#active) return;
5312
+ this.#active = false;
5313
+ this.#unschedule();
5314
+ this.#abort?.abort();
5315
+ }
5316
+ resume() {
5317
+ if (this.#disposed || this.#active) return;
5318
+ this.#active = true;
5319
+ if (!this.#started || !this.environment.isVisible()) return;
5320
+ this.#schedule();
5321
+ this.whenIdle().then(() => {
5322
+ if (this.#active && !this.#disposed) this.#refreshVisible();
5323
+ });
5324
+ }
5667
5325
  whenIdle() {
5668
- return Promise.all([this.#pending ?? Promise.resolve(), this.#statusPending ?? Promise.resolve()]).then(() => void 0);
5326
+ return this.#pending ?? Promise.resolve();
5669
5327
  }
5670
- async preview(scope) {
5671
- if (this.#disposed || this.#state.previewPending || this.#state.mutationPending) return;
5672
- const workspaceId = this.getWorkspaceId();
5673
- if (scope === "PROJECT" && workspaceId === void 0) {
5674
- const error = { code: "PURGE_SCOPE_UNAVAILABLE" };
5328
+ async open() {
5329
+ if (this.#disposed) return;
5330
+ this.#publish({
5331
+ ...this.#state,
5332
+ open: true
5333
+ });
5334
+ await this.whenIdle();
5335
+ if (this.#disposed || !this.#state.open) return;
5336
+ if (this.options.attentionDriven === true && this.#scopeAccess().actions.length === 0) {
5675
5337
  this.#publish({
5676
5338
  ...this.#state,
5677
- error,
5678
- announcement: errorAnnouncement(error)
5339
+ listPhase: "READY",
5340
+ items: [],
5341
+ selectedProposalId: void 0,
5342
+ detailPhase: "IDLE",
5343
+ detail: void 0
5679
5344
  });
5680
5345
  return;
5681
5346
  }
5347
+ await this.#loadList();
5348
+ }
5349
+ close() {
5350
+ if (this.#disposed) return;
5351
+ this.#publish({
5352
+ ...this.#state,
5353
+ open: false
5354
+ });
5355
+ }
5356
+ async select(proposalId) {
5357
+ if (this.#disposed || !/^prop_[a-f0-9]{64}$/.test(proposalId)) return;
5358
+ await this.whenIdle();
5359
+ if (this.#disposed || !this.#state.open) return;
5682
5360
  await this.#execute(async (signal) => {
5683
5361
  this.#publish({
5684
5362
  ...this.#state,
5685
- previewPending: true,
5686
- previewScope: scope,
5687
- preview: void 0,
5688
- error: void 0,
5363
+ selectedProposalId: proposalId,
5364
+ detailPhase: "LOADING",
5365
+ detail: void 0,
5689
5366
  announcement: ""
5690
5367
  });
5691
- const preview = parseValue(previewSchema, await this.call("purge/preview", scope === "PROJECT" ? {
5692
- apiVersion: 1,
5693
- scope,
5694
- workspaceId
5695
- } : {
5368
+ const scopeAccess = this.#scopeAccess();
5369
+ const action = scopeAccess.actions.find((candidate) => candidate.proposalRef.proposalId === proposalId);
5370
+ if (action === void 0) throw new Error("proposal action is stale");
5371
+ const detail = parseProposalDetail(await this.call(endpoint.get, {
5696
5372
  apiVersion: 1,
5697
- scope
5373
+ proposalId,
5374
+ currentScope: scopeAccess.currentScope,
5375
+ action
5698
5376
  }, signal));
5377
+ if (detail === void 0) throw new Error("invalid detail response");
5699
5378
  this.#publish({
5700
5379
  ...this.#state,
5701
- previewPending: false,
5702
- preview,
5703
- error: void 0
5380
+ detailPhase: "READY",
5381
+ detail,
5382
+ announcement: describeProposalOutcome(detail)
5704
5383
  });
5705
- }, (error) => {
5384
+ }, () => {
5706
5385
  this.#publish({
5707
5386
  ...this.#state,
5708
- previewPending: false,
5709
- preview: void 0,
5710
- error,
5711
- announcement: errorAnnouncement(error)
5387
+ detailPhase: "ERROR",
5388
+ detail: void 0
5712
5389
  });
5713
5390
  });
5714
5391
  }
5715
- cancelPreview() {
5716
- if (this.#disposed || this.#state.mutationPending) return;
5717
- this.#publish({
5718
- ...this.#state,
5719
- preview: void 0,
5720
- previewScope: void 0,
5721
- error: void 0,
5722
- announcement: ""
5723
- });
5724
- }
5725
- async confirm() {
5726
- const preview = this.#state.preview;
5727
- const scope = this.#state.previewScope;
5728
- if (this.#disposed || preview === void 0 || scope === void 0 || this.#state.mutationPending) return;
5392
+ async mutate(action) {
5393
+ await this.whenIdle();
5394
+ const detail = this.#state.detail;
5395
+ if (this.#disposed || detail === void 0 || this.#state.mutationPending) return;
5396
+ const proposalRef = {
5397
+ proposalId: detail.proposal.proposalId,
5398
+ revision: detail.proposal.revision,
5399
+ digest: detail.proposal.digest
5400
+ };
5401
+ const targetEndpoint = action === "APPROVE" ? endpoint.approve : action === "REJECT" ? endpoint.reject : action === "RETRY" ? endpoint.retry : endpoint.confirmDiscard;
5402
+ const request = {
5403
+ apiVersion: 1,
5404
+ workItemId: detail.workItemId,
5405
+ workItemRevision: detail.workItemRevision,
5406
+ proposalRef,
5407
+ currentScope: this.#scopeAccess().currentScope,
5408
+ action: this.#scopeAccess().actions.find((candidate) => candidate.proposalRef.proposalId === proposalRef.proposalId),
5409
+ ...action === "REJECT" ? { confirm: true } : {}
5410
+ };
5729
5411
  await this.#execute(async (signal) => {
5412
+ if (request.action === void 0) throw new Error("proposal action is stale");
5730
5413
  this.#publish({
5731
5414
  ...this.#state,
5732
5415
  mutationPending: true,
5733
- error: void 0,
5734
5416
  announcement: ""
5735
5417
  });
5736
- const receipt = parseValue(receiptSchema, await this.call("purge/confirm", {
5737
- apiVersion: 1,
5738
- previewId: preview.previewId,
5739
- digest: preview.digest
5740
- }, signal));
5741
- this.#acceptReceipt(scope, receipt);
5742
- }, async (error) => {
5418
+ const receipt = parseMutationReceipt(await this.call(targetEndpoint, request, signal));
5419
+ if (receipt === void 0) throw new Error("invalid mutation receipt");
5420
+ const announcement = describeProposalOutcome(receipt) || "技能草稿状态已更新";
5743
5421
  this.#publish({
5744
5422
  ...this.#state,
5745
5423
  mutationPending: false,
5746
- error,
5747
- announcement: errorAnnouncement(error),
5748
- preview: void 0,
5749
- previewScope: void 0
5750
- });
5751
- await this.#readStatusAfterFailure(error);
5752
- });
5753
- }
5754
- async retry() {
5755
- const status = this.#state.status;
5756
- if (this.#disposed || status?.state !== "IN_PROGRESS" || status.lastError === void 0 || this.#state.mutationPending) return;
5757
- await this.#execute(async (signal) => {
5758
- this.#publish({
5759
- ...this.#state,
5760
- mutationPending: true,
5761
- error: void 0,
5762
- announcement: "正在重试数据清理。"
5424
+ announcement,
5425
+ detail: {
5426
+ ...detail,
5427
+ workItemRevision: receipt.workItemRevision,
5428
+ processingState: receipt.processingState,
5429
+ reviewDecision: receipt.reviewDecision,
5430
+ publicationOutcome: receipt.publicationOutcome
5431
+ }
5763
5432
  });
5764
- const receipt = parseValue(receiptSchema, await this.call("purge/retry", {
5765
- apiVersion: 1,
5766
- purgeId: status.purgeId
5767
- }, signal));
5768
- this.#acceptReceipt(this.#state.previewScope, receipt);
5769
- }, async (error) => {
5433
+ try {
5434
+ await this.#refreshWithin(signal);
5435
+ } catch {
5436
+ if (!this.#disposed && !signal.aborted) this.#publish({
5437
+ ...this.#state,
5438
+ summaryPhase: this.#state.summary === void 0 ? "UNAVAILABLE" : "STALE",
5439
+ listPhase: this.#state.listPhase === "LOADING" ? "ERROR" : this.#state.listPhase
5440
+ });
5441
+ }
5442
+ }, () => {
5770
5443
  this.#publish({
5771
5444
  ...this.#state,
5772
5445
  mutationPending: false,
5773
- error,
5774
- announcement: errorAnnouncement(error, true)
5446
+ announcement: "操作未完成,请刷新后重试"
5775
5447
  });
5776
- await this.#readStatusAfterFailure(error, true);
5777
5448
  });
5778
5449
  }
5779
5450
  dispose() {
5780
5451
  if (this.#disposed) return;
5781
5452
  this.#disposed = true;
5782
5453
  this.#unschedule();
5454
+ this.#removeFocus?.();
5783
5455
  this.#removeVisibility?.();
5456
+ this.#removeOnline?.();
5457
+ this.#removeFocus = void 0;
5784
5458
  this.#removeVisibility = void 0;
5459
+ this.#removeOnline = void 0;
5785
5460
  this.#abort?.abort();
5786
- this.#statusAbort?.abort();
5787
5461
  this.#listeners.clear();
5788
5462
  }
5789
- #acceptReceipt(scope, receipt) {
5790
- if (receipt.state === "COMPLETED") {
5463
+ async #refreshVisible() {
5464
+ if (this.#disposed || !this.#active || !this.environment.isVisible()) return;
5465
+ await this.#execute(async (signal) => {
5466
+ await this.#refreshWithin(signal);
5467
+ }, () => {
5791
5468
  this.#publish({
5792
5469
  ...this.#state,
5793
- statusPhase: "READY",
5794
- status: {
5795
- apiVersion: 1,
5796
- state: "IDLE"
5797
- },
5798
- inProgressReceipt: void 0,
5799
- preview: void 0,
5800
- previewScope: void 0,
5801
- mutationPending: false,
5802
- error: void 0,
5803
- announcement: completionAnnouncement(scope, receipt)
5470
+ summaryPhase: this.#state.summary === void 0 ? "UNAVAILABLE" : "STALE"
5804
5471
  });
5472
+ });
5473
+ }
5474
+ async #refreshWithin(signal) {
5475
+ if (this.options.attentionDriven === true) {
5476
+ if (!this.#state.open) return;
5477
+ if (this.#scopeAccess().actions.length === 0) {
5478
+ this.#publish({
5479
+ ...this.#state,
5480
+ listPhase: "READY",
5481
+ items: [],
5482
+ selectedProposalId: void 0,
5483
+ detailPhase: "IDLE",
5484
+ detail: void 0
5485
+ });
5486
+ return;
5487
+ }
5488
+ await this.#loadListWithin(signal);
5489
+ await this.#refreshPublishingDetailWithin(signal);
5805
5490
  return;
5806
5491
  }
5492
+ const summary = parseProposalSummary(await this.call(endpoint.summary, {
5493
+ apiVersion: 1,
5494
+ workspaceId: this.workspaceId
5495
+ }, signal));
5496
+ if (summary === void 0) throw new Error("invalid summary response");
5807
5497
  this.#publish({
5808
5498
  ...this.#state,
5809
- mutationPending: false,
5810
- preview: void 0,
5811
- inProgressReceipt: receipt,
5812
- announcement: `run2skill 数据清理进行中:${receipt.phase ?? "HIDING"}。`
5499
+ summaryPhase: "READY",
5500
+ summary
5813
5501
  });
5502
+ if (!this.#state.open) return;
5503
+ await this.#loadListWithin(signal);
5504
+ await this.#refreshPublishingDetailWithin(signal);
5814
5505
  }
5815
- async #readStatusAfterFailure(error, durableBoundary = false) {
5816
- try {
5817
- const status = parseValue(statusSchema, await this.call("purge/status", { apiVersion: 1 }, new AbortController().signal));
5818
- this.#publish({
5506
+ async #refreshPublishingDetailWithin(signal) {
5507
+ const selected = this.#state.selectedProposalId;
5508
+ if (selected !== void 0 && this.#state.detail?.processingState === "PUBLISHING") {
5509
+ const scopeAccess = this.#scopeAccess();
5510
+ const action = scopeAccess.actions.find((candidate) => candidate.proposalRef.proposalId === selected);
5511
+ if (action === void 0) return;
5512
+ const detail = parseProposalDetail(await this.call(endpoint.get, {
5513
+ apiVersion: 1,
5514
+ proposalId: selected,
5515
+ currentScope: scopeAccess.currentScope,
5516
+ action
5517
+ }, signal));
5518
+ if (detail !== void 0) this.#publish({
5819
5519
  ...this.#state,
5820
- statusPhase: "READY",
5821
- status,
5822
- inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
5823
- announcement: errorAnnouncement(error, durableBoundary || status.state === "IN_PROGRESS")
5520
+ detailPhase: "READY",
5521
+ detail,
5522
+ announcement: describeProposalOutcome(detail) || this.#state.announcement
5824
5523
  });
5825
- } catch {}
5826
- }
5827
- async #refreshStatus() {
5828
- if (this.#disposed || !this.environment.isVisible()) return;
5829
- if (this.#statusPending !== void 0) return this.#statusPending;
5830
- const abort = new AbortController();
5831
- this.#statusAbort = abort;
5832
- const pending = (async () => {
5833
- try {
5834
- const status = parseValue(statusSchema, await this.call("purge/status", { apiVersion: 1 }, abort.signal));
5835
- if (this.#disposed || abort.signal.aborted) return;
5836
- const completed = status.state === "IDLE" && (this.#state.status?.state === "IN_PROGRESS" || this.#state.inProgressReceipt?.state === "IN_PROGRESS");
5837
- this.#publish({
5838
- ...this.#state,
5839
- statusPhase: "READY",
5840
- status,
5841
- inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
5842
- previewScope: completed ? void 0 : this.#state.previewScope,
5843
- announcement: completed ? polledCompletionAnnouncement(this.#state.previewScope) : this.#state.announcement
5844
- });
5845
- } catch {
5846
- if (this.#disposed || abort.signal.aborted) return;
5524
+ }
5525
+ }
5526
+ async #loadList() {
5527
+ await this.#execute(async (signal) => {
5528
+ await this.#loadListWithin(signal);
5529
+ }, () => {
5530
+ this.#publish({
5531
+ ...this.#state,
5532
+ listPhase: "ERROR"
5533
+ });
5534
+ });
5535
+ }
5536
+ async #loadListWithin(signal) {
5537
+ if (this.#state.listPhase !== "READY") this.#publish({
5538
+ ...this.#state,
5539
+ listPhase: "LOADING"
5540
+ });
5541
+ const items = [];
5542
+ let cursor;
5543
+ for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
5544
+ const page = parseProposalList(await this.call(endpoint.list, {
5545
+ apiVersion: 1,
5546
+ currentScope: this.#scopeAccess().currentScope,
5547
+ limit: 20,
5548
+ ...cursor === void 0 ? {} : { cursor }
5549
+ }, signal));
5550
+ if (page === void 0) throw new Error("invalid list response");
5551
+ items.push(...page.items);
5552
+ cursor = page.nextCursor;
5553
+ if (cursor === void 0) {
5847
5554
  this.#publish({
5848
5555
  ...this.#state,
5849
- statusPhase: this.#state.status === void 0 ? "UNAVAILABLE" : "STALE"
5556
+ listPhase: "READY",
5557
+ items
5850
5558
  });
5559
+ return;
5851
5560
  }
5852
- })();
5853
- this.#statusPending = pending;
5854
- await pending.finally(() => {
5855
- if (this.#statusPending === pending) this.#statusPending = void 0;
5856
- if (this.#statusAbort === abort) this.#statusAbort = void 0;
5857
- });
5561
+ }
5562
+ throw new Error("proposal list page limit exceeded");
5858
5563
  }
5859
5564
  async #execute(task, onError) {
5860
5565
  if (this.#disposed || this.#pending !== void 0) return this.#pending ?? Promise.resolve();
@@ -5863,8 +5568,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5863
5568
  const pending = (async () => {
5864
5569
  try {
5865
5570
  await task(abort.signal);
5866
- } catch (error) {
5867
- if (!this.#disposed && !abort.signal.aborted) await onError(error instanceof PurgeResponseError ? error.facts : { code: "PURGE_STORAGE_UNAVAILABLE" });
5571
+ } catch {
5572
+ if (!this.#disposed && !abort.signal.aborted) onError();
5868
5573
  }
5869
5574
  })();
5870
5575
  this.#pending = pending;
@@ -5874,13 +5579,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5874
5579
  });
5875
5580
  }
5876
5581
  #schedule() {
5877
- if (this.#disposed) return;
5878
- const delay = this.#state.status?.state === "IN_PROGRESS" || this.#state.inProgressReceipt?.state === "IN_PROGRESS" || this.#state.mutationPending ? PURGE_ACTIVE_POLL_INTERVAL_MS : PURGE_IDLE_POLL_INTERVAL_MS;
5582
+ if (this.#disposed || !this.#active) return;
5583
+ const hasVisibleWork = this.options.attentionDriven !== true || this.#scopeAccess().actions.length > 0;
5584
+ const delay = this.#state.open && hasVisibleWork ? PROPOSAL_ACTIVE_POLL_INTERVAL_MS : PROPOSAL_POLL_INTERVAL_MS;
5879
5585
  if (this.#timer !== void 0 && this.#timerDelay === delay) return;
5880
5586
  this.#unschedule();
5881
5587
  this.#timerDelay = delay;
5882
5588
  this.#timer = this.environment.setInterval(() => {
5883
- this.#refreshStatus();
5589
+ this.#refreshVisible();
5884
5590
  }, delay);
5885
5591
  }
5886
5592
  #unschedule() {
@@ -5892,26 +5598,406 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5892
5598
  #publish(state) {
5893
5599
  if (this.#disposed) return;
5894
5600
  this.#state = state;
5895
- if (this.#started && this.environment.isVisible()) this.#schedule();
5601
+ if (this.#started && this.#active && this.environment.isVisible()) this.#schedule();
5896
5602
  for (const listener of this.#listeners) listener();
5897
5603
  }
5604
+ #scopeAccess() {
5605
+ return this.options.scopeAccess?.() ?? {
5606
+ currentScope: {
5607
+ kind: "WORKSPACE",
5608
+ generation: 1,
5609
+ workspaceId: this.workspaceId
5610
+ },
5611
+ actions: []
5612
+ };
5613
+ }
5898
5614
  };
5899
- const phaseCopy = {
5900
- HIDING: "正在隐藏命中数据",
5901
- DELETING_LINEAGES: "正在删除发布沿袭记录",
5902
- DELETING_WORK_ITEMS: "正在删除待处理数据",
5903
- VERIFYING: "正在验证清理结果"
5904
- };
5905
- function PreviewSummary({ preview }) {
5906
- const keep = Object.fromEntries(preview.willKeep.map((item) => [item.reason, item.count]));
5907
- return (0, react.createElement)("dl", null, (0, react.createElement)("dt", null, "将删除"), (0, react.createElement)("dd", null, `${String(preview.workItemCount)} 条待处理数据;${String(preview.lineageCount)} 条发布沿袭记录`), (0, react.createElement)("dt", null, "将保留"), (0, react.createElement)("dd", null, `${String(keep.KEEP_NEW ?? 0)} 条边界后新数据;${String(keep.KEEP_SCOPE ?? 0)} 条其他作用域数据`), (0, react.createElement)("dt", null, "无法证明"), (0, react.createElement)("dd", null, `${String(preview.blockedOrUnprovenCount)} 条无法证明作用域的数据将保留`), (0, react.createElement)("dt", null, "正在发布"), (0, react.createElement)("dd", null, `${String(preview.busyPublicationCount)} 条;存在时确认会安全拒绝`));
5615
+ const formatCharacters = /* @__PURE__ */ new Map([
5616
+ [0, "NULL"],
5617
+ [9, "CHARACTER TABULATION"],
5618
+ [1564, "ARABIC LETTER MARK"],
5619
+ [8203, "ZERO WIDTH SPACE"],
5620
+ [8204, "ZERO WIDTH NON-JOINER"],
5621
+ [8205, "ZERO WIDTH JOINER"],
5622
+ [8206, "LEFT-TO-RIGHT MARK"],
5623
+ [8207, "RIGHT-TO-LEFT MARK"],
5624
+ [8234, "LEFT-TO-RIGHT EMBEDDING"],
5625
+ [8235, "RIGHT-TO-LEFT EMBEDDING"],
5626
+ [8236, "POP DIRECTIONAL FORMATTING"],
5627
+ [8237, "LEFT-TO-RIGHT OVERRIDE"],
5628
+ [8238, "RIGHT-TO-LEFT OVERRIDE"],
5629
+ [8288, "WORD JOINER"],
5630
+ [8294, "LEFT-TO-RIGHT ISOLATE"],
5631
+ [8295, "RIGHT-TO-LEFT ISOLATE"],
5632
+ [8296, "FIRST STRONG ISOLATE"],
5633
+ [8297, "POP DIRECTIONAL ISOLATE"],
5634
+ [65279, "ZERO WIDTH NO-BREAK SPACE"]
5635
+ ]);
5636
+ function makeSafeText(value) {
5637
+ let result = "";
5638
+ for (const character of value) {
5639
+ const codePoint = character.codePointAt(0);
5640
+ if (character === "\n") {
5641
+ result += character;
5642
+ continue;
5643
+ }
5644
+ const name = formatCharacters.get(codePoint) ?? (/\p{Cf}/u.test(character) ? "FORMAT" : codePoint < 32 || codePoint >= 127 && codePoint <= 159 ? "CONTROL" : void 0);
5645
+ result += name === void 0 ? character : `[U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} ${name}]`;
5646
+ }
5647
+ return result;
5648
+ }
5649
+ function ProposalTextView(props) {
5650
+ return (0, react.createElement)("pre", {
5651
+ "aria-label": props.label,
5652
+ "data-run2skill-text-mode": props.mode,
5653
+ className: _run2skill_css_run2skill_settings_page_module_css_default.textBlock
5654
+ }, props.mode === "SAFE" ? makeSafeText(props.value) : props.value);
5655
+ }
5656
+ function makeExactLineDiff(before, after) {
5657
+ const left = before.split("\n");
5658
+ const right = after.split("\n");
5659
+ if (left.length * right.length > 25e4) return [...left.map((text) => ({
5660
+ kind: "REMOVE",
5661
+ text
5662
+ })), ...right.map((text) => ({
5663
+ kind: "ADD",
5664
+ text
5665
+ }))];
5666
+ const table = Array.from({ length: left.length + 1 }, () => new Uint32Array(right.length + 1));
5667
+ for (let leftIndex = left.length - 1; leftIndex >= 0; leftIndex -= 1) for (let rightIndex = right.length - 1; rightIndex >= 0; rightIndex -= 1) table[leftIndex][rightIndex] = left[leftIndex] === right[rightIndex] ? table[leftIndex + 1][rightIndex + 1] + 1 : Math.max(table[leftIndex + 1][rightIndex], table[leftIndex][rightIndex + 1]);
5668
+ const result = [];
5669
+ let leftIndex = 0;
5670
+ let rightIndex = 0;
5671
+ while (leftIndex < left.length || rightIndex < right.length) if (leftIndex < left.length && rightIndex < right.length && left[leftIndex] === right[rightIndex]) {
5672
+ result.push({
5673
+ kind: "CONTEXT",
5674
+ text: left[leftIndex]
5675
+ });
5676
+ leftIndex += 1;
5677
+ rightIndex += 1;
5678
+ } else if (leftIndex < left.length && (rightIndex >= right.length || table[leftIndex + 1][rightIndex] >= table[leftIndex][rightIndex + 1])) {
5679
+ result.push({
5680
+ kind: "REMOVE",
5681
+ text: left[leftIndex]
5682
+ });
5683
+ leftIndex += 1;
5684
+ } else {
5685
+ result.push({
5686
+ kind: "ADD",
5687
+ text: right[rightIndex]
5688
+ });
5689
+ rightIndex += 1;
5690
+ }
5691
+ return result;
5692
+ }
5693
+ //#endregion
5694
+ //#region src/client/status-copy.ts
5695
+ function describeRun2skillHealth(status) {
5696
+ if (status === "INCOMPATIBLE") return "Run2Skill 当前版本不兼容";
5697
+ if (status === "DEGRADED") return "Run2Skill 当前功能受限";
5698
+ if (status === "RECOVERING") return "Run2Skill 正在恢复历史观察";
5699
+ return "Run2Skill 已就绪";
5700
+ }
5701
+ //#endregion
5702
+ //#region src/client/proposal-inbox-view.ts
5703
+ function proposalInboxContentBlocked(mutationPending, rejectConfirm) {
5704
+ return mutationPending || rejectConfirm;
5705
+ }
5706
+ function useDialogFocus(open, triggerRef, dialogRef, focusTrapSuspended) {
5707
+ (0, react.useEffect)(() => {
5708
+ if (!open) return;
5709
+ if (typeof document === "undefined") return;
5710
+ const dialog = dialogRef.current;
5711
+ const initial = dialog?.querySelector("[data-initial-focus]") ?? dialog?.querySelector(focusableSelector);
5712
+ const recapture = (event) => {
5713
+ if (focusTrapSuspended.current) return;
5714
+ if (dialog !== null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
5715
+ };
5716
+ document.addEventListener("focusin", recapture, true);
5717
+ initial?.focus();
5718
+ return () => {
5719
+ document.removeEventListener("focusin", recapture, true);
5720
+ triggerRef.current?.focus();
5721
+ };
5722
+ }, [
5723
+ dialogRef,
5724
+ focusTrapSuspended,
5725
+ open,
5726
+ triggerRef
5727
+ ]);
5728
+ }
5729
+ function countLabel(state) {
5730
+ const queue = state.summary?.queue;
5731
+ if (queue === void 0 || queue.completeness === "UNKNOWN") return "技能草稿待处理数量未知";
5732
+ const total = queue.pendingReview + queue.publishing + queue.needsAttention;
5733
+ const facts = [
5734
+ queue.pendingReview > 0 ? `${String(queue.pendingReview)} 份待审核` : void 0,
5735
+ queue.publishing > 0 ? `${String(queue.publishing)} 份正在保存` : void 0,
5736
+ queue.needsAttention > 0 ? `${String(queue.needsAttention)} 份需要处理` : void 0
5737
+ ].filter((fact) => fact !== void 0);
5738
+ return facts.length === 0 ? "当前没有待处理的技能草稿" : `${String(total)} 份技能草稿待处理:${facts.join(",")}`;
5739
+ }
5740
+ function describeProposalSummaryState(state) {
5741
+ if (state.summaryPhase === "LOADING") return "正在加载技能草稿队列";
5742
+ if (state.summaryPhase === "UNAVAILABLE") return "技能草稿队列暂不可用";
5743
+ if (state.summary === void 0) return "技能草稿队列状态未知";
5744
+ const health = describeRun2skillHealth(state.summary.status);
5745
+ const queue = state.summary.queue.completeness === "UNKNOWN" ? "技能草稿待处理数量未知" : countLabel(state);
5746
+ return state.summaryPhase === "STALE" ? `技能草稿队列状态可能已过期:${health};${queue}` : `${health};${queue}`;
5747
+ }
5748
+ function ProposalInboxHeaderAction(props) {
5749
+ const controller = (0, react.useMemo)(() => new ProposalInboxController(props.workspaceId, props.callReview, void 0, props.scopeAccess === void 0 ? {} : { scopeAccess: props.scopeAccess }), [
5750
+ props.scopeAccess,
5751
+ props.callReview,
5752
+ props.workspaceId
5753
+ ]);
5754
+ (0, react.useEffect)(() => {
5755
+ controller.start();
5756
+ return () => {
5757
+ controller.dispose();
5758
+ };
5759
+ }, [controller]);
5760
+ const state = (0, react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
5761
+ const triggerRef = (0, react.useRef)(null);
5762
+ const dialogRef = (0, react.useRef)(null);
5763
+ const [textMode, setTextMode] = (0, react.useState)("SAFE");
5764
+ const [rejectConfirm, setRejectConfirm] = (0, react.useState)(false);
5765
+ const focusTrapSuspended = (0, react.useRef)(false);
5766
+ focusTrapSuspended.current = rejectConfirm;
5767
+ useDialogFocus(state.open, triggerRef, dialogRef, focusTrapSuspended);
5768
+ const label = countLabel(state);
5769
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
5770
+ ref: triggerRef,
5771
+ type: "button",
5772
+ "aria-label": label,
5773
+ "aria-haspopup": "dialog",
5774
+ "aria-expanded": state.open,
5775
+ "data-run2skill-proposal-trigger": true,
5776
+ onClick: () => {
5777
+ controller.open();
5778
+ }
5779
+ }, label), state.open ? (0, react.createElement)(ProposalInboxPanel, {
5780
+ controller,
5781
+ state,
5782
+ dialogRef,
5783
+ textMode,
5784
+ setTextMode,
5785
+ rejectConfirm,
5786
+ setRejectConfirm
5787
+ }) : null);
5788
+ }
5789
+ function closeOnKeyboard(event, controller, dialog) {
5790
+ if (event.key === "Escape") {
5791
+ event.preventDefault();
5792
+ controller.close();
5793
+ } else if (dialog !== null) trapDialogTab(event.key, event.shiftKey, () => {
5794
+ event.preventDefault();
5795
+ }, dialog, typeof document === "undefined" ? void 0 : document.activeElement);
5796
+ }
5797
+ function ProposalInboxPanel(props) {
5798
+ const { controller, state } = props;
5799
+ const contentBlocked = proposalInboxContentBlocked(state.mutationPending, props.rejectConfirm);
5800
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", {
5801
+ "aria-hidden": true,
5802
+ "data-run2skill-proposal-backdrop": true,
5803
+ className: _run2skill_css_run2skill_settings_page_module_css_default.backdrop
5804
+ }), (0, react.createElement)("div", {
5805
+ ref: props.dialogRef,
5806
+ role: "dialog",
5807
+ "aria-modal": true,
5808
+ "aria-labelledby": "run2skill-proposal-inbox-title",
5809
+ onKeyDown: (event) => {
5810
+ closeOnKeyboard(event, controller, props.dialogRef.current);
5811
+ },
5812
+ className: _run2skill_css_run2skill_settings_page_module_css_default.legacyDialog
5813
+ }, (0, react.createElement)("div", {
5814
+ "aria-hidden": props.rejectConfirm || void 0,
5815
+ inert: props.rejectConfirm ? "" : void 0,
5816
+ className: props.rejectConfirm ? _run2skill_css_run2skill_settings_page_module_css_default.blocked : void 0
5817
+ }, (0, react.createElement)("section", {
5818
+ "aria-label": "技能草稿待处理队列",
5819
+ className: _run2skill_css_run2skill_settings_page_module_css_default.queue
5820
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.legacyHeader }, (0, react.createElement)("h2", { id: "run2skill-proposal-inbox-title" }, "技能草稿"), (0, react.createElement)("button", {
5821
+ type: "button",
5822
+ "data-initial-focus": true,
5823
+ onClick: () => {
5824
+ controller.close();
5825
+ }
5826
+ }, "关闭")), (0, react.createElement)("p", { role: "status" }, countLabel(state)), (0, react.createElement)("p", {
5827
+ role: "status",
5828
+ "aria-live": "polite"
5829
+ }, describeProposalSummaryState(state)), state.listPhase === "LOADING" ? (0, react.createElement)("p", null, "正在加载待处理队列…") : null, state.listPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "待处理队列暂不可用") : null, state.listPhase === "READY" && state.items.length === 0 ? (0, react.createElement)("p", null, "当前没有待处理的技能草稿") : null, (0, react.createElement)("ul", { className: _run2skill_css_run2skill_settings_page_module_css_default.legacyList }, ...state.items.map((item) => (0, react.createElement)("li", { key: item.proposalRef.proposalId }, (0, react.createElement)("button", {
5830
+ type: "button",
5831
+ disabled: contentBlocked || state.detailPhase === "LOADING",
5832
+ "aria-current": state.selectedProposalId === item.proposalRef.proposalId ? "true" : void 0,
5833
+ onClick: () => {
5834
+ controller.select(item.proposalRef.proposalId);
5835
+ },
5836
+ className: _run2skill_css_run2skill_settings_page_module_css_default.proposalListButton
5837
+ }, `${item.kind} · ${makeSafeText(item.name)} · ${item.persistenceScope} · ${describeProposalListItem(item)}`))))), (0, react.createElement)("section", {
5838
+ "aria-label": "技能草稿详情",
5839
+ className: _run2skill_css_run2skill_settings_page_module_css_default.legacyDetail
5840
+ }, state.detailPhase === "IDLE" ? (0, react.createElement)("p", null, "选择一份技能草稿查看完整内容") : null, state.detailPhase === "LOADING" ? (0, react.createElement)("p", null, "正在加载技能草稿详情…") : null, state.detailPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "技能草稿详情暂不可用") : null, state.detail === void 0 ? null : (0, react.createElement)(ProposalDetailView, {
5841
+ detail: state.detail,
5842
+ textMode: props.textMode,
5843
+ setTextMode: props.setTextMode,
5844
+ mutationPending: contentBlocked,
5845
+ onApprove: () => {
5846
+ controller.mutate("APPROVE");
5847
+ },
5848
+ onReject: () => {
5849
+ props.setRejectConfirm(true);
5850
+ },
5851
+ onRetry: () => {
5852
+ controller.mutate("RETRY");
5853
+ },
5854
+ onConfirmDiscard: () => {
5855
+ controller.mutate("CONFIRM_DISCARD");
5856
+ }
5857
+ }), (0, react.createElement)("div", {
5858
+ "aria-live": "polite",
5859
+ "aria-atomic": true
5860
+ }, state.announcement))), props.rejectConfirm ? (0, react.createElement)(RejectConfirmation, {
5861
+ disabled: state.mutationPending,
5862
+ onCancel: () => {
5863
+ props.setRejectConfirm(false);
5864
+ },
5865
+ onConfirm: () => {
5866
+ props.setRejectConfirm(false);
5867
+ controller.mutate("REJECT");
5868
+ }
5869
+ }) : null));
5908
5870
  }
5909
- function PurgeConfirmationDialog(props) {
5871
+ function factsFromAction(detail) {
5872
+ const action = detail.proposal.actionBinding;
5873
+ if (action.kind === "CREATE") return [
5874
+ `Skill 存储规则版本:${action.rootBinding.rootContractVersion}`,
5875
+ `Skill 存储定位器版本:${action.rootBinding.resolverVersion}`,
5876
+ `预期存储方式 / 来源:${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5877
+ `存储定位规则校验值:${action.rootBinding.resolutionContractDigest}`,
5878
+ `存储目录状态:${action.rootBinding.state}`,
5879
+ `Skill 名称:${action.targetBinding.skillName}`,
5880
+ `Skill 内容校验值:${detail.proposal.skillBytesDigest}`,
5881
+ `确认目标不存在的时间:${action.expectedAbsence.observedAt}`,
5882
+ "目标文件夹不存在:是",
5883
+ "目标 SKILL.md 不存在:是",
5884
+ "同名扁平 Skill 文件不存在:是"
5885
+ ].join("\n");
5886
+ if (action.kind === "MERGE") return [
5887
+ `Skill 存储规则版本:${action.rootBinding.rootContractVersion}`,
5888
+ `Skill 存储定位器版本:${action.rootBinding.resolverVersion}`,
5889
+ `预期存储方式 / 来源:${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5890
+ `存储定位规则校验值:${action.rootBinding.resolutionContractDigest}`,
5891
+ `存储目录状态:${action.rootBinding.state}`,
5892
+ `现有 Skill 标识:${action.baseBinding.candidateKey}`,
5893
+ `Skill 名称:${action.targetBinding.skillName}`,
5894
+ `原内容校验值:${action.baseBinding.bytesDigest}`,
5895
+ `原内容确认时间:${action.baseBinding.observedAt}`
5896
+ ].join("\n");
5897
+ return [
5898
+ `匹配到的现有 Skill 标识:${action.coveringCandidateBinding.candidateKey}`,
5899
+ `名称:${action.coveringCandidateBinding.name}`,
5900
+ `来源:${action.coveringCandidateBinding.source}`,
5901
+ `内容校验值:${action.coveringCandidateBinding.contentDigest}`,
5902
+ `确认时间:${action.coveringCandidateBinding.observedAt}`
5903
+ ].join("\n");
5904
+ }
5905
+ function proposalDetailAction(detail, mutationPending) {
5906
+ if (mutationPending) return "NONE";
5907
+ if (detail.reviewDecision === "PENDING" && detail.processingState === "READY_FOR_REVIEW" && detail.publicationOutcome === "PENDING_REVIEW") return "REVIEW";
5908
+ if (detail.reviewDecision === "APPROVED" && detail.processingState === "NEEDS_ATTENTION" && detail.publicationOutcome === "PUBLISH_FAILED") return "RETRY_PUBLICATION";
5909
+ return "NONE";
5910
+ }
5911
+ function ProposalDetailView(props) {
5912
+ const { detail, mutationPending } = props;
5913
+ const proposal = detail.proposal;
5914
+ const action = proposalDetailAction(detail, mutationPending);
5915
+ const actionable = action === "REVIEW";
5916
+ const coordinate = detail.sessionCoordinate;
5917
+ const baseBytes = proposal.actionBinding.kind === "MERGE" ? proposal.actionBinding.baseBinding.exactBytes : void 0;
5918
+ const coveringBytes = proposal.actionBinding.kind === "DISCARD" ? proposal.actionBinding.coveringCandidateBinding.content : void 0;
5919
+ const diff = baseBytes === void 0 ? [] : makeExactLineDiff(baseBytes, proposal.exactSkillBytes);
5920
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h3", null, `${proposal.kind}: ${makeSafeText(proposal.name)}`), (0, react.createElement)("dl", null, (0, react.createElement)("dt", null, "说明"), (0, react.createElement)("dd", null, makeSafeText(proposal.description)), (0, react.createElement)("dt", null, "何时使用"), (0, react.createElement)("dd", null, makeSafeText(proposal.whenToUse)), (0, react.createElement)("dt", null, "保存范围"), (0, react.createElement)("dd", null, proposal.persistenceScope === "PROJECT" ? "当前项目(PROJECT)" : "当前用户(USER)"), (0, react.createElement)("dt", null, "草稿版本"), (0, react.createElement)("dd", null, String(proposal.revision)), (0, react.createElement)("dt", null, "草稿校验值"), (0, react.createElement)("dd", null, proposal.digest), (0, react.createElement)("dt", null, "会话位置"), (0, react.createElement)("dd", null, `${coordinate.rootSessionId} / 回合 ${String(coordinate.turn)} / 消息序号 ${String(coordinate.turnEndSeq)}`), proposal.workspaceBinding === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("dt", null, "项目工作区"), (0, react.createElement)("dd", null, `当前项目(PROJECT)· ${proposal.workspaceBinding.workspaceId}`)), proposal.dshHomeBinding === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("dt", null, "DSH 用户目录"), (0, react.createElement)("dd", null, `当前用户(USER)· ${proposal.dshHomeBinding.resolutionKind}`), (0, react.createElement)("dt", null, "DSH 用户目录校验值"), (0, react.createElement)("dd", null, proposal.dshHomeBinding.identityDigest)), (0, react.createElement)("dt", null, "审核决定"), (0, react.createElement)("dd", null, describeReviewDecision(detail.reviewDecision)), (0, react.createElement)("dt", null, "处理状态"), (0, react.createElement)("dd", null, describeProcessingState(detail.processingState)), (0, react.createElement)("dt", null, "保存结果"), (0, react.createElement)("dd", null, describePublicationOutcome(detail.publicationOutcome))), (0, react.createElement)("h4", null, "为什么生成这份草稿"), (0, react.createElement)("p", null, makeSafeText(proposal.curationRationale)), ...detail.experiences.map((experience) => (0, react.createElement)("article", { key: experience.experienceId }, (0, react.createElement)("strong", null, `${experience.type} · ${experience.evidenceStrength}`), (0, react.createElement)("p", null, makeSafeText(experience.lesson)))), (0, react.createElement)("h4", null, "相关对话片段(已过滤)"), ...detail.evidenceRefs.map((evidence) => (0, react.createElement)("article", { key: `${String(evidence.messageSeq)}:${evidence.excerptDigest}` }, (0, react.createElement)("strong", null, `消息序号 ${String(evidence.messageSeq)}${evidence.truncated ? " · 已截断" : ""}`), (0, react.createElement)(ProposalTextView, {
5921
+ value: evidence.excerpt,
5922
+ mode: "SAFE",
5923
+ label: `相关对话片段 ${String(evidence.messageSeq)}`
5924
+ }))), (0, react.createElement)("h4", null, "保存前核对信息"), (0, react.createElement)(ProposalTextView, {
5925
+ value: factsFromAction(detail),
5926
+ mode: "SAFE",
5927
+ label: "保存前核对信息"
5928
+ }), (0, react.createElement)("div", {
5929
+ role: "group",
5930
+ "aria-label": "内容显示方式",
5931
+ className: _run2skill_css_run2skill_settings_page_module_css_default.modeGroup
5932
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
5933
+ "aria-pressed": props.textMode === "SAFE",
5934
+ active: props.textMode === "SAFE",
5935
+ onClick: () => {
5936
+ props.setTextMode("SAFE");
5937
+ }
5938
+ }, "安全视图"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
5939
+ "aria-pressed": props.textMode === "RAW",
5940
+ active: props.textMode === "RAW",
5941
+ onClick: () => {
5942
+ props.setTextMode("RAW");
5943
+ }
5944
+ }, "原始内容")), (0, react.createElement)("h4", null, "将要保存的完整 SKILL.md"), (0, react.createElement)(ProposalTextView, {
5945
+ value: proposal.exactSkillBytes,
5946
+ mode: props.textMode,
5947
+ label: "将要保存的完整 SKILL.md"
5948
+ }), baseBytes === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h4", null, "合并前的 Skill 内容"), (0, react.createElement)(ProposalTextView, {
5949
+ value: baseBytes,
5950
+ mode: props.textMode,
5951
+ label: "合并前的 Skill 内容"
5952
+ }), (0, react.createElement)("h4", null, "内容差异(精确对比)"), (0, react.createElement)("pre", { "aria-label": "内容差异(精确对比)" }, diff.map((line) => `${line.kind === "ADD" ? "+" : line.kind === "REMOVE" ? "-" : " "} ${makeSafeText(line.text)}`).join("\n"))), coveringBytes === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h4", null, "覆盖已有 Skill 的完整内容"), (0, react.createElement)(ProposalTextView, {
5953
+ value: coveringBytes,
5954
+ mode: props.textMode,
5955
+ label: "覆盖已有 Skill 的完整内容"
5956
+ })), (0, react.createElement)("div", {
5957
+ role: "group",
5958
+ "aria-label": "技能草稿操作",
5959
+ className: _run2skill_css_run2skill_settings_page_module_css_default.actions
5960
+ }, action === "RETRY_PUBLICATION" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5961
+ variant: "primary",
5962
+ disabled: mutationPending,
5963
+ onClick: props.onRetry
5964
+ }, "重试保存") : proposal.kind === "DISCARD" ? (0, react.createElement)(react.Fragment, null, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5965
+ variant: "primary",
5966
+ disabled: !actionable,
5967
+ onClick: props.onConfirmDiscard
5968
+ }, "确认无需新建 Skill"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5969
+ variant: "outline",
5970
+ disabled: !actionable,
5971
+ onClick: props.onRetry
5972
+ }, "不同意,重新分析一次")) : (0, react.createElement)(react.Fragment, null, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5973
+ variant: "primary",
5974
+ disabled: !actionable,
5975
+ onClick: props.onApprove
5976
+ }, detail.processingState === "PUBLISHING" ? "正在保存…" : "确认并保存"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5977
+ variant: "outline",
5978
+ disabled: !actionable,
5979
+ onClick: (event) => {
5980
+ props.onReject(event.currentTarget);
5981
+ }
5982
+ }, "放弃草稿"))));
5983
+ }
5984
+ function RejectConfirmationBody(props) {
5985
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h3", { id: "run2skill-reject-title" }, "确认放弃这份技能草稿?"), (0, react.createElement)("p", { id: "run2skill-reject-description" }, "现有 Skill 不会改变;这份技能草稿将离开待处理队列;经过筛选的学习材料仍按项目规则保留。"), (0, react.createElement)("button", {
5986
+ type: "button",
5987
+ "data-initial-focus": true,
5988
+ disabled: props.disabled,
5989
+ onClick: props.onCancel
5990
+ }, "取消"), (0, react.createElement)("button", {
5991
+ type: "button",
5992
+ disabled: props.disabled,
5993
+ onClick: props.onConfirm
5994
+ }, "确认放弃"));
5995
+ }
5996
+ function RejectConfirmation(props) {
5910
5997
  const dialogRef = (0, react.useRef)(null);
5911
- const preview = props.state.preview;
5912
- const scope = props.state.previewScope;
5913
5998
  (0, react.useEffect)(() => {
5914
- if (preview === void 0 || typeof document === "undefined") return;
5999
+ if (typeof document === "undefined") return;
6000
+ const previous = document.activeElement;
5915
6001
  const dialog = dialogRef.current;
5916
6002
  const initial = dialog?.querySelector("[data-initial-focus]");
5917
6003
  const recapture = (event) => {
@@ -5921,205 +6007,966 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5921
6007
  initial?.focus();
5922
6008
  return () => {
5923
6009
  document.removeEventListener("focusin", recapture, true);
5924
- props.restoreFocusRef.current?.focus();
6010
+ previous?.focus();
5925
6011
  };
5926
- }, [preview?.previewId, props.restoreFocusRef]);
5927
- if (preview === void 0 || scope === void 0) return null;
5928
- const titleId = "run2skill-purge-confirm-title";
5929
- const descriptionId = "run2skill-purge-confirm-description";
5930
- return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", {
5931
- "aria-hidden": true,
5932
- style: {
5933
- position: "fixed",
5934
- inset: 0,
5935
- zIndex: 1999,
5936
- background: "rgb(0 0 0 / 35%)"
5937
- }
5938
- }), (0, react.createElement)("div", {
6012
+ }, []);
6013
+ return (0, react.createElement)("div", {
5939
6014
  ref: dialogRef,
5940
6015
  role: "alertdialog",
5941
6016
  "aria-modal": true,
5942
- "aria-labelledby": titleId,
5943
- "aria-describedby": descriptionId,
6017
+ "aria-labelledby": "run2skill-reject-title",
6018
+ "aria-describedby": "run2skill-reject-description",
5944
6019
  onKeyDown: (event) => {
6020
+ event.stopPropagation();
5945
6021
  if (event.key === "Escape") {
5946
6022
  event.preventDefault();
5947
- event.stopPropagation();
5948
- props.controller.cancelPreview();
6023
+ props.onCancel();
5949
6024
  } else if (dialogRef.current !== null) trapDialogTab(event.key, event.shiftKey, () => {
5950
6025
  event.preventDefault();
5951
6026
  }, dialogRef.current, typeof document === "undefined" ? void 0 : document.activeElement);
5952
6027
  },
5953
- style: {
5954
- position: "fixed",
5955
- inset: "10vh max(1rem, 20vw)",
5956
- zIndex: 2e3,
5957
- overflow: "auto",
5958
- padding: "1rem",
5959
- background: "Canvas",
5960
- color: "CanvasText",
5961
- border: "2px solid currentColor",
5962
- borderRadius: "0.5rem"
6028
+ className: _run2skill_css_run2skill_settings_page_module_css_default.rejectOverlay
6029
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.rejectCard }, (0, react.createElement)(RejectConfirmationBody, props)));
6030
+ }
6031
+ //#endregion
6032
+ //#region src/client/run2skill-settings-page.ts
6033
+ const recentSkillActivityResultSchema = object({
6034
+ ok: literal(true),
6035
+ value: object({
6036
+ apiVersion: literal(1),
6037
+ visibilityRevision: string().regex(/^visibility_[a-f0-9]{64}$/),
6038
+ items: array(object({
6039
+ activityId: string().regex(/^activity_[a-f0-9]{64}$/),
6040
+ skillName: string().min(1).max(128),
6041
+ operation: _enum(["CREATED", "UPDATED"]),
6042
+ scope: _enum(["PROJECT", "USER"]),
6043
+ occurredAt: string().datetime({ offset: true })
6044
+ }).strict()).max(256)
6045
+ }).strict()
6046
+ }).strict();
6047
+ const recentSkillActivityVisibilityStaleSchema = object({
6048
+ ok: literal(false),
6049
+ error: object({ code: literal("visibility-stale") }).passthrough()
6050
+ }).passthrough();
6051
+ const RECENT_SKILL_ACTIVITY_VISIBILITY_POLL_MS = 2e3;
6052
+ function waitForActivityVisibilityPoll(signal, delayMs) {
6053
+ if (signal.aborted) return Promise.resolve();
6054
+ return new Promise((resolve) => {
6055
+ const finish = () => {
6056
+ globalThis.clearTimeout(timer);
6057
+ signal.removeEventListener("abort", finish);
6058
+ resolve();
6059
+ };
6060
+ const timer = globalThis.setTimeout(finish, delayMs);
6061
+ signal.addEventListener("abort", finish, { once: true });
6062
+ });
6063
+ }
6064
+ function formatActivityTime(value) {
6065
+ return new Intl.DateTimeFormat("zh-CN", {
6066
+ dateStyle: "medium",
6067
+ timeStyle: "short"
6068
+ }).format(new Date(value));
6069
+ }
6070
+ function usefulActivityScope(items, selected) {
6071
+ return items.some((item) => item.skillName === selected.skillName && item.scope !== selected.scope);
6072
+ }
6073
+ function RecentSkillActivitySection(props) {
6074
+ const scopeIdentity = JSON.stringify([
6075
+ props.workspaceId ?? null,
6076
+ props.scopeGeneration,
6077
+ props.hostDataEpoch ?? 0
6078
+ ]);
6079
+ const [state, setState] = (0, react.useState)({
6080
+ phase: "IDLE",
6081
+ items: []
6082
+ });
6083
+ const visibleState = state.scopeIdentity === scopeIdentity ? state : {
6084
+ phase: "IDLE",
6085
+ items: []
6086
+ };
6087
+ (0, react.useEffect)(() => {
6088
+ if (!props.active) return;
6089
+ const abort = new AbortController();
6090
+ setState({
6091
+ phase: "LOADING",
6092
+ items: [],
6093
+ scopeIdentity
6094
+ });
6095
+ (async () => {
6096
+ const readStable = async () => {
6097
+ let expectedVisibilityRevision;
6098
+ let confirmedReads = 0;
6099
+ for (let attempt = 0; attempt < 8; attempt += 1) {
6100
+ const result = await props.call({
6101
+ apiVersion: 1,
6102
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6103
+ ...expectedVisibilityRevision === void 0 ? {} : { expectedVisibilityRevision }
6104
+ }, abort.signal);
6105
+ if (abort.signal.aborted) return void 0;
6106
+ if (recentSkillActivityVisibilityStaleSchema.safeParse(result).success) {
6107
+ expectedVisibilityRevision = void 0;
6108
+ confirmedReads = 0;
6109
+ continue;
6110
+ }
6111
+ const parsed = recentSkillActivityResultSchema.safeParse(result);
6112
+ if (!parsed.success) throw new Error("RECENT_ACTIVITY_UNAVAILABLE");
6113
+ if (parsed.data.value.visibilityRevision === expectedVisibilityRevision) {
6114
+ confirmedReads += 1;
6115
+ if (confirmedReads >= 2) return parsed.data.value;
6116
+ } else {
6117
+ expectedVisibilityRevision = parsed.data.value.visibilityRevision;
6118
+ confirmedReads = 0;
6119
+ }
6120
+ }
6121
+ throw new Error("RECENT_ACTIVITY_VISIBILITY_UNSTABLE");
6122
+ };
6123
+ let current = await readStable();
6124
+ if (current === void 0 || abort.signal.aborted) return;
6125
+ setState({
6126
+ phase: "READY",
6127
+ items: current.items,
6128
+ scopeIdentity
6129
+ });
6130
+ while (!abort.signal.aborted) {
6131
+ await waitForActivityVisibilityPoll(abort.signal, props.visibilityPollMs ?? RECENT_SKILL_ACTIVITY_VISIBILITY_POLL_MS);
6132
+ if (abort.signal.aborted) return;
6133
+ const result = await props.call({
6134
+ apiVersion: 1,
6135
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6136
+ expectedVisibilityRevision: current.visibilityRevision
6137
+ }, abort.signal);
6138
+ if (abort.signal.aborted) return;
6139
+ const parsed = recentSkillActivityResultSchema.safeParse(result);
6140
+ if (recentSkillActivityVisibilityStaleSchema.safeParse(result).success || !parsed.success || parsed.data.value.visibilityRevision !== current.visibilityRevision) {
6141
+ setState({
6142
+ phase: "LOADING",
6143
+ items: [],
6144
+ scopeIdentity
6145
+ });
6146
+ current = await readStable();
6147
+ if (current === void 0 || abort.signal.aborted) return;
6148
+ } else current = parsed.data.value;
6149
+ setState({
6150
+ phase: "READY",
6151
+ items: current.items,
6152
+ scopeIdentity
6153
+ });
6154
+ }
6155
+ })().catch(() => {
6156
+ if (!abort.signal.aborted) setState({
6157
+ phase: "ERROR",
6158
+ items: [],
6159
+ scopeIdentity
6160
+ });
6161
+ });
6162
+ return () => {
6163
+ abort.abort();
6164
+ };
6165
+ }, [
6166
+ props.active,
6167
+ props.call,
6168
+ props.hostDataEpoch,
6169
+ props.scopeGeneration,
6170
+ props.visibilityPollMs,
6171
+ props.workspaceId,
6172
+ scopeIdentity
6173
+ ]);
6174
+ return (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody }, (0, react.createElement)("p", { className: _run2skill_css_run2skill_settings_page_module_css_default.muted }, "展示最近沉淀的 Skill"), visibleState.phase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在读取最近活动…") : null, visibleState.phase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "最近活动暂不可用。") : null, visibleState.phase === "READY" && visibleState.items.length === 0 ? (0, react.createElement)("p", null, "最近 7 天没有成功沉淀的 Skill。") : null, visibleState.items.length === 0 ? null : (0, react.createElement)("ul", { className: _run2skill_css_run2skill_settings_page_module_css_default.activityList }, ...visibleState.items.map((item) => (0, react.createElement)("li", {
6175
+ className: _run2skill_css_run2skill_settings_page_module_css_default.activityItem,
6176
+ key: item.activityId
6177
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.activitySummary }, (0, react.createElement)("strong", null, item.skillName), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, item.operation === "CREATED" ? "创建" : "更新"), usefulActivityScope(visibleState.items, item) ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, item.scope === "PROJECT" ? "项目" : "用户") : null), (0, react.createElement)("time", {
6178
+ dateTime: item.occurredAt,
6179
+ className: _run2skill_css_run2skill_settings_page_module_css_default.muted
6180
+ }, formatActivityTime(item.occurredAt))))));
6181
+ }
6182
+ const learningIssueSchema = object({
6183
+ workItemId: string().regex(/^wi_[a-f0-9]{64}$/),
6184
+ workItemRevision: number().int().safe().positive(),
6185
+ createdAt: string().datetime({ offset: true }),
6186
+ updatedAt: string().datetime({ offset: true }),
6187
+ failureCode: string().min(1).max(256),
6188
+ failureDetail: string().min(1).max(256).optional(),
6189
+ retryable: boolean(),
6190
+ attempt: number().int().nonnegative().max(3),
6191
+ requestBudgetUsed: number().int().nonnegative().max(2),
6192
+ modelRoute: object({
6193
+ provider: string().min(1).max(256),
6194
+ model: string().min(1).max(256)
6195
+ }).strict().optional(),
6196
+ calls: array(object({
6197
+ requestOrdinal: number().int().positive(),
6198
+ kind: string().min(1).max(256),
6199
+ inputTokens: number().int().nonnegative().optional(),
6200
+ outputTokens: number().int().nonnegative().optional(),
6201
+ outcome: string().min(1).max(256)
6202
+ }).strict()).max(2)
6203
+ }).strict();
6204
+ const learningIssuePageSchema = object({
6205
+ ok: literal(true),
6206
+ value: object({
6207
+ apiVersion: literal(1),
6208
+ items: array(learningIssueSchema).max(20),
6209
+ nextCursor: string().regex(/^c_[1-9][0-9]*_[1-9][0-9]*_[a-f0-9]{64}$/).optional()
6210
+ }).strict()
6211
+ }).strict();
6212
+ function attentionValue(value) {
6213
+ if (value === null || typeof value !== "object" || !("ok" in value) || value.ok !== true || !("value" in value)) return;
6214
+ const projection = value.value;
6215
+ if (projection.apiVersion !== 1 || !Array.isArray(projection.actions) || !Array.isArray(projection.runtimeWarnings)) return void 0;
6216
+ return projection;
6217
+ }
6218
+ const seenAttentionKeys = /* @__PURE__ */ new Set();
6219
+ const ATTENTION_SETTINGS_POLL_INTERVAL_MS = 1e4;
6220
+ function currentScope(workspaceId, generation) {
6221
+ return workspaceId === void 0 ? {
6222
+ kind: "USER_ONLY",
6223
+ generation
6224
+ } : {
6225
+ kind: "WORKSPACE",
6226
+ generation,
6227
+ workspaceId
6228
+ };
6229
+ }
6230
+ function Run2skillAttentionToast(props) {
6231
+ const [toast, setToast] = (0, react.useState)();
6232
+ const scopeGeneration = (0, react.useRef)(0);
6233
+ (0, react.useEffect)(() => {
6234
+ const abort = new AbortController();
6235
+ let timer;
6236
+ let sequence = 0;
6237
+ const generation = ++scopeGeneration.current;
6238
+ const refresh = async () => {
6239
+ try {
6240
+ const projection = attentionValue(await props.callAttention({
6241
+ apiVersion: 1,
6242
+ currentScope: currentScope(props.workspaceId, generation),
6243
+ sessionId: props.sessionId
6244
+ }, abort.signal));
6245
+ if (projection === void 0 || abort.signal.aborted) return;
6246
+ const fresh = [...projection.actions.map((action) => action.actionKey), ...projection.runtimeWarnings.map((warning) => warning.noticeKey)].filter((key) => /^act_[a-f0-9]{64}$|^notice_[a-f0-9]{64}$/.test(key)).filter((key) => !seenAttentionKeys.has(key));
6247
+ if (fresh.length > 0) {
6248
+ for (const key of fresh) seenAttentionKeys.add(key);
6249
+ sequence += 1;
6250
+ setToast({
6251
+ sequence,
6252
+ count: fresh.length
6253
+ });
6254
+ }
6255
+ } catch {} finally {
6256
+ if (!abort.signal.aborted) timer = setTimeout(() => {
6257
+ refresh();
6258
+ }, 1e4);
6259
+ }
6260
+ };
6261
+ refresh();
6262
+ return () => {
6263
+ abort.abort();
6264
+ if (timer !== void 0) clearTimeout(timer);
6265
+ };
6266
+ }, [
6267
+ props.callAttention,
6268
+ props.sessionId,
6269
+ props.workspaceId
6270
+ ]);
6271
+ if (toast === void 0) return null;
6272
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
6273
+ key: toast.sequence,
6274
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16),
6275
+ text: `Run2Skill 有 ${String(toast.count)} 项需要处理,请前往设置 → 插件 → Run2Skill`,
6276
+ onDone: () => {
6277
+ setToast(void 0);
5963
6278
  }
5964
- }, (0, react.createElement)("h3", { id: titleId }, `确认清理 ${scope} run2skill 数据?`), (0, react.createElement)("div", { id: descriptionId }, (0, react.createElement)("p", null, "此操作只清理本次预览边界内的 run2skill 数据,不会卸载插件。"), (0, react.createElement)("ul", null, (0, react.createElement)("li", null, "删除 run2skill 的过滤 Evidence、Experience、pending、Proposal、Revision metadata、usage 和相关审计事实。"), (0, react.createElement)("li", null, "保留 DSH Session Log。"), (0, react.createElement)("li", null, "保留所有已发布的原生 Skill。"), (0, react.createElement)("li", null, "删除 Lineage 后,保留的 Skill 将作为普通现有 Skill 使用。"))), (0, react.createElement)(PreviewSummary, { preview }), (0, react.createElement)("button", {
5965
- type: "button",
5966
- "data-initial-focus": true,
5967
- disabled: props.state.mutationPending,
6279
+ });
6280
+ }
6281
+ function AutomaticLearningSection(props) {
6282
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot, props.controller.getSnapshot);
6283
+ const disabled = state.status !== "ready" || !state.writable || state.saving;
6284
+ return (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody }, (0, react.createElement)("p", { className: _run2skill_css_run2skill_settings_page_module_css_default.muted }, "在后台识别可复用经验并生成待确认的技能草稿;关闭后,明确说“保存为 Skill”仍然有效。"), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6285
+ variant: state.automaticLearning ? "primary" : "outline",
6286
+ "aria-pressed": state.automaticLearning === true,
6287
+ disabled,
5968
6288
  onClick: () => {
5969
- props.controller.cancelPreview();
6289
+ props.controller.setAutomaticLearning(state.automaticLearning !== true);
5970
6290
  }
5971
- }, "取消清理"), (0, react.createElement)("button", {
5972
- type: "button",
5973
- disabled: props.state.mutationPending,
6291
+ }, state.saving ? "正在保存…" : state.automaticLearning ? "自动学习已开启" : "自动学习已关闭"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "沿用当前会话模型")), state.error === void 0 ? null : (0, react.createElement)("p", {
6292
+ role: "status",
6293
+ className: _run2skill_css_run2skill_settings_page_module_css_default.danger
6294
+ }, "设置已变化,请刷新后重试。"));
6295
+ }
6296
+ function ProposalSettingsSection(props) {
6297
+ const scopeAccessRef = (0, react.useRef)({
6298
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6299
+ actions: []
6300
+ });
6301
+ scopeAccessRef.current = {
6302
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6303
+ actions: props.actions.flatMap((action) => action.subjectId === void 0 || action.proposalRef === void 0 || !["REVIEW_PROPOSAL", "RETRY_PUBLICATION"].includes(action.kind ?? "") ? [] : [{
6304
+ actionKey: action.actionKey,
6305
+ subjectId: action.subjectId,
6306
+ kind: action.kind,
6307
+ proposalRef: action.proposalRef
6308
+ }])
6309
+ };
6310
+ const controller = (0, react.useMemo)(() => new ProposalInboxController(props.workspaceId ?? "__run2skill_user_only__", props.callReview, void 0, {
6311
+ attentionDriven: true,
6312
+ scopeAccess: () => scopeAccessRef.current
6313
+ }), [
6314
+ props.callReview,
6315
+ props.scopeGeneration,
6316
+ props.workspaceId
6317
+ ]);
6318
+ (0, react.useEffect)(() => {
6319
+ if (!props.active) controller.pause();
6320
+ controller.start();
6321
+ return () => {
6322
+ controller.dispose();
6323
+ };
6324
+ }, [controller]);
6325
+ (0, react.useEffect)(() => {
6326
+ if (props.active) {
6327
+ controller.resume();
6328
+ controller.open();
6329
+ } else {
6330
+ controller.pause();
6331
+ controller.close();
6332
+ }
6333
+ }, [controller, props.active]);
6334
+ const state = (0, react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
6335
+ const [filter, setFilter] = (0, react.useState)("");
6336
+ const [textMode, setTextMode] = (0, react.useState)("SAFE");
6337
+ const [rejectConfirm, setRejectConfirm] = (0, react.useState)(false);
6338
+ const rejectTriggerRef = (0, react.useRef)(null);
6339
+ const items = actionableProposalItems(props.actions, state.items).filter((item) => `${item.name} ${item.description} ${item.kind}`.toLocaleLowerCase().includes(filter.trim().toLocaleLowerCase()));
6340
+ const selectedActionable = items.some((item) => item.proposalRef.proposalId === state.selectedProposalId);
6341
+ if (props.actions.length === 0) return (0, react.createElement)(react.Fragment);
6342
+ return (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.toolbar }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Input, {
6343
+ value: filter,
6344
+ placeholder: "筛选技能草稿",
6345
+ "aria-label": "筛选技能草稿",
6346
+ onChange: (event) => {
6347
+ setFilter(event.currentTarget.value);
6348
+ }
6349
+ }), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6350
+ variant: "outline",
6351
+ size: "sm",
6352
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
6353
+ onClick: () => {
6354
+ controller.open();
6355
+ }
6356
+ }, "刷新")), state.listPhase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在加载待处理事项…") : null, state.listPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "待处理事项暂不可用。") : null, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.proposalLayout }, (0, react.createElement)("ul", { className: _run2skill_css_run2skill_settings_page_module_css_default.proposalList }, ...items.map((item) => (0, react.createElement)("li", { key: item.proposalRef.proposalId }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6357
+ variant: state.selectedProposalId === item.proposalRef.proposalId ? "primary" : "outline",
6358
+ className: _run2skill_css_run2skill_settings_page_module_css_default.proposalListButton,
6359
+ disabled: state.mutationPending || state.detailPhase === "LOADING",
6360
+ onClick: () => {
6361
+ controller.select(item.proposalRef.proposalId);
6362
+ }
6363
+ }, `${item.kind} · ${item.name} · ${item.persistenceScope} · ${describeProposalListItem(item)}`)))), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.detail }, !selectedActionable || state.detailPhase === "IDLE" ? (0, react.createElement)("p", null, "选择一项查看审核事实。") : null, selectedActionable && state.detailPhase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在加载详情…") : null, selectedActionable && state.detailPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "详情暂不可用。") : null, !selectedActionable || state.detail === void 0 ? null : (0, react.createElement)(ProposalDetailView, {
6364
+ detail: state.detail,
6365
+ textMode,
6366
+ setTextMode,
6367
+ mutationPending: state.mutationPending,
6368
+ onApprove: () => {
6369
+ controller.mutate("APPROVE").finally(props.onMutationSettled);
6370
+ },
6371
+ onReject: (trigger) => {
6372
+ rejectTriggerRef.current = trigger ?? null;
6373
+ setRejectConfirm(true);
6374
+ },
6375
+ onRetry: () => {
6376
+ controller.mutate("RETRY").finally(props.onMutationSettled);
6377
+ },
6378
+ onConfirmDiscard: () => {
6379
+ controller.mutate("CONFIRM_DISCARD").finally(props.onMutationSettled);
6380
+ }
6381
+ }))), (0, react.createElement)("p", {
6382
+ role: "status",
6383
+ "aria-live": "polite",
6384
+ "aria-atomic": true,
6385
+ className: _run2skill_css_run2skill_settings_page_module_css_default.status
6386
+ }, state.announcement), (0, react.createElement)(RejectProposalModal, {
6387
+ open: rejectConfirm,
6388
+ disabled: state.mutationPending,
6389
+ triggerRef: rejectTriggerRef,
6390
+ onClose: () => {
6391
+ setRejectConfirm(false);
6392
+ },
6393
+ onConfirm: () => {
6394
+ setRejectConfirm(false);
6395
+ controller.mutate("REJECT").finally(props.onMutationSettled);
6396
+ }
6397
+ }));
6398
+ }
6399
+ function LearningFailureSection(props) {
6400
+ const [phase, setPhase] = (0, react.useState)("IDLE");
6401
+ const [items, setItems] = (0, react.useState)([]);
6402
+ const [refreshGeneration, setRefreshGeneration] = (0, react.useState)(0);
6403
+ const [pending, setPending] = (0, react.useState)();
6404
+ const [dismiss, setDismiss] = (0, react.useState)();
6405
+ const dismissTriggerRef = (0, react.useRef)(null);
6406
+ const learningActions = (0, react.useMemo)(() => new Map(props.actions.flatMap((action) => action.subjectId === void 0 || !["RETRY_LEARNING", "DISMISS_LEARNING"].includes(action.kind ?? "") ? [] : [[action.subjectId, action]])), [props.actions]);
6407
+ (0, react.useEffect)(() => {
6408
+ if (!props.active || learningActions.size === 0) {
6409
+ setPhase("IDLE");
6410
+ setItems([]);
6411
+ return;
6412
+ }
6413
+ const abort = new AbortController();
6414
+ setPhase("LOADING");
6415
+ (async () => {
6416
+ const next = [];
6417
+ let cursor;
6418
+ for (let page = 0; page < 10; page += 1) {
6419
+ const parsed = learningIssuePageSchema.safeParse(await props.call("learning/issues/list", {
6420
+ apiVersion: 1,
6421
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration ?? 1),
6422
+ limit: 20,
6423
+ ...cursor === void 0 ? {} : { cursor }
6424
+ }, abort.signal));
6425
+ if (!parsed.success) throw new Error("invalid learning issue response");
6426
+ next.push(...parsed.data.value.items.filter((item) => learningActions.has(item.workItemId)));
6427
+ cursor = parsed.data.value.nextCursor;
6428
+ if (cursor === void 0) {
6429
+ if (!abort.signal.aborted) {
6430
+ setItems(next);
6431
+ setPhase("READY");
6432
+ }
6433
+ return;
6434
+ }
6435
+ }
6436
+ throw new Error("learning issue page limit exceeded");
6437
+ })().catch(() => {
6438
+ if (!abort.signal.aborted) {
6439
+ setItems([]);
6440
+ setPhase("ERROR");
6441
+ }
6442
+ });
6443
+ return () => {
6444
+ abort.abort();
6445
+ };
6446
+ }, [
6447
+ learningActions,
6448
+ props.active,
6449
+ props.call,
6450
+ props.scopeGeneration,
6451
+ props.workspaceId,
6452
+ refreshGeneration
6453
+ ]);
6454
+ const mutate = (endpoint, item) => {
6455
+ if (pending !== void 0) return;
6456
+ const selectedAction = learningActions.get(item.workItemId);
6457
+ if (selectedAction?.subjectId === void 0 || selectedAction.kind === void 0) return;
6458
+ setPending(item.workItemId);
6459
+ props.call(endpoint, {
6460
+ apiVersion: 1,
6461
+ workItemId: item.workItemId,
6462
+ workItemRevision: item.workItemRevision,
6463
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration ?? 1),
6464
+ action: {
6465
+ actionKey: selectedAction.actionKey,
6466
+ subjectId: selectedAction.subjectId,
6467
+ kind: selectedAction.kind,
6468
+ ...selectedAction.proposalRef === void 0 ? {} : { proposalRef: selectedAction.proposalRef }
6469
+ },
6470
+ ...endpoint === "learning/issues/dismiss" ? { confirm: true } : {}
6471
+ }, new AbortController().signal).then((result) => {
6472
+ if (result === null || typeof result !== "object" || !("ok" in result) || result.ok !== true) throw new Error("learning issue mutation rejected");
6473
+ setItems((current) => current.filter((candidate) => candidate.workItemId !== item.workItemId));
6474
+ props.onMutationSettled();
6475
+ }).catch(() => {
6476
+ setPhase("ERROR");
6477
+ }).finally(() => {
6478
+ setPending(void 0);
6479
+ });
6480
+ };
6481
+ if (learningActions.size === 0) return (0, react.createElement)(react.Fragment);
6482
+ return (0, react.createElement)("section", {
6483
+ className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody,
6484
+ "aria-label": "学习失败恢复"
6485
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.toolbar }, (0, react.createElement)("strong", null, `学习失败 · ${String(learningActions.size)} 项`), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6486
+ variant: "outline",
6487
+ size: "sm",
6488
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
6489
+ disabled: !props.active || pending !== void 0,
5974
6490
  onClick: () => {
5975
- props.controller.confirm();
6491
+ setRefreshGeneration((value) => value + 1);
6492
+ }
6493
+ }, "刷新失败详情")), phase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在加载学习失败详情…") : null, phase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "学习失败详情暂不可用,请保持 DSH 运行并稍后重试。") : null, ...items.map((item) => {
6494
+ const action = learningActions.get(item.workItemId);
6495
+ const busy = pending === item.workItemId;
6496
+ return (0, react.createElement)("article", {
6497
+ className: _run2skill_css_run2skill_settings_page_module_css_default.detail,
6498
+ key: item.workItemId
6499
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.toolbar }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, item.failureCode), item.failureDetail === void 0 ? null : (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, item.failureDetail)), (0, react.createElement)("p", null, `第 ${String(item.attempt)} 轮 · 已使用 ${String(item.requestBudgetUsed)} 次模型请求`), item.modelRoute === void 0 ? null : (0, react.createElement)("p", null, `模型路由:${item.modelRoute.provider} / ${item.modelRoute.model}`), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, action?.availableActions?.includes("RETRY") === true ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6500
+ variant: "primary",
6501
+ disabled: busy || pending !== void 0,
6502
+ onClick: () => {
6503
+ mutate("learning/issues/retry", item);
6504
+ }
6505
+ }, busy ? "正在重试…" : "重试学习") : null, action?.availableActions?.includes("DISMISS") === true ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6506
+ variant: "outline",
6507
+ disabled: busy || pending !== void 0,
6508
+ onClick: (event) => {
6509
+ dismissTriggerRef.current = event.currentTarget;
6510
+ setDismiss(item);
6511
+ }
6512
+ }, "关闭此失败") : null));
6513
+ }), (0, react.createElement)(ManagedConfirmationModal, {
6514
+ open: dismiss !== void 0,
6515
+ title: "确认关闭此学习失败?",
6516
+ description: "该失败会从待处理列表隐藏;已有 Skill 和 DSH 的原始会话记录不会改变。",
6517
+ disabled: pending !== void 0,
6518
+ triggerRef: dismissTriggerRef,
6519
+ onClose: () => {
6520
+ setDismiss(void 0);
6521
+ },
6522
+ confirmLabel: "确认关闭",
6523
+ onConfirm: () => {
6524
+ const selected = dismiss;
6525
+ setDismiss(void 0);
6526
+ if (selected !== void 0) mutate("learning/issues/dismiss", selected);
6527
+ }
6528
+ }));
6529
+ }
6530
+ function RejectProposalModal(props) {
6531
+ return (0, react.createElement)(ManagedConfirmationModal, {
6532
+ ...props,
6533
+ title: "确认放弃这份技能草稿?",
6534
+ description: "现有 Skill 不会改变;这份技能草稿将离开待处理队列。",
6535
+ confirmLabel: "确认放弃"
6536
+ });
6537
+ }
6538
+ function ManagedConfirmationModal(props) {
6539
+ const contentRef = (0, react.useRef)(null);
6540
+ const onCloseRef = (0, react.useRef)(props.onClose);
6541
+ onCloseRef.current = props.onClose;
6542
+ (0, react.useEffect)(() => {
6543
+ if (!props.open || typeof document === "undefined") return;
6544
+ const dialog = contentRef.current?.closest("[role=\"dialog\"]");
6545
+ const initial = dialog?.querySelector("[autofocus]") ?? dialog?.querySelector(focusableSelector);
6546
+ const onFocus = (event) => {
6547
+ if (dialog != null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
6548
+ };
6549
+ const onKeyboardEvent = (event) => {
6550
+ if (dialog == null) return;
6551
+ if (event.key === "Escape") {
6552
+ event.preventDefault();
6553
+ event.stopImmediatePropagation();
6554
+ onCloseRef.current?.();
6555
+ } else if (event.key === "Tab") trapDialogTab(event.key, event.shiftKey, () => {
6556
+ event.preventDefault();
6557
+ }, dialog, document.activeElement);
6558
+ };
6559
+ document.addEventListener("focusin", onFocus, true);
6560
+ document.addEventListener("keydown", onKeyboardEvent, true);
6561
+ initial?.focus();
6562
+ return () => {
6563
+ document.removeEventListener("focusin", onFocus, true);
6564
+ document.removeEventListener("keydown", onKeyboardEvent, true);
6565
+ props.triggerRef.current?.focus();
6566
+ };
6567
+ }, [props.open, props.triggerRef]);
6568
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
6569
+ open: props.open,
6570
+ title: props.title,
6571
+ closeLabel: "取消",
6572
+ description: props.description,
6573
+ onClose: props.onClose,
6574
+ footer: (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6575
+ variant: "ghost",
6576
+ autoFocus: true,
6577
+ disabled: props.disabled,
6578
+ onClick: props.onClose
6579
+ }, "取消"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6580
+ variant: "primary",
6581
+ disabled: props.disabled,
6582
+ onClick: props.onConfirm
6583
+ }, props.confirmLabel))
6584
+ }, (0, react.createElement)("div", { ref: contentRef }));
6585
+ }
6586
+ function actionableProposalItems(actions, items) {
6587
+ const identities = new Set(actions.flatMap((action) => action.subjectId === void 0 || action.proposalRef === void 0 || !["REVIEW_PROPOSAL", "RETRY_PUBLICATION"].includes(action.kind ?? "") ? [] : [`${action.subjectId}\u0000${action.proposalRef.proposalId}\u0000${String(action.proposalRef.revision)}\u0000${action.proposalRef.digest}`]));
6588
+ return items.filter((item) => identities.has(`${item.workItemId}\u0000${item.proposalRef.proposalId}\u0000${String(item.proposalRef.revision)}\u0000${item.proposalRef.digest}`));
6589
+ }
6590
+ function hostTabVisible(element) {
6591
+ return element !== null && element.closest("[hidden]") === null;
6592
+ }
6593
+ function useHostTabVisibility() {
6594
+ const ref = (0, react.useRef)(null);
6595
+ const [visible, setVisible] = (0, react.useState)(true);
6596
+ (0, react.useEffect)(() => {
6597
+ const update = () => {
6598
+ setVisible(hostTabVisible(ref.current));
6599
+ };
6600
+ update();
6601
+ if (ref.current === null || typeof MutationObserver === "undefined") return;
6602
+ const observer = new MutationObserver(update);
6603
+ let current = ref.current;
6604
+ while (current !== null) {
6605
+ observer.observe(current, {
6606
+ attributes: true,
6607
+ attributeFilter: ["hidden"]
6608
+ });
6609
+ current = current.parentElement;
5976
6610
  }
5977
- }, props.state.mutationPending ? "正在清理…" : "确认清理")));
6611
+ return () => {
6612
+ observer.disconnect();
6613
+ };
6614
+ }, []);
6615
+ return {
6616
+ ref,
6617
+ visible
6618
+ };
5978
6619
  }
5979
- function PurgeSettingsSection(props) {
5980
- const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.snapshot, props.controller.snapshot);
5981
- const projectRef = (0, react.useRef)(null);
5982
- const userRef = (0, react.useRef)(null);
5983
- const [restoreRef, setRestoreRef] = (0, react.useState)(projectRef);
5984
- const active = state.status?.state === "IN_PROGRESS" ? state.status : void 0;
5985
- const activeReceipt = state.inProgressReceipt?.state === "IN_PROGRESS" ? state.inProgressReceipt : void 0;
5986
- const activePhase = active?.phase ?? activeReceipt?.phase ?? (activeReceipt === void 0 ? void 0 : "HIDING");
5987
- const disabled = state.previewPending || state.mutationPending || active !== void 0 || activeReceipt !== void 0;
5988
- return (0, react.createElement)("section", { "aria-labelledby": "run2skill-purge-heading" }, (0, react.createElement)("h3", { id: "run2skill-purge-heading" }, "清理 run2skill 数据"), (0, react.createElement)("p", null, "清理只影响 run2skill 派生数据;不会删除 DSH Session Log 或已发布 Skill。"), (0, react.createElement)("button", {
5989
- ref: projectRef,
5990
- type: "button",
5991
- disabled,
5992
- onClick: () => {
5993
- setRestoreRef(projectRef);
5994
- props.controller.preview("PROJECT");
6620
+ function AttentionSettingsSummary(props) {
6621
+ const [state, setState] = (0, react.useState)({ phase: "LOADING" });
6622
+ const requestGeneration = (0, react.useRef)(0);
6623
+ const activeAbort = (0, react.useRef)();
6624
+ const currentValue = (0, react.useRef)();
6625
+ const currentScopeIdentity = (0, react.useRef)();
6626
+ const scopeIdentity = `${props.sessionId ?? ""}:${props.workspaceId ?? ""}`;
6627
+ const refresh = (0, react.useCallback)(() => {
6628
+ activeAbort.current?.abort();
6629
+ const abort = new AbortController();
6630
+ activeAbort.current = abort;
6631
+ const generation = ++requestGeneration.current;
6632
+ const scopeChanged = currentScopeIdentity.current !== scopeIdentity;
6633
+ currentScopeIdentity.current = scopeIdentity;
6634
+ if (scopeChanged) currentValue.current = void 0;
6635
+ if (currentValue.current === void 0) {
6636
+ setState({ phase: "LOADING" });
6637
+ props.onProjection(void 0);
5995
6638
  }
5996
- }, "预览并清理当前 PROJECT 数据"), (0, react.createElement)("button", {
5997
- ref: userRef,
5998
- type: "button",
5999
- disabled,
6000
- onClick: () => {
6001
- setRestoreRef(userRef);
6002
- props.controller.preview("USER");
6639
+ props.callAttention({
6640
+ apiVersion: 1,
6641
+ currentScope: currentScope(props.workspaceId, generation),
6642
+ ...props.sessionId === void 0 ? {} : { sessionId: props.sessionId }
6643
+ }, abort.signal).then((result) => {
6644
+ if (abort.signal.aborted || requestGeneration.current !== generation) return;
6645
+ const value = attentionValue(result);
6646
+ if (value === void 0) {
6647
+ if (currentValue.current === void 0) {
6648
+ setState({ phase: "UNAVAILABLE" });
6649
+ props.onProjection(void 0);
6650
+ }
6651
+ return;
6652
+ }
6653
+ currentValue.current = value;
6654
+ setState({
6655
+ phase: "READY",
6656
+ value
6657
+ });
6658
+ props.onProjection(value);
6659
+ }, () => {
6660
+ if (abort.signal.aborted || requestGeneration.current !== generation) return;
6661
+ if (currentValue.current === void 0) {
6662
+ setState({ phase: "UNAVAILABLE" });
6663
+ props.onProjection(void 0);
6664
+ }
6665
+ });
6666
+ return () => {
6667
+ abort.abort();
6668
+ if (activeAbort.current === abort) activeAbort.current = void 0;
6669
+ };
6670
+ }, [
6671
+ props.callAttention,
6672
+ props.onProjection,
6673
+ props.refreshGeneration,
6674
+ props.sessionId,
6675
+ props.workspaceId,
6676
+ scopeIdentity
6677
+ ]);
6678
+ (0, react.useEffect)(() => {
6679
+ if (props.active === false) {
6680
+ activeAbort.current?.abort();
6681
+ activeAbort.current = void 0;
6682
+ return;
6003
6683
  }
6004
- }, "预览并清理 USER 数据"), state.statusPhase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在读取 Purge 状态…") : null, state.statusPhase === "UNAVAILABLE" ? (0, react.createElement)("p", { role: "alert" }, "Purge 状态暂不可用") : null, state.statusPhase === "STALE" ? (0, react.createElement)("p", { role: "status" }, "Purge 状态可能已过期") : null, state.previewPending ? (0, react.createElement)("p", { role: "status" }, "正在生成清理预览…") : null, activePhase === void 0 ? null : (0, react.createElement)("div", {
6005
- role: "status",
6006
- "aria-label": "当前 Purge 状态"
6007
- }, (0, react.createElement)("p", null, phaseCopy[activePhase]), (0, react.createElement)("p", null, `已删除 ${String(active?.deletedWorkItems ?? activeReceipt?.deletedWorkItems ?? 0)} 条待处理数据和 ${String(active?.deletedLineages ?? activeReceipt?.deletedLineages ?? 0)} 条发布沿袭记录。`), active?.lastError === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("p", { role: "alert" }, `清理失败:${active.lastError.code}`), (0, react.createElement)("button", {
6008
- type: "button",
6009
- disabled: state.mutationPending,
6010
- onClick: () => {
6011
- props.controller.retry();
6684
+ refresh();
6685
+ const timer = setInterval(() => {
6686
+ refresh();
6687
+ }, ATTENTION_SETTINGS_POLL_INTERVAL_MS);
6688
+ return () => {
6689
+ clearInterval(timer);
6690
+ activeAbort.current?.abort();
6691
+ activeAbort.current = void 0;
6692
+ };
6693
+ }, [props.active, refresh]);
6694
+ const value = state.value;
6695
+ if (value === void 0 || value.actions.length === 0 && value.runtimeWarnings.length === 0 && value.projectCompleteness !== "UNKNOWN" && value.runtimeCompleteness !== "UNKNOWN") return (0, react.createElement)(react.Fragment);
6696
+ return (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.toolbar }, (0, react.createElement)(react.Fragment, null, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, `${String(value.actions.length)} 项可操作事项`), value.projectCompleteness === "UNAVAILABLE" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "未选择当前项目") : value.projectCompleteness === "UNKNOWN" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "PROJECT 状态未知") : null, value.runtimeCompleteness === "UNKNOWN" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "未保存数量未知") : null, ...value.runtimeWarnings.map((warning) => (0, react.createElement)("span", {
6697
+ key: warning.noticeKey,
6698
+ role: "alert"
6699
+ }, warning.message ?? "存在尚未持久化的学习信号。"))));
6700
+ }
6701
+ function Run2skillSettingsPage(props) {
6702
+ const liveSessionId = props.useSessions?.((state) => state.current);
6703
+ const liveWorkspaceId = props.useWorkspaces?.((state) => state.items.find((workspace) => workspace.sessionIds?.includes(liveSessionId ?? ""))?.workspaceId);
6704
+ const workspaceId = props.useSessions === void 0 ? props.workspaceId : liveWorkspaceId;
6705
+ const sessionId = props.useSessions === void 0 ? props.sessionId : liveSessionId;
6706
+ const [open, setOpen] = (0, react.useState)(() => /* @__PURE__ */ new Set(["attention"]));
6707
+ const hostTab = useHostTabVisibility();
6708
+ const [attention, setAttention] = (0, react.useState)();
6709
+ const [attentionRefresh, setAttentionRefresh] = (0, react.useState)(0);
6710
+ const [scopeGeneration, setScopeGeneration] = (0, react.useState)(1);
6711
+ const purgeState = (0, react.useSyncExternalStore)(props.purgeController.subscribe, props.purgeController.snapshot, props.purgeController.snapshot);
6712
+ const purgeDataChanging = purgeState.mutationPending || purgeState.status?.state === "IN_PROGRESS" || purgeState.inProgressReceipt?.state === "IN_PROGRESS";
6713
+ (0, react.useEffect)(() => {
6714
+ setAttention(void 0);
6715
+ setScopeGeneration((value) => value + 1);
6716
+ }, [sessionId, workspaceId]);
6717
+ const attentionEmpty = attention !== void 0 && attention.actions.length === 0 && attention.runtimeWarnings.length === 0 && attention.projectCompleteness !== "UNKNOWN" && attention.runtimeCompleteness !== "UNKNOWN";
6718
+ const disclosure = (id, title, icon, content, collapsedContent) => (0, react.createElement)("section", {
6719
+ className: _run2skill_css_run2skill_settings_page_module_css_default.section,
6720
+ key: id
6721
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
6722
+ icon,
6723
+ title,
6724
+ rowClassName: _run2skill_css_run2skill_settings_page_module_css_default.sectionHeader,
6725
+ titleClassName: _run2skill_css_run2skill_settings_page_module_css_default.sectionTitle,
6726
+ open: open.has(id),
6727
+ expandable: true,
6728
+ expandOnRowClick: true,
6729
+ collapsedContent,
6730
+ onToggle: () => {
6731
+ setOpen((previous) => {
6732
+ const next = new Set(previous);
6733
+ if (next.has(id)) next.delete(id);
6734
+ else next.add(id);
6735
+ return next;
6736
+ });
6012
6737
  }
6013
- }, state.mutationPending ? "正在重试…" : "重试清理"))), state.error === void 0 ? null : (0, react.createElement)("p", { role: "alert" }, state.announcement), (0, react.createElement)("div", {
6014
- role: "status",
6015
- "aria-label": "Purge 状态播报",
6016
- "aria-live": "polite",
6017
- "aria-atomic": true
6018
- }, state.announcement), (0, react.createElement)(PurgeConfirmationDialog, {
6019
- controller: props.controller,
6020
- state,
6021
- restoreFocusRef: restoreRef
6738
+ }, content));
6739
+ return (0, react.createElement)("div", {
6740
+ ref: hostTab.ref,
6741
+ className: _run2skill_css_run2skill_settings_page_module_css_default.page,
6742
+ "data-run2skill-settings-page": true
6743
+ }, (0, react.createElement)("p", { className: _run2skill_css_run2skill_settings_page_module_css_default.intro }, "Run2Skill 在后台自动沉淀经验;这里只展示需要处理的事项和持久设置。"), disclosure("attention", "需要处理", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody }, (0, react.createElement)(AttentionSettingsSummary, {
6744
+ active: hostTab.visible && open.has("attention"),
6745
+ ...sessionId === void 0 ? {} : { sessionId },
6746
+ ...workspaceId === void 0 ? {} : { workspaceId },
6747
+ callAttention: props.callAttention,
6748
+ refreshGeneration: attentionRefresh,
6749
+ onProjection: setAttention
6750
+ }), attention?.actions.some((action) => ["REVIEW_PROPOSAL", "RETRY_PUBLICATION"].includes(action.kind ?? "")) === true ? (0, react.createElement)(ProposalSettingsSection, {
6751
+ ...workspaceId === void 0 ? {} : { workspaceId },
6752
+ callReview: props.callReview,
6753
+ active: hostTab.visible && open.has("attention"),
6754
+ actions: attention.actions,
6755
+ onMutationSettled: () => {
6756
+ setAttentionRefresh((value) => value + 1);
6757
+ },
6758
+ scopeGeneration
6759
+ }) : null, (0, react.createElement)(LearningFailureSection, {
6760
+ ...workspaceId === void 0 ? {} : { workspaceId },
6761
+ call: props.callReview,
6762
+ active: hostTab.visible && open.has("attention"),
6763
+ actions: attention?.actions ?? [],
6764
+ onMutationSettled: () => {
6765
+ setAttentionRefresh((value) => value + 1);
6766
+ },
6767
+ scopeGeneration
6768
+ }), attentionEmpty ? (0, react.createElement)("p", { className: _run2skill_css_run2skill_settings_page_module_css_default.empty }, "暂无") : null)), disclosure("activity", "最近活动", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16), (0, react.createElement)(RecentSkillActivitySection, {
6769
+ key: JSON.stringify([
6770
+ workspaceId ?? null,
6771
+ scopeGeneration,
6772
+ purgeState.hostDataEpoch,
6773
+ hostTab.visible && open.has("activity") && !purgeDataChanging
6774
+ ]),
6775
+ ...workspaceId === void 0 ? {} : { workspaceId },
6776
+ call: props.callActivity,
6777
+ active: hostTab.visible && open.has("activity") && !purgeDataChanging,
6778
+ scopeGeneration,
6779
+ hostDataEpoch: purgeState.hostDataEpoch
6780
+ }), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "最近 7 天")), disclosure("automatic", "自动学习", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16), (0, react.createElement)(AutomaticLearningSection, { controller: props.controller })), disclosure("purge", "缓存清理", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16), (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody }, (0, react.createElement)(PurgeSettingsSection, {
6781
+ controller: props.purgeController,
6782
+ active: hostTab.visible && open.has("purge"),
6783
+ onCompleted: () => {
6784
+ setAttentionRefresh((value) => value + 1);
6785
+ }
6786
+ }))));
6787
+ }
6788
+ function currentSessionId(context) {
6789
+ return context.sessions?.list.getSnapshot().current;
6790
+ }
6791
+ function workspaceFor(context, sessionId) {
6792
+ if (sessionId === void 0) return void 0;
6793
+ return context.workspaces.list.getSnapshot().items.find((workspace) => workspace.sessionIds?.includes(sessionId))?.workspaceId;
6794
+ }
6795
+ function applyRun2skillClient(context) {
6796
+ const controller = new AutomaticLearningSettingsController(context.settingsScope.bind({ namespace: "run2skill" }));
6797
+ const callReview = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
6798
+ const callAttention = async (payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, "attention", payload, signal);
6799
+ const callActivity = async (payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, "recent-activity/list", payload, signal);
6800
+ const callPurge = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
6801
+ const purgeController = new PurgeSettingsController(callPurge, () => workspaceFor(context, currentSessionId(context)));
6802
+ context.effect(() => {
6803
+ const disposeStyle = acquireRun2skillStyle();
6804
+ return () => {
6805
+ controller.dispose();
6806
+ purgeController.dispose();
6807
+ disposeStyle();
6808
+ };
6809
+ }, "run2skill: native settings surface");
6810
+ context.slots.inject("settings.plugins.tab", () => context.slots.register({
6811
+ name: "settings.plugins.tab",
6812
+ id: "run2skill",
6813
+ order: 20,
6814
+ label: "Run2Skill",
6815
+ inject: () => ({
6816
+ controller,
6817
+ purgeController,
6818
+ workspaceId: workspaceFor(context, currentSessionId(context)),
6819
+ sessionId: currentSessionId(context),
6820
+ callAttention,
6821
+ callReview,
6822
+ callActivity
6823
+ })
6824
+ }, Run2skillSettingsPage));
6825
+ context.slots.inject("conversation.session.header.actions", () => context.slots.register({
6826
+ name: "conversation.session.header.actions",
6827
+ id: "run2skill-attention",
6828
+ order: 30,
6829
+ inject: () => ({ callAttention })
6830
+ }, function HeaderAttention(props) {
6831
+ const workspaceId = props.useWorkspaces((state) => state.items.find((workspace) => workspace.sessionIds?.includes(props.sessionId))?.workspaceId);
6832
+ return (0, react.createElement)(Run2skillAttentionToast, {
6833
+ sessionId: props.sessionId,
6834
+ ...workspaceId === void 0 ? {} : { workspaceId },
6835
+ callAttention: props.callAttention
6836
+ });
6022
6837
  }));
6023
6838
  }
6024
6839
  //#endregion
6025
- //#region src/client/automatic-learning-settings.ts
6026
- var AutomaticLearningSettingsController = class {
6027
- scope;
6840
+ //#region src/client/observe-summary-poller.ts
6841
+ const OBSERVE_SUMMARY_POLL_INTERVAL_MS = 1e4;
6842
+ function browserEnvironment() {
6843
+ return {
6844
+ isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
6845
+ setInterval: (callback, delayMs) => globalThis.setInterval(callback, delayMs),
6846
+ clearInterval: (handle) => {
6847
+ globalThis.clearInterval(handle);
6848
+ },
6849
+ onFocus: (listener) => {
6850
+ if (typeof window === "undefined") return () => void 0;
6851
+ window.addEventListener("focus", listener);
6852
+ return () => {
6853
+ window.removeEventListener("focus", listener);
6854
+ };
6855
+ },
6856
+ onVisibilityChange: (listener) => {
6857
+ if (typeof document === "undefined") return () => void 0;
6858
+ document.addEventListener("visibilitychange", listener);
6859
+ return () => {
6860
+ document.removeEventListener("visibilitychange", listener);
6861
+ };
6862
+ }
6863
+ };
6864
+ }
6865
+ function parsedSummary(result) {
6866
+ if (result === null || typeof result !== "object" || !("ok" in result) || result.ok !== true) return;
6867
+ const parsed = ObserveSummaryV1Schema.safeParse("value" in result ? result.value : void 0);
6868
+ return parsed.success ? parsed.data : void 0;
6869
+ }
6870
+ var ObserveSummaryPoller = class {
6871
+ call;
6872
+ environment;
6028
6873
  #listeners = /* @__PURE__ */ new Set();
6029
- #snapshot;
6030
- #saving = false;
6031
- #error;
6032
- #unsubscribe;
6033
- constructor(scope) {
6034
- this.scope = scope;
6035
- this.#snapshot = this.#project();
6036
- this.#unsubscribe = scope.subscribe(() => {
6037
- this.#publish();
6038
- });
6874
+ #state = { phase: "LOADING" };
6875
+ #started = false;
6876
+ #disposed = false;
6877
+ #timer;
6878
+ #pending;
6879
+ #abort;
6880
+ #removeFocus;
6881
+ #removeVisibility;
6882
+ constructor(call, environment = browserEnvironment()) {
6883
+ this.call = call;
6884
+ this.environment = environment;
6039
6885
  }
6040
- getSnapshot = () => this.#snapshot;
6886
+ snapshot = () => this.#state;
6041
6887
  subscribe = (listener) => {
6042
6888
  this.#listeners.add(listener);
6043
6889
  return () => {
6044
6890
  this.#listeners.delete(listener);
6045
6891
  };
6046
6892
  };
6047
- async setAutomaticLearning(value) {
6048
- const before = this.scope.getSnapshot();
6049
- if (before.status !== "ready" || !before.writable || this.#saving) return;
6050
- this.#saving = true;
6051
- this.#error = void 0;
6052
- this.#publish();
6053
- try {
6054
- await this.scope.set("automaticLearning", value);
6055
- const after = this.scope.getSnapshot();
6056
- this.#error = after.status === "ready" && after.value?.automaticLearning === value ? void 0 : "SETTINGS_CHANGED";
6057
- } catch {
6058
- this.#error = "SETTINGS_CHANGED";
6059
- } finally {
6060
- this.#saving = false;
6061
- this.#publish();
6893
+ start() {
6894
+ if (this.#started || this.#disposed) return;
6895
+ this.#started = true;
6896
+ this.#removeFocus = this.environment.onFocus(() => {
6897
+ if (this.environment.isVisible()) this.#refresh();
6898
+ });
6899
+ this.#removeVisibility = this.environment.onVisibilityChange(() => {
6900
+ if (this.environment.isVisible()) {
6901
+ this.#schedule();
6902
+ this.#refresh();
6903
+ } else this.#unschedule();
6904
+ });
6905
+ if (this.environment.isVisible()) {
6906
+ this.#schedule();
6907
+ this.#refresh();
6062
6908
  }
6063
6909
  }
6910
+ whenIdle() {
6911
+ return this.#pending ?? Promise.resolve();
6912
+ }
6064
6913
  dispose() {
6065
- this.#unsubscribe();
6914
+ if (this.#disposed) return;
6915
+ this.#disposed = true;
6916
+ this.#unschedule();
6917
+ this.#removeFocus?.();
6918
+ this.#removeVisibility?.();
6919
+ this.#removeFocus = void 0;
6920
+ this.#removeVisibility = void 0;
6921
+ this.#abort?.abort();
6066
6922
  this.#listeners.clear();
6067
6923
  }
6068
- #project() {
6069
- const snapshot = this.scope.getSnapshot();
6070
- return Object.freeze({
6071
- status: snapshot.status,
6072
- automaticLearning: snapshot.value?.automaticLearning,
6073
- writable: snapshot.writable,
6074
- saving: this.#saving,
6075
- error: this.#error
6924
+ #schedule() {
6925
+ if (this.#timer !== void 0 || this.#disposed) return;
6926
+ this.#timer = this.environment.setInterval(() => {
6927
+ this.#refresh();
6928
+ }, OBSERVE_SUMMARY_POLL_INTERVAL_MS);
6929
+ }
6930
+ #unschedule() {
6931
+ if (this.#timer === void 0) return;
6932
+ this.environment.clearInterval(this.#timer);
6933
+ this.#timer = void 0;
6934
+ }
6935
+ #refresh() {
6936
+ if (this.#disposed || this.#pending !== void 0 || !this.environment.isVisible()) return this.#pending ?? Promise.resolve();
6937
+ const abort = new AbortController();
6938
+ this.#abort = abort;
6939
+ const pending = (async () => {
6940
+ try {
6941
+ const summary = parsedSummary(await this.call({ apiVersion: 1 }, abort.signal));
6942
+ if (this.#disposed) return;
6943
+ if (summary === void 0) this.#markUnavailable();
6944
+ else this.#publish({
6945
+ phase: "READY",
6946
+ summary
6947
+ });
6948
+ } catch {
6949
+ if (!this.#disposed && !abort.signal.aborted) this.#markUnavailable();
6950
+ }
6951
+ })();
6952
+ this.#pending = pending;
6953
+ pending.finally(() => {
6954
+ if (this.#pending === pending) this.#pending = void 0;
6955
+ if (this.#abort === abort) this.#abort = void 0;
6076
6956
  });
6957
+ return pending;
6077
6958
  }
6078
- #publish() {
6079
- this.#snapshot = this.#project();
6959
+ #markUnavailable() {
6960
+ this.#publish("summary" in this.#state ? {
6961
+ phase: "STALE",
6962
+ summary: this.#state.summary
6963
+ } : { phase: "UNAVAILABLE" });
6964
+ }
6965
+ #publish(state) {
6966
+ this.#state = state;
6080
6967
  for (const listener of this.#listeners) listener();
6081
6968
  }
6082
6969
  };
6083
- function AutomaticLearningSettingsCard(props) {
6084
- const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot, props.controller.getSnapshot);
6085
- const [open, setOpen] = (0, react.useState)(false);
6086
- if (state.status === "unavailable") return null;
6087
- const disabled = state.status !== "ready" || !state.writable || state.saving;
6088
- return (0, react.createElement)("li", { "data-run2skill-settings-card": true }, (0, react.createElement)("button", {
6089
- type: "button",
6090
- "aria-expanded": open,
6091
- onClick: () => {
6092
- setOpen((value) => !value);
6093
- }
6094
- }, "run2skill", (0, react.createElement)("span", null, "将真实执行中的明确经验转为待审核 Skill Proposal")), open ? (0, react.createElement)("div", null, (0, react.createElement)("label", null, (0, react.createElement)("input", {
6095
- type: "checkbox",
6096
- checked: state.automaticLearning === true,
6097
- disabled,
6098
- onChange: (event) => {
6099
- props.controller.setAutomaticLearning(event.currentTarget.checked);
6100
- }
6101
- }), "Automatic Learning"), (0, react.createElement)("p", null, "关闭后暂停普通自动学习;显式“保存为 Skill”仍会持久保存。已开始的分析继续使用启动时快照。"), (0, react.createElement)("p", null, "Learning 模型沿用发起 Session 的 provider/model(inherit-session)。"), state.error === void 0 ? null : (0, react.createElement)("p", { role: "status" }, "设置已变化,请刷新后重试。"), (0, react.createElement)(PurgeSettingsSection, { controller: props.purgeController })) : null);
6102
- }
6103
- function applyAutomaticLearningSettingsClient(context) {
6104
- const controller = new AutomaticLearningSettingsController(context.settingsScope.bind({ namespace: "run2skill" }));
6105
- const callPurge = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
6106
- const purgeController = new PurgeSettingsController(callPurge, () => {
6107
- return context.workspaces.list.getSnapshot().recentWorkspaceId;
6108
- });
6109
- purgeController.start();
6110
- context.effect?.(() => () => {
6111
- controller.dispose();
6112
- purgeController.dispose();
6113
- }, "run2skill: settings and purge");
6114
- context.slots.inject("settings.plugin.item", () => context.slots.register({
6115
- name: "settings.plugin.item",
6116
- key: "run2skill",
6117
- inject: () => ({
6118
- controller,
6119
- purgeController
6120
- })
6121
- }, AutomaticLearningSettingsCard));
6122
- }
6123
6970
  //#endregion
6124
6971
  //#region src/client/index.ts
6125
6972
  const name = "run2skill-client";
@@ -6127,19 +6974,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6127
6974
  "connection",
6128
6975
  "slots",
6129
6976
  "workspaces",
6977
+ "sessions",
6130
6978
  "remote",
6131
6979
  "settingsScope"
6132
6980
  ];
6133
6981
  function apply(context) {
6134
- applyObserveSummaryClient(context);
6135
- applyAutomaticLearningSettingsClient(context);
6982
+ applyRun2skillClient(context);
6136
6983
  }
6137
6984
  //#endregion
6985
+ exports.AttentionSettingsSummary = AttentionSettingsSummary;
6138
6986
  exports.AutomaticLearningSettingsCard = AutomaticLearningSettingsCard;
6139
6987
  exports.AutomaticLearningSettingsController = AutomaticLearningSettingsController;
6988
+ exports.LearningFailureSection = LearningFailureSection;
6140
6989
  exports.OBSERVE_SUMMARY_POLL_INTERVAL_MS = OBSERVE_SUMMARY_POLL_INTERVAL_MS;
6141
- exports.ObserveHeaderAction = ObserveHeaderAction;
6142
- exports.ObserveStatusPill = ObserveStatusPill;
6143
6990
  exports.ObserveSummaryPoller = ObserveSummaryPoller;
6144
6991
  exports.PROPOSAL_ACTIVE_POLL_INTERVAL_MS = PROPOSAL_ACTIVE_POLL_INTERVAL_MS;
6145
6992
  exports.PROPOSAL_POLL_INTERVAL_MS = PROPOSAL_POLL_INTERVAL_MS;
@@ -6152,12 +6999,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6152
6999
  exports.ProposalTextView = ProposalTextView;
6153
7000
  exports.PurgeSettingsController = PurgeSettingsController;
6154
7001
  exports.PurgeSettingsSection = PurgeSettingsSection;
7002
+ exports.RecentSkillActivitySection = RecentSkillActivitySection;
6155
7003
  exports.RejectConfirmationBody = RejectConfirmationBody;
6156
- exports.Run2skillHeaderAction = Run2skillHeaderAction;
7004
+ exports.RejectProposalModal = RejectProposalModal;
7005
+ exports.Run2skillAttentionToast = Run2skillAttentionToast;
7006
+ exports.Run2skillSettingsPage = Run2skillSettingsPage;
7007
+ exports.actionableProposalItems = actionableProposalItems;
6157
7008
  exports.apply = apply;
6158
7009
  exports.applyAutomaticLearningSettingsClient = applyAutomaticLearningSettingsClient;
6159
- exports.applyObserveSummaryClient = applyObserveSummaryClient;
6160
- exports.describeObserveState = describeObserveState;
7010
+ exports.applyRun2skillClient = applyRun2skillClient;
6161
7011
  exports.describeProcessingState = describeProcessingState;
6162
7012
  exports.describeProposalListItem = describeProposalListItem;
6163
7013
  exports.describeProposalOutcome = describeProposalOutcome;
@@ -6166,10 +7016,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6166
7016
  exports.describeReviewDecision = describeReviewDecision;
6167
7017
  exports.describeRun2skillHealth = describeRun2skillHealth;
6168
7018
  exports.factsFromAction = factsFromAction;
7019
+ exports.hostTabVisible = hostTabVisible;
6169
7020
  exports.inject = inject;
6170
7021
  exports.makeExactLineDiff = makeExactLineDiff;
6171
7022
  exports.makeSafeText = makeSafeText;
6172
7023
  exports.name = name;
7024
+ exports.proposalDetailAction = proposalDetailAction;
6173
7025
  exports.proposalInboxContentBlocked = proposalInboxContentBlocked;
6174
7026
  exports.trapDialogTab = trapDialogTab;
6175
7027
  return module.exports;