@qilin-agent/schedule 3.1.7 → 3.1.8
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.i18n.yaml +72 -39
- package/README.md +180 -56
- package/README.zh.md +185 -61
- package/lib/index.js +5565 -1191
- package/lib/typert.host.js +1886 -267
- package/lib/typert.remote-client.d.ts +22 -10
- package/lib/typert.remote-client.js +1727 -190
- package/lib/types/delivery-history.d.ts +4 -6
- package/lib/types/delivery-history.js +7 -20
- package/lib/types/domain.d.ts +117 -80
- package/lib/types/domain.js +311 -797
- package/lib/types/index.d.ts +168 -80
- package/lib/types/index.js +531 -222
- package/lib/types/runtime.d.ts +92 -4
- package/lib/types/runtime.js +392 -72
- package/lib/types/storage.d.ts +126 -24
- package/lib/types/storage.js +85 -16
- package/lib/types/types.d.ts +287 -148
- package/lib/types/types.js +1 -1
- package/lib/types/update.d.ts +16 -21
- package/lib/types/update.js +72 -144
- package/package.json +38 -35
package/lib/types/storage.d.ts
CHANGED
|
@@ -1,57 +1,159 @@
|
|
|
1
|
-
/** Durable Host-wide Schedule tasks, independently of Session activation. */
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
import { SessionId } from '@qilin-agent/session';
|
|
4
3
|
import { MessageId } from '@qilin-agent/llm/brand';
|
|
5
|
-
import type { ScheduleId } from './types.ts';
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import type { ScheduleDeliveryRecord, ScheduleId, ScheduleTarget } from './types.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Version-2 task schema accepting version-1 rows. New metadata is optional on disk. An explicit target
|
|
7
|
+
* ignores the former Session binding; a missing target uses that binding or a stable create-once destination.
|
|
8
|
+
* Normal decoding does not write. The next edit or delivery writes this current shape without `sessionId`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const scheduleTaskSchema: z.ZodPipe<z.ZodObject<{
|
|
11
|
+
record: z.ZodPipe<z.ZodUnknown, z.ZodTransform<import("./types.ts").AfterScheduleRecord | import("./types.ts").AtScheduleRecord | import("./types.ts").EveryScheduleRecord | import("./types.ts").DailyScheduleRecord | import("./types.ts").WeeklyScheduleRecord | import("./types.ts").CronScheduleRecord | import("./types.ts").RRuleScheduleRecord, unknown>>;
|
|
12
|
+
sessionId: z.ZodOptional<z.ZodUnknown>;
|
|
13
|
+
target: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
14
|
+
kind: z.ZodLiteral<"session">;
|
|
15
|
+
sessionId: z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>;
|
|
16
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
17
|
+
kind: z.ZodLiteral<"new_once">;
|
|
18
|
+
sessionId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>>;
|
|
19
|
+
create: z.ZodDefault<z.ZodObject<{
|
|
20
|
+
cwd: z.ZodExactOptional<z.ZodString>;
|
|
21
|
+
agentPreset: z.ZodExactOptional<z.ZodString>;
|
|
22
|
+
}, z.core.$strict>>;
|
|
23
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
24
|
+
kind: z.ZodLiteral<"new">;
|
|
25
|
+
create: z.ZodDefault<z.ZodObject<{
|
|
26
|
+
cwd: z.ZodExactOptional<z.ZodString>;
|
|
27
|
+
agentPreset: z.ZodExactOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$strict>>;
|
|
29
|
+
}, z.core.$strict>], "kind">>;
|
|
30
|
+
involvedSessionIds: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>>>;
|
|
10
31
|
status: z.ZodDefault<z.ZodEnum<{
|
|
11
32
|
active: "active";
|
|
33
|
+
paused: "paused";
|
|
34
|
+
ended: "ended";
|
|
12
35
|
inactive: "inactive";
|
|
13
36
|
}>>;
|
|
37
|
+
createdAt: z.ZodOptional<z.ZodISODateTime>;
|
|
38
|
+
updatedAt: z.ZodOptional<z.ZodISODateTime>;
|
|
14
39
|
lastDelivery: z.ZodOptional<z.ZodObject<{
|
|
15
40
|
scheduledAt: z.ZodISODateTime;
|
|
16
41
|
deliveredAt: z.ZodISODateTime;
|
|
17
42
|
messageId: z.ZodPipe<z.ZodString, z.ZodTransform<MessageId, string>>;
|
|
43
|
+
targetSessionId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>>;
|
|
18
44
|
}, z.core.$strict>>;
|
|
19
45
|
deliveryHistory: z.ZodOptional<z.ZodObject<{
|
|
20
46
|
records: z.ZodArray<z.ZodObject<{
|
|
21
47
|
scheduledAt: z.ZodISODateTime;
|
|
22
48
|
deliveredAt: z.ZodISODateTime;
|
|
23
49
|
messageId: z.ZodPipe<z.ZodString, z.ZodTransform<MessageId, string>>;
|
|
24
|
-
|
|
50
|
+
targetSessionId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>>;
|
|
51
|
+
prompt: z.ZodExactOptional<z.ZodString>;
|
|
25
52
|
}, z.core.$strict>>;
|
|
26
53
|
earlierRecordsUnavailable: z.ZodBoolean;
|
|
27
|
-
earlierRecordsPruned: z.
|
|
54
|
+
earlierRecordsPruned: z.ZodDefault<z.ZodBoolean>;
|
|
28
55
|
}, z.core.$strict>>;
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
pendingDelivery: z.ZodOptional<z.ZodObject<{
|
|
57
|
+
targetSessionId: z.ZodPipe<z.ZodString, z.ZodTransform<SessionId, string>>;
|
|
58
|
+
}, z.core.$strict>>;
|
|
59
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
60
|
+
deliveryHistory: {
|
|
61
|
+
records: ScheduleDeliveryRecord[];
|
|
62
|
+
earlierRecordsUnavailable: boolean;
|
|
63
|
+
earlierRecordsPruned: boolean;
|
|
64
|
+
};
|
|
65
|
+
lastDelivery?: {
|
|
66
|
+
scheduledAt: string;
|
|
67
|
+
deliveredAt: string;
|
|
68
|
+
messageId: MessageId;
|
|
69
|
+
targetSessionId?: SessionId | undefined;
|
|
70
|
+
} & {
|
|
71
|
+
targetSessionId: SessionId;
|
|
72
|
+
};
|
|
73
|
+
target: ScheduleTarget;
|
|
74
|
+
status: "active" | "paused" | "ended";
|
|
75
|
+
involvedSessionIds: SessionId[];
|
|
76
|
+
record: import("./types.ts").AfterScheduleRecord | import("./types.ts").AtScheduleRecord | import("./types.ts").EveryScheduleRecord | import("./types.ts").DailyScheduleRecord | import("./types.ts").WeeklyScheduleRecord | import("./types.ts").CronScheduleRecord | import("./types.ts").RRuleScheduleRecord;
|
|
77
|
+
createdAt?: string | undefined;
|
|
78
|
+
updatedAt?: string | undefined;
|
|
79
|
+
pendingDelivery?: {
|
|
80
|
+
targetSessionId: SessionId;
|
|
81
|
+
} | undefined;
|
|
82
|
+
}, {
|
|
83
|
+
record: import("./types.ts").AfterScheduleRecord | import("./types.ts").AtScheduleRecord | import("./types.ts").EveryScheduleRecord | import("./types.ts").DailyScheduleRecord | import("./types.ts").WeeklyScheduleRecord | import("./types.ts").CronScheduleRecord | import("./types.ts").RRuleScheduleRecord;
|
|
84
|
+
status: "active" | "paused" | "ended" | "inactive";
|
|
85
|
+
sessionId?: unknown;
|
|
86
|
+
target?: {
|
|
87
|
+
kind: "session";
|
|
88
|
+
sessionId: SessionId;
|
|
89
|
+
} | {
|
|
90
|
+
kind: "new_once";
|
|
91
|
+
create: {
|
|
92
|
+
cwd?: string;
|
|
93
|
+
agentPreset?: string;
|
|
94
|
+
};
|
|
95
|
+
sessionId?: SessionId | undefined;
|
|
96
|
+
} | {
|
|
97
|
+
kind: "new";
|
|
98
|
+
create: {
|
|
99
|
+
cwd?: string;
|
|
100
|
+
agentPreset?: string;
|
|
101
|
+
};
|
|
102
|
+
} | undefined;
|
|
103
|
+
involvedSessionIds?: SessionId[] | undefined;
|
|
104
|
+
createdAt?: string | undefined;
|
|
105
|
+
updatedAt?: string | undefined;
|
|
106
|
+
lastDelivery?: {
|
|
107
|
+
scheduledAt: string;
|
|
108
|
+
deliveredAt: string;
|
|
109
|
+
messageId: MessageId;
|
|
110
|
+
targetSessionId?: SessionId | undefined;
|
|
111
|
+
} | undefined;
|
|
112
|
+
deliveryHistory?: {
|
|
113
|
+
records: {
|
|
114
|
+
scheduledAt: string;
|
|
115
|
+
deliveredAt: string;
|
|
116
|
+
messageId: MessageId;
|
|
117
|
+
targetSessionId?: SessionId | undefined;
|
|
118
|
+
prompt?: string;
|
|
119
|
+
}[];
|
|
120
|
+
earlierRecordsUnavailable: boolean;
|
|
121
|
+
earlierRecordsPruned: boolean;
|
|
122
|
+
} | undefined;
|
|
123
|
+
pendingDelivery?: {
|
|
124
|
+
targetSessionId: SessionId;
|
|
125
|
+
} | undefined;
|
|
126
|
+
}>>;
|
|
127
|
+
/** Persistent task value; a delivery receipt does not record model execution completion. */
|
|
31
128
|
export type ScheduleTask = z.infer<typeof scheduleTaskSchema>;
|
|
32
|
-
/** Authoritative
|
|
129
|
+
/** Authoritative Host tasks retain their domain, backend routing, and envelope; v2 also reads v1 without startup writes. */
|
|
33
130
|
export declare const scheduleDomain: {
|
|
34
131
|
name: string;
|
|
35
132
|
version: number;
|
|
133
|
+
compatibleVersions: number[];
|
|
36
134
|
tables: {
|
|
37
135
|
tasks: import("@qilin-agent/storage-domain").DomainTableSpec<ScheduleId, {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
136
|
+
deliveryHistory: {
|
|
137
|
+
records: ScheduleDeliveryRecord[];
|
|
138
|
+
earlierRecordsUnavailable: boolean;
|
|
139
|
+
earlierRecordsPruned: boolean;
|
|
140
|
+
};
|
|
41
141
|
lastDelivery?: {
|
|
42
142
|
scheduledAt: string;
|
|
43
143
|
deliveredAt: string;
|
|
44
144
|
messageId: MessageId;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
145
|
+
targetSessionId?: SessionId | undefined;
|
|
146
|
+
} & {
|
|
147
|
+
targetSessionId: SessionId;
|
|
148
|
+
};
|
|
149
|
+
target: ScheduleTarget;
|
|
150
|
+
status: "active" | "paused" | "ended";
|
|
151
|
+
involvedSessionIds: SessionId[];
|
|
152
|
+
record: import("./types.ts").AfterScheduleRecord | import("./types.ts").AtScheduleRecord | import("./types.ts").EveryScheduleRecord | import("./types.ts").DailyScheduleRecord | import("./types.ts").WeeklyScheduleRecord | import("./types.ts").CronScheduleRecord | import("./types.ts").RRuleScheduleRecord;
|
|
153
|
+
createdAt?: string | undefined;
|
|
154
|
+
updatedAt?: string | undefined;
|
|
155
|
+
pendingDelivery?: {
|
|
156
|
+
targetSessionId: SessionId;
|
|
55
157
|
} | undefined;
|
|
56
158
|
}>;
|
|
57
159
|
};
|
package/lib/types/storage.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Durable Host-wide Schedule tasks, independently of Session activation. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
import { SessionId } from '@qilin-agent/session';
|
|
4
6
|
import { MessageId } from '@qilin-agent/llm/brand';
|
|
@@ -13,42 +15,109 @@ const recordSchema = z.unknown().transform((value, context) => {
|
|
|
13
15
|
}
|
|
14
16
|
return z.NEVER;
|
|
15
17
|
});
|
|
18
|
+
/** Canonical millisecond UTC instant with a four-digit year. */
|
|
16
19
|
const instantSchema = z.iso.datetime({ precision: 3 }).refine(value => !value.startsWith('0000-'), {
|
|
17
20
|
message: 'Expected a canonical four-digit-year UTC calendar instant',
|
|
18
21
|
});
|
|
22
|
+
/** Identity of one delivered inbox message: non-empty, without surrounding whitespace. */
|
|
23
|
+
const messageIdSchema = z.string().min(1).refine(value => value.trim() === value).transform(MessageId);
|
|
24
|
+
/** Non-empty saved Session identity. */
|
|
25
|
+
const sessionIdSchema = z.string().min(1).transform(SessionId);
|
|
19
26
|
const deliveryReceiptSchema = z.object({
|
|
20
27
|
scheduledAt: instantSchema,
|
|
21
28
|
deliveredAt: instantSchema,
|
|
22
|
-
messageId:
|
|
29
|
+
messageId: messageIdSchema,
|
|
30
|
+
targetSessionId: sessionIdSchema.optional(),
|
|
23
31
|
}).strict();
|
|
24
32
|
const deliveryHistorySchema = z.object({
|
|
25
|
-
records: z.array(deliveryReceiptSchema.extend({ prompt: z.string().
|
|
33
|
+
records: z.array(deliveryReceiptSchema.extend({ prompt: z.string().exactOptional() }).strict()),
|
|
26
34
|
earlierRecordsUnavailable: z.boolean(),
|
|
27
|
-
earlierRecordsPruned: z.boolean().
|
|
35
|
+
earlierRecordsPruned: z.boolean().default(false),
|
|
28
36
|
}).strict().refine(history => new Set(history.records.map(record => record.messageId)).size === history.records.length, {
|
|
29
37
|
message: 'Delivery history message identities must be unique within a task',
|
|
30
38
|
});
|
|
31
|
-
|
|
39
|
+
const createSpecSchema = z.object({
|
|
40
|
+
cwd: z.string().min(1).exactOptional(),
|
|
41
|
+
agentPreset: z.string().min(1).exactOptional(),
|
|
42
|
+
}).strict();
|
|
43
|
+
const targetSchema = z.discriminatedUnion('kind', [
|
|
44
|
+
z.object({ kind: z.literal('session'), sessionId: sessionIdSchema }).strict(),
|
|
45
|
+
z.object({ kind: z.literal('new_once'), sessionId: sessionIdSchema.optional(), create: createSpecSchema.default({}) }).strict(),
|
|
46
|
+
z.object({ kind: z.literal('new'), create: createSpecSchema.default({}) }).strict(),
|
|
47
|
+
]);
|
|
48
|
+
/** Stable reservation for a task without a saved destination; reads never allocate or persist a Session. */
|
|
49
|
+
function defaultSessionId(id) {
|
|
50
|
+
return SessionId(`session-schedule-${createHash('sha256').update(id).digest('hex')}`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Version-2 task schema accepting version-1 rows. New metadata is optional on disk. An explicit target
|
|
54
|
+
* ignores the former Session binding; a missing target uses that binding or a stable create-once destination.
|
|
55
|
+
* Normal decoding does not write. The next edit or delivery writes this current shape without `sessionId`.
|
|
56
|
+
*/
|
|
32
57
|
export const scheduleTaskSchema = z.object({
|
|
33
|
-
sessionId: z.string().min(1).transform(SessionId),
|
|
34
58
|
record: recordSchema,
|
|
35
|
-
|
|
59
|
+
sessionId: z.unknown().optional(),
|
|
60
|
+
target: targetSchema.optional(),
|
|
61
|
+
involvedSessionIds: z.array(sessionIdSchema).optional(),
|
|
62
|
+
status: z.enum(['active', 'paused', 'ended', 'inactive']).default('active'),
|
|
63
|
+
createdAt: instantSchema.optional(),
|
|
64
|
+
updatedAt: instantSchema.optional(),
|
|
36
65
|
lastDelivery: deliveryReceiptSchema.optional(),
|
|
37
66
|
deliveryHistory: deliveryHistorySchema.optional(),
|
|
38
|
-
}).strict().
|
|
39
|
-
|
|
40
|
-
|
|
67
|
+
pendingDelivery: z.object({ targetSessionId: sessionIdSchema }).strict().optional(),
|
|
68
|
+
}).strict().transform(({ sessionId, target: savedTarget, lastDelivery, deliveryHistory, ...task }, context) => {
|
|
69
|
+
const binding = savedTarget === undefined && sessionId !== undefined ? sessionIdSchema.safeParse(sessionId) : undefined;
|
|
70
|
+
if (binding?.success === false) {
|
|
71
|
+
context.addIssue({ code: 'custom', path: ['sessionId'], message: 'A missing target requires a valid stored Session binding' });
|
|
72
|
+
return z.NEVER;
|
|
73
|
+
}
|
|
74
|
+
const target = savedTarget === undefined
|
|
75
|
+
? binding?.success === true
|
|
76
|
+
? { kind: 'session', sessionId: binding.data }
|
|
77
|
+
: { kind: 'new_once', sessionId: defaultSessionId(task.record.id), create: {} }
|
|
78
|
+
: savedTarget.kind === 'new_once'
|
|
79
|
+
? { ...savedTarget, sessionId: savedTarget.sessionId ?? defaultSessionId(task.record.id) }
|
|
80
|
+
: savedTarget;
|
|
81
|
+
const hasReceiptTarget = savedTarget === undefined
|
|
82
|
+
? binding?.success === true
|
|
83
|
+
: savedTarget.kind !== 'new' && savedTarget.sessionId !== undefined;
|
|
84
|
+
const receipts = [lastDelivery, ...(deliveryHistory?.records ?? [])];
|
|
85
|
+
if (!hasReceiptTarget && receipts.some(value => value !== undefined && value.targetSessionId === undefined)) {
|
|
86
|
+
context.addIssue({ code: 'custom', message: 'A saved delivery without an existing destination must name its target Session' });
|
|
87
|
+
return z.NEVER;
|
|
88
|
+
}
|
|
89
|
+
const receipt = (value) => {
|
|
90
|
+
if (value.targetSessionId !== undefined)
|
|
91
|
+
return { ...value, targetSessionId: value.targetSessionId };
|
|
92
|
+
/* v8 ignore next -- the receipt-destination validation above rejects this combination before normalization. */
|
|
93
|
+
if (target.kind === 'new')
|
|
94
|
+
throw new Error('A delivery to a fresh Session must name its target Session');
|
|
95
|
+
return { ...value, targetSessionId: target.sessionId };
|
|
96
|
+
};
|
|
97
|
+
const last = lastDelivery === undefined ? undefined : receipt(lastDelivery);
|
|
98
|
+
const history = deliveryHistory === undefined
|
|
99
|
+
? { records: last === undefined ? [] : [last], earlierRecordsUnavailable: true, earlierRecordsPruned: false }
|
|
100
|
+
: { ...deliveryHistory, records: deliveryHistory.records.map(value => receipt(value)) };
|
|
101
|
+
return {
|
|
102
|
+
...task,
|
|
103
|
+
target,
|
|
104
|
+
status: task.status === 'inactive' ? 'ended' : task.status,
|
|
105
|
+
involvedSessionIds: task.involvedSessionIds ?? (target.kind === 'new' ? [] : [target.sessionId]),
|
|
106
|
+
...(last === undefined ? {} : { lastDelivery: last }),
|
|
107
|
+
deliveryHistory: history,
|
|
108
|
+
};
|
|
109
|
+
}).refine((task) => {
|
|
41
110
|
const latest = task.deliveryHistory.records.at(-1);
|
|
42
111
|
if (latest === undefined)
|
|
43
112
|
return task.lastDelivery === undefined;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
/** Authoritative
|
|
113
|
+
const { prompt: _prompt, ...receipt } = latest;
|
|
114
|
+
return isDeepStrictEqual(receipt, task.lastDelivery);
|
|
115
|
+
}, {
|
|
116
|
+
message: 'Last delivery must match the latest saved delivery receipt',
|
|
117
|
+
});
|
|
118
|
+
/** Authoritative Host tasks retain their domain, backend routing, and envelope; v2 also reads v1 without startup writes. */
|
|
50
119
|
export const scheduleDomain = defineDomain({
|
|
51
|
-
name: 'schedule', version: 1,
|
|
120
|
+
name: 'schedule', version: 2, compatibleVersions: [1],
|
|
52
121
|
tables: { tasks: domainTable(scheduleTaskSchema) },
|
|
53
122
|
});
|
|
54
123
|
//# sourceMappingURL=storage.js.map
|