@michengai/dsh-automation 0.1.6 → 0.1.7

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 CHANGED
@@ -25,7 +25,7 @@
25
25
  - Create, pause, resume, run now, and delete rules from the Web UI or Agent tools.
26
26
  - Start each occurrence in a fresh root Agent and Session. Source-chat history is not inherited.
27
27
  - Support once, interval, hourly, daily, weekly, monthly, and custom-every-N-days schedules.
28
- - Pick workspace, model, skills, and `read-only` / `workspace-write` in the create dialog.
28
+ - Pick workspace, model, skills, and any permission preset exposed by the Host.
29
29
  - Create from chat: describe the schedule in any conversation, then confirm with the official approval card.
30
30
  - Keep durable run history: `queued`, `running`, `succeeded`, `failed`, `skipped`, `cancelled`.
31
31
  - Add a sidebar **Scheduled** tab. Folders are task names and child sessions are run times. On stock DSH it wraps the official workspace tree and does not depend on `dsh-codex-ui`.
@@ -124,8 +124,8 @@ Each dispatched run uses the saved prompt, workspace, model, and permission boun
124
124
 
125
125
  | Item | Behavior |
126
126
  | --- | --- |
127
- | Permission | Default is `read-only`. File writes require an explicit `workspace-write` choice. |
128
- | Full access | Unattended `danger-full-access` is not offered. |
127
+ | Permission | Options and the default come directly from the Host `permissionPresets` service, including custom presets. |
128
+ | Full access | The official `danger-full-access` option uses the same risk confirmation and orange warning as Chat. |
129
129
  | Approval | Chat create follows the session policy. Full access (`never`) proceeds; Workspace Write / Read Only (`ask`) shows the official card. Unattended runs stay fail-closed `never`. |
130
130
  | Retry | No automatic retry after a started run. |
131
131
  | Host restart | Leftover `queued` / `running` records become `failed(host_interrupted)`. |
package/README.zh-CN.md CHANGED
@@ -25,7 +25,7 @@
25
25
  - 支持 Web 设置页和 Agent 工具创建、暂停、恢复、立即运行和删除。
26
26
  - 每次到期都启动全新 root Agent 和 Session,不继承来源对话。
27
27
  - 计划类型包括不重复、间隔、每小时、每天、每周、每月和自定义间隔天数。
28
- - 新建弹窗可选择工作目录、模型、技能,以及 `read-only` / `workspace-write`。
28
+ - 新建弹窗可选择工作目录、模型、技能,以及 Host 官方提供的权限预设。
29
29
  - 在任意对话里描述定时任务即可创建;Full access 直接执行,其他权限走官方授权卡。
30
30
  - 运行状态包含 `queued`、`running`、`succeeded`、`failed`、`skipped`、`cancelled`。
31
31
  - 侧栏提供「定时」页签:文件夹是任务名称,子会话是执行时间。原生下只包裹官方任务树,不依赖 `dsh-codex-ui`。
@@ -124,8 +124,8 @@ dsh --profile web --dump-config
124
124
 
125
125
  | 项目 | 行为 |
126
126
  | --- | --- |
127
- | 权限 | 默认 `read-only`。改文件必须显式选择 `workspace-write`。 |
128
- | 完全访问 | 不提供无人值守 `danger-full-access`。 |
127
+ | 权限 | 列表和默认值直接来自 Host 官方 `permissionPresets` 服务;支持 Host 注册的自定义预设。 |
128
+ | 完全访问 | 选择官方 `danger-full-access` 时显示与 Chat 一致的风险确认和橙色提示。 |
129
129
  | 审批 | 对话创建跟随当前会话策略。Full access(`never`)直接创建;Workspace Write / Read Only(`ask`)走官方授权卡。无人值守运行仍是 fail-closed 的 `never`。 |
130
130
  | 重试 | 已经开始的运行不会自动重试。 |
131
131
  | Host 重启 | 遗留的 `queued` / `running` 会变成 `failed(host_interrupted)`。 |
package/lib/client.js CHANGED
@@ -63,7 +63,7 @@ function localDateTimeValue(date = /* @__PURE__ */ new Date()) {
63
63
  const offset = future.getTimezoneOffset() * 6e4;
64
64
  return new Date(future.getTime() - offset).toISOString().slice(0, 16);
65
65
  }
