dsh-automations 0.1.0 → 0.2.0
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 +8 -0
- package/README.md +7 -4
- package/lib/actions.js +8 -3
- package/lib/client.js +94 -38
- package/lib/config.js +2 -0
- package/lib/http.js +9 -5
- package/lib/index.js +1 -1
- package/lib/model.js +1 -1
- package/lib/settings.js +3 -0
- package/lib/version.js +1 -1
- package/package.json +1 -1
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
|
-
-
|
|
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 :
|
|
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 = {
|
|
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:
|
|
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
|
-
|
|
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: "
|
|
813
|
-
h("
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-automations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|