dsh-run2skill 0.1.0-alpha → 0.1.1-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,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,188 @@ 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_OghXMq{max-width:960px;color:var(--dsw-alias-content-primary,inherit);gap:20px;padding:8px 0 32px;display:grid}.run2skill_intro_OghXMq,.run2skill_muted_OghXMq{color:var(--dsw-alias-content-secondary,currentColor);margin:0;font-size:13px;line-height:1.5}.run2skill_section_OghXMq{border:1px solid var(--dsw-alias-border-subtle,color-mix(in srgb, currentColor 16%, transparent));background:var(--dsw-alias-surface-primary,transparent);border-radius:16px;overflow:hidden}.run2skill_sectionBody_OghXMq{gap:14px;padding:0 18px 18px 48px;display:grid}.run2skill_toolbar_OghXMq,.run2skill_actions_OghXMq,.run2skill_modeGroup_OghXMq{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.run2skill_attentionList_OghXMq,.run2skill_proposalList_OghXMq{gap:8px;margin:0;padding:0;list-style:none;display:grid}.run2skill_proposalLayout_OghXMq{grid-template-columns:minmax(220px,.8fr) minmax(0,1.6fr);gap:16px;display:grid}.run2skill_proposalListButton_OghXMq{text-align:start;white-space:normal;justify-content:flex-start;width:100%}.run2skill_detail_OghXMq{border:1px solid var(--dsw-alias-border-subtle,color-mix(in srgb, currentColor 16%, transparent));background:var(--dsw-alias-surface-secondary,transparent);border-radius:12px;min-width:0;max-height:640px;padding:14px;overflow:auto}.run2skill_detail_OghXMq pre{white-space:pre-wrap;unicode-bidi:isolate;overflow:auto}.run2skill_status_OghXMq{min-height:20px;margin:0;font-size:13px}.run2skill_danger_OghXMq{color:var(--dsw-alias-content-negative,var(--dsw-alias-content-primary,currentColor))}.run2skill_backdrop_OghXMq,.run2skill_rejectOverlay_OghXMq{background:var(--dsw-alias-overlay,color-mix(in srgb, currentColor 35%, transparent));position:fixed;inset:0}.run2skill_backdrop_OghXMq{z-index:999}.run2skill_rejectOverlay_OghXMq{z-index:1001;place-items:center;padding:16px;display:grid}.run2skill_legacyDialog_OghXMq{z-index:1000;border:1px solid var(--dsw-alias-border-subtle,currentColor);background:var(--dsw-alias-surface-primary,inherit);color:var(--dsw-alias-content-primary,inherit);box-shadow:var(--dsw-alias-shadow-modal,0 16px 48px color-mix(in srgb, currentColor 35%, transparent));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_OghXMq{pointer-events:none;display:contents}.run2skill_queue_OghXMq,.run2skill_legacyDetail_OghXMq{min-width:0;overflow:auto}.run2skill_legacyHeader_OghXMq{justify-content:space-between;align-items:center;gap:8px;display:flex}.run2skill_legacyList_OghXMq{margin:0;padding:0;list-style:none}.run2skill_rejectCard_OghXMq{background:var(--dsw-alias-surface-primary,inherit);border-radius:16px;max-width:32rem;padding:16px}.run2skill_textBlock_OghXMq{white-space:pre-wrap;overflow-wrap:anywhere;unicode-bidi:isolate;overflow:auto}.run2skill_previewFacts_OghXMq{grid-template-columns:max-content 1fr;gap:6px 12px;display:grid}.run2skill_previewFacts_OghXMq dt,.run2skill_previewFacts_OghXMq dd{margin:0}@media (width<=720px){.run2skill_proposalLayout_OghXMq{grid-template-columns:1fr}.run2skill_sectionBody_OghXMq{padding-left:18px}}";
4223
+ if (typeof document !== "undefined") upsertRun2skillStyle(css);
4224
+ var _run2skill_css_run2skill_settings_page_module_css_default = {
4225
+ "proposalLayout": "run2skill_proposalLayout_OghXMq",
4226
+ "section": "run2skill_section_OghXMq",
4227
+ "danger": "run2skill_danger_OghXMq",
4228
+ "legacyDetail": "run2skill_legacyDetail_OghXMq",
4229
+ "attentionList": "run2skill_attentionList_OghXMq",
4230
+ "legacyList": "run2skill_legacyList_OghXMq",
4231
+ "page": "run2skill_page_OghXMq",
4232
+ "modeGroup": "run2skill_modeGroup_OghXMq",
4233
+ "rejectCard": "run2skill_rejectCard_OghXMq",
4234
+ "previewFacts": "run2skill_previewFacts_OghXMq",
4235
+ "proposalList": "run2skill_proposalList_OghXMq",
4236
+ "legacyHeader": "run2skill_legacyHeader_OghXMq",
4237
+ "textBlock": "run2skill_textBlock_OghXMq",
4238
+ "rejectOverlay": "run2skill_rejectOverlay_OghXMq",
4239
+ "queue": "run2skill_queue_OghXMq",
4240
+ "backdrop": "run2skill_backdrop_OghXMq",
4241
+ "status": "run2skill_status_OghXMq",
4242
+ "intro": "run2skill_intro_OghXMq",
4243
+ "detail": "run2skill_detail_OghXMq",
4244
+ "legacyDialog": "run2skill_legacyDialog_OghXMq",
4245
+ "muted": "run2skill_muted_OghXMq",
4246
+ "sectionBody": "run2skill_sectionBody_OghXMq",
4247
+ "blocked": "run2skill_blocked_OghXMq",
4248
+ "toolbar": "run2skill_toolbar_OghXMq",
4249
+ "actions": "run2skill_actions_OghXMq",
4250
+ "proposalListButton": "run2skill_proposalListButton_OghXMq"
4251
+ };
4252
+ //#endregion
4253
+ //#region src/client/purge-settings.ts
4254
+ const PURGE_IDLE_POLL_INTERVAL_MS = 1e4;
4255
+ const PURGE_ACTIVE_POLL_INTERVAL_MS = 2e3;
4256
+ const previewId = string().regex(/^purv_[a-f0-9]{64}$/);
4257
+ const purgeId = string().regex(/^purge_[a-f0-9]{64}$/);
4258
+ const digest = string().regex(/^[a-f0-9]{64}$/);
4259
+ const isoDateTime = string().datetime({ offset: true });
4260
+ const count = number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
4261
+ const scopeBinding = discriminatedUnion("scope", [object({
4262
+ scope: literal("PROJECT"),
4263
+ workspaceId: string().min(1).max(256)
4264
+ }).strict(), object({ scope: literal("USER") }).strict()]);
4265
+ const previewSchema = object({
4266
+ apiVersion: literal(1),
4267
+ previewId,
4268
+ digest,
4269
+ expiresAt: isoDateTime,
4270
+ scopeBinding,
4271
+ hideBefore: isoDateTime,
4272
+ workItemCount: count,
4273
+ lineageCount: count,
4274
+ blockedOrUnprovenCount: count,
4275
+ willDelete: array(object({
4276
+ kind: _enum(["WORK_ITEMS", "LINEAGES"]),
4277
+ count
4278
+ }).strict()).length(2),
4279
+ willKeep: array(object({
4280
+ reason: _enum([
4281
+ "KEEP_NEW",
4282
+ "KEEP_SCOPE",
4283
+ "KEEP_UNPROVEN"
4284
+ ]),
4285
+ count
4286
+ }).strict()).length(3),
4287
+ busyPublicationCount: count
4288
+ }).strict();
4289
+ const receiptSchema = object({
4290
+ apiVersion: literal(1),
4291
+ purgeId,
4292
+ state: _enum(["COMPLETED", "IN_PROGRESS"]),
4293
+ phase: _enum([
4294
+ "HIDING",
4295
+ "DELETING_LINEAGES",
4296
+ "DELETING_WORK_ITEMS",
4297
+ "VERIFYING"
4298
+ ]).optional(),
4299
+ deletedWorkItems: count,
4300
+ deletedLineages: count
4301
+ }).strict();
4302
+ const statusSchema = discriminatedUnion("state", [object({
4303
+ apiVersion: literal(1),
4304
+ state: literal("IDLE")
4305
+ }).strict(), object({
4306
+ apiVersion: literal(1),
4307
+ state: literal("IN_PROGRESS"),
4308
+ purgeId,
4309
+ hideBefore: isoDateTime,
4310
+ startedAt: isoDateTime,
4311
+ phase: _enum([
4312
+ "HIDING",
4313
+ "DELETING_LINEAGES",
4314
+ "DELETING_WORK_ITEMS",
4315
+ "VERIFYING"
4316
+ ]),
4317
+ deletedWorkItems: count,
4318
+ deletedLineages: count,
4319
+ lastError: object({
4320
+ code: string().regex(/^[A-Z0-9_]+$/),
4321
+ occurredAt: isoDateTime
4322
+ }).strict().optional()
4323
+ }).strict()]);
4324
+ const errorSchema = object({
4325
+ ok: literal(false),
4326
+ error: object({
4327
+ code: string().min(1).max(128),
4328
+ message: string().max(512),
4329
+ details: object({ busyPublicationCount: count.optional() }).strict().optional()
4330
+ }).strict()
4331
+ }).strict();
4332
+ var PurgeResponseError = class extends Error {
4333
+ facts;
4334
+ constructor(facts) {
4335
+ super(facts.code);
4336
+ this.facts = facts;
4337
+ }
4338
+ };
4161
4339
  function browserEnvironment$2() {
4162
4340
  return {
4163
4341
  isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
@@ -4165,13 +4343,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4165
4343
  clearInterval: (handle) => {
4166
4344
  globalThis.clearInterval(handle);
4167
4345
  },
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
4346
  onVisibilityChange: (listener) => {
4176
4347
  if (typeof document === "undefined") return () => void 0;
4177
4348
  document.addEventListener("visibilitychange", listener);
@@ -4181,25 +4352,62 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4181
4352
  }
4182
4353
  };
4183
4354
  }
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;
4355
+ function parseValue$1(schema, response) {
4356
+ const success = object({
4357
+ ok: literal(true),
4358
+ value: schema
4359
+ }).strict().safeParse(response);
4360
+ if (success.success) return success.data.value;
4361
+ const failure = errorSchema.safeParse(response);
4362
+ if (failure.success) {
4363
+ const busy = failure.data.error.details?.busyPublicationCount;
4364
+ throw new PurgeResponseError({
4365
+ code: failure.data.error.code,
4366
+ ...typeof busy === "number" && Number.isSafeInteger(busy) && busy >= 0 ? { busyPublicationCount: busy } : {}
4367
+ });
4368
+ }
4369
+ throw new PurgeResponseError({ code: "PURGE_STORAGE_UNAVAILABLE" });
4188
4370
  }
4189
- var ObserveSummaryPoller = class {
4371
+ function completionAnnouncement(scope, receipt) {
4372
+ return `${scope === void 0 ? "run2skill 数据" : `${scope} run2skill 数据`}清理完成:${String(receipt.deletedWorkItems)} 条待处理数据,${String(receipt.deletedLineages)} 条发布沿袭记录。`;
4373
+ }
4374
+ function polledCompletionAnnouncement(scope) {
4375
+ return `${scope === void 0 ? "" : `${scope} `}run2skill 数据清理完成。`;
4376
+ }
4377
+ function errorAnnouncement(error, durableBoundary = false) {
4378
+ if (error.code === "PURGE_PREVIEW_STALE") return "清理预览已失效,请重新预览后确认。";
4379
+ if (error.code === "PURGE_BUSY") return `当前有 ${String(error.busyPublicationCount ?? 0)} 条正在发布;请等待发布完成后重新预览。`;
4380
+ if (error.code === "PURGE_ALREADY_RUNNING") return "已有一项数据清理正在运行,请等待完成。";
4381
+ if (error.code === "PURGE_SCOPE_UNAVAILABLE") return "当前作用域无法可靠确认,未执行清理。";
4382
+ if (error.code === "PURGE_INCOMPATIBLE") return "当前数据版本不兼容,未执行清理。";
4383
+ if (error.code === "PURGE_FENCE_LIMIT") return "PROJECT 清理保护记录已达上限,未执行清理。";
4384
+ return durableBoundary ? "数据清理未完成;已建立的清理边界继续隐藏,可重试。" : "数据清理暂不可用;未确认已建立清理边界,请稍后重试。";
4385
+ }
4386
+ const initialState$1 = {
4387
+ statusPhase: "LOADING",
4388
+ previewPending: false,
4389
+ mutationPending: false,
4390
+ announcement: ""
4391
+ };
4392
+ var PurgeSettingsController = class {
4190
4393
  call;
4394
+ getWorkspaceId;
4191
4395
  environment;
4192
4396
  #listeners = /* @__PURE__ */ new Set();
4193
- #state = { phase: "LOADING" };
4397
+ #state = initialState$1;
4194
4398
  #started = false;
4399
+ #active = true;
4195
4400
  #disposed = false;
4196
- #timer;
4197
4401
  #pending;
4198
4402
  #abort;
4199
- #removeFocus;
4403
+ #statusPending;
4404
+ #statusAbort;
4405
+ #timer;
4406
+ #timerDelay;
4200
4407
  #removeVisibility;
4201
- constructor(call, environment = browserEnvironment$2()) {
4408
+ constructor(call, getWorkspaceId, environment = browserEnvironment$2()) {
4202
4409
  this.call = call;
4410
+ this.getWorkspaceId = getWorkspaceId;
4203
4411
  this.environment = environment;
4204
4412
  }
4205
4413
  snapshot = () => this.#state;
@@ -4212,87 +4420,521 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4212
4420
  start() {
4213
4421
  if (this.#started || this.#disposed) return;
4214
4422
  this.#started = true;
4215
- this.#removeFocus = this.environment.onFocus(() => {
4216
- if (this.environment.isVisible()) this.#refresh();
4217
- });
4218
4423
  this.#removeVisibility = this.environment.onVisibilityChange(() => {
4219
- if (this.environment.isVisible()) {
4424
+ if (this.#active && this.environment.isVisible()) {
4220
4425
  this.#schedule();
4221
- this.#refresh();
4426
+ this.#refreshStatus();
4222
4427
  } else this.#unschedule();
4223
4428
  });
4224
- if (this.environment.isVisible()) {
4429
+ if (this.#active && this.environment.isVisible()) {
4225
4430
  this.#schedule();
4226
- this.#refresh();
4431
+ this.#refreshStatus();
4227
4432
  }
4228
4433
  }
4229
- whenIdle() {
4230
- return this.#pending ?? Promise.resolve();
4231
- }
4232
- dispose() {
4233
- if (this.#disposed) return;
4234
- this.#disposed = true;
4434
+ pause() {
4435
+ if (this.#disposed || !this.#active) return;
4436
+ this.#active = false;
4235
4437
  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;
4438
+ this.#statusAbort?.abort();
4253
4439
  }
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;
4440
+ resume() {
4441
+ if (this.#disposed || this.#active) return;
4442
+ this.#active = true;
4443
+ if (!this.#started || !this.environment.isVisible()) return;
4444
+ this.#schedule();
4445
+ this.whenIdle().then(() => {
4446
+ if (this.#active && !this.#disposed) this.#refreshStatus();
4275
4447
  });
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
4448
  }
4284
- #publish(state) {
4285
- this.#state = state;
4286
- for (const listener of this.#listeners) listener();
4449
+ whenIdle() {
4450
+ return Promise.all([this.#pending ?? Promise.resolve(), this.#statusPending ?? Promise.resolve()]).then(() => void 0);
4287
4451
  }
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}$/);
4452
+ async preview(scope) {
4453
+ if (this.#disposed || this.#state.previewPending || this.#state.mutationPending) return;
4454
+ const workspaceId = this.getWorkspaceId();
4455
+ if (scope === "PROJECT" && workspaceId === void 0) {
4456
+ const error = { code: "PURGE_SCOPE_UNAVAILABLE" };
4457
+ this.#publish({
4458
+ ...this.#state,
4459
+ error,
4460
+ announcement: errorAnnouncement(error)
4461
+ });
4462
+ return;
4463
+ }
4464
+ await this.#execute(async (signal) => {
4465
+ this.#publish({
4466
+ ...this.#state,
4467
+ previewPending: true,
4468
+ previewScope: scope,
4469
+ preview: void 0,
4470
+ error: void 0,
4471
+ announcement: ""
4472
+ });
4473
+ const preview = parseValue$1(previewSchema, await this.call("purge/preview", scope === "PROJECT" ? {
4474
+ apiVersion: 1,
4475
+ scope,
4476
+ workspaceId
4477
+ } : {
4478
+ apiVersion: 1,
4479
+ scope
4480
+ }, signal));
4481
+ this.#publish({
4482
+ ...this.#state,
4483
+ previewPending: false,
4484
+ preview,
4485
+ error: void 0
4486
+ });
4487
+ }, (error) => {
4488
+ this.#publish({
4489
+ ...this.#state,
4490
+ previewPending: false,
4491
+ preview: void 0,
4492
+ error,
4493
+ announcement: errorAnnouncement(error)
4494
+ });
4495
+ });
4496
+ }
4497
+ cancelPreview() {
4498
+ if (this.#disposed || this.#state.mutationPending) return;
4499
+ this.#publish({
4500
+ ...this.#state,
4501
+ preview: void 0,
4502
+ previewScope: void 0,
4503
+ error: void 0,
4504
+ announcement: ""
4505
+ });
4506
+ }
4507
+ async confirm() {
4508
+ const preview = this.#state.preview;
4509
+ const scope = this.#state.previewScope;
4510
+ if (this.#disposed || preview === void 0 || scope === void 0 || this.#state.mutationPending) return;
4511
+ const currentWorkspaceId = this.getWorkspaceId();
4512
+ if (scope === "PROJECT" && (currentWorkspaceId === void 0 || preview.scopeBinding.scope !== "PROJECT" || preview.scopeBinding.workspaceId !== currentWorkspaceId)) {
4513
+ const error = { code: "PURGE_PREVIEW_STALE" };
4514
+ this.#publish({
4515
+ ...this.#state,
4516
+ preview: void 0,
4517
+ previewScope: void 0,
4518
+ error,
4519
+ announcement: errorAnnouncement(error)
4520
+ });
4521
+ return;
4522
+ }
4523
+ await this.#execute(async (signal) => {
4524
+ this.#publish({
4525
+ ...this.#state,
4526
+ mutationPending: true,
4527
+ error: void 0,
4528
+ announcement: ""
4529
+ });
4530
+ const receipt = parseValue$1(receiptSchema, await this.call("purge/confirm", scope === "PROJECT" ? {
4531
+ apiVersion: 1,
4532
+ scope,
4533
+ workspaceId: currentWorkspaceId,
4534
+ previewId: preview.previewId,
4535
+ digest: preview.digest
4536
+ } : {
4537
+ apiVersion: 1,
4538
+ scope,
4539
+ previewId: preview.previewId,
4540
+ digest: preview.digest
4541
+ }, signal));
4542
+ this.#acceptReceipt(scope, receipt);
4543
+ }, async (error) => {
4544
+ this.#publish({
4545
+ ...this.#state,
4546
+ mutationPending: false,
4547
+ error,
4548
+ announcement: errorAnnouncement(error),
4549
+ preview: void 0,
4550
+ previewScope: void 0
4551
+ });
4552
+ await this.#readStatusAfterFailure(error);
4553
+ });
4554
+ }
4555
+ async retry() {
4556
+ const status = this.#state.status;
4557
+ if (this.#disposed || status?.state !== "IN_PROGRESS" || status.lastError === void 0 || this.#state.mutationPending) return;
4558
+ await this.#execute(async (signal) => {
4559
+ this.#publish({
4560
+ ...this.#state,
4561
+ mutationPending: true,
4562
+ error: void 0,
4563
+ announcement: "正在重试数据清理。"
4564
+ });
4565
+ const receipt = parseValue$1(receiptSchema, await this.call("purge/retry", {
4566
+ apiVersion: 1,
4567
+ purgeId: status.purgeId
4568
+ }, signal));
4569
+ this.#acceptReceipt(this.#state.previewScope, receipt);
4570
+ }, async (error) => {
4571
+ this.#publish({
4572
+ ...this.#state,
4573
+ mutationPending: false,
4574
+ error,
4575
+ announcement: errorAnnouncement(error, true)
4576
+ });
4577
+ await this.#readStatusAfterFailure(error, true);
4578
+ });
4579
+ }
4580
+ dispose() {
4581
+ if (this.#disposed) return;
4582
+ this.#disposed = true;
4583
+ this.#unschedule();
4584
+ this.#removeVisibility?.();
4585
+ this.#removeVisibility = void 0;
4586
+ this.#abort?.abort();
4587
+ this.#statusAbort?.abort();
4588
+ this.#listeners.clear();
4589
+ }
4590
+ #acceptReceipt(scope, receipt) {
4591
+ if (receipt.state === "COMPLETED") {
4592
+ this.#publish({
4593
+ ...this.#state,
4594
+ statusPhase: "READY",
4595
+ status: {
4596
+ apiVersion: 1,
4597
+ state: "IDLE"
4598
+ },
4599
+ inProgressReceipt: void 0,
4600
+ preview: void 0,
4601
+ previewScope: void 0,
4602
+ mutationPending: false,
4603
+ error: void 0,
4604
+ announcement: completionAnnouncement(scope, receipt)
4605
+ });
4606
+ return;
4607
+ }
4608
+ this.#publish({
4609
+ ...this.#state,
4610
+ mutationPending: false,
4611
+ preview: void 0,
4612
+ inProgressReceipt: receipt,
4613
+ announcement: `run2skill 数据清理进行中:${receipt.phase ?? "HIDING"}。`
4614
+ });
4615
+ }
4616
+ async #readStatusAfterFailure(error, durableBoundary = false) {
4617
+ try {
4618
+ const status = parseValue$1(statusSchema, await this.call("purge/status", { apiVersion: 1 }, new AbortController().signal));
4619
+ this.#publish({
4620
+ ...this.#state,
4621
+ statusPhase: "READY",
4622
+ status,
4623
+ inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
4624
+ announcement: errorAnnouncement(error, durableBoundary || status.state === "IN_PROGRESS")
4625
+ });
4626
+ } catch {}
4627
+ }
4628
+ async #refreshStatus() {
4629
+ if (this.#disposed || !this.#active || !this.environment.isVisible()) return;
4630
+ if (this.#statusPending !== void 0) return this.#statusPending;
4631
+ const abort = new AbortController();
4632
+ this.#statusAbort = abort;
4633
+ const pending = (async () => {
4634
+ try {
4635
+ const status = parseValue$1(statusSchema, await this.call("purge/status", { apiVersion: 1 }, abort.signal));
4636
+ if (this.#disposed || abort.signal.aborted) return;
4637
+ const completed = status.state === "IDLE" && (this.#state.status?.state === "IN_PROGRESS" || this.#state.inProgressReceipt?.state === "IN_PROGRESS");
4638
+ this.#publish({
4639
+ ...this.#state,
4640
+ statusPhase: "READY",
4641
+ status,
4642
+ inProgressReceipt: status.state === "IDLE" ? void 0 : this.#state.inProgressReceipt,
4643
+ previewScope: completed ? void 0 : this.#state.previewScope,
4644
+ announcement: completed ? polledCompletionAnnouncement(this.#state.previewScope) : this.#state.announcement
4645
+ });
4646
+ } catch {
4647
+ if (this.#disposed || abort.signal.aborted) return;
4648
+ this.#publish({
4649
+ ...this.#state,
4650
+ statusPhase: this.#state.status === void 0 ? "UNAVAILABLE" : "STALE"
4651
+ });
4652
+ }
4653
+ })();
4654
+ this.#statusPending = pending;
4655
+ await pending.finally(() => {
4656
+ if (this.#statusPending === pending) this.#statusPending = void 0;
4657
+ if (this.#statusAbort === abort) this.#statusAbort = void 0;
4658
+ });
4659
+ }
4660
+ async #execute(task, onError) {
4661
+ if (this.#disposed || this.#pending !== void 0) return this.#pending ?? Promise.resolve();
4662
+ const abort = new AbortController();
4663
+ this.#abort = abort;
4664
+ const pending = (async () => {
4665
+ try {
4666
+ await task(abort.signal);
4667
+ } catch (error) {
4668
+ if (!this.#disposed && !abort.signal.aborted) await onError(error instanceof PurgeResponseError ? error.facts : { code: "PURGE_STORAGE_UNAVAILABLE" });
4669
+ }
4670
+ })();
4671
+ this.#pending = pending;
4672
+ await pending.finally(() => {
4673
+ if (this.#pending === pending) this.#pending = void 0;
4674
+ if (this.#abort === abort) this.#abort = void 0;
4675
+ });
4676
+ }
4677
+ #schedule() {
4678
+ if (this.#disposed || !this.#active) return;
4679
+ 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;
4680
+ if (this.#timer !== void 0 && this.#timerDelay === delay) return;
4681
+ this.#unschedule();
4682
+ this.#timerDelay = delay;
4683
+ this.#timer = this.environment.setInterval(() => {
4684
+ this.#refreshStatus();
4685
+ }, delay);
4686
+ }
4687
+ #unschedule() {
4688
+ if (this.#timer === void 0) return;
4689
+ this.environment.clearInterval(this.#timer);
4690
+ this.#timer = void 0;
4691
+ this.#timerDelay = void 0;
4692
+ }
4693
+ #publish(state) {
4694
+ if (this.#disposed) return;
4695
+ this.#state = state;
4696
+ if (this.#started && this.#active && this.environment.isVisible()) this.#schedule();
4697
+ for (const listener of this.#listeners) listener();
4698
+ }
4699
+ };
4700
+ const phaseCopy = {
4701
+ HIDING: "正在隐藏命中数据",
4702
+ DELETING_LINEAGES: "正在删除发布沿袭记录",
4703
+ DELETING_WORK_ITEMS: "正在删除待处理数据",
4704
+ VERIFYING: "正在验证清理结果"
4705
+ };
4706
+ function PreviewSummary({ preview }) {
4707
+ const keep = Object.fromEntries(preview.willKeep.map((item) => [item.reason, item.count]));
4708
+ 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)} 条;存在时确认会安全拒绝`));
4709
+ }
4710
+ function PurgeConfirmationDialog(props) {
4711
+ const dialogRef = (0, react.useRef)(null);
4712
+ const preview = props.state.preview;
4713
+ const scope = props.state.previewScope;
4714
+ (0, react.useEffect)(() => {
4715
+ if (preview === void 0 || typeof document === "undefined") return;
4716
+ const dialog = dialogRef.current?.closest("[role=\"dialog\"]");
4717
+ const initial = dialog?.querySelector("[autofocus]");
4718
+ const recapture = (event) => {
4719
+ if (dialog != null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
4720
+ };
4721
+ const captureKeyboard = (event) => {
4722
+ if (dialog == null || event.key !== "Escape" && event.key !== "Tab") return;
4723
+ if (event.key === "Escape") {
4724
+ event.preventDefault();
4725
+ event.stopImmediatePropagation();
4726
+ props.controller.cancelPreview();
4727
+ return;
4728
+ }
4729
+ trapDialogTab(event.key, event.shiftKey, () => {
4730
+ event.preventDefault();
4731
+ }, dialog, document.activeElement);
4732
+ };
4733
+ document.addEventListener("focusin", recapture, true);
4734
+ document.addEventListener("keydown", captureKeyboard, true);
4735
+ initial?.focus();
4736
+ return () => {
4737
+ document.removeEventListener("focusin", recapture, true);
4738
+ document.removeEventListener("keydown", captureKeyboard, true);
4739
+ props.restoreFocusRef.current?.focus();
4740
+ };
4741
+ }, [preview?.previewId, props.restoreFocusRef]);
4742
+ if (preview === void 0 || scope === void 0) return null;
4743
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
4744
+ open: true,
4745
+ title: `确认清理 ${scope} run2skill 数据?`,
4746
+ description: "此操作只清理本次预览边界内的 run2skill 数据,不会卸载插件。",
4747
+ closeLabel: "关闭清理确认框",
4748
+ onClose: () => {
4749
+ props.controller.cancelPreview();
4750
+ },
4751
+ 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, {
4752
+ variant: "ghost",
4753
+ autoFocus: true,
4754
+ disabled: props.state.mutationPending,
4755
+ onClick: () => {
4756
+ props.controller.cancelPreview();
4757
+ }
4758
+ }, "取消清理"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4759
+ variant: "primary",
4760
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16),
4761
+ disabled: props.state.mutationPending,
4762
+ onClick: () => {
4763
+ props.controller.confirm();
4764
+ }
4765
+ }, props.state.mutationPending ? "正在清理…" : "确认清理"))
4766
+ }, (0, react.createElement)("div", {
4767
+ ref: dialogRef,
4768
+ onKeyDown: (event) => {
4769
+ event.stopPropagation();
4770
+ if (event.key === "Escape") {
4771
+ event.preventDefault();
4772
+ props.controller.cancelPreview();
4773
+ } else if (dialogRef.current !== null) trapDialogTab(event.key, event.shiftKey, () => {
4774
+ event.preventDefault();
4775
+ }, dialogRef.current, typeof document === "undefined" ? void 0 : document.activeElement);
4776
+ }
4777
+ }, (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 })));
4778
+ }
4779
+ function PurgeSettingsSection(props) {
4780
+ const surfaceActive = props.active ?? true;
4781
+ (0, react.useEffect)(() => {
4782
+ if (!surfaceActive) props.controller.pause();
4783
+ props.controller.start();
4784
+ }, [props.controller]);
4785
+ (0, react.useEffect)(() => {
4786
+ if (surfaceActive) props.controller.resume();
4787
+ else props.controller.pause();
4788
+ }, [surfaceActive, props.controller]);
4789
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.snapshot, props.controller.snapshot);
4790
+ const restoreRef = (0, react.useMemo)(() => ({ current: null }), []);
4791
+ const active = state.status?.state === "IN_PROGRESS" ? state.status : void 0;
4792
+ const activeReceipt = state.inProgressReceipt?.state === "IN_PROGRESS" ? state.inProgressReceipt : void 0;
4793
+ const activePhase = active?.phase ?? activeReceipt?.phase ?? (activeReceipt === void 0 ? void 0 : "HIDING");
4794
+ const disabled = state.previewPending || state.mutationPending || active !== void 0 || activeReceipt !== void 0;
4795
+ 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)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.actions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4796
+ variant: "outline",
4797
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16),
4798
+ disabled,
4799
+ onClick: (event) => {
4800
+ restoreRef.current = event.currentTarget;
4801
+ props.controller.preview("PROJECT");
4802
+ }
4803
+ }, "预览并清理当前 PROJECT 数据"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4804
+ variant: "outline",
4805
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16),
4806
+ disabled,
4807
+ onClick: (event) => {
4808
+ restoreRef.current = event.currentTarget;
4809
+ props.controller.preview("USER");
4810
+ }
4811
+ }, "预览并清理 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", {
4812
+ role: "status",
4813
+ "aria-label": "当前 Purge 状态"
4814
+ }, (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)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4815
+ variant: "outline",
4816
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
4817
+ disabled: state.mutationPending,
4818
+ onClick: () => {
4819
+ props.controller.retry();
4820
+ }
4821
+ }, state.mutationPending ? "正在重试…" : "重试清理"))), state.error === void 0 ? null : (0, react.createElement)("p", { role: "alert" }, state.announcement), (0, react.createElement)("div", {
4822
+ role: "status",
4823
+ "aria-label": "Purge 状态播报",
4824
+ "aria-live": "polite",
4825
+ "aria-atomic": true
4826
+ }, state.announcement), (0, react.createElement)(PurgeConfirmationDialog, {
4827
+ controller: props.controller,
4828
+ state,
4829
+ restoreFocusRef: restoreRef
4830
+ }));
4831
+ }
4832
+ //#endregion
4833
+ //#region src/client/automatic-learning-settings.ts
4834
+ var AutomaticLearningSettingsController = class {
4835
+ scope;
4836
+ #listeners = /* @__PURE__ */ new Set();
4837
+ #snapshot;
4838
+ #saving = false;
4839
+ #error;
4840
+ #unsubscribe;
4841
+ constructor(scope) {
4842
+ this.scope = scope;
4843
+ this.#snapshot = this.#project();
4844
+ this.#unsubscribe = scope.subscribe(() => {
4845
+ this.#publish();
4846
+ });
4847
+ }
4848
+ getSnapshot = () => this.#snapshot;
4849
+ subscribe = (listener) => {
4850
+ this.#listeners.add(listener);
4851
+ return () => {
4852
+ this.#listeners.delete(listener);
4853
+ };
4854
+ };
4855
+ async setAutomaticLearning(value) {
4856
+ const before = this.scope.getSnapshot();
4857
+ if (before.status !== "ready" || !before.writable || this.#saving) return;
4858
+ this.#saving = true;
4859
+ this.#error = void 0;
4860
+ this.#publish();
4861
+ try {
4862
+ await this.scope.set("automaticLearning", value);
4863
+ const after = this.scope.getSnapshot();
4864
+ this.#error = after.status === "ready" && after.value?.automaticLearning === value ? void 0 : "SETTINGS_CHANGED";
4865
+ } catch {
4866
+ this.#error = "SETTINGS_CHANGED";
4867
+ } finally {
4868
+ this.#saving = false;
4869
+ this.#publish();
4870
+ }
4871
+ }
4872
+ dispose() {
4873
+ this.#unsubscribe();
4874
+ this.#listeners.clear();
4875
+ }
4876
+ #project() {
4877
+ const snapshot = this.scope.getSnapshot();
4878
+ return Object.freeze({
4879
+ status: snapshot.status,
4880
+ automaticLearning: snapshot.value?.automaticLearning,
4881
+ writable: snapshot.writable,
4882
+ saving: this.#saving,
4883
+ error: this.#error
4884
+ });
4885
+ }
4886
+ #publish() {
4887
+ this.#snapshot = this.#project();
4888
+ for (const listener of this.#listeners) listener();
4889
+ }
4890
+ };
4891
+ function AutomaticLearningSettingsCard(props) {
4892
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot, props.controller.getSnapshot);
4893
+ const [open, setOpen] = (0, react.useState)(false);
4894
+ if (state.status === "unavailable") return null;
4895
+ const disabled = state.status !== "ready" || !state.writable || state.saving;
4896
+ return (0, react.createElement)("li", { "data-run2skill-settings-card": true }, (0, react.createElement)("button", {
4897
+ type: "button",
4898
+ "aria-expanded": open,
4899
+ onClick: () => {
4900
+ setOpen((value) => !value);
4901
+ }
4902
+ }, "run2skill", (0, react.createElement)("span", null, "将真实执行中的明确经验转为待审核 Skill Proposal")), open ? (0, react.createElement)("div", null, (0, react.createElement)("label", null, (0, react.createElement)("input", {
4903
+ type: "checkbox",
4904
+ checked: state.automaticLearning === true,
4905
+ disabled,
4906
+ onChange: (event) => {
4907
+ props.controller.setAutomaticLearning(event.currentTarget.checked);
4908
+ }
4909
+ }), "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);
4910
+ }
4911
+ function applyAutomaticLearningSettingsClient(context) {
4912
+ const controller = new AutomaticLearningSettingsController(context.settingsScope.bind({ namespace: "run2skill" }));
4913
+ const callPurge = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
4914
+ const purgeController = new PurgeSettingsController(callPurge, () => {
4915
+ return context.workspaces.list.getSnapshot().recentWorkspaceId;
4916
+ });
4917
+ purgeController.start();
4918
+ context.effect?.(() => () => {
4919
+ controller.dispose();
4920
+ purgeController.dispose();
4921
+ }, "run2skill: settings and purge");
4922
+ context.slots.inject("settings.plugin.item", () => context.slots.register({
4923
+ name: "settings.plugin.item",
4924
+ key: "run2skill",
4925
+ inject: () => ({
4926
+ controller,
4927
+ purgeController
4928
+ })
4929
+ }, AutomaticLearningSettingsCard));
4930
+ }
4931
+ //#endregion
4932
+ //#region src/client/proposal-inbox-wire.ts
4933
+ const identity = string().min(1).max(256);
4934
+ const sha256 = string().regex(/^[a-f0-9]{64}$/);
4935
+ const positiveInteger = number().int().safe().positive();
4936
+ const nonNegativeInteger = number().int().safe().nonnegative();
4937
+ const proposalId = string().regex(/^prop_[a-f0-9]{64}$/);
4296
4938
  const workItemId = string().regex(/^wi_[a-f0-9]{64}$/);
4297
4939
  const proposalRef = object({
4298
4940
  proposalId,
@@ -4320,35 +4962,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4320
4962
  expectedSource: _enum(["project-dsh", "user-dsh"]),
4321
4963
  resolverVersion: identity,
4322
4964
  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();
4965
+ resolutionContractDigest: sha256
4966
+ }).strict();
4967
+ const targetDisplay = object({ skillName: identity }).strict();
4330
4968
  const actionBinding = discriminatedUnion("kind", [
4331
4969
  object({
4332
4970
  kind: literal("CREATE"),
4333
4971
  rootBinding: rootDisplay,
4334
4972
  targetBinding: targetDisplay,
4335
- expectedAbsence: object({
4336
- observedAt: string().min(1).max(64),
4337
- flatSkillFilePath: string().min(1).max(8192)
4338
- }).passthrough()
4339
- }).passthrough(),
4973
+ expectedAbsence: object({ observedAt: string().min(1).max(64) }).strict()
4974
+ }).strict(),
4340
4975
  object({
4341
4976
  kind: literal("MERGE"),
4342
4977
  rootBinding: rootDisplay,
4343
4978
  targetBinding: targetDisplay,
4344
4979
  baseBinding: object({
4345
4980
  candidateKey: string().regex(/^cand_[a-f0-9]{64}$/),
4346
- path: string().min(1).max(8192),
4347
4981
  exactBytes: string().min(1).max(65536),
4348
4982
  bytesDigest: sha256,
4349
4983
  observedAt: string().min(1).max(64)
4350
- }).passthrough()
4351
- }).passthrough(),
4984
+ }).strict()
4985
+ }).strict(),
4352
4986
  object({
4353
4987
  kind: literal("DISCARD"),
4354
4988
  coveringCandidateBinding: object({
@@ -4357,10 +4991,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4357
4991
  source: identity,
4358
4992
  content: string().min(1).max(65536),
4359
4993
  contentDigest: sha256,
4360
- observedAt: string().min(1).max(64),
4361
- path: string().min(1).max(8192).optional()
4362
- }).passthrough()
4363
- }).passthrough()
4994
+ observedAt: string().min(1).max(64)
4995
+ }).strict()
4996
+ }).strict()
4364
4997
  ]);
4365
4998
  const proposal = object({
4366
4999
  schemaVersion: literal(1),
@@ -4383,20 +5016,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4383
5016
  skillBytesDigest: sha256,
4384
5017
  rendererVersion: identity,
4385
5018
  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(),
5019
+ workspaceBinding: object({ workspaceId: identity }).strict().optional(),
4391
5020
  dshHomeBinding: object({
4392
5021
  resolutionKind: _enum([
4393
5022
  "CONFIGURATION",
4394
5023
  "ENVIRONMENT",
4395
5024
  "DEFAULT"
4396
5025
  ]),
4397
- canonicalPath: string().min(1).max(8192),
4398
- identityDigest: sha256,
4399
- observedAt: string().min(1).max(64)
5026
+ identityDigest: sha256
4400
5027
  }).strict().optional(),
4401
5028
  supportingExperienceIds: array(string().regex(/^exp_[a-f0-9]{64}$/)).min(1).max(3),
4402
5029
  catalogObservationDigest: sha256,
@@ -4477,7 +5104,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4477
5104
  const listPage = object({
4478
5105
  apiVersion: literal(1),
4479
5106
  items: array(listItem).max(20),
4480
- nextCursor: string().regex(/^c_[0-9]+$/).optional()
5107
+ nextCursor: string().regex(/^c_[1-9][0-9]*_[1-9][0-9]*_[a-f0-9]{64}$/).optional()
4481
5108
  }).strict();
4482
5109
  const detail = object({
4483
5110
  apiVersion: literal(1),
@@ -4514,7 +5141,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4514
5141
  ]),
4515
5142
  publicationOutcome
4516
5143
  }).strict();
4517
- function parseValue$1(schema, value) {
5144
+ function parseValue(schema, value) {
4518
5145
  const parsed = object({
4519
5146
  ok: literal(true),
4520
5147
  value: schema
@@ -4522,16 +5149,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4522
5149
  return parsed.success ? parsed.data.value : void 0;
4523
5150
  }
4524
5151
  function parseProposalSummary(value) {
4525
- return parseValue$1(summary, value);
5152
+ return parseValue(summary, value);
4526
5153
  }
4527
5154
  function parseProposalList(value) {
4528
- return parseValue$1(listPage, value);
5155
+ return parseValue(listPage, value);
4529
5156
  }
4530
5157
  function parseProposalDetail(value) {
4531
- return parseValue$1(detail, value);
5158
+ return parseValue(detail, value);
4532
5159
  }
4533
5160
  function parseMutationReceipt(value) {
4534
- return parseValue$1(receipt, value);
5161
+ return parseValue(receipt, value);
4535
5162
  }
4536
5163
  //#endregion
4537
5164
  //#region src/client/proposal-inbox.ts
@@ -4607,7 +5234,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4607
5234
  }
4608
5235
  };
4609
5236
  }
4610
- const initialState$1 = {
5237
+ const initialState = {
4611
5238
  open: false,
4612
5239
  summaryPhase: "LOADING",
4613
5240
  listPhase: "IDLE",
@@ -4620,9 +5247,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4620
5247
  workspaceId;
4621
5248
  call;
4622
5249
  environment;
5250
+ options;
4623
5251
  #listeners = /* @__PURE__ */ new Set();
4624
- #state = initialState$1;
5252
+ #state = initialState;
4625
5253
  #started = false;
5254
+ #active = true;
4626
5255
  #disposed = false;
4627
5256
  #pending;
4628
5257
  #abort;
@@ -4631,10 +5260,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4631
5260
  #removeFocus;
4632
5261
  #removeVisibility;
4633
5262
  #removeOnline;
4634
- constructor(workspaceId, call, environment = browserEnvironment$1()) {
5263
+ constructor(workspaceId, call, environment = browserEnvironment$1(), options = {}) {
4635
5264
  this.workspaceId = workspaceId;
4636
5265
  this.call = call;
4637
5266
  this.environment = environment;
5267
+ this.options = options;
4638
5268
  }
4639
5269
  snapshot = () => this.#state;
4640
5270
  subscribe = (listener) => {
@@ -4647,21 +5277,36 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4647
5277
  if (this.#started || this.#disposed) return;
4648
5278
  this.#started = true;
4649
5279
  const refresh = () => {
4650
- if (this.environment.isVisible()) this.#refreshVisible();
5280
+ if (this.#active && this.environment.isVisible()) this.#refreshVisible();
4651
5281
  };
4652
5282
  this.#removeFocus = this.environment.onFocus(refresh);
4653
5283
  this.#removeOnline = this.environment.onOnline(refresh);
4654
5284
  this.#removeVisibility = this.environment.onVisibilityChange(() => {
4655
- if (this.environment.isVisible()) {
5285
+ if (this.#active && this.environment.isVisible()) {
4656
5286
  this.#schedule();
4657
5287
  this.#refreshVisible();
4658
5288
  } else this.#unschedule();
4659
5289
  });
4660
- if (this.environment.isVisible()) {
5290
+ if (this.#active && this.environment.isVisible()) {
4661
5291
  this.#schedule();
4662
5292
  this.#refreshVisible();
4663
5293
  }
4664
5294
  }
5295
+ pause() {
5296
+ if (this.#disposed || !this.#active) return;
5297
+ this.#active = false;
5298
+ this.#unschedule();
5299
+ this.#abort?.abort();
5300
+ }
5301
+ resume() {
5302
+ if (this.#disposed || this.#active) return;
5303
+ this.#active = true;
5304
+ if (!this.#started || !this.environment.isVisible()) return;
5305
+ this.#schedule();
5306
+ this.whenIdle().then(() => {
5307
+ if (this.#active && !this.#disposed) this.#refreshVisible();
5308
+ });
5309
+ }
4665
5310
  whenIdle() {
4666
5311
  return this.#pending ?? Promise.resolve();
4667
5312
  }
@@ -4693,9 +5338,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4693
5338
  detail: void 0,
4694
5339
  announcement: ""
4695
5340
  });
5341
+ const scopeAccess = this.#scopeAccess();
5342
+ const action = scopeAccess.actions.find((candidate) => candidate.proposalRef.proposalId === proposalId);
5343
+ if (action === void 0) throw new Error("proposal action is stale");
4696
5344
  const detail = parseProposalDetail(await this.call(endpoint.get, {
4697
5345
  apiVersion: 1,
4698
- proposalId
5346
+ proposalId,
5347
+ currentScope: scopeAccess.currentScope,
5348
+ action
4699
5349
  }, signal));
4700
5350
  if (detail === void 0) throw new Error("invalid detail response");
4701
5351
  this.#publish({
@@ -4727,9 +5377,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4727
5377
  workItemId: detail.workItemId,
4728
5378
  workItemRevision: detail.workItemRevision,
4729
5379
  proposalRef,
5380
+ currentScope: this.#scopeAccess().currentScope,
5381
+ action: this.#scopeAccess().actions.find((candidate) => candidate.proposalRef.proposalId === proposalRef.proposalId),
4730
5382
  ...action === "REJECT" ? { confirm: true } : {}
4731
5383
  };
4732
5384
  await this.#execute(async (signal) => {
5385
+ if (request.action === void 0) throw new Error("proposal action is stale");
4733
5386
  this.#publish({
4734
5387
  ...this.#state,
4735
5388
  mutationPending: true,
@@ -4781,7 +5434,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4781
5434
  this.#listeners.clear();
4782
5435
  }
4783
5436
  async #refreshVisible() {
4784
- if (this.#disposed || !this.environment.isVisible()) return;
5437
+ if (this.#disposed || !this.#active || !this.environment.isVisible()) return;
4785
5438
  await this.#execute(async (signal) => {
4786
5439
  await this.#refreshWithin(signal);
4787
5440
  }, () => {
@@ -4792,6 +5445,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4792
5445
  });
4793
5446
  }
4794
5447
  async #refreshWithin(signal) {
5448
+ if (this.options.attentionDriven === true) {
5449
+ if (!this.#state.open) return;
5450
+ await this.#loadListWithin(signal);
5451
+ await this.#refreshPublishingDetailWithin(signal);
5452
+ return;
5453
+ }
4795
5454
  const summary = parseProposalSummary(await this.call(endpoint.summary, {
4796
5455
  apiVersion: 1,
4797
5456
  workspaceId: this.workspaceId
@@ -4804,11 +5463,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4804
5463
  });
4805
5464
  if (!this.#state.open) return;
4806
5465
  await this.#loadListWithin(signal);
5466
+ await this.#refreshPublishingDetailWithin(signal);
5467
+ }
5468
+ async #refreshPublishingDetailWithin(signal) {
4807
5469
  const selected = this.#state.selectedProposalId;
4808
5470
  if (selected !== void 0 && this.#state.detail?.processingState === "PUBLISHING") {
5471
+ const scopeAccess = this.#scopeAccess();
5472
+ const action = scopeAccess.actions.find((candidate) => candidate.proposalRef.proposalId === selected);
5473
+ if (action === void 0) return;
4809
5474
  const detail = parseProposalDetail(await this.call(endpoint.get, {
4810
5475
  apiVersion: 1,
4811
- proposalId: selected
5476
+ proposalId: selected,
5477
+ currentScope: scopeAccess.currentScope,
5478
+ action
4812
5479
  }, signal));
4813
5480
  if (detail !== void 0) this.#publish({
4814
5481
  ...this.#state,
@@ -4838,7 +5505,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4838
5505
  for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
4839
5506
  const page = parseProposalList(await this.call(endpoint.list, {
4840
5507
  apiVersion: 1,
4841
- workspaceId: this.workspaceId,
5508
+ currentScope: this.#scopeAccess().currentScope,
5509
+ limit: 20,
4842
5510
  ...cursor === void 0 ? {} : { cursor }
4843
5511
  }, signal));
4844
5512
  if (page === void 0) throw new Error("invalid list response");
@@ -4873,7 +5541,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4873
5541
  });
4874
5542
  }
4875
5543
  #schedule() {
4876
- if (this.#disposed) return;
5544
+ if (this.#disposed || !this.#active) return;
4877
5545
  const delay = this.#state.open ? PROPOSAL_ACTIVE_POLL_INTERVAL_MS : PROPOSAL_POLL_INTERVAL_MS;
4878
5546
  if (this.#timer !== void 0 && this.#timerDelay === delay) return;
4879
5547
  this.#unschedule();
@@ -4891,9 +5559,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4891
5559
  #publish(state) {
4892
5560
  if (this.#disposed) return;
4893
5561
  this.#state = state;
4894
- if (this.#started && this.environment.isVisible()) this.#schedule();
5562
+ if (this.#started && this.#active && this.environment.isVisible()) this.#schedule();
4895
5563
  for (const listener of this.#listeners) listener();
4896
5564
  }
5565
+ #scopeAccess() {
5566
+ return this.options.scopeAccess?.() ?? {
5567
+ currentScope: {
5568
+ kind: "WORKSPACE",
5569
+ generation: 1,
5570
+ workspaceId: this.workspaceId
5571
+ },
5572
+ actions: []
5573
+ };
5574
+ }
4897
5575
  };
4898
5576
  const formatCharacters = /* @__PURE__ */ new Map([
4899
5577
  [0, "NULL"],
@@ -4933,16 +5611,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4933
5611
  return (0, react.createElement)("pre", {
4934
5612
  "aria-label": props.label,
4935
5613
  "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
- }
5614
+ className: _run2skill_css_run2skill_settings_page_module_css_default.textBlock
4946
5615
  }, props.mode === "SAFE" ? makeSafeText(props.value) : props.value);
4947
5616
  }
4948
5617
  function makeExactLineDiff(before, after) {
@@ -4983,38 +5652,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4983
5652
  return result;
4984
5653
  }
4985
5654
  //#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;
5008
- }
5009
- if (backwards && activeElement === first) {
5010
- preventDefault();
5011
- last.focus();
5012
- } else if (!backwards && activeElement === last) {
5013
- preventDefault();
5014
- first.focus();
5015
- }
5016
- }
5017
- //#endregion
5018
5655
  //#region src/client/status-copy.ts
5019
5656
  function describeRun2skillHealth(status) {
5020
5657
  if (status === "INCOMPATIBLE") return "run2skill 当前版本不兼容";
@@ -5065,853 +5702,263 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5065
5702
  if (state.summaryPhase === "LOADING") return "Proposal 队列状态加载中";
5066
5703
  if (state.summaryPhase === "UNAVAILABLE") return "Proposal 队列状态暂不可用";
5067
5704
  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
- }, "确认拒绝"));
5705
+ const health = describeRun2skillHealth(state.summary.status);
5706
+ const queue = state.summary.queue.completeness === "UNKNOWN" ? "待处理数量未知" : countLabel(state);
5707
+ return state.summaryPhase === "STALE" ? `Proposal 队列状态可能已过期:${health};${queue}` : `${health};${queue}`;
5352
5708
  }
5353
- function RejectConfirmation(props) {
5354
- const dialogRef = (0, react.useRef)(null);
5709
+ function ProposalInboxHeaderAction(props) {
5710
+ const controller = (0, react.useMemo)(() => new ProposalInboxController(props.workspaceId, props.callReview, void 0, props.scopeAccess === void 0 ? {} : { scopeAccess: props.scopeAccess }), [
5711
+ props.scopeAccess,
5712
+ props.callReview,
5713
+ props.workspaceId
5714
+ ]);
5355
5715
  (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]");
5360
- const recapture = (event) => {
5361
- if (dialog !== null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
5362
- };
5363
- document.addEventListener("focusin", recapture, true);
5364
- initial?.focus();
5716
+ controller.start();
5365
5717
  return () => {
5366
- document.removeEventListener("focusin", recapture, true);
5367
- previous?.focus();
5718
+ controller.dispose();
5368
5719
  };
5369
- }, []);
5370
- return (0, react.createElement)("div", {
5371
- ref: dialogRef,
5372
- role: "alertdialog",
5720
+ }, [controller]);
5721
+ const state = (0, react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
5722
+ const triggerRef = (0, react.useRef)(null);
5723
+ const dialogRef = (0, react.useRef)(null);
5724
+ const [textMode, setTextMode] = (0, react.useState)("SAFE");
5725
+ const [rejectConfirm, setRejectConfirm] = (0, react.useState)(false);
5726
+ const focusTrapSuspended = (0, react.useRef)(false);
5727
+ focusTrapSuspended.current = rejectConfirm;
5728
+ useDialogFocus(state.open, triggerRef, dialogRef, focusTrapSuspended);
5729
+ const label = countLabel(state);
5730
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
5731
+ ref: triggerRef,
5732
+ type: "button",
5733
+ "aria-label": label,
5734
+ "aria-haspopup": "dialog",
5735
+ "aria-expanded": state.open,
5736
+ "data-run2skill-proposal-trigger": true,
5737
+ onClick: () => {
5738
+ controller.open();
5739
+ }
5740
+ }, label), state.open ? (0, react.createElement)(ProposalInboxPanel, {
5741
+ controller,
5742
+ state,
5743
+ dialogRef,
5744
+ textMode,
5745
+ setTextMode,
5746
+ rejectConfirm,
5747
+ setRejectConfirm
5748
+ }) : null);
5749
+ }
5750
+ function closeOnKeyboard(event, controller, dialog) {
5751
+ if (event.key === "Escape") {
5752
+ event.preventDefault();
5753
+ controller.close();
5754
+ } else if (dialog !== null) trapDialogTab(event.key, event.shiftKey, () => {
5755
+ event.preventDefault();
5756
+ }, dialog, typeof document === "undefined" ? void 0 : document.activeElement);
5757
+ }
5758
+ function ProposalInboxPanel(props) {
5759
+ const { controller, state } = props;
5760
+ const contentBlocked = proposalInboxContentBlocked(state.mutationPending, props.rejectConfirm);
5761
+ return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("div", {
5762
+ "aria-hidden": true,
5763
+ "data-run2skill-proposal-backdrop": true,
5764
+ className: _run2skill_css_run2skill_settings_page_module_css_default.backdrop
5765
+ }), (0, react.createElement)("div", {
5766
+ ref: props.dialogRef,
5767
+ role: "dialog",
5373
5768
  "aria-modal": true,
5374
- "aria-labelledby": "run2skill-reject-title",
5375
- "aria-describedby": "run2skill-reject-description",
5769
+ "aria-labelledby": "run2skill-proposal-inbox-title",
5376
5770
  onKeyDown: (event) => {
5377
- event.stopPropagation();
5378
- if (event.key === "Escape") {
5379
- event.preventDefault();
5380
- props.onCancel();
5381
- } else if (dialogRef.current !== null) trapDialogTab(event.key, event.shiftKey, () => {
5382
- event.preventDefault();
5383
- }, dialogRef.current, typeof document === "undefined" ? void 0 : document.activeElement);
5771
+ closeOnKeyboard(event, controller, props.dialogRef.current);
5384
5772
  },
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%)"
5773
+ className: _run2skill_css_run2skill_settings_page_module_css_default.legacyDialog
5774
+ }, (0, react.createElement)("div", {
5775
+ "aria-hidden": props.rejectConfirm || void 0,
5776
+ inert: props.rejectConfirm ? "" : void 0,
5777
+ className: props.rejectConfirm ? _run2skill_css_run2skill_settings_page_module_css_default.blocked : void 0
5778
+ }, (0, react.createElement)("section", {
5779
+ "aria-label": "Proposal 待处理队列",
5780
+ className: _run2skill_css_run2skill_settings_page_module_css_default.queue
5781
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.legacyHeader }, (0, react.createElement)("h2", { id: "run2skill-proposal-inbox-title" }, "Skill Proposal Inbox"), (0, react.createElement)("button", {
5782
+ type: "button",
5783
+ "data-initial-focus": true,
5784
+ onClick: () => {
5785
+ controller.close();
5393
5786
  }
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)));
5402
- }
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", {
5787
+ }, "关闭")), (0, react.createElement)("p", { role: "status" }, countLabel(state)), (0, react.createElement)("p", {
5430
5788
  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
5445
- }
5446
- }, description);
5447
- }
5448
- function ObserveHeaderAction({ callSummary }) {
5449
- const poller = (0, react.useMemo)(() => new ObserveSummaryPoller(callSummary), [callSummary]);
5450
- (0, react.useEffect)(() => {
5451
- poller.start();
5452
- return () => {
5453
- poller.dispose();
5454
- };
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
- }));
5464
- }
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,
5473
- inject: () => ({
5474
- callSummary,
5475
- callReview,
5476
- getWorkspaceId
5477
- })
5478
- }, Run2skillHeaderAction));
5479
- }
5480
- //#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"
5518
- ]),
5519
- count
5520
- }).strict()).length(3),
5521
- busyPublicationCount: count
5522
- }).strict();
5523
- const receiptSchema = object({
5524
- 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
5535
- }).strict();
5536
- const statusSchema = discriminatedUnion("state", [object({
5537
- apiVersion: literal(1),
5538
- state: literal("IDLE")
5539
- }).strict(), object({
5540
- 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"
5550
- ]),
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()
5565
- }).strict();
5566
- var PurgeResponseError = class extends Error {
5567
- facts;
5568
- constructor(facts) {
5569
- super(facts.code);
5570
- this.facts = facts;
5571
- }
5572
- };
5573
- function browserEnvironment() {
5574
- return {
5575
- isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
5576
- setInterval: (callback, delayMs) => globalThis.setInterval(callback, delayMs),
5577
- clearInterval: (handle) => {
5578
- globalThis.clearInterval(handle);
5789
+ "aria-live": "polite"
5790
+ }, 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", { 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", {
5791
+ type: "button",
5792
+ disabled: contentBlocked || state.detailPhase === "LOADING",
5793
+ "aria-current": state.selectedProposalId === item.proposalRef.proposalId ? "true" : void 0,
5794
+ onClick: () => {
5795
+ controller.select(item.proposalRef.proposalId);
5579
5796
  },
5580
- onVisibilityChange: (listener) => {
5581
- if (typeof document === "undefined") return () => void 0;
5582
- document.addEventListener("visibilitychange", listener);
5583
- return () => {
5584
- document.removeEventListener("visibilitychange", listener);
5585
- };
5797
+ className: _run2skill_css_run2skill_settings_page_module_css_default.proposalListButton
5798
+ }, `${item.kind} · ${makeSafeText(item.name)} · ${item.persistenceScope} · ${describeProposalListItem(item)}`))))), (0, react.createElement)("section", {
5799
+ "aria-label": "Proposal 详情",
5800
+ className: _run2skill_css_run2skill_settings_page_module_css_default.legacyDetail
5801
+ }, 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, {
5802
+ detail: state.detail,
5803
+ textMode: props.textMode,
5804
+ setTextMode: props.setTextMode,
5805
+ mutationPending: contentBlocked,
5806
+ onApprove: () => {
5807
+ controller.mutate("APPROVE");
5808
+ },
5809
+ onReject: () => {
5810
+ props.setRejectConfirm(true);
5811
+ },
5812
+ onRetry: () => {
5813
+ controller.mutate("RETRY");
5814
+ },
5815
+ onConfirmDiscard: () => {
5816
+ controller.mutate("CONFIRM_DISCARD");
5586
5817
  }
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)} 条发布沿袭记录。`;
5818
+ }), (0, react.createElement)("div", {
5819
+ "aria-live": "polite",
5820
+ "aria-atomic": true
5821
+ }, state.announcement))), props.rejectConfirm ? (0, react.createElement)(RejectConfirmation, {
5822
+ disabled: state.mutationPending,
5823
+ onCancel: () => {
5824
+ props.setRejectConfirm(false);
5825
+ },
5826
+ onConfirm: () => {
5827
+ props.setRejectConfirm(false);
5828
+ controller.mutate("REJECT");
5829
+ }
5830
+ }) : null));
5607
5831
  }
