@michengai/dsh-automation 1.0.6 → 1.0.8

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/index.js CHANGED
@@ -2707,10 +2707,10 @@ function isValidJWT(token, algorithm = null) {
2707
2707
  const tokensParts = token.split(".");
2708
2708
  if (tokensParts.length !== 3)
2709
2709
  return false;
2710
- const [header2] = tokensParts;
2711
- if (!header2)
2710
+ const [header3] = tokensParts;
2711
+ if (!header3)
2712
2712
  return false;
2713
- const parsedHeader = JSON.parse(atob(header2));
2713
+ const parsedHeader = JSON.parse(atob(header3));
2714
2714
  if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
2715
2715
  return false;
2716
2716
  if (!parsedHeader.alg)
@@ -21629,8 +21629,8 @@ async function settlesWithin(promise2, timeoutMs) {
21629
21629
  try {
21630
21630
  return await Promise.race([
21631
21631
  promise2.then(() => true, () => false),
21632
- new Promise((resolve2) => {
21633
- timer = setTimeout(() => resolve2(false), timeoutMs);
21632
+ new Promise((resolve3) => {
21633
+ timer = setTimeout(() => resolve3(false), timeoutMs);
21634
21634
  })
21635
21635
  ]);
21636
21636
  } finally {
@@ -21771,19 +21771,19 @@ async function executeAutomationRun(ctx, definition, run, config2) {
21771
21771
  let timedOut = false;
21772
21772
  let aborted2 = false;
21773
21773
  const idle = handle.agent.whenIdle();
21774
- const deadline = new Promise((resolve2) => {
21774
+ const deadline = new Promise((resolve3) => {
21775
21775
  timeout = setTimeout(() => {
21776
21776
  timedOut = true;
21777
21777
  handle?.agent.cancel({ kind: "hook", reason: "automation run timeout" });
21778
- resolve2();
21778
+ resolve3();
21779
21779
  }, config2.runTimeoutMs);
21780
21780
  });
21781
- const cancellation = new Promise((resolve2) => {
21781
+ const cancellation = new Promise((resolve3) => {
21782
21782
  if (config2.signal === void 0) return;
21783
21783
  const cancel = () => {
21784
21784
  aborted2 = true;
21785
21785
  handle?.agent.cancel({ kind: "hook", reason: "automation service disposed" });
21786
- resolve2();
21786
+ resolve3();
21787
21787
  };
21788
21788
  if (config2.signal.aborted) cancel();
21789
21789
  else {
@@ -22703,8 +22703,8 @@ var AutomationService = class _AutomationService {
22703
22703
  function readListedSessionId(item) {
22704
22704
  if (typeof item !== "object" || item === null) return void 0;
22705
22705
  const value = item;
22706
- const header2 = typeof value.header === "object" && value.header !== null ? value.header : void 0;
22707
- for (const id of [header2?.id, value.id]) {
22706
+ const header3 = typeof value.header === "object" && value.header !== null ? value.header : void 0;
22707
+ for (const id of [header3?.id, value.id]) {
22708
22708
  if (typeof id === "string" && id.trim() !== "") return id;
22709
22709
  }
22710
22710
  return void 0;
@@ -23822,6 +23822,13 @@ function registerPluginUpdater(ctx, options) {
23822
23822
  path: options.endpoint,
23823
23823
  handler: async (request, response) => {
23824
23824
  try {
23825
+ if (options.isEnabled) {
23826
+ const allowed = await options.isEnabled();
23827
+ if (!allowed) {
23828
+ json3(response, 403, { ok: false, error: "\u5728\u7EBF\u66F4\u65B0\u5DF2\u88AB\u7981\u7528\u3002" });
23829
+ return;
23830
+ }
23831
+ }
23825
23832
  const target = runtime(host);
23826
23833
  if (request.method === "GET" || request.method === "HEAD") {
23827
23834
  const payload = await status(options, target);
@@ -23871,6 +23878,275 @@ function registerPluginUpdater(ctx, options) {
23871
23878
  });
23872
23879
  }
23873
23880
 
23881
+ // src/settings-manager.ts
23882
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
23883
+ import { homedir as homedir3 } from "node:os";
23884
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
23885
+ var SETTINGS_API_HEADER = "x-michengai-client";
23886
+ var MAX_SETTINGS_BODY_BYTES = 64 * 1024;
23887
+ function resolveProfileDir(ctx) {
23888
+ try {
23889
+ return resolveUpdateRuntime(ctx).profileDir;
23890
+ } catch {
23891
+ const env = process.env;
23892
+ const home = homedir3();
23893
+ return resolve2(env.DSH_PROFILE_DIR ?? resolve2(home, ".dsh", "profiles", "web"));
23894
+ }
23895
+ }
23896
+ function header2(request, name2) {
23897
+ const value = request.headers?.[name2];
23898
+ return Array.isArray(value) ? value[0] : value;
23899
+ }
23900
+ function validateApiMutation(request) {
23901
+ const method = (request.method ?? "GET").toUpperCase();
23902
+ if (method === "GET" || method === "HEAD") return void 0;
23903
+ const marker = header2(request, SETTINGS_API_HEADER);
23904
+ if (marker !== "1") return { status: 403, error: "forbidden mutation request" };
23905
+ const contentType = header2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
23906
+ if (contentType !== "application/json") return { status: 415, error: "content-type must be application/json" };
23907
+ return void 0;
23908
+ }
23909
+ var SettingsManager = class {
23910
+ file;
23911
+ hostUpdateDisabled;
23912
+ store = {};
23913
+ constructor(options) {
23914
+ this.hostUpdateDisabled = options.hostUpdateDisabled === true;
23915
+ if (options.stateDir !== void 0 && options.stateDir !== "") {
23916
+ this.file = isAbsolute2(options.stateDir) ? join2(options.stateDir, "settings.json") : resolve2(options.stateDir, "settings.json");
23917
+ } else {
23918
+ const profileDir = resolveProfileDir(options.ctx);
23919
+ this.file = join2(profileDir, "automation-settings.json");
23920
+ }
23921
+ this.load();
23922
+ }
23923
+ load() {
23924
+ if (!existsSync3(this.file)) return;
23925
+ try {
23926
+ const content = readFileSync3(this.file, "utf8");
23927
+ const parsed = JSON.parse(content);
23928
+ if (typeof parsed === "object" && parsed !== null) {
23929
+ const item = parsed;
23930
+ this.store = {
23931
+ sidebarEntry: typeof item.sidebarEntry === "boolean" ? item.sidebarEntry : void 0,
23932
+ onlineUpdate: typeof item.onlineUpdate === "boolean" ? item.onlineUpdate : void 0,
23933
+ hideOfficialSchedule: typeof item.hideOfficialSchedule === "boolean" ? item.hideOfficialSchedule : void 0
23934
+ };
23935
+ }
23936
+ } catch {
23937
+ this.store = {};
23938
+ }
23939
+ }
23940
+ flush() {
23941
+ try {
23942
+ const dir = dirname2(this.file);
23943
+ if (!existsSync3(dir)) {
23944
+ mkdirSync(dir, { recursive: true });
23945
+ }
23946
+ writeFileSync(this.file, JSON.stringify(this.store, null, 2), "utf8");
23947
+ } catch {
23948
+ }
23949
+ }
23950
+ getSidebarEntry() {
23951
+ return this.store.sidebarEntry !== false;
23952
+ }
23953
+ async setSidebarEntry(enabled) {
23954
+ if (typeof enabled !== "boolean") throw new Error("enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
23955
+ this.store.sidebarEntry = enabled;
23956
+ this.flush();
23957
+ return enabled;
23958
+ }
23959
+ getHideOfficialSchedule() {
23960
+ return this.store.hideOfficialSchedule ?? this.getSidebarEntry();
23961
+ }
23962
+ async setHideOfficialSchedule(enabled) {
23963
+ if (typeof enabled !== "boolean") throw new Error("enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
23964
+ this.store.hideOfficialSchedule = enabled;
23965
+ this.flush();
23966
+ return enabled;
23967
+ }
23968
+ isHostUpdateDisabled() {
23969
+ return this.hostUpdateDisabled;
23970
+ }
23971
+ isUpdateEnabled() {
23972
+ if (this.hostUpdateDisabled) return false;
23973
+ return this.store.onlineUpdate !== false;
23974
+ }
23975
+ async setOnlineUpdate(enabled) {
23976
+ if (typeof enabled !== "boolean") throw new Error("enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C");
23977
+ if (this.hostUpdateDisabled) throw new Error("\u5728\u7EBF\u66F4\u65B0\u5DF2\u88AB\u5BBF\u4E3B\u914D\u7F6E\u9501\u5B9A\u7981\u7528");
23978
+ this.store.onlineUpdate = enabled;
23979
+ this.flush();
23980
+ return enabled;
23981
+ }
23982
+ };
23983
+ function registerSettingsApi(ctx, manager) {
23984
+ const host = ctx;
23985
+ if (typeof host.webServer?.register !== "function") return () => {
23986
+ };
23987
+ const json4 = (res, status2, body) => {
23988
+ res.writeHead(status2, {
23989
+ "content-type": "application/json; charset=utf-8",
23990
+ "cache-control": "no-store"
23991
+ });
23992
+ res.end(JSON.stringify(body));
23993
+ };
23994
+ const checkAuthAndMutation = (request, response) => {
23995
+ const connection = typeof ctx.get === "function" ? ctx.get("connection") : void 0;
23996
+ if (typeof connection?.requestRejection === "function") {
23997
+ const rejection = connection.requestRejection(request);
23998
+ if (rejection !== void 0) {
23999
+ json4(response, rejection, { ok: false, error: rejection === 401 ? "\u8BF7\u5148\u767B\u5F55 DSH / Sign in to DSH first" : "\u5BBF\u4E3B\u62D2\u7EDD\u6B64\u8BF7\u6C42 / Request forbidden" });
24000
+ return false;
24001
+ }
24002
+ }
24003
+ const mutationError = validateApiMutation(request);
24004
+ if (mutationError !== void 0) {
24005
+ json4(response, mutationError.status, { ok: false, error: mutationError.error });
24006
+ return false;
24007
+ }
24008
+ return true;
24009
+ };
24010
+ const readBody = (req) => new Promise((resolveBody) => {
24011
+ const chunks = [];
24012
+ let received = 0;
24013
+ let oversized = false;
24014
+ req.on("data", (chunk) => {
24015
+ received += chunk.length;
24016
+ if (received > MAX_SETTINGS_BODY_BYTES) {
24017
+ oversized = true;
24018
+ return;
24019
+ }
24020
+ chunks.push(chunk);
24021
+ });
24022
+ req.on("end", () => {
24023
+ if (oversized) {
24024
+ resolveBody({ ok: false, status: 413, error: "payload too large" });
24025
+ return;
24026
+ }
24027
+ try {
24028
+ const raw = Buffer.concat(chunks).toString("utf8");
24029
+ resolveBody({ ok: true, data: raw ? JSON.parse(raw) : {} });
24030
+ } catch {
24031
+ resolveBody({ ok: false, status: 400, error: "invalid json" });
24032
+ }
24033
+ });
24034
+ req.on("error", () => resolveBody({ ok: false, status: 400, error: "read error" }));
24035
+ });
24036
+ const offSidebar = host.webServer.register({
24037
+ kind: "exact",
24038
+ path: "/api/michengai/dsh-automation/sidebar-entry",
24039
+ handler: async (request, response) => {
24040
+ if (!checkAuthAndMutation(request, response)) return;
24041
+ const method = request.method;
24042
+ if (method === "GET" || method === "HEAD") {
24043
+ if (method === "HEAD") {
24044
+ response.writeHead(200, { "cache-control": "no-store" });
24045
+ response.end();
24046
+ return;
24047
+ }
24048
+ json4(response, 200, { ok: true, enabled: manager.getSidebarEntry() });
24049
+ return;
24050
+ }
24051
+ if (method === "POST") {
24052
+ const bodyRes = await readBody(request);
24053
+ if (!bodyRes.ok || !bodyRes.data || typeof bodyRes.data.enabled !== "boolean") {
24054
+ json4(response, bodyRes.status ?? 400, { ok: false, error: bodyRes.error ?? "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
24055
+ return;
24056
+ }
24057
+ const enabled = await manager.setSidebarEntry(bodyRes.data.enabled);
24058
+ json4(response, 200, { ok: true, enabled });
24059
+ return;
24060
+ }
24061
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
24062
+ response.end();
24063
+ }
24064
+ });
24065
+ const offUpdate = host.webServer.register({
24066
+ kind: "exact",
24067
+ path: "/api/michengai/dsh-automation/online-update",
24068
+ handler: async (request, response) => {
24069
+ if (!checkAuthAndMutation(request, response)) return;
24070
+ const method = request.method;
24071
+ if (method === "GET" || method === "HEAD") {
24072
+ if (method === "HEAD") {
24073
+ response.writeHead(200, { "cache-control": "no-store" });
24074
+ response.end();
24075
+ return;
24076
+ }
24077
+ json4(response, 200, {
24078
+ ok: true,
24079
+ enabled: manager.isUpdateEnabled(),
24080
+ locked: manager.isHostUpdateDisabled()
24081
+ });
24082
+ return;
24083
+ }
24084
+ if (method === "POST") {
24085
+ if (manager.isHostUpdateDisabled()) {
24086
+ json4(response, 403, { ok: false, error: "\u5728\u7EBF\u66F4\u65B0\u5DF2\u88AB\u5BBF\u4E3B\u914D\u7F6E\u9501\u5B9A\u7981\u7528" });
24087
+ return;
24088
+ }
24089
+ const bodyRes = await readBody(request);
24090
+ if (!bodyRes.ok || !bodyRes.data || typeof bodyRes.data.enabled !== "boolean") {
24091
+ json4(response, bodyRes.status ?? 400, { ok: false, error: bodyRes.error ?? "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
24092
+ return;
24093
+ }
24094
+ const enabled = await manager.setOnlineUpdate(bodyRes.data.enabled);
24095
+ json4(response, 200, { ok: true, enabled });
24096
+ return;
24097
+ }
24098
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
24099
+ response.end();
24100
+ }
24101
+ });
24102
+ const offOfficial = host.webServer.register({
24103
+ kind: "exact",
24104
+ path: "/api/michengai/dsh-automation/official-schedule",
24105
+ handler: async (request, response) => {
24106
+ if (!checkAuthAndMutation(request, response)) return;
24107
+ const method = request.method;
24108
+ if (method === "GET" || method === "HEAD") {
24109
+ if (method === "HEAD") {
24110
+ response.writeHead(200, { "cache-control": "no-store" });
24111
+ response.end();
24112
+ return;
24113
+ }
24114
+ json4(response, 200, {
24115
+ ok: true,
24116
+ enabled: manager.getHideOfficialSchedule()
24117
+ });
24118
+ return;
24119
+ }
24120
+ if (method === "POST") {
24121
+ const bodyRes = await readBody(request);
24122
+ if (!bodyRes.ok || !bodyRes.data || typeof bodyRes.data.enabled !== "boolean") {
24123
+ json4(response, bodyRes.status ?? 400, { ok: false, error: bodyRes.error ?? "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" });
24124
+ return;
24125
+ }
24126
+ const enabled = await manager.setHideOfficialSchedule(bodyRes.data.enabled);
24127
+ json4(response, 200, { ok: true, enabled });
24128
+ return;
24129
+ }
24130
+ response.writeHead(405, { allow: "GET, HEAD, POST" });
24131
+ response.end();
24132
+ }
24133
+ });
24134
+ return () => {
24135
+ try {
24136
+ offSidebar();
24137
+ } catch {
24138
+ }
24139
+ try {
24140
+ offUpdate();
24141
+ } catch {
24142
+ }
24143
+ try {
24144
+ offOfficial();
24145
+ } catch {
24146
+ }
24147
+ };
24148
+ }
24149
+
23874
24150
  // src/index.ts
23875
24151
  var name = "dsh-automation";
23876
24152
  var inject = [
@@ -23889,7 +24165,10 @@ var inject = [
23889
24165
  var Config = z2.object({
23890
24166
  runTimeoutMinutes: z2.number().step(1).min(1).max(1440).default(60),
23891
24167
  misfireGraceMinutes: z2.number().step(1).min(0).max(10080).default(15),
23892
- historyLimit: z2.number().step(1).min(1).max(5e3).default(200)
24168
+ historyLimit: z2.number().step(1).min(1).max(5e3).default(200),
24169
+ stateDir: z2.string(),
24170
+ updater: z2.boolean(),
24171
+ onlineUpdate: z2.boolean()
23893
24172
  });
23894
24173
  var MUTATING_TOOLS = /* @__PURE__ */ new Set([
23895
24174
  "automation_create",
@@ -23915,11 +24194,19 @@ function humanApprovalReason(toolName) {
23915
24194
  return toolName === "automation_delete" ? "\u6B64\u64CD\u4F5C\u4F1A\u6C38\u4E45\u5220\u9664\u81EA\u52A8\u5316\u5B9A\u4E49\u3002\u8FD0\u884C\u5386\u53F2\u4F1A\u4FDD\u7559\uFF0C\u4F46\u8BA1\u5212\u65E0\u6CD5\u81EA\u52A8\u6062\u590D\u3002" : "\u6B64\u64CD\u4F5C\u4F1A\u521B\u5EFA\u6216\u6269\u5927\u65E0\u4EBA\u503C\u5B88\u7684\u672A\u6765\u5DE5\u4F5C\u3002\u8BF7\u6838\u5BF9\u4EFB\u52A1\u8BF4\u660E\u3001\u8BA1\u5212\u3001\u5DE5\u4F5C\u533A\u548C\u6743\u9650\u8FB9\u754C\u3002";
23916
24195
  }
23917
24196
  async function apply(ctx, rawConfig) {
24197
+ const hostUpdateDisabled = rawConfig.updater === false || rawConfig.onlineUpdate === false;
24198
+ const settingsManager = new SettingsManager({
24199
+ ctx,
24200
+ ...rawConfig.stateDir !== void 0 ? { stateDir: rawConfig.stateDir } : {},
24201
+ hostUpdateDisabled
24202
+ });
23918
24203
  ctx.effect(() => registerPluginUpdater(ctx, {
23919
24204
  endpoint: "/api/michengai/dsh-automation/update",
23920
24205
  packageName: "@michengai/dsh-automation",
23921
- manifestUrl: new URL("../package.json", import.meta.url)
24206
+ manifestUrl: new URL("../package.json", import.meta.url),
24207
+ isEnabled: async () => !hostUpdateDisabled && settingsManager.isUpdateEnabled()
23922
24208
  }), "dsh-automation: plugin updater");
24209
+ ctx.effect(() => registerSettingsApi(ctx, settingsManager), "dsh-automation: settings api");
23923
24210
  const config2 = rawConfig;
23924
24211
  await ctx.effect(async () => {
23925
24212
  let alive = true;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * API 基址解析:把插件自己的根绝对路径端点解析到页面实际所在的 base 之下。
3
+ *
4
+ * 为什么需要:DSH 可能被挂在带路径前缀的反向代理后面(例如 https://host/app/dsh/,
5
+ * 网关转发前剥掉前缀)。此时浏览器把根绝对路径 "/api/..." 解析到站点根,前缀丢失,
6
+ * 网关返回自己的 HTML 404,面板就报「服务端返回非 JSON 响应(HTTP 404)」。
7
+ *
8
+ * 为什么这么解析:宿主前端同样以页面 base 解析 API 路径(参见 dsh-client-connection 的
9
+ * openConnectionWebSocket 与 dsh-client-file-upload 的 new URL(path, document.baseURI)),
10
+ * 这里沿用同一约定——先去掉前导 "/",再用 new URL 相对解析;若直接 new URL("/api/x", base)
11
+ * 会丢弃 base 的路径段,前缀就没了。
12
+ */
13
+ /**
14
+ * 解析插件端点 URL。
15
+ *
16
+ * @param path 端点路径,通常是 "/api/michengai/dsh-automation/xxx" 这类根绝对路径。
17
+ * @returns 基于页面 base 的绝对 URL;相对路径、完整 URL、协议相对 URL、无 document
18
+ * 或 base 非法时原样返回,保证非浏览器环境与测试夹具行为不变。
19
+ */
20
+ export declare function resolveApiUrl(path: string): string;
@@ -0,0 +1,59 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { ClientContext } from './contracts.js';
3
+ export declare const BUNDLE_KEY = "@michengai/dsh-automation";
4
+ export declare const SIDEBAR_PANEL_ID = "dsh-automation";
5
+ export declare const SIDEBAR_PANEL_ORDER = 4;
6
+ export declare const HIDE_OFFICIAL_SCHEDULE_STYLE_TAG = "dsh-automation-hide-official-schedule";
7
+ export declare const STORAGE_KEY_HIDE_OFFICIAL = "dsh-automation.hide-official-schedule";
8
+ export declare const STORAGE_KEY_SIDEBAR_ENTRY = "dsh-automation.sidebar-entry";
9
+ export declare function readCachedSidebarEntry(): boolean;
10
+ export declare function readCachedHideOfficial(): boolean;
11
+ export declare function applyHideOfficialScheduleStyle(hidden: boolean): void;
12
+ export declare function AutomationSidebarIcon(props: {
13
+ readonly size?: number;
14
+ }): ReactElement;
15
+ export interface BundleConfigCardProps {
16
+ readonly t: (key: string) => string;
17
+ readonly enabled: boolean;
18
+ readonly disabled?: boolean;
19
+ readonly onToggle: (next: boolean) => void;
20
+ readonly onlineUpdateEnabled?: boolean;
21
+ readonly onlineUpdateDisabled?: boolean;
22
+ readonly onlineUpdateLocked?: boolean;
23
+ readonly onToggleOnlineUpdate?: (next: boolean) => void;
24
+ readonly hideOfficialScheduleEnabled?: boolean;
25
+ readonly hideOfficialScheduleDisabled?: boolean;
26
+ readonly onToggleHideOfficialSchedule?: (next: boolean) => void;
27
+ }
28
+ export declare function AutomationBundleConfigCard(props: BundleConfigCardProps): ReactElement;
29
+ export interface BundleConfigWrapperProps {
30
+ readonly t: (key: string) => string;
31
+ readonly getEnabled: () => boolean;
32
+ readonly setSidebar: (next: boolean) => Promise<boolean>;
33
+ readonly getOnlineUpdateEnabled?: () => boolean;
34
+ readonly getOnlineUpdateLocked?: () => boolean;
35
+ readonly setOnlineUpdate?: (next: boolean) => Promise<boolean>;
36
+ readonly getHideOfficialScheduleEnabled?: () => boolean;
37
+ readonly setHideOfficialSchedule?: (next: boolean) => Promise<boolean>;
38
+ readonly subscribe: (listener: () => void) => () => void;
39
+ }
40
+ export declare function AutomationBundleConfigCardWrapper(props: BundleConfigWrapperProps): ReactElement;
41
+ export interface SetupSidebarEntryOptions {
42
+ readonly callApi: (path: string, options?: RequestInit) => Promise<any>;
43
+ readonly renderPanel: (ownerProps: any) => ReactElement;
44
+ readonly ns: string;
45
+ readonly injectProps?: Record<string, unknown> | (() => Record<string, unknown>);
46
+ readonly onOnlineUpdateChange?: (enabled: boolean) => void;
47
+ }
48
+ export interface SidebarController {
49
+ isEnabled(): boolean;
50
+ isSupported(): boolean;
51
+ setEnabled(next: boolean): Promise<boolean>;
52
+ isOnlineUpdateEnabled(): boolean;
53
+ isOnlineUpdateLocked(): boolean;
54
+ setOnlineUpdate(next: boolean): Promise<boolean>;
55
+ isHideOfficialScheduleEnabled(): boolean;
56
+ setHideOfficialSchedule(next: boolean): Promise<boolean>;
57
+ destroy(): void;
58
+ }
59
+ export declare function setupSidebarEntry(ctx: ClientContext, options: SetupSidebarEntryOptions): SidebarController;
@@ -21,6 +21,7 @@ export interface ClientRpc {
21
21
  export interface SlotRegisterOptions {
22
22
  readonly name: string;
23
23
  readonly id?: string;
24
+ readonly key?: string;
24
25
  readonly order?: number;
25
26
  readonly locale?: string;
26
27
  readonly label?: () => string;
@@ -2,3 +2,5 @@ import type { ClientContext } from './contracts.js';
2
2
  export declare const name = "dsh-automation-client";
3
3
  export declare const inject: string[];
4
4
  export declare function apply(ctx: ClientContext): void;
5
+ export { resolveApiUrl } from './api-base.js';
6
+ export { setupSidebarEntry } from './bundle-config.js';
@@ -15,6 +15,16 @@ export declare const en: {
15
15
  readonly "sidebar.workspaces": "Workspaces";
16
16
  readonly "sidebar.search": "Search";
17
17
  readonly "sidebar.clearSearch": "Clear search";
18
+ readonly "sidebar.entry.title": "Sidebar entry";
19
+ readonly "sidebar.entry.desc": "Show dedicated scheduled tasks entry in the primary sidebar";
20
+ readonly "sidebar.entry.toggle": "Toggle sidebar entry";
21
+ readonly "update.entry.title": "Online update";
22
+ readonly "update.entry.desc": "Check for updates and notify when new versions are available";
23
+ readonly "update.entry.toggle": "Toggle online update";
24
+ readonly "officialSchedule.entry.title": "Hide official automation entry";
25
+ readonly "officialSchedule.entry.desc": "Hide built-in 'Automation tasks' sidebar entry to avoid conflict. Restores automatically when disabled or uninstalled.";
26
+ readonly "officialSchedule.entry.toggle": "Toggle hide official automation entry";
27
+ readonly "error.update.disabled": "locked by host configuration";
18
28
  readonly "header.lead": "Plan automatic tasks, or trigger them by hand. Describe the recurring work in any chat to create it quickly.";
19
29
  readonly "header.githubProject": "GitHub";
20
30
  readonly "header.githubFeedback": "Issues";
@@ -1,5 +1,6 @@
1
1
  import { handlePluginUpdateEscape, manualPluginUpdateCommand } from './plugin-update-model.js';
2
2
  export { handlePluginUpdateEscape, manualPluginUpdateCommand };
3
+ export declare const PLUGIN_UPDATE_TOGGLE_EVENT = "dsh-automation-update-toggle";
3
4
  export type PluginUpdateUiOptions = {
4
5
  readonly endpoint: string;
5
6
  readonly packageName: string;
@@ -8,6 +9,7 @@ export type PluginUpdateUiOptions = {
8
9
  readonly zhName: string;
9
10
  readonly enName: string;
10
11
  readonly createIcon: (name: PluginUpdateIconName) => HTMLElement;
12
+ readonly isEnabled?: () => boolean;
11
13
  };
12
14
  export type PluginUpdateIconName = 'refresh' | 'download' | 'copy' | 'close';
13
15
  export declare function observePluginUpdate(options: PluginUpdateUiOptions): () => void;
@@ -5,20 +5,14 @@
5
5
  * 会与宿主/皮肤的内容级图标规则互相覆盖。这里改为插入独立的宿主时钟图标,
6
6
  * 只通过 marker 属性隐藏宿主 svg,卸载时可以完整还原。
7
7
  */
8
- export declare const SETTINGS_NAV_ICON_CLASS = "dsh-st-nav-icon-schedule";
9
8
  export declare const SETTINGS_NAV_ICON_MARKER = "data-dsh-schedule-nav";
10
9
  /**
11
- * 为目标导航按钮注入 mask 图标。
12
- *
13
- * @param button 设置弹窗导航按钮
14
- * @param labels 返回当前语言下目标分区名称的函数
15
- * @returns 是否发生了实际变更;重复调用保持幂等
10
+ * 100% 对齐 dshmarket:用纯属性标记控制 ::before 伪元素渲染图标。
11
+ * 不修改任何 DOM 节点,避免移动端列表项排版下与文字重叠冲突。
16
12
  */
17
- export declare function applySettingsNavIcon(button: HTMLButtonElement, labels: () => readonly string[], markup?: () => string): boolean;
13
+ export declare function applySettingsNavIcon(button: HTMLButtonElement, labels: () => readonly string[], _markup?: () => string): boolean;
18
14
  /**
19
- * 还原导航按钮:移除 marker 与插件插入的图标,宿主 svg 从未被修改。
20
- *
21
- * @returns 是否清理了插件留下的内容
15
+ * 还原导航按钮:移除 marker,宿主 svg 恢复显示。
22
16
  */
23
17
  export declare function restoreSettingsNavIcon(button: HTMLButtonElement): boolean;
24
18
  /**
@@ -6,6 +6,9 @@ export interface Config {
6
6
  readonly runTimeoutMinutes?: number;
7
7
  readonly misfireGraceMinutes?: number;
8
8
  readonly historyLimit?: number;
9
+ readonly stateDir?: string;
10
+ readonly updater?: boolean;
11
+ readonly onlineUpdate?: boolean;
9
12
  }
10
13
  export declare const Config: any;
11
14
  export type SessionApprovalPolicy = 'ask' | 'never';
@@ -39,6 +39,7 @@ export type PluginUpdaterOptions = {
39
39
  readonly endpoint: string;
40
40
  readonly packageName: string;
41
41
  readonly manifestUrl: URL;
42
+ readonly isEnabled?: () => Promise<boolean> | boolean;
42
43
  };
43
44
  type Runtime = {
44
45
  profileName: string;
@@ -0,0 +1,27 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export declare const SETTINGS_API_HEADER = "x-michengai-client";
3
+ export interface PersistedSettings {
4
+ sidebarEntry?: boolean | undefined;
5
+ onlineUpdate?: boolean | undefined;
6
+ hideOfficialSchedule?: boolean | undefined;
7
+ }
8
+ export declare class SettingsManager {
9
+ private readonly file;
10
+ private readonly hostUpdateDisabled;
11
+ private store;
12
+ constructor(options: {
13
+ ctx: Context;
14
+ stateDir?: string;
15
+ hostUpdateDisabled?: boolean;
16
+ });
17
+ private load;
18
+ private flush;
19
+ getSidebarEntry(): boolean;
20
+ setSidebarEntry(enabled: boolean): Promise<boolean>;
21
+ getHideOfficialSchedule(): boolean;
22
+ setHideOfficialSchedule(enabled: boolean): Promise<boolean>;
23
+ isHostUpdateDisabled(): boolean;
24
+ isUpdateEnabled(): boolean;
25
+ setOnlineUpdate(enabled: boolean): Promise<boolean>;
26
+ }
27
+ export declare function registerSettingsApi(ctx: Context, manager: SettingsManager): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-automation",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "DSH Automation — 在独立 DSH Session 中按计划执行和管理编码任务 · Schedule and manage coding tasks in isolated DSH sessions",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
@@ -73,21 +73,21 @@
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
76
- "@deepseek-ai/dsh-agent": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
77
- "@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
78
- "@deepseek-ai/dsh-client-connection": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
79
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
80
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
81
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
82
- "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
83
- "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
84
- "@deepseek-ai/dsh-llm": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
85
- "@deepseek-ai/dsh-permission-presets": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
86
- "@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
87
- "@deepseek-ai/dsh-storage-domain": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
88
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
89
- "@deepseek-ai/dsh-user-approval": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
90
- "@deepseek-ai/dsh-workspace": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2",
76
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
77
+ "@deepseek-ai/dsh-agent-default-model": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
78
+ "@deepseek-ai/dsh-client-connection": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
79
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
80
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
81
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
82
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
83
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
84
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
85
+ "@deepseek-ai/dsh-permission-presets": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
86
+ "@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
87
+ "@deepseek-ai/dsh-storage-domain": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
88
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
89
+ "@deepseek-ai/dsh-user-approval": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
90
+ "@deepseek-ai/dsh-workspace": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3",
91
91
  "@deepseek-ai/schemastery": ">=3.18.1 <4.0.0",
92
92
  "react": "^18.2.0"
93
93
  },