dsh-automations 0.1.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-11-06
4
+
5
+ - 面板拆成「规则 / 运行」两个 tab:规则页只放规则卡片与模板,运行页展示完整运行历史(含触发类型、退出码、可展开输出),tab 选择记在 `localStorage`。
6
+ - shell 动作的工作目录有了默认值:规则里留空时使用 `defaultCwd` 配置,未配置则用**用户主目录**(不再落到宿主进程目录)。新建规则时表单会**预填**这个默认路径。
7
+ - 新增配置项 `defaultCwd`。
8
+ - 自检增至 14 项(默认工作目录、面板视图的 `defaultCwd`)。
9
+
3
10
  ## 0.1.0 — 2026-11-06
4
11
 
5
12
  首个版本。
@@ -12,3 +19,4 @@
12
19
  - 同一 turn 的多条 `user/message` 事件按 turn 去重,一条用户消息只触发一次。
13
20
  - 中性命令串行执行队列(上限 200),触发侧 cooldown。
14
21
  - 12 项离线自检(`node test/selftest.mjs`)。
22
+
package/README.md CHANGED
@@ -41,7 +41,7 @@
41
41
 
42
42
  | 类型 | 做什么 | 说明 |
43
43
  |---|---|---|
44
- | `shell` | 起一个**非交互**子进程 | `command`、`cwd`、`shell`(`auto`/`powershell`/`cmd`/`bash`)、`timeoutMs`。捕获 stdout+stderr 尾部与退出码;超时会杀掉整个进程树 |
44
+ | `shell` | 起一个**非交互**子进程 | `command`、`cwd`、`shell`(`auto`/`powershell`/`cmd`/`bash`)、`timeoutMs`。捕获 stdout+stderr 尾部与退出码;超时会杀掉整个进程树。**`cwd` 留空 = `defaultCwd`(默认用户主目录)** |
45
45
  | `http` | 发一个 HTTP 请求 | `url`、`method`、`headers`(JSON 对象字符串)、`body`、`timeoutMs`。适合 webhook、飞书机器人、本地探活 |
46
46
  | `log` | 只写一条运行记录 | 无副作用,用来验证「时机到底有没有命中」 |
47
47
 
@@ -62,10 +62,12 @@ DSH_AUTOMATION_CONTEXT 一行 JSON(kind/sessionId/tool/source/at)
62
62
 
63
63
  ## 3. 面板
64
64
 
65
- - 左栏图标 → 主区页面:统计条、规则卡片(启停 / 运行 / 编辑 / 删除)、最近运行(点开看输出)。
66
- - 「新建规则」用同一份 **catalog** 动态生成表单:选触发时机后只出现相关字段,换动作同理。
65
+ - 左栏图标 → 主区页面:统计条 + **「规则 / 运行」两个 tab**。
66
+ - **规则** tab:规则卡片(启停 / 运行 / 编辑 / 删除);空面板时给三个模板(启动写日志、每天 08:30 探活、会话空闲记录)。
67
+ - **运行** tab:完整运行历史(时间 / 规则 / 触发类型 / 状态 / 耗时),点一行展开输出,展开头部带触发与退出码。
68
+ - tab 选择记在 `localStorage`,刷新后停在上次那个 tab。
69
+ - 「新建规则」用同一份 **catalog** 动态生成表单:选触发时机后只出现相关字段,换动作同理;shell 规则的**工作目录会预填默认路径**(见下节)。
67
70
  - 「试运行」不保存也能执行一次,便于调命令。
68
- - 首次使用提供三个模板:启动写日志、每天 08:30 探活、会话空闲记录。
69
71
 
70
72
  界面文案跟随 DSH 的语言(中文 / English)。
71
73
 
@@ -104,6 +106,7 @@ $DSH_HOME/storages/dsh-automations/
104
106
  maxRunHistory: 200
105
107
  maxOutputChars: 8000
106
108
  defaultTimeoutMs: 120000
