@goodandready/dsh-cron 0.2.11 → 0.2.13
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 +20 -0
- package/README.ru.md +20 -0
- package/README.zh.md +20 -0
- package/docs/README.ru.md +20 -0
- package/docs/README.zh.md +20 -0
- package/lib/chat-start.js +21 -1
- package/lib/client.js +69 -0
- package/lib/runner.js +111 -7
- package/lib/store.js +3 -0
- package/lib/task-transfer.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -352,6 +352,26 @@ Developer-facing, no behaviour change. `parseScheduleExpression` was split into
|
|
|
352
352
|
|
|
353
353
|
---
|
|
354
354
|
|
|
355
|
+
### 24. Automatic Agent Preset Mounting & Tool Availability (#141 / GH-1, Added in v0.2.12)
|
|
356
|
+
- **Automatic Agent Preset Mounting**: Scheduled autonomous `llm` tasks and on-demand agent runs now automatically resolve and mount the system agent preset (defaulting to user's standard preset via `presets.mount(agentCtx, preset.id)` in `setup`). Scheduled agent turns now possess complete tool access (file editing, workspace exploration, shell commands, etc.) instead of running with bare chat sessions.
|
|
357
|
+
- **Per-Task Preset Override**: Tasks can explicitly configure an `agentPreset` identifier in the task form UI, REST API, or profile jobs (e.g. `coding`, `system`, `minimal`). When left empty, tasks automatically resolve to the harness's default preset.
|
|
358
|
+
- **Graceful Toolset Fallback**: If the `agentPresets` service is absent or an unknown preset ID is provided, the scheduler logs an informative warning and safely proceeds with basic model execution without aborting the scheduled task.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
### 25. Persistent Execution Sessions & Context Continuity (`targetSessionId`, Added in v0.2.13, #143)
|
|
363
|
+
- **Continuous Conversation Context**: Tasks can configure a `targetSessionId`. When set, the scheduler resumes the existing session via `agents.resume()` instead of generating an isolated ephemeral session (`cron-exec-${id}-${uuid}`) on every tick. The agent retains conversational memory across runs, allowing periodic auditors or assistants to reference prior findings and outputs directly in context.
|
|
364
|
+
- **Context Window Protection & Session Rotation (`targetSessionReset`)**: To prevent unbounded context growth and token cost explosion over long schedules, tasks can set an automatic rotation policy:
|
|
365
|
+
- `never`: Continues a single session thread indefinitely.
|
|
366
|
+
- `daily`: Automatically rotates the session daily (`<id>-YYYY-MM-DD`).
|
|
367
|
+
- `weekly`: Automatically rotates the session weekly (`<id>-YYYY-Www`).
|
|
368
|
+
- Custom templating: Any `targetSessionId` containing `{{date}}` automatically interpolates today's date (`YYYY-MM-DD`).
|
|
369
|
+
- **Main Chat Visibility**: Persistent sessions are omitted from `sessions.archive()` and unflagged from `ephemeral`/`internal`, making them visible and interactable directly in the DeepSeek Harness chat interface.
|
|
370
|
+
- **Preset & Tooling Compatibility**: Resumed sessions automatically preserve and remount configured `agentPresets`, ensuring full access to workspace, terminal, and file tools on every turn.
|
|
371
|
+
- *Inspiration credit*: Community idea originally explored by [@RaulLazaro](https://github.com/RaulLazaro).
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
355
375
|
## 📦 Installation
|
|
356
376
|
|
|
357
377
|
Install into your DeepSeek Harness web profile:
|
package/README.ru.md
CHANGED
|
@@ -228,6 +228,26 @@ cron_create_task({
|
|
|
228
228
|
|
|
229
229
|
---
|
|
230
230
|
|
|
231
|
+
### 24. Автоматическое подключение пресетов агента и инструментов (#141 / GH-1, добавлено в v0.2.12)
|
|
232
|
+
- **Автоматическое монтирование пресета агента**: Запланированные автономные `llm`-задачи и интерактивные запуски агента теперь автоматически определяют и подключают пресет агента системы (по умолчанию используется стандартный пресет пользователя через `presets.mount(agentCtx, preset.id)` внутри хука `setup`). Автономные сессии по расписанию получают полный доступ к инструментам (файлы, рабочее окружение, терминал и т.д.) вместо изолированного чата без инструментов.
|
|
233
|
+
- **Индивидуальный пресет для задачи**: Для каждой задачи можно явно задать идентификатор `agentPreset` в веб-интерфейсе, через REST API или в декларативных задачах профиля (например, `coding`, `system`, `minimal`). Если поле не заполнено, автоматически применяется пресет по умолчанию из настроек харнесса.
|
|
234
|
+
- **Безопасная деградация**: Если сервис `agentPresets` недоступен или указан несуществующий пресет, планировщик выводит информативное предупреждение и штатно продолжает выполнение модели без аварийной остановки задачи.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### 25. Долговременные сессии и непрерывность контекста (`targetSessionId`, добавлено в v0.2.13, #143)
|
|
239
|
+
- **Непрерывный контекст диалога**: Для задач можно задать `targetSessionId`. При наличии этого идентификатора планировщик возобновляет существующую сессию через `agents.resume()` вместо создания одноразовой сессии (`cron-exec-${id}-${uuid}`) на каждом тике. Агент сохраняет память предыдущих ходов и может ссылаться на ранее обнаруженные данные и выводы.
|
|
240
|
+
- **Защита от переполнения контекста и ротация (`targetSessionReset`)**: Чтобы контекстное окно и расход токенов не разрастались бесконечно при частых запусках, предусмотрены политики автоматической ротации:
|
|
241
|
+
- `never`: единая непрерывная сессия без сброса.
|
|
242
|
+
- `daily`: ежедневная автоматическая ротация (`<id>-YYYY-MM-DD`).
|
|
243
|
+
- `weekly`: еженедельная автоматическая ротация (`<id>-YYYY-Www`).
|
|
244
|
+
- Шаблоны дат: в `targetSessionId` поддерживается плейсхолдер `{{date}}`, который автоматически заменяется на текущую дату `YYYY-MM-DD`.
|
|
245
|
+
- **Видимость в списке чатов DSH**: Долговременные сессии не помечаются как `ephemeral`/`internal` и исключены из автоматической архивации (`sessions.archive()`), поэтому они остаются доступны для чтения и прямого диалога в веб-интерфейсе DSH.
|
|
246
|
+
- **Совместимость с пресетами и инструментами**: При возобновлении сессии автоматически подключаются инструменты пресета `agentPreset`, гарантируя доступ к терминалу, файлам и командам.
|
|
247
|
+
- *Благодарность*: концепция вдохновлена разработкой [@RaulLazaro](https://github.com/RaulLazaro).
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
231
251
|
## 📦 Установка
|
|
232
252
|
|
|
233
253
|
```bash
|
package/README.zh.md
CHANGED
|
@@ -351,6 +351,26 @@ bash deploy.sh verify [exact-version]
|
|
|
351
351
|
|
|
352
352
|
---
|
|
353
353
|
|
|
354
|
+
### 24. 自动挂载智能体预设与工具支持 (#141 / GH-1,v0.2.12 新增)
|
|
355
|
+
- **自动挂载智能体预设**:计划执行的自主 `llm` 任务和交互式启动现在会自动解析并挂载系统智能体预设(默认通过 `setup` 钩子中的 `presets.mount(agentCtx, preset.id)` 挂载用户的标准预设)。计划会话现已具备完整的工具调用能力(文件读写、工作区操作、Shell 终端等),彻底解决此前空会话无工具调用的问题。
|
|
356
|
+
- **单任务预设覆盖**:可在 Web 管理界面、REST API 或配置文件中为具体任务配置独立的 `agentPreset` 标识(例如 `coding`、`system`、`minimal`)。未设置时自动继承系统默认预设。
|
|
357
|
+
- **优雅降级保障**:当未安装 `agentPresets` 服务或指定了未知的预设 ID 时,调度器仅记录友好的警告日志,并安全平稳地继续执行基础模型会话,避免定时任务中断。
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
### 25. 常驻持久会话与上下文延续 (`targetSessionId`,v0.2.13 新增,#143)
|
|
362
|
+
- **跨周期会话上下文延续**:支持在任务中配置 `targetSessionId`。设置后,调度器将在每次定时触发时通过 `agents.resume()` 唤醒已有会话,而不再每次生成孤立的临时会话(`cron-exec-${id}-${uuid}`)。智能体能够完整继承上一轮对话的历史记忆与分析结论。
|
|
363
|
+
- **上下文窗口保护与轮转机制 (`targetSessionReset`)**:为防止高频执行导致模型上下文窗口超限与 Token 成本暴增,支持智能轮转策略:
|
|
364
|
+
- `never`:持续累积单一会话,不重置。
|
|
365
|
+
- `daily`:每日自动开启全新子会话(后缀 `<id>-YYYY-MM-DD`)。
|
|
366
|
+
- `weekly`:每周自动开启全新子会话(后缀 `<id>-YYYY-Www`)。
|
|
367
|
+
- 日期变量插值:`targetSessionId` 中支持 `{{date}}` 占位符,自动注入当前日期 `YYYY-MM-DD`。
|
|
368
|
+
- **主界面原生可见交互**:持久会话不会被标记为 `ephemeral`/`internal`,且在执行后跳过自动归档(`sessions.archive()`),用户可在 DSH 聊天列表中直接查看并继续手动对话。
|
|
369
|
+
- **预设工具链无缝适配**:恢复会话时同样完整挂载 `agentPresets`,保障文件读写、代码编辑与终端工具持续可用。
|
|
370
|
+
- *致谢*:功能灵感源自社区开发者 [@RaulLazaro](https://github.com/RaulLazaro)。
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
354
374
|
## 📦 安装
|
|
355
375
|
|
|
356
376
|
```bash
|
package/docs/README.ru.md
CHANGED
|
@@ -228,6 +228,26 @@ cron_create_task({
|
|
|
228
228
|
|
|
229
229
|
---
|
|
230
230
|
|
|
231
|
+
### 24. Автоматическое подключение пресетов агента и инструментов (#141 / GH-1, добавлено в v0.2.12)
|
|
232
|
+
- **Автоматическое монтирование пресета агента**: Запланированные автономные `llm`-задачи и интерактивные запуски агента теперь автоматически определяют и подключают пресет агента системы (по умолчанию используется стандартный пресет пользователя через `presets.mount(agentCtx, preset.id)` внутри хука `setup`). Автономные сессии по расписанию получают полный доступ к инструментам (файлы, рабочее окружение, терминал и т.д.) вместо изолированного чата без инструментов.
|
|
233
|
+
- **Индивидуальный пресет для задачи**: Для каждой задачи можно явно задать идентификатор `agentPreset` в веб-интерфейсе, через REST API или в декларативных задачах профиля (например, `coding`, `system`, `minimal`). Если поле не заполнено, автоматически применяется пресет по умолчанию из настроек харнесса.
|
|
234
|
+
- **Безопасная деградация**: Если сервис `agentPresets` недоступен или указан несуществующий пресет, планировщик выводит информативное предупреждение и штатно продолжает выполнение модели без аварийной остановки задачи.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### 25. Долговременные сессии и непрерывность контекста (`targetSessionId`, добавлено в v0.2.13, #143)
|
|
239
|
+
- **Непрерывный контекст диалога**: Для задач можно задать `targetSessionId`. При наличии этого идентификатора планировщик возобновляет существующую сессию через `agents.resume()` вместо создания одноразовой сессии (`cron-exec-${id}-${uuid}`) на каждом тике. Агент сохраняет память предыдущих ходов и может ссылаться на ранее обнаруженные данные и выводы.
|
|
240
|
+
- **Защита от переполнения контекста и ротация (`targetSessionReset`)**: Чтобы контекстное окно и расход токенов не разрастались бесконечно при частых запусках, предусмотрены политики автоматической ротации:
|
|
241
|
+
- `never`: единая непрерывная сессия без сброса.
|
|
242
|
+
- `daily`: ежедневная автоматическая ротация (`<id>-YYYY-MM-DD`).
|
|
243
|
+
- `weekly`: еженедельная автоматическая ротация (`<id>-YYYY-Www`).
|
|
244
|
+
- Шаблоны дат: в `targetSessionId` поддерживается плейсхолдер `{{date}}`, который автоматически заменяется на текущую дату `YYYY-MM-DD`.
|
|
245
|
+
- **Видимость в списке чатов DSH**: Долговременные сессии не помечаются как `ephemeral`/`internal` и исключены из автоматической архивации (`sessions.archive()`), поэтому они остаются доступны для чтения и прямого диалога в веб-интерфейсе DSH.
|
|
246
|
+
- **Совместимость с пресетами и инструментами**: При возобновлении сессии автоматически подключаются инструменты пресета `agentPreset`, гарантируя доступ к терминалу, файлам и командам.
|
|
247
|
+
- *Благодарность*: концепция вдохновлена разработкой [@RaulLazaro](https://github.com/RaulLazaro).
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
231
251
|
## 📦 Установка
|
|
232
252
|
|
|
233
253
|
```bash
|
package/docs/README.zh.md
CHANGED
|
@@ -351,6 +351,26 @@ bash deploy.sh verify [exact-version]
|
|
|
351
351
|
|
|
352
352
|
---
|
|
353
353
|
|
|
354
|
+
### 24. 自动挂载智能体预设与工具支持 (#141 / GH-1,v0.2.12 新增)
|
|
355
|
+
- **自动挂载智能体预设**:计划执行的自主 `llm` 任务和交互式启动现在会自动解析并挂载系统智能体预设(默认通过 `setup` 钩子中的 `presets.mount(agentCtx, preset.id)` 挂载用户的标准预设)。计划会话现已具备完整的工具调用能力(文件读写、工作区操作、Shell 终端等),彻底解决此前空会话无工具调用的问题。
|
|
356
|
+
- **单任务预设覆盖**:可在 Web 管理界面、REST API 或配置文件中为具体任务配置独立的 `agentPreset` 标识(例如 `coding`、`system`、`minimal`)。未设置时自动继承系统默认预设。
|
|
357
|
+
- **优雅降级保障**:当未安装 `agentPresets` 服务或指定了未知的预设 ID 时,调度器仅记录友好的警告日志,并安全平稳地继续执行基础模型会话,避免定时任务中断。
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
### 25. 常驻持久会话与上下文延续 (`targetSessionId`,v0.2.13 新增,#143)
|
|
362
|
+
- **跨周期会话上下文延续**:支持在任务中配置 `targetSessionId`。设置后,调度器将在每次定时触发时通过 `agents.resume()` 唤醒已有会话,而不再每次生成孤立的临时会话(`cron-exec-${id}-${uuid}`)。智能体能够完整继承上一轮对话的历史记忆与分析结论。
|
|
363
|
+
- **上下文窗口保护与轮转机制 (`targetSessionReset`)**:为防止高频执行导致模型上下文窗口超限与 Token 成本暴增,支持智能轮转策略:
|
|
364
|
+
- `never`:持续累积单一会话,不重置。
|
|
365
|
+
- `daily`:每日自动开启全新子会话(后缀 `<id>-YYYY-MM-DD`)。
|
|
366
|
+
- `weekly`:每周自动开启全新子会话(后缀 `<id>-YYYY-Www`)。
|
|
367
|
+
- 日期变量插值:`targetSessionId` 中支持 `{{date}}` 占位符,自动注入当前日期 `YYYY-MM-DD`。
|
|
368
|
+
- **主界面原生可见交互**:持久会话不会被标记为 `ephemeral`/`internal`,且在执行后跳过自动归档(`sessions.archive()`),用户可在 DSH 聊天列表中直接查看并继续手动对话。
|
|
369
|
+
- **预设工具链无缝适配**:恢复会话时同样完整挂载 `agentPresets`,保障文件读写、代码编辑与终端工具持续可用。
|
|
370
|
+
- *致谢*:功能灵感源自社区开发者 [@RaulLazaro](https://github.com/RaulLazaro)。
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
354
374
|
## 📦 安装
|
|
355
375
|
|
|
356
376
|
```bash
|
package/lib/chat-start.js
CHANGED
|
@@ -40,13 +40,33 @@ export async function chatStartHandler(ctx, req, res, parseJsonBody, sendJson, r
|
|
|
40
40
|
const model = body.model || defaultSel?.model;
|
|
41
41
|
|
|
42
42
|
const cwd = process.cwd();
|
|
43
|
+
const presets = ctx.agentPresets || ctx.get?.('agentPresets');
|
|
44
|
+
let preset = null;
|
|
45
|
+
if (presets && typeof presets.resolve === 'function') {
|
|
46
|
+
try {
|
|
47
|
+
preset = await presets.resolve(body.agentPreset || 'standard');
|
|
48
|
+
if (preset && typeof presets.standingKeyFor === 'function') {
|
|
49
|
+
await presets.standingKeyFor(preset.id);
|
|
50
|
+
}
|
|
51
|
+
} catch (presetErr) {
|
|
52
|
+
ctx.logger?.warn?.(`[dsh-cron] could not resolve agentPreset: ${presetErr.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
const handle = await agents.create({
|
|
44
57
|
sessionId: mintSessionId(),
|
|
45
58
|
meta: {
|
|
46
59
|
cwd,
|
|
47
|
-
agentPreset: 'standard',
|
|
60
|
+
agentPreset: preset ? preset.id : 'standard',
|
|
48
61
|
},
|
|
49
62
|
agentOptions: provider && model ? { provider, model } : undefined,
|
|
63
|
+
...(preset ? {
|
|
64
|
+
setup: async (agentCtx) => {
|
|
65
|
+
if (typeof presets.mount === 'function') {
|
|
66
|
+
await presets.mount(agentCtx, preset.id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} : {}),
|
|
50
70
|
});
|
|
51
71
|
|
|
52
72
|
const permissions = ctx.get?.('permissionPresets');
|
package/lib/client.js
CHANGED
|
@@ -323,6 +323,17 @@ window.__ModuleLoader__.load({
|
|
|
323
323
|
'form.selfHealingLabel': 'Self-healing runbook command (shell)',
|
|
324
324
|
'form.selfHealingPlaceholder': 'systemctl restart app || true',
|
|
325
325
|
'form.autoDiagnoseLabel': 'Auto-diagnose failures with AI agent',
|
|
326
|
+
'form.agentPresetLabel': 'Agent preset',
|
|
327
|
+
'form.agentPresetPlaceholder': 'standard (leave empty for configured default)',
|
|
328
|
+
'form.agentPresetHint': 'Agent preset from dsh-agent-presets (e.g. standard, minimal, cordis, ptc). Mounts workspace and coding tools.',
|
|
329
|
+
'form.targetSessionIdLabel': 'Target persistent session ID',
|
|
330
|
+
'form.targetSessionIdPlaceholder': 'e.g. audit-chat or dev-summary (empty = isolated run)',
|
|
331
|
+
'form.targetSessionIdHint': 'When specified, scheduled runs resume this session to maintain conversation context across runs instead of creating a new isolated session.',
|
|
332
|
+
'form.targetSessionResetLabel': 'Session context rotation',
|
|
333
|
+
'form.targetSessionResetNever': 'Continuous (never reset)',
|
|
334
|
+
'form.targetSessionResetDaily': 'Daily rotation (new session each day)',
|
|
335
|
+
'form.targetSessionResetWeekly': 'Weekly rotation (new session each week)',
|
|
336
|
+
'form.targetSessionResetHint': 'Automatically rotates the session identifier to prevent context window explosion over long periods.',
|
|
326
337
|
'form.previewSchedule': 'Preview next runs',
|
|
327
338
|
'form.previewScheduleTitle': 'Next 5 scheduled runs:',
|
|
328
339
|
},
|
|
@@ -396,6 +407,17 @@ window.__ModuleLoader__.load({
|
|
|
396
407
|
'form.selfHealingLabel': '故障自愈脚本 (Shell)',
|
|
397
408
|
'form.selfHealingPlaceholder': 'systemctl restart app || true',
|
|
398
409
|
'form.autoDiagnoseLabel': '任务失败时启用 AI 智能分析诊断',
|
|
410
|
+
'form.agentPresetLabel': '智能体预设 (Agent Preset)',
|
|
411
|
+
'form.agentPresetPlaceholder': 'standard (留空使用全局默认预设)',
|
|
412
|
+
'form.agentPresetHint': '来自 dsh-agent-presets 的智能体预设(如 standard, minimal, cordis, ptc),挂载工作区工具与文件读写能力。',
|
|
413
|
+
'form.targetSessionIdLabel': '目标常驻会话 ID (Target Session ID)',
|
|
414
|
+
'form.targetSessionIdPlaceholder': '如 audit-chat 或 dev-summary(留空为单次隔离运行)',
|
|
415
|
+
'form.targetSessionIdHint': '配置后定时任务将在此常驻会话中持续交互,跨周期保留历史上下文对话记忆,不再每次新建孤立会话。',
|
|
416
|
+
'form.targetSessionResetLabel': '上下文轮转策略 (Session Rotation)',
|
|
417
|
+
'form.targetSessionResetNever': '永不轮转(单一会话持续累积)',
|
|
418
|
+
'form.targetSessionResetDaily': '每日轮转(每日自动开启新会话)',
|
|
419
|
+
'form.targetSessionResetWeekly': '每周轮转(每周自动开启新会话)',
|
|
420
|
+
'form.targetSessionResetHint': '自动按周期加注日期后缀轮转会话,有效防止长时间运行导致的模型上下文窗口超限与 Token 浪费。',
|
|
399
421
|
'form.previewSchedule': '预测下次运行时间',
|
|
400
422
|
'form.previewScheduleTitle': '未来 5 次计划运行时间:',
|
|
401
423
|
'search.placeholder': '搜索定时任务',
|
|
@@ -1155,6 +1177,9 @@ window.__ModuleLoader__.load({
|
|
|
1155
1177
|
const [formMisfirePolicy, setFormMisfirePolicy] = React.useState('skip');
|
|
1156
1178
|
const [formMaxRetries, setFormMaxRetries] = React.useState(0);
|
|
1157
1179
|
const [formPermissionPreset, setFormPermissionPreset] = React.useState('default');
|
|
1180
|
+
const [formAgentPreset, setFormAgentPreset] = React.useState('');
|
|
1181
|
+
const [formTargetSessionId, setFormTargetSessionId] = React.useState('');
|
|
1182
|
+
const [formTargetSessionReset, setFormTargetSessionReset] = React.useState('never');
|
|
1158
1183
|
const [formEnv, setFormEnv] = React.useState('');
|
|
1159
1184
|
const [formCwd, setFormCwd] = React.useState('');
|
|
1160
1185
|
const [formWorkspaceId, setFormWorkspaceId] = React.useState('');
|
|
@@ -1705,6 +1730,9 @@ window.__ModuleLoader__.load({
|
|
|
1705
1730
|
setFormMisfirePolicy('skip');
|
|
1706
1731
|
setFormMaxRetries(0);
|
|
1707
1732
|
setFormPermissionPreset('default');
|
|
1733
|
+
setFormAgentPreset('');
|
|
1734
|
+
setFormTargetSessionId('');
|
|
1735
|
+
setFormTargetSessionReset('never');
|
|
1708
1736
|
setFormEnv('');
|
|
1709
1737
|
setFormCwd('');
|
|
1710
1738
|
setFormWorkspaceId('');
|
|
@@ -1803,6 +1831,9 @@ window.__ModuleLoader__.load({
|
|
|
1803
1831
|
setFormMisfirePolicy(task.misfirePolicy || 'skip');
|
|
1804
1832
|
setFormMaxRetries(task.maxRetries !== undefined ? task.maxRetries : 0);
|
|
1805
1833
|
setFormPermissionPreset(task.permissionPreset || 'default');
|
|
1834
|
+
setFormAgentPreset(task.agentPreset || '');
|
|
1835
|
+
setFormTargetSessionId(task.targetSessionId || '');
|
|
1836
|
+
setFormTargetSessionReset(task.targetSessionReset || 'never');
|
|
1806
1837
|
setFormEnv(formatEnvText(task.env));
|
|
1807
1838
|
setFormCwd(task.cwd || '');
|
|
1808
1839
|
setFormWorkspaceId(task.workspaceId || '');
|
|
@@ -1860,6 +1891,9 @@ window.__ModuleLoader__.load({
|
|
|
1860
1891
|
misfirePolicy: formMisfirePolicy,
|
|
1861
1892
|
maxRetries: Number(formMaxRetries) || 0,
|
|
1862
1893
|
permissionPreset: formPermissionPreset,
|
|
1894
|
+
agentPreset: formAgentPreset.trim() || undefined,
|
|
1895
|
+
targetSessionId: formTargetSessionId.trim() || undefined,
|
|
1896
|
+
targetSessionReset: formTargetSessionReset || 'never',
|
|
1863
1897
|
kanbanMode: formKanbanMode,
|
|
1864
1898
|
onSuccess: formOnSuccess.trim() || undefined,
|
|
1865
1899
|
onFailure: formOnFailure.trim() || undefined,
|
|
@@ -2180,6 +2214,7 @@ window.__ModuleLoader__.load({
|
|
|
2180
2214
|
task.onSuccess ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On success: triggers ' + task.onSuccess, style: { background: 'rgba(56, 189, 248, 0.15)', color: '#38bdf8' } }, '➜ ' + task.onSuccess) : null,
|
|
2181
2215
|
task.onFailure ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On failure: triggers ' + task.onFailure, style: { background: 'rgba(239, 68, 68, 0.15)', color: '#ef4444' } }, '↳ ' + task.onFailure) : null,
|
|
2182
2216
|
task.heartbeatIntervalSeconds > 0 ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Heartbeat: every ' + task.heartbeatIntervalSeconds + 's', style: { background: 'rgba(168, 85, 247, 0.15)', color: '#a855f7' } }, '💓 ' + task.heartbeatIntervalSeconds + 's') : null,
|
|
2217
|
+
task.targetSessionId ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Target session: ' + task.targetSessionId + (task.targetSessionReset && task.targetSessionReset !== 'never' ? ' (' + task.targetSessionReset + ')' : ''), style: { background: 'rgba(99, 102, 241, 0.15)', color: '#818cf8' } }, '🧵 ' + task.targetSessionId) : null,
|
|
2183
2218
|
task.preflightType && task.preflightType !== 'none' ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Preflight: ' + task.preflightType, style: { background: 'rgba(234, 179, 8, 0.15)', color: '#eab308' } }, '🛡️ ' + task.preflightType) : null,
|
|
2184
2219
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
2185
2220
|
task.totalTokens > 0 ? React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (task.totalTokens > 1000 ? Math.round(task.totalTokens / 1000) + 'k' : task.totalTokens) + ' tok') : null
|
|
@@ -2520,6 +2555,40 @@ window.__ModuleLoader__.load({
|
|
|
2520
2555
|
React.createElement('option', { value: 'always' }, T_KEY(t, 'form.kanbanAlways'))
|
|
2521
2556
|
)
|
|
2522
2557
|
),
|
|
2558
|
+
AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2559
|
+
React.createElement('label', null, T_KEY(t, 'form.agentPresetLabel')),
|
|
2560
|
+
React.createElement('input', {
|
|
2561
|
+
type: 'text',
|
|
2562
|
+
value: formAgentPreset,
|
|
2563
|
+
placeholder: T_KEY(t, 'form.agentPresetPlaceholder'),
|
|
2564
|
+
onChange: (e) => setFormAgentPreset(e.target.value)
|
|
2565
|
+
}),
|
|
2566
|
+
React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.agentPresetHint'))
|
|
2567
|
+
),
|
|
2568
|
+
AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
2569
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2570
|
+
React.createElement('label', null, T_KEY(t, 'form.targetSessionIdLabel')),
|
|
2571
|
+
React.createElement('input', {
|
|
2572
|
+
type: 'text',
|
|
2573
|
+
value: formTargetSessionId,
|
|
2574
|
+
placeholder: T_KEY(t, 'form.targetSessionIdPlaceholder'),
|
|
2575
|
+
onChange: (e) => setFormTargetSessionId(e.target.value)
|
|
2576
|
+
}),
|
|
2577
|
+
React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.targetSessionIdHint'))
|
|
2578
|
+
),
|
|
2579
|
+
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2580
|
+
React.createElement('label', null, T_KEY(t, 'form.targetSessionResetLabel')),
|
|
2581
|
+
React.createElement('select', {
|
|
2582
|
+
value: formTargetSessionReset,
|
|
2583
|
+
onChange: (e) => setFormTargetSessionReset(e.target.value)
|
|
2584
|
+
},
|
|
2585
|
+
React.createElement('option', { value: 'never' }, T_KEY(t, 'form.targetSessionResetNever')),
|
|
2586
|
+
React.createElement('option', { value: 'daily' }, T_KEY(t, 'form.targetSessionResetDaily')),
|
|
2587
|
+
React.createElement('option', { value: 'weekly' }, T_KEY(t, 'form.targetSessionResetWeekly'))
|
|
2588
|
+
),
|
|
2589
|
+
React.createElement('div', { className: 'dsh-cron-hint' }, T_KEY(t, 'form.targetSessionResetHint'))
|
|
2590
|
+
)
|
|
2591
|
+
),
|
|
2523
2592
|
AGENT_FORM_TYPES.includes(formType) && React.createElement('div', { className: 'dsh-cron-form-row' },
|
|
2524
2593
|
React.createElement('div', { className: 'dsh-cron-form-group' },
|
|
2525
2594
|
React.createElement('label', null, T_KEY(t, 'form.providerLabel')),
|
package/lib/runner.js
CHANGED
|
@@ -23,6 +23,42 @@ import {
|
|
|
23
23
|
/**
|
|
24
24
|
* Runner executing scheduled tasks in DeepSeek Harness sessions.
|
|
25
25
|
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the effective target session ID taking into account rotation policies (#143).
|
|
29
|
+
* Supports {{date}} interpolation, 'daily' rotation (adds -YYYY-MM-DD), and 'weekly' rotation (-YYYY-Www).
|
|
30
|
+
*/
|
|
31
|
+
export function resolveTargetSessionId(targetSessionId, resetPolicy = 'never', date = new Date()) {
|
|
32
|
+
if (!targetSessionId || typeof targetSessionId !== 'string') return null;
|
|
33
|
+
let baseId = targetSessionId.trim();
|
|
34
|
+
if (!baseId) return null;
|
|
35
|
+
|
|
36
|
+
const yyyy = date.getFullYear();
|
|
37
|
+
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
38
|
+
const dd = String(date.getDate()).padStart(2, '0');
|
|
39
|
+
const yyyymmdd = `${yyyy}-${mm}-${dd}`;
|
|
40
|
+
|
|
41
|
+
if (baseId.includes('{{date}}')) {
|
|
42
|
+
return baseId.replaceAll('{{date}}', yyyymmdd);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (resetPolicy === 'daily') {
|
|
46
|
+
return `${baseId}-${yyyymmdd}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (resetPolicy === 'weekly') {
|
|
50
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
51
|
+
const dayNum = d.getUTCDay() || 7;
|
|
52
|
+
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
53
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
54
|
+
const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
|
|
55
|
+
const ww = String(weekNo).padStart(2, '0');
|
|
56
|
+
return `${baseId}-${d.getUTCFullYear()}-W${ww}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return baseId;
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
export class SessionRunner {
|
|
27
63
|
constructor(ctx) {
|
|
28
64
|
this.ctx = ctx;
|
|
@@ -381,7 +417,15 @@ export class SessionRunner {
|
|
|
381
417
|
sessionModule = await import('@deepseek-ai/dsh-session');
|
|
382
418
|
} catch {}
|
|
383
419
|
|
|
420
|
+
const targetSessionId = resolveTargetSessionId(task.targetSessionId, task.targetSessionReset);
|
|
421
|
+
|
|
384
422
|
const mintSessionId = () => {
|
|
423
|
+
if (targetSessionId) {
|
|
424
|
+
if (sessionModule && typeof sessionModule.SessionId === 'function') {
|
|
425
|
+
return sessionModule.SessionId(targetSessionId);
|
|
426
|
+
}
|
|
427
|
+
return targetSessionId;
|
|
428
|
+
}
|
|
385
429
|
const sid = `cron-exec-${task.id}-${randomUUID()}`;
|
|
386
430
|
if (sessionModule && typeof sessionModule.SessionId === 'function') {
|
|
387
431
|
return sessionModule.SessionId(sid);
|
|
@@ -393,6 +437,7 @@ export class SessionRunner {
|
|
|
393
437
|
return {
|
|
394
438
|
createUserMessage,
|
|
395
439
|
mintSessionId,
|
|
440
|
+
targetSessionId,
|
|
396
441
|
provider: task.provider || defaultSel?.provider,
|
|
397
442
|
model: task.model || defaultSel?.model || undefined,
|
|
398
443
|
...(await this._resolveRunContext(task)),
|
|
@@ -456,12 +501,67 @@ ${prompt}`;
|
|
|
456
501
|
/** Create the session, drive one turn, and always clean up. */
|
|
457
502
|
async _executeAgentTurn(task, prep) {
|
|
458
503
|
let handle = null;
|
|
504
|
+
let isResumed = false;
|
|
459
505
|
try {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
506
|
+
// Resolve agent preset (#GH-1): scheduled llm sessions must join an agent preset
|
|
507
|
+
// (default or task-configured), otherwise tools resolve against the empty global layer.
|
|
508
|
+
const presets = this.ctx.agentPresets || (this.ctx.get && this.ctx.get('agentPresets'));
|
|
509
|
+
let preset = null;
|
|
510
|
+
if (presets && typeof presets.resolve === 'function') {
|
|
511
|
+
try {
|
|
512
|
+
preset = await presets.resolve(task.agentPreset || undefined);
|
|
513
|
+
if (preset && typeof presets.standingKeyFor === 'function') {
|
|
514
|
+
await presets.standingKeyFor(preset.id);
|
|
515
|
+
}
|
|
516
|
+
} catch (presetErr) {
|
|
517
|
+
console.warn('[dsh-cron] agent preset unavailable, running without one:', presetErr && presetErr.message);
|
|
518
|
+
preset = null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const presetSetup = preset ? {
|
|
523
|
+
setup: async (agentCtx) => {
|
|
524
|
+
if (typeof presets.mount === 'function') {
|
|
525
|
+
await presets.mount(agentCtx, preset.id);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} : {};
|
|
529
|
+
|
|
530
|
+
// If targetSessionId is set, attempt to resume the existing session (#143)
|
|
531
|
+
if (prep.targetSessionId && this.ctx.agents && typeof this.ctx.agents.resume === 'function') {
|
|
532
|
+
try {
|
|
533
|
+
handle = await this.ctx.agents.resume({
|
|
534
|
+
sessionId: prep.targetSessionId,
|
|
535
|
+
meta: {
|
|
536
|
+
cwd: prep.cwd,
|
|
537
|
+
...(preset ? { agentPreset: preset.id } : {})
|
|
538
|
+
},
|
|
539
|
+
agentOptions: prep.provider && prep.model ? { provider: prep.provider, model: prep.model } : undefined,
|
|
540
|
+
...presetSetup,
|
|
541
|
+
});
|
|
542
|
+
isResumed = true;
|
|
543
|
+
console.log(`[dsh-cron] resumed existing persistent session: ${prep.targetSessionId}`);
|
|
544
|
+
} catch (resumeErr) {
|
|
545
|
+
console.log(`[dsh-cron] could not resume session ${prep.targetSessionId} (${resumeErr.message}), creating new persistent session`);
|
|
546
|
+
handle = null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Create new session if not resumed
|
|
551
|
+
if (!handle) {
|
|
552
|
+
handle = await this.ctx.agents.create({
|
|
553
|
+
sessionId: prep.mintSessionId(),
|
|
554
|
+
meta: {
|
|
555
|
+
cwd: prep.cwd,
|
|
556
|
+
ephemeral: !prep.targetSessionId,
|
|
557
|
+
internal: !prep.targetSessionId,
|
|
558
|
+
...(preset ? { agentPreset: preset.id } : {})
|
|
559
|
+
},
|
|
560
|
+
agentOptions: prep.provider && prep.model ? { provider: prep.provider, model: prep.model } : undefined,
|
|
561
|
+
...presetSetup,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
465
565
|
this._applyPermissionPreset(task, handle);
|
|
466
566
|
await prep.raceAbort(handle.agent.whenIdle());
|
|
467
567
|
handle.agent.followup(prep.createUserMessage({
|
|
@@ -473,8 +573,11 @@ ${prompt}`;
|
|
|
473
573
|
await prep.raceAbort(handle.agent.whenIdle());
|
|
474
574
|
}
|
|
475
575
|
const usage = this._extractUsage(handle);
|
|
576
|
+
const sessionNote = isResumed
|
|
577
|
+
? `resumed: ${prep.targetSessionId}`
|
|
578
|
+
: `session: ${handle.agent.session?.id || 'cron'}`;
|
|
476
579
|
return {
|
|
477
|
-
output: `[dsh-cron] Agent finished turn for task "${task.title}" (
|
|
580
|
+
output: `[dsh-cron] Agent finished turn for task "${task.title}" (${sessionNote})`,
|
|
478
581
|
usage,
|
|
479
582
|
costUsd: estimateTokenCost(prep.model, usage),
|
|
480
583
|
sessionId: handle.agent.session?.id || null,
|
|
@@ -527,7 +630,8 @@ ${prompt}`;
|
|
|
527
630
|
console.warn('[dsh-cron] session dispose notice:', disposeErr.message);
|
|
528
631
|
}
|
|
529
632
|
// Best-effort: keep ephemeral cron sessions out of the main chat list (#30).
|
|
530
|
-
|
|
633
|
+
// Persistent sessions (targetSessionId configured) stay unarchived so users can interact with them (#143).
|
|
634
|
+
if (archivedSessionId && this.ctx && this.ctx.get && !prep.targetSessionId) {
|
|
531
635
|
try {
|
|
532
636
|
const sessions = this.ctx.get('sessions');
|
|
533
637
|
if (sessions && typeof sessions.archive === 'function') {
|
package/lib/store.js
CHANGED
|
@@ -249,6 +249,9 @@ export class TaskStore {
|
|
|
249
249
|
priority: task.priority !== undefined ? Number(task.priority) : (prev.priority || 5),
|
|
250
250
|
selfHealingCommand: task.selfHealingCommand !== undefined ? String(task.selfHealingCommand).trim() : (prev.selfHealingCommand || ''),
|
|
251
251
|
autoDiagnose: task.autoDiagnose !== undefined ? Boolean(task.autoDiagnose) : (prev.autoDiagnose || false),
|
|
252
|
+
agentPreset: task.agentPreset !== undefined ? String(task.agentPreset).trim() : (prev.agentPreset || ''),
|
|
253
|
+
targetSessionId: task.targetSessionId !== undefined ? String(task.targetSessionId).trim() : (prev.targetSessionId || ''),
|
|
254
|
+
targetSessionReset: task.targetSessionReset !== undefined ? String(task.targetSessionReset).trim() : (prev.targetSessionReset || 'never'),
|
|
252
255
|
totalTokens: prev.totalTokens || 0,
|
|
253
256
|
totalCostUsd: prev.totalCostUsd || 0,
|
|
254
257
|
createdAt: prev.createdAt || task.createdAt || now,
|
package/lib/task-transfer.js
CHANGED