5608
- function polledCompletionAnnouncement(scope) {
5609
- return `${scope === void 0 ? "" : `${scope} `}run2skill 数据清理完成。`;
5832
+ function factsFromAction(detail) {
5833
+ const action = detail.proposal.actionBinding;
5834
+ if (action.kind === "CREATE") return [
5835
+ `Root contract: ${action.rootBinding.rootContractVersion}`,
5836
+ `Root resolver: ${action.rootBinding.resolverVersion}`,
5837
+ `Expected provider/source: ${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5838
+ `Resolution digest: ${action.rootBinding.resolutionContractDigest}`,
5839
+ `Root state: ${action.rootBinding.state}`,
5840
+ `Skill name: ${action.targetBinding.skillName}`,
5841
+ `Skill bytes digest: ${detail.proposal.skillBytesDigest}`,
5842
+ `Expected absence observed: ${action.expectedAbsence.observedAt}`,
5843
+ "Bundle absent: yes",
5844
+ "Skill file absent: yes",
5845
+ "Flat Skill file absent: yes"
5846
+ ].join("\n");
5847
+ if (action.kind === "MERGE") return [
5848
+ `Root contract: ${action.rootBinding.rootContractVersion}`,
5849
+ `Root resolver: ${action.rootBinding.resolverVersion}`,
5850
+ `Expected provider/source: ${action.rootBinding.expectedProvider} / ${action.rootBinding.expectedSource}`,
5851
+ `Resolution digest: ${action.rootBinding.resolutionContractDigest}`,
5852
+ `Root state: ${action.rootBinding.state}`,
5853
+ `Target identity: ${action.baseBinding.candidateKey}`,
5854
+ `Skill name: ${action.targetBinding.skillName}`,
5855
+ `Base digest: ${action.baseBinding.bytesDigest}`,
5856
+ `Base observed: ${action.baseBinding.observedAt}`
5857
+ ].join("\n");
5858
+ return [
5859
+ `Covering candidate: ${action.coveringCandidateBinding.candidateKey}`,
5860
+ `Name: ${action.coveringCandidateBinding.name}`,
5861
+ `Source: ${action.coveringCandidateBinding.source}`,
5862
+ `Digest: ${action.coveringCandidateBinding.contentDigest}`,
5863
+ `Observed: ${action.coveringCandidateBinding.observedAt}`
5864
+ ].join("\n");
5610
5865
  }
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 ? "数据清理未完成;已建立的清理边界继续隐藏,可重试。" : "数据清理暂不可用;未确认已建立清理边界,请稍后重试。";
5866
+ function proposalDetailAction(detail, mutationPending) {
5867
+ if (mutationPending) return "NONE";
5868
+ if (detail.reviewDecision === "PENDING" && detail.processingState === "READY_FOR_REVIEW" && detail.publicationOutcome === "PENDING_REVIEW") return "REVIEW";
5869
+ if (detail.reviewDecision === "APPROVED" && detail.processingState === "NEEDS_ATTENTION" && detail.publicationOutcome === "PUBLISH_FAILED") return "RETRY_PUBLICATION";
5870
+ return "NONE";
5619
5871
  }
5620
- const initialState = {
5621
- statusPhase: "LOADING",
5622
- previewPending: false,
5623
- mutationPending: false,
5624
- announcement: ""
5625
- };
5626
- var PurgeSettingsController = class {
5627
- call;
5628
- getWorkspaceId;
5629
- environment;
5630
- #listeners = /* @__PURE__ */ new Set();
5631
- #state = initialState;
5632
- #started = false;
5633
- #disposed = false;
5634
- #pending;
5635
- #abort;
5636
- #statusPending;
5637
- #statusAbort;
5638
- #timer;
5639
- #timerDelay;
5640
- #removeVisibility;
5641
- constructor(call, getWorkspaceId, environment = browserEnvironment()) {
5642
- this.call = call;
5643
- this.getWorkspaceId = getWorkspaceId;
5644
- this.environment = environment;
5645
- }
5646
- snapshot = () => this.#state;
5647
- subscribe = (listener) => {
5648
- this.#listeners.add(listener);
5649
- return () => {
5650
- this.#listeners.delete(listener);
5651
- };
5652
- };
5653
- start() {
5654
- if (this.#started || this.#disposed) return;
5655
- this.#started = true;
5656
- this.#removeVisibility = this.environment.onVisibilityChange(() => {
5657
- if (this.environment.isVisible()) {
5658
- this.#schedule();
5659
- this.#refreshStatus();
5660
- } else this.#unschedule();
5661
- });
5662
- if (this.environment.isVisible()) {
5663
- this.#schedule();
5664
- this.#refreshStatus();
5872
+ function ProposalDetailView(props) {
5873
+ const { detail, mutationPending } = props;
5874
+ const proposal = detail.proposal;
5875
+ const action = proposalDetailAction(detail, mutationPending);
5876
+ const actionable = action === "REVIEW";
5877
+ const coordinate = detail.sessionCoordinate;
5878
+ const baseBytes = proposal.actionBinding.kind === "MERGE" ? proposal.actionBinding.baseBinding.exactBytes : void 0;
5879
+ const coveringBytes = proposal.actionBinding.kind === "DISCARD" ? proposal.actionBinding.coveringCandidateBinding.content : void 0;
5880
+ const diff = baseBytes === void 0 ? [] : makeExactLineDiff(baseBytes, proposal.exactSkillBytes);
5881
+ 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, `PROJECT · ${proposal.workspaceBinding.workspaceId}`)), proposal.dshHomeBinding === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("dt", null, "DSH Home"), (0, react.createElement)("dd", null, `USER · ${proposal.dshHomeBinding.resolutionKind}`), (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, {
5882
+ value: evidence.excerpt,
5883
+ mode: "SAFE",
5884
+ label: `Evidence ${String(evidence.messageSeq)}`
5885
+ }))), (0, react.createElement)("h4", null, "绑定事实"), (0, react.createElement)(ProposalTextView, {
5886
+ value: factsFromAction(detail),
5887
+ mode: "SAFE",
5888
+ label: "绑定事实"
5889
+ }), (0, react.createElement)("div", {
5890
+ role: "group",
5891
+ "aria-label": "内容显示方式",
5892
+ className: _run2skill_css_run2skill_settings_page_module_css_default.modeGroup
5893
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
5894
+ "aria-pressed": props.textMode === "SAFE",
5895
+ active: props.textMode === "SAFE",
5896
+ onClick: () => {
5897
+ props.setTextMode("SAFE");
5665
5898
  }
5666
- }
5667
- whenIdle() {
5668
- return Promise.all([this.#pending ?? Promise.resolve(), this.#statusPending ?? Promise.resolve()]).then(() => void 0);
5669
- }
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" };
5675
- this.#publish({
5676
- ...this.#state,
5677
- error,
5678
- announcement: errorAnnouncement(error)
5679
- });
5680
- return;
5899
+ }, "安全视图"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
5900
+ "aria-pressed": props.textMode === "RAW",
5901
+ active: props.textMode === "RAW",
5902
+ onClick: () => {
5903
+ props.setTextMode("RAW");
5681
5904
  }
5682
- await this.#execute(async (signal) => {
5683
- this.#publish({
5684
- ...this.#state,
5685
- previewPending: true,
5686
- previewScope: scope,
5687
- preview: void 0,
5688
- error: void 0,
5689
- announcement: ""
5690
- });
5691
- const preview = parseValue(previewSchema, await this.call("purge/preview", scope === "PROJECT" ? {
5692
- apiVersion: 1,
5693
- scope,
5694
- workspaceId
5695
- } : {
5696
- apiVersion: 1,
5697
- scope
5698
- }, signal));
5699
- this.#publish({
5700
- ...this.#state,
5701
- previewPending: false,
5702
- preview,
5703
- error: void 0
5704
- });
5705
- }, (error) => {
5706
- this.#publish({
5707
- ...this.#state,
5708
- previewPending: false,
5709
- preview: void 0,
5710
- error,
5711
- announcement: errorAnnouncement(error)
5712
- });
5713
- });
5714
- }
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;
5729
- await this.#execute(async (signal) => {
5730
- this.#publish({
5731
- ...this.#state,
5732
- mutationPending: true,
5733
- error: void 0,
5734
- announcement: ""
5735
- });
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) => {
5743
- this.#publish({
5744
- ...this.#state,
5745
- 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: "正在重试数据清理。"
5763
- });
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) => {
5770
- this.#publish({
5771
- ...this.#state,
5772
- mutationPending: false,
5773
- error,
5774
- announcement: errorAnnouncement(error, true)
5775
- });
5776
- await this.#readStatusAfterFailure(error, true);
5777
- });
5778
- }
5779
- dispose() {
5780
- if (this.#disposed) return;
5781
- this.#disposed = true;
5782
- this.#unschedule();
5783
- this.#removeVisibility?.();
5784
- this.#removeVisibility = void 0;
5785
- this.#abort?.abort();
5786
- this.#statusAbort?.abort();
5787
- this.#listeners.clear();
5788
- }
5789
- #acceptReceipt(scope, receipt) {
5790
- if (receipt.state === "COMPLETED") {
5791
- this.#publish({
5792
- ...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)
5804
- });
5805
- return;
5905
+ }, "原始内容")), (0, react.createElement)("h4", null, "将要批准的完整 SKILL.md"), (0, react.createElement)(ProposalTextView, {
5906
+ value: proposal.exactSkillBytes,
5907
+ mode: props.textMode,
5908
+ label: "将要批准的完整 SKILL.md"
5909
+ }), baseBytes === void 0 ? null : (0, react.createElement)(react.Fragment, null, (0, react.createElement)("h4", null, "MERGE Base"), (0, react.createElement)(ProposalTextView, {
5910
+ value: baseBytes,
5911
+ mode: props.textMode,
5912
+ label: "MERGE Base"
5913
+ }), (0, react.createElement)("h4", null, "精确 Diff"), (0, react.createElement)("pre", { "aria-label": "精确 Diff" }, 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, {
5914
+ value: coveringBytes,
5915
+ mode: props.textMode,
5916
+ label: "覆盖已有 Skill 的完整内容"
5917
+ })), (0, react.createElement)("div", {
5918
+ role: "group",
5919
+ "aria-label": "Proposal 操作",
5920
+ className: _run2skill_css_run2skill_settings_page_module_css_default.actions
5921
+ }, action === "RETRY_PUBLICATION" ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5922
+ variant: "primary",
5923
+ disabled: mutationPending,
5924
+ onClick: props.onRetry
5925
+ }, "重试发布") : proposal.kind === "DISCARD" ? (0, react.createElement)(react.Fragment, null, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5926
+ variant: "primary",
5927
+ disabled: !actionable,
5928
+ onClick: props.onConfirmDiscard
5929
+ }, "确认无需新建 Skill"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5930
+ variant: "outline",
5931
+ disabled: !actionable,
5932
+ onClick: props.onRetry
5933
+ }, "不同意,重新分析一次")) : (0, react.createElement)(react.Fragment, null, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5934
+ variant: "primary",
5935
+ disabled: !actionable,
5936
+ onClick: props.onApprove
5937
+ }, detail.processingState === "PUBLISHING" ? "正在发布…" : "批准并发布"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5938
+ variant: "outline",
5939
+ disabled: !actionable,
5940
+ onClick: (event) => {
5941
+ props.onReject(event.currentTarget);
5806
5942
  }
5807
- this.#publish({
5808
- ...this.#state,
5809
- mutationPending: false,
5810
- preview: void 0,
5811
- inProgressReceipt: receipt,
5812
- announcement: `run2skill 数据清理进行中:${receipt.phase ?? "HIDING"}。`
5813
- });
5814
- }
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({
5819
- ...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")
5824
- });
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;
5847
- this.#publish({
5848
- ...this.#state,
5849
- statusPhase: this.#state.status === void 0 ? "UNAVAILABLE" : "STALE"
5850
- });
5851
- }
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
- });
5858
- }
5859
- async #execute(task, onError) {
5860
- if (this.#disposed || this.#pending !== void 0) return this.#pending ?? Promise.resolve();
5861
- const abort = new AbortController();
5862
- this.#abort = abort;
5863
- const pending = (async () => {
5864
- try {
5865
- 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" });
5868
- }
5869
- })();
5870
- this.#pending = pending;
5871
- await pending.finally(() => {
5872
- if (this.#pending === pending) this.#pending = void 0;
5873
- if (this.#abort === abort) this.#abort = void 0;
5874
- });
5875
- }
5876
- #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;
5879
- if (this.#timer !== void 0 && this.#timerDelay === delay) return;
5880
- this.#unschedule();
5881
- this.#timerDelay = delay;
5882
- this.#timer = this.environment.setInterval(() => {
5883
- this.#refreshStatus();
5884
- }, delay);
5885
- }
5886
- #unschedule() {
5887
- if (this.#timer === void 0) return;
5888
- this.environment.clearInterval(this.#timer);
5889
- this.#timer = void 0;
5890
- this.#timerDelay = void 0;
5891
- }
5892
- #publish(state) {
5893
- if (this.#disposed) return;
5894
- this.#state = state;
5895
- if (this.#started && this.environment.isVisible()) this.#schedule();
5896
- for (const listener of this.#listeners) listener();
5897
- }
5898
- };
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)} 条;存在时确认会安全拒绝`));
5943
+ }, "拒绝 Proposal"))));
5908
5944
  }