66
- function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], defaultModel) {
66
+ function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], defaultModel, defaultPermission = "") {
67
67
  return {
68
68
  name: "",
69
69
  prompt: "",
@@ -76,7 +76,7 @@ function defaultFormState(now = /* @__PURE__ */ new Date(), workspaces = [], def
76
76
  monthDay: "1",
77
77
  customDays: "2",
78
78
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
79
- permission: "read-only",
79
+ permission: defaultPermission,
80
80
  workspaceId: workspaces[0]?.id ?? "",
81
81
  modelKey: defaultModel === void 0 || defaultModel === null ? "default" : `${defaultModel.provider}::${defaultModel.model}`,
82
82
  reasoningEffort: "high",
@@ -228,8 +228,8 @@ function startOfWeek(value) {
228
228
  next.setDate(next.getDate() - offset);
229
229
  return next;
230
230
  }
231
- function formFromAutomation(item, workspaces = [], defaultModel) {
232
- const base = defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel);
231
+ function formFromAutomation(item, workspaces = [], defaultModel, defaultPermission = item.permission) {
232
+ const base = defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel, defaultPermission);
233
233
  const schedule = item.schedule;
234
234
  const modelKey = item.provider && item.model ? `${item.provider}::${item.model}` : "default";
235
235
  const common = {
@@ -397,9 +397,23 @@ function RunningStateDot() {
397
397
  // src/client/create-modal.tsx
398
398
  var import_react2 = require("react");
399
399
 
400
+ // src/client/permissions.ts
401
+ var BUILT_IN_PERMISSION_LABELS = /* @__PURE__ */ new Map([
402
+ ["read-only", ["preset.readOnly", "Read Only"]],
403
+ ["workspace-write", ["preset.workspaceWrite", "Workspace Write"]],
404
+ ["danger-full-access", ["preset.fullAccess", "Full access"]]
405
+ ]);
406
+ function permissionLabel(option, t) {
407
+ const builtIn = BUILT_IN_PERMISSION_LABELS.get(option.value);
408
+ if (builtIn !== void 0 && (option.name === option.value || option.name === builtIn[1])) {
409
+ return t(builtIn[0]);
410
+ }
411
+ return option.name || option.value;
412
+ }
413
+
400
414
  // src/client/create-modal-logic.ts
401
415
  function shouldConfirmFullAccess(current, next) {
402
- return current !== "full-access" && next === "full-access";
416
+ return current !== next && next === "danger-full-access";
403
417
  }
404
418
  async function adoptPickedWorkspace(input) {
405
419
  const path = await input.pick();
@@ -618,11 +632,14 @@ var MINUTES = Array.from({ length: 60 }, (_, index) => String(index).padStart(2,
618
632
  var EFFORTS = ["none", "low", "medium", "high"];
619
633
  function CreateModal({
620
634
  t,
635
+ permissionT,
621
636
  busy,
622
637
  workspaces,
623
638
  models,
624
639
  defaultModel,
625
640
  skills,
641
+ permissions,
642
+ defaultPermission,
626
643
  draft,
627
644
  editing,
628
645
  onClose,
@@ -630,9 +647,9 @@ function CreateModal({
630
647
  onAddWorkspace,
631
648
  pickWorkspaceDirectory
632
649
  }) {
633
- const [form, setForm] = (0, import_react2.useState)(() => ({ ...defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel), ...draft }));
650
+ const [form, setForm] = (0, import_react2.useState)(() => ({ ...defaultFormState(/* @__PURE__ */ new Date(), workspaces, defaultModel, defaultPermission), ...draft }));
634
651
  const [validationError, setValidationError] = (0, import_react2.useState)();
635
- const [confirmingFullAccess, setConfirmingFullAccess] = (0, import_react2.useState)(false);
652
+ const [confirmingPermission, setConfirmingPermission] = (0, import_react2.useState)();
636
653
  const [fullAccessAcknowledged, setFullAccessAcknowledged] = (0, import_react2.useState)(false);
637
654
  const update = (patch) => {
638
655
  setForm((current) => ({ ...current, ...patch }));
@@ -641,18 +658,19 @@ function CreateModal({
641
658
  const choosePermission = (permission) => {
642
659
  if (shouldConfirmFullAccess(form.permission, permission)) {
643
660
  setFullAccessAcknowledged(false);
644
- setConfirmingFullAccess(true);
661
+ setConfirmingPermission(permission);
645
662
  return;
646
663
  }
647
664
  update({ permission });
648
665
  };
649
666
  const cancelFullAccessConfirmation = () => {
650
667
  setFullAccessAcknowledged(false);
651
- setConfirmingFullAccess(false);
668
+ setConfirmingPermission(void 0);
652
669
  };
653
670
  const confirmFullAccess = () => {
654
671
  if (!fullAccessAcknowledged) return;
655
- update({ permission: "full-access" });
672
+ if (confirmingPermission === void 0) return;
673
+ update({ permission: confirmingPermission });
656
674
  cancelFullAccessConfirmation();
657
675
  };
658
676
  (0, import_react2.useEffect)(() => {
@@ -843,11 +861,11 @@ function CreateModal({
843
861
  up: true,
844
862
  icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShieldIcon, { width: 14, height: 14 }),
845
863
  value: form.permission,
846
- options: [
847
- { value: "read-only", label: t("form.readOnly"), icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShieldIcon, { width: 14, height: 14 }) },
848
- { value: "workspace-write", label: t("form.workspaceWrite"), icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShieldIcon, { width: 14, height: 14 }) },
849
- { value: "full-access", label: t("form.fullAccess"), icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShieldIcon, { width: 14, height: 14 }) }
850
- ],
864
+ options: permissions.map((option) => ({
865
+ value: option.value,
866
+ label: permissionLabel(option, permissionT),
867
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShieldIcon, { width: 14, height: 14 })
868
+ })),
851
869
  onChange: choosePermission
852
870
  }
853
871
  )
@@ -876,12 +894,12 @@ function CreateModal({
876
894
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
877
895
  import_dsh_client_ui_primitives.RiskConfirmation,
878
896
  {
879
- open: confirmingFullAccess,
880
- title: t("access.confirm.title"),
881
- description: t("access.confirm.description"),
882
- acknowledgeLabel: t("access.confirm.acknowledge"),
883
- cancelLabel: t("access.confirm.cancel"),
884
- confirmLabel: t("access.confirm.enable"),
897
+ open: confirmingPermission !== void 0,
898
+ title: permissionT("confirm.title"),
899
+ description: permissionT("confirm.description"),
900
+ acknowledgeLabel: permissionT("confirm.acknowledge"),
901
+ cancelLabel: permissionT("confirm.cancel"),
902
+ confirmLabel: permissionT("confirm.enable"),
885
903
  acknowledged: fullAccessAcknowledged,
886
904
  onAcknowledgedChange: setFullAccessAcknowledged,
887
905
  onCancel: cancelFullAccessConfirmation,
@@ -1193,7 +1211,7 @@ var EXAMPLES = [
1193
1211
  { name: "\u6BCF\u5468\u4F9D\u8D56\u5DE1\u68C0", scheduleKind: "weekly", time: "10:00", weekdays: [1] },
1194
1212
  { name: "\u5DE5\u4F5C\u65E5\u65E9\u62A5", scheduleKind: "weekly", time: "08:00", weekdays: [1, 2, 3, 4, 5] }
1195
1213
  ];
1196
- function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1214
+ function AutomationView({ t, permissionT, runtime, closeSettings, pickWorkspaceDirectory }) {
1197
1215
  const state = (0, import_react3.useSyncExternalStore)(runtime.source.subscribe, runtime.source.getSnapshot, runtime.source.getSnapshot);
1198
1216
  const [tab, setTab] = (0, import_react3.useState)("mine");
1199
1217
  const [query, setQuery] = (0, import_react3.useState)("");
@@ -1218,6 +1236,8 @@ function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1218
1236
  const snapshot = state.snapshot;
1219
1237
  const workspaces = snapshot?.workspaces ?? [];
1220
1238
  const models = snapshot?.models ?? [];
1239
+ const permissions = snapshot?.permissions ?? [];
1240
+ const defaultPermission = snapshot?.defaultPermission ?? "";
1221
1241
  const automations = (snapshot?.automations ?? []).filter((item) => query.trim() === "" || `${item.name} ${item.prompt}`.toLowerCase().includes(query.trim().toLowerCase())).slice().sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt));
1222
1242
  const runs = (snapshot?.runs ?? []).filter((run) => {
1223
1243
  if (historyTask !== "all" && run.automationId !== historyTask) return false;
@@ -1242,13 +1262,14 @@ function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1242
1262
  setEditingId(void 0);
1243
1263
  };
1244
1264
  const openCreate = (partial) => {
1265
+ if (defaultPermission === "" || permissions.length === 0) return;
1245
1266
  setEditingId(void 0);
1246
1267
  setDraft(partial);
1247
1268
  setCreating(true);
1248
1269
  };
1249
1270
  const openEdit = (item) => {
1250
1271
  setEditingId(item.id);
1251
- setDraft(formFromAutomation(item, workspaces, snapshot?.defaultModel ?? null));
1272
+ setDraft(formFromAutomation(item, workspaces, snapshot?.defaultModel ?? null, snapshot?.defaultPermission ?? item.permission));
1252
1273
  setCreating(true);
1253
1274
  };
1254
1275
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-st-shell", children: [
@@ -1266,7 +1287,7 @@ function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1266
1287
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ChatIcon, {}),
1267
1288
  t("action.chatCreate")
1268
1289
  ] }),
1269
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", className: "dsh-st-btn dsh-st-btn--primary", onClick: () => openCreate(), children: [
1290
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", className: "dsh-st-btn dsh-st-btn--primary", disabled: defaultPermission === "" || permissions.length === 0, onClick: () => openCreate(), children: [
1270
1291
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlusIcon, {}),
1271
1292
  t("action.create")
1272
1293
  ] }),
@@ -1286,6 +1307,7 @@ function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1286
1307
  {
1287
1308
  type: "button",
1288
1309
  className: "dsh-st-example",
1310
+ disabled: defaultPermission === "" || permissions.length === 0,
1289
1311
  onClick: () => openCreate({
1290
1312
  name: t(`examples.${index + 1}.title`),
1291
1313
  prompt: t(`examples.${index + 1}.body`),
@@ -1361,11 +1383,14 @@ function AutomationView({ t, runtime, closeSettings, pickWorkspaceDirectory }) {
1361
1383
  CreateModal,
1362
1384
  {
1363
1385
  t,
1386
+ permissionT,
1364
1387
  busy,
1365
1388
  workspaces,
1366
1389
  models,
1367
1390
  defaultModel: snapshot?.defaultModel ?? null,
1368
1391
  skills: snapshot?.skills ?? [],
1392
+ permissions,
1393
+ defaultPermission,
1369
1394
  onAddWorkspace: async (path) => runtime.addWorkspace(path).then((value) => value.id),
1370
1395
  ...pickWorkspaceDirectory === void 0 ? {} : { pickWorkspaceDirectory },
1371
1396
  editing: editingId !== void 0,
@@ -1595,16 +1620,6 @@ var en = {
1595
1620
  "form.days": "Weekdays",
1596
1621
  "form.timeZone": "Time zone",
1597
1622
  "form.permission": "Permission boundary",
1598
- "form.readOnly": "Read Only",
1599
- "form.readOnlyHint": "Inspect the workspace without changing files.",
1600
- "form.workspaceWrite": "Workspace Write",
1601
- "form.fullAccess": "Full access",
1602
- "form.workspaceWriteHint": "May change files in this workspace; past approvals are not reused.",
1603
- "access.confirm.title": "Enable Full access?",
1604
- "access.confirm.description": "Full access reduces confirmation steps and allows the Agent to directly perform more operations, including sensitive operations, file changes, or external commands. Use it only when you trust this task.",
1605
- "access.confirm.acknowledge": "I understand the risks and want to continue",
1606
- "access.confirm.cancel": "Cancel",
1607
- "access.confirm.enable": "Enable Full access",
1608
1623
  "form.cancel": "Cancel",
1609
1624
  "form.submit": "Create automation",
1610
1625
  "form.submitting": "Creating\u2026",
@@ -1632,9 +1647,6 @@ var en = {
1632
1647
  "card.nextRun": "Next",
1633
1648
  "card.lastRun": "Last",
1634
1649
  "card.never": "Not yet run",
1635
- "card.permission.read-only": "Read-only",
1636
- "card.permission.workspace-write": "Workspace write",
1637
- "card.permission.full-access": "Full access",
1638
1650
  "schedule.onceAt": "Once \xB7 {time}",
1639
1651
  "schedule.everyMinutes": "Every {count} minutes",
1640
1652
  "schedule.dailyAt": "Daily \xB7 {time}",
@@ -1800,16 +1812,6 @@ var zh = {
1800
1812
  "form.days": "\u661F\u671F",
1801
1813
  "form.timeZone": "\u65F6\u533A",
1802
1814
  "form.permission": "\u6743\u9650\u8FB9\u754C",
1803
- "form.readOnly": "Read Only",
1804
- "form.readOnlyHint": "\u53EF\u4EE5\u68C0\u67E5\u5DE5\u4F5C\u533A\uFF0C\u4F46\u4E0D\u4F1A\u4FEE\u6539\u6587\u4EF6\u3002",
1805
- "form.workspaceWrite": "Workspace Write",
1806
- "form.fullAccess": "Full access",
1807
- "form.workspaceWriteHint": "\u53EF\u4EE5\u4FEE\u6539\u5F53\u524D\u5DE5\u4F5C\u533A\u6587\u4EF6\uFF1B\u4E0D\u4F1A\u7EE7\u627F\u5386\u53F2\u6279\u51C6\u3002",
1808
- "access.confirm.title": "\u786E\u8BA4\u542F\u7528 \u5B8C\u5168\u8BBF\u95EE\uFF1F",
1809
- "access.confirm.description": "\u542F\u7528 \u5B8C\u5168\u8BBF\u95EE \u540E\uFF0Cagent \u5C06\u51CF\u5C11\u786E\u8BA4\u6B65\u9AA4\uFF0C\u5E76\u4E14\u53EF\u4EE5\u76F4\u63A5\u6267\u884C\u66F4\u591A\u64CD\u4F5C\uFF0C\u5305\u62EC\u654F\u611F\u64CD\u4F5C\u3001\u6587\u4EF6\u4FEE\u6539\u6216\u5916\u90E8\u547D\u4EE4\u3002\u4EC5\u5EFA\u8BAE\u5728\u4F60\u4FE1\u4EFB\u5F53\u524D\u4EFB\u52A1\u65F6\u4F7F\u7528\u3002",
1810
- "access.confirm.acknowledge": "\u6211\u5DF2\u4E86\u89E3\u98CE\u9669\uFF0C\u5E76\u613F\u610F\u7EE7\u7EED",
1811
- "access.confirm.cancel": "\u53D6\u6D88",
1812
- "access.confirm.enable": "\u542F\u7528 \u5B8C\u5168\u8BBF\u95EE",
1813
1815
  "form.cancel": "\u53D6\u6D88",
1814
1816
  "form.submit": "\u521B\u5EFA\u81EA\u52A8\u5316",
1815
1817
  "form.submitting": "\u521B\u5EFA\u4E2D\u2026",
@@ -1837,9 +1839,6 @@ var zh = {
1837
1839
  "card.nextRun": "\u4E0B\u6B21",
1838
1840
  "card.lastRun": "\u6700\u8FD1",
1839
1841
  "card.never": "\u5C1A\u672A\u8FD0\u884C",
1840
- "card.permission.read-only": "\u53EA\u8BFB",
1841
- "card.permission.workspace-write": "\u53EF\u5199\u5DE5\u4F5C\u533A",
1842
- "card.permission.full-access": "Full access",
1843
1842
  "schedule.onceAt": "\u5355\u6B21 \xB7 {time}",
1844
1843
  "schedule.everyMinutes": "\u6BCF {count} \u5206\u949F",
1845
1844
  "schedule.dailyAt": "\u6BCF\u5929 \xB7 {time}",
@@ -3007,6 +3006,7 @@ function apply(ctx) {
3007
3006
  ctx.effect(() => installStyles(), "dsh-automation: styles");
3008
3007
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-automation: locale");
3009
3008
  const t = ctx.locale.bind(NS);
3009
+ const permissionT = ctx.locale.bind("permission.access");
3010
3010
  const runtime = createAutomationRuntime(ctx.connection.rpc);
3011
3011
  ctx.effect(() => installSettingsNavIcon(() => [t("tab"), "Scheduled tasks", "\u5B9A\u65F6\u4EFB\u52A1"]), "dsh-automation: settings icon");
3012
3012
  ctx.slots.inject("settings.section", () => ctx.slots.register({
@@ -3018,7 +3018,7 @@ function apply(ctx) {
3018
3018
  icon: "schedule"
3019
3019
  }, function ScheduledTasksSettings(props) {
3020
3020
  const workspacesApi = readOptionalWorkspaces(ctx);
3021
- return (0, import_react7.createElement)(AutomationView, { t, runtime, ...props.close === void 0 ? {} : { closeSettings: props.close }, ...workspacesApi === void 0 ? {} : { pickWorkspaceDirectory: () => workspacesApi.pickDirectory() } });
3021
+ return (0, import_react7.createElement)(AutomationView, { t, permissionT, runtime, ...props.close === void 0 ? {} : { closeSettings: props.close }, ...workspacesApi === void 0 ? {} : { pickWorkspaceDirectory: () => workspacesApi.pickDirectory() } });
3022
3022
  }));
3023
3023
  ctx.slots.inject("sidebar.schedule", () => ctx.slots.register({
3024
3024
  name: "sidebar.schedule",