109
+ defaultCwd: "" # shell 规则的默认工作目录;空 = 用户主目录
107
110
  startupDelayMs: 2000
108
111
  tickSeconds: 15
109
112
  promptEnabled: true
package/lib/actions.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { spawn, spawnSync } from "node:child_process";
10
+ import { homedir } from "node:os";
10
11
 
11
12
  import { tailText } from "./util.js";
12
13
 
@@ -96,7 +97,7 @@ export function shellInvocation(action) {
96
97
  *
97
98
  * @param {object} action - normalized shell action.
98
99
  * @param {object} context - trigger context.
99
- * @param {{timeoutMs: number, maxOutputChars: number}} limits - resolved limits.
100
+ * @param {{timeoutMs: number, maxOutputChars: number, defaultCwd: string}} limits - resolved limits.
100
101
  * @returns {Promise<ActionOutcome>}
101
102
  */
102
103
  function runShell(action, context, limits) {
@@ -127,7 +128,7 @@ function runShell(action, context, limits) {
127
128
 
128
129
  try {
129
130
  child = spawn(file, args, {
130
- cwd: typeof action.cwd === "string" && action.cwd.trim() !== "" ? action.cwd : process.cwd(),
131
+ cwd: typeof action.cwd === "string" && action.cwd.trim() !== "" ? action.cwd : limits.defaultCwd,
131
132
  env: childEnv(context),
132
133
  windowsHide: true,
133
134
  windowsVerbatimArguments: verbatim,
@@ -220,7 +221,11 @@ export function createActions({ settings }) {
220
221
  * @returns {Promise<ActionOutcome & {durationMs: number}>}
221
222
  */
222
223
  async run(action, context) {
223
- const limits = { timeoutMs: settings.defaultTimeoutMs, maxOutputChars: settings.maxOutputChars };
224
+ const limits = {
225
+ timeoutMs: settings.defaultTimeoutMs,
226
+ maxOutputChars: settings.maxOutputChars,
227
+ defaultCwd: settings.defaultCwd !== "" ? settings.defaultCwd : homedir(),
228
+ };
224
229
  const started = Date.now();
225
230
  if (action.type === "shell") return runShell(action, context, limits);
226
231
  if (action.type === "http") return runHttp(action, limits);
package/lib/client.js CHANGED
@@ -102,6 +102,15 @@ window.__ModuleLoader__.load({
102
102
  ".dsha_stat span{font-size:11.5px;color:var(--dsw-alias-label-secondary,#8a8f98)}",
103
103
  ".dsha_section{margin-top:22px}",
104
104
  ".dsha_h2{display:flex;align-items:center;gap:8px;margin:0 0 10px;font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-secondary,#8a8f98);text-transform:none}",
105
+ ".dsha_tabs{display:flex;gap:4px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));margin:0 0 16px}",
106
+ ".dsha_tab{appearance:none;display:inline-flex;align-items:center;gap:6px;border:none;border-bottom:2px solid transparent;background:transparent;color:var(--dsw-alias-label-secondary,#8a8f98);font:inherit;font-size:13px;padding:7px 10px;margin-bottom:-1px;cursor:pointer}",
107
+ ".dsha_tab:hover{color:var(--dsw-alias-label-primary,#e6e8ee)}",
108
+ ".dsha_tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px}",
109
+ ".dsha_tab[aria-selected=true]{color:var(--dsw-alias-label-primary,#e6e8ee);border-bottom-color:var(--dsw-alias-brand-primary,#5b8def)}",
110
+ ".dsha_tab .dsha_badge{font-size:10px;padding:0 6px}",
111
+ ".dsha_runrow .dsha_badge{flex:none;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
112
+ ".dsha_runmeta{padding:6px 11px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));font-size:11.5px;color:var(--dsw-alias-label-secondary,#8a8f98);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
113
+ ".dsha_runbox .dsha_pre{border-top:none}",
105
114
  ".dsha_card{display:flex;align-items:flex-start;gap:12px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));border-radius:10px;background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.05));padding:12px 14px;margin-bottom:8px}",
106
115
  ".dsha_card[data-off=true]{opacity:.55}",
107
116
  ".dsha_glyph{flex:none;width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.14));color:var(--dsw-alias-brand-primary,#5b8def)}",
@@ -130,7 +139,7 @@ window.__ModuleLoader__.load({
130
139
  ".dsha_runrow{display:flex;align-items:center;gap:10px;padding:7px 11px;font-size:12px;cursor:pointer}",
131
140
  ".dsha_runrow:hover{background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08))}",
132
141
  ".dsha_runat{flex:none;width:112px;color:var(--dsw-alias-label-secondary,#8a8f98);font-variant-numeric:tabular-nums}",
133
- ".dsha_runname{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
142
+ ".dsha_runname{flex:0 1 auto;max-width:45%;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
134
143
  ".dsha_runstatus{flex:none;width:56px;font-size:11.5px}",
135
144
  ".dsha_runstatus[data-status=ok]{color:var(--dsw-alias-state-success-primary,#34a853)}",
136
145
  ".dsha_runstatus[data-status=error]{color:var(--dsw-alias-state-error-primary,#e5534b)}",
@@ -623,8 +632,25 @@ window.__ModuleLoader__.load({
623
632
  const [busyId, setBusyId] = useState("");
624
633
  const [toast, setToast] = useState(null);
625
634
  const [openRun, setOpenRun] = useState("");
635
+ const [tab, setTab] = useState(() => {
636
+ try {
637
+ return window.localStorage.getItem("dsha.tab") === "runs" ? "runs" : "rules";
638
+ } catch {
639
+ return "rules";
640
+ }
641
+ });
626
642
  const toastTimer = useRef(null);
627
643
 
644
+ /** @param {"rules"|"runs"} next - tab to activate. */
645
+ const switchTab = useCallback((next) => {
646
+ setTab(next);
647
+ try {
648
+ window.localStorage.setItem("dsha.tab", next);
649
+ } catch {
650
+ /* storage may be unavailable */
651
+ }
652
+ }, []);
653
+
628
654
  /** @param {string} message - toast text. @param {string} [kind] - error|info. */
629
655
  const notify = useCallback((message, kind = "info") => {
630
656
  setToast({ message, kind });
@@ -654,11 +680,20 @@ window.__ModuleLoader__.load({
654
680
  return () => window.clearInterval(timer);
655
681
  }, [refresh]);
656
682
 
683
+ const defaultCwd = typeof view?.defaultCwd === "string" ? view.defaultCwd : "";
684
+
657
685
  const openEditor = useCallback((rule) => {
658
- setDraft(rule === null || rule === undefined ? emptyDraft() : draftFromRule(rule));
686
+ let next;
687
+ if (rule === null || rule === undefined) {
688
+ next = emptyDraft();
689
+ if (defaultCwd !== "") next = { ...next, action: { ...next.action, cwd: defaultCwd } };
690
+ } else {
691
+ next = draftFromRule(rule);
692
+ }
693
+ setDraft(next);
659
694
  setModalError("");
660
695
  setTestResult(null);
661
- }, []);
696
+ }, [defaultCwd]);
662
697
 
663
698
  const saveDraft = useCallback(async () => {
664
699
  if (draft === null) return;
@@ -730,6 +765,11 @@ window.__ModuleLoader__.load({
730
765
  const runs = view?.runs ?? [];
731
766
  const stats = view?.stats ?? { total: 0, enabled: 0, runsToday: 0, lastRunAt: null, busy: 0 };
732
767
  const catalog = view?.catalog ?? { triggers: [], actions: [] };
768
+ /** @param {string} type - trigger type. @returns {string} localised trigger name. */
769
+ const triggerNameOf = (type) => {
770
+ const spec = (catalog.triggers ?? []).find((entry) => entry.type === type);
771
+ return spec ? catalogLabel(spec) : String(type ?? "");
772
+ };
733
773
 
734
774
  const templates = useMemo(() => [
735
775
  {
@@ -794,7 +834,7 @@ window.__ModuleLoader__.load({
794
834
  h("h1", { className: "dsha_h1" }, T("生命周期自动化", "Automations")),
795
835
  h("p", { className: "dsha_sub" }, T("在 DSH 生命周期与时机点自动执行命令、HTTP 请求或记录。", "Run a command, HTTP request or log line at DSH lifecycle moments.")),
796
836
  ),
797
- h("button", { type: "button", className: "dsha_btn primary", onClick: () => openEditor(null) }, T("+ 新建规则", "+ New rule")),
837
+ h("button", { type: "button", className: "dsha_btn primary", onClick: () => { switchTab("rules"); openEditor(null); } }, T("+ 新建规则", "+ New rule")),
798
838
  ),
799
839
  loadError ? h("div", { className: "dsha_err", role: "alert" }, loadError) : null,
800
840
 
@@ -809,43 +849,59 @@ window.__ModuleLoader__.load({
809
849
 
810
850
  h(
811
851
  "div",
812
- { className: "dsha_section" },
813
- h("h2", { className: "dsha_h2" }, T("规则", "Rules"), h("span", { className: "dsha_badge" }, String(rules.length))),
814
- rules.length === 0
815
- ? h("div", { className: "dsha_empty" },
816
- h("div", null, T("还没有自动化规则。从一个模板开始:", "No automations yet. Start from a template:")),
817
- h("div", { className: "dsha_tpls" },
818
- templates.map((template) => h("button", { key: template.key, type: "button", className: "dsha_tpl", onClick: () => setDraft(template.build()) },
819
- h("b", null, template.title),
820
- h("span", null, template.desc),
821
- )),
822
- ),
823
- )
824
- : rules.map((rule) => h(RuleCard, { key: rule.id, rule, catalog, busyId, onRun: runRule, onEdit: openEditor, onDelete: deleteRule, onToggle: toggleRule })),
852
+ { className: "dsha_tabs", role: "tablist" },
853
+ h("button", { type: "button", className: "dsha_tab", role: "tab", "aria-selected": tab === "rules", onClick: () => switchTab("rules") },
854
+ T("规则", "Rules"), h("span", { className: "dsha_badge" }, String(rules.length))),
855
+ h("button", { type: "button", className: "dsha_tab", role: "tab", "aria-selected": tab === "runs", onClick: () => switchTab("runs") },
856
+ T("运行", "Runs"), h("span", { className: "dsha_badge" }, String(runs.length))),
825
857
  ),
826
858
 
827
- h(
828
- "div",
829
- { className: "dsha_section" },
830
- h("h2", { className: "dsha_h2" }, T("最近运行", "Recent runs"), h("span", { className: "dsha_badge" }, String(runs.length))),
831
- runs.length === 0
832
- ? h("div", { className: "dsha_empty" }, T("还没有运行记录。点规则上的「运行」可以试一次。", "No runs yet. Use “Run” on a rule to try one."))
833
- : h("div", { className: "dsha_runs" },
834
- runs.slice(0, 12).map((run) => h(
835
- "div",
836
- { key: run.id, className: "dsha_runbox" },
837
- h("div", { className: "dsha_runrow", role: "button", tabIndex: 0, onClick: () => setOpenRun(openRun === run.id ? "" : run.id), onKeyDown: (event) => { if (event.key === "Enter") setOpenRun(openRun === run.id ? "" : run.id); } },
838
- h("span", { className: "dsha_runat" }, fmtClock(run.at)),
839
- h("span", { className: "dsha_runname", title: run.ruleName }, run.ruleName),
840
- h("span", { className: "dsha_runstatus", "data-status": run.status }, statusLabel(run.status)),
841
- h("span", { className: "dsha_rundur" }, fmtDuration(run.durationMs)),
859
+ tab === "rules"
860
+ ? h(
861
+ "div",
862
+ { className: "dsha_section", role: "tabpanel" },
863
+ rules.length === 0
864
+ ? h("div", { className: "dsha_empty" },
865
+ h("div", null, T("还没有自动化规则。从一个模板开始:", "No automations yet. Start from a template:")),
866
+ h("div", { className: "dsha_tpls" },
867
+ templates.map((template) => h("button", { key: template.key, type: "button", className: "dsha_tpl", onClick: () => setDraft(template.build()) },
868
+ h("b", null, template.title),
869
+ h("span", null, template.desc),
870
+ )),
842
871
  ),
843
- openRun === run.id
844
- ? h("pre", { className: "dsha_pre" }, run.output || run.error || T("(无输出)", "(no output)"))
845
- : null,
846
- )),
847
- ),
848
- ),
872
+ )
873
+ : rules.map((rule) => h(RuleCard, { key: rule.id, rule, catalog, busyId, onRun: runRule, onEdit: openEditor, onDelete: deleteRule, onToggle: toggleRule })),
874
+ )
875
+ : h(
876
+ "div",
877
+ { className: "dsha_section", role: "tabpanel" },
878
+ runs.length === 0
879
+ ? h("div", { className: "dsha_empty" }, T("还没有运行记录。去「规则」页点一次「运行」试试。", "No runs yet. Use “Run” on a rule to try one."))
880
+ : h("div", { className: "dsha_runs" },
881
+ runs.map((run) => h(
882
+ "div",
883
+ { key: run.id, className: "dsha_runbox" },
884
+ h("div", { className: "dsha_runrow", role: "button", tabIndex: 0, onClick: () => setOpenRun(openRun === run.id ? "" : run.id), onKeyDown: (event) => { if (event.key === "Enter") setOpenRun(openRun === run.id ? "" : run.id); } },
885
+ h("span", { className: "dsha_runat" }, fmtClock(run.at)),
886
+ h("span", { className: "dsha_runname", title: run.ruleName }, run.ruleName),
887
+ h("span", { className: "dsha_badge" }, triggerNameOf(run.trigger)),
888
+ h("span", { className: "dsha_grow" }),
889
+ h("span", { className: "dsha_runstatus", "data-status": run.status }, statusLabel(run.status)),
890
+ h("span", { className: "dsha_rundur" }, fmtDuration(run.durationMs)),
891
+ ),
892
+ openRun === run.id
893
+ ? h(React.Fragment, null,
894
+ h("div", { className: "dsha_runmeta" },
895
+ T("触发:", "Trigger: ") + triggerNameOf(run.trigger)
896
+ + T(" · 退出码 ", " · exit ") + (run.exitCode ?? "-")
897
+ + (run.error ? " · " + run.error : ""),
898
+ ),
899
+ h("pre", { className: "dsha_pre" }, run.output || T("(无输出)", "(no output)")),
900
+ )
901
+ : null,
902
+ )),
903
+ ),
904
+ ),
849
905
 
850
906
  h("p", { className: "dsha_sub", style: { marginTop: 22, fontSize: 11.5 } },
851
907
  T("规则保存在 ", "Rules live in ") + (view.stateDir ?? "") + T(";宿主启动于 ", "; host booted ") + fmtClock(view.bootedAt) + T("。", "."),
package/lib/config.js CHANGED
@@ -30,6 +30,8 @@ export const Config = schema === undefined ? undefined : schema.object({
30
30
  maxOutputChars: schema.natural().default(DEFAULTS.maxOutputChars),
31
31
  /** Default per-run timeout when the action does not set one. */
32
32
  defaultTimeoutMs: schema.natural().default(DEFAULTS.defaultTimeoutMs),
33
+ /** Working directory for shell actions whose rule leaves `cwd` empty; empty = user home. */
34
+ defaultCwd: schema.string().default(DEFAULTS.defaultCwd),
33
35
  /** Grace period after host boot before `startup` rules run. */
34
36
  startupDelayMs: schema.natural().default(DEFAULTS.startupDelayMs),
35
37
  /** Resolution of the daily / interval scheduler, in seconds. */
package/lib/http.js CHANGED
@@ -9,6 +9,8 @@
9
9
  * @module dsh-automations/http
10
10
  */
11
11
 
12
+ import { homedir } from "node:os";
13
+
12
14
  import { ACTION_CATALOG, normalizeRule, RuleError, summarizeAction, summarizeTrigger, TRIGGER_CATALOG } from "./model.js";
13
15
  import { localDay } from "./util.js";
14
16
  import { VERSION } from "./version.js";
@@ -48,10 +50,10 @@ async function readJsonBody(request) {
48
50
  /**
49
51
  * Build the panel view.
50
52
  *
51
- * @param {{store: object, engine: {busy: Function}, hub: {bootedAt: string}}} parts - live pieces.
53
+ * @param {{store: object, engine: {busy: Function}, hub: {bootedAt: string}, settings: object}} parts - live pieces.
52
54
  * @returns {object}
53
55
  */
54
- export function buildView({ store, engine, hub }) {
56
+ export function buildView({ store, engine, hub, settings }) {
55
57
  const rules = store.list().map((rule) => ({
56
58
  ...rule,
57
59
  triggerSummary: summarizeTrigger(rule.trigger, "zh"),
@@ -66,6 +68,8 @@ export function buildView({ store, engine, hub }) {
66
68
  now: new Date().toISOString(),
67
69
  bootedAt: hub.bootedAt,
68
70
  stateDir: store.dir,
71
+ /** Prefill for a new shell rule's working directory. */
72
+ defaultCwd: settings?.defaultCwd !== undefined && settings.defaultCwd !== "" ? settings.defaultCwd : homedir(),
69
73
  rules,
70
74
  runs,
71
75
  stats: {
@@ -85,10 +89,10 @@ export function buildView({ store, engine, hub }) {
85
89
  /**
86
90
  * Register every route on the authenticated API bridge.
87
91
  *
88
- * @param {{ctx: object, store: object, engine: object, hub: object, log: object}} options
92
+ * @param {{ctx: object, store: object, engine: object, hub: object, settings: object, log: object}} options
89
93
  * @returns {() => void} disposer for all routes.
90
94
  */
91
- export function registerRoutes({ ctx, store, engine, hub, log }) {
95
+ export function registerRoutes({ ctx, store, engine, hub, settings, log }) {
92
96
  const bridge = ctx.get("connection")?.fetch;
93
97
  if (bridge === undefined || typeof bridge.register !== "function") {
94
98
  log?.warn?.("[dsh-automations] connection.fetch unavailable; panel API disabled");
@@ -125,7 +129,7 @@ export function registerRoutes({ ctx, store, engine, hub, log }) {
125
129
  if (typeof dispose === "function") disposers.push(dispose);
126
130
  };
127
131
 
128
- const view = () => buildView({ store, engine, hub });
132
+ const view = () => buildView({ store, engine, hub, settings });
129
133
 
130
134
  route(`${BASE}/state`, ["GET"], async () => ({ body: view() }));
131
135
 
package/lib/index.js CHANGED
@@ -94,7 +94,7 @@ export function apply(ctx, config) {
94
94
  // The panel API needs the authenticated connection bridge; mounting the
95
95
  // route plugin lazily keeps headless compositions (no bridge) loadable.
96
96
  ctx.inject(["connection"], (routeCtx) => {
97
- routeCtx.effect(() => registerRoutes({ ctx: routeCtx, store, engine, hub, log }), "dsh-automations: panel api");
97
+ routeCtx.effect(() => registerRoutes({ ctx: routeCtx, store, engine, hub, settings, log }), "dsh-automations: panel api");
98
98
  });
99
99
 
100
100
  if (settings.promptEnabled) {
package/lib/model.js CHANGED
@@ -157,7 +157,7 @@ export const ACTION_CATALOG = [
157
157
  hintEn: "Spawn a non-interactive local process and capture output/exit code",
158
158
  fields: [
159
159
  { key: "command", label: "命令", labelEn: "Command", type: "textarea", default: "", placeholder: "Get-Date | Out-File $env:USERPROFILE\\boot.log -Append", required: true },
160
- { key: "cwd", label: "工作目录(可空)", labelEn: "Working directory (optional)", type: "text", default: "" },
160
+ { key: "cwd", label: "工作目录(可空)", labelEn: "Working directory (optional)", type: "text", default: "", hint: "留空 = 用户主目录;新建规则时会预填默认值", hintEn: "Empty = user home directory; new rules prefill the default" },
161
161
  {
162
162
  key: "shell",
163
163
  label: "解释器",
package/lib/settings.js CHANGED
@@ -13,6 +13,8 @@ export const DEFAULTS = Object.freeze({
13
13
  maxRunHistory: 200,
14
14
  maxOutputChars: 8000,
15
15
  defaultTimeoutMs: 120000,
16
+ /** Working directory for shell actions whose rule leaves `cwd` empty; "" = user home. */
17
+ defaultCwd: "",
16
18
  startupDelayMs: 2000,
17
19
  tickSeconds: 15,
18
20
  promptEnabled: true,
@@ -46,6 +48,7 @@ export function resolveConfig(raw) {
46
48
  maxRunHistory: positiveInteger(config.maxRunHistory, DEFAULTS.maxRunHistory),
47
49
  maxOutputChars: positiveInteger(config.maxOutputChars, DEFAULTS.maxOutputChars),
48
50
  defaultTimeoutMs: positiveInteger(config.defaultTimeoutMs, DEFAULTS.defaultTimeoutMs),
51
+ defaultCwd: typeof config.defaultCwd === "string" ? config.defaultCwd.trim() : "",
49
52
  startupDelayMs: Number.isSafeInteger(startupDelay) && startupDelay >= 0 ? startupDelay : DEFAULTS.startupDelayMs,
50
53
  tickSeconds: positiveInteger(config.tickSeconds, DEFAULTS.tickSeconds),
51
54
  promptEnabled: config.promptEnabled !== false,
package/lib/version.js CHANGED
@@ -5,4 +5,4 @@
5
5
  * @module dsh-automations/version
6
6
  */
7
7
 
8
- export const VERSION = "0.1.0";
8
+ export const VERSION = "0.2.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-automations",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lifecycle and event driven automations for DeepSeek Harness: run a command, an HTTP request or a log entry when DSH boots, a session starts, ends or goes idle, a message arrives, a tool finishes, or on a daily/interval schedule.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,8 +45,8 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@deepseek-ai/cordis": "^4.0.1",
48
- "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.2",
49
- "@deepseek-ai/dsh-tools": "^0.2.0-rc.2"
48
+ "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.2 || ^0.2.1-rc.1 || ^0.2.2-rc.1",
49
+ "@deepseek-ai/dsh-tools": "^0.2.0-rc.2 || ^0.2.1-rc.1 || ^0.2.2-rc.1"
50
50
  },
51
51
  "dsh": {
52
52
  "bundle": {
@@ -62,7 +62,9 @@
62
62
  "compatibility": {
63
63
  "dshReleases": {
64
64
  "0.2.0-rc.2": "compatible",
65
- "0.2.1-rc.3": "compatible"
65
+ "0.2.1-rc.3": "compatible",
66
+ "0.2.1-rc.2": "compatible",
67
+ "0.2.2-rc.1": "compatible"
66
68
  }
67
69
  }
68
70
  },
@@ -73,4 +75,4 @@
73
75
  "scripts": {
74
76
  "test": "node test/selftest.mjs"
75
77
  }
76
- }
78
+ }