5909
- function PurgeConfirmationDialog(props) {
5945
+ function RejectConfirmationBody(props) {
5946
+ 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", {
5947
+ type: "button",
5948
+ "data-initial-focus": true,
5949
+ disabled: props.disabled,
5950
+ onClick: props.onCancel
5951
+ }, "取消"), (0, react.createElement)("button", {
5952
+ type: "button",
5953
+ disabled: props.disabled,
5954
+ onClick: props.onConfirm
5955
+ }, "确认拒绝"));
5956
+ }
5957
+ function RejectConfirmation(props) {
5910
5958
  const dialogRef = (0, react.useRef)(null);
5911
- const preview = props.state.preview;
5912
- const scope = props.state.previewScope;
5913
5959
  (0, react.useEffect)(() => {
5914
- if (preview === void 0 || typeof document === "undefined") return;
5960
+ if (typeof document === "undefined") return;
5961
+ const previous = document.activeElement;
5915
5962
  const dialog = dialogRef.current;
5916
5963
  const initial = dialog?.querySelector("[data-initial-focus]");
5917
5964
  const recapture = (event) => {
@@ -5921,205 +5968,765 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5921
5968
  initial?.focus();
5922
5969
  return () => {
5923
5970
  document.removeEventListener("focusin", recapture, true);
5924
- props.restoreFocusRef.current?.focus();
5971
+ previous?.focus();
5925
5972
  };
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", {
5973
+ }, []);
5974
+ return (0, react.createElement)("div", {
5939
5975
  ref: dialogRef,
5940
5976
  role: "alertdialog",
5941
5977
  "aria-modal": true,
5942
- "aria-labelledby": titleId,
5943
- "aria-describedby": descriptionId,
5978
+ "aria-labelledby": "run2skill-reject-title",
5979
+ "aria-describedby": "run2skill-reject-description",
5944
5980
  onKeyDown: (event) => {
5981
+ event.stopPropagation();
5945
5982
  if (event.key === "Escape") {
5946
5983
  event.preventDefault();
5947
- event.stopPropagation();
5948
- props.controller.cancelPreview();
5984
+ props.onCancel();
5949
5985
  } else if (dialogRef.current !== null) trapDialogTab(event.key, event.shiftKey, () => {
5950
5986
  event.preventDefault();
5951
5987
  }, dialogRef.current, typeof document === "undefined" ? void 0 : document.activeElement);
5952
5988
  },
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"
5989
+ className: _run2skill_css_run2skill_settings_page_module_css_default.rejectOverlay
5990
+ }, (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.rejectCard }, (0, react.createElement)(RejectConfirmationBody, props)));
5991
+ }
5992
+ //#endregion
5993
+ //#region src/client/run2skill-settings-page.ts
5994
+ const learningIssueSchema = object({
5995
+ workItemId: string().regex(/^wi_[a-f0-9]{64}$/),
5996
+ workItemRevision: number().int().safe().positive(),
5997
+ createdAt: string().datetime({ offset: true }),
5998
+ updatedAt: string().datetime({ offset: true }),
5999
+ failureCode: string().min(1).max(256),
6000
+ failureDetail: string().min(1).max(256).optional(),
6001
+ retryable: boolean(),
6002
+ attempt: number().int().nonnegative().max(3),
6003
+ requestBudgetUsed: number().int().nonnegative().max(2),
6004
+ modelRoute: object({
6005
+ provider: string().min(1).max(256),
6006
+ model: string().min(1).max(256)
6007
+ }).strict().optional(),
6008
+ calls: array(object({
6009
+ requestOrdinal: number().int().positive(),
6010
+ kind: string().min(1).max(256),
6011
+ inputTokens: number().int().nonnegative().optional(),
6012
+ outputTokens: number().int().nonnegative().optional(),
6013
+ outcome: string().min(1).max(256)
6014
+ }).strict()).max(2)
6015
+ }).strict();
6016
+ const learningIssuePageSchema = object({
6017
+ ok: literal(true),
6018
+ value: object({
6019
+ apiVersion: literal(1),
6020
+ items: array(learningIssueSchema).max(20),
6021
+ nextCursor: string().regex(/^c_[1-9][0-9]*_[1-9][0-9]*_[a-f0-9]{64}$/).optional()
6022
+ }).strict()
6023
+ }).strict();
6024
+ function attentionValue(value) {
6025
+ if (value === null || typeof value !== "object" || !("ok" in value) || value.ok !== true || !("value" in value)) return;
6026
+ const projection = value.value;
6027
+ if (projection.apiVersion !== 1 || !Array.isArray(projection.actions) || !Array.isArray(projection.runtimeWarnings)) return void 0;
6028
+ return projection;
6029
+ }
6030
+ const seenAttentionKeys = /* @__PURE__ */ new Set();
6031
+ function currentScope(workspaceId, generation) {
6032
+ return workspaceId === void 0 ? {
6033
+ kind: "USER_ONLY",
6034
+ generation
6035
+ } : {
6036
+ kind: "WORKSPACE",
6037
+ generation,
6038
+ workspaceId
6039
+ };
6040
+ }
6041
+ function Run2skillAttentionToast(props) {
6042
+ const [toast, setToast] = (0, react.useState)();
6043
+ const scopeGeneration = (0, react.useRef)(0);
6044
+ (0, react.useEffect)(() => {
6045
+ const abort = new AbortController();
6046
+ let timer;
6047
+ let sequence = 0;
6048
+ const generation = ++scopeGeneration.current;
6049
+ const refresh = async () => {
6050
+ try {
6051
+ const projection = attentionValue(await props.callAttention({
6052
+ apiVersion: 1,
6053
+ currentScope: currentScope(props.workspaceId, generation),
6054
+ sessionId: props.sessionId
6055
+ }, abort.signal));
6056
+ if (projection === void 0 || abort.signal.aborted) return;
6057
+ 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));
6058
+ if (fresh.length > 0) {
6059
+ for (const key of fresh) seenAttentionKeys.add(key);
6060
+ sequence += 1;
6061
+ setToast({
6062
+ sequence,
6063
+ count: fresh.length
6064
+ });
6065
+ }
6066
+ } catch {} finally {
6067
+ if (!abort.signal.aborted) timer = setTimeout(() => {
6068
+ refresh();
6069
+ }, 1e4);
6070
+ }
6071
+ };
6072
+ refresh();
6073
+ return () => {
6074
+ abort.abort();
6075
+ if (timer !== void 0) clearTimeout(timer);
6076
+ };
6077
+ }, [
6078
+ props.callAttention,
6079
+ props.sessionId,
6080
+ props.workspaceId
6081
+ ]);
6082
+ if (toast === void 0) return null;
6083
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
6084
+ key: toast.sequence,
6085
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16),
6086
+ text: `run2skill 有 ${String(toast.count)} 项需要处理,请前往设置 → 插件 → run2skill`,
6087
+ onDone: () => {
6088
+ setToast(void 0);
5963
6089
  }
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,
6090
+ });
6091
+ }
6092
+ function AutomaticLearningSection(props) {
6093
+ const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot, props.controller.getSnapshot);
6094
+ const disabled = state.status !== "ready" || !state.writable || state.saving;
6095
+ 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 }, "在后台识别可复用经验并生成待审核 Proposal;关闭后仍保留明确“保存为 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, {
6096
+ variant: state.automaticLearning ? "primary" : "outline",
6097
+ "aria-pressed": state.automaticLearning === true,
6098
+ disabled,
5968
6099
  onClick: () => {
5969
- props.controller.cancelPreview();
6100
+ props.controller.setAutomaticLearning(state.automaticLearning !== true);
5970
6101
  }
