@goodandready/dsh-cron 0.3.7 → 0.3.9
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 +8 -0
- package/lib/api.js +1 -0
- package/lib/runner.js +45 -13
- package/lib/store.js +1 -0
- package/lib/task-create.js +1 -0
- package/lib/task-transfer.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -607,6 +607,14 @@ Save LLM tokens and API quotas on watcher tasks by dynamically slowing down exec
|
|
|
607
607
|
- Multiplies interval by `backoffFactor` up to `maxInterval` after `idleRunsThreshold` silent or unchanged runs.
|
|
608
608
|
- Instantly resets to `minInterval` as soon as real changes or non-silent output are detected.
|
|
609
609
|
|
|
610
|
+
### 🔄 Session Resumption & GUI Session Naming (#346, GH #9, #354, GH #10, Added in v0.3.8, Patched in v0.3.9)
|
|
611
|
+
|
|
612
|
+
- **HTTP API `sessionTitle` Persistence (v0.3.9, #354, GH #10, PR #11)**: Tasks created or updated via HTTP endpoints (`POST /dsh-cron/tasks` and `POST /dsh-cron/api/tasks`) now reliably forward and persist `sessionTitle` into the task store and runner, mirroring the agent-tool (`cron_create_task`) and PATCH pathways.
|
|
613
|
+
|
|
614
|
+
- **Universal Host Resumption (`resumeSessionId`)**: DeepSeek Harness host `0.2.0-rc.2+` specifies `readonly resumeSessionId: SessionId` on `agents.resume()`. The runner now passes both `resumeSessionId` and `sessionId`, guaranteeing seamless session resumption across all DSH core versions without falling back to recreating sessions that already exist.
|
|
615
|
+
- **Enhanced Diagnostics on Failure**: When an existing session cannot be resumed, `dsh-cron` logs an actionable warning and, if recreating the session fails, surfaces the underlying resume error in the task run output.
|
|
616
|
+
- **Automatic GUI Session Naming (`sessionTitle`)**: Executed or resumed sessions now integrate with the host's `sessionTitle` service. Tasks can specify an explicit `sessionTitle` (or begin `task.title` with `[...]`) to automatically label the session in the DeepSeek Harness navigation sidebar instead of leaving it untitled (`cron-exec-...`).
|
|
617
|
+
|
|
610
618
|
### 📺 Real-Time Streaming Logs via SSE (#336)
|
|
611
619
|
|
|
612
620
|
Connect to `GET /dsh-cron/tasks/:id/stream` using Server-Sent Events (SSE) to receive live stdout/stderr chunks and execution completion events with an automatic 500-line ring buffer.
|
package/lib/api.js
CHANGED
|
@@ -639,6 +639,7 @@ function mergeExecutionFields(current, body, parsed) {
|
|
|
639
639
|
agentPreset: body.agentPreset !== undefined ? String(body.agentPreset).trim() : (current ? (current.agentPreset || '') : ''),
|
|
640
640
|
targetSessionId: body.targetSessionId !== undefined ? String(body.targetSessionId).trim() : (current ? (current.targetSessionId || '') : ''),
|
|
641
641
|
targetSessionReset: body.targetSessionReset !== undefined ? String(body.targetSessionReset).trim() : (current ? (current.targetSessionReset || 'never') : 'never'),
|
|
642
|
+
sessionTitle: body.sessionTitle !== undefined ? String(body.sessionTitle).trim() : (current ? (current.sessionTitle || '') : ''),
|
|
642
643
|
onSuccess: body.onSuccess !== undefined ? String(body.onSuccess).trim() : (current ? (current.onSuccess || '') : ''),
|
|
643
644
|
onFailure: body.onFailure !== undefined ? String(body.onFailure).trim() : (current ? (current.onFailure || '') : ''),
|
|
644
645
|
heartbeatIntervalSeconds: body.heartbeatIntervalSeconds !== undefined ? Number(body.heartbeatIntervalSeconds) : (current ? (Number(current.heartbeatIntervalSeconds) || 0) : 0),
|
package/lib/runner.js
CHANGED
|
@@ -475,11 +475,13 @@ export class SessionRunner {
|
|
|
475
475
|
}
|
|
476
476
|
} : {};
|
|
477
477
|
|
|
478
|
-
// If targetSessionId is set, attempt to resume the existing session (#143)
|
|
478
|
+
// If targetSessionId is set, attempt to resume the existing session (#143, #346, GH #9)
|
|
479
|
+
let resumeFailure = null;
|
|
479
480
|
if (prep.targetSessionId && this.ctx.agents && typeof this.ctx.agents.resume === 'function') {
|
|
480
481
|
try {
|
|
481
482
|
handle = await this.ctx.agents.resume({
|
|
482
483
|
sessionId: prep.targetSessionId,
|
|
484
|
+
resumeSessionId: prep.targetSessionId,
|
|
483
485
|
meta: {
|
|
484
486
|
cwd: prep.cwd,
|
|
485
487
|
...(preset ? { agentPreset: preset.id } : {})
|
|
@@ -490,27 +492,36 @@ export class SessionRunner {
|
|
|
490
492
|
isResumed = true;
|
|
491
493
|
logger.info(`[dsh-cron] resumed existing persistent session: ${prep.targetSessionId}`);
|
|
492
494
|
} catch (resumeErr) {
|
|
493
|
-
|
|
495
|
+
resumeFailure = resumeErr;
|
|
496
|
+
logger.warn(`[dsh-cron] could not resume session ${prep.targetSessionId} (${resumeErr.message}), fallback creating new session`);
|
|
494
497
|
handle = null;
|
|
495
498
|
}
|
|
496
499
|
}
|
|
497
500
|
|
|
498
501
|
// Create new session if not resumed
|
|
499
502
|
if (!handle) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
503
|
+
try {
|
|
504
|
+
handle = await this.ctx.agents.create({
|
|
505
|
+
sessionId: prep.mintSessionId(),
|
|
506
|
+
meta: {
|
|
507
|
+
cwd: prep.cwd,
|
|
508
|
+
ephemeral: !prep.targetSessionId,
|
|
509
|
+
internal: !prep.targetSessionId,
|
|
510
|
+
...(preset ? { agentPreset: preset.id } : {})
|
|
511
|
+
},
|
|
512
|
+
agentOptions: prep.provider && prep.model ? { provider: prep.provider, model: prep.model } : undefined,
|
|
513
|
+
...presetSetup,
|
|
514
|
+
});
|
|
515
|
+
} catch (createErr) {
|
|
516
|
+
if (resumeFailure) {
|
|
517
|
+
throw new Error(`could not resume session "${prep.targetSessionId}" (${resumeFailure.message}): ${createErr.message}`);
|
|
518
|
+
}
|
|
519
|
+
throw createErr;
|
|
520
|
+
}
|
|
511
521
|
}
|
|
512
522
|
|
|
513
523
|
this._applyPermissionPreset(task, handle);
|
|
524
|
+
this._nameSession(task, handle, isResumed);
|
|
514
525
|
await prep.raceAbort(handle.agent.whenIdle());
|
|
515
526
|
|
|
516
527
|
// 1. Capture starting sequence before sending user message (#227, #228)
|
|
@@ -639,6 +650,27 @@ export class SessionRunner {
|
|
|
639
650
|
}
|
|
640
651
|
}
|
|
641
652
|
|
|
653
|
+
_nameSession(task, handle, isResumed) {
|
|
654
|
+
if (!handle?.agent?.session) return;
|
|
655
|
+
const session = handle.agent.session;
|
|
656
|
+
try {
|
|
657
|
+
const sessionTitle = this.ctx.get ? this.ctx.get('sessionTitle') : this.ctx.sessionTitle;
|
|
658
|
+
if (!sessionTitle || typeof sessionTitle.rename !== 'function') return;
|
|
659
|
+
|
|
660
|
+
const explicitTitle = task.sessionTitle ? String(task.sessionTitle).trim() : '';
|
|
661
|
+
const taskTitle = task.title ? String(task.title).trim() : '';
|
|
662
|
+
const label = explicitTitle || (taskTitle.startsWith('[') ? taskTitle : '');
|
|
663
|
+
|
|
664
|
+
if (!label) return;
|
|
665
|
+
if (isResumed && !explicitTitle) return;
|
|
666
|
+
|
|
667
|
+
sessionTitle.rename(session, label);
|
|
668
|
+
logger.info(`[dsh-cron] assigned session title "${label}" for session ${session.id}`);
|
|
669
|
+
} catch (titleErr) {
|
|
670
|
+
logger.debug?.(`[dsh-cron] could not rename session ${session?.id}: ${titleErr.message}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
642
674
|
/** Token usage reported by session events or the handle (#228). */
|
|
643
675
|
_extractUsage(handle, turnEvents = []) {
|
|
644
676
|
const usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0 };
|
package/lib/store.js
CHANGED
|
@@ -628,6 +628,7 @@ export class TaskStore {
|
|
|
628
628
|
agentPreset: task.agentPreset !== undefined ? String(task.agentPreset).trim() : (prev.agentPreset || ''),
|
|
629
629
|
targetSessionId: task.targetSessionId !== undefined ? String(task.targetSessionId).trim() : (prev.targetSessionId || ''),
|
|
630
630
|
targetSessionReset: task.targetSessionReset !== undefined ? String(task.targetSessionReset).trim() : (prev.targetSessionReset || 'never'),
|
|
631
|
+
sessionTitle: task.sessionTitle !== undefined ? String(task.sessionTitle).trim() : (prev.sessionTitle || ''),
|
|
631
632
|
totalTokens: task.totalTokens !== undefined ? Number(task.totalTokens) : (prev.totalTokens || 0),
|
|
632
633
|
totalCostUsd: task.totalCostUsd !== undefined ? Number(task.totalCostUsd) : (prev.totalCostUsd || 0),
|
|
633
634
|
createdAt: prev.createdAt || task.createdAt || now,
|
package/lib/task-create.js
CHANGED
|
@@ -48,6 +48,7 @@ export function executeCreateTask(store, scheduler, args) {
|
|
|
48
48
|
agentPreset: args.agentPreset ? String(args.agentPreset).trim() : '',
|
|
49
49
|
targetSessionId: args.targetSessionId ? String(args.targetSessionId).trim() : '',
|
|
50
50
|
targetSessionReset: args.targetSessionReset ? String(args.targetSessionReset).trim() : 'never',
|
|
51
|
+
sessionTitle: args.sessionTitle ? String(args.sessionTitle).trim() : '',
|
|
51
52
|
onSuccess: args.onSuccess ? String(args.onSuccess).trim() : '',
|
|
52
53
|
onFailure: args.onFailure ? String(args.onFailure).trim() : '',
|
|
53
54
|
dependsOn: args.dependsOn !== undefined ? sanitizeDependsOn(args.dependsOn) : undefined,
|
package/lib/task-transfer.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cron",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Background automation runner for DSH: isolated agent runs, script/HTTP/SSH/Docker runtimes, cost guard, notifications, heartbeats.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|