@fastagent-sh/fastagent 0.17.1 → 0.19.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/README.md +25 -7
- package/dist/agent.d.ts +12 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +19 -0
- package/dist/atomic-write.js +48 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-service.d.ts +54 -0
- package/dist/channels/agentcore-service.js +111 -0
- package/dist/channels/agentcore.d.ts +12 -2
- package/dist/channels/agentcore.js +6 -4
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +16 -6
- package/dist/channels/control.js +106 -4
- package/dist/{engines/pi/channel.d.ts → channels/discover.d.ts} +2 -2
- package/dist/{engines/pi/channel.js → channels/discover.js} +8 -12
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +1 -1
- package/dist/channels/feishu/context-buffer.js +4 -4
- package/dist/channels/feishu/feishu-api.d.ts +4 -2
- package/dist/channels/feishu/feishu-api.js +1 -1
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +45 -15
- package/dist/channels/feishu/invoke-turn.d.ts +9 -3
- package/dist/channels/feishu/invoke-turn.js +152 -34
- package/dist/channels/feishu/parse.js +6 -0
- package/dist/channels/feishu/preview.d.ts +1 -1
- package/dist/channels/feishu/preview.js +7 -3
- package/dist/channels/feishu/ws-ingress.d.ts +1 -1
- package/dist/channels/github/github.d.ts +1 -1
- package/dist/channels/http.d.ts +11 -7
- package/dist/channels/http.js +16 -111
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +2 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +18 -4
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +23 -8
- package/dist/channels/{preview-kit.d.ts → kit/preview-kit.d.ts} +3 -1
- package/dist/channels/{seen.js → kit/seen.js} +1 -1
- package/dist/channels/kit/state.d.ts +6 -0
- package/dist/channels/{state.js → kit/state.js} +4 -24
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +2 -2
- package/dist/channels/{tasks.js → kit/tasks.js} +1 -1
- package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +7 -0
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +4 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.js → kit/turn-store.js} +1 -1
- package/dist/channels/lark/lark.d.ts +1 -1
- package/dist/channels/serve.d.ts +73 -0
- package/dist/channels/serve.js +243 -0
- package/dist/channels/slack/bot-auth.js +3 -14
- package/dist/channels/slack/context-buffer.d.ts +2 -2
- package/dist/channels/slack/context-buffer.js +2 -2
- package/dist/channels/slack/invoke-turn.d.ts +1 -1
- package/dist/channels/slack/invoke-turn.js +9 -6
- package/dist/channels/slack/parse.js +2 -2
- package/dist/channels/slack/preview.d.ts +1 -1
- package/dist/channels/slack/preview.js +11 -3
- package/dist/channels/slack/slack-api.js +1 -1
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +8 -12
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +2 -2
- package/dist/channels/telegram/context-buffer.js +2 -2
- package/dist/channels/telegram/invoke-turn.d.ts +1 -1
- package/dist/channels/telegram/invoke-turn.js +2 -2
- package/dist/channels/telegram/parse.js +2 -2
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/telegram.d.ts +1 -1
- package/dist/channels/telegram/telegram.js +3 -3
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/deploy.js +13 -5
- package/dist/cli/commands/dev.js +26 -14
- package/dist/cli/commands/fire.js +2 -2
- package/dist/cli/commands/info.js +28 -5
- package/dist/cli/commands/invoke.js +1 -1
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +64 -95
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +18 -57
- package/dist/cli/serve.js +57 -243
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +14 -7
- package/dist/collect.d.ts +30 -4
- package/dist/collect.js +39 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/preflight.d.ts +8 -2
- package/dist/deploy/preflight.js +22 -4
- package/dist/deploy/secrets.d.ts +3 -0
- package/dist/deploy/secrets.js +6 -0
- package/dist/dev-supervisor.js +16 -4
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +314 -0
- package/dist/engines/pi/config.d.ts +4 -4
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +56 -42
- package/dist/engines/pi/create.js +150 -75
- package/dist/engines/pi/definition.d.ts +41 -7
- package/dist/engines/pi/definition.js +115 -11
- package/dist/engines/pi/invoke-session.d.ts +35 -0
- package/dist/engines/pi/invoke-session.js +357 -0
- package/dist/engines/pi/login.js +7 -4
- package/dist/engines/pi/models.d.ts +59 -11
- package/dist/engines/pi/models.js +64 -9
- package/dist/engines/pi/open.d.ts +9 -6
- package/dist/engines/pi/open.js +20 -10
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +19 -0
- package/dist/engines/pi/session-builder.js +88 -90
- package/dist/engines/pi/session-control.d.ts +11 -9
- package/dist/engines/pi/session-control.js +114 -77
- package/dist/engines/pi/session-inheritance.d.ts +44 -0
- package/dist/engines/pi/session-inheritance.js +242 -0
- package/dist/engines/pi/session-settings.d.ts +14 -2
- package/dist/engines/pi/session-settings.js +18 -0
- package/dist/engines/pi/session-store.d.ts +70 -0
- package/dist/engines/pi/session-store.js +313 -0
- package/dist/engines/pi/tool-context.d.ts +18 -8
- package/dist/engines/pi/tool-context.js +23 -1
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +1 -1
- package/dist/engines/pi/{invoke.d.ts → turn-kit.d.ts} +46 -45
- package/dist/engines/pi/turn-kit.js +188 -0
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -1
- package/dist/node.d.ts +16 -0
- package/dist/node.js +20 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +16 -0
- package/dist/pi.d.ts +15 -8
- package/dist/pi.js +7 -6
- package/dist/scaffold/templates/fastagent.config.mjs +2 -0
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +1 -1
- package/dist/service.d.ts +158 -0
- package/dist/service.js +354 -0
- package/dist/session-remote.d.ts +1 -1
- package/dist/session-remote.js +10 -2
- package/dist/session.d.ts +4 -2
- package/dist/session.js +1 -1
- package/package.json +14 -5
- package/dist/channels/state.d.ts +0 -16
- package/dist/engines/pi/harness.d.ts +0 -112
- package/dist/engines/pi/harness.js +0 -153
- package/dist/engines/pi/invoke.js +0 -618
- package/dist/engines/pi/read-image.d.ts +0 -4
- package/dist/engines/pi/read-image.js +0 -62
- package/dist/engines/pi/sessions.d.ts +0 -44
- package/dist/engines/pi/sessions.js +0 -182
- package/dist/host/node.js +0 -52
- /package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +0 -0
- /package/dist/channels/{preview-kit.js → kit/preview-kit.js} +0 -0
- /package/dist/channels/{seen.d.ts → kit/seen.d.ts} +0 -0
- /package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +0 -0
- /package/dist/channels/{text.d.ts → kit/text.d.ts} +0 -0
- /package/dist/channels/{text.js → kit/text.js} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
- /package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +0 -0
|
@@ -4,14 +4,14 @@ import { isAbsolute, join } from "node:path";
|
|
|
4
4
|
import { log } from "../../log.js";
|
|
5
5
|
import { readBodyCapped } from "../body.js";
|
|
6
6
|
import { text } from "../respond.js";
|
|
7
|
-
import { createSeenRing } from "../seen.js";
|
|
8
|
-
import { createThreadParticipants } from "../thread-participants.js";
|
|
9
|
-
import { createTaskTracker } from "../tasks.js";
|
|
10
|
-
import { ensureStateHome
|
|
11
|
-
import { dispatchStop, isStopText } from "../stop-command.js";
|
|
12
|
-
import { codePointPrefix } from "../text.js";
|
|
13
|
-
import { createTurnQueue } from "../turn-queue.js";
|
|
14
|
-
import { createTurnStore } from "../turn-store.js";
|
|
7
|
+
import { createSeenRing } from "../kit/seen.js";
|
|
8
|
+
import { createThreadParticipants } from "../kit/thread-participants.js";
|
|
9
|
+
import { createTaskTracker } from "../kit/tasks.js";
|
|
10
|
+
import { ensureStateHome } from "../kit/state.js";
|
|
11
|
+
import { dispatchStop, isStopText } from "../kit/stop-command.js";
|
|
12
|
+
import { codePointPrefix } from "../kit/text.js";
|
|
13
|
+
import { createTurnQueue } from "../kit/turn-queue.js";
|
|
14
|
+
import { createTurnStore } from "../kit/turn-store.js";
|
|
15
15
|
import { createSlackBotTokenProvider } from "./bot-auth.js";
|
|
16
16
|
import { collectSlackBufferedFiles, createSlackContextBuffer } from "./context-buffer.js";
|
|
17
17
|
import { invokeSlackTurn } from "./invoke-turn.js";
|
|
@@ -141,10 +141,6 @@ export function slackChannel(options) {
|
|
|
141
141
|
* under a custom route is the `routed.session === undefined` condition on the write, not the
|
|
142
142
|
* absence of a route: a route that supplies its own session records nothing here. */
|
|
143
143
|
const threadKey = (teamId, channelId, threadTs) => `slack:${teamId}:${channelId}:${threadTs}`;
|
|
144
|
-
// The participant model replaced the owned-thread index (a cache, so nothing is lost). REMOVE THIS
|
|
145
|
-
// after the release following the participant model ships — by then no live deployment can still
|
|
146
|
-
// be carrying the file. test/migration-deadline.test.ts fails when due.
|
|
147
|
-
removeRetiredStateFile(stateHome, "owned-threads.json", label);
|
|
148
144
|
const welcomed = createWelcomedUsers(join(stateHome, "welcomed.json"), label);
|
|
149
145
|
const buffer = createSlackContextBuffer(join(stateHome, "buffers.json"), label);
|
|
150
146
|
const store = createTurnStore(join(stateHome, "turns.json"), {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Durable set of users who have already received the first-run DM welcome, so it is sent at most once. */
|
|
2
2
|
import { log } from "../../log.js";
|
|
3
|
-
import { loadStateFile, saveStateFile } from "../state.js";
|
|
3
|
+
import { loadStateFile, saveStateFile } from "../kit/state.js";
|
|
4
4
|
function key(teamId, userId) {
|
|
5
5
|
return `${teamId}:${userId}`;
|
|
6
6
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram's half of the shared context buffer (mechanics + consume protocol: ../context-buffer.ts):
|
|
2
|
+
* Telegram's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts):
|
|
3
3
|
* the entry shape, its fold-line rendering, and buffered-attachment selection. Bucketed by Telegram
|
|
4
4
|
* "place" (chat[:thread]), not session: an un-summoned message has no route session, and the flush
|
|
5
5
|
* feeds whatever turn answers that place.
|
|
6
6
|
*/
|
|
7
|
-
import { type ContextBuffer as GenericContextBuffer } from "../context-buffer.ts";
|
|
7
|
+
import { type ContextBuffer as GenericContextBuffer } from "../kit/context-buffer.ts";
|
|
8
8
|
/** One buffered un-summoned message (object identity is the commit key). Besides the sender label and
|
|
9
9
|
* one-line body, it carries what a LATER summon needs to resolve references into the discussion:
|
|
10
10
|
* message ids ("reply to the one Alex answered"), and attachment file_ids so "summarize the file from
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Telegram's half of the shared context buffer (mechanics + consume protocol: ../context-buffer.ts):
|
|
2
|
+
* Telegram's half of the shared context buffer (mechanics + consume protocol: ../kit/context-buffer.ts):
|
|
3
3
|
* the entry shape, its fold-line rendering, and buffered-attachment selection. Bucketed by Telegram
|
|
4
4
|
* "place" (chat[:thread]), not session: an un-summoned message has no route session, and the flush
|
|
5
5
|
* feeds whatever turn answers that place.
|
|
6
6
|
*/
|
|
7
|
-
import { BUFFER_ATTACH_MAX, createContextBuffer as createGenericContextBuffer, } from "../context-buffer.js";
|
|
7
|
+
import { BUFFER_ATTACH_MAX, createContextBuffer as createGenericContextBuffer, } from "../kit/context-buffer.js";
|
|
8
8
|
function bufferLine(e) {
|
|
9
9
|
const meta = [
|
|
10
10
|
e.messageId !== undefined ? `msg ${e.messageId}` : undefined,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* wiring and the per-turn lifecycle.
|
|
7
7
|
*/
|
|
8
8
|
import type { Agent, AgentEvent } from "../../agent.ts";
|
|
9
|
-
import { type BusyRetry } from "../invoke-turn-kit.ts";
|
|
9
|
+
import { type BusyRetry } from "../kit/invoke-turn-kit.ts";
|
|
10
10
|
import type { BufferedRef } from "./context-buffer.ts";
|
|
11
11
|
/** Everything the transport needs to fetch a turn's attachments. */
|
|
12
12
|
export interface TurnTransport {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { log } from "../../log.js";
|
|
2
|
-
import { DEFAULT_BUSY_RETRY, attachedFilesManifest, attributedFileName, missingAttachmentsNote, streamTurnWithBusyRetry, } from "../invoke-turn-kit.js";
|
|
2
|
+
import { DEFAULT_BUSY_RETRY, attachedFilesManifest, attributedFileName, missingAttachmentsNote, streamTurnWithBusyRetry, } from "../kit/invoke-turn-kit.js";
|
|
3
3
|
import { resolveFiles, resolveImages } from "./telegram-api.js";
|
|
4
4
|
/** Appended to the prompt (not the system prompt): the channel owns Telegram-HTML formatting. */
|
|
5
5
|
const HTML_INSTRUCTION = "\n\n(Format your reply in Telegram-supported HTML — <b> <i> <u> <s> <code> <pre> <a href> — not Markdown.)";
|
|
@@ -76,5 +76,5 @@ export async function* invokeTurn(agent, session, text, transport, attachments,
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
const prompt = { text: `${text}${resolved.promptSuffix}${HTML_INSTRUCTION}`, images: resolved.images };
|
|
79
|
-
yield* streamTurnWithBusyRetry(agent, session, prompt, { label: "[telegram]", onCompleted, busyRetry });
|
|
79
|
+
yield* streamTurnWithBusyRetry(agent, { session }, prompt, { label: "[telegram]", onCompleted, busyRetry });
|
|
80
80
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* "decide" and prompt-building half; telegram.ts wires it in and owns the stateful lifecycle. Kept
|
|
6
6
|
* separate so this layer tests as plain functions and reads without the factory's noise.
|
|
7
7
|
*/
|
|
8
|
-
import { BUFFER_LINE_MAX_CHARS } from "../context-buffer.js";
|
|
9
|
-
import { REFERENT_MAX_CODE_POINTS, truncateCodePointPrefix } from "../text.js";
|
|
8
|
+
import { BUFFER_LINE_MAX_CHARS } from "../kit/context-buffer.js";
|
|
9
|
+
import { REFERENT_MAX_CODE_POINTS, truncateCodePointPrefix } from "../kit/text.js";
|
|
10
10
|
/** The actionable message in an update (a fresh message or channel post). Edits (`edited_message` /
|
|
11
11
|
* `edited_channel_post`) are deliberately NOT actionable: answering them re-answers every typo fix (a
|
|
12
12
|
* duplicate reply per edit), so an edited message changes nothing — the standard bot behavior. The
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* message, works in groups and private (unlike sendMessageDraft, which is private/forum-topic only).
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentEvent } from "../../agent.ts";
|
|
9
|
-
import { type ChannelFailure, defaultErrorMessage } from "../preview-kit.ts";
|
|
9
|
+
import { type ChannelFailure, defaultErrorMessage } from "../kit/preview-kit.ts";
|
|
10
10
|
import { type Target } from "./telegram-api.ts";
|
|
11
11
|
/** A terminal failure, as the channel hands it to `onError` — the shared channel shape. */
|
|
12
12
|
export type TelegramFailure = ChannelFailure;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RETRY_NOTICE, THINKING_PLACEHOLDER, applyTurnEvent, composeTurnBody, createPreviewPump, createTurnView, defaultErrorMessage, revealedAnswer, thinkingLine, toolLines, } from "../preview-kit.js";
|
|
1
|
+
import { RETRY_NOTICE, THINKING_PLACEHOLDER, applyTurnEvent, composeTurnBody, createPreviewPump, createTurnView, defaultErrorMessage, revealedAnswer, thinkingLine, toolLines, } from "../kit/preview-kit.js";
|
|
2
2
|
import { log } from "../../log.js";
|
|
3
3
|
import { TELEGRAM_MAX_TEXT, callApi, editMessageText, sendMessage } from "./telegram-api.js";
|
|
4
4
|
export { defaultErrorMessage };
|
|
@@ -120,7 +120,11 @@ export async function streamReply(events, api, botToken, target, formatError, pr
|
|
|
120
120
|
// below regardless.
|
|
121
121
|
finalized = true;
|
|
122
122
|
{
|
|
123
|
-
const msg = formatError({
|
|
123
|
+
const msg = formatError({
|
|
124
|
+
details: e.details,
|
|
125
|
+
retryable: e.retryable,
|
|
126
|
+
...(e.code !== undefined ? { code: e.code } : {}),
|
|
127
|
+
}) ?? "";
|
|
124
128
|
await finalize(api, botToken, target, messageId, msg).catch(() => { });
|
|
125
129
|
}
|
|
126
130
|
throw new Error(`agent failed: ${e.details} (retryable=${e.retryable})`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChannelModule } from "../../
|
|
1
|
+
import type { ChannelModule } from "../../channel.ts";
|
|
2
2
|
import { type TelegramMessage, type TelegramRoute, type TelegramUpdate, defaultTelegramRoute, telegramEnvelope } from "./parse.ts";
|
|
3
3
|
import { type TelegramFailure } from "./preview.ts";
|
|
4
4
|
export { defaultTelegramRoute, telegramEnvelope };
|
|
@@ -31,10 +31,10 @@ import { invokeTurn } from "./invoke-turn.js";
|
|
|
31
31
|
import { collectAttachments, createContextBuffer } from "./context-buffer.js";
|
|
32
32
|
import { attachmentSummary, defaultTelegramRoute, extractFiles, extractImages, fromLabel, messageText, ownFiles, ownImages, pickMessage, telegramEnvelope, } from "./parse.js";
|
|
33
33
|
import { defaultErrorMessage, streamReply } from "./preview.js";
|
|
34
|
-
import { ensureStateHome } from "../state.js";
|
|
35
|
-
import { dispatchStop } from "../stop-command.js";
|
|
34
|
+
import { ensureStateHome } from "../kit/state.js";
|
|
35
|
+
import { dispatchStop } from "../kit/stop-command.js";
|
|
36
36
|
import { callApi, editMessageText, sendMessage } from "./telegram-api.js";
|
|
37
|
-
import { createTurnQueue } from "../turn-queue.js";
|
|
37
|
+
import { createTurnQueue } from "../kit/turn-queue.js";
|
|
38
38
|
import { createTurnStore } from "./turn-store.js";
|
|
39
39
|
// Re-export the public surface authored elsewhere, so `@fastagent-sh/fastagent/telegram` keeps one entry point.
|
|
40
40
|
export { defaultTelegramRoute, telegramEnvelope };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The telegram-shaped turn store: the channel's persisted record (what its runner needs to re-execute a
|
|
3
3
|
* turn), its IO-boundary shape validator, and its arrival ordering, over the generic L1 turn store
|
|
4
|
-
* (../turn-store.ts — the semantics live there: pre-ACK persist, replay on the next start, the poison-
|
|
4
|
+
* (../kit/turn-store.ts — the semantics live there: pre-ACK persist, replay on the next start, the poison-
|
|
5
5
|
* turn execution ceiling, the fail-closed attempt bump).
|
|
6
6
|
*/
|
|
7
|
-
import { type TurnStore } from "../turn-store.ts";
|
|
7
|
+
import { type TurnStore } from "../kit/turn-store.ts";
|
|
8
8
|
/** An accepted turn's persisted intent — the SOURCE for the fields a runner needs to re-execute it
|
|
9
9
|
* (telegram.ts's PendingTurn derives from this, so a new execution field added here propagates and
|
|
10
10
|
* cannot silently drop from the persisted record). Minus the live `previewId` (a restart's queue
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The telegram-shaped turn store: the channel's persisted record (what its runner needs to re-execute a
|
|
3
3
|
* turn), its IO-boundary shape validator, and its arrival ordering, over the generic L1 turn store
|
|
4
|
-
* (../turn-store.ts — the semantics live there: pre-ACK persist, replay on the next start, the poison-
|
|
4
|
+
* (../kit/turn-store.ts — the semantics live there: pre-ACK persist, replay on the next start, the poison-
|
|
5
5
|
* turn execution ceiling, the fail-closed attempt bump).
|
|
6
6
|
*/
|
|
7
|
-
import { createTurnStore as createGenericTurnStore } from "../turn-store.js";
|
|
7
|
+
import { createTurnStore as createGenericTurnStore } from "../kit/turn-store.js";
|
|
8
8
|
/** State files are an IO boundary: valid JSON of the WRONG SHAPE must degrade like a corrupt file
|
|
9
9
|
* (warn + empty), not flow in as trusted data (mirrors context-buffer's isBufferEntry). */
|
|
10
10
|
function isStoredTurn(t) {
|
package/dist/cli/commands/add.js
CHANGED
|
@@ -11,7 +11,7 @@ import { dotEnvPath, loadDotEnv } from "../../env.js";
|
|
|
11
11
|
import { resolveStateRoot } from "../../paths.js";
|
|
12
12
|
import { SECRETS_DIRNAME } from "../../paths.js";
|
|
13
13
|
import { detectRuntime, readPackageJson } from "../../runtime.js";
|
|
14
|
-
import { isUnderDir } from "../../
|
|
14
|
+
import { isUnderDir } from "../../paths.js";
|
|
15
15
|
import { displayPath } from "../../paths.js";
|
|
16
16
|
import { appendChannelDotEnv, appendChannelEnv, assertChannelReady, channelExists, channelSetup, scaffoldChannel, } from "../../scaffold/add-channel.js";
|
|
17
17
|
import { exists } from "../../paths.js";
|
|
@@ -76,7 +76,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
76
76
|
failStartup(new Error(`deploy stopped: ${pre.gate}`));
|
|
77
77
|
for (const m of pre.messages)
|
|
78
78
|
console.error(`[fastagent] ${m.level}: ${m.text}`);
|
|
79
|
-
const { channels, routeChannels, longConnectionChannels, hasTimeTriggers, modelAuth, authPath, container, port, extraSecrets, } = pre;
|
|
79
|
+
const { channels, routeChannels, longConnectionChannels, hasTimeTriggers, modelAuth, modelKeyInDefinition, authPath, container, port, extraSecrets, } = pre;
|
|
80
80
|
const hasDeclaredChannels = routeChannels.length + longConnectionChannels.length > 0;
|
|
81
81
|
// Docker: one app service + loopback port + state volume. `--tunnel` shapes the generated topology
|
|
82
82
|
// with an optional Quick Tunnel service; `--run` alone decides whether Docker receives side effects.
|
|
@@ -126,6 +126,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
126
126
|
port,
|
|
127
127
|
requireTunnel: requestedTunnel,
|
|
128
128
|
modelAuth,
|
|
129
|
+
modelKeyInDefinition,
|
|
129
130
|
authPath,
|
|
130
131
|
channels,
|
|
131
132
|
longConnectionChannels,
|
|
@@ -176,6 +177,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
176
177
|
workspace,
|
|
177
178
|
name: serviceName,
|
|
178
179
|
modelAuth,
|
|
180
|
+
modelKeyInDefinition,
|
|
179
181
|
authPath,
|
|
180
182
|
channels,
|
|
181
183
|
longConnectionChannels,
|
|
@@ -278,6 +280,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
278
280
|
agentPrefix: container.agentPrefix,
|
|
279
281
|
name: acName,
|
|
280
282
|
modelAuth,
|
|
283
|
+
modelKeyInDefinition,
|
|
281
284
|
authPath,
|
|
282
285
|
channels,
|
|
283
286
|
extraSecrets,
|
|
@@ -371,6 +374,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
371
374
|
agentPrefix: container.agentPrefix,
|
|
372
375
|
appName,
|
|
373
376
|
modelAuth,
|
|
377
|
+
modelKeyInDefinition,
|
|
374
378
|
authPath,
|
|
375
379
|
channels,
|
|
376
380
|
longConnectionChannels,
|
|
@@ -468,9 +472,10 @@ function deployEnvironment(agentDir, channels) {
|
|
|
468
472
|
* when present, yields an ephemeral URL that reuses the same webhook announcer as `dev --tunnel`.
|
|
469
473
|
*/
|
|
470
474
|
async function runDeployDocker(params) {
|
|
471
|
-
const { agentDir, workspace, composeFile, port, requireTunnel, modelAuth, authPath, channels, longConnectionChannels, extraSecrets, } = params;
|
|
475
|
+
const { agentDir, workspace, composeFile, port, requireTunnel, modelAuth, modelKeyInDefinition, authPath, channels, longConnectionChannels, extraSecrets, } = params;
|
|
472
476
|
const { secrets, missingSecrets, needsModelCredential } = assembleSecrets({
|
|
473
477
|
modelAuth,
|
|
478
|
+
modelKeyInDefinition,
|
|
474
479
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
475
480
|
channels,
|
|
476
481
|
longConnectionChannels,
|
|
@@ -511,7 +516,7 @@ async function runDeployDocker(params) {
|
|
|
511
516
|
* behind the shared {@link spawnRunner} seam (spawned `fly`, cwd = the workspace so the build context is the whole workspace).
|
|
512
517
|
*/
|
|
513
518
|
async function runDeployFly(params) {
|
|
514
|
-
const { agentDir, workspace, agentPrefix, appName, modelAuth, authPath, channels, longConnectionChannels, flyTomlPath, extraSecrets, } = params;
|
|
519
|
+
const { agentDir, workspace, agentPrefix, appName, modelAuth, modelKeyInDefinition, authPath, channels, longConnectionChannels, flyTomlPath, extraSecrets, } = params;
|
|
515
520
|
const fly = spawnRunner("fly", workspace);
|
|
516
521
|
// Fail fast if flyctl is absent (spawn ENOENT → 127), with the install link — not a confusing auth gate.
|
|
517
522
|
if ((await fly(["version"], { capture: true })).code === 127) {
|
|
@@ -520,6 +525,7 @@ async function runDeployFly(params) {
|
|
|
520
525
|
const region = parseFlyRegion(await readFile(flyTomlPath, "utf8")) ?? "iad";
|
|
521
526
|
const { secrets, missingSecrets, needsModelCredential } = assembleSecrets({
|
|
522
527
|
modelAuth,
|
|
528
|
+
modelKeyInDefinition,
|
|
523
529
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
524
530
|
channels,
|
|
525
531
|
longConnectionChannels,
|
|
@@ -553,9 +559,10 @@ async function runDeployFly(params) {
|
|
|
553
559
|
* file (secret values off argv) is created here — 0600, removed after the run either way.
|
|
554
560
|
*/
|
|
555
561
|
async function runDeployAgentcore(params) {
|
|
556
|
-
const { agentDir, workspace, agentPrefix, name, modelAuth, authPath, channels, extraSecrets, selfSchedule } = params;
|
|
562
|
+
const { agentDir, workspace, agentPrefix, name, modelAuth, modelKeyInDefinition, authPath, channels, extraSecrets, selfSchedule, } = params;
|
|
557
563
|
const { secrets, missingSecrets, needsModelCredential } = assembleSecrets({
|
|
558
564
|
modelAuth,
|
|
565
|
+
modelKeyInDefinition,
|
|
559
566
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
560
567
|
channels,
|
|
561
568
|
extraSecrets,
|
|
@@ -627,7 +634,7 @@ async function runDeployAgentcore(params) {
|
|
|
627
634
|
* webhook) lives in {@link deployRailwayRun}; see there for why Railway differs from Fly.
|
|
628
635
|
*/
|
|
629
636
|
async function runDeployRailway(params) {
|
|
630
|
-
const { agentDir, workspace, name, modelAuth, authPath, channels, longConnectionChannels, extraSecrets, intoLinked, dockerfilePath, } = params;
|
|
637
|
+
const { agentDir, workspace, name, modelAuth, modelKeyInDefinition, authPath, channels, longConnectionChannels, extraSecrets, intoLinked, dockerfilePath, } = params;
|
|
631
638
|
const railway = spawnRunner("railway", workspace);
|
|
632
639
|
// Fail fast if the railway CLI is absent (spawn ENOENT → 127), with the install link.
|
|
633
640
|
if ((await railway(["--version"], { capture: true })).code === 127) {
|
|
@@ -635,6 +642,7 @@ async function runDeployRailway(params) {
|
|
|
635
642
|
}
|
|
636
643
|
const { secrets, missingSecrets, needsModelCredential } = assembleSecrets({
|
|
637
644
|
modelAuth,
|
|
645
|
+
modelKeyInDefinition,
|
|
638
646
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
639
647
|
channels,
|
|
640
648
|
longConnectionChannels,
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { runDevSupervisor } from "../../dev-supervisor.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import { reportFindingsIfChanged,
|
|
9
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "../../engines/pi/report.js";
|
|
10
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
11
|
+
import { CODING_TOOL_NAMES } from "../../engines/pi/create.js";
|
|
10
12
|
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
13
|
+
import { mountAgentService } from "../../service.js";
|
|
11
14
|
import { setLogLevel } from "../../log.js";
|
|
12
15
|
import { logAgentLoop } from "../../observe.js";
|
|
13
16
|
import { installProxyFetch } from "../../proxy.js";
|
|
14
17
|
import { workspaceHint } from "../../paths.js";
|
|
15
18
|
import { bindAddress } from "../../bind.js";
|
|
16
19
|
import { failStartup, placementOrExit } from "../fail.js";
|
|
17
|
-
import { assertTunnelBindable, maybeTunnel,
|
|
20
|
+
import { SHUTDOWN_GRACE_MS, assertTunnelBindable, maybeTunnel, reportServing, serve } from "../serve.js";
|
|
18
21
|
import { parseBind, parsePort, reportAuth, reportLine, resolveFirstRunModel, reportWorkspaceHint } from "../shared.js";
|
|
19
22
|
export async function runDev(dirArg, opts) {
|
|
20
23
|
const dir = resolve(dirArg);
|
|
@@ -56,26 +59,34 @@ async function serveOnce(dir, opts) {
|
|
|
56
59
|
reportWorkspaceHint(workspaceHint(a));
|
|
57
60
|
reportLine("config", a.configPath ?? "(none)");
|
|
58
61
|
reportLine("model", `${a.modelSpec}${a.config.thinkingLevel ? ` (thinking: ${a.config.thinkingLevel})` : ""}`);
|
|
59
|
-
await reportAuth(a.modelSpec, a.authPath);
|
|
62
|
+
await reportAuth(a.agentDir, a.modelSpec, a.authPath);
|
|
60
63
|
reportAgentsSkillsTools(a);
|
|
61
64
|
// Trace each turn's agent loop (tool calls + reply) to the log at debug level — shown in dev, gated
|
|
62
65
|
// out in start (level info), keeping end-user content out of production logs. Wired in both postures.
|
|
63
|
-
const traced = logAgentLoop(a.agent);
|
|
64
|
-
const routed = await routesFor(a.agentDir, traced, a.stateRoot, a.sessionControl).catch(failStartup);
|
|
65
66
|
// `http.host` enters here the way the flag enters `parseBind` — through `bindAddress`, so a
|
|
66
67
|
// configured `localhost` is an ADDRESS by the time anything binds, renders or dials it.
|
|
67
68
|
const configured = a.config.http?.host;
|
|
68
69
|
const host = bindFlag ?? (configured === undefined ? undefined : bindAddress(configured));
|
|
69
70
|
assertTunnelBindable(host, opts.tunnel ?? false, bindFlag ? "flag" : "config");
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
// The SAME assembly an embedder gets from `createAgentService` — channels, control plane,
|
|
72
|
+
// schedules, long connections. `dev` opens the directory itself only because its startup report
|
|
73
|
+
// prints the opened values before anything mounts.
|
|
74
|
+
const service = await mountAgentService(a, {
|
|
75
|
+
// Trace each turn's agent loop (tool calls + reply) to the log at debug level — shown in dev,
|
|
76
|
+
// gated out in start (level info), keeping end-user content out of production logs.
|
|
77
|
+
wrapAgent: logAgentLoop,
|
|
78
|
+
closeTimeoutMs: SHUTDOWN_GRACE_MS,
|
|
79
|
+
control: { tunnel: opts.tunnel ?? false, ...(host !== undefined ? { host } : {}) },
|
|
80
|
+
onChannelClosed: (name, error) => failStartup(new Error(`${name} ${error === undefined ? "closed unexpectedly" : `failed: ${String(error)}`}`)),
|
|
81
|
+
}).catch(failStartup);
|
|
82
|
+
serve(service.handler, { port: portFlag ?? a.config.http?.port ?? 8787, host }, {
|
|
83
|
+
ready: service.ready,
|
|
84
|
+
onListening: (p) => {
|
|
85
|
+
reportServing(service, host, p);
|
|
86
|
+
service.announce(p);
|
|
87
|
+
maybeTunnel(a.agentDir, service.channels.routes, p, opts.tunnel ?? false, a.stateRoot);
|
|
88
|
+
},
|
|
89
|
+
onShutdown: () => service.close(),
|
|
79
90
|
});
|
|
80
91
|
}
|
|
81
92
|
/** The agents/skills/tools/collisions report lines. */
|
|
@@ -84,6 +95,7 @@ function reportAgentsSkillsTools(a) {
|
|
|
84
95
|
if (a.definition.persona)
|
|
85
96
|
reportLine("persona", "persona.md");
|
|
86
97
|
reportLine("skills", a.definition.skills.map((s) => s.name).join(", ") || "(none)");
|
|
98
|
+
reportLine("codingTools", CODING_TOOL_NAMES.join(", "));
|
|
87
99
|
if (a.toolNames.length > 0)
|
|
88
100
|
reportLine("tools", a.toolNames.join(", "));
|
|
89
101
|
if (a.deferredToolNames.length > 0) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { join, resolve } from "node:path";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
9
|
import { displayPath } from "../../paths.js";
|
|
10
|
-
import { reportModuleLoadFailures } from "../../
|
|
10
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
11
11
|
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
12
12
|
import { runInvokeStream } from "../invoke-stream.js";
|
|
13
13
|
import { installProxyFetch } from "../../proxy.js";
|
|
@@ -37,7 +37,7 @@ export async function runFire(name, dirArg, opts) {
|
|
|
37
37
|
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
38
38
|
}).catch(failStartup);
|
|
39
39
|
console.error(`[fastagent] fire: ${name} (${modelSpec})`);
|
|
40
|
-
await reportAuth(modelSpec, authPath);
|
|
40
|
+
await reportAuth(placement.agentDir, modelSpec, authPath);
|
|
41
41
|
const exitCode = await runInvokeStream(agent.invoke({ session: scheduleSession(name) }, { text: schedule.prompt }), (text) => process.stdout.write(text), (line) => console.error(line));
|
|
42
42
|
process.stdout.write("\n");
|
|
43
43
|
process.exit(exitCode);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/** `fastagent info [dir] [--json]`: print what the directory ASSEMBLES into, WITHOUT booting a server. Read-only. */
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadDotEnv } from "../../env.js";
|
|
4
|
-
import { discoverChannelFiles } from "../../
|
|
5
|
-
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModelSpec, resolveSessionsDirOverride, } from "../../engines/pi/config.js";
|
|
4
|
+
import { discoverChannelFiles } from "../../channels/discover.js";
|
|
5
|
+
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModel, resolveModelSpec, resolveSessionsDirOverride, } from "../../engines/pi/config.js";
|
|
6
|
+
import { createPiModelRuntime } from "../../engines/pi/models.js";
|
|
6
7
|
import { resolveStateRoot, workspaceHint } from "../../paths.js";
|
|
7
|
-
import { resolveAgentTools } from "../../engines/pi/create.js";
|
|
8
|
+
import { CODING_TOOL_NAMES, resolveAgentTools } from "../../engines/pi/create.js";
|
|
8
9
|
import { loadAgentDefinition } from "../../engines/pi/definition.js";
|
|
9
|
-
import { reportFindingsIfChanged,
|
|
10
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "../../engines/pi/report.js";
|
|
11
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
10
12
|
import { log } from "../../log.js";
|
|
11
13
|
import { nextRun } from "../../schedule/cron.js";
|
|
12
14
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
@@ -24,7 +26,7 @@ export async function runInfo(dirArg, opts) {
|
|
|
24
26
|
// tool), is isolated the same way everywhere (G2): info, dev, AND start report it and keep going with
|
|
25
27
|
// the tools that loaded. The `error`/`.catch` below only fires for a whole-load fault (an unreadable
|
|
26
28
|
// tools/ dir), not a single bad file.
|
|
27
|
-
const tools = await resolveAgentTools(config, agentDir)
|
|
29
|
+
const tools = await resolveAgentTools(config, agentDir, workspace)
|
|
28
30
|
.then((r) => ({
|
|
29
31
|
names: r.toolNames,
|
|
30
32
|
deferred: r.deferredToolNames,
|
|
@@ -55,13 +57,29 @@ export async function runInfo(dirArg, opts) {
|
|
|
55
57
|
const stateRoot = resolveStateRoot(agentDir);
|
|
56
58
|
const sessionsDir = resolveSessionsDirOverride(opts.sessionsDir) ?? defaultSessionsDir(stateRoot);
|
|
57
59
|
const authPath = resolveAuthPath(agentDir, opts.authPath); // flag > FASTAGENT_AUTH_PATH > default — the one owner
|
|
60
|
+
// RESOLVE the spec, do not just echo it: a spec is only real once its provider/model exist in the
|
|
61
|
+
// agent's own surface (built-ins + its models.json), which is exactly what a custom endpoint changes.
|
|
62
|
+
// Reporting a healthy-looking spec that `dev`/`start` then reject is the failure this pre-empts.
|
|
63
|
+
// Reported as DATA rather than thrown — a broken agent is what `info` is for — and read-only: the
|
|
64
|
+
// runtime reads models.json without creating anything (its catalog cache is written on refresh, and
|
|
65
|
+
// there is none here).
|
|
66
|
+
const modelError = modelSpec
|
|
67
|
+
? await createPiModelRuntime({ agentDir, authPath })
|
|
68
|
+
.then((models) => {
|
|
69
|
+
resolveModel(models, modelSpec);
|
|
70
|
+
return undefined;
|
|
71
|
+
})
|
|
72
|
+
.catch((error) => error.message)
|
|
73
|
+
: undefined;
|
|
58
74
|
if (opts.json) {
|
|
59
75
|
console.log(JSON.stringify({
|
|
60
76
|
agentDir,
|
|
61
77
|
workspace,
|
|
62
78
|
configPath: configPath ?? null,
|
|
63
79
|
model: modelSpec ?? null,
|
|
80
|
+
modelError: modelError ?? null,
|
|
64
81
|
thinkingLevel: config.thinkingLevel ?? null,
|
|
82
|
+
codingTools: [...CODING_TOOL_NAMES],
|
|
65
83
|
context: definition.contextFiles.map((f) => f.path),
|
|
66
84
|
persona: definition.persona !== undefined,
|
|
67
85
|
skills: definition.skills.map((skill) => ({ name: skill.name, description: skill.description })),
|
|
@@ -85,6 +103,8 @@ export async function runInfo(dirArg, opts) {
|
|
|
85
103
|
// One padded label writer: hand-spaced labels drifted out of alignment the moment a longer one
|
|
86
104
|
// (agent/workspace/selfSchedule) joined the report.
|
|
87
105
|
const line = (label, value) => console.log(`${`${label}:`.padEnd(13)} ${value}`);
|
|
106
|
+
/** A continuation under the previous line, aligned to the same column (no label, so no bare colon). */
|
|
107
|
+
const cont = (value) => console.log(`${"".padEnd(13)} ${value}`);
|
|
88
108
|
line("agent", agentDir);
|
|
89
109
|
line("workspace", workspace);
|
|
90
110
|
const hint = workspaceHint({ agentDir, workspace });
|
|
@@ -92,8 +112,11 @@ export async function runInfo(dirArg, opts) {
|
|
|
92
112
|
line("hint", hint);
|
|
93
113
|
line("config", configPath ?? "(none)");
|
|
94
114
|
line("model", modelSpec ?? "(not set — pass --model, set FASTAGENT_MODEL, or config.model)");
|
|
115
|
+
if (modelError)
|
|
116
|
+
cont(`⚠ does not resolve: ${modelError}`);
|
|
95
117
|
if (config.thinkingLevel)
|
|
96
118
|
line("thinking", config.thinkingLevel);
|
|
119
|
+
line("codingTools", CODING_TOOL_NAMES.join(", "));
|
|
97
120
|
line("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
98
121
|
line("persona", definition.persona ? "persona.md" : "(none)");
|
|
99
122
|
line("skills", definition.skills.map((skill) => skill.name).join(", ") || "(none)");
|
|
@@ -20,7 +20,7 @@ export async function runInvoke(message, dirArg, opts) {
|
|
|
20
20
|
// BOTH directories, like dev/start: from the workspace, `placement.workspace` alone equals the dir you
|
|
21
21
|
// typed, so it cannot tell you which agent actually ran.
|
|
22
22
|
console.error(`[fastagent] invoke: ${placement.agentDir} (workspace ${placement.workspace}, ${modelSpec})`);
|
|
23
|
-
await reportAuth(modelSpec, authPath);
|
|
23
|
+
await reportAuth(placement.agentDir, modelSpec, authPath);
|
|
24
24
|
// Fresh session per invoke (one-shot, no resume). runInvokeStream maps events→IO: reply→stdout,
|
|
25
25
|
// tool/failure→stderr, exit 1 iff the turn failed (so CI can gate on it).
|
|
26
26
|
const exitCode = await runInvokeStream(agent.invoke({ session: randomUUID() }, { text: message }), (text) => process.stdout.write(text), (line) => console.error(line));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { loadDotEnv } from "../../env.js";
|
|
8
8
|
import { resolveStateRoot } from "../../paths.js";
|
|
9
|
-
import { reportModuleLoadFailures } from "../../
|
|
9
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
10
10
|
import { readRuns } from "../../schedule/audit.js";
|
|
11
11
|
import { nextRun } from "../../schedule/cron.js";
|
|
12
12
|
import { loadSchedules } from "../../schedule/discover.js";
|