5971
- }, "取消清理"), (0, react.createElement)("button", {
5972
- type: "button",
5973
- disabled: props.state.mutationPending,
6102
+ }, state.saving ? "正在保存…" : state.automaticLearning ? "自动学习已开启" : "自动学习已关闭"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "inherit-session")), state.error === void 0 ? null : (0, react.createElement)("p", {
6103
+ role: "status",
6104
+ className: _run2skill_css_run2skill_settings_page_module_css_default.danger
6105
+ }, "设置已变化,请刷新后重试。"));
6106
+ }
6107
+ function ProposalSettingsSection(props) {
6108
+ const scopeAccessRef = (0, react.useRef)({
6109
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6110
+ actions: []
6111
+ });
6112
+ scopeAccessRef.current = {
6113
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration),
6114
+ actions: props.actions.flatMap((action) => action.subjectId === void 0 || action.proposalRef === void 0 || !["REVIEW_PROPOSAL", "RETRY_PUBLICATION"].includes(action.kind ?? "") ? [] : [{
6115
+ actionKey: action.actionKey,
6116
+ subjectId: action.subjectId,
6117
+ kind: action.kind,
6118
+ proposalRef: action.proposalRef
6119
+ }])
6120
+ };
6121
+ const controller = (0, react.useMemo)(() => new ProposalInboxController(props.workspaceId ?? "__run2skill_user_only__", props.callReview, void 0, {
6122
+ attentionDriven: true,
6123
+ scopeAccess: () => scopeAccessRef.current
6124
+ }), [
6125
+ props.callReview,
6126
+ props.scopeGeneration,
6127
+ props.workspaceId
6128
+ ]);
6129
+ (0, react.useEffect)(() => {
6130
+ if (!props.active) controller.pause();
6131
+ controller.start();
6132
+ return () => {
6133
+ controller.dispose();
6134
+ };
6135
+ }, [controller]);
6136
+ (0, react.useEffect)(() => {
6137
+ if (props.active) {
6138
+ controller.resume();
6139
+ controller.open();
6140
+ } else {
6141
+ controller.pause();
6142
+ controller.close();
6143
+ }
6144
+ }, [controller, props.active]);
6145
+ const state = (0, react.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
6146
+ const [filter, setFilter] = (0, react.useState)("");
6147
+ const [textMode, setTextMode] = (0, react.useState)("SAFE");
6148
+ const [rejectConfirm, setRejectConfirm] = (0, react.useState)(false);
6149
+ const rejectTriggerRef = (0, react.useRef)(null);
6150
+ const items = actionableProposalItems(props.actions, state.items).filter((item) => `${item.name} ${item.description} ${item.kind}`.toLocaleLowerCase().includes(filter.trim().toLocaleLowerCase()));
6151
+ const selectedActionable = items.some((item) => item.proposalRef.proposalId === state.selectedProposalId);
6152
+ 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, {
6153
+ value: filter,
6154
+ placeholder: "筛选 Proposal",
6155
+ "aria-label": "筛选 Proposal",
6156
+ onChange: (event) => {
6157
+ setFilter(event.currentTarget.value);
6158
+ }
6159
+ }), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6160
+ variant: "outline",
6161
+ size: "sm",
6162
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
6163
+ onClick: () => {
6164
+ controller.open();
6165
+ }
6166
+ }, "刷新")), state.listPhase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在加载待处理事项…") : null, state.listPhase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "待处理事项暂不可用。") : null, props.attentionAvailable && props.actions.length === 0 ? (0, react.createElement)("p", null, props.workspaceId === void 0 ? "当前没有 USER 待处理事项;未选择当前项目。" : "当前 PROJECT 与 USER 没有待处理事项。") : 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, {
6167
+ variant: state.selectedProposalId === item.proposalRef.proposalId ? "primary" : "outline",
6168
+ className: _run2skill_css_run2skill_settings_page_module_css_default.proposalListButton,
6169
+ disabled: state.mutationPending || state.detailPhase === "LOADING",
6170
+ onClick: () => {
6171
+ controller.select(item.proposalRef.proposalId);
6172
+ }
6173
+ }, `${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, {
6174
+ detail: state.detail,
6175
+ textMode,
6176
+ setTextMode,
6177
+ mutationPending: state.mutationPending,
6178
+ onApprove: () => {
6179
+ controller.mutate("APPROVE").finally(props.onMutationSettled);
6180
+ },
6181
+ onReject: (trigger) => {
6182
+ rejectTriggerRef.current = trigger ?? null;
6183
+ setRejectConfirm(true);
6184
+ },
6185
+ onRetry: () => {
6186
+ controller.mutate("RETRY").finally(props.onMutationSettled);
6187
+ },
6188
+ onConfirmDiscard: () => {
6189
+ controller.mutate("CONFIRM_DISCARD").finally(props.onMutationSettled);
6190
+ }
6191
+ }))), (0, react.createElement)("p", {
6192
+ role: "status",
6193
+ "aria-live": "polite",
6194
+ "aria-atomic": true,
6195
+ className: _run2skill_css_run2skill_settings_page_module_css_default.status
6196
+ }, state.announcement), (0, react.createElement)(RejectProposalModal, {
6197
+ open: rejectConfirm,
6198
+ disabled: state.mutationPending,
6199
+ triggerRef: rejectTriggerRef,
6200
+ onClose: () => {
6201
+ setRejectConfirm(false);
6202
+ },
6203
+ onConfirm: () => {
6204
+ setRejectConfirm(false);
6205
+ controller.mutate("REJECT").finally(props.onMutationSettled);
6206
+ }
6207
+ }));
6208
+ }
6209
+ function LearningFailureSection(props) {
6210
+ const [phase, setPhase] = (0, react.useState)("IDLE");
6211
+ const [items, setItems] = (0, react.useState)([]);
6212
+ const [refreshGeneration, setRefreshGeneration] = (0, react.useState)(0);
6213
+ const [pending, setPending] = (0, react.useState)();
6214
+ const [dismiss, setDismiss] = (0, react.useState)();
6215
+ const dismissTriggerRef = (0, react.useRef)(null);
6216
+ 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]);
6217
+ (0, react.useEffect)(() => {
6218
+ if (!props.active || learningActions.size === 0) {
6219
+ setPhase("IDLE");
6220
+ setItems([]);
6221
+ return;
6222
+ }
6223
+ const abort = new AbortController();
6224
+ setPhase("LOADING");
6225
+ (async () => {
6226
+ const next = [];
6227
+ let cursor;
6228
+ for (let page = 0; page < 10; page += 1) {
6229
+ const parsed = learningIssuePageSchema.safeParse(await props.call("learning/issues/list", {
6230
+ apiVersion: 1,
6231
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration ?? 1),
6232
+ limit: 20,
6233
+ ...cursor === void 0 ? {} : { cursor }
6234
+ }, abort.signal));
6235
+ if (!parsed.success) throw new Error("invalid learning issue response");
6236
+ next.push(...parsed.data.value.items.filter((item) => learningActions.has(item.workItemId)));
6237
+ cursor = parsed.data.value.nextCursor;
6238
+ if (cursor === void 0) {
6239
+ if (!abort.signal.aborted) {
6240
+ setItems(next);
6241
+ setPhase("READY");
6242
+ }
6243
+ return;
6244
+ }
6245
+ }
6246
+ throw new Error("learning issue page limit exceeded");
6247
+ })().catch(() => {
6248
+ if (!abort.signal.aborted) {
6249
+ setItems([]);
6250
+ setPhase("ERROR");
6251
+ }
6252
+ });
6253
+ return () => {
6254
+ abort.abort();
6255
+ };
6256
+ }, [
6257
+ learningActions,
6258
+ props.active,
6259
+ props.call,
6260
+ props.scopeGeneration,
6261
+ props.workspaceId,
6262
+ refreshGeneration
6263
+ ]);
6264
+ const mutate = (endpoint, item) => {
6265
+ if (pending !== void 0) return;
6266
+ const selectedAction = learningActions.get(item.workItemId);
6267
+ if (selectedAction?.subjectId === void 0 || selectedAction.kind === void 0) return;
6268
+ setPending(item.workItemId);
6269
+ props.call(endpoint, {
6270
+ apiVersion: 1,
6271
+ workItemId: item.workItemId,
6272
+ workItemRevision: item.workItemRevision,
6273
+ currentScope: currentScope(props.workspaceId, props.scopeGeneration ?? 1),
6274
+ action: {
6275
+ actionKey: selectedAction.actionKey,
6276
+ subjectId: selectedAction.subjectId,
6277
+ kind: selectedAction.kind,
6278
+ ...selectedAction.proposalRef === void 0 ? {} : { proposalRef: selectedAction.proposalRef }
6279
+ },
6280
+ ...endpoint === "learning/issues/dismiss" ? { confirm: true } : {}
6281
+ }, new AbortController().signal).then((result) => {
6282
+ if (result === null || typeof result !== "object" || !("ok" in result) || result.ok !== true) throw new Error("learning issue mutation rejected");
6283
+ setItems((current) => current.filter((candidate) => candidate.workItemId !== item.workItemId));
6284
+ props.onMutationSettled();
6285
+ }).catch(() => {
6286
+ setPhase("ERROR");
6287
+ }).finally(() => {
6288
+ setPending(void 0);
6289
+ });
6290
+ };
6291
+ if (learningActions.size === 0) return (0, react.createElement)(react.Fragment);
6292
+ return (0, react.createElement)("section", {
6293
+ className: _run2skill_css_run2skill_settings_page_module_css_default.sectionBody,
6294
+ "aria-label": "学习失败恢复"
6295
+ }, (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, {
6296
+ variant: "outline",
6297
+ size: "sm",
6298
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
6299
+ disabled: !props.active || pending !== void 0,
5974
6300
  onClick: () => {
5975
- props.controller.confirm();
6301
+ setRefreshGeneration((value) => value + 1);
6302
+ }
6303
+ }, "刷新失败详情")), phase === "LOADING" ? (0, react.createElement)("p", { role: "status" }, "正在加载学习失败详情…") : null, phase === "ERROR" ? (0, react.createElement)("p", { role: "alert" }, "学习失败详情暂不可用,请保持 DSH 运行并稍后重试。") : null, ...items.map((item) => {
6304
+ const action = learningActions.get(item.workItemId);
6305
+ const busy = pending === item.workItemId;
6306
+ return (0, react.createElement)("article", {
6307
+ className: _run2skill_css_run2skill_settings_page_module_css_default.detail,
6308
+ key: item.workItemId
6309
+ }, (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, {
6310
+ variant: "primary",
6311
+ disabled: busy || pending !== void 0,
6312
+ onClick: () => {
6313
+ mutate("learning/issues/retry", item);
6314
+ }
6315
+ }, busy ? "正在重试…" : "重试学习") : null, action?.availableActions?.includes("DISMISS") === true ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6316
+ variant: "outline",
6317
+ disabled: busy || pending !== void 0,
6318
+ onClick: (event) => {
6319
+ dismissTriggerRef.current = event.currentTarget;
6320
+ setDismiss(item);
6321
+ }
6322
+ }, "关闭此失败") : null));
6323
+ }), (0, react.createElement)(ManagedConfirmationModal, {
6324
+ open: dismiss !== void 0,
6325
+ title: "确认关闭此学习失败?",
6326
+ description: "该失败会从待处理列表隐藏;已有 Skill 和 DSH Session Log 不会改变。",
6327
+ disabled: pending !== void 0,
6328
+ triggerRef: dismissTriggerRef,
6329
+ onClose: () => {
6330
+ setDismiss(void 0);
6331
+ },
6332
+ confirmLabel: "确认关闭",
6333
+ onConfirm: () => {
6334
+ const selected = dismiss;
6335
+ setDismiss(void 0);
6336
+ if (selected !== void 0) mutate("learning/issues/dismiss", selected);
6337
+ }
6338
+ }));
6339
+ }
6340
+ function RejectProposalModal(props) {
6341
+ return (0, react.createElement)(ManagedConfirmationModal, {
6342
+ ...props,
6343
+ title: "确认拒绝 Proposal?",
6344
+ description: "现有 Skill 不会改变;该 Proposal 将离开待处理队列。",
6345
+ confirmLabel: "确认拒绝"
6346
+ });
6347
+ }
6348
+ function ManagedConfirmationModal(props) {
6349
+ const contentRef = (0, react.useRef)(null);
6350
+ const onCloseRef = (0, react.useRef)(props.onClose);
6351
+ onCloseRef.current = props.onClose;
6352
+ (0, react.useEffect)(() => {
6353
+ if (!props.open || typeof document === "undefined") return;
6354
+ const dialog = contentRef.current?.closest("[role=\"dialog\"]");
6355
+ const initial = dialog?.querySelector("[autofocus]") ?? dialog?.querySelector(focusableSelector);
6356
+ const onFocus = (event) => {
6357
+ if (dialog != null && event.target instanceof Node && !dialog.contains(event.target)) initial?.focus();
6358
+ };
6359
+ const onKeyboardEvent = (event) => {
6360
+ if (dialog == null) return;
6361
+ if (event.key === "Escape") {
6362
+ event.preventDefault();
6363
+ event.stopImmediatePropagation();
6364
+ onCloseRef.current?.();
6365
+ } else if (event.key === "Tab") trapDialogTab(event.key, event.shiftKey, () => {
6366
+ event.preventDefault();
6367
+ }, dialog, document.activeElement);
6368
+ };
6369
+ document.addEventListener("focusin", onFocus, true);
6370
+ document.addEventListener("keydown", onKeyboardEvent, true);
6371
+ initial?.focus();
6372
+ return () => {
6373
+ document.removeEventListener("focusin", onFocus, true);
6374
+ document.removeEventListener("keydown", onKeyboardEvent, true);
6375
+ props.triggerRef.current?.focus();
6376
+ };
6377
+ }, [props.open, props.triggerRef]);
6378
+ return (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
6379
+ open: props.open,
6380
+ title: props.title,
6381
+ closeLabel: "取消",
6382
+ description: props.description,
6383
+ onClose: props.onClose,
6384
+ 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, {
6385
+ variant: "ghost",
6386
+ autoFocus: true,
6387
+ disabled: props.disabled,
6388
+ onClick: props.onClose
6389
+ }, "取消"), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6390
+ variant: "primary",
6391
+ disabled: props.disabled,
6392
+ onClick: props.onConfirm
6393
+ }, props.confirmLabel))
6394
+ }, (0, react.createElement)("div", { ref: contentRef }));
6395
+ }
6396
+ function actionableProposalItems(actions, items) {
6397
+ 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}`]));
6398
+ return items.filter((item) => identities.has(`${item.workItemId}\u0000${item.proposalRef.proposalId}\u0000${String(item.proposalRef.revision)}\u0000${item.proposalRef.digest}`));
6399
+ }
6400
+ function hostTabVisible(element) {
6401
+ return element !== null && element.closest("[hidden]") === null;
6402
+ }
6403
+ function useHostTabVisibility() {
6404
+ const ref = (0, react.useRef)(null);
6405
+ const [visible, setVisible] = (0, react.useState)(true);
6406
+ (0, react.useEffect)(() => {
6407
+ const update = () => {
6408
+ setVisible(hostTabVisible(ref.current));
6409
+ };
6410
+ update();
6411
+ if (ref.current === null || typeof MutationObserver === "undefined") return;
6412
+ const observer = new MutationObserver(update);
6413
+ let current = ref.current;
6414
+ while (current !== null) {
6415
+ observer.observe(current, {
6416
+ attributes: true,
6417
+ attributeFilter: ["hidden"]
6418
+ });
6419
+ current = current.parentElement;
5976
6420
  }
5977
- }, props.state.mutationPending ? "正在清理…" : "确认清理")));
6421
+ return () => {
6422
+ observer.disconnect();
6423
+ };
6424
+ }, []);
6425
+ return {
6426
+ ref,
6427
+ visible
6428
+ };
5978
6429
  }
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");
5995
- }
5996
- }, "预览并清理当前 PROJECT 数据"), (0, react.createElement)("button", {
5997
- ref: userRef,
5998
- type: "button",
5999
- disabled,
6430
+ function AttentionSettingsSummary(props) {
6431
+ const [state, setState] = (0, react.useState)({ phase: "LOADING" });
6432
+ const requestGeneration = (0, react.useRef)(0);
6433
+ const activeAbort = (0, react.useRef)();
6434
+ const refresh = (0, react.useCallback)(() => {
6435
+ activeAbort.current?.abort();
6436
+ const abort = new AbortController();
6437
+ activeAbort.current = abort;
6438
+ const generation = ++requestGeneration.current;
6439
+ setState({ phase: "LOADING" });
6440
+ props.onProjection(void 0);
6441
+ props.callAttention({
6442
+ apiVersion: 1,
6443
+ currentScope: currentScope(props.workspaceId, generation),
6444
+ ...props.sessionId === void 0 ? {} : { sessionId: props.sessionId }
6445
+ }, abort.signal).then((result) => {
6446
+ if (abort.signal.aborted || requestGeneration.current !== generation) return;
6447
+ const value = attentionValue(result);
6448
+ setState(value === void 0 ? { phase: "UNAVAILABLE" } : {
6449
+ phase: "READY",
6450
+ value
6451
+ });
6452
+ props.onProjection(value);
6453
+ }, () => {
6454
+ if (abort.signal.aborted || requestGeneration.current !== generation) return;
6455
+ setState({ phase: "UNAVAILABLE" });
6456
+ props.onProjection(void 0);
6457
+ });
6458
+ return () => {
6459
+ abort.abort();
6460
+ if (activeAbort.current === abort) activeAbort.current = void 0;
6461
+ };
6462
+ }, [
6463
+ props.callAttention,
6464
+ props.onProjection,
6465
+ props.refreshGeneration,
6466
+ props.sessionId,
6467
+ props.workspaceId
6468
+ ]);
6469
+ (0, react.useEffect)(() => refresh(), [refresh]);
6470
+ const value = state.value;
6471
+ return (0, react.createElement)("div", { className: _run2skill_css_run2skill_settings_page_module_css_default.toolbar }, state.phase === "LOADING" ? (0, react.createElement)("span", { role: "status" }, "正在读取当前事项…") : null, state.phase === "UNAVAILABLE" ? (0, react.createElement)("span", { role: "alert" }, "当前事项暂不可用,未按空队列处理。") : null, value === void 0 ? null : (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", {
6472
+ key: warning.noticeKey,
6473
+ role: "alert"
6474
+ }, warning.message ?? "存在尚未持久化的学习信号。"))), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6475
+ variant: "outline",
6476
+ size: "sm",
6477
+ icon: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16),
6000
6478
  onClick: () => {
6001
- setRestoreRef(userRef);
6002
- props.controller.preview("USER");
6479
+ refresh();
6003
6480
  }
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();
6481
+ }, "刷新状态"));
6482
+ }
6483
+ function Run2skillSettingsPage(props) {
6484
+ const liveSessionId = props.useSessions?.((state) => state.current);
6485
+ const liveWorkspaceId = props.useWorkspaces?.((state) => state.items.find((workspace) => workspace.sessionIds?.includes(liveSessionId ?? ""))?.workspaceId);
6486
+ const workspaceId = props.useSessions === void 0 ? props.workspaceId : liveWorkspaceId;
6487
+ const sessionId = props.useSessions === void 0 ? props.sessionId : liveSessionId;
6488
+ const [open, setOpen] = (0, react.useState)(() => /* @__PURE__ */ new Set(["attention"]));
6489
+ const hostTab = useHostTabVisibility();
6490
+ const [attention, setAttention] = (0, react.useState)();
6491
+ const [attentionRefresh, setAttentionRefresh] = (0, react.useState)(0);
6492
+ const [scopeGeneration, setScopeGeneration] = (0, react.useState)(1);
6493
+ (0, react.useEffect)(() => {
6494
+ setAttention(void 0);
6495
+ setScopeGeneration((value) => value + 1);
6496
+ }, [sessionId, workspaceId]);
6497
+ const disclosure = (id, title, icon, content, collapsedContent) => (0, react.createElement)("section", {
6498
+ className: _run2skill_css_run2skill_settings_page_module_css_default.section,
6499
+ key: id
6500
+ }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
6501
+ icon,
6502
+ title,
6503
+ open: open.has(id),
6504
+ expandable: true,
6505
+ expandOnRowClick: true,
6506
+ collapsedContent,
6507
+ onToggle: () => {
6508
+ setOpen((previous) => {
6509
+ const next = new Set(previous);
6510
+ if (next.has(id)) next.delete(id);
6511
+ else next.add(id);
6512
+ return next;
6513
+ });
6012
6514
  }
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
6515
+ }, content));
6516
+ return (0, react.createElement)("div", {
6517
+ ref: hostTab.ref,
6518
+ className: _run2skill_css_run2skill_settings_page_module_css_default.page,
6519
+ "data-run2skill-settings-page": true
6520
+ }, (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, {
6521
+ ...sessionId === void 0 ? {} : { sessionId },
6522
+ ...workspaceId === void 0 ? {} : { workspaceId },
6523
+ callAttention: props.callAttention,
6524
+ refreshGeneration: attentionRefresh,
6525
+ onProjection: setAttention
6526
+ }), (0, react.createElement)(ProposalSettingsSection, {
6527
+ ...workspaceId === void 0 ? {} : { workspaceId },
6528
+ callReview: props.callReview,
6529
+ active: hostTab.visible && open.has("attention"),
6530
+ actions: attention?.actions ?? [],
6531
+ attentionAvailable: attention !== void 0,
6532
+ onMutationSettled: () => {
6533
+ setAttentionRefresh((value) => value + 1);
6534
+ },
6535
+ scopeGeneration
6536
+ }), (0, react.createElement)(LearningFailureSection, {
6537
+ ...workspaceId === void 0 ? {} : { workspaceId },
6538
+ call: props.callReview,
6539
+ active: hostTab.visible && open.has("attention"),
6540
+ actions: attention?.actions ?? [],
6541
+ onMutationSettled: () => {
6542
+ setAttentionRefresh((value) => value + 1);
6543
+ },
6544
+ scopeGeneration
6545
+ }))), disclosure("activity", "最近活动", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16), (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 }, "当前版本仅展示仍保留的待处理记录;完整历史不在本 Issue 范围内。")), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Pill, null, "有界记录")), disclosure("automatic", "自动学习", (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSettingsOutline16), (0, react.createElement)(AutomaticLearningSection, { controller: props.controller })), disclosure("data", "数据管理", (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, {
6546
+ controller: props.purgeController,
6547
+ active: hostTab.visible && open.has("data")
6548
+ }))));
6549
+ }
6550
+ function currentSessionId(context) {
6551
+ return context.sessions?.list.getSnapshot().current;
6552
+ }
6553
+ function workspaceFor(context, sessionId) {
6554
+ if (sessionId === void 0) return void 0;
6555
+ return context.workspaces.list.getSnapshot().items.find((workspace) => workspace.sessionIds?.includes(sessionId))?.workspaceId;
6556
+ }
6557
+ function applyRun2skillClient(context) {
6558
+ const controller = new AutomaticLearningSettingsController(context.settingsScope.bind({ namespace: "run2skill" }));
6559
+ const callReview = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
6560
+ const callAttention = async (payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, "attention", payload, signal);
6561
+ const callPurge = async (endpoint, payload, signal) => await context.connection.rpc.call(RUN2SKILL_RPC_CHANNEL, endpoint, payload, signal);
6562
+ const purgeController = new PurgeSettingsController(callPurge, () => workspaceFor(context, currentSessionId(context)));
6563
+ context.effect(() => {
6564
+ const disposeStyle = acquireRun2skillStyle();
6565
+ return () => {
6566
+ controller.dispose();
6567
+ purgeController.dispose();
6568
+ disposeStyle();
6569
+ };
6570
+ }, "run2skill: native settings surface");
6571
+ context.slots.inject("settings.plugins.tab", () => context.slots.register({
6572
+ name: "settings.plugins.tab",
6573
+ id: "run2skill",
6574
+ order: 20,
6575
+ label: "run2skill",
6576
+ inject: () => ({
6577
+ controller,
6578
+ purgeController,
6579
+ workspaceId: workspaceFor(context, currentSessionId(context)),
6580
+ sessionId: currentSessionId(context),
6581
+ callAttention,
6582
+ callReview
6583
+ })
6584
+ }, Run2skillSettingsPage));
6585
+ context.slots.inject("conversation.session.header.actions", () => context.slots.register({
6586
+ name: "conversation.session.header.actions",
6587
+ id: "run2skill-attention",
6588
+ order: 30,
6589
+ inject: () => ({ callAttention })
6590
+ }, function HeaderAttention(props) {
6591
+ const workspaceId = props.useWorkspaces((state) => state.items.find((workspace) => workspace.sessionIds?.includes(props.sessionId))?.workspaceId);
6592
+ return (0, react.createElement)(Run2skillAttentionToast, {
6593
+ sessionId: props.sessionId,
6594
+ ...workspaceId === void 0 ? {} : { workspaceId },
6595
+ callAttention: props.callAttention
6596
+ });
6022
6597
  }));
6023
6598
  }
