dsh-task-dispatch-table 0.0.0 → 0.1.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/README.md +95 -31
- package/README.zh.md +113 -0
- package/assets/icon-scheduler.svg +1 -0
- package/cordis.patch.yml +9 -0
- package/dist/attachment-allowlist.d.ts +24 -0
- package/dist/attachment-allowlist.js +41 -0
- package/dist/calendar-plan.d.ts +55 -0
- package/dist/calendar-plan.js +104 -0
- package/dist/client.js +72655 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +80 -0
- package/dist/deps.d.ts +25 -0
- package/dist/deps.js +54 -0
- package/dist/dispatch.d.ts +133 -0
- package/dist/dispatch.js +509 -0
- package/dist/event-bus.d.ts +16 -0
- package/dist/event-bus.js +67 -0
- package/dist/event-catalog.d.ts +42 -0
- package/dist/event-catalog.js +60 -0
- package/dist/host.d.ts +299 -0
- package/dist/host.js +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +0 -0
- package/dist/receipt.d.ts +38 -0
- package/dist/receipt.js +236 -0
- package/dist/reconcile.d.ts +105 -0
- package/dist/reconcile.js +641 -0
- package/dist/runtime-index.d.ts +102 -0
- package/dist/runtime-index.js +340 -0
- package/dist/schedule-next.d.ts +44 -0
- package/dist/schedule-next.js +216 -0
- package/dist/scheduler.d.ts +82 -0
- package/dist/scheduler.js +639 -0
- package/dist/store.d.ts +461 -0
- package/dist/store.js +845 -0
- package/dist/submit.d.ts +1 -0
- package/dist/submit.js +60 -0
- package/dist/task-assets.d.ts +124 -0
- package/dist/task-assets.js +380 -0
- package/dist/task-sort.d.ts +27 -0
- package/dist/task-sort.js +59 -0
- package/dist/tasks.d.ts +191 -0
- package/dist/tasks.js +527 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +67 -6
- package/index.js +0 -7
package/README.md
CHANGED
|
@@ -1,48 +1,112 @@
|
|
|
1
|
-
# dsh-task-dispatch-table
|
|
1
|
+
# dsh-task-dispatch-table · Scheduled task dispatcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
One dsh host plugin: **a single table of task definitions drives your recurring agent work** — when a task is due, it is dispatched as an **independent dsh session** that an agent actually executes. The scheduling itself is pure program logic and **never burns a token**.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **Zero model involvement in scheduling** — tick → check the schedule window → check dependencies → dispatch. Judgment-heavy work goes to the agent session; deciding *when to run* costs nothing
|
|
10
|
+
- **Three schedule modes** — one-off, cron-style daily/weekly, or every-N-hours intervals, each with an **allowed-delay window** so a busy host catches up instead of silently skipping
|
|
11
|
+
- **Dependencies declared by the downstream** — a task can require upstream tasks to have succeeded; the exact upstream instance and its outputs are **frozen at dispatch time** and handed to the downstream session
|
|
12
|
+
- **Every run is an independent session** with its own status, token usage, artifacts and event log — inspectable afterwards, retryable on failure, serialized per task
|
|
13
|
+
- **A full UI** — task list, per-task runs and logs, a cross-task execution timeline, a monthly calendar of planned and actual runs, a split-panel editor, and a settings page
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|---|---|---|
|
|
13
|
-
| 1 | 每 N 小时巡检容器与主机资源,异常立即上报 | 高频、纯阈值判断,不该烧 token |
|
|
14
|
-
| 2 | 每天查镜像升级,汇报「更新了什么 + 是否建议升」 | 需要**判断**(读 release notes 评风险) |
|
|
15
|
-
| 3 | 每天汇报 GitHub 上自己的 PR / Issue 进展 | 归纳 |
|
|
16
|
-
| 4 | 每天列 GitHub 趋势项目,给出「新增 / 替换 / 观望」建议 | 重度判断 |
|
|
15
|
+
## Install
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
```bash
|
|
18
|
+
dsh plugin --profile web add dsh-task-dispatch-table
|
|
19
|
+
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
Every release is published to npm, so it works right after installing — no extra configuration and no local build. The UI copy is **bilingual** and follows dsh's interface language (Settings → General → Language).
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## How it works
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
- **调度器零大模型介入**:定时 tick → 判时间 → 判前置 → 派发,全部是程序逻辑。
|
|
26
|
-
- **下依赖由下游声明**:加下游不改上游(Airflow / GitHub Actions `needs` 的模型)。
|
|
27
|
-
- **控制平面与数据平面分离**:agent 只写产物,**任务状态只由调度器写**。
|
|
28
|
-
- **状态存 SQLite**(原子领取),任务定义存 JSON。
|
|
25
|
+
The plugin keeps two layers of state:
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
| Object | What it is | What it decides |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| **Task definition** | One row per scheduled unit: title, prompt, schedule, attachments, workspace, model, dependencies, on/off switch | *Whether and how* a task should run |
|
|
30
|
+
| **Task instance** | One row per actual run: status, planned/actual time, session id, token usage, artifacts | *What happened* in a run |
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The scheduler is a plain loop: on every tick it computes which tasks are due, claims them atomically in SQLite, dispatches each as a new dsh session in the task's workspace, then tracks the session via a receipt tool the session is required to call when done. Missed slots (host was down past the allowed delay) are recorded and marked `skipped` instead of disappearing.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|---|---|
|
|
36
|
-
| 项目进度、未决项、下一步 | [`docs/PROGRESS.md`](docs/PROGRESS.md) |
|
|
37
|
-
| 三层架构与职责边界 | [`docs/design/architecture.md`](docs/design/architecture.md) |
|
|
38
|
-
| 决策与理由 | [`docs/design/decisions.md`](docs/design/decisions.md) |
|
|
39
|
-
| 状态机、依赖语义、必补机制 | [`docs/design/state-machine.md`](docs/design/state-machine.md) |
|
|
40
|
-
| 为什么任务手册是 MD 而不是 skill | [`docs/design/task-manual-vs-skill.md`](docs/design/task-manual-vs-skill.md) |
|
|
34
|
+
## Tasks
|
|
41
35
|
|
|
42
|
-
|
|
36
|
+
### Creating and editing
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
Press **New task** (or **Edit** on a card) and the editor opens as a layout split panel, not a floating drawer: basics, schedule, prompt (with version history), advanced options, attachments and upstream tasks. Attachments are either **links** to files already in the workspace or **uploads** (stored in the task's own directory, up to 20 MB each).
|
|
41
|
+
|
|
42
|
+
The footer switches between **View / Edit** modes: View is a read-only rendering of the task — configuration, last run, and the prompt — for checking a task without risking an accidental edit.
|
|
43
|
+
|
|
44
|
+
### Schedules
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
Three modes, each showing a plain-language preview of the next run:
|
|
49
|
+
|
|
50
|
+
- **Once** — runs at the given moment, then never again
|
|
51
|
+
- **Recurring** — daily / weekly / monthly at a given time
|
|
52
|
+
- **Interval** — every N hours (or days), optionally restricted to selected weekdays
|
|
53
|
+
|
|
54
|
+
Every mode has an **allowed delay**: if the host was busy or down when the moment came, the run still fires within the window; past the window the slot is recorded as `skipped` (with the reason) rather than lost silently. A task card's **Run now** button triggers an extra run immediately, outside the schedule.
|
|
55
|
+
|
|
56
|
+
### Dependencies
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
A task declares which upstream tasks must have succeeded before it may run (the Airflow / GitHub Actions `needs` model — **the downstream declares, so adding a downstream never touches the upstream**). When the task fires, the plugin freezes *which upstream instance it matched* together with that instance's outputs and passes them into the new session, so the downstream agent reads exactly the data its run was keyed to — even if the upstream has re-run since.
|
|
61
|
+
|
|
62
|
+
## Runs and records
|
|
63
|
+
|
|
64
|
+
### Per task
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
Expanding a task opens three panels: **basic info** (configuration side by side with the last run), **runs** (one row per instance: status, planned vs actual time, duration, token usage, artifacts, and a button to open the archived session), and **logs** (the plugin's own diagnostic log for that task — missed slots, missing attachments, manual runs).
|
|
69
|
+
|
|
70
|
+
### Execution timeline
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
The **Records** tab is the ledger across *all* tasks, grouped by day and filtered by time range, workspace, status and task. Each entry expands in place to its full artifact list and raw event log (state changes, dispatch record, receipt); the archived session itself opens only when you press **View session**.
|
|
75
|
+
|
|
76
|
+
### Calendar
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
The **Schedule** tab is a month view. Days show two kinds of markers: **actual runs** from the instance table (solid dots in status colors) and **planned runs** computed from the current task definitions (dashed dots). Clicking a day expands that week in place with the full run details — the same entry blocks as the timeline, expandable again down to artifacts and events.
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
The **Settings** tab is the primary place to configure the plugin. Each item shows whether it was customized and can be reset individually; changes are staged and written only on **Save**; the log block below shows the plugin's diagnostic log with level coloring and auto-refresh.
|
|
87
|
+
|
|
88
|
+
| Item | Default | Meaning |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| Tick interval | 60 s | How often the scheduler scans the task table |
|
|
91
|
+
| Run log retention | 30 days | How long diagnostic log rows are kept |
|
|
92
|
+
| Temp attachment retention | 7 days | How long uploaded temp files are kept |
|
|
93
|
+
| Default model | follow host | Model used when a task does not pick one |
|
|
94
|
+
| Dispatch grace | 60 s | Max wait for a dispatched session to appear |
|
|
95
|
+
| Run lease | 1800 s | How long a silent session keeps its lease before being reclaimed |
|
|
96
|
+
| Observation window | 300 s | How long an unknown-status session is watched before being judged dead |
|
|
97
|
+
|
|
98
|
+
Configuration is stored in the plugin's own state database, so it survives host config resets; defaults apply underneath.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install
|
|
104
|
+
npm run build # builds host first, then client
|
|
105
|
+
npm run typecheck
|
|
106
|
+
npm run smoke # smoke tests against dist/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> **`dist/` is a build artefact committed to git** — dsh installs plugins from git/npm without running build scripts, so after changing `src/` you must run `npm run build` and commit `dist/` with it, or the change will not take effect.
|
|
46
110
|
|
|
47
111
|
## License
|
|
48
112
|
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# dsh-task-dispatch-table · 定时任务调度器
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
一个 dsh 宿主层插件:用**一张任务定义表驱动周期性的 agent 工作**——任务到点后被派发成一个**独立的 dsh 会话**,由 agent 真正执行。调度本身是纯程序逻辑,**一行 token 都不烧**。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
- **调度零大模型介入**——tick → 判时间窗 → 判依赖 → 派发。判断类工作交给 agent 会话,「什么时候该跑」这个决定不花一分钱
|
|
10
|
+
- **三种排期模式**——单次、cron 式每天/每周、每 N 小时间隔,每种都带**允许延迟窗口**:宿主忙不过来时补跑,而不是悄悄跳过
|
|
11
|
+
- **依赖由下游声明**——任务可以要求上游任务执行成功才放行;派发瞬间**冻结命中的上游实例及其产出**,随任务下传给下游会话
|
|
12
|
+
- **每次执行都是独立会话**——有自己的状态、token 用量、产出物和事件流水,事后可查、失败可重试、任务内串行
|
|
13
|
+
- **完整的界面**——任务列表、单任务执行记录与日志、跨任务执行流水账、月历日程、分栏编辑器、设置页
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
dsh plugin --profile web add dsh-task-dispatch-table
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
每个版本都发布到 npm,装完即用——无需额外配置、无需本地构建。界面文案**中英双语**,跟随 dsh 的界面语言(设置 → 通用 → 语言)。
|
|
22
|
+
|
|
23
|
+
## 它是怎么工作的
|
|
24
|
+
|
|
25
|
+
插件维护两层状态:
|
|
26
|
+
|
|
27
|
+
| 对象 | 代表什么 | 决定什么 |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| **任务定义** | 每个调度单元一行:标题、提示词、排期、附件、工作区、模型、依赖、开关 | 任务**该不该跑、怎么跑** |
|
|
30
|
+
| **执行记录** | 每次实际触发一行:状态、计划/实际时间、会话 id、token 用量、产出物 | 这趟**实际发生了什么** |
|
|
31
|
+
|
|
32
|
+
调度器是一个朴素的循环:每个 tick 算出哪些任务到点,在 SQLite 里原子领取,把每个任务派发成其工作区里的一个新 dsh 会话,再通过一个回执工具跟踪会话(会话干完活必须调它)。错过的刻度(宿主停机超出允许延迟)会补一条记录并标 `skipped`(带原因),而不是无声消失。
|
|
33
|
+
|
|
34
|
+
## 任务
|
|
35
|
+
|
|
36
|
+
### 新建与编辑
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
点**新建任务**(或卡片上的**编辑**),编辑器以分栏形式打开(不是浮层抽屉):基础、排期、提示词(带版本)、高级选项、附加文件、前置任务。附加文件两种来路:**link**(工作区已有文件,只记路径)与 **upload**(落盘到任务自己的目录,单个不超过 20 MB)。
|
|
41
|
+
|
|
42
|
+
底栏可在**查看 / 编辑**两档切换:查看档 = 只读人话视图——配置、上次执行、提示词,用于检查任务而不怕误改。
|
|
43
|
+
|
|
44
|
+
### 排期
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
三种模式,每种都用一句人话预览下次执行:
|
|
49
|
+
|
|
50
|
+
- **单次**——到点执行一次,之后不再重复
|
|
51
|
+
- **周期**——每天 / 每周 / 每月的固定时刻
|
|
52
|
+
- **间隔**——每 N 小时(或天),可选限定星期几
|
|
53
|
+
|
|
54
|
+
每种模式都带**允许延迟**:到点时宿主忙或停机了,窗口内仍然补跑;超出窗口则该刻度记 `skipped`(带原因),不是无声丢掉。任务卡片上的**立即执行**按钮可以在排期之外马上补跑一趟。
|
|
55
|
+
|
|
56
|
+
### 依赖(前置任务)
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
任务声明哪些上游任务必须执行成功它才放行(Airflow / GitHub Actions `needs` 的模型——**由下游声明,加下游不动上游**)。任务触发时,插件冻结「命中了哪条上游实例」及其产出,一并传进新会话——下游 agent 读到的就是它这趟所键定的那份数据,哪怕上游后来又跑过。
|
|
61
|
+
|
|
62
|
+
## 执行与记录
|
|
63
|
+
|
|
64
|
+
### 单任务
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
展开任务有三个面板:**基础信息**(任务配置与上次执行并排)、**执行记录**(每次执行一行:状态、计划/实际时间、时长、token 用量、产出物,以及打开归档会话的按钮)、**日志**(该任务专属的诊断日志——错过的刻度、附件缺失、手动执行等)。
|
|
69
|
+
|
|
70
|
+
### 执行记录(流水账)
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
顶部 **执行记录** tab 是全部任务的总账:按天分组,支持时间 / 工作区 / 状态 / 任务四维过滤。每条就地展开,给出完整产出物列表与原始事件流水(状态迁移、派发记录、回执);按**查看会话**才打开归档会话本体。
|
|
75
|
+
|
|
76
|
+
### 任务日程(月历)
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
顶部 **任务日程** tab 是月视图。格子里有两类标记:**已执行**(来自实例表的状态色实心点)与**计划**(按当前任务定义现算的虚线空心点)。点某天,那一天所在的一周就地拉开,铺开全部执行信息——与流水账同款条目块,可再展开到产出物与事件。
|
|
81
|
+
|
|
82
|
+
## 配置
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
顶部 **设置** tab 是配置的正式主场。每项标注是否「自定义」、可单独恢复默认;改动先暂存、点**保存**才写入;下方日志块带 level 语义着色与自动刷新。
|
|
87
|
+
|
|
88
|
+
| 项 | 默认 | 含义 |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| 循环间隔 | 60 秒 | 调度器多久巡检一次任务表 |
|
|
91
|
+
| 运行日志保留 | 30 天 | 诊断日志留多久 |
|
|
92
|
+
| 临时附件保留 | 7 天 | 上传的临时文件留多久 |
|
|
93
|
+
| 默认模型 | 跟随宿主 | 任务未指定模型时用哪个 |
|
|
94
|
+
| 派发宽限 | 60 秒 | 派发后等会话出现的最长时间 |
|
|
95
|
+
| 运行租约 | 1800 秒 | 运行中会话多久没动静才被收回 |
|
|
96
|
+
| 观察宽限 | 300 秒 | 会话状态未知时观察多久再判死 |
|
|
97
|
+
|
|
98
|
+
配置存在插件自有状态库里,不随宿主配置重置而丢;其下始终有默认值兜底。
|
|
99
|
+
|
|
100
|
+
## 开发
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install
|
|
104
|
+
npm run build # 先构建 host,再构建 client
|
|
105
|
+
npm run typecheck
|
|
106
|
+
npm run smoke # 直接测 dist/ 产物
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> **`dist/` 是提交进 git 的构建产物**——dsh 从 git/npm 安装插件时不跑构建脚本,所以改了 `src/` 必须重新 `npm run build` 并把 `dist/` 一并提交,否则改动不生效。
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
[MIT](LICENSE) © 2026 cq-guojia
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1791449355917" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17284" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M178.208 496.97q0-131.5 0.014-263c0.01-24.871 15.029-44.548 38.23-50.326a60.945 60.945 0 0 1 14.869-1.461q91.5 0.048 183 0.107c3.775 0.008 5.281-0.816 6.278-4.778a75.3 75.3 0 0 1 146.207-0.065c0.981 3.885 2.369 4.859 6.223 4.851q91-0.2 182-0.089c32.054 0.006 53.173 21.13 53.176 53.2q0.014 156.249-0.086 312.5c0 3.946 1.255 6.117 4.5 8.361 47.854 33.093 77.1 78.194 84.6 136.06 12.265 94.688-42.181 180.843-132.377 211.17-85.613 28.787-183-7.793-228.272-85.958-2.46-4.248-5.014-5.451-9.71-5.444q-148.5 0.217-297 0.093c-24.663-0.009-44.133-14.829-50.092-37.868a60.7 60.7 0 0 1-1.57-15.351q0.03-131.003 0.01-262.002z m525 396.066c13.055 0.473 25.916-1.181 38.624-3.927 87.387-18.883 147.2-102.545 136.093-191.412-6.64-53.11-32.467-95.109-77.045-125.055-29.462-19.791-62.2-29.26-97.679-29.254a171.144 171.144 0 0 0-54.811 9.072c-80.684 27.3-130.318 108.368-116.933 192.6 7.928 49.889 33.087 89.694 75.02 118.237a167.82 167.82 0 0 0 96.73 29.74zM504.155 287.325c-33.824-0.042-67.648 0.006-101.472-0.156a60.872 60.872 0 0 1-58.583-46.234c-2.753-11.021-1.82-22.176-1.764-33.285 0.022-4.384-1.085-5.575-5.534-5.553-36.824 0.185-73.649 0.08-110.474 0.105-14.541 0.01-28.121 13.667-28.122 28.265q-0.015 266.688 0 533.375c0 14.87 13.589 28.37 28.524 28.37q136.218 0 272.436-0.013c1.665 0 3.33-0.075 4.995-0.114 4.663 0.04 9.334-0.076 13.987 0.165 4.16 0.216 5.578-0.291 3.724-5.121a193.841 193.841 0 0 1 13.625-166.619c18.806-32.068 44.446-57.094 77.265-74.47 33.541-17.759 69.269-25.233 107.289-22.014a194.253 194.253 0 0 1 55.553 12.8c4.059 1.606 8.127 3.188 12.186 4.78a5.362 5.362 0 0 0 0.363-0.995q0.048-155.944 0.023-311.887c-0.01-12.764-13.973-26.459-26.75-26.5-37.32-0.117-74.64 0-111.96-0.08-3.208-0.007-4.408 0.869-4.32 4.213 0.2 7.658 0.434 15.35-0.042 22.985-1.037 16.636-7.568 30.99-20 42.138-12.55 11.25-27.605 15.945-44.472 15.792-25.496-0.228-50.987 0.013-76.477 0.053zM493.3 266.2c30.316 0 60.631 0.1 90.946-0.034a39.594 39.594 0 0 0 39.177-32.677c1.548-8.1 0.2-16.3 0.879-24.406 0.41-4.861-1.115-6.063-5.956-6-22.15 0.284-44.306 0.048-66.459 0.2-3.549 0.025-4.55-0.713-3.913-4.4 0.766-4.428 0.023-8.94-0.747-13.393-4.654-26.905-31.624-47.237-58.585-44.11-29.559 3.429-51.54 28.72-49.157 57.155 0.387 4.618-1.128 4.732-4.632 4.716-22.486-0.1-44.974 0.005-67.46-0.115-3.168-0.017-4.419 0.725-4.257 4.115 0.269 5.65 0.06 11.323 0.076 16.987 0.069 24.929 17.116 41.928 42.139 41.955q43.972 0.051 87.949 0.007z" fill="#343741" p-id="17285"></path><path d="M504.155 792.084c-1.665 0.04-3.33 0.115-4.994 0.115q-136.218 0.014-272.436 0.013c-14.935 0-28.523-13.5-28.524-28.37q-0.012-266.688 0-533.375c0-14.6 13.58-28.255 28.122-28.265 36.825-0.025 73.65 0.08 110.474-0.105 4.449-0.022 5.556 1.169 5.534 5.553-0.056 11.109-0.989 22.264 1.764 33.285a60.872 60.872 0 0 0 58.588 46.234c33.823 0.162 67.648 0.113 101.472 0.155l0.014 81.793q-38.488 0.059-76.977 0.093c-5.081 0-9.6 1.287-13.011 5.154-4.1 4.64-5.311 10.051-2.754 15.776 2.687 6.018 7.557 9.08 14.243 9.09q39.237 0.062 78.475 0.141l0.013 110.714c-1 0.04-2 0.114-3 0.114q-37.5 0.011-75 0.009c-5.706 0-10.492 1.951-13.488 7.005-6.321 10.662 0.534 22.878 12.988 22.951 26.164 0.153 52.329 0.118 78.493 0.159q0.006 125.877 0.004 251.761z m-244.78-148.07c-7.308 0.14-11.729 2.743-14.414 7.886-2.978 5.7-2.728 11.535 1.63 16.294 11.916 13.013 24.349 25.555 37.421 37.4 6.113 5.539 14.073 5.044 19.865-0.691q31.95-31.632 63.606-63.56c3.712-3.73 5.5-8.333 4.281-13.682-2.7-11.843-16.526-16.011-25.317-7.384-16.282 15.978-32.314 32.211-48.451 48.336-4.469 4.465-4.472 4.464-9.1-0.162q-9.717-9.712-19.428-19.429c-3.194-3.203-6.878-5.222-10.093-5.008z m-1.742-140.884c-2.983-0.316-5.888 0.881-8.486 2.8-7.794 5.763-8.082 16.038-0.4 23.909 10.585 10.842 21.347 21.513 32.123 32.166 8.6 8.5 16.578 8.417 25.125-0.115q23.689-23.646 47.338-47.331c4.827-4.828 9.827-9.5 14.422-14.539 7.407-8.123 4.976-19.822-4.775-24.059-6.061-2.634-11.868-1.41-17.229 3.924-16.289 16.209-32.58 32.418-48.7 48.793-2.795 2.839-4.418 2.791-7.109-0.055-6.634-7.014-13.6-13.71-20.422-20.551-3.188-3.198-6.85-5.253-11.887-4.942z m0.851-141.042c-7.321-0.022-12.745 4.045-14.726 10.8-1.862 6.345 0.516 11.458 4.849 15.836 10.893 11.007 21.79 22.014 32.859 32.843 7.993 7.819 16.166 7.664 24.043-0.194q30.417-30.341 60.751-60.765a24.849 24.849 0 0 0 3.486-4.216 14.712 14.712 0 0 0-8.939-22.175c-6.064-1.5-10.976 0.47-15.32 4.853-15.825 15.966-31.9 31.684-47.558 47.814-3.686 3.8-5.624 3.418-8.932-0.171-6.315-6.851-13.1-13.267-19.7-19.853-3.072-3.06-6.64-4.991-10.813-4.772zM440.224 689.2c4.823 0 9.647 0.06 14.468-0.013 8.831-0.135 15.44-6.519 15.493-14.874s-6.553-14.977-15.278-15.075q-14.716-0.164-29.435 0c-8.721 0.1-15.312 6.743-15.244 15.108a15.074 15.074 0 0 0 15.029 14.827c4.987 0.112 9.978 0.021 14.967 0.027z" fill="#707A9A" p-id="17286"></path><path d="M504.169 369.117l-0.014-81.793c25.489-0.039 50.981-0.28 76.468-0.049 16.867 0.153 31.922-4.542 44.472-15.792 12.436-11.148 18.967-25.5 20-42.138 0.476-7.635 0.243-15.327 0.042-22.985-0.088-3.343 1.111-4.22 4.32-4.213 37.32 0.084 74.64-0.038 111.96 0.08 12.777 0.04 26.74 13.734 26.75 26.5q0.118 155.943-0.023 311.887a5.362 5.362 0 0 1-0.363 0.995c-4.059-1.591-8.127-3.173-12.186-4.78a194.253 194.253 0 0 0-55.553-12.8c-38.02-3.219-73.748 4.255-107.289 22.014-32.818 17.376-58.459 42.4-77.265 74.47a193.841 193.841 0 0 0-13.621 166.617c1.854 4.83 0.436 5.336-3.724 5.121-4.653-0.241-9.324-0.125-13.987-0.166V540.323c10.975-0.04 21.95-0.064 32.924-0.125 9.279-0.052 16-6.222 16.111-14.744 0.117-8.78-6.635-15.189-16.111-15.243-10.974-0.062-21.949-0.086-32.924-0.126l-0.013-110.715c2-0.05 3.992-0.141 5.987-0.142 28.136-0.01 56.272 0.038 84.408-0.032 11.582-0.029 19.065-11.7 13.787-21.562-3.187-5.955-8.473-8.464-15.258-8.451-29.631 0.055-59.267-0.031-88.898-0.066z m179.855 30.1h20.949c8.643-0.182 15.019-6.266 15.222-14.525a15.163 15.163 0 0 0-14.555-15.36q-21.436-0.472-42.889 0c-8.475 0.2-14.917 7.726-14.515 15.934 0.387 7.9 6.869 13.839 15.34 13.932 6.814 0.079 13.631 0.019 20.448 0.022z" fill="#5A6286" p-id="17287"></path><path d="M703.207 893.037a167.82 167.82 0 0 1-96.732-29.744c-41.933-28.543-67.092-68.348-75.02-118.237-13.384-84.229 36.249-165.3 116.933-192.6a171.144 171.144 0 0 1 54.811-9.072v19.688c-4.15 0.795-7.9 2.316-10.627 5.753-7.231 9.106-2.236 22.771 10.627 24.5 0 3.46 0.193 6.933-0.041 10.377-0.342 5.035 1.152 10.186-0.915 15.111-6.652 2.63-8.427 5.1-8.452 12.381-0.055 16.12-0.127 32.241 0.066 48.358 0.043 3.617-0.911 5.421-4.382 7.068-12.62 5.986-19.554 16.3-20.161 30.254-0.845 19.526 12.963 34.126 33.886 36.479v89.722c-4.15 0.795-7.9 2.316-10.627 5.753-7.231 9.106-2.236 22.771 10.627 24.5q0.005 9.857 0.007 19.709z m-138.875-189.8a14.97 14.97 0 1 0 14.841 14.968 15.07 15.07 0 0 0-14.841-14.968z m3.777-55.248a14.822 14.822 0 0 0 14.636 15.178 14.963 14.963 0 1 0 0.433-29.922 14.814 14.814 0 0 0-15.069 14.744z m66.327-65.889a14.839 14.839 0 0 0-15.195 14.628 14.967 14.967 0 1 0 29.931 0.456 14.829 14.829 0 0 0-14.736-15.084z m-66.33 205.873a14.839 14.839 0 0 0 14.629 15.194 14.967 14.967 0 1 0 0.455-29.93 14.829 14.829 0 0 0-15.084 14.739z m65.978 66.327a14.819 14.819 0 0 0 15.085-14.73 14.963 14.963 0 1 0-29.924-0.248 14.812 14.812 0 0 0 14.839 14.978z" fill="#FEE180" p-id="17288"></path><path d="M703.207 893.037v-19.7c9.888-0.921 15.832-6.46 15.97-14.882 0.141-8.621-5.9-14.439-15.97-15.377v-89.725c14.694-0.5 26-6.73 32.443-20.183 1.757-3.67 3.645-4.679 7.449-4.635 16.646 0.193 33.3 0.161 49.942 0.043 7.919-0.056 12.5-5.221 11.329-12.318-0.879-5.335-5-8.419-11.741-8.446-16.648-0.068-33.3-0.089-49.943 0.031-3.306 0.024-5.324-0.538-6.771-4.092a30.837 30.837 0 0 0-17.178-17.226c-3.462-1.433-4.2-3.344-4.174-6.719 0.143-16.147 0.106-32.3 0.047-48.445-0.033-8.935-3.327-12.2-12.357-12.541 2.067-4.925 0.573-10.075 0.915-15.111 0.234-3.444 0.042-6.917 0.041-10.377 9.888-0.921 15.832-6.46 15.97-14.881 0.141-8.62-5.9-14.439-15.97-15.377v-19.688c35.482-0.005 68.216 9.463 97.679 29.254 44.579 29.946 70.405 71.945 77.045 125.055 11.111 88.868-48.705 172.53-136.093 191.412-12.717 2.746-25.577 4.401-38.633 3.928z m155.965-175.052a14.967 14.967 0 1 0-14.622 15.186 15.067 15.067 0 0 0 14.622-15.186zM840.3 647.977a14.813 14.813 0 0 0-15.076-14.733 14.963 14.963 0 1 0 0.452 29.923 14.823 14.823 0 0 0 14.624-15.19z m-66.146-65.869a14.815 14.815 0 0 0-14.914 14.9 14.964 14.964 0 1 0 29.928-0.1 14.815 14.815 0 0 0-15.011-14.8zM840.3 788.27a14.814 14.814 0 0 0-14.786-15.029 14.963 14.963 0 1 0-0.127 29.926A14.8 14.8 0 0 0 840.3 788.27z m-66.175 66.03a14.824 14.824 0 0 0 15.046-14.77 14.965 14.965 0 1 0-29.929-0.166 14.818 14.818 0 0 0 14.883 14.936z" fill="#FED85A" p-id="17289"></path><path d="M493.3 266.2q-43.974 0.006-87.948 0c-25.023-0.027-42.069-17.026-42.139-41.955-0.016-5.663 0.194-11.336-0.076-16.987-0.162-3.39 1.089-4.132 4.257-4.115 22.486 0.121 44.973 0.015 67.46 0.115 3.5 0.016 5.019-0.1 4.632-4.716-2.382-28.435 19.6-53.726 49.157-57.155 26.961-3.128 53.93 17.2 58.585 44.11 0.77 4.453 1.514 8.964 0.747 13.393-0.637 3.683 0.363 4.421 3.913 4.4 22.153-0.155 44.309 0.081 66.459-0.2 4.841-0.062 6.365 1.139 5.956 6-0.683 8.106 0.669 16.305-0.879 24.406a39.594 39.594 0 0 1-39.177 32.677c-30.319 0.119-60.635 0.027-90.947 0.027z" fill="#F5715A" p-id="17290"></path><path d="M504.169 369.117c29.633 0.035 59.267 0.121 88.9 0.066 6.786-0.013 12.071 2.5 15.258 8.451 5.278 9.862-2.2 21.533-13.787 21.562-28.136 0.071-56.272 0.022-84.408 0.032-2 0-3.991 0.092-5.987 0.142l-78.475-0.14c-6.686-0.01-11.556-3.072-14.243-9.09-2.556-5.725-1.345-11.136 2.754-15.776 3.416-3.867 7.93-5.157 13.011-5.154q38.489 0.022 76.977-0.093z" fill="#343741" p-id="17291"></path><path d="M259.375 644.014c3.215-0.211 6.9 1.805 10.093 5.008q9.7 9.728 19.428 19.429c4.629 4.626 4.632 4.627 9.1 0.162 16.137-16.125 32.169-32.358 48.451-48.336 8.791-8.627 22.613-4.459 25.317 7.384 1.221 5.349-0.569 9.951-4.281 13.682q-31.714 31.869-63.606 63.56c-5.793 5.735-13.752 6.23-19.865 0.691-13.072-11.845-25.5-24.387-37.421-37.4-4.358-4.759-4.608-10.591-1.63-16.294 2.685-5.143 7.106-7.746 14.414-7.886zM257.633 503.13c5.037-0.311 8.7 1.744 11.889 4.945 6.817 6.841 13.788 13.537 20.422 20.551 2.692 2.846 4.315 2.894 7.109 0.055 16.122-16.375 32.412-32.584 48.7-48.793 5.361-5.335 11.168-6.558 17.229-3.924 9.751 4.236 12.183 15.935 4.775 24.059-4.6 5.04-9.6 9.711-14.422 14.539q-23.663 23.67-47.335 47.331c-8.547 8.531-16.528 8.613-25.125 0.115-10.777-10.653-21.538-21.324-32.123-32.166-7.685-7.871-7.4-18.147 0.4-23.909 2.593-1.922 5.498-3.12 8.481-2.803zM258.484 362.088c4.173-0.218 7.741 1.708 10.81 4.77 6.6 6.587 13.388 13 19.7 19.853 3.309 3.589 5.246 3.97 8.932 0.171 15.654-16.131 31.733-31.848 47.558-47.814 4.344-4.382 9.256-6.35 15.32-4.853a14.712 14.712 0 0 1 8.939 22.175 24.849 24.849 0 0 1-3.486 4.216q-30.346 30.412-60.751 60.765c-7.878 7.858-16.051 8.013-24.043 0.194-11.063-10.828-21.963-21.835-32.856-32.842-4.333-4.378-6.71-9.492-4.849-15.836 1.982-6.755 7.406-10.821 14.726-10.799z" fill="#FEE180" p-id="17292"></path><path d="M504.159 510.085c10.975 0.04 21.949 0.064 32.924 0.126 9.477 0.054 16.228 6.463 16.111 15.243-0.114 8.522-6.831 14.692-16.111 14.744-10.975 0.062-21.949 0.085-32.924 0.125-26.165-0.041-52.33-0.005-78.493-0.159-12.454-0.073-19.309-12.289-12.988-22.951 3-5.055 7.782-7 13.488-7.005q37.5 0 75-0.009c0.996 0.001 1.994-0.074 2.993-0.114zM440.224 689.2c-4.989 0-9.98 0.089-14.967-0.023a15.074 15.074 0 0 1-15.029-14.827c-0.068-8.365 6.523-15.007 15.244-15.108q14.716-0.17 29.435 0c8.725 0.1 15.33 6.722 15.278 15.075s-6.662 14.739-15.493 14.874c-4.821 0.069-9.645 0.009-14.468 0.009zM684.024 399.218c-6.816 0-13.633 0.059-20.449-0.017-8.471-0.094-14.953-6.036-15.34-13.932-0.4-8.208 6.04-15.737 14.515-15.934q21.435-0.5 42.889 0a15.163 15.163 0 0 1 14.555 15.36c-0.2 8.259-6.578 14.343-15.222 14.525h-0.5l-20.448-0.002zM702.247 618.822c9.03 0.344 12.324 3.606 12.357 12.541 0.059 16.148 0.1 32.3-0.047 48.445-0.03 3.374 0.713 5.286 4.174 6.719a30.837 30.837 0 0 1 17.178 17.226c1.447 3.555 3.465 4.116 6.771 4.092 16.647-0.119 33.3-0.1 49.943-0.031 6.741 0.027 10.862 3.111 11.741 8.446 1.169 7.1-3.41 12.262-11.329 12.318-16.647 0.118-33.3 0.15-49.942-0.043-3.8-0.044-5.692 0.965-7.449 4.635-6.439 13.452-17.75 19.685-32.443 20.183-20.925-2.349-34.732-16.951-33.883-36.47 0.607-13.951 7.542-24.268 20.161-30.254 3.471-1.647 4.425-3.45 4.382-7.068-0.192-16.117-0.121-32.239-0.066-48.358 0.025-7.278 1.8-9.751 8.452-12.381zM703.2 843.075c10.067 0.937 16.11 6.756 15.97 15.377-0.137 8.422-6.081 13.961-15.97 14.882-12.864-1.734-17.858-15.4-10.627-24.5 2.732-3.443 6.479-4.964 10.627-5.759zM564.332 703.237a14.97 14.97 0 1 1-15.092 14.718 15.075 15.075 0 0 1 15.092-14.718zM703.2 563.075c10.067 0.938 16.11 6.756 15.97 15.377-0.137 8.422-6.082 13.961-15.97 14.881-12.864-1.734-17.859-15.4-10.627-24.5 2.732-3.442 6.479-4.963 10.627-5.758zM568.109 647.989a14.814 14.814 0 0 1 15.069-14.744 14.963 14.963 0 1 1-0.433 29.922 14.822 14.822 0 0 1-14.636-15.178zM634.436 582.1a14.829 14.829 0 0 1 14.736 15.084 14.967 14.967 0 1 1-29.931-0.456 14.839 14.839 0 0 1 15.195-14.628zM568.106 787.976a14.829 14.829 0 0 1 15.083-14.737 14.967 14.967 0 1 1-0.455 29.93 14.839 14.839 0 0 1-14.628-15.193zM634.084 854.3a14.812 14.812 0 0 1-14.839-14.978 14.963 14.963 0 1 1 29.924 0.248 14.819 14.819 0 0 1-15.085 14.73zM859.172 717.985a14.967 14.967 0 1 1-15.061-14.747 15.072 15.072 0 0 1 15.061 14.747zM840.3 647.977a14.823 14.823 0 0 1-14.624 15.19 14.963 14.963 0 1 1-0.452-29.923 14.813 14.813 0 0 1 15.076 14.733zM774.157 582.108a14.815 14.815 0 0 1 15.014 14.8 14.964 14.964 0 1 1-29.928 0.1 14.815 14.815 0 0 1 14.914-14.9zM840.3 788.27a14.8 14.8 0 0 1-14.913 14.9 14.963 14.963 0 1 1 0.127-29.926A14.814 14.814 0 0 1 840.3 788.27zM774.125 854.3a14.818 14.818 0 0 1-14.883-14.936 14.965 14.965 0 1 1 29.929 0.166 14.824 14.824 0 0 1-15.046 14.77z" fill="#343741" p-id="17293"></path></svg>
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# bundle 补丁:把本 npm 包作为插件插入宿主组合。
|
|
2
|
+
# 格式依据 /tmp/dsh-source packages/boot/app-boot/src/index.ts:279-298(顶层 YAML 数组,
|
|
3
|
+
# 条目 = loader patch 项:insert 列表,见 packages/experimental/agent-team-profile/cordis.patch.yml)。
|
|
4
|
+
# 全部配置项有默认值(见 src/config.ts),也可经 ctx.settings 命名空间
|
|
5
|
+
# dsh-task-dispatch-table 在运行期覆盖(live 生效)。
|
|
6
|
+
- insert:
|
|
7
|
+
- id: dsh-task-dispatch-table
|
|
8
|
+
name: dsh-task-dispatch-table
|
|
9
|
+
config: {}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 附件上传的**共享约束**:体积上限 + 扩展名白名单 + 扩展名解析。
|
|
3
|
+
*
|
|
4
|
+
* 宿主路由(src/index.ts,服务端 415/413 拦截)与浏览器端预检(src/client/task-editor.tsx,
|
|
5
|
+
* 发包前当场拒)**必须用同一份**——两处各写一份必然漂移(白名单改了这边忘了那边)。
|
|
6
|
+
* 客户端打包(tsdown alwaysBundle)会把本模块内联进 client.js,宿主侧 tsc 直编,均无碍。
|
|
7
|
+
*/
|
|
8
|
+
/** 附件上传:体积上限(字节,20MB)。 */
|
|
9
|
+
export declare const ATTACHMENT_MAX_BYTES: number;
|
|
10
|
+
/** 附件上传:允许的常见扩展名(文本/代码/图片/文档)。命中白名单才收。 */
|
|
11
|
+
export declare const ALLOWED_ATTACHMENT_EXT: ReadonlySet<string>;
|
|
12
|
+
/** 取文件名扩展名(小写,无点返回 '')。 */
|
|
13
|
+
export declare const extOf: (name: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* 附件 `ref` 是否**安全**(可以拼进路径):非空、不含 `..`(相对路径上跳)、
|
|
16
|
+
* 不以 `/` 或 `\` 开头(绝对路径)、不含 `\`(Windows 分隔符)。
|
|
17
|
+
*
|
|
18
|
+
* **唯一实现**(2026-09-30 抽象收敛):此前同一规则写了 **4 份**且各有出入——
|
|
19
|
+
* 宿主 zod(`tasks.ts`:拒绝 `..`/绝对/反斜杠)、客户端保存前校验(`task-editor`:多查了开头 `\`)、
|
|
20
|
+
* 真删前防御(`task-assets.removeAttachmentFiles`:没查开头 `\`)、上传定位
|
|
21
|
+
* (`task-assets.locateUploaded`:连反斜杠都没查)。各写一份必然漂移——
|
|
22
|
+
* 「选工作区文件报 422 附件 ref 非法」那类 bug 的温床就在这。
|
|
23
|
+
*/
|
|
24
|
+
export declare const isSafeAttachmentRef: (ref: string) => boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 附件上传的**共享约束**:体积上限 + 扩展名白名单 + 扩展名解析。
|
|
3
|
+
*
|
|
4
|
+
* 宿主路由(src/index.ts,服务端 415/413 拦截)与浏览器端预检(src/client/task-editor.tsx,
|
|
5
|
+
* 发包前当场拒)**必须用同一份**——两处各写一份必然漂移(白名单改了这边忘了那边)。
|
|
6
|
+
* 客户端打包(tsdown alwaysBundle)会把本模块内联进 client.js,宿主侧 tsc 直编,均无碍。
|
|
7
|
+
*/
|
|
8
|
+
/** 附件上传:体积上限(字节,20MB)。 */
|
|
9
|
+
export const ATTACHMENT_MAX_BYTES = 20 * 1024 * 1024;
|
|
10
|
+
/** 附件上传:允许的常见扩展名(文本/代码/图片/文档)。命中白名单才收。 */
|
|
11
|
+
export const ALLOWED_ATTACHMENT_EXT = new Set([
|
|
12
|
+
// 文本 / 代码
|
|
13
|
+
'txt', 'md', 'markdown', 'json', 'jsonc', 'yaml', 'yml', 'csv', 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
|
|
14
|
+
'py', 'sh', 'bash', 'zsh', 'toml', 'ini', 'cfg', 'log', 'xml', 'html', 'css', 'scss', 'sql', 'go', 'rs',
|
|
15
|
+
'java', 'c', 'cpp', 'h', 'hpp', 'rb', 'php', 'pl', 'r', 'scala', 'kt', 'swift', 'dockerfile', 'gitignore', 'env',
|
|
16
|
+
// 图片
|
|
17
|
+
'png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg', 'ico', 'avif',
|
|
18
|
+
// 文档
|
|
19
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'rtf',
|
|
20
|
+
]);
|
|
21
|
+
/** 取文件名扩展名(小写,无点返回 '')。 */
|
|
22
|
+
export const extOf = (name) => {
|
|
23
|
+
const base = name.slice(Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')) + 1);
|
|
24
|
+
const dot = base.lastIndexOf('.');
|
|
25
|
+
return dot <= 0 ? '' : base.slice(dot + 1).toLowerCase();
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* 附件 `ref` 是否**安全**(可以拼进路径):非空、不含 `..`(相对路径上跳)、
|
|
29
|
+
* 不以 `/` 或 `\` 开头(绝对路径)、不含 `\`(Windows 分隔符)。
|
|
30
|
+
*
|
|
31
|
+
* **唯一实现**(2026-09-30 抽象收敛):此前同一规则写了 **4 份**且各有出入——
|
|
32
|
+
* 宿主 zod(`tasks.ts`:拒绝 `..`/绝对/反斜杠)、客户端保存前校验(`task-editor`:多查了开头 `\`)、
|
|
33
|
+
* 真删前防御(`task-assets.removeAttachmentFiles`:没查开头 `\`)、上传定位
|
|
34
|
+
* (`task-assets.locateUploaded`:连反斜杠都没查)。各写一份必然漂移——
|
|
35
|
+
* 「选工作区文件报 422 附件 ref 非法」那类 bug 的温床就在这。
|
|
36
|
+
*/
|
|
37
|
+
export const isSafeAttachmentRef = (ref) => ref !== ''
|
|
38
|
+
&& !ref.includes('..')
|
|
39
|
+
&& !ref.startsWith('/')
|
|
40
|
+
&& !ref.startsWith('\\')
|
|
41
|
+
&& !ref.includes('\\');
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** 日历只认任务的这几个字段(服务端 `TaskDefinition` 与客户端 overview 行都天然形状兼容)。 */
|
|
2
|
+
export interface CalendarTask {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
schedule: {
|
|
7
|
+
cron: string | null;
|
|
8
|
+
once: string | null;
|
|
9
|
+
timezone: string | null;
|
|
10
|
+
start: string | null;
|
|
11
|
+
everyNWeeks: number | null;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** 一条**未来计划**(尚未产生执行记录)。 */
|
|
15
|
+
export interface CalendarPlanEntry {
|
|
16
|
+
taskId: string;
|
|
17
|
+
title: string;
|
|
18
|
+
/** 绝对时刻(ISO)—— 与历史行的 `scheduled_at` 同一口径。 */
|
|
19
|
+
scheduledAt: string;
|
|
20
|
+
/** 分桶键 = 该时刻的**浏览器本地日**(`YYYY-MM-DD`;与执行记录页同一抉择,不用 `logical_date`)。 */
|
|
21
|
+
day: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 某自然月的区间,**半开** `[月首 00:00, 次月首 00:00)`,均为本地时区。
|
|
25
|
+
* 上界取次月首(`day` 精度)而不是「月末 23:59:59.999」——与执行记录页同一条半开区间规矩
|
|
26
|
+
* (`client/ui/time-range.ts` 的 `rangeToQuery` + `store.ts` 的 `scheduled_at < toTs`)。
|
|
27
|
+
*
|
|
28
|
+
* @param month **1-based(1 = 一月)** —— 与 `buildMonthCells` / `monthTitle` 同口径,
|
|
29
|
+
* 与 `Date` 的 0-based 差一。⚠️ 2026-10-05 真机 bug:此处原按 0-based 写,而调用方(日历页
|
|
30
|
+
* `cursor.m`)是 1-based ⇒ 显示 10 月却查了 11 月的区间,整页一条记录都读不出来。
|
|
31
|
+
* **日历里所有「月份」参数一律 1-based**,别再混。
|
|
32
|
+
*/
|
|
33
|
+
export declare function monthRangeOf(year: number, month: number): {
|
|
34
|
+
from: Date;
|
|
35
|
+
to: Date;
|
|
36
|
+
};
|
|
37
|
+
/** 月区间 → 服务端查询串(ISO;与 `rangeToQuery` 同口径的 `.toISOString()`)。 */
|
|
38
|
+
export declare function monthRangeQuery(year: number, month: number): {
|
|
39
|
+
fromTs: string;
|
|
40
|
+
toTs: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 某月的**未来计划刻度**,按本地日分桶。
|
|
44
|
+
*
|
|
45
|
+
* 三条硬规矩(都是真值约束,不是显示偏好):
|
|
46
|
+
* ① **只算启用的任务**(用户 2026-10-05 拍板):停用任务不会跑,给它出格子就是撒谎;
|
|
47
|
+
* ② **只列 >= now 的刻度**:过去「该跑没跑」的日子,日历上只能显示真实记录(没有就是没有),
|
|
48
|
+
* 拿**现在的定义**去回填过去,会造出从未发生过的执行;
|
|
49
|
+
* ③ `once` 出窗口即作废(决策 18 / 拍板 A)⇒ 过期的 `once` 不算计划(同 ②,只认 >= now)。
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ 未来**没有实例行**(决策 31 懒建行:到点才 INSERT,不预建、不回看)⇒ 这里算出的是
|
|
52
|
+
* 「按当前定义推算的计划」,任务改定义 / 停用后会随之变化 —— 是真实计算、不是模拟数据,
|
|
53
|
+
* UI 必须把它与「已发生」如实区分(AGENTS.md 第五条:正常功能一律真实取数,禁止模拟)。
|
|
54
|
+
*/
|
|
55
|
+
export declare function planEntriesByDay(tasks: readonly CalendarTask[], from: Date, to: Date, now: Date): Map<string, CalendarPlanEntry[]>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// calendar-plan.ts — 「任务日程」日历页的纯计算核(服务端与浏览器**同一份**,2026-10-05)。
|
|
2
|
+
//
|
|
3
|
+
// 为什么单独成文件:日历是两种数据的合流 —— **已发生**(查 `task_instances` 的真实行)与
|
|
4
|
+
// **按当前任务定义现算的未来计划**。后者的「一个月的刻度怎么算、怎么按天分桶、哪些不该出现」
|
|
5
|
+
// 全是纯逻辑:既要能被 `scripts/smoke.mjs` 直接断言(不许只靠肉眼验收),
|
|
6
|
+
// 又必须让浏览器端复用(客户端不许另写一份 cron 解析 —— 本仓硬规矩:一类东西一个实现)。
|
|
7
|
+
//
|
|
8
|
+
// ⚠️ 与 [`schedule-next.ts`](./schedule-next.ts) / `deps.ts` 同一条约束:
|
|
9
|
+
// 不许 import `node:*` / 宿主模块 / React;类型用本文件声明的最小结构。
|
|
10
|
+
import { logicalDateOf, onceScheduledAt, scheduledSlotsFor } from './schedule-next.js';
|
|
11
|
+
/**
|
|
12
|
+
* 单次调用 `scheduledSlotsFor` 的迭代上限。
|
|
13
|
+
* 刻度最密的 cron 是「每分钟」⇒ 一周最多 10080 个刻度,取 11000 留一档余量。
|
|
14
|
+
* ⚠️ 别退回「整月一次算」:整月最多 44640 个刻度,会撞上 `scheduledSlotsFor` 的 cap 被**静默截断**
|
|
15
|
+
* (表现为「月底那几天的任务凭空消失」)。
|
|
16
|
+
*/
|
|
17
|
+
const SLOTS_CAP_PER_SHARD = 11_000;
|
|
18
|
+
/** 分片步长 = 一周(配合上面的 cap,保证再密的 cron 也不会被截断)。 */
|
|
19
|
+
const SHARD_MS = 7 * 24 * 3600_000;
|
|
20
|
+
/** 排期字段 → 纯核要的形状(`null` 归一为 `undefined`;不新造转换层)。 */
|
|
21
|
+
function toScheduleNext(task) {
|
|
22
|
+
return {
|
|
23
|
+
schedule: {
|
|
24
|
+
cron: task.schedule.cron ?? undefined,
|
|
25
|
+
once: task.schedule.once ?? undefined,
|
|
26
|
+
timezone: task.schedule.timezone ?? undefined,
|
|
27
|
+
start: task.schedule.start ?? undefined,
|
|
28
|
+
everyNWeeks: task.schedule.everyNWeeks,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 某自然月的区间,**半开** `[月首 00:00, 次月首 00:00)`,均为本地时区。
|
|
34
|
+
* 上界取次月首(`day` 精度)而不是「月末 23:59:59.999」——与执行记录页同一条半开区间规矩
|
|
35
|
+
* (`client/ui/time-range.ts` 的 `rangeToQuery` + `store.ts` 的 `scheduled_at < toTs`)。
|
|
36
|
+
*
|
|
37
|
+
* @param month **1-based(1 = 一月)** —— 与 `buildMonthCells` / `monthTitle` 同口径,
|
|
38
|
+
* 与 `Date` 的 0-based 差一。⚠️ 2026-10-05 真机 bug:此处原按 0-based 写,而调用方(日历页
|
|
39
|
+
* `cursor.m`)是 1-based ⇒ 显示 10 月却查了 11 月的区间,整页一条记录都读不出来。
|
|
40
|
+
* **日历里所有「月份」参数一律 1-based**,别再混。
|
|
41
|
+
*/
|
|
42
|
+
export function monthRangeOf(year, month) {
|
|
43
|
+
return { from: new Date(year, month - 1, 1, 0, 0, 0, 0), to: new Date(year, month, 1, 0, 0, 0, 0) };
|
|
44
|
+
}
|
|
45
|
+
/** 月区间 → 服务端查询串(ISO;与 `rangeToQuery` 同口径的 `.toISOString()`)。 */
|
|
46
|
+
export function monthRangeQuery(year, month) {
|
|
47
|
+
const { from, to } = monthRangeOf(year, month);
|
|
48
|
+
return { fromTs: from.toISOString(), toTs: to.toISOString() };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 某月的**未来计划刻度**,按本地日分桶。
|
|
52
|
+
*
|
|
53
|
+
* 三条硬规矩(都是真值约束,不是显示偏好):
|
|
54
|
+
* ① **只算启用的任务**(用户 2026-10-05 拍板):停用任务不会跑,给它出格子就是撒谎;
|
|
55
|
+
* ② **只列 >= now 的刻度**:过去「该跑没跑」的日子,日历上只能显示真实记录(没有就是没有),
|
|
56
|
+
* 拿**现在的定义**去回填过去,会造出从未发生过的执行;
|
|
57
|
+
* ③ `once` 出窗口即作废(决策 18 / 拍板 A)⇒ 过期的 `once` 不算计划(同 ②,只认 >= now)。
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ 未来**没有实例行**(决策 31 懒建行:到点才 INSERT,不预建、不回看)⇒ 这里算出的是
|
|
60
|
+
* 「按当前定义推算的计划」,任务改定义 / 停用后会随之变化 —— 是真实计算、不是模拟数据,
|
|
61
|
+
* UI 必须把它与「已发生」如实区分(AGENTS.md 第五条:正常功能一律真实取数,禁止模拟)。
|
|
62
|
+
*/
|
|
63
|
+
export function planEntriesByDay(tasks, from, to, now) {
|
|
64
|
+
const out = new Map();
|
|
65
|
+
const push = (task, at) => {
|
|
66
|
+
if (at.getTime() < now.getTime())
|
|
67
|
+
return;
|
|
68
|
+
const day = logicalDateOf(at, undefined);
|
|
69
|
+
const entry = { taskId: task.id, title: task.title, scheduledAt: at.toISOString(), day };
|
|
70
|
+
const list = out.get(day);
|
|
71
|
+
if (list === undefined)
|
|
72
|
+
out.set(day, [entry]);
|
|
73
|
+
else
|
|
74
|
+
list.push(entry);
|
|
75
|
+
};
|
|
76
|
+
for (const task of tasks) {
|
|
77
|
+
if (task.enabled === false)
|
|
78
|
+
continue;
|
|
79
|
+
const next = toScheduleNext(task);
|
|
80
|
+
const once = task.schedule.once;
|
|
81
|
+
if (once !== null && once !== '') {
|
|
82
|
+
// 一次性任务:整月只有它那一个时刻(`onceScheduledAt` 按日期部分判定,不在该日返回 undefined)。
|
|
83
|
+
const at = onceScheduledAt(next, once.slice(0, 10));
|
|
84
|
+
if (at !== undefined)
|
|
85
|
+
push(task, at);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const cron = task.schedule.cron;
|
|
89
|
+
if (cron === null || cron === '')
|
|
90
|
+
continue;
|
|
91
|
+
// 按周分片:分片是半开区间 `[start, end)`,下一片从 end 起 ⇒ 落在该点上的刻度只会被取到一次,
|
|
92
|
+
// 不重不漏(与 `store.ts` 的半开区间同一口径)。
|
|
93
|
+
for (let start = from.getTime(); start < to.getTime(); start += SHARD_MS) {
|
|
94
|
+
const end = Math.min(start + SHARD_MS, to.getTime());
|
|
95
|
+
for (const slot of scheduledSlotsFor(next, new Date(start), new Date(end), SLOTS_CAP_PER_SHARD)) {
|
|
96
|
+
push(task, slot);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ISO 串(`...Z`)字典序 = 时间序 ⇒ 直接比串即可,不必回解析成 Date。
|
|
101
|
+
for (const list of out.values())
|
|
102
|
+
list.sort((a, b) => (a.scheduledAt < b.scheduledAt ? -1 : a.scheduledAt > b.scheduledAt ? 1 : 0));
|
|
103
|
+
return out;
|
|
104
|
+
}
|