@ixo/oracle-runtime-workers 0.38.0 → 0.40.1
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/package.json +5 -4
- package/src/action-interactions/routes.ts +78 -0
- package/src/action-interactions/store.ts +1038 -0
- package/src/channels/replies.ts +8 -4
- package/src/core/capability-expiry.ts +62 -0
- package/src/core/capability-router.ts +126 -95
- package/src/core/context-budget.ts +116 -4
- package/src/core/domain-context/document.ts +74 -2
- package/src/core/domain-context/index.ts +1 -0
- package/src/core/domain-context/resolver.ts +9 -6
- package/src/core/domain-context/turn-context.ts +62 -13
- package/src/core/domain-context/types.ts +17 -0
- package/src/core/env.ts +21 -2
- package/src/core/index.ts +4 -0
- package/src/core/llm.ts +29 -0
- package/src/core/main-agent-types.ts +45 -4
- package/src/core/main-agent.ts +210 -122
- package/src/core/manifest.ts +109 -24
- package/src/core/mcp-result.ts +56 -0
- package/src/core/meta-tools.ts +258 -110
- package/src/core/middlewares/capability-gate.ts +77 -28
- package/src/core/middlewares/context-guard.ts +154 -21
- package/src/core/middlewares/result-cap.ts +1 -1
- package/src/core/middlewares/summarization.ts +9 -0
- package/src/core/middlewares/tool-marks.ts +11 -2
- package/src/core/middlewares/tool-repetition-guard.ts +61 -1
- package/src/core/middlewares/turn-boundary.ts +22 -12
- package/src/core/operating-guides.ts +126 -0
- package/src/core/plugins/skills/skills-tools.ts +6 -31
- package/src/core/plugins/skills/skills.plugin.ts +4 -3
- package/src/core/plugins/weather/weather.plugin.ts +1 -0
- package/src/core/prompt-composer.ts +188 -111
- package/src/core/read-result-tool.ts +48 -14
- package/src/core/registries.ts +13 -5
- package/src/core/state.ts +4 -3
- package/src/core/sub-agent-fallback.ts +1 -0
- package/src/core/subagent-as-tool.ts +4 -1
- package/src/core/tool-access.ts +3 -32
- package/src/core/wrap-plugin-tool.ts +2 -1
- package/src/do/contracts.ts +45 -1
- package/src/do/transcript.ts +40 -11
- package/src/do/turn-metadata.ts +16 -4
- package/src/do/user-oracle-do.ts +358 -62
- package/src/index.ts +1 -0
- package/src/memory/session-history-indexer.ts +60 -14
- package/src/plugin-api/oracle-plugin.ts +4 -5
- package/src/plugin-api/types.ts +32 -3
- package/src/plugins/action-proposals/action-proposals.plugin.ts +7 -2
- package/src/plugins/agui/agui.plugin.ts +1 -0
- package/src/plugins/compact-schema.ts +80 -0
- package/src/plugins/composio/composio.plugin.ts +1 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +4 -3
- package/src/plugins/editor/editor.plugin.ts +4 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +2 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +1 -0
- package/src/plugins/flows/flows.plugin.ts +37 -6
- package/src/plugins/flows/prompts.ts +1 -1
- package/src/plugins/flows/tools/authoring.ts +5 -7
- package/src/plugins/flows/tools/discovery.ts +1 -4
- package/src/plugins/flows/tools/forms.ts +2 -8
- package/src/plugins/flows/tools/linkage.ts +1 -4
- package/src/plugins/flows/tools/settings.ts +1 -4
- package/src/plugins/flows/translator.ts +16 -5
- package/src/plugins/flows/types.ts +27 -48
- package/src/plugins/hub-actions/hub-actions.plugin.ts +30 -4
- package/src/plugins/index.ts +2 -2
- package/src/plugins/ixo-chain/ixo-chain.plugin.ts +29 -5
- package/src/plugins/ixo-chain/kinds/core.ts +2 -2
- package/src/plugins/ixo-chain/kinds/types.ts +34 -5
- package/src/plugins/ixo-chain/queries.ts +10 -1
- package/src/plugins/ixo-transaction/ixo-transaction.plugin.ts +1 -0
- package/src/plugins/matrix-group-chats/index.ts +1 -0
- package/src/plugins/memory/memory-tools.ts +4 -4
- package/src/plugins/memory/memory.plugin.ts +27 -1
- package/src/plugins/memory/oracle-document-tools.ts +24 -49
- package/src/plugins/pod-creator/pod-creator.plugin.ts +1 -0
- package/src/plugins/portal/portal.plugin.ts +3 -1
- package/src/plugins/sandbox/sandbox-write-blob.ts +3 -15
- package/src/plugins/sandbox/sandbox.plugin.ts +19 -2
- package/src/plugins/tasks/manifest.ts +1 -0
- package/src/plugins/tasks/tasks-tools.ts +34 -39
- package/src/plugins/user-preferences/user-preferences-tool.ts +11 -37
- package/src/plugins/user-preferences/user-preferences.plugin.ts +4 -0
- package/src/plugins/vfs/vfs-tools.ts +6 -6
- package/src/plugins/vfs/vfs.plugin.ts +4 -5
- package/src/plugins/yoma/yoma.plugin.ts +1 -0
- package/src/shell/app.ts +241 -6
- package/src/sqlite/serialization.ts +12 -0
- package/src/sqlite/sqlite-saver.ts +17 -0
- package/src/tasks/schedule-input.ts +5 -22
package/src/shell/app.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionInteractionError,
|
|
3
|
+
CheckClaimDispatchSchema,
|
|
4
|
+
CheckDispatchSchema,
|
|
5
|
+
} from '@ixo/action-interactions';
|
|
6
|
+
import { ZodError } from 'zod';
|
|
7
|
+
import {
|
|
8
|
+
interactionStatus,
|
|
9
|
+
parseInteractionCommand,
|
|
10
|
+
parseInteractionRead,
|
|
11
|
+
} from '../action-interactions/routes';
|
|
1
12
|
import {
|
|
2
13
|
ActionProposalWireSchema,
|
|
3
14
|
TopicReceiptReferencesSchema,
|
|
@@ -246,6 +257,9 @@ export function rateLimitKey(
|
|
|
246
257
|
env: Pick<OracleWorkerEnv, 'ORACLE_DID'>,
|
|
247
258
|
scope:
|
|
248
259
|
| 'user'
|
|
260
|
+
| 'card-read'
|
|
261
|
+
| 'card-write'
|
|
262
|
+
| 'card-dispatch'
|
|
249
263
|
| 'poll'
|
|
250
264
|
| 'channel'
|
|
251
265
|
| 'channel-control'
|
|
@@ -422,6 +436,32 @@ export function createShell(
|
|
|
422
436
|
status: 'ok',
|
|
423
437
|
timestamp: new Date().toISOString(),
|
|
424
438
|
frontendTools: FRONTEND_BRIDGE,
|
|
439
|
+
...(c.env.ACTION_INTERACTIONS_ENABLED === 'true'
|
|
440
|
+
? {
|
|
441
|
+
actionInteractions: {
|
|
442
|
+
version: 1,
|
|
443
|
+
owners: [
|
|
444
|
+
'flow-claim',
|
|
445
|
+
'flow-claim-block',
|
|
446
|
+
'flow-bid',
|
|
447
|
+
'owner-reference',
|
|
448
|
+
'approval',
|
|
449
|
+
'task',
|
|
450
|
+
'question',
|
|
451
|
+
'artifact',
|
|
452
|
+
],
|
|
453
|
+
operations: [
|
|
454
|
+
'prepare',
|
|
455
|
+
'read',
|
|
456
|
+
'update',
|
|
457
|
+
'reserve',
|
|
458
|
+
'observe',
|
|
459
|
+
'dispatch',
|
|
460
|
+
'read-task',
|
|
461
|
+
],
|
|
462
|
+
},
|
|
463
|
+
}
|
|
464
|
+
: {}),
|
|
425
465
|
}),
|
|
426
466
|
);
|
|
427
467
|
// A liveness probe: the gateway's in-memory flag, no storage read. The
|
|
@@ -688,15 +728,27 @@ export function createShell(
|
|
|
688
728
|
if (!limiter) return next();
|
|
689
729
|
const auth = c.get('auth') as AuthResult | undefined;
|
|
690
730
|
if (!auth) return next();
|
|
691
|
-
// The chat lists' status poll
|
|
692
|
-
//
|
|
693
|
-
// while chatting must not push turns into 429
|
|
731
|
+
// The chat lists' status poll, the card routes (Portal's review page) and
|
|
732
|
+
// action-card reads, writes and dispatch checks each have their own bucket:
|
|
733
|
+
// polling or typing in a card while chatting must not push turns into 429,
|
|
734
|
+
// and neither chat nor card edits can use up the dispatch check, whose 429
|
|
735
|
+
// the Portal treats as "could not ask" and lets the submission through.
|
|
736
|
+
const isGet = c.req.method === 'GET';
|
|
737
|
+
const path = c.req.path;
|
|
738
|
+
const card = path.startsWith('/action-interactions');
|
|
694
739
|
const scope =
|
|
695
|
-
|
|
740
|
+
isGet && path === '/runs/active'
|
|
696
741
|
? 'poll'
|
|
697
|
-
:
|
|
742
|
+
: path.startsWith('/action-proposals/')
|
|
698
743
|
? 'action'
|
|
699
|
-
:
|
|
744
|
+
: card && isGet
|
|
745
|
+
? 'card-read'
|
|
746
|
+
: path === '/action-interactions/dispatch' ||
|
|
747
|
+
path === '/action-interactions/claim-dispatch'
|
|
748
|
+
? 'card-dispatch'
|
|
749
|
+
: card
|
|
750
|
+
? 'card-write'
|
|
751
|
+
: 'user';
|
|
700
752
|
const { success } = await limiter.limit({
|
|
701
753
|
key: rateLimitKey(c.env, scope, auth.userDid),
|
|
702
754
|
});
|
|
@@ -712,6 +764,189 @@ export function createShell(
|
|
|
712
764
|
return next();
|
|
713
765
|
});
|
|
714
766
|
|
|
767
|
+
// These routes own private proposals, never action execution. No route is a
|
|
768
|
+
// model tool; host reservation requires the authenticated signed invocation.
|
|
769
|
+
app.use('/action-interactions*', async (c, next) => {
|
|
770
|
+
if (
|
|
771
|
+
c.env.ACTION_INTERACTIONS_ENABLED !== 'true' &&
|
|
772
|
+
c.req.path !== '/action-interactions/claim-dispatch' &&
|
|
773
|
+
c.req.path !== '/action-interactions/dispatch'
|
|
774
|
+
)
|
|
775
|
+
return c.json(
|
|
776
|
+
{ code: 'unavailable', message: 'Action interactions are disabled' },
|
|
777
|
+
404,
|
|
778
|
+
);
|
|
779
|
+
if (c.get('auth')?.via !== 'invocation')
|
|
780
|
+
return c.json(
|
|
781
|
+
{
|
|
782
|
+
code: 'invocation_required',
|
|
783
|
+
message: 'A signed invocation is required',
|
|
784
|
+
},
|
|
785
|
+
401,
|
|
786
|
+
);
|
|
787
|
+
return next();
|
|
788
|
+
});
|
|
789
|
+
const interactionBodyLimit = (maxSize: number) =>
|
|
790
|
+
bodyLimit({
|
|
791
|
+
maxSize,
|
|
792
|
+
onError: (c) =>
|
|
793
|
+
c.json({ code: 'too_large', message: 'Request is too large' }, 413),
|
|
794
|
+
});
|
|
795
|
+
const smallInteractionBody = interactionBodyLimit(96 * 1024);
|
|
796
|
+
// Only a prepare can carry a domain-write approval (up to 2 MiB).
|
|
797
|
+
const preparedInteractionBody = interactionBodyLimit(2 * 1024 * 1024);
|
|
798
|
+
app.use('/action-interactions*', (c, next) =>
|
|
799
|
+
c.req.method === 'POST' && c.req.path === '/action-interactions'
|
|
800
|
+
? preparedInteractionBody(c, next)
|
|
801
|
+
: smallInteractionBody(c, next),
|
|
802
|
+
);
|
|
803
|
+
app.get('/action-interactions/tasks/:taskId', async (c) => {
|
|
804
|
+
const sessionId = c.req.query('sessionId');
|
|
805
|
+
if (!sessionId)
|
|
806
|
+
return c.json(
|
|
807
|
+
{ code: 'invalid_request', message: 'sessionId is required' },
|
|
808
|
+
400,
|
|
809
|
+
);
|
|
810
|
+
const identity = identityOf(c.get('auth'), c.req.raw.headers);
|
|
811
|
+
const result = await userStub(
|
|
812
|
+
c.env,
|
|
813
|
+
identity.userDid,
|
|
814
|
+
).readActionInteractionTask(identity, sessionId, c.req.param('taskId'));
|
|
815
|
+
if (result.ok)
|
|
816
|
+
return new Response(result.value, {
|
|
817
|
+
headers: {
|
|
818
|
+
'content-type': 'application/json',
|
|
819
|
+
'cache-control': 'no-store',
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
return c.json({ code: result.code, message: result.message }, 404);
|
|
823
|
+
});
|
|
824
|
+
app.post('/action-interactions/dispatch', async (c) => {
|
|
825
|
+
const parsed = CheckDispatchSchema.safeParse(
|
|
826
|
+
await c.req.json().catch(() => null),
|
|
827
|
+
);
|
|
828
|
+
if (!parsed.success)
|
|
829
|
+
return c.json(
|
|
830
|
+
{ code: 'invalid_request', message: 'Invalid dispatch binding' },
|
|
831
|
+
400,
|
|
832
|
+
);
|
|
833
|
+
const identity = identityOf(c.get('auth'), c.req.raw.headers);
|
|
834
|
+
const result = await userStub(c.env, identity.userDid).checkDispatch(
|
|
835
|
+
identity,
|
|
836
|
+
parsed.data,
|
|
837
|
+
);
|
|
838
|
+
if (result.ok)
|
|
839
|
+
return new Response(result.value, {
|
|
840
|
+
headers: {
|
|
841
|
+
'content-type': 'application/json',
|
|
842
|
+
'cache-control': 'no-store',
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
return c.json(
|
|
846
|
+
{ code: result.code, message: result.message },
|
|
847
|
+
result.status === 403 ? 403 : 409,
|
|
848
|
+
);
|
|
849
|
+
});
|
|
850
|
+
app.post('/action-interactions/claim-dispatch', async (c) => {
|
|
851
|
+
const parsed = CheckClaimDispatchSchema.safeParse(
|
|
852
|
+
await c.req.json().catch(() => null),
|
|
853
|
+
);
|
|
854
|
+
if (!parsed.success)
|
|
855
|
+
return c.json(
|
|
856
|
+
{ code: 'invalid_request', message: 'Invalid claim dispatch binding' },
|
|
857
|
+
400,
|
|
858
|
+
);
|
|
859
|
+
const identity = identityOf(c.get('auth'), c.req.raw.headers);
|
|
860
|
+
const result = await userStub(c.env, identity.userDid).checkClaimDispatch(
|
|
861
|
+
identity,
|
|
862
|
+
parsed.data,
|
|
863
|
+
);
|
|
864
|
+
if (result.ok)
|
|
865
|
+
return new Response(result.value, {
|
|
866
|
+
headers: {
|
|
867
|
+
'content-type': 'application/json',
|
|
868
|
+
'cache-control': 'no-store',
|
|
869
|
+
},
|
|
870
|
+
});
|
|
871
|
+
return c.json(
|
|
872
|
+
{ code: result.code, message: result.message },
|
|
873
|
+
result.status === 403 ? 403 : 409,
|
|
874
|
+
);
|
|
875
|
+
});
|
|
876
|
+
app.on(
|
|
877
|
+
['GET', 'POST', 'PATCH'],
|
|
878
|
+
[
|
|
879
|
+
'/action-interactions',
|
|
880
|
+
'/action-interactions/:id',
|
|
881
|
+
'/action-interactions/:id/:operation',
|
|
882
|
+
],
|
|
883
|
+
async (c) => {
|
|
884
|
+
try {
|
|
885
|
+
// Every request is parsed before a user object is addressed.
|
|
886
|
+
const identity = identityOf(c.get('auth'), c.req.raw.headers);
|
|
887
|
+
if (c.req.method === 'GET') {
|
|
888
|
+
const read = parseInteractionRead(
|
|
889
|
+
c.req.query('sessionId'),
|
|
890
|
+
c.req.param('id'),
|
|
891
|
+
c.req.param('operation'),
|
|
892
|
+
);
|
|
893
|
+
const result = await userStub(
|
|
894
|
+
c.env,
|
|
895
|
+
identity.userDid,
|
|
896
|
+
).readActionInteraction(
|
|
897
|
+
identity,
|
|
898
|
+
read.sessionId,
|
|
899
|
+
read.id,
|
|
900
|
+
c.req.query('view') === 'references',
|
|
901
|
+
);
|
|
902
|
+
if (result.ok)
|
|
903
|
+
return new Response(result.value, {
|
|
904
|
+
headers: {
|
|
905
|
+
'content-type': 'application/json',
|
|
906
|
+
'cache-control': 'no-store',
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
return c.json(
|
|
910
|
+
{ code: result.code, message: result.message },
|
|
911
|
+
interactionStatus(result.status),
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
const command = await parseInteractionCommand(
|
|
915
|
+
c.req.raw,
|
|
916
|
+
c.req.param('id'),
|
|
917
|
+
c.req.param('operation'),
|
|
918
|
+
);
|
|
919
|
+
const result = await userStub(
|
|
920
|
+
c.env,
|
|
921
|
+
identity.userDid,
|
|
922
|
+
).actionInteraction(identity, command);
|
|
923
|
+
if (result.ok)
|
|
924
|
+
return new Response(result.value, {
|
|
925
|
+
headers: {
|
|
926
|
+
'content-type': 'application/json',
|
|
927
|
+
'cache-control': 'no-store',
|
|
928
|
+
},
|
|
929
|
+
});
|
|
930
|
+
return c.json(
|
|
931
|
+
{ code: result.code, message: result.message },
|
|
932
|
+
interactionStatus(result.status),
|
|
933
|
+
);
|
|
934
|
+
} catch (error) {
|
|
935
|
+
if (error instanceof ZodError)
|
|
936
|
+
return c.json(
|
|
937
|
+
{ code: 'invalid_request', message: 'Invalid interaction request' },
|
|
938
|
+
400,
|
|
939
|
+
);
|
|
940
|
+
if (error instanceof ActionInteractionError)
|
|
941
|
+
return c.json(
|
|
942
|
+
{ code: error.code, message: error.message },
|
|
943
|
+
interactionStatus(error.status),
|
|
944
|
+
);
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
|
|
715
950
|
// What the owner object answers a proposal command with: the card; a
|
|
716
951
|
// conflict (the card or the authority it was sealed on changed: the Portal
|
|
717
952
|
// re-reads and retries, 409); or an authority read that failed and may
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* and LangGraph `Send` objects. `stringify` is fast-safe-stringify's
|
|
13
13
|
* circular-safe variant, typed.
|
|
14
14
|
*/
|
|
15
|
+
import {
|
|
16
|
+
TURN_GUIDE_NOTE_KWARG,
|
|
17
|
+
USAGE_CARRIED_KWARG,
|
|
18
|
+
} from '../core/operating-guides';
|
|
15
19
|
import { TURN_TIME_NOTE_KWARG } from '../core/turn-time-note';
|
|
16
20
|
import {
|
|
17
21
|
CHANNEL_DELIVERY_NOTICE_KWARG,
|
|
@@ -382,6 +386,14 @@ export function cleanAdditionalKwargs(
|
|
|
382
386
|
const deliveryNotice = additionalKwargs[CHANNEL_DELIVERY_NOTICE_KWARG];
|
|
383
387
|
if (typeof deliveryNotice === 'string')
|
|
384
388
|
cleaned[CHANNEL_DELIVERY_NOTICE_KWARG] = deliveryNotice;
|
|
389
|
+
const guideNote = additionalKwargs[TURN_GUIDE_NOTE_KWARG];
|
|
390
|
+
if (typeof guideNote === 'string') cleaned[TURN_GUIDE_NOTE_KWARG] = guideNote;
|
|
391
|
+
// Kept for the context guard and the summary (core/operating-guides.ts).
|
|
392
|
+
const usage = additionalKwargs[USAGE_CARRIED_KWARG];
|
|
393
|
+
if (Array.isArray(usage))
|
|
394
|
+
cleaned[USAGE_CARRIED_KWARG] = usage.filter(
|
|
395
|
+
(name): name is string => typeof name === 'string',
|
|
396
|
+
);
|
|
385
397
|
// Channel provenance, read back typed by the transcript (ChannelOrigin,
|
|
386
398
|
// ChannelReplyReference); anything malformed is dropped there.
|
|
387
399
|
for (const key of ['org.ixo.qi.origin', 'org.ixo.qi.reply'] as const)
|
|
@@ -536,6 +536,23 @@ export class SqliteSaver extends BaseCheckpointSaver {
|
|
|
536
536
|
return Promise.all(rows.map((row) => this.loadMessage(row.message)));
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
+
/**
|
|
540
|
+
* The thread's newest human and AI rows with stored content, newest first.
|
|
541
|
+
* `limit` bounds the rows decoded, so the read stays O(limit) in a long
|
|
542
|
+
* thread, unlike `listThreadMessages`.
|
|
543
|
+
*/
|
|
544
|
+
async listRecentThreadMessages(
|
|
545
|
+
threadId: string,
|
|
546
|
+
limit: number,
|
|
547
|
+
): Promise<BaseMessage[]> {
|
|
548
|
+
await this.setup();
|
|
549
|
+
const rows = await this.db.exec<MessageRow>(
|
|
550
|
+
`SELECT message FROM messages WHERE thread_id = ? AND message_type IN ('human', 'ai') AND message_content <> '' ORDER BY created_at DESC, rowid DESC LIMIT ?`,
|
|
551
|
+
[threadId, limit],
|
|
552
|
+
);
|
|
553
|
+
return Promise.all(rows.map((row) => this.loadMessage(row.message)));
|
|
554
|
+
}
|
|
555
|
+
|
|
539
556
|
/**
|
|
540
557
|
* Where a transcript row sits in the listing order (`created_at`, then
|
|
541
558
|
* `rowid`), or null when the thread has no row with that message id. A
|
|
@@ -14,32 +14,15 @@ import { z } from 'zod';
|
|
|
14
14
|
import type { OracleTaskSchedule } from '../plugin-api/types';
|
|
15
15
|
|
|
16
16
|
const SCHEDULE_DESCRIPTION =
|
|
17
|
-
'
|
|
17
|
+
'kind "once" needs `at`, computed from the current time: ALWAYS for one-time requests such as "in 10 minutes" or "tomorrow at 5pm". kind "cron" needs `cron` and is ONLY for genuinely recurring intents: `*/10 * * * *` means every 10 minutes forever, not once in 10 minutes. kind "interval" needs `everySeconds`. Leave the other kinds\' fields out.';
|
|
18
18
|
|
|
19
19
|
export const ScheduleInputSchema = z
|
|
20
20
|
.object({
|
|
21
21
|
kind: z.enum(['once', 'cron', 'interval']),
|
|
22
|
-
at: z
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.describe('ISO timestamp — kind "once" only.'),
|
|
27
|
-
cron: z
|
|
28
|
-
.string()
|
|
29
|
-
.min(1)
|
|
30
|
-
.optional()
|
|
31
|
-
.describe('Cron expression (5 fields) — kind "cron" only.'),
|
|
32
|
-
timezone: z
|
|
33
|
-
.string()
|
|
34
|
-
.min(1)
|
|
35
|
-
.optional()
|
|
36
|
-
.describe('IANA timezone for the cron expression — kind "cron" only.'),
|
|
37
|
-
everySeconds: z
|
|
38
|
-
.number()
|
|
39
|
-
.int()
|
|
40
|
-
.positive()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('Seconds between runs — kind "interval" only.'),
|
|
22
|
+
at: z.string().min(1).optional().describe('ISO timestamp.'),
|
|
23
|
+
cron: z.string().min(1).optional().describe('5 fields.'),
|
|
24
|
+
timezone: z.string().min(1).optional().describe('IANA, for `cron`.'),
|
|
25
|
+
everySeconds: z.number().int().positive().optional(),
|
|
43
26
|
})
|
|
44
27
|
.describe(SCHEDULE_DESCRIPTION);
|
|
45
28
|
|