6024
6599
  //#endregion
6025
- //#region src/client/automatic-learning-settings.ts
6026
- var AutomaticLearningSettingsController = class {
6027
- scope;
6600
+ //#region src/client/observe-summary-poller.ts
6601
+ const OBSERVE_SUMMARY_POLL_INTERVAL_MS = 1e4;
6602
+ function browserEnvironment() {
6603
+ return {
6604
+ isVisible: () => typeof document === "undefined" || document.visibilityState === "visible",
6605
+ setInterval: (callback, delayMs) => globalThis.setInterval(callback, delayMs),
6606
+ clearInterval: (handle) => {
6607
+ globalThis.clearInterval(handle);
6608
+ },
6609
+ onFocus: (listener) => {
6610
+ if (typeof window === "undefined") return () => void 0;
6611
+ window.addEventListener("focus", listener);
6612
+ return () => {
6613
+ window.removeEventListener("focus", listener);
6614
+ };
6615
+ },
6616
+ onVisibilityChange: (listener) => {
6617
+ if (typeof document === "undefined") return () => void 0;
6618
+ document.addEventListener("visibilitychange", listener);
6619
+ return () => {
6620
+ document.removeEventListener("visibilitychange", listener);
6621
+ };
6622
+ }
6623
+ };
6624
+ }
6625
+ function parsedSummary(result) {
6626
+ if (result === null || typeof result !== "object" || !("ok" in result) || result.ok !== true) return;
6627
+ const parsed = ObserveSummaryV1Schema.safeParse("value" in result ? result.value : void 0);
6628
+ return parsed.success ? parsed.data : void 0;
6629
+ }
6630
+ var ObserveSummaryPoller = class {
6631
+ call;
6632
+ environment;
6028
6633
  #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
- });
6634
+ #state = { phase: "LOADING" };
6635
+ #started = false;
6636
+ #disposed = false;
6637
+ #timer;
6638
+ #pending;
6639
+ #abort;
6640
+ #removeFocus;
6641
+ #removeVisibility;
6642
+ constructor(call, environment = browserEnvironment()) {
6643
+ this.call = call;
6644
+ this.environment = environment;
6039
6645
  }
