hiwork-core 0.0.11 → 0.0.17
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/README.md +6 -0
- package/lib/ai-router-client.js +255 -1
- package/lib/client.js +815 -36
- package/lib/index.js +1098 -2
- package/lib/managed-skills.js +446 -0
- package/lib/office-credentials.js +113 -0
- package/lib/office-relay.js +113 -0
- package/lib/plugin-updates-rpc.js +187 -0
- package/lib/types/ai-router-client.d.ts +244 -0
- package/lib/types/client/contracts.d.ts +38 -0
- package/lib/types/client/managed-skills.d.ts +218 -0
- package/lib/types/client/managed-workbench.d.ts +212 -0
- package/lib/types/client/plugin-updates.d.ts +150 -0
- package/lib/types/client/styles.d.ts +27 -0
- package/lib/types/client/types.d.ts +27 -1
- package/lib/types/client/user-badge.d.ts +12 -2
- package/lib/types/managed-skills.d.ts +261 -0
- package/lib/types/office-credentials.d.ts +99 -0
- package/lib/types/office-relay.d.ts +105 -0
- package/lib/types/plugin-updates-rpc.d.ts +169 -0
- package/lib/types/workbench-bridge.d.ts +259 -0
- package/lib/workbench-bridge.js +447 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -90,6 +90,11 @@ var hostHideCss = [
|
|
|
90
90
|
`header [class*="moreButton"] { display: none !important; }`,
|
|
91
91
|
`[class*="settingsArea"] [aria-haspopup="dialog"] { display: none !important; }`
|
|
92
92
|
].join("\n");
|
|
93
|
+
var composerCss = [
|
|
94
|
+
`[data-composer-card]:has([data-composer-input]) { --dsw-elevation-stroke-color: var(--dsw-alias-border-l4); }`,
|
|
95
|
+
`[data-composer-card]:has([data-composer-input]):focus-within { --dsw-elevation-stroke-color: ${brandAccent}; }`,
|
|
96
|
+
`[data-composer-card]:has([data-composer-input]) > * { --dsw-elevation-stroke-color: var(--dsw-alias-border-l4); }`
|
|
97
|
+
].join("\n");
|
|
93
98
|
var brandCss = [
|
|
94
99
|
`body[data-ds-dark-theme] .hiwork-brand-wordmark { filter: invert(1) brightness(1.35); }`
|
|
95
100
|
].join("\n");
|
|
@@ -345,6 +350,146 @@ function AiRouterSection(props) {
|
|
|
345
350
|
var import_react2 = require("react");
|
|
346
351
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
347
352
|
|
|
353
|
+
// src/client/plugin-updates.ts
|
|
354
|
+
var PLUGIN_UPDATES_CHANNEL = "/hiwork-plugin-updates";
|
|
355
|
+
var PLUGIN_UPDATES_POLL_INTERVAL_MS = 5 * 6e4;
|
|
356
|
+
var PluginUpdatesRpcError = class extends Error {
|
|
357
|
+
constructor(code, message) {
|
|
358
|
+
super(message);
|
|
359
|
+
this.code = code;
|
|
360
|
+
this.name = "PluginUpdatesRpcError";
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
var GENERIC_FAILURE_MESSAGE = "\u63D2\u4EF6\u66F4\u65B0\u72B6\u6001\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
364
|
+
var INVALID_RESPONSE_MESSAGE = "\u63D2\u4EF6\u66F4\u65B0\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002";
|
|
365
|
+
function isRecord(value) {
|
|
366
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
367
|
+
}
|
|
368
|
+
function text(value) {
|
|
369
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
370
|
+
}
|
|
371
|
+
function unwrapPluginUpdatesResult(value) {
|
|
372
|
+
if (!isRecord(value) || !("ok" in value)) {
|
|
373
|
+
throw new PluginUpdatesRpcError("internal", INVALID_RESPONSE_MESSAGE);
|
|
374
|
+
}
|
|
375
|
+
if (value["ok"] === true) return value["value"];
|
|
376
|
+
if (value["ok"] === false) {
|
|
377
|
+
const error = isRecord(value["error"]) ? value["error"] : void 0;
|
|
378
|
+
const code = text(error?.["code"]) ?? "internal";
|
|
379
|
+
const message = typeof error?.["message"] === "string" && error["message"].trim() !== "" ? error["message"] : GENERIC_FAILURE_MESSAGE;
|
|
380
|
+
throw new PluginUpdatesRpcError(code, message);
|
|
381
|
+
}
|
|
382
|
+
throw new PluginUpdatesRpcError("internal", INVALID_RESPONSE_MESSAGE);
|
|
383
|
+
}
|
|
384
|
+
function toPackageUpdate(value) {
|
|
385
|
+
if (!isRecord(value)) return void 0;
|
|
386
|
+
const packageName = text(value["packageName"]);
|
|
387
|
+
if (packageName === void 0) return void 0;
|
|
388
|
+
return {
|
|
389
|
+
packageName,
|
|
390
|
+
displayName: text(value["displayName"]) ?? packageName,
|
|
391
|
+
from: text(value["from"]) ?? "",
|
|
392
|
+
to: text(value["to"]) ?? ""
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function toPluginUpdatesSnapshot(value) {
|
|
396
|
+
if (!isRecord(value)) return void 0;
|
|
397
|
+
const packages = [];
|
|
398
|
+
for (const item of Array.isArray(value["packages"]) ? value["packages"] : []) {
|
|
399
|
+
const normalized = toPackageUpdate(item);
|
|
400
|
+
if (normalized !== void 0) packages.push(normalized);
|
|
401
|
+
}
|
|
402
|
+
const revision = value["revision"];
|
|
403
|
+
const fetchedAt = value["fetchedAt"];
|
|
404
|
+
return {
|
|
405
|
+
packages,
|
|
406
|
+
fetchedAt: typeof fetchedAt === "number" && Number.isFinite(fetchedAt) ? fetchedAt : 0,
|
|
407
|
+
revision: typeof revision === "number" && Number.isFinite(revision) ? revision : null
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
var PluginUpdatesStore = class {
|
|
411
|
+
constructor(rpc) {
|
|
412
|
+
this.rpc = rpc;
|
|
413
|
+
}
|
|
414
|
+
current;
|
|
415
|
+
dismissed;
|
|
416
|
+
version = 0;
|
|
417
|
+
listeners = /* @__PURE__ */ new Set();
|
|
418
|
+
/** 最近一次成功取到的快照(同步读,可能比服务端现状旧)。没有 / 不可用时 `undefined`。 */
|
|
419
|
+
snapshot() {
|
|
420
|
+
return this.current;
|
|
421
|
+
}
|
|
422
|
+
/** 变化计数:`useSyncExternalStore` 的 `getSnapshot`(值变化即重渲染)。 */
|
|
423
|
+
getVersion = () => this.version;
|
|
424
|
+
/** 订阅变化(快照更新 / 关闭)。返回退订函数。 */
|
|
425
|
+
subscribe = (listener) => {
|
|
426
|
+
this.listeners.add(listener);
|
|
427
|
+
return () => {
|
|
428
|
+
this.listeners.delete(listener);
|
|
429
|
+
};
|
|
430
|
+
};
|
|
431
|
+
/** 这个 revision 是否已经被用户关掉过(同 revision 不再提示;换 revision 会重新提示)。 */
|
|
432
|
+
isDismissed(revision) {
|
|
433
|
+
return this.dismissed !== void 0 && this.dismissed === revision;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* 现在是否该渲染提示卡片。
|
|
437
|
+
*
|
|
438
|
+
* 三个条件缺一不可:**有快照**(不可用 / 从没取到 → 不渲染)、**`packages` 非空**
|
|
439
|
+
* (只有降级项时桌面端返回的也是空 `packages`,spec §4.5 明确不弹)、**这个 revision 没被关过**。
|
|
440
|
+
* ⚠️ 判据里**没有** `held` / `from` / `displayName`(spec §4.3 的 ⚠️)。
|
|
441
|
+
*/
|
|
442
|
+
shouldPrompt() {
|
|
443
|
+
const snapshot = this.current;
|
|
444
|
+
if (snapshot === void 0 || snapshot.packages.length === 0) return false;
|
|
445
|
+
return !this.isDismissed(snapshot.revision);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* 记住「用户关掉了这一个 revision 的提示」。**只记内存**(spec §4.5:本次会话 + 该 revision),
|
|
449
|
+
* 不落盘、不调任何 apply。
|
|
450
|
+
*/
|
|
451
|
+
dismiss() {
|
|
452
|
+
const snapshot = this.current;
|
|
453
|
+
if (snapshot === void 0) return;
|
|
454
|
+
if (this.dismissed === snapshot.revision) return;
|
|
455
|
+
this.dismissed = snapshot.revision;
|
|
456
|
+
this.bump();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 向 Host 取一次「有没有待生效的更新」。
|
|
460
|
+
*
|
|
461
|
+
* 返回 `undefined` 有两种来源,对调用方是同一件事(**不渲染**):Host 说「不可用」(`null`),
|
|
462
|
+
* 或响应形状不合法。⚠️ **失败(`ok:false` / 传输异常)一律抛错且不覆写镜像**——提示条不该
|
|
463
|
+
* 因为一次抖动而消失,而「更新已经在排队」这件事不会因为读不出来就不存在。
|
|
464
|
+
*/
|
|
465
|
+
async refresh(signal) {
|
|
466
|
+
const raw = await this.rpc.call(PLUGIN_UPDATES_CHANNEL, "snapshot", {}, signal);
|
|
467
|
+
const next = toPluginUpdatesSnapshot(unwrapPluginUpdatesResult(raw));
|
|
468
|
+
this.current = next;
|
|
469
|
+
this.bump();
|
|
470
|
+
return next;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 请求「重启并更新」。**这是全链路唯一的回收入口,且只能由用户点击到达。**
|
|
474
|
+
*
|
|
475
|
+
* 返回 `true` 表示触发**已经递下去**(最终落到主进程的 `recycleDshForPluginUpdate()`),
|
|
476
|
+
* **不是**「更新成功」——主进程会重建窗口,客户端那一刻就没了。
|
|
477
|
+
* `false` 只在「老桌面端上没有 `desktopPlugins`」时出现(Host 回 `requested: false`)。
|
|
478
|
+
*/
|
|
479
|
+
async requestRestart(signal) {
|
|
480
|
+
const raw = await this.rpc.call(PLUGIN_UPDATES_CHANNEL, "apply", {}, signal);
|
|
481
|
+
const value = unwrapPluginUpdatesResult(raw);
|
|
482
|
+
return isRecord(value) && value["requested"] === true;
|
|
483
|
+
}
|
|
484
|
+
bump() {
|
|
485
|
+
this.version += 1;
|
|
486
|
+
for (const listener of [...this.listeners]) listener();
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
function createPluginUpdates(rpc) {
|
|
490
|
+
return new PluginUpdatesStore(rpc);
|
|
491
|
+
}
|
|
492
|
+
|
|
348
493
|
// src/client/settings-navigation.ts
|
|
349
494
|
function pickSettingsSectionButton(buttons, labels) {
|
|
350
495
|
for (const label of labels) {
|
|
@@ -571,6 +716,90 @@ var confirmActionsStyle = {
|
|
|
571
716
|
gap: 8,
|
|
572
717
|
marginTop: 8
|
|
573
718
|
};
|
|
719
|
+
var UPDATES_CARD_GAP = 8;
|
|
720
|
+
var UPDATES_CARD_FALLBACK_BOTTOM = 64;
|
|
721
|
+
var UPDATES_CARD_MIN_BOTTOM = 8;
|
|
722
|
+
var updatesCardStyle = (bottom) => ({
|
|
723
|
+
position: "fixed",
|
|
724
|
+
left: 8,
|
|
725
|
+
bottom,
|
|
726
|
+
zIndex: 1200,
|
|
727
|
+
boxSizing: "border-box",
|
|
728
|
+
width: 268,
|
|
729
|
+
maxWidth: "calc(100vw - 16px)",
|
|
730
|
+
padding: "12px 12px 10px",
|
|
731
|
+
display: "flex",
|
|
732
|
+
flexDirection: "column",
|
|
733
|
+
gap: 6,
|
|
734
|
+
background: bgLayer2,
|
|
735
|
+
borderRadius: 12,
|
|
736
|
+
border: `1px solid ${borderL2}`,
|
|
737
|
+
boxShadow: "var(--dsw-shadow-lv3, 0 12px 32px rgba(15,17,21,0.18))"
|
|
738
|
+
});
|
|
739
|
+
var updatesTitleStyle = {
|
|
740
|
+
display: "flex",
|
|
741
|
+
alignItems: "center",
|
|
742
|
+
gap: 6,
|
|
743
|
+
fontSize: 13,
|
|
744
|
+
fontWeight: 600,
|
|
745
|
+
color: textPrimary
|
|
746
|
+
};
|
|
747
|
+
var updatesBodyStyle = { fontSize: 12, lineHeight: 1.6, color: textSecondary };
|
|
748
|
+
var updatesErrorStyle = { fontSize: 12, lineHeight: 1.6, color: warnText };
|
|
749
|
+
var updatesActionsStyle = {
|
|
750
|
+
display: "flex",
|
|
751
|
+
justifyContent: "flex-end",
|
|
752
|
+
gap: 8,
|
|
753
|
+
marginTop: 4
|
|
754
|
+
};
|
|
755
|
+
function PluginUpdatesCard(props) {
|
|
756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
757
|
+
"div",
|
|
758
|
+
{
|
|
759
|
+
style: updatesCardStyle(props.bottom),
|
|
760
|
+
"data-hiwork-plugin-updates": true,
|
|
761
|
+
role: "status",
|
|
762
|
+
"aria-label": "\u63D2\u4EF6\u66F4\u65B0\u63D0\u793A",
|
|
763
|
+
children: [
|
|
764
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: updatesTitleStyle, children: "\u63D2\u4EF6\u66F4\u65B0\u5DF2\u5C31\u7EEA" }),
|
|
765
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: updatesBodyStyle, children: "\u91CD\u542F\u540E\u751F\u6548\u3002" }),
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: updatesBodyStyle, children: [
|
|
767
|
+
"\u70B9\u300C\u91CD\u542F\u5E76\u66F4\u65B0\u300D\u4F1A\u7ACB\u523B\u91CD\u542F\u5E94\u7528\uFF1A",
|
|
768
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "\u5F53\u524D\u6B63\u5728\u8F93\u5165\u7684\u5185\u5BB9\u4F1A\u4E22\u5931" }),
|
|
769
|
+
"\uFF08\u4F1A\u8BDD\u8BB0\u5F55\u4FDD\u5B58\u5728\u78C1\u76D8\u4E0A\uFF0C\u4E0D\u4F1A\u4E22\uFF09\u3002"
|
|
770
|
+
] }),
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: updatesBodyStyle, children: "\u4E5F\u53EF\u4EE5\u7A0D\u540E\u624B\u52A8\u91CD\u542F\uFF1A\u9000\u51FA HiWork \u540E\u91CD\u65B0\u6253\u5F00\uFF08macOS \u6309 Cmd+Q\uFF0C\u5173\u95ED\u7A97\u53E3\u4E0D\u7B97\u9000\u51FA\uFF09\u3002" }),
|
|
772
|
+
props.restartFailed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: updatesErrorStyle, children: "\u6682\u65F6\u65E0\u6CD5\u89E6\u53D1\u91CD\u542F\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u6309\u4E0A\u9762\u7684\u65B9\u5F0F\u624B\u52A8\u9000\u51FA\u540E\u91CD\u65B0\u6253\u5F00\u3002" }) : null,
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: updatesActionsStyle, children: [
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
775
|
+
"button",
|
|
776
|
+
{
|
|
777
|
+
type: "button",
|
|
778
|
+
"data-hiwork-core-secondary": true,
|
|
779
|
+
"data-hiwork-plugin-updates-dismiss": true,
|
|
780
|
+
style: secondaryButton,
|
|
781
|
+
onClick: props.onDismiss,
|
|
782
|
+
children: "\u5173\u95ED"
|
|
783
|
+
}
|
|
784
|
+
),
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
786
|
+
"button",
|
|
787
|
+
{
|
|
788
|
+
type: "button",
|
|
789
|
+
"data-hiwork-core-primary": true,
|
|
790
|
+
"data-hiwork-plugin-updates-restart": true,
|
|
791
|
+
style: primaryButton,
|
|
792
|
+
disabled: props.restarting,
|
|
793
|
+
onClick: props.onRestart,
|
|
794
|
+
children: props.restarting ? "\u5DF2\u8BF7\u6C42\u91CD\u542F\u2026" : "\u91CD\u542F\u5E76\u66F4\u65B0"
|
|
795
|
+
}
|
|
796
|
+
)
|
|
797
|
+
] })
|
|
798
|
+
]
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
var serverUpdatesVersion = () => 0;
|
|
574
803
|
function LogoutIcon() {
|
|
575
804
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
576
805
|
"svg",
|
|
@@ -626,6 +855,41 @@ function LogoutConfirm(props) {
|
|
|
626
855
|
function UserBadge(props) {
|
|
627
856
|
const state = props.useAiRouterCard((snapshot) => snapshot);
|
|
628
857
|
const [confirming, setConfirming] = (0, import_react2.useState)(false);
|
|
858
|
+
const updates = props.pluginUpdates;
|
|
859
|
+
const [restarting, setRestarting] = (0, import_react2.useState)(false);
|
|
860
|
+
const [restartFailed, setRestartFailed] = (0, import_react2.useState)(false);
|
|
861
|
+
const [cardBottom, setCardBottom] = (0, import_react2.useState)(UPDATES_CARD_FALLBACK_BOTTOM);
|
|
862
|
+
const wideRowRef = (0, import_react2.useRef)(null);
|
|
863
|
+
const railRowRef = (0, import_react2.useRef)(null);
|
|
864
|
+
const loggedIn = state.status === "logged-in" && state.username !== void 0;
|
|
865
|
+
(0, import_react2.useSyncExternalStore)(updates.subscribe, updates.getVersion, serverUpdatesVersion);
|
|
866
|
+
(0, import_react2.useEffect)(() => {
|
|
867
|
+
if (!loggedIn) return;
|
|
868
|
+
const tick = () => {
|
|
869
|
+
void updates.refresh().catch(() => {
|
|
870
|
+
});
|
|
871
|
+
};
|
|
872
|
+
tick();
|
|
873
|
+
const timer = setInterval(tick, PLUGIN_UPDATES_POLL_INTERVAL_MS);
|
|
874
|
+
return () => {
|
|
875
|
+
clearInterval(timer);
|
|
876
|
+
};
|
|
877
|
+
}, [loggedIn, updates]);
|
|
878
|
+
const showUpdates = loggedIn && updates.shouldPrompt();
|
|
879
|
+
(0, import_react2.useLayoutEffect)(() => {
|
|
880
|
+
if (!showUpdates) return;
|
|
881
|
+
const measure = () => {
|
|
882
|
+
const node = props.wide ? wideRowRef.current : railRowRef.current;
|
|
883
|
+
if (node === null) return;
|
|
884
|
+
const rect = node.getBoundingClientRect();
|
|
885
|
+
setCardBottom(Math.max(UPDATES_CARD_MIN_BOTTOM, Math.round(window.innerHeight - rect.top + UPDATES_CARD_GAP)));
|
|
886
|
+
};
|
|
887
|
+
measure();
|
|
888
|
+
window.addEventListener("resize", measure);
|
|
889
|
+
return () => {
|
|
890
|
+
window.removeEventListener("resize", measure);
|
|
891
|
+
};
|
|
892
|
+
}, [showUpdates, props.wide]);
|
|
629
893
|
if (state.status !== "logged-in" || state.username === void 0) return null;
|
|
630
894
|
const displayName = state.displayName ?? state.username;
|
|
631
895
|
const initial = displayName.slice(0, 1).toUpperCase();
|
|
@@ -633,22 +897,51 @@ function UserBadge(props) {
|
|
|
633
897
|
setConfirming(false);
|
|
634
898
|
void props.actions.logout();
|
|
635
899
|
} }) : null;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
title: displayName,
|
|
644
|
-
"aria-label": "\u6253\u5F00\u8BBE\u7F6E",
|
|
645
|
-
onClick: openGatewaySettings,
|
|
646
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: avatarStyle(24), children: initial })
|
|
900
|
+
const onRestart = () => {
|
|
901
|
+
setRestartFailed(false);
|
|
902
|
+
setRestarting(true);
|
|
903
|
+
void updates.requestRestart().then((requested) => {
|
|
904
|
+
if (!requested) {
|
|
905
|
+
setRestarting(false);
|
|
906
|
+
setRestartFailed(true);
|
|
647
907
|
}
|
|
648
|
-
)
|
|
908
|
+
}).catch(() => {
|
|
909
|
+
setRestarting(false);
|
|
910
|
+
setRestartFailed(true);
|
|
911
|
+
});
|
|
912
|
+
};
|
|
913
|
+
const updatesCard = showUpdates ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
914
|
+
PluginUpdatesCard,
|
|
915
|
+
{
|
|
916
|
+
bottom: cardBottom,
|
|
917
|
+
restarting,
|
|
918
|
+
restartFailed,
|
|
919
|
+
onRestart,
|
|
920
|
+
onDismiss: () => {
|
|
921
|
+
updates.dismiss();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
) : null;
|
|
925
|
+
if (!props.wide) {
|
|
926
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
928
|
+
"button",
|
|
929
|
+
{
|
|
930
|
+
ref: railRowRef,
|
|
931
|
+
type: "button",
|
|
932
|
+
"data-hiwork-core-icon": true,
|
|
933
|
+
style: railRowStyle,
|
|
934
|
+
title: displayName,
|
|
935
|
+
"aria-label": "\u6253\u5F00\u8BBE\u7F6E",
|
|
936
|
+
onClick: openGatewaySettings,
|
|
937
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: avatarStyle(24), children: initial })
|
|
938
|
+
}
|
|
939
|
+
),
|
|
940
|
+
updatesCard
|
|
941
|
+
] });
|
|
649
942
|
}
|
|
650
943
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
651
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: wideRowStyle, title: displayName, children: [
|
|
944
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: wideRowRef, style: wideRowStyle, title: displayName, children: [
|
|
652
945
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
653
946
|
"button",
|
|
654
947
|
{
|
|
@@ -689,7 +982,8 @@ function UserBadge(props) {
|
|
|
689
982
|
}
|
|
690
983
|
)
|
|
691
984
|
] }),
|
|
692
|
-
confirm
|
|
985
|
+
confirm,
|
|
986
|
+
updatesCard
|
|
693
987
|
] });
|
|
694
988
|
}
|
|
695
989
|
|
|
@@ -724,7 +1018,7 @@ function BrandMark(props) {
|
|
|
724
1018
|
}
|
|
725
1019
|
var brandNameStyle = {
|
|
726
1020
|
display: "inline-flex",
|
|
727
|
-
alignItems: "
|
|
1021
|
+
alignItems: "flex-end",
|
|
728
1022
|
gap: 8,
|
|
729
1023
|
minWidth: 0
|
|
730
1024
|
};
|
|
@@ -739,7 +1033,8 @@ var versionStyle = {
|
|
|
739
1033
|
flex: "none",
|
|
740
1034
|
fontSize: 12,
|
|
741
1035
|
fontWeight: 400,
|
|
742
|
-
lineHeight: "
|
|
1036
|
+
lineHeight: "normal",
|
|
1037
|
+
marginBottom: 1,
|
|
743
1038
|
whiteSpace: "nowrap",
|
|
744
1039
|
color: textTertiary
|
|
745
1040
|
};
|
|
@@ -2870,28 +3165,28 @@ function toAssignment(value) {
|
|
|
2870
3165
|
const record = value;
|
|
2871
3166
|
const authRaw = record["auth"];
|
|
2872
3167
|
const auth = typeof authRaw === "object" && authRaw !== null ? authRaw : void 0;
|
|
2873
|
-
const assignmentId =
|
|
2874
|
-
const serverName =
|
|
2875
|
-
const url =
|
|
2876
|
-
const kind =
|
|
2877
|
-
const mintUrl =
|
|
3168
|
+
const assignmentId = text2(record["assignmentId"]);
|
|
3169
|
+
const serverName = text2(record["serverName"]);
|
|
3170
|
+
const url = text2(record["url"]);
|
|
3171
|
+
const kind = text2(auth?.["kind"]);
|
|
3172
|
+
const mintUrl = text2(auth?.["mintUrl"]);
|
|
2878
3173
|
if (assignmentId === void 0 || serverName === void 0 || url === void 0 || kind === void 0 || mintUrl === void 0) {
|
|
2879
3174
|
return void 0;
|
|
2880
3175
|
}
|
|
2881
3176
|
return {
|
|
2882
3177
|
assignmentId,
|
|
2883
3178
|
revision: typeof record["revision"] === "number" ? record["revision"] : 0,
|
|
2884
|
-
slug:
|
|
2885
|
-
displayName:
|
|
3179
|
+
slug: text2(record["slug"]) ?? serverName,
|
|
3180
|
+
displayName: text2(record["displayName"]) ?? serverName,
|
|
2886
3181
|
serverName,
|
|
2887
|
-
transport:
|
|
3182
|
+
transport: text2(record["transport"]) ?? "",
|
|
2888
3183
|
url,
|
|
2889
3184
|
extraHeaders: stringMap(record["extraHeaders"]),
|
|
2890
|
-
auth: { kind, mintUrl, revokeUrl:
|
|
2891
|
-
updatedAt:
|
|
3185
|
+
auth: { kind, mintUrl, revokeUrl: text2(auth?.["revokeUrl"]) ?? "" },
|
|
3186
|
+
updatedAt: text2(record["updatedAt"]) ?? ""
|
|
2892
3187
|
};
|
|
2893
3188
|
}
|
|
2894
|
-
function
|
|
3189
|
+
function text2(value) {
|
|
2895
3190
|
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
2896
3191
|
}
|
|
2897
3192
|
function stringMap(raw) {
|
|
@@ -2955,17 +3250,17 @@ var ManagedMcpStore = class {
|
|
|
2955
3250
|
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2956
3251
|
}
|
|
2957
3252
|
const record = value;
|
|
2958
|
-
const assertion =
|
|
2959
|
-
const mintUrl =
|
|
3253
|
+
const assertion = text2(record["assertion"]);
|
|
3254
|
+
const mintUrl = text2(record["mintUrl"]);
|
|
2960
3255
|
if (assertion === void 0 || mintUrl === void 0) {
|
|
2961
3256
|
throw new ManagedMcpRpcError("internal", "MCP \u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002");
|
|
2962
3257
|
}
|
|
2963
3258
|
return {
|
|
2964
3259
|
assertion,
|
|
2965
3260
|
mintUrl,
|
|
2966
|
-
revokeUrl:
|
|
2967
|
-
expiresAt:
|
|
2968
|
-
serverName:
|
|
3261
|
+
revokeUrl: text2(record["revokeUrl"]) ?? "",
|
|
3262
|
+
expiresAt: text2(record["expiresAt"]) ?? "",
|
|
3263
|
+
serverName: text2(record["serverName"]) ?? ""
|
|
2969
3264
|
};
|
|
2970
3265
|
}
|
|
2971
3266
|
/** 订阅镜像变化(连接器域据此重渲染 / 重排同步)。返回退订函数。 */
|
|
@@ -3004,6 +3299,464 @@ var ManagedMcpStore = class {
|
|
|
3004
3299
|
}
|
|
3005
3300
|
};
|
|
3006
3301
|
|
|
3302
|
+
// src/client/managed-skills.ts
|
|
3303
|
+
var MANAGED_SKILLS_CHANNEL = "/hiwork-managed-skills";
|
|
3304
|
+
var MANAGED_SKILLS_STALE_MS = 5 * 6e4;
|
|
3305
|
+
var ManagedSkillsRpcError = class extends Error {
|
|
3306
|
+
constructor(code, message) {
|
|
3307
|
+
super(message);
|
|
3308
|
+
this.code = code;
|
|
3309
|
+
this.name = "ManagedSkillsRpcError";
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
var GENERIC_FAILURE_MESSAGE2 = "\u6280\u80FD\u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
3313
|
+
var INVALID_RESPONSE_MESSAGE2 = "\u6280\u80FD\u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002";
|
|
3314
|
+
function unwrapManagedSkillsResult(value) {
|
|
3315
|
+
if (typeof value !== "object" || value === null || !("ok" in value)) {
|
|
3316
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3317
|
+
}
|
|
3318
|
+
const result = value;
|
|
3319
|
+
if (result.ok === true) return result.value;
|
|
3320
|
+
if (result.ok === false) {
|
|
3321
|
+
const code = typeof result.error?.code === "string" ? result.error.code : "internal";
|
|
3322
|
+
const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : GENERIC_FAILURE_MESSAGE2;
|
|
3323
|
+
throw new ManagedSkillsRpcError(code, message);
|
|
3324
|
+
}
|
|
3325
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3326
|
+
}
|
|
3327
|
+
function toSnapshot2(value) {
|
|
3328
|
+
if (typeof value !== "object" || value === null) {
|
|
3329
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3330
|
+
}
|
|
3331
|
+
const record = value;
|
|
3332
|
+
const rawList = record["assignments"];
|
|
3333
|
+
if (!Array.isArray(rawList)) {
|
|
3334
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3335
|
+
}
|
|
3336
|
+
const assignments = [];
|
|
3337
|
+
for (const item of rawList) {
|
|
3338
|
+
const normalized = toAssignment2(item);
|
|
3339
|
+
if (normalized !== void 0) assignments.push(normalized);
|
|
3340
|
+
}
|
|
3341
|
+
const lastError = record["lastError"];
|
|
3342
|
+
const snapshot = {
|
|
3343
|
+
revision: typeof record["revision"] === "number" ? record["revision"] : 0,
|
|
3344
|
+
fetchedAt: typeof record["fetchedAt"] === "string" ? record["fetchedAt"] : "",
|
|
3345
|
+
assignments,
|
|
3346
|
+
stale: record["stale"] === true
|
|
3347
|
+
};
|
|
3348
|
+
if (typeof lastError === "object" && lastError !== null) {
|
|
3349
|
+
const detail = lastError;
|
|
3350
|
+
snapshot.lastError = {
|
|
3351
|
+
code: typeof detail.code === "string" ? detail.code : "internal",
|
|
3352
|
+
message: typeof detail.message === "string" ? detail.message : GENERIC_FAILURE_MESSAGE2
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
return snapshot;
|
|
3356
|
+
}
|
|
3357
|
+
function toAssignment2(value) {
|
|
3358
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3359
|
+
const record = value;
|
|
3360
|
+
const assignmentId = text3(record["assignmentId"]);
|
|
3361
|
+
const skillName = text3(record["skillName"]);
|
|
3362
|
+
const contentSha256 = text3(record["contentSha256"]);
|
|
3363
|
+
if (assignmentId === void 0 || skillName === void 0 || contentSha256 === void 0) return void 0;
|
|
3364
|
+
return {
|
|
3365
|
+
assignmentId,
|
|
3366
|
+
revision: typeof record["revision"] === "number" ? record["revision"] : 0,
|
|
3367
|
+
slug: text3(record["slug"]) ?? skillName,
|
|
3368
|
+
displayName: text3(record["displayName"]) ?? skillName,
|
|
3369
|
+
skillName,
|
|
3370
|
+
description: text3(record["description"]) ?? "",
|
|
3371
|
+
contentSha256,
|
|
3372
|
+
sizeBytes: typeof record["sizeBytes"] === "number" ? record["sizeBytes"] : 0,
|
|
3373
|
+
version: text3(record["version"]) ?? "",
|
|
3374
|
+
updatedAt: text3(record["updatedAt"]) ?? ""
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
function text3(value) {
|
|
3378
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
3379
|
+
}
|
|
3380
|
+
var ManagedSkillsStore = class {
|
|
3381
|
+
constructor(rpc) {
|
|
3382
|
+
this.rpc = rpc;
|
|
3383
|
+
}
|
|
3384
|
+
current;
|
|
3385
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3386
|
+
/**
|
|
3387
|
+
* 在飞请求**连同它所属的世代**一起记账:只有同一世代的那条才可以被复用。
|
|
3388
|
+
*
|
|
3389
|
+
* 光记 promise 不够——`clear()`(登出 / 换账号)之后,账上那条是**上一个人**发起的,
|
|
3390
|
+
* 它的清单属于上一个人。复用它等于让新账号的同步拿到旧账号的清单,而 `refresh` 的返回值
|
|
3391
|
+
* 是技能域唯一被允许用来删本地受管技能的依据(spec §6 的唯一安全不变量)⇒ 会把新账号
|
|
3392
|
+
* 被分配的技能**硬删掉**。所以要有一个「这条账还算不算数」的判据,见 {@link clear}。
|
|
3393
|
+
*/
|
|
3394
|
+
inFlight;
|
|
3395
|
+
/** 世代号:`clear()` 每调用一次 +1,用来作废它之前发起的在飞请求。 */
|
|
3396
|
+
generation = 0;
|
|
3397
|
+
/** 本地镜像(同步)。从没成功同步过返回 undefined。**不要拿它做删除决策**(见文件头)。 */
|
|
3398
|
+
snapshot() {
|
|
3399
|
+
return this.current;
|
|
3400
|
+
}
|
|
3401
|
+
/** 本地镜像是否已经陈旧(或从未同步过)。 */
|
|
3402
|
+
isStale(ttlMs = MANAGED_SKILLS_STALE_MS, now = Date.now()) {
|
|
3403
|
+
if (this.current === void 0 || this.current.fetchedAt === "") return true;
|
|
3404
|
+
const fetchedAt = Date.parse(this.current.fetchedAt);
|
|
3405
|
+
if (Number.isNaN(fetchedAt)) return true;
|
|
3406
|
+
return now - fetchedAt >= ttlMs;
|
|
3407
|
+
}
|
|
3408
|
+
/**
|
|
3409
|
+
* 强制同步一次并返回**刚拉到的**清单。
|
|
3410
|
+
*
|
|
3411
|
+
* 返回值(而不是让人随后去读 `snapshot()`)是刻意的:只有这个返回值保证「这一次
|
|
3412
|
+
* 网络请求成功了」,用它做删除决策才是安全的;读镜像可能读到上一次的结果。
|
|
3413
|
+
* 失败时抛错且**不动镜像**——调用方据此保持现有的受管技能一条不删(spec §6 的
|
|
3414
|
+
* 唯一安全不变量)。
|
|
3415
|
+
*/
|
|
3416
|
+
async refresh(signal) {
|
|
3417
|
+
return await this.dedupe(async (generation) => {
|
|
3418
|
+
const raw = await this.rpc.call(MANAGED_SKILLS_CHANNEL, "refresh", {}, signal);
|
|
3419
|
+
return this.adopt(unwrapManagedSkillsResult(raw), generation);
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* 取一份清单,**能用 Host 的预热缓存就用**(Host 后台每 30 分钟拉一次)。
|
|
3424
|
+
*
|
|
3425
|
+
* 与 {@link refresh} 的分工:这里是「启动时先把界面填上」的便宜入口——命中缓存时
|
|
3426
|
+
* 一次网络请求都不发;Host 缓存也是冷的才会真的去拉。返回值仍保证是「某一次成功
|
|
3427
|
+
* 拉取的结果」,但**可能比服务端现状旧**,所以调用方按 `stale` / `isStale()` 判定它
|
|
3428
|
+
* 够不够权威,不够就再 `refresh()` 一次。
|
|
3429
|
+
*/
|
|
3430
|
+
async warm(signal) {
|
|
3431
|
+
return await this.dedupe(async (generation) => {
|
|
3432
|
+
const raw = await this.rpc.call(MANAGED_SKILLS_CHANNEL, "snapshot", {}, signal);
|
|
3433
|
+
return this.adopt(unwrapManagedSkillsResult(raw), generation);
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3436
|
+
/**
|
|
3437
|
+
* 取一条分配的技能包(base64 字节)。
|
|
3438
|
+
*
|
|
3439
|
+
* **本层不缓存包体、也不解码**:它是一条分配的内容,不是「服务端此刻认什么」的投影;
|
|
3440
|
+
* 缓存下来等于把每次同步装过的所有技能包都钉在内存里(base64 后每条约 5 MiB)。
|
|
3441
|
+
* 校验与落盘在技能域(spec §4.6)。
|
|
3442
|
+
*/
|
|
3443
|
+
async package(assignmentId, signal) {
|
|
3444
|
+
const raw = await this.rpc.call(MANAGED_SKILLS_CHANNEL, "package", { assignmentId }, signal);
|
|
3445
|
+
const value = unwrapManagedSkillsResult(raw);
|
|
3446
|
+
if (typeof value !== "object" || value === null) {
|
|
3447
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3448
|
+
}
|
|
3449
|
+
const record = value;
|
|
3450
|
+
const id = text3(record["assignmentId"]);
|
|
3451
|
+
const skillName = text3(record["skillName"]);
|
|
3452
|
+
const contentSha256 = text3(record["contentSha256"]);
|
|
3453
|
+
const archiveBase64 = text3(record["archiveBase64"]);
|
|
3454
|
+
if (id === void 0 || skillName === void 0 || contentSha256 === void 0 || archiveBase64 === void 0) {
|
|
3455
|
+
throw new ManagedSkillsRpcError("internal", INVALID_RESPONSE_MESSAGE2);
|
|
3456
|
+
}
|
|
3457
|
+
return { assignmentId: id, skillName, contentSha256, archiveBase64 };
|
|
3458
|
+
}
|
|
3459
|
+
/** 订阅镜像变化(技能域据此重渲染 / 重排同步)。返回退订函数。 */
|
|
3460
|
+
subscribe(listener) {
|
|
3461
|
+
this.listeners.add(listener);
|
|
3462
|
+
return () => {
|
|
3463
|
+
this.listeners.delete(listener);
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* 清空镜像(登出 / 换账号:清单是按人的,留着会串号),并**作废在飞的请求**。
|
|
3468
|
+
*
|
|
3469
|
+
* 只清镜像是不够的:清空那一刻正有一次拉取在飞时,那次拉取是**上一个人**发起的,它的
|
|
3470
|
+
* 清单属于上一个人——清空之后到来的调用者若复用了它,就会拿旧账号的清单去对账本地受管
|
|
3471
|
+
* 技能,把新账号被分配的技能全删掉(`refresh` 的返回值是唯一被允许用来删的那条路,
|
|
3472
|
+
* spec §6)。所以世代 +1:那之后的调用者一律不复用旧世代的那条,自己重新发一次 RPC。
|
|
3473
|
+
*
|
|
3474
|
+
* 用世代号而**不是**把 `inFlight` 直接抹成 undefined:旧 promise 的 `finally` 会回头把
|
|
3475
|
+
* **新**发起的那条记账清掉,于是「清空后重新发起」与「又在飞的旧请求」互相踩。世代判据
|
|
3476
|
+
* 天然避开这一格——收尾只在「自己还是账上那一条」时才发生(与 Host 侧 `src/managed-skills.ts`
|
|
3477
|
+
* 的 `load()` 同形)。
|
|
3478
|
+
*
|
|
3479
|
+
* ⚠️ 这条修复**刻意只落在技能这半**:`src/client/managed-mcp.ts` 是逐字同形的既有缺口,
|
|
3480
|
+
* **用户已裁定 MCP 那半本轮不动**。别为了「两半对齐」把这里改回原样。
|
|
3481
|
+
*/
|
|
3482
|
+
clear() {
|
|
3483
|
+
this.generation += 1;
|
|
3484
|
+
this.current = void 0;
|
|
3485
|
+
this.emit();
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* 并发去重:`warm` 与 `refresh` 撞在一起(启动同步与定时同步常常同拍)时共用一次
|
|
3489
|
+
* 请求,且**共用同一个返回值**——两边对「这一次拉到了什么」的判断必须一致。
|
|
3490
|
+
*
|
|
3491
|
+
* 合并只会让清单更旧(`warm` 可能命中 Host 缓存)、**不会让它更空**:旧清单里条目更多,
|
|
3492
|
+
* 于是删除得更少——偏差落在「该删的这次没删」这一侧,正是 spec §6 允许的那一侧。
|
|
3493
|
+
*
|
|
3494
|
+
* 上面这条讲的是**新旧次序**,管不到**身份**:并发合并还有第二个维度——「这几次调用是不是
|
|
3495
|
+
* 同一个人的」。世代号({@link clear})负责的就是这一格:换账号之后账上那条属于上一个人,
|
|
3496
|
+
* 谁都不许复用,必须各自重新发起。
|
|
3497
|
+
*
|
|
3498
|
+
* 传给 `task` 的 `generation` 是**发起这一次请求时**的世代号,由它决定这次结果能不能写进镜像
|
|
3499
|
+
* (见 {@link adopt})——晚到的旧世代结果照旧返回给它自己的调用方,但不许覆盖新世代的镜像。
|
|
3500
|
+
*/
|
|
3501
|
+
async dedupe(task) {
|
|
3502
|
+
const inFlight = this.inFlight;
|
|
3503
|
+
if (inFlight !== void 0 && inFlight.generation === this.generation) return await inFlight.task;
|
|
3504
|
+
const generation = this.generation;
|
|
3505
|
+
const promise = task(generation);
|
|
3506
|
+
this.inFlight = { generation, task: promise };
|
|
3507
|
+
try {
|
|
3508
|
+
return await promise;
|
|
3509
|
+
} finally {
|
|
3510
|
+
if (this.inFlight?.task === promise) this.inFlight = void 0;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
/**
|
|
3514
|
+
* 采纳一份成功响应:写镜像 + 通知订阅者(失败路径永远不会走到这里)。
|
|
3515
|
+
*
|
|
3516
|
+
* 世代不符的那一份**不写镜像**(但仍原样返回给调用方):它是 `clear()` 之前发起的请求,
|
|
3517
|
+
* 属于上一个人。允许它写回等于「清空之后又被上一个人的清单占住」,而换账号时下一次同步
|
|
3518
|
+
* 就靠 `snapshot()` / `isStale()` 判「要不要拉」——镜像被别人的清单占着,这一页会既不显示
|
|
3519
|
+
* 新账号的技能,也不触发同步。
|
|
3520
|
+
*/
|
|
3521
|
+
adopt(value, generation) {
|
|
3522
|
+
const snapshot = toSnapshot2(value);
|
|
3523
|
+
if (generation !== this.generation) return snapshot;
|
|
3524
|
+
this.current = snapshot;
|
|
3525
|
+
this.emit();
|
|
3526
|
+
return snapshot;
|
|
3527
|
+
}
|
|
3528
|
+
emit() {
|
|
3529
|
+
for (const listener of [...this.listeners]) listener();
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
|
|
3533
|
+
// src/client/managed-workbench.ts
|
|
3534
|
+
var MANAGED_WORKBENCH_CHANNEL = "/hiwork-workbench";
|
|
3535
|
+
var MANAGED_WORKBENCH_STALE_MS = 5 * 6e4;
|
|
3536
|
+
var ManagedWorkbenchRpcError = class extends Error {
|
|
3537
|
+
constructor(code, message) {
|
|
3538
|
+
super(message);
|
|
3539
|
+
this.code = code;
|
|
3540
|
+
this.name = "ManagedWorkbenchRpcError";
|
|
3541
|
+
}
|
|
3542
|
+
};
|
|
3543
|
+
var GENERIC_FAILURE_MESSAGE3 = "\u5DE5\u4F5C\u53F0\u5E94\u7528\u6E05\u5355\u6682\u65F6\u65E0\u6CD5\u83B7\u53D6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
3544
|
+
var INVALID_RESPONSE_MESSAGE3 = "\u5DE5\u4F5C\u53F0\u6E05\u5355\u4E3B\u673A\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002";
|
|
3545
|
+
function unwrapManagedWorkbenchResult(value) {
|
|
3546
|
+
if (typeof value !== "object" || value === null || !("ok" in value)) {
|
|
3547
|
+
throw new ManagedWorkbenchRpcError("internal", INVALID_RESPONSE_MESSAGE3);
|
|
3548
|
+
}
|
|
3549
|
+
const result = value;
|
|
3550
|
+
if (result.ok === true) return result.value;
|
|
3551
|
+
if (result.ok === false) {
|
|
3552
|
+
const code = typeof result.error?.code === "string" ? result.error.code : "internal";
|
|
3553
|
+
const message = typeof result.error?.message === "string" && result.error.message.trim() !== "" ? result.error.message : GENERIC_FAILURE_MESSAGE3;
|
|
3554
|
+
throw new ManagedWorkbenchRpcError(code, message);
|
|
3555
|
+
}
|
|
3556
|
+
throw new ManagedWorkbenchRpcError("internal", INVALID_RESPONSE_MESSAGE3);
|
|
3557
|
+
}
|
|
3558
|
+
function text4(value) {
|
|
3559
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
3560
|
+
}
|
|
3561
|
+
function isRecord2(value) {
|
|
3562
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3563
|
+
}
|
|
3564
|
+
function stringArray(value) {
|
|
3565
|
+
if (!Array.isArray(value)) return [];
|
|
3566
|
+
return value.filter((item) => typeof item === "string");
|
|
3567
|
+
}
|
|
3568
|
+
function toApp(value) {
|
|
3569
|
+
if (!isRecord2(value)) return void 0;
|
|
3570
|
+
const code = text4(value["code"]);
|
|
3571
|
+
const executor = value["executor"];
|
|
3572
|
+
if (code === void 0 || !isRecord2(executor)) return void 0;
|
|
3573
|
+
const kind = text4(executor["kind"]);
|
|
3574
|
+
if (kind === void 0) return void 0;
|
|
3575
|
+
const normalizedExecutor = { ...executor, kind };
|
|
3576
|
+
const mode = text4(executor["mode"]);
|
|
3577
|
+
if (mode === void 0) delete normalizedExecutor.mode;
|
|
3578
|
+
else normalizedExecutor.mode = mode;
|
|
3579
|
+
const category = isRecord2(value["category"]) ? value["category"] : {};
|
|
3580
|
+
const inputs = [];
|
|
3581
|
+
for (const item of Array.isArray(value["inputs"]) ? value["inputs"] : []) {
|
|
3582
|
+
if (!isRecord2(item)) continue;
|
|
3583
|
+
const key = text4(item["key"]);
|
|
3584
|
+
if (key === void 0) continue;
|
|
3585
|
+
inputs.push({
|
|
3586
|
+
...item,
|
|
3587
|
+
key,
|
|
3588
|
+
type: text4(item["type"]) ?? "",
|
|
3589
|
+
label: text4(item["label"]) ?? key,
|
|
3590
|
+
required: item["required"] === true
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
const outputs = [];
|
|
3594
|
+
for (const item of Array.isArray(value["outputs"]) ? value["outputs"] : []) {
|
|
3595
|
+
if (!isRecord2(item)) continue;
|
|
3596
|
+
const key = text4(item["key"]);
|
|
3597
|
+
if (key === void 0) continue;
|
|
3598
|
+
outputs.push({ ...item, key, type: text4(item["type"]) ?? "" });
|
|
3599
|
+
}
|
|
3600
|
+
const iconUrl = text4(value["iconUrl"]);
|
|
3601
|
+
const revision = value["revision"];
|
|
3602
|
+
return {
|
|
3603
|
+
code,
|
|
3604
|
+
displayName: text4(value["displayName"]) ?? code,
|
|
3605
|
+
summary: text4(value["summary"]) ?? "",
|
|
3606
|
+
...iconUrl === void 0 ? {} : { iconUrl },
|
|
3607
|
+
category: { code: text4(category["code"]) ?? "", name: text4(category["name"]) ?? "" },
|
|
3608
|
+
tags: stringArray(value["tags"]),
|
|
3609
|
+
featured: value["featured"] === true,
|
|
3610
|
+
revision: typeof revision === "number" && Number.isFinite(revision) ? revision : 0,
|
|
3611
|
+
executor: normalizedExecutor,
|
|
3612
|
+
inputs,
|
|
3613
|
+
outputs,
|
|
3614
|
+
effects: stringArray(value["effects"])
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3617
|
+
function toSnapshot3(value) {
|
|
3618
|
+
if (!isRecord2(value)) throw new ManagedWorkbenchRpcError("internal", INVALID_RESPONSE_MESSAGE3);
|
|
3619
|
+
const rawApps = value["apps"];
|
|
3620
|
+
if (!Array.isArray(rawApps)) throw new ManagedWorkbenchRpcError("internal", INVALID_RESPONSE_MESSAGE3);
|
|
3621
|
+
const apps = [];
|
|
3622
|
+
for (const item of rawApps) {
|
|
3623
|
+
const app = toApp(item);
|
|
3624
|
+
if (app !== void 0) apps.push(app);
|
|
3625
|
+
}
|
|
3626
|
+
const snapshot = {
|
|
3627
|
+
revision: typeof value["revision"] === "number" && Number.isFinite(value["revision"]) ? value["revision"] : 0,
|
|
3628
|
+
fetchedAt: typeof value["fetchedAt"] === "string" ? value["fetchedAt"] : "",
|
|
3629
|
+
apps,
|
|
3630
|
+
stale: value["stale"] === true
|
|
3631
|
+
};
|
|
3632
|
+
const lastError = value["lastError"];
|
|
3633
|
+
if (isRecord2(lastError)) {
|
|
3634
|
+
snapshot.lastError = {
|
|
3635
|
+
code: text4(lastError["code"]) ?? "internal",
|
|
3636
|
+
message: typeof lastError["message"] === "string" ? lastError["message"] : GENERIC_FAILURE_MESSAGE3
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
return snapshot;
|
|
3640
|
+
}
|
|
3641
|
+
var ManagedWorkbenchStore = class {
|
|
3642
|
+
constructor(rpc) {
|
|
3643
|
+
this.rpc = rpc;
|
|
3644
|
+
}
|
|
3645
|
+
current;
|
|
3646
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3647
|
+
/**
|
|
3648
|
+
* 在飞请求**连同它所属的世代**一起记账:只有同一世代的那条才可以被复用。
|
|
3649
|
+
*
|
|
3650
|
+
* 光记 promise 不够——`clear()`(登出 / 换账号)之后,账上那条是**上一个人**发起的,它的清单
|
|
3651
|
+
* 属于上一个人。复用它等于让新账号的货架显示旧账号的应用(管理员给谁分配是按人的)。
|
|
3652
|
+
* 所以要有一个「这条账还算不算数」的判据,见 {@link clear}。
|
|
3653
|
+
*/
|
|
3654
|
+
inFlight;
|
|
3655
|
+
/** 世代号:`clear()` 每调用一次 +1,用来作废它之前发起的在飞请求。 */
|
|
3656
|
+
generation = 0;
|
|
3657
|
+
/** 本地镜像(同步)。从没成功同步过返回 undefined。**不要拿它做下架决策**(见文件头)。 */
|
|
3658
|
+
snapshot() {
|
|
3659
|
+
return this.current;
|
|
3660
|
+
}
|
|
3661
|
+
/** 本地镜像是否已经陈旧(或从未同步过)。 */
|
|
3662
|
+
isStale(ttlMs = MANAGED_WORKBENCH_STALE_MS, now = Date.now()) {
|
|
3663
|
+
if (this.current === void 0 || this.current.fetchedAt === "") return true;
|
|
3664
|
+
const fetchedAt = Date.parse(this.current.fetchedAt);
|
|
3665
|
+
if (Number.isNaN(fetchedAt)) return true;
|
|
3666
|
+
return now - fetchedAt >= ttlMs;
|
|
3667
|
+
}
|
|
3668
|
+
/**
|
|
3669
|
+
* 强制同步一次并返回**刚拉到的**清单。
|
|
3670
|
+
*
|
|
3671
|
+
* 返回值(而不是让人随后去读 `snapshot()`)是刻意的:只有这个返回值保证「这一次网络请求成功了」,
|
|
3672
|
+
* 用它判断「这个应用还在不在」才是安全的;读镜像可能读到上一次的结果。失败时抛错且**不动镜像**
|
|
3673
|
+
* ——调用方据此保持货架原样(离线态显示上次成功缓存 + 「可能不是最新」)。
|
|
3674
|
+
*/
|
|
3675
|
+
async refresh(signal) {
|
|
3676
|
+
return await this.dedupe(async (generation) => {
|
|
3677
|
+
const raw = await this.rpc.call(MANAGED_WORKBENCH_CHANNEL, "refresh", {}, signal);
|
|
3678
|
+
return this.adopt(unwrapManagedWorkbenchResult(raw), generation);
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
/**
|
|
3682
|
+
* 取一份清单,**能用 Host 的预热缓存就用**(Host 后台每 30 分钟拉一次)。
|
|
3683
|
+
*
|
|
3684
|
+
* 与 {@link refresh} 的分工:这里是「挂载时先把界面填上」的便宜入口——命中缓存时一次网络请求
|
|
3685
|
+
* 都不发;Host 缓存也是冷的才会真的去拉。返回值仍保证是「某一次成功拉取的结果」,但**可能比
|
|
3686
|
+
* 服务端现状旧**,所以调用方按 `stale` / `isStale()` 判定它够不够权威,不够就再 `refresh()`。
|
|
3687
|
+
*/
|
|
3688
|
+
async warm(signal) {
|
|
3689
|
+
return await this.dedupe(async (generation) => {
|
|
3690
|
+
const raw = await this.rpc.call(MANAGED_WORKBENCH_CHANNEL, "snapshot", {}, signal);
|
|
3691
|
+
return this.adopt(unwrapManagedWorkbenchResult(raw), generation);
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
/** 订阅镜像变化(货架页据此重渲染)。返回退订函数。 */
|
|
3695
|
+
subscribe(listener) {
|
|
3696
|
+
this.listeners.add(listener);
|
|
3697
|
+
return () => {
|
|
3698
|
+
this.listeners.delete(listener);
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
/**
|
|
3702
|
+
* 清空镜像(登出 / 换账号:清单是按人的,留着会串号),并**作废在飞的请求**。
|
|
3703
|
+
*
|
|
3704
|
+
* 只清镜像是不够的:清空那一刻正有一次拉取在飞时,那次拉取是**上一个人**发起的,它的清单
|
|
3705
|
+
* 属于上一个人——清空之后到来的调用者若复用了它,新账号的货架就会显示旧账号的应用。所以
|
|
3706
|
+
* 世代 +1:那之后的调用者一律不复用旧世代的那条,自己重新发一次 RPC。
|
|
3707
|
+
*
|
|
3708
|
+
* 用世代号而**不是**把 `inFlight` 直接抹成 undefined:旧 promise 的 `finally` 会回头把**新**
|
|
3709
|
+
* 发起的那条记账清掉,于是「清空后重新发起」与「又在飞的旧请求」互相踩。世代判据天然避开
|
|
3710
|
+
* 这一格——收尾只在「自己还是账上那一条」时才发生(与 Host 侧 `src/workbench-bridge.ts`
|
|
3711
|
+
* 的 `load()` 同形,也与 `src/client/managed-skills.ts` 的同名方法逐字同款)。
|
|
3712
|
+
*/
|
|
3713
|
+
clear() {
|
|
3714
|
+
this.generation += 1;
|
|
3715
|
+
this.current = void 0;
|
|
3716
|
+
this.emit();
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* 并发去重:`warm` 与 `refresh` 撞在一起(挂载预热与手动刷新常常同拍)时共用一次请求,
|
|
3720
|
+
* 且**共用同一个返回值**——两边对「这一次拉到了什么」的判断必须一致。
|
|
3721
|
+
*
|
|
3722
|
+
* 合并只会让清单更旧(`warm` 可能命中 Host 缓存)、**不会让它更空**:旧清单里条目更多,
|
|
3723
|
+
* 于是「该消失的这次没消失」——偏差落在保守的那一侧。
|
|
3724
|
+
*
|
|
3725
|
+
* 上面这条讲的是**新旧次序**,管不到**身份**:世代号({@link clear})负责的就是这一格——
|
|
3726
|
+
* 换账号之后账上那条属于上一个人,谁都不许复用,必须各自重新发起。
|
|
3727
|
+
*/
|
|
3728
|
+
async dedupe(task) {
|
|
3729
|
+
const inFlight = this.inFlight;
|
|
3730
|
+
if (inFlight !== void 0 && inFlight.generation === this.generation) return await inFlight.task;
|
|
3731
|
+
const generation = this.generation;
|
|
3732
|
+
const promise = task(generation);
|
|
3733
|
+
this.inFlight = { generation, task: promise };
|
|
3734
|
+
try {
|
|
3735
|
+
return await promise;
|
|
3736
|
+
} finally {
|
|
3737
|
+
if (this.inFlight?.task === promise) this.inFlight = void 0;
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
/**
|
|
3741
|
+
* 采纳一份成功响应:写镜像 + 通知订阅者(失败路径永远不会走到这里)。
|
|
3742
|
+
*
|
|
3743
|
+
* 世代不符的那一份**不写镜像**(但仍原样返回给调用方):它是 `clear()` 之前发起的请求,
|
|
3744
|
+
* 属于上一个人。允许它写回等于「清空之后又被上一个人的清单占住」,而换账号时下一次同步就靠
|
|
3745
|
+
* `snapshot()` / `isStale()` 判「要不要拉」——镜像被别人的清单占着,这一页会既不显示新账号的
|
|
3746
|
+
* 应用,也不触发同步。
|
|
3747
|
+
*/
|
|
3748
|
+
adopt(value, generation) {
|
|
3749
|
+
const snapshot = toSnapshot3(value);
|
|
3750
|
+
if (generation !== this.generation) return snapshot;
|
|
3751
|
+
this.current = snapshot;
|
|
3752
|
+
this.emit();
|
|
3753
|
+
return snapshot;
|
|
3754
|
+
}
|
|
3755
|
+
emit() {
|
|
3756
|
+
for (const listener of [...this.listeners]) listener();
|
|
3757
|
+
}
|
|
3758
|
+
};
|
|
3759
|
+
|
|
3007
3760
|
// src/client/index.ts
|
|
3008
3761
|
var inject = ["slots", "locale", "remote", "remote.credentials", "settingsScope", "connection"];
|
|
3009
3762
|
var INTERACTION_STYLE_ID = "hiwork-core-interaction-styles";
|
|
@@ -3012,7 +3765,7 @@ function injectInteractionStyles() {
|
|
|
3012
3765
|
if (document.getElementById(INTERACTION_STYLE_ID) !== null) return;
|
|
3013
3766
|
const style = document.createElement("style");
|
|
3014
3767
|
style.id = INTERACTION_STYLE_ID;
|
|
3015
|
-
style.textContent = [interactionCss, brandCss, frameCss, hostHideCss].join("\n");
|
|
3768
|
+
style.textContent = [interactionCss, brandCss, frameCss, hostHideCss, composerCss].join("\n");
|
|
3016
3769
|
document.head.appendChild(style);
|
|
3017
3770
|
}
|
|
3018
3771
|
function apply(ctx) {
|
|
@@ -3042,7 +3795,10 @@ function apply(ctx) {
|
|
|
3042
3795
|
const sessionCost = new SessionCostStore(connection.rpc);
|
|
3043
3796
|
const dailyQuotes = new DailyDataStore(connection.rpc);
|
|
3044
3797
|
const managedMcp = new ManagedMcpStore(connection.rpc);
|
|
3798
|
+
const managedSkills = new ManagedSkillsStore(connection.rpc);
|
|
3799
|
+
const managedWorkbench = new ManagedWorkbenchStore(connection.rpc);
|
|
3045
3800
|
const appVersion = createAppVersion(connection.rpc);
|
|
3801
|
+
const pluginUpdates = createPluginUpdates(connection.rpc);
|
|
3046
3802
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
3047
3803
|
name: "settings.section",
|
|
3048
3804
|
id: "hiwork-usage",
|
|
@@ -3060,6 +3816,8 @@ function apply(ctx) {
|
|
|
3060
3816
|
usage.clear();
|
|
3061
3817
|
sessionCost.clear();
|
|
3062
3818
|
managedMcp.clear();
|
|
3819
|
+
managedSkills.clear();
|
|
3820
|
+
managedWorkbench.clear();
|
|
3063
3821
|
}), "hiwork-core: \u7528\u91CF\u7F13\u5B58\u968F\u8D26\u53F7 / Key \u53D8\u5316\u6E05\u7A7A");
|
|
3064
3822
|
ctx.effect(() => observeAboutNavIcon(), "hiwork-core: about nav icon");
|
|
3065
3823
|
ctx.slots.inject("sidebar.brand.mark", () => ctx.slots.register({
|
|
@@ -3074,13 +3832,13 @@ function apply(ctx) {
|
|
|
3074
3832
|
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
3075
3833
|
name: "sidebar.footer.action",
|
|
3076
3834
|
id: AI_ROUTER_NS,
|
|
3077
|
-
inject: () => controller.inject()
|
|
3835
|
+
inject: () => ({ ...controller.inject(), pluginUpdates })
|
|
3078
3836
|
}, UserBadge));
|
|
3079
3837
|
ctx.effect(() => mountLoginGate(scope, storeCredential), "hiwork-core: login gate");
|
|
3080
3838
|
ctx.effect(() => observeHeroDecorations(document, dailyQuotes), "hiwork-core: hero decorations");
|
|
3081
|
-
installFeatureCenter(ctx, managedMcp);
|
|
3839
|
+
installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench);
|
|
3082
3840
|
}
|
|
3083
|
-
function installFeatureCenter(ctx, managedMcp) {
|
|
3841
|
+
function installFeatureCenter(ctx, managedMcp, managedSkills, managedWorkbench) {
|
|
3084
3842
|
const featureRegistry = createFeatureRegistry();
|
|
3085
3843
|
let center;
|
|
3086
3844
|
const sessions = ctx.get("sessions");
|
|
@@ -3109,6 +3867,27 @@ function installFeatureCenter(ctx, managedMcp) {
|
|
|
3109
3867
|
refresh: async (signal) => await managedMcp.refresh(signal),
|
|
3110
3868
|
assertion: async (assignmentId, signal) => await managedMcp.assertion(assignmentId, signal),
|
|
3111
3869
|
subscribe: (listener) => managedMcp.subscribe(listener)
|
|
3870
|
+
},
|
|
3871
|
+
// 受管技能面:技能域(hiwork-capabilities)按这个成员判断「有没有这个能力」,老 core 上
|
|
3872
|
+
// 它是 undefined,能力中心照常工作、只是没有受管技能那一块(可选解耦,见 contracts.ts)。
|
|
3873
|
+
// 六项与 spec §4.5 逐字对应;核心不落盘、不解包、不持有会话 cookie——包体由 Host 取。
|
|
3874
|
+
managedSkills: {
|
|
3875
|
+
snapshot: () => managedSkills.snapshot(),
|
|
3876
|
+
isStale: (ttlMs) => managedSkills.isStale(ttlMs),
|
|
3877
|
+
warm: async (signal) => await managedSkills.warm(signal),
|
|
3878
|
+
refresh: async (signal) => await managedSkills.refresh(signal),
|
|
3879
|
+
package: async (assignmentId, signal) => await managedSkills.package(assignmentId, signal),
|
|
3880
|
+
subscribe: (listener) => managedSkills.subscribe(listener)
|
|
3881
|
+
},
|
|
3882
|
+
// 工作台应用清单面:工作台域(hiwork-workbench)按这个成员判断「有没有这个能力」,老 core 上
|
|
3883
|
+
// 它是 undefined,货架按「本部署尚未启用工作台」降级(spec §10 的降级矩阵)。M0 只有清单
|
|
3884
|
+
// 相关的方法——`submit` / `poll` / `cancel` / `materialize` 属 M1(见 contracts.ts)。
|
|
3885
|
+
managedWorkbench: {
|
|
3886
|
+
snapshot: () => managedWorkbench.snapshot(),
|
|
3887
|
+
isStale: (ttlMs) => managedWorkbench.isStale(ttlMs),
|
|
3888
|
+
warm: async (signal) => await managedWorkbench.warm(signal),
|
|
3889
|
+
refresh: async (signal) => await managedWorkbench.refresh(signal),
|
|
3890
|
+
subscribe: (listener) => managedWorkbench.subscribe(listener)
|
|
3112
3891
|
}
|
|
3113
3892
|
};
|
|
3114
3893
|
ctx.provide(HIWORK_FEATURE_CENTER_SERVICE, face);
|