@bizagent/dsh 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +124 -0
- package/LICENSE +21 -0
- package/README.md +61 -2
- package/cordis.patch.yml +14 -0
- package/docs/README.md +23 -0
- package/docs/architecture.md +154 -0
- package/docs/concepts.md +192 -0
- package/docs/configuration.md +154 -0
- package/docs/quickstart.md +103 -0
- package/docs/quickstart.zh-CN.md +86 -0
- package/docs/tools.md +607 -0
- package/docs/user-guide.zh-CN.md +132 -0
- package/docs/verification.md +69 -0
- package/examples/README.md +64 -0
- package/examples/demo-runtime.mjs +71 -0
- package/examples/learning-lab.mjs +263 -0
- package/examples/scenarios.mjs +45 -0
- package/lib/agent-preset.d.ts +20 -0
- package/lib/agent-preset.js +46 -0
- package/lib/agent-session-address.d.ts +14 -0
- package/lib/agent-session-address.js +15 -0
- package/lib/agent-session-coordinator.d.ts +24 -0
- package/lib/agent-session-coordinator.js +101 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +142 -0
- package/lib/client-types/agent-session-address.d.ts +13 -0
- package/lib/client-types/client/agent-mentions.d.ts +3 -0
- package/lib/client-types/client/api.d.ts +180 -0
- package/lib/client-types/client/components.d.ts +22 -0
- package/lib/client-types/client/controller.d.ts +23 -0
- package/lib/client-types/client/frame-participants.d.ts +5 -0
- package/lib/client-types/client/home-name.d.ts +9 -0
- package/lib/client-types/client/human-inbox.d.ts +8 -0
- package/lib/client-types/client/index.d.ts +13 -0
- package/lib/client-types/client/locales.d.ts +796 -0
- package/lib/client-types/client/markdown.d.ts +12 -0
- package/lib/client-types/client/project-artifact-link.d.ts +10 -0
- package/lib/client-types/client/project-outcome-review-inbox.d.ts +13 -0
- package/lib/client-types/client/projects.d.ts +11 -0
- package/lib/client-types/client/sample-guide.d.ts +9 -0
- package/lib/client-types/client/session-resource.d.ts +34 -0
- package/lib/client-types/client/session-surfaces.d.ts +24 -0
- package/lib/client-types/client/styles.d.ts +1 -0
- package/lib/client-types/config.d.ts +55 -0
- package/lib/client-types/cron.d.ts +8 -0
- package/lib/client-types/domain.d.ts +818 -0
- package/lib/client-types/frame-reference.d.ts +4 -0
- package/lib/client-types/home-directory.d.ts +5 -0
- package/lib/client-types/home-space.d.ts +49 -0
- package/lib/client-types/home-store.d.ts +125 -0
- package/lib/client-types/inbox-stalled-topic.d.ts +15 -0
- package/lib/client-types/inbox-wake-policy.d.ts +18 -0
- package/lib/client-types/known-format.d.ts +2 -0
- package/lib/client-types/learning-scheduler.d.ts +36 -0
- package/lib/client-types/learning-session-runner.d.ts +49 -0
- package/lib/client-types/learning-trigger.d.ts +17 -0
- package/lib/client-types/learning-usage.d.ts +17 -0
- package/lib/client-types/ledger.d.ts +190 -0
- package/lib/client-types/local-owner.d.ts +7 -0
- package/lib/client-types/message-source.d.ts +9 -0
- package/lib/client-types/organization-location.d.ts +19 -0
- package/lib/client-types/project-ledger.d.ts +182 -0
- package/lib/client-types/project-seat-context.d.ts +17 -0
- package/lib/client-types/safe-file.d.ts +23 -0
- package/lib/client-types/sample-organization.d.ts +9 -0
- package/lib/client-types/service.d.ts +596 -0
- package/lib/client-types/shared-knowledge.d.ts +116 -0
- package/lib/client-types/ui-contract.d.ts +276 -0
- package/lib/client-types/untrusted-text.d.ts +15 -0
- package/lib/client-types/work-ledger.d.ts +108 -0
- package/lib/client-types/work-output-eval.d.ts +15 -0
- package/lib/client-types/worklog-ledger.d.ts +88 -0
- package/lib/client-types/worklog-snapshot.d.ts +33 -0
- package/lib/client.js +5463 -0
- package/lib/config.d.ts +56 -0
- package/lib/config.js +115 -0
- package/lib/cron.d.ts +9 -0
- package/lib/cron.js +127 -0
- package/lib/domain.d.ts +819 -0
- package/lib/domain.js +662 -0
- package/lib/frame-admission.d.ts +6 -0
- package/lib/frame-admission.js +39 -0
- package/lib/frame-reference.d.ts +5 -0
- package/lib/frame-reference.js +12 -0
- package/lib/home-archive.d.ts +17 -0
- package/lib/home-archive.js +177 -0
- package/lib/home-directory.d.ts +6 -0
- package/lib/home-directory.js +38 -0
- package/lib/home-space.d.ts +50 -0
- package/lib/home-space.js +120 -0
- package/lib/home-store.d.ts +126 -0
- package/lib/home-store.js +837 -0
- package/lib/host.d.ts +22 -0
- package/lib/host.js +15 -0
- package/lib/human-inbox-transport.d.ts +23 -0
- package/lib/human-inbox-transport.js +105 -0
- package/lib/human-notification-runner.d.ts +83 -0
- package/lib/human-notification-runner.js +138 -0
- package/lib/human-notification-webhook.d.ts +18 -0
- package/lib/human-notification-webhook.js +102 -0
- package/lib/human-turn-completion.d.ts +61 -0
- package/lib/human-turn-completion.js +157 -0
- package/lib/inbox-return-notifier.d.ts +46 -0
- package/lib/inbox-return-notifier.js +279 -0
- package/lib/inbox-stalled-escalation-runner.d.ts +31 -0
- package/lib/inbox-stalled-escalation-runner.js +68 -0
- package/lib/inbox-stalled-topic.d.ts +16 -0
- package/lib/inbox-stalled-topic.js +44 -0
- package/lib/inbox-wake-policy.d.ts +19 -0
- package/lib/inbox-wake-policy.js +28 -0
- package/lib/inbox-wake-runner.d.ts +45 -0
- package/lib/inbox-wake-runner.js +481 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +480 -0
- package/lib/known-format.d.ts +3 -0
- package/lib/known-format.js +8 -0
- package/lib/learning-scheduler.d.ts +37 -0
- package/lib/learning-scheduler.js +182 -0
- package/lib/learning-session-runner.d.ts +50 -0
- package/lib/learning-session-runner.js +435 -0
- package/lib/learning-trigger.d.ts +18 -0
- package/lib/learning-trigger.js +92 -0
- package/lib/learning-usage.d.ts +18 -0
- package/lib/learning-usage.js +49 -0
- package/lib/ledger.d.ts +191 -0
- package/lib/ledger.js +752 -0
- package/lib/local-human-host.d.ts +11 -0
- package/lib/local-human-host.js +191 -0
- package/lib/local-human-token.d.ts +9 -0
- package/lib/local-human-token.js +32 -0
- package/lib/local-owner.d.ts +8 -0
- package/lib/local-owner.js +26 -0
- package/lib/message-source.d.ts +10 -0
- package/lib/message-source.js +2 -0
- package/lib/organization-location.d.ts +20 -0
- package/lib/organization-location.js +80 -0
- package/lib/organization-workspace.d.ts +22 -0
- package/lib/organization-workspace.js +32 -0
- package/lib/project-ledger.d.ts +183 -0
- package/lib/project-ledger.js +189 -0
- package/lib/project-review-transport.d.ts +23 -0
- package/lib/project-review-transport.js +155 -0
- package/lib/project-seat-context.d.ts +18 -0
- package/lib/project-seat-context.js +34 -0
- package/lib/proposal-review-transport.d.ts +26 -0
- package/lib/proposal-review-transport.js +104 -0
- package/lib/route-guard.d.ts +31 -0
- package/lib/route-guard.js +80 -0
- package/lib/safe-file.d.ts +24 -0
- package/lib/safe-file.js +93 -0
- package/lib/sample-organization.d.ts +10 -0
- package/lib/sample-organization.js +46 -0
- package/lib/service.d.ts +597 -0
- package/lib/service.js +4076 -0
- package/lib/session-surface.d.ts +11 -0
- package/lib/session-surface.js +109 -0
- package/lib/shared-knowledge-review-transport.d.ts +28 -0
- package/lib/shared-knowledge-review-transport.js +85 -0
- package/lib/shared-knowledge.d.ts +117 -0
- package/lib/shared-knowledge.js +240 -0
- package/lib/tools.d.ts +7 -0
- package/lib/tools.js +955 -0
- package/lib/ui-contract.d.ts +277 -0
- package/lib/ui-contract.js +12 -0
- package/lib/ui-host.d.ts +24 -0
- package/lib/ui-host.js +471 -0
- package/lib/untrusted-text.d.ts +16 -0
- package/lib/untrusted-text.js +50 -0
- package/lib/work-ledger.d.ts +109 -0
- package/lib/work-ledger.js +107 -0
- package/lib/work-output-eval.d.ts +16 -0
- package/lib/work-output-eval.js +58 -0
- package/lib/work-runner.d.ts +47 -0
- package/lib/work-runner.js +352 -0
- package/lib/worklog-ledger.d.ts +89 -0
- package/lib/worklog-ledger.js +91 -0
- package/lib/worklog-snapshot.d.ts +34 -0
- package/lib/worklog-snapshot.js +100 -0
- package/lib/writer-guard.d.ts +11 -0
- package/lib/writer-guard.js +207 -0
- package/package.json +172 -4
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
2
|
+
import { type ContextBudget, type HomeAddress } from './domain.js';
|
|
3
|
+
export interface LearningScheduleConfig {
|
|
4
|
+
home: string;
|
|
5
|
+
cron: string;
|
|
6
|
+
model: string;
|
|
7
|
+
provider?: string;
|
|
8
|
+
maxTokens?: number;
|
|
9
|
+
dailyRuns?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvedLearningSchedule {
|
|
12
|
+
home: HomeAddress;
|
|
13
|
+
cron: string;
|
|
14
|
+
model: string;
|
|
15
|
+
provider?: string;
|
|
16
|
+
maxTokens?: number;
|
|
17
|
+
dailyRuns: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Config {
|
|
20
|
+
homeRoot?: string;
|
|
21
|
+
defaultHome?: string;
|
|
22
|
+
defaultIdentity?: string;
|
|
23
|
+
autoCreateDefaultHome?: boolean;
|
|
24
|
+
workspaceHomes?: Record<string, string>;
|
|
25
|
+
identityMaxBytes?: number;
|
|
26
|
+
indexMaxBytes?: number;
|
|
27
|
+
learningCheckpointEnabled?: boolean;
|
|
28
|
+
learningSchedulerEnabled?: boolean;
|
|
29
|
+
learningSchedules?: LearningScheduleConfig[];
|
|
30
|
+
timeZone?: string;
|
|
31
|
+
owner?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const Config: Schema<Config>;
|
|
34
|
+
export interface ResolvedConfig {
|
|
35
|
+
homeRoot: string;
|
|
36
|
+
defaultHome?: HomeAddress;
|
|
37
|
+
defaultIdentity?: string;
|
|
38
|
+
autoCreateDefaultHome: boolean;
|
|
39
|
+
workspaceHomes: ReadonlyArray<{
|
|
40
|
+
root: string;
|
|
41
|
+
address: HomeAddress;
|
|
42
|
+
}>;
|
|
43
|
+
budget: ContextBudget;
|
|
44
|
+
learningCheckpointEnabled: boolean;
|
|
45
|
+
learningSchedulerEnabled: boolean;
|
|
46
|
+
learningSchedules: ReadonlyArray<ResolvedLearningSchedule>;
|
|
47
|
+
timeZone: string;
|
|
48
|
+
owner: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function resolveConfig(input?: Config): ResolvedConfig;
|
|
51
|
+
/**
|
|
52
|
+
* `$BIZAGENT_HOME`, else `~/BizAgent`. Data written before the data root moved, at
|
|
53
|
+
* `$DSH_HOME/bizagent`, keeps being used until it is moved, so upgrading never hides it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function defaultHomeRoot(): string;
|
|
56
|
+
//# sourceMappingURL=config.d.ts.map
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
5
|
+
import { parseHomeAddress } from './domain.js';
|
|
6
|
+
import { hostTimeZone, nextCronAt, resolveTimeZone } from './cron.js';
|
|
7
|
+
import { isLocalUserId, localOwnerId } from './local-owner.js';
|
|
8
|
+
export const Config = Schema.object({
|
|
9
|
+
homeRoot: Schema.string().description('BizAgent data root: your personal Homes, and your organizations under orgs/. '
|
|
10
|
+
+ 'Defaults to $BIZAGENT_HOME or ~/BizAgent.'),
|
|
11
|
+
defaultHome: Schema.string().default('personal:default'),
|
|
12
|
+
defaultIdentity: Schema.string(),
|
|
13
|
+
autoCreateDefaultHome: Schema.boolean().default(true),
|
|
14
|
+
workspaceHomes: Schema.dict(Schema.string()).default({}),
|
|
15
|
+
identityMaxBytes: Schema.number().step(1).min(256).default(6144),
|
|
16
|
+
indexMaxBytes: Schema.number().step(1).min(512).default(16384),
|
|
17
|
+
learningCheckpointEnabled: Schema.boolean().default(true),
|
|
18
|
+
learningSchedulerEnabled: Schema.boolean().default(false).description('Run host-configured learning schedules every 30 seconds. Enable on exactly one host process for this profile.'),
|
|
19
|
+
learningSchedules: Schema.array(Schema.object({
|
|
20
|
+
home: Schema.string(), cron: Schema.string(), model: Schema.string(),
|
|
21
|
+
provider: Schema.string(), maxTokens: Schema.number().step(1).min(1),
|
|
22
|
+
dailyRuns: Schema.number().step(1).min(1).max(4).default(1),
|
|
23
|
+
})).default([]),
|
|
24
|
+
timeZone: Schema.string().description('IANA time zone for cron schedules and daily run limits. Defaults to the host time zone.'),
|
|
25
|
+
owner: Schema.string().description('Person who owns Homes this host creates (the default Home, onboarding organizations, and Homes created in the UI). '
|
|
26
|
+
+ 'Defaults to $BIZAGENT_LOCAL_HUMAN_ID or the operating system user name.'),
|
|
27
|
+
});
|
|
28
|
+
export function resolveConfig(input = {}) {
|
|
29
|
+
const homeRoot = resolve(expandHome(input.homeRoot?.trim() || defaultHomeRoot()));
|
|
30
|
+
const defaultHome = input.defaultHome?.trim()
|
|
31
|
+
? parseHomeAddress(input.defaultHome.trim())
|
|
32
|
+
: undefined;
|
|
33
|
+
const workspaceHomes = Object.entries(input.workspaceHomes ?? {})
|
|
34
|
+
.map(([root, address]) => ({
|
|
35
|
+
root: resolve(expandHome(root)),
|
|
36
|
+
address: parseHomeAddress(address),
|
|
37
|
+
}))
|
|
38
|
+
.sort((a, b) => b.root.length - a.root.length);
|
|
39
|
+
const identityMaxBytes = input.identityMaxBytes ?? 6144;
|
|
40
|
+
const indexMaxBytes = input.indexMaxBytes ?? 16384;
|
|
41
|
+
if (!Number.isSafeInteger(identityMaxBytes) || identityMaxBytes < 256) {
|
|
42
|
+
throw new TypeError('identityMaxBytes must be an integer >= 256');
|
|
43
|
+
}
|
|
44
|
+
if (!Number.isSafeInteger(indexMaxBytes) || indexMaxBytes < 512) {
|
|
45
|
+
throw new TypeError('indexMaxBytes must be an integer >= 512');
|
|
46
|
+
}
|
|
47
|
+
const timeZone = input.timeZone?.trim() ? resolveTimeZone(input.timeZone) : hostTimeZone();
|
|
48
|
+
const owner = input.owner?.trim() || localOwnerId();
|
|
49
|
+
if (!isLocalUserId(owner))
|
|
50
|
+
throw new TypeError('owner must use 1-100 letters, numbers, dots, underscores, or hyphens');
|
|
51
|
+
const learningSchedules = (input.learningSchedules ?? []).map(item => {
|
|
52
|
+
const home = parseHomeAddress(item.home);
|
|
53
|
+
const cron = item.cron.trim();
|
|
54
|
+
nextCronAt(cron, Date.now());
|
|
55
|
+
const model = item.model.trim();
|
|
56
|
+
const provider = item.provider?.trim();
|
|
57
|
+
const dailyRuns = item.dailyRuns ?? 1;
|
|
58
|
+
if (!model || model.length > 200)
|
|
59
|
+
throw new TypeError('learning schedule model must contain 1 to 200 characters');
|
|
60
|
+
if (provider !== undefined && (!provider || provider.length > 200)) {
|
|
61
|
+
throw new TypeError('learning schedule provider must contain 1 to 200 characters');
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isSafeInteger(dailyRuns) || dailyRuns < 1 || dailyRuns > 4) {
|
|
64
|
+
throw new TypeError('learning schedule dailyRuns must be between 1 and 4');
|
|
65
|
+
}
|
|
66
|
+
if (item.maxTokens !== undefined && (!Number.isSafeInteger(item.maxTokens) || item.maxTokens < 1)) {
|
|
67
|
+
throw new TypeError('learning schedule maxTokens must be a positive integer');
|
|
68
|
+
}
|
|
69
|
+
if (workspaceHomes.filter(mapping => mapping.address === home).length !== 1) {
|
|
70
|
+
throw new TypeError(`learning schedule Home ${home} requires exactly one mapped Workspace`);
|
|
71
|
+
}
|
|
72
|
+
return { home, cron, model, dailyRuns,
|
|
73
|
+
...(provider ? { provider } : {}),
|
|
74
|
+
...(item.maxTokens ? { maxTokens: item.maxTokens } : {}),
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
if (new Set(learningSchedules.map(item => item.home)).size !== learningSchedules.length) {
|
|
78
|
+
throw new TypeError('learning schedules require distinct Homes');
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
homeRoot,
|
|
82
|
+
...(defaultHome !== undefined ? { defaultHome } : {}),
|
|
83
|
+
...(input.defaultIdentity?.trim() ? { defaultIdentity: input.defaultIdentity.trim() } : {}),
|
|
84
|
+
autoCreateDefaultHome: input.autoCreateDefaultHome ?? true,
|
|
85
|
+
workspaceHomes,
|
|
86
|
+
budget: { identityMaxBytes, indexMaxBytes },
|
|
87
|
+
learningCheckpointEnabled: input.learningCheckpointEnabled ?? true,
|
|
88
|
+
learningSchedulerEnabled: input.learningSchedulerEnabled ?? false,
|
|
89
|
+
learningSchedules,
|
|
90
|
+
timeZone,
|
|
91
|
+
owner,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `$BIZAGENT_HOME`, else `~/BizAgent`. Data written before the data root moved, at
|
|
96
|
+
* `$DSH_HOME/bizagent`, keeps being used until it is moved, so upgrading never hides it.
|
|
97
|
+
*/
|
|
98
|
+
export function defaultHomeRoot() {
|
|
99
|
+
const configured = process.env['BIZAGENT_HOME']?.trim();
|
|
100
|
+
if (configured)
|
|
101
|
+
return expandHome(configured);
|
|
102
|
+
const current = join(homedir(), 'BizAgent');
|
|
103
|
+
const legacy = join(process.env['DSH_HOME']?.trim() || join(homedir(), '.dsh'), 'bizagent');
|
|
104
|
+
return !existsSync(current) && existsSync(join(legacy, 'directory.yaml')) ? legacy : current;
|
|
105
|
+
}
|
|
106
|
+
function expandHome(path) {
|
|
107
|
+
if (path === '~')
|
|
108
|
+
return homedir();
|
|
109
|
+
if (path.startsWith('~/') || path.startsWith('~\\'))
|
|
110
|
+
return join(homedir(), path.slice(2));
|
|
111
|
+
if (isAbsolute(path))
|
|
112
|
+
return path;
|
|
113
|
+
return resolve(path);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=config.js.map
|
package/lib/cron.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The host's IANA time zone, or UTC when the runtime cannot report one. */
|
|
2
|
+
export declare function hostTimeZone(): string;
|
|
3
|
+
/** Validate an IANA time zone name and return its canonical spelling. */
|
|
4
|
+
export declare function resolveTimeZone(timeZone: string): string;
|
|
5
|
+
/** Calendar day (YYYY-MM-DD) of an instant in the given time zone. */
|
|
6
|
+
export declare function zonedDay(time: number, timeZone?: string): string;
|
|
7
|
+
/** Next five-field cron occurrence in `timeZone`, strictly after `after`. */
|
|
8
|
+
export declare function nextCronAt(cron: string, after: number, timeZone?: string): number;
|
|
9
|
+
//# sourceMappingURL=cron.d.ts.map
|
package/lib/cron.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const MINUTE = 60_000;
|
|
2
|
+
const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
|
|
3
|
+
const DAYS = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
|
|
4
|
+
const formatters = new Map();
|
|
5
|
+
/** The host's IANA time zone, or UTC when the runtime cannot report one. */
|
|
6
|
+
export function hostTimeZone() {
|
|
7
|
+
try {
|
|
8
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return 'UTC';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Validate an IANA time zone name and return its canonical spelling. */
|
|
15
|
+
export function resolveTimeZone(timeZone) {
|
|
16
|
+
const name = timeZone.trim();
|
|
17
|
+
if (!name)
|
|
18
|
+
throw new TypeError('timeZone must name an IANA time zone');
|
|
19
|
+
try {
|
|
20
|
+
return new Intl.DateTimeFormat('en-US', { timeZone: name }).resolvedOptions().timeZone;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new TypeError(`unknown timeZone: ${timeZone}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Calendar day (YYYY-MM-DD) of an instant in the given time zone. */
|
|
27
|
+
export function zonedDay(time, timeZone = 'UTC') {
|
|
28
|
+
const clock = wallClock(time, timeZone);
|
|
29
|
+
return `${clock.year}-${String(clock.month).padStart(2, '0')}-${String(clock.day).padStart(2, '0')}`;
|
|
30
|
+
}
|
|
31
|
+
/** Next five-field cron occurrence in `timeZone`, strictly after `after`. */
|
|
32
|
+
export function nextCronAt(cron, after, timeZone = 'UTC') {
|
|
33
|
+
if (!Number.isSafeInteger(after) || after < 0)
|
|
34
|
+
throw new Error('invalid cron clock');
|
|
35
|
+
const fields = cron.trim().split(/\s+/);
|
|
36
|
+
if (fields.length !== 5)
|
|
37
|
+
throw new Error('cron requires five fields');
|
|
38
|
+
const [minute, hour, day, month, weekday] = [
|
|
39
|
+
parseField(fields[0], 0, 59), parseField(fields[1], 0, 23),
|
|
40
|
+
parseField(fields[2], 1, 31), parseField(fields[3], 1, 12, MONTHS),
|
|
41
|
+
parseField(fields[4], 0, 7, DAYS),
|
|
42
|
+
];
|
|
43
|
+
const limit = after + 8 * 366 * 24 * 60 * MINUTE;
|
|
44
|
+
let candidate = Math.floor(after / MINUTE) * MINUTE + MINUTE;
|
|
45
|
+
while (candidate < limit) {
|
|
46
|
+
const local = wallClock(candidate, timeZone);
|
|
47
|
+
const dateMatch = day.wildcard && weekday.wildcard ? true
|
|
48
|
+
: day.wildcard ? weekday.values.has(local.weekday)
|
|
49
|
+
: weekday.wildcard ? day.values.has(local.day)
|
|
50
|
+
: day.values.has(local.day) || weekday.values.has(local.weekday);
|
|
51
|
+
// Skip to the next local hour when the date or hour cannot match. Offset changes happen on
|
|
52
|
+
// hour or half-hour boundaries, so this never steps over a valid minute.
|
|
53
|
+
if (!month.values.has(local.month) || !dateMatch || !hour.values.has(local.hour)) {
|
|
54
|
+
candidate += (60 - local.minute) * MINUTE;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (minute.values.has(local.minute))
|
|
58
|
+
return candidate;
|
|
59
|
+
candidate += MINUTE;
|
|
60
|
+
}
|
|
61
|
+
throw new Error('cron has no occurrence within eight years');
|
|
62
|
+
}
|
|
63
|
+
function wallClock(time, timeZone) {
|
|
64
|
+
if (timeZone === 'UTC') {
|
|
65
|
+
const date = new Date(time);
|
|
66
|
+
return { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(),
|
|
67
|
+
weekday: date.getUTCDay(), hour: date.getUTCHours(), minute: date.getUTCMinutes() };
|
|
68
|
+
}
|
|
69
|
+
let formatter = formatters.get(timeZone);
|
|
70
|
+
if (formatter === undefined) {
|
|
71
|
+
formatter = new Intl.DateTimeFormat('en-US', { timeZone, hourCycle: 'h23', year: 'numeric',
|
|
72
|
+
month: 'numeric', day: 'numeric', weekday: 'short', hour: 'numeric', minute: 'numeric' });
|
|
73
|
+
formatters.set(timeZone, formatter);
|
|
74
|
+
}
|
|
75
|
+
const parts = {};
|
|
76
|
+
for (const part of formatter.formatToParts(time))
|
|
77
|
+
parts[part.type] = part.value;
|
|
78
|
+
return { year: Number(parts['year']), month: Number(parts['month']), day: Number(parts['day']),
|
|
79
|
+
weekday: DAYS.indexOf(String(parts['weekday']).toUpperCase()),
|
|
80
|
+
hour: Number(parts['hour']), minute: Number(parts['minute']) };
|
|
81
|
+
}
|
|
82
|
+
function parseField(text, min, max, names) {
|
|
83
|
+
const wildcard = text === '*' || text === '?';
|
|
84
|
+
const values = new Set();
|
|
85
|
+
for (const entry of text.toUpperCase().split(',')) {
|
|
86
|
+
const match = /^([^/]+)(?:\/(\d+))?$/.exec(entry);
|
|
87
|
+
if (!match)
|
|
88
|
+
throw new Error(`invalid cron field: ${text}`);
|
|
89
|
+
const [, base, stepText] = match;
|
|
90
|
+
const step = stepText === undefined ? 1 : Number(stepText);
|
|
91
|
+
if (!Number.isSafeInteger(step) || step < 1)
|
|
92
|
+
throw new Error(`invalid cron step: ${text}`);
|
|
93
|
+
let start;
|
|
94
|
+
let end;
|
|
95
|
+
if (base === '*' || base === '?') {
|
|
96
|
+
start = min;
|
|
97
|
+
end = max;
|
|
98
|
+
}
|
|
99
|
+
else if (base.includes('-')) {
|
|
100
|
+
const parts = base.split('-');
|
|
101
|
+
if (parts.length !== 2)
|
|
102
|
+
throw new Error(`invalid cron range: ${text}`);
|
|
103
|
+
start = fieldNumber(parts[0], min, max, names);
|
|
104
|
+
end = fieldNumber(parts[1], min, max, names);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
start = fieldNumber(base, min, max, names);
|
|
108
|
+
end = stepText === undefined ? start : max;
|
|
109
|
+
}
|
|
110
|
+
if (start > end)
|
|
111
|
+
throw new Error(`reversed cron range: ${text}`);
|
|
112
|
+
for (let value = start; value <= end; value += step)
|
|
113
|
+
values.add(max === 7 && value === 7 ? 0 : value);
|
|
114
|
+
}
|
|
115
|
+
if (values.size === 0)
|
|
116
|
+
throw new Error(`empty cron field: ${text}`);
|
|
117
|
+
return { values, wildcard };
|
|
118
|
+
}
|
|
119
|
+
function fieldNumber(text, min, max, names) {
|
|
120
|
+
const named = names?.indexOf(text);
|
|
121
|
+
const value = named !== undefined && named >= 0 ? named + (max === 12 ? 1 : 0) : Number(text);
|
|
122
|
+
if (!Number.isSafeInteger(value) || value < min || value > max || text.trim() === '') {
|
|
123
|
+
throw new Error(`invalid cron value: ${text}`);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=cron.js.map
|