6040
- getSnapshot = () => this.#snapshot;
6646
+ snapshot = () => this.#state;
6041
6647
  subscribe = (listener) => {
6042
6648
  this.#listeners.add(listener);
6043
6649
  return () => {
6044
6650
  this.#listeners.delete(listener);
6045
6651
  };
6046
6652
  };
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();
6653
+ start() {
6654
+ if (this.#started || this.#disposed) return;
6655
+ this.#started = true;
6656
+ this.#removeFocus = this.environment.onFocus(() => {
6657
+ if (this.environment.isVisible()) this.#refresh();
6658
+ });
6659
+ this.#removeVisibility = this.environment.onVisibilityChange(() => {
6660
+ if (this.environment.isVisible()) {
6661
+ this.#schedule();
6662
+ this.#refresh();
6663
+ } else this.#unschedule();
6664
+ });
6665
+ if (this.environment.isVisible()) {
6666
+ this.#schedule();
6667
+ this.#refresh();
6062
6668
  }
6063
6669
  }
6670
+ whenIdle() {
6671
+ return this.#pending ?? Promise.resolve();
6672
+ }
6064
6673
  dispose() {
6065
- this.#unsubscribe();
6674
+ if (this.#disposed) return;
6675
+ this.#disposed = true;
6676
+ this.#unschedule();
6677
+ this.#removeFocus?.();
6678
+ this.#removeVisibility?.();
6679
+ this.#removeFocus = void 0;
6680
+ this.#removeVisibility = void 0;
6681
+ this.#abort?.abort();
6066
6682
  this.#listeners.clear();
6067
6683
  }
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
6684
+ #schedule() {
6685
+ if (this.#timer !== void 0 || this.#disposed) return;
6686
+ this.#timer = this.environment.setInterval(() => {
6687
+ this.#refresh();
6688
+ }, OBSERVE_SUMMARY_POLL_INTERVAL_MS);
6689
+ }
6690
+ #unschedule() {
6691
+ if (this.#timer === void 0) return;
6692
+ this.environment.clearInterval(this.#timer);
6693
+ this.#timer = void 0;
6694
+ }
6695
+ #refresh() {
6696
+ if (this.#disposed || this.#pending !== void 0 || !this.environment.isVisible()) return this.#pending ?? Promise.resolve();
6697
+ const abort = new AbortController();
6698
+ this.#abort = abort;
6699
+ const pending = (async () => {
6700
+ try {
6701
+ const summary = parsedSummary(await this.call({ apiVersion: 1 }, abort.signal));
6702
+ if (this.#disposed) return;
6703
+ if (summary === void 0) this.#markUnavailable();
6704
+ else this.#publish({
6705
+ phase: "READY",
6706
+ summary
6707
+ });
6708
+ } catch {
6709
+ if (!this.#disposed && !abort.signal.aborted) this.#markUnavailable();
6710
+ }
6711
+ })();
6712
+ this.#pending = pending;
6713
+ pending.finally(() => {
6714
+ if (this.#pending === pending) this.#pending = void 0;
6715
+ if (this.#abort === abort) this.#abort = void 0;
6076
6716
  });
6717
+ return pending;
6077
6718
  }
6078
- #publish() {
6079
- this.#snapshot = this.#project();
6719
+ #markUnavailable() {
6720
+ this.#publish("summary" in this.#state ? {
6721
+ phase: "STALE",
6722
+ summary: this.#state.summary
6723
+ } : { phase: "UNAVAILABLE" });
6724
+ }
6725
+ #publish(state) {
6726
+ this.#state = state;
6080
6727
  for (const listener of this.#listeners) listener();
6081
6728
  }
6082
6729
  };
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
6730
  //#endregion
6124
6731
  //#region src/client/index.ts
6125
6732
  const name = "run2skill-client";
@@ -6127,19 +6734,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6127
6734
  "connection",
6128
6735
  "slots",
6129
6736
  "workspaces",
6737
+ "sessions",
6130
6738
  "remote",
6131
6739
  "settingsScope"
6132
6740
  ];
6133
6741
  function apply(context) {
6134
- applyObserveSummaryClient(context);
6135
- applyAutomaticLearningSettingsClient(context);
6742
+ applyRun2skillClient(context);
6136
6743
  }
6137
6744
  //#endregion
6745
+ exports.AttentionSettingsSummary = AttentionSettingsSummary;
6138
6746
  exports.AutomaticLearningSettingsCard = AutomaticLearningSettingsCard;
6139
6747
  exports.AutomaticLearningSettingsController = AutomaticLearningSettingsController;
6748
+ exports.LearningFailureSection = LearningFailureSection;
6140
6749
  exports.OBSERVE_SUMMARY_POLL_INTERVAL_MS = OBSERVE_SUMMARY_POLL_INTERVAL_MS;
6141
- exports.ObserveHeaderAction = ObserveHeaderAction;
6142
- exports.ObserveStatusPill = ObserveStatusPill;
6143
6750
  exports.ObserveSummaryPoller = ObserveSummaryPoller;
