@hhuangpanda/dsh-automation 0.0.0-stage → 0.3.34
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/LICENSE +22 -0
- package/README.md +133 -2
- package/README.zh.md +134 -0
- package/cordis.patch.yml +3 -0
- package/docs/images/automation-overview-en.png +0 -0
- package/docs/images/automation-overview-zh.png +0 -0
- package/docs/reference.md +55 -0
- package/docs/reference.zh.md +55 -0
- package/lib/agent-configuration.d.ts +27 -0
- package/lib/agent-configuration.js +217 -0
- package/lib/api.d.ts +5 -0
- package/lib/api.js +253 -0
- package/lib/client/AutomationPanel.d.ts +9 -0
- package/lib/client/EmptyState.d.ts +10 -0
- package/lib/client/ModelSelection.d.ts +17 -0
- package/lib/client/ScheduleSection.d.ts +32 -0
- package/lib/client/SchedulerHealth.d.ts +10 -0
- package/lib/client/SkillPicker.d.ts +15 -0
- package/lib/client/TaskDetail.d.ts +34 -0
- package/lib/client/TaskEditor.d.ts +38 -0
- package/lib/client/TaskRow.d.ts +12 -0
- package/lib/client/editor-layout.d.ts +20 -0
- package/lib/client/i18n.d.ts +15 -0
- package/lib/client/icons.d.ts +30 -0
- package/lib/client/index.d.ts +6 -0
- package/lib/client/locales.d.ts +260 -0
- package/lib/client/rrule-editor.d.ts +15 -0
- package/lib/client/session-selection.d.ts +4 -0
- package/lib/client/shared.d.ts +77 -0
- package/lib/client/store.d.ts +40 -0
- package/lib/client.js +3014 -0
- package/lib/controller.d.ts +36 -0
- package/lib/controller.js +154 -0
- package/lib/domain.d.ts +44 -0
- package/lib/domain.js +473 -0
- package/lib/errors.d.ts +14 -0
- package/lib/errors.js +16 -0
- package/lib/im-delivery.d.ts +30 -0
- package/lib/im-delivery.js +69 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +92 -0
- package/lib/recurrence.d.ts +14 -0
- package/lib/recurrence.js +203 -0
- package/lib/runner.d.ts +27 -0
- package/lib/runner.js +295 -0
- package/lib/runtime-marker.d.ts +3 -0
- package/lib/runtime-marker.js +2 -0
- package/lib/scheduler.d.ts +58 -0
- package/lib/scheduler.js +287 -0
- package/lib/store.d.ts +14 -0
- package/lib/store.js +108 -0
- package/lib/tool-configuration.d.ts +8 -0
- package/lib/tool-configuration.js +220 -0
- package/lib/tool-support.d.ts +67 -0
- package/lib/tool-support.js +47 -0
- package/lib/tools.d.ts +6 -0
- package/lib/tools.js +140 -0
- package/lib/types.d.ts +465 -0
- package/lib/types.js +99 -0
- package/lib/validation.d.ts +34 -0
- package/lib/validation.js +99 -0
- package/package.json +163 -4
- package/screenshots.json +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type AutomationDomain } from './domain.js';
|
|
3
|
+
import type { AutomationScheduler } from './scheduler.js';
|
|
4
|
+
import type { AgentConfiguration } from './agent-configuration.js';
|
|
5
|
+
import type { AutomationDelivery, AutomationDeliveryOptions, AutomationRun, AutomationSchedulerHealth, AutomationTask, AutomationTaskView, CreateAutomationRequest, ResumeOptions, UpdateAutomationRequest } from './types.js';
|
|
6
|
+
export declare function validatePersistedSessionTarget(ctx: Context, task: AutomationTask, sessionId: string): Promise<void>;
|
|
7
|
+
export declare class AutomationController {
|
|
8
|
+
readonly domain: AutomationDomain;
|
|
9
|
+
readonly scheduler: AutomationScheduler;
|
|
10
|
+
private readonly now;
|
|
11
|
+
private readonly agentConfiguration?;
|
|
12
|
+
private readonly validateSessionTarget?;
|
|
13
|
+
private readonly delivery?;
|
|
14
|
+
constructor(domain: AutomationDomain, scheduler: AutomationScheduler, now?: () => number, agentConfiguration?: AgentConfiguration | undefined, validateSessionTarget?: ((task: AutomationTask, sessionId: string) => Promise<void>) | undefined, delivery?: {
|
|
15
|
+
options(botId?: string): Promise<AutomationDeliveryOptions>;
|
|
16
|
+
validate(destination: AutomationDelivery): Promise<void>;
|
|
17
|
+
} | undefined);
|
|
18
|
+
deliveryOptions(botId?: string): Promise<AutomationDeliveryOptions>;
|
|
19
|
+
list(): AutomationTaskView[];
|
|
20
|
+
get(id: string): AutomationTask;
|
|
21
|
+
schedulerHealth(): AutomationSchedulerHealth;
|
|
22
|
+
create(request: CreateAutomationRequest): Promise<AutomationTask>;
|
|
23
|
+
update(id: string, request: UpdateAutomationRequest): Promise<AutomationTask>;
|
|
24
|
+
options(id: string, agentPreset?: string | null, provider?: string, model?: string): Promise<import("./types.js").AgentConfigurationOptions>;
|
|
25
|
+
delete(id: string): Promise<boolean>;
|
|
26
|
+
deleteRun(taskId: string, runId: string): Promise<boolean>;
|
|
27
|
+
markNotificationsRead(): Promise<void>;
|
|
28
|
+
pause(id: string): Promise<AutomationTask>;
|
|
29
|
+
resume(id: string, options: ResumeOptions): Promise<AutomationTask>;
|
|
30
|
+
runNow(id: string): Promise<AutomationRun>;
|
|
31
|
+
stop(id: string): Promise<{
|
|
32
|
+
runId: string;
|
|
33
|
+
status: 'canceling' | 'canceled';
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import { WorkspaceId } from '@deepseek-ai/dsh-workspace';
|
|
3
|
+
import { AutomationError } from './errors.js';
|
|
4
|
+
import { applyExecutionPatch, normalizeSkills } from './validation.js';
|
|
5
|
+
import { AutomationDomainError } from './domain.js';
|
|
6
|
+
export async function validatePersistedSessionTarget(ctx, task, sessionId) {
|
|
7
|
+
const persistence = ctx.get('sessionPersistence');
|
|
8
|
+
if (persistence === undefined)
|
|
9
|
+
throw new AutomationError('target_session_unavailable', 'persisted session inspection is unavailable.');
|
|
10
|
+
const id = SessionId(sessionId);
|
|
11
|
+
const snapshot = await persistence.stat(id);
|
|
12
|
+
if (snapshot === undefined || snapshot.header.id !== id)
|
|
13
|
+
throw new AutomationError('target_session_not_found', 'target session could not be resolved.');
|
|
14
|
+
if (snapshot.header.cwd !== task.execution.cwd)
|
|
15
|
+
throw new AutomationError('target_workspace_mismatch', 'target session cwd does not match the task.');
|
|
16
|
+
const registry = ctx.get('workspaceRegistry');
|
|
17
|
+
const workspace = registry?.get(WorkspaceId(task.execution.workspaceId));
|
|
18
|
+
if (workspace === undefined || workspace.path !== task.execution.cwd || !workspace.sessionIds.includes(id)) {
|
|
19
|
+
throw new AutomationError('target_workspace_mismatch', 'target session must belong to the task workspace.');
|
|
20
|
+
}
|
|
21
|
+
if (snapshot.header.origin === 'subagent' || registry?.archivedSessionIds.includes(id)) {
|
|
22
|
+
throw new AutomationError('target_session_unavailable', 'select a visible ordinary session.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class AutomationController {
|
|
26
|
+
domain;
|
|
27
|
+
scheduler;
|
|
28
|
+
now;
|
|
29
|
+
agentConfiguration;
|
|
30
|
+
validateSessionTarget;
|
|
31
|
+
delivery;
|
|
32
|
+
constructor(domain, scheduler, now = () => Date.now(), agentConfiguration, validateSessionTarget, delivery) {
|
|
33
|
+
this.domain = domain;
|
|
34
|
+
this.scheduler = scheduler;
|
|
35
|
+
this.now = now;
|
|
36
|
+
this.agentConfiguration = agentConfiguration;
|
|
37
|
+
this.validateSessionTarget = validateSessionTarget;
|
|
38
|
+
this.delivery = delivery;
|
|
39
|
+
}
|
|
40
|
+
async deliveryOptions(botId) {
|
|
41
|
+
return this.delivery?.options(botId) ?? { available: false, bots: [], targets: [] };
|
|
42
|
+
}
|
|
43
|
+
list() {
|
|
44
|
+
return this.domain.list().map((task) => {
|
|
45
|
+
const permissionDisplayName = this.agentConfiguration?.permissionName(task.security.permissionPreset);
|
|
46
|
+
return permissionDisplayName === undefined ? task : { ...task, permissionDisplayName };
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
get(id) {
|
|
50
|
+
return this.domain.get(id);
|
|
51
|
+
}
|
|
52
|
+
schedulerHealth() {
|
|
53
|
+
return this.scheduler.health();
|
|
54
|
+
}
|
|
55
|
+
async create(request) {
|
|
56
|
+
const execution = normalizedExecution(request.execution);
|
|
57
|
+
await this.agentConfiguration?.validate(execution.target?.mode === 'pinned-session'
|
|
58
|
+
? { ...execution, agentPreset: undefined, provider: undefined, model: undefined, reasoningEffort: undefined, skills: [] }
|
|
59
|
+
: execution, request.permissionPreset);
|
|
60
|
+
const task = await this.domain.create(request, this.now());
|
|
61
|
+
this.scheduler.requestDrive();
|
|
62
|
+
return task;
|
|
63
|
+
}
|
|
64
|
+
async update(id, request) {
|
|
65
|
+
const agentConfiguration = this.agentConfiguration;
|
|
66
|
+
const beforeCommit = async (current) => {
|
|
67
|
+
if (request.delivery != null && (request.delivery.botId !== current.delivery?.botId || request.delivery.targetId !== current.delivery?.targetId)) {
|
|
68
|
+
if (this.delivery === undefined)
|
|
69
|
+
throw new Error('dsh-im direct delivery is unavailable on this Host.');
|
|
70
|
+
await this.delivery.validate(request.delivery);
|
|
71
|
+
}
|
|
72
|
+
if (request.execution?.target?.mode === 'pinned-session') {
|
|
73
|
+
if (this.validateSessionTarget === undefined)
|
|
74
|
+
throw new Error('target_session_unavailable: persisted session validation is unavailable.');
|
|
75
|
+
await this.validateSessionTarget(current, request.execution.target.sessionId);
|
|
76
|
+
}
|
|
77
|
+
if (request.permissionPreset !== undefined && request.permissionPreset !== current.security.permissionPreset && request.permissionChangeConfirmed !== true) {
|
|
78
|
+
throw new Error('Explicit user confirmation is required to change permissions.');
|
|
79
|
+
}
|
|
80
|
+
const execution = applyExecutionPatch(current.execution, request.execution);
|
|
81
|
+
const preservesLegacyPartialModel = request.execution?.provider === undefined
|
|
82
|
+
&& request.execution?.model === undefined
|
|
83
|
+
&& ((current.execution.provider === undefined) !== (current.execution.model === undefined));
|
|
84
|
+
await agentConfiguration?.validate(execution.target?.mode === 'pinned-session'
|
|
85
|
+
? { ...execution, agentPreset: undefined, provider: undefined, model: undefined, reasoningEffort: undefined, skills: [] }
|
|
86
|
+
: execution, request.permissionPreset ?? current.security.permissionPreset, { allowLegacyPartialModel: preservesLegacyPartialModel });
|
|
87
|
+
};
|
|
88
|
+
const task = await this.domain.update(id, request, this.now(), beforeCommit);
|
|
89
|
+
this.scheduler.requestDrive();
|
|
90
|
+
return task;
|
|
91
|
+
}
|
|
92
|
+
async options(id, agentPreset, provider, model) {
|
|
93
|
+
if (this.agentConfiguration === undefined)
|
|
94
|
+
throw new Error('Agent configuration is unavailable.');
|
|
95
|
+
const task = this.domain.get(id);
|
|
96
|
+
const savedModel = provider === undefined && model === undefined && task.execution.target?.mode !== 'pinned-session'
|
|
97
|
+
&& task.execution.provider !== undefined && task.execution.model !== undefined;
|
|
98
|
+
return this.agentConfiguration.options(task.execution.cwd, agentPreset === undefined ? task.execution.agentPreset : agentPreset ?? undefined, savedModel ? task.execution.provider : provider, savedModel ? task.execution.model : model);
|
|
99
|
+
}
|
|
100
|
+
async delete(id) {
|
|
101
|
+
const deleted = await this.domain.delete(id);
|
|
102
|
+
if (deleted)
|
|
103
|
+
this.scheduler.requestDrive();
|
|
104
|
+
return deleted;
|
|
105
|
+
}
|
|
106
|
+
async deleteRun(taskId, runId) {
|
|
107
|
+
return this.domain.deleteRun(taskId, runId);
|
|
108
|
+
}
|
|
109
|
+
async markNotificationsRead() {
|
|
110
|
+
await this.domain.markNotificationsRead();
|
|
111
|
+
}
|
|
112
|
+
async pause(id) {
|
|
113
|
+
const task = await this.domain.pause(id, this.now());
|
|
114
|
+
this.scheduler.requestDrive();
|
|
115
|
+
return task;
|
|
116
|
+
}
|
|
117
|
+
async resume(id, options) {
|
|
118
|
+
const task = await this.domain.resume(id, options, this.now());
|
|
119
|
+
this.scheduler.requestDrive();
|
|
120
|
+
return task;
|
|
121
|
+
}
|
|
122
|
+
async runNow(id) {
|
|
123
|
+
const run = await this.domain.runNow(id, this.now());
|
|
124
|
+
this.scheduler.requestDrive();
|
|
125
|
+
return run;
|
|
126
|
+
}
|
|
127
|
+
async stop(id) {
|
|
128
|
+
const task = this.domain.get(id);
|
|
129
|
+
const run = task.runs.find((entry) => entry.status === 'queued' || entry.status === 'running');
|
|
130
|
+
if (run === undefined) {
|
|
131
|
+
throw new AutomationDomainError('invalid_state', `Automation ${id} has no queued or running run.`);
|
|
132
|
+
}
|
|
133
|
+
if (run.status === 'queued') {
|
|
134
|
+
await this.domain.cancelQueuedRun(id, run.id, this.now());
|
|
135
|
+
this.scheduler.requestDrive();
|
|
136
|
+
return { runId: run.id, status: 'canceled' };
|
|
137
|
+
}
|
|
138
|
+
if (!this.scheduler.cancelRun(id, run.id)) {
|
|
139
|
+
throw new AutomationDomainError('invalid_state', `Automation ${id} is no longer cancelable.`);
|
|
140
|
+
}
|
|
141
|
+
return { runId: run.id, status: 'canceling' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function normalizedExecution(execution) {
|
|
145
|
+
return {
|
|
146
|
+
...execution,
|
|
147
|
+
target: execution.target ?? { mode: 'fresh' },
|
|
148
|
+
...(execution.agentPreset === undefined ? {} : { agentPreset: execution.agentPreset.trim() }),
|
|
149
|
+
...(execution.provider === undefined ? {} : { provider: execution.provider.trim() }),
|
|
150
|
+
...(execution.model === undefined ? {} : { model: execution.model.trim() }),
|
|
151
|
+
skills: normalizeSkills(execution.skills),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=controller.js.map
|
package/lib/domain.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AutomationStore } from './store.js';
|
|
2
|
+
import { type AutomationDelivery, type AutomationRunDelivery } from './types.js';
|
|
3
|
+
import { AutomationError } from './errors.js';
|
|
4
|
+
import type { AutomationRun, AutomationTask, AutomationTaskView, CreateAutomationRequest, ResumeOptions, UpdateAutomationRequest } from './types.js';
|
|
5
|
+
export declare class AutomationDomainError extends AutomationError {
|
|
6
|
+
readonly code: 'task_not_found' | 'invalid_state' | 'run_in_progress' | 'schedule_exhausted';
|
|
7
|
+
constructor(code: 'task_not_found' | 'invalid_state' | 'run_in_progress' | 'schedule_exhausted', message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface ClaimedRun {
|
|
10
|
+
readonly task: AutomationTask;
|
|
11
|
+
readonly run: AutomationRun;
|
|
12
|
+
}
|
|
13
|
+
export interface RunOutcome {
|
|
14
|
+
readonly status: 'succeeded' | 'failed' | 'interrupted' | 'timed_out' | 'canceled';
|
|
15
|
+
readonly sessionId?: string;
|
|
16
|
+
readonly summary?: string;
|
|
17
|
+
/** Full own-turn text for delivery only; never persisted in the run record. */
|
|
18
|
+
readonly output?: string;
|
|
19
|
+
readonly error?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class AutomationDomain {
|
|
22
|
+
readonly store: AutomationStore;
|
|
23
|
+
readonly maxRunHistory: number;
|
|
24
|
+
constructor(store: AutomationStore, maxRunHistory?: number);
|
|
25
|
+
init(now: number): Promise<void>;
|
|
26
|
+
list(): AutomationTaskView[];
|
|
27
|
+
get(id: string): AutomationTask;
|
|
28
|
+
create(request: CreateAutomationRequest, now: number): Promise<AutomationTask>;
|
|
29
|
+
update(id: string, request: UpdateAutomationRequest, now: number, beforeCommit?: (current: AutomationTask) => Promise<void>): Promise<AutomationTask>;
|
|
30
|
+
delete(id: string): Promise<boolean>;
|
|
31
|
+
deleteRun(taskId: string, runId: string): Promise<boolean>;
|
|
32
|
+
markNotificationsRead(): Promise<void>;
|
|
33
|
+
pause(id: string, now: number): Promise<AutomationTask>;
|
|
34
|
+
resume(id: string, options: ResumeOptions, now: number): Promise<AutomationTask>;
|
|
35
|
+
runNow(id: string, now: number): Promise<AutomationRun>;
|
|
36
|
+
cancelQueuedRun(id: string, runId: string, now: number): Promise<AutomationRun>;
|
|
37
|
+
claimDue(now: number): Promise<AutomationRun[]>;
|
|
38
|
+
nextWakeAt(now: number): number | undefined;
|
|
39
|
+
takeNextQueued(now: number): Promise<ClaimedRun | undefined>;
|
|
40
|
+
finishRun(taskId: string, runId: string, outcome: RunOutcome, now: number, delivery?: AutomationDelivery): Promise<void>;
|
|
41
|
+
/** Delivery settlement never changes execution status or failure counters. */
|
|
42
|
+
finishDelivery(taskId: string, runId: string, delivery: AutomationRunDelivery): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=domain.d.ts.map
|