6144
6751
  exports.PROPOSAL_ACTIVE_POLL_INTERVAL_MS = PROPOSAL_ACTIVE_POLL_INTERVAL_MS;
6145
6752
  exports.PROPOSAL_POLL_INTERVAL_MS = PROPOSAL_POLL_INTERVAL_MS;
@@ -6153,11 +6760,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6153
6760
  exports.PurgeSettingsController = PurgeSettingsController;
6154
6761
  exports.PurgeSettingsSection = PurgeSettingsSection;
6155
6762
  exports.RejectConfirmationBody = RejectConfirmationBody;
6156
- exports.Run2skillHeaderAction = Run2skillHeaderAction;
6763
+ exports.RejectProposalModal = RejectProposalModal;
6764
+ exports.Run2skillAttentionToast = Run2skillAttentionToast;
6765
+ exports.Run2skillSettingsPage = Run2skillSettingsPage;
6766
+ exports.actionableProposalItems = actionableProposalItems;
6157
6767
  exports.apply = apply;
6158
6768
  exports.applyAutomaticLearningSettingsClient = applyAutomaticLearningSettingsClient;
6159
- exports.applyObserveSummaryClient = applyObserveSummaryClient;
6160
- exports.describeObserveState = describeObserveState;
6769
+ exports.applyRun2skillClient = applyRun2skillClient;
6161
6770
  exports.describeProcessingState = describeProcessingState;
6162
6771
  exports.describeProposalListItem = describeProposalListItem;
6163
6772
  exports.describeProposalOutcome = describeProposalOutcome;
@@ -6166,10 +6775,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6166
6775
  exports.describeReviewDecision = describeReviewDecision;
6167
6776
  exports.describeRun2skillHealth = describeRun2skillHealth;
6168
6777
  exports.factsFromAction = factsFromAction;
6778
+ exports.hostTabVisible = hostTabVisible;
6169
6779
  exports.inject = inject;
6170
6780
  exports.makeExactLineDiff = makeExactLineDiff;
6171
6781
  exports.makeSafeText = makeSafeText;
6172
6782
  exports.name = name;
6783
+ exports.proposalDetailAction = proposalDetailAction;
6173
6784
  exports.proposalInboxContentBlocked = proposalInboxContentBlocked;
6174
6785
  exports.trapDialogTab = trapDialogTab;
6175
6786
  return module.exports;