@plasm_lang/vercel-agent 0.3.85 → 0.3.87
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 +8 -10
- package/package.json +2 -2
- package/src/archive/adapters.ts +4 -0
- package/src/archive/prod-archive-store.ts +28 -45
- package/src/archive/resolve-backend.ts +8 -26
- package/src/archive/types.ts +1 -0
- package/src/archive/vercel-blob-adapter.ts +5 -0
- package/src/authoring/schedule-manager.ts +36 -22
- package/src/cli/build-manifest.ts +18 -0
- package/src/cli/build.ts +4 -13
- package/src/cli/compile-authored-slots.ts +9 -0
- package/src/cli/init-scaffold.ts +6 -2
- package/src/cli/init.ts +2 -6
- package/src/index.ts +3 -2
- package/src/nitro/agent-summary.ts +7 -7
- package/src/nitro/build-application.ts +43 -0
- package/src/nitro/create-plasm-nitro.ts +16 -2
- package/src/nitro/ensure-workflow-builder-dirs.ts +42 -0
- package/src/nitro/patch-vercel-config.ts +0 -3
- package/src/nitro/prepare-host.ts +5 -6
- package/src/nitro/workflow-directives.ts +1 -0
- package/src/nitro/write-nitro-schedule-tasks.ts +67 -0
- package/src/nitro/write-workflow-dispatch-route.ts +68 -0
- package/src/operator/routes.ts +6 -6
- package/src/project-info.ts +10 -6
- package/src/server/plasm-handler.ts +4 -4
- package/src/state/blob-state-adapter.ts +65 -0
- package/src/state/define-state.ts +10 -2
- package/src/state/fs-state-adapter.ts +1 -1
- package/src/storage/vercel-blob.ts +47 -0
- package/templates/mcp-radar/README.md +19 -16
- package/templates/mcp-radar/agent/channels/mcp-radar.ts +7 -7
- package/templates/mcp-radar/agent/schedules/mcp-radar-scan.ts +3 -5
- package/templates/mcp-radar/lib/proof-store.ts +43 -44
- package/templates/mcp-radar/lib/run-radar.ts +1 -1
- package/templates/mcp-radar/lib/start-mcp-radar.ts +12 -0
- package/templates/mcp-radar/scripts/provision-vercel-storage.mjs +18 -0
- package/templates/mcp-radar/workflows/mcp-radar-scan.ts +26 -0
package/README.md
CHANGED
|
@@ -95,10 +95,10 @@ cd ../plasm-agent && npm install
|
|
|
95
95
|
|
|
96
96
|
## Storage (`defineState` + archives)
|
|
97
97
|
|
|
98
|
-
| Concern | Dev (`fs`) | Prod |
|
|
99
|
-
|
|
100
|
-
| Sessions / symbols | `agent/.plasm/` | `PLASM_STATE_BACKEND=
|
|
101
|
-
| Run/trace archives | local dirs | `
|
|
98
|
+
| Concern | Dev (`fs`) | Prod (Vercel) |
|
|
99
|
+
|---------|------------|----------------|
|
|
100
|
+
| Sessions / symbols | `agent/.plasm/` | Vercel Blob (`PLASM_STATE_BACKEND=blob`, default on Vercel) |
|
|
101
|
+
| Run/trace archives | local dirs | Vercel Blob (`vercel blob create-store` — OIDC, no manual token) |
|
|
102
102
|
|
|
103
103
|
See **Connect / state / archive env** below.
|
|
104
104
|
|
|
@@ -180,7 +180,7 @@ Hot reload watches `catalogs/`, `skills/`, `channels/`, `schedules/`, `hooks/`,
|
|
|
180
180
|
|------|--------|-------|
|
|
181
181
|
| `skills/` | markdown or `defineSkill()` | Progressive index + `read_skill` tool (`experimental.skills: "inline"` for full inject) |
|
|
182
182
|
| `channels/` | `defineChannel()` | HTTP ingress; call stubs, not `plasm_context` |
|
|
183
|
-
| `schedules/` | `defineSchedule()` | Dev: `*/N * * * *` timers; prod:
|
|
183
|
+
| `schedules/` | `defineSchedule()` | Dev: `*/N * * * *` timers; prod: Nitro scheduled tasks + Vercel Cron (zero config) |
|
|
184
184
|
| `hooks/` | `defineHook()` | `agent:start`, `plan:commit`, `run:complete`, … |
|
|
185
185
|
| `subagents/` | `defineAgent()` in `subagents/<name>/agent.ts` | `delegate_subagent` harness tool |
|
|
186
186
|
|
|
@@ -222,11 +222,9 @@ export PLASM_CONNECT_SUBJECT_TYPE=app # or user + PLASM_CONNECT
|
|
|
222
222
|
### Production state + Workflow
|
|
223
223
|
|
|
224
224
|
```bash
|
|
225
|
-
|
|
226
|
-
export
|
|
227
|
-
export
|
|
228
|
-
export KV_REST_API_URL=...
|
|
229
|
-
export KV_REST_API_TOKEN=...
|
|
225
|
+
# On Vercel: Blob + Gateway OIDC automatic after `vercel blob create-store` + `plasm-agent link`
|
|
226
|
+
export PLASM_STATE_BACKEND=blob|postgres|fs|kv # blob default on Vercel
|
|
227
|
+
export PLASM_ARCHIVE_BACKEND=vercel|local
|
|
230
228
|
|
|
231
229
|
# Workflow (agent.ts experimental.workflow.world.type)
|
|
232
230
|
export PLASM_WORKFLOW_WORLD=postgres
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasm_lang/vercel-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.87",
|
|
4
4
|
"description": "Catalog-native TypeScript agent framework (Plasm CGS/CML, Vercel AI SDK, Nitro-oriented)",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@ai-sdk/otel": "^1.0.3",
|
|
65
65
|
"@opentelemetry/api": "^1.9.0",
|
|
66
|
-
"@plasm_lang/engine": "^0.3.
|
|
66
|
+
"@plasm_lang/engine": "^0.3.87",
|
|
67
67
|
"@vercel/blob": "^0.27.3",
|
|
68
68
|
"@vercel/connect": "^0.2.6",
|
|
69
69
|
"@vercel/kv": "^3.0.0",
|
package/src/archive/adapters.ts
CHANGED
|
@@ -9,6 +9,10 @@ export class UnimplementedBlobArchiveAdapter implements BlobArchiveAdapter {
|
|
|
9
9
|
async get(): Promise<Uint8Array | null> {
|
|
10
10
|
throw new Error("BlobArchiveAdapter not configured — set PLASM_RUN_ARTIFACTS_URL in prod");
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
async list(): Promise<string[]> {
|
|
14
|
+
throw new Error("BlobArchiveAdapter not configured — set PLASM_RUN_ARTIFACTS_URL in prod");
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/** Stub KV index adapter for production archive listings. */
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LocalArchiveStore } from "./index.js";
|
|
2
2
|
import type {
|
|
3
3
|
BlobArchiveAdapter,
|
|
4
|
-
KvArchiveIndexAdapter,
|
|
5
4
|
PlanArchiveSnapshot,
|
|
6
5
|
RunSnapshot,
|
|
7
6
|
TraceDetail,
|
|
@@ -13,18 +12,17 @@ function blobKey(kind: "plan" | "run" | "trace", id: string): string {
|
|
|
13
12
|
return `plasm/archives/${kind}/${id}.json`;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
function
|
|
17
|
-
return `plasm
|
|
15
|
+
function archivePrefix(kind: "plan" | "run" | "trace"): string {
|
|
16
|
+
return `plasm/archives/${kind}/`;
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
/** Durable archive store: local FS cache + Blob bodies
|
|
19
|
+
/** Durable archive store: local FS cache + Blob bodies (Eve-aligned, no KV index). */
|
|
21
20
|
export class ProdArchiveStore {
|
|
22
21
|
readonly local: LocalArchiveStore;
|
|
23
22
|
|
|
24
23
|
constructor(
|
|
25
24
|
agentRoot: string,
|
|
26
25
|
private readonly blob: BlobArchiveAdapter,
|
|
27
|
-
private readonly index: KvArchiveIndexAdapter,
|
|
28
26
|
) {
|
|
29
27
|
this.local = LocalArchiveStore.fromAgentRoot(agentRoot);
|
|
30
28
|
}
|
|
@@ -47,6 +45,17 @@ export class ProdArchiveStore {
|
|
|
47
45
|
return JSON.parse(Buffer.from(bytes).toString("utf8")) as T;
|
|
48
46
|
}
|
|
49
47
|
|
|
48
|
+
private async listJson<T>(kind: "plan" | "run" | "trace"): Promise<T[]> {
|
|
49
|
+
const paths = await this.blob.list(archivePrefix(kind));
|
|
50
|
+
const items: T[] = [];
|
|
51
|
+
for (const key of paths) {
|
|
52
|
+
if (!key.endsWith(".json")) continue;
|
|
53
|
+
const item = await this.getJson<T>(key);
|
|
54
|
+
if (item) items.push(item);
|
|
55
|
+
}
|
|
56
|
+
return items;
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
async recordToolEvent(
|
|
51
60
|
tenantId: string,
|
|
52
61
|
traceId: string,
|
|
@@ -59,59 +68,40 @@ export class ProdArchiveStore {
|
|
|
59
68
|
|
|
60
69
|
async finalizeTrace(detail: TraceDetail): Promise<void> {
|
|
61
70
|
await this.local.finalizeTrace(detail);
|
|
62
|
-
|
|
63
|
-
await this.putJson(key, detail);
|
|
64
|
-
await this.index.set(indexKey("trace", detail.summary.trace_id), key);
|
|
71
|
+
await this.putJson(blobKey("trace", detail.summary.trace_id), detail);
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
async writePlanArchive(snapshot: PlanArchiveSnapshot): Promise<void> {
|
|
68
75
|
await this.local.writePlanArchive(snapshot);
|
|
69
|
-
|
|
70
|
-
await this.putJson(key, snapshot);
|
|
71
|
-
await this.index.set(indexKey("plan", snapshot.plan_commit_ref), key);
|
|
76
|
+
await this.putJson(blobKey("plan", snapshot.plan_commit_ref), snapshot);
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
async writeRunSnapshot(snapshot: RunSnapshot): Promise<void> {
|
|
75
80
|
await this.local.writeRunSnapshot(snapshot);
|
|
76
|
-
|
|
77
|
-
await this.putJson(key, snapshot);
|
|
78
|
-
await this.index.set(indexKey("run", snapshot.run_id), key);
|
|
81
|
+
await this.putJson(blobKey("run", snapshot.run_id), snapshot);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
async listTraces(tenantId: string, limit = 50): Promise<TraceSummary[]> {
|
|
82
|
-
const
|
|
83
|
-
const items: TraceSummary[] = [];
|
|
84
|
-
for (const idx of keys) {
|
|
85
|
-
const blobRef = await this.index.get(idx);
|
|
86
|
-
if (!blobRef) continue;
|
|
87
|
-
const detail = await this.getJson<TraceDetail>(blobRef);
|
|
88
|
-
if (detail?.summary) items.push(detail.summary);
|
|
89
|
-
}
|
|
85
|
+
const items = await this.listJson<TraceDetail>("trace");
|
|
90
86
|
if (items.length) {
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const summaries = items
|
|
88
|
+
.map((detail) => detail.summary)
|
|
89
|
+
.filter((summary) => summary.tenant_id === tenantId);
|
|
90
|
+
summaries.sort((a, b) => b.started_at_ms - a.started_at_ms);
|
|
91
|
+
return summaries.slice(0, limit);
|
|
93
92
|
}
|
|
94
93
|
return this.local.listTraces(tenantId, limit);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
async getTrace(tenantId: string, traceId: string): Promise<TraceDetail | null> {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (detail) return detail;
|
|
102
|
-
}
|
|
97
|
+
void tenantId;
|
|
98
|
+
const detail = await this.getJson<TraceDetail>(blobKey("trace", traceId));
|
|
99
|
+
if (detail) return detail;
|
|
103
100
|
return this.local.getTrace(tenantId, traceId);
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
async listPlans(limit = 50): Promise<PlanArchiveSnapshot[]> {
|
|
107
|
-
const
|
|
108
|
-
const items: PlanArchiveSnapshot[] = [];
|
|
109
|
-
for (const idx of keys) {
|
|
110
|
-
const blobRef = await this.index.get(idx);
|
|
111
|
-
if (!blobRef) continue;
|
|
112
|
-
const plan = await this.getJson<PlanArchiveSnapshot>(blobRef);
|
|
113
|
-
if (plan) items.push(plan);
|
|
114
|
-
}
|
|
104
|
+
const items = await this.listJson<PlanArchiveSnapshot>("plan");
|
|
115
105
|
if (items.length) {
|
|
116
106
|
items.sort((a, b) => b.archived_at.localeCompare(a.archived_at));
|
|
117
107
|
return items.slice(0, limit);
|
|
@@ -120,14 +110,7 @@ export class ProdArchiveStore {
|
|
|
120
110
|
}
|
|
121
111
|
|
|
122
112
|
async listRuns(limit = 50): Promise<RunSnapshot[]> {
|
|
123
|
-
const
|
|
124
|
-
const items: RunSnapshot[] = [];
|
|
125
|
-
for (const idx of keys) {
|
|
126
|
-
const blobRef = await this.index.get(idx);
|
|
127
|
-
if (!blobRef) continue;
|
|
128
|
-
const run = await this.getJson<RunSnapshot>(blobRef);
|
|
129
|
-
if (run) items.push(run);
|
|
130
|
-
}
|
|
113
|
+
const items = await this.listJson<RunSnapshot>("run");
|
|
131
114
|
if (items.length) {
|
|
132
115
|
items.sort((a, b) => b.archived_at.localeCompare(a.archived_at));
|
|
133
116
|
return items.slice(0, limit);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AgentWorkflowWorldDefinition } from "../define-agent.js";
|
|
2
|
+
import { isVercelHosted } from "../gateway-model.js";
|
|
3
|
+
import { hasLinkedBlobStore } from "../storage/vercel-blob.js";
|
|
2
4
|
import { LocalArchiveStore } from "./index.js";
|
|
3
|
-
import { PostgresArchiveIndexAdapter } from "./postgres-kv-adapter.js";
|
|
4
5
|
import { ProdArchiveStore } from "./prod-archive-store.js";
|
|
5
6
|
import { VercelBlobArchiveAdapter } from "./vercel-blob-adapter.js";
|
|
6
|
-
import {
|
|
7
|
-
import type { BlobArchiveAdapter, KvArchiveIndexAdapter } from "./types.js";
|
|
7
|
+
import type { BlobArchiveAdapter } from "./types.js";
|
|
8
8
|
|
|
9
9
|
export type ArchiveBackend = "local" | "vercel" | "postgres";
|
|
10
10
|
|
|
@@ -15,46 +15,28 @@ export function resolveArchiveBackend(
|
|
|
15
15
|
if (explicit === "local" || explicit === "vercel" || explicit === "postgres") {
|
|
16
16
|
return explicit;
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
process.env.PLASM_RUN_ARTIFACTS_URL?.trim()
|
|
21
|
-
) {
|
|
22
|
-
if (process.env.KV_REST_API_URL?.trim() || process.env.PLASM_KV_REST_API_URL?.trim()) {
|
|
23
|
-
return "vercel";
|
|
24
|
-
}
|
|
18
|
+
if (isVercelHosted() || hasLinkedBlobStore()) {
|
|
19
|
+
return "vercel";
|
|
25
20
|
}
|
|
26
21
|
if (world?.type === "postgres") return "postgres";
|
|
27
22
|
return "local";
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
function resolveBlobAdapter(): BlobArchiveAdapter | undefined {
|
|
31
|
-
if (
|
|
26
|
+
if (hasLinkedBlobStore() || isVercelHosted()) {
|
|
32
27
|
return new VercelBlobArchiveAdapter();
|
|
33
28
|
}
|
|
34
29
|
return undefined;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
function resolveIndexAdapter(
|
|
38
|
-
backend: ArchiveBackend,
|
|
39
|
-
): KvArchiveIndexAdapter | undefined {
|
|
40
|
-
if (backend === "vercel") {
|
|
41
|
-
return new VercelKvArchiveIndexAdapter();
|
|
42
|
-
}
|
|
43
|
-
if (backend === "postgres") {
|
|
44
|
-
return new PostgresArchiveIndexAdapter();
|
|
45
|
-
}
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
32
|
export function createArchiveStore(
|
|
50
33
|
agentRoot: string,
|
|
51
34
|
options?: { backend?: ArchiveBackend; world?: AgentWorkflowWorldDefinition },
|
|
52
35
|
): LocalArchiveStore | ProdArchiveStore {
|
|
53
36
|
const backend = options?.backend ?? resolveArchiveBackend(options?.world);
|
|
54
37
|
const blob = resolveBlobAdapter();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return new ProdArchiveStore(agentRoot, blob, index);
|
|
38
|
+
if (backend === "vercel" && blob) {
|
|
39
|
+
return new ProdArchiveStore(agentRoot, blob);
|
|
58
40
|
}
|
|
59
41
|
return LocalArchiveStore.fromAgentRoot(agentRoot);
|
|
60
42
|
}
|
package/src/archive/types.ts
CHANGED
|
@@ -65,6 +65,7 @@ export interface ArchivePaths {
|
|
|
65
65
|
export interface BlobArchiveAdapter {
|
|
66
66
|
put(key: string, body: string | Uint8Array): Promise<void>;
|
|
67
67
|
get(key: string): Promise<Uint8Array | null>;
|
|
68
|
+
list(prefix: string): Promise<string[]>;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** Production KV / Postgres index adapter for archive listings. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlobArchiveAdapter } from "./types.js";
|
|
2
|
+
import { blobList } from "../storage/vercel-blob.js";
|
|
2
3
|
|
|
3
4
|
export class VercelBlobArchiveAdapter implements BlobArchiveAdapter {
|
|
4
5
|
async put(key: string, body: string | Uint8Array): Promise<void> {
|
|
@@ -18,4 +19,8 @@ export class VercelBlobArchiveAdapter implements BlobArchiveAdapter {
|
|
|
18
19
|
if (!response.ok) return null;
|
|
19
20
|
return new Uint8Array(await response.arrayBuffer());
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
async list(prefix: string): Promise<string[]> {
|
|
24
|
+
return blobList(prefix);
|
|
25
|
+
}
|
|
21
26
|
}
|
|
@@ -10,28 +10,48 @@ export interface ScheduleHandle {
|
|
|
10
10
|
stop(): void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface ScheduleTaskManifest {
|
|
14
|
+
scheduledTasks: Record<string, string | string[]>;
|
|
15
|
+
tasks: Array<{ name: string; cron: string }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export interface ScheduleCronManifest {
|
|
14
19
|
crons: Array<{ path: string; schedule: string; name: string }>;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
export function
|
|
22
|
+
export function exportScheduleTaskManifest(schedules: LoadedSchedule[]): ScheduleTaskManifest {
|
|
23
|
+
const scheduledTasks: Record<string, string | string[]> = {};
|
|
24
|
+
const tasks: Array<{ name: string; cron: string }> = [];
|
|
25
|
+
|
|
26
|
+
for (const schedule of schedules) {
|
|
27
|
+
const { name, cron } = schedule.definition;
|
|
28
|
+
tasks.push({ name, cron });
|
|
29
|
+
const existing = scheduledTasks[cron];
|
|
30
|
+
if (!existing) {
|
|
31
|
+
scheduledTasks[cron] = name;
|
|
32
|
+
} else if (Array.isArray(existing)) {
|
|
33
|
+
existing.push(name);
|
|
34
|
+
} else {
|
|
35
|
+
scheduledTasks[cron] = [existing, name];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { scheduledTasks, tasks };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @deprecated Use Nitro scheduled tasks in production. */
|
|
43
|
+
export function exportScheduleCronManifest(schedules: LoadedSchedule[]) {
|
|
18
44
|
return {
|
|
19
45
|
crons: schedules.map((schedule) => ({
|
|
20
46
|
name: schedule.definition.name,
|
|
21
|
-
path: `/internal/
|
|
47
|
+
path: `/internal/schedule/${schedule.definition.name}`,
|
|
22
48
|
schedule: schedule.definition.cron,
|
|
23
49
|
})),
|
|
24
50
|
};
|
|
25
51
|
}
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!secret) return process.env.NODE_ENV !== "production";
|
|
30
|
-
const auth = req.headers.authorization ?? "";
|
|
31
|
-
return auth === `Bearer ${secret}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function tryHandleScheduleCronRoute(
|
|
53
|
+
/** Eve-style dev dispatch — manually trigger a schedule by name. */
|
|
54
|
+
export function tryHandleScheduleDevDispatch(
|
|
35
55
|
req: IncomingMessage,
|
|
36
56
|
res: ServerResponse,
|
|
37
57
|
schedules: LoadedSchedule[],
|
|
@@ -39,14 +59,8 @@ export function tryHandleScheduleCronRoute(
|
|
|
39
59
|
): boolean {
|
|
40
60
|
const method = (req.method ?? "GET").toUpperCase();
|
|
41
61
|
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
42
|
-
const match = pathname.match(/^\/internal\/
|
|
43
|
-
if (method !== "
|
|
44
|
-
|
|
45
|
-
if (!authorizeCronRequest(req)) {
|
|
46
|
-
res.statusCode = 401;
|
|
47
|
-
res.end("Unauthorized");
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
62
|
+
const match = pathname.match(/^\/internal\/schedule\/([^/]+)$/);
|
|
63
|
+
if (method !== "POST" || !match) return false;
|
|
50
64
|
|
|
51
65
|
const name = match[1]!;
|
|
52
66
|
const schedule = schedules.find((s) => s.definition.name === name);
|
|
@@ -61,7 +75,7 @@ export function tryHandleScheduleCronRoute(
|
|
|
61
75
|
.then(() => {
|
|
62
76
|
res.statusCode = 200;
|
|
63
77
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
64
|
-
res.end(JSON.stringify({ ok: true, schedule: name }));
|
|
78
|
+
res.end(JSON.stringify({ ok: true, schedule: name, mode: "dev_dispatch" }));
|
|
65
79
|
})
|
|
66
80
|
.catch((err: unknown) => {
|
|
67
81
|
res.statusCode = 500;
|
|
@@ -79,10 +93,10 @@ export function startScheduleTimers(
|
|
|
79
93
|
): ScheduleHandle {
|
|
80
94
|
const world = resolveWorkflowWorldType(workflow);
|
|
81
95
|
if (world !== "local") {
|
|
82
|
-
const manifest =
|
|
96
|
+
const manifest = exportScheduleTaskManifest(schedules);
|
|
83
97
|
console.log(
|
|
84
|
-
`[plasm:schedule] prod world=${world} —
|
|
85
|
-
JSON.stringify(manifest.
|
|
98
|
+
`[plasm:schedule] prod world=${world} — Nitro scheduled tasks:`,
|
|
99
|
+
JSON.stringify(manifest.scheduledTasks, null, 2),
|
|
86
100
|
);
|
|
87
101
|
return { stop: () => {} };
|
|
88
102
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface PlasmBuildManifest {
|
|
5
|
+
compiledSlots?: Record<string, string>;
|
|
6
|
+
projectRoot?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Read `.plasm/discovery/manifest.json` written by `plasm-agent build`. */
|
|
10
|
+
export async function readBuildManifest(agentRoot: string): Promise<PlasmBuildManifest | null> {
|
|
11
|
+
const manifestPath = path.join(agentRoot, ".plasm", "discovery", "manifest.json");
|
|
12
|
+
try {
|
|
13
|
+
const raw = await readFile(manifestPath, "utf8");
|
|
14
|
+
return JSON.parse(raw) as PlasmBuildManifest;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/cli/build.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { mkdir, writeFile
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { readBuildManifest } from "./build-manifest.js";
|
|
4
5
|
import { compileAuthoredSlots } from "./compile-authored-slots.js";
|
|
5
6
|
import type { ResolvedAgentProject } from "./project-root.js";
|
|
6
7
|
import { isNativeEngineAvailable } from "../engine/napi-binding.js";
|
|
@@ -79,15 +80,5 @@ export async function runPlasmBuild(project: ResolvedAgentProject): Promise<Plas
|
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
projectRoot?: string;
|
|
85
|
-
} | null> {
|
|
86
|
-
const manifestPath = path.join(agentRoot, ".plasm", "discovery", "manifest.json");
|
|
87
|
-
try {
|
|
88
|
-
const raw = await readFile(manifestPath, "utf8");
|
|
89
|
-
return JSON.parse(raw) as { compiledSlots?: Record<string, string>; projectRoot?: string };
|
|
90
|
-
} catch {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
83
|
+
export { readBuildManifest } from "./build-manifest.js";
|
|
84
|
+
export type { PlasmBuildManifest } from "./build-manifest.js";
|
|
@@ -62,6 +62,15 @@ export async function compileAuthoredSlots(
|
|
|
62
62
|
format: "esm",
|
|
63
63
|
outfile: outFile,
|
|
64
64
|
banner: { js: ESBUILD_BANNER },
|
|
65
|
+
external: [
|
|
66
|
+
"@plasm_lang/vercel-agent",
|
|
67
|
+
"@plasm_lang/vercel-agent/*",
|
|
68
|
+
"@plasm_lang/engine",
|
|
69
|
+
"@vercel/functions",
|
|
70
|
+
"@vercel/blob",
|
|
71
|
+
"workflow",
|
|
72
|
+
"workflow/api",
|
|
73
|
+
],
|
|
65
74
|
logLevel: "silent",
|
|
66
75
|
});
|
|
67
76
|
|
package/src/cli/init-scaffold.ts
CHANGED
|
@@ -114,20 +114,24 @@ export async function patchProjectPackageJson(
|
|
|
114
114
|
...pkg.dependencies,
|
|
115
115
|
"@plasm_lang/vercel-agent": `^${version}`,
|
|
116
116
|
"@plasm_lang/engine": `^${version}`,
|
|
117
|
+
"@ai-sdk/otel": "^1.0.3",
|
|
117
118
|
"@vercel/blob": "^0.27.3",
|
|
118
119
|
"@vercel/functions": "^3.4.3",
|
|
119
|
-
"@vercel/kv": "^3.0.0",
|
|
120
120
|
"@vercel/otel": "^1.5.0",
|
|
121
|
+
ai: "^6.0.0",
|
|
122
|
+
workflow: "^4.5.0",
|
|
121
123
|
};
|
|
122
124
|
} else {
|
|
123
125
|
pkg.dependencies = {
|
|
124
126
|
...pkg.dependencies,
|
|
125
127
|
"@plasm_lang/vercel-agent": `file:${path.resolve(packageRoot)}`,
|
|
126
128
|
"@plasm_lang/engine": `file:${monorepoEngineRoot(packageRoot)}`,
|
|
129
|
+
"@ai-sdk/otel": "^1.0.3",
|
|
127
130
|
"@vercel/blob": "^0.27.3",
|
|
128
131
|
"@vercel/functions": "^3.4.3",
|
|
129
|
-
"@vercel/kv": "^3.0.0",
|
|
130
132
|
"@vercel/otel": "^1.5.0",
|
|
133
|
+
ai: "^6.0.0",
|
|
134
|
+
workflow: "^4.5.0",
|
|
131
135
|
};
|
|
132
136
|
}
|
|
133
137
|
|
package/src/cli/init.ts
CHANGED
|
@@ -94,15 +94,11 @@ const ENV_EXAMPLE = `# Vercel AI Gateway
|
|
|
94
94
|
# Local / self-host: run \`plasm-agent link\` or set AI_GATEWAY_API_KEY.
|
|
95
95
|
AI_GATEWAY_API_KEY=
|
|
96
96
|
|
|
97
|
-
# Vercel Cron
|
|
97
|
+
# Optional: manual cron trigger auth (Vercel Cron uses x-vercel-cron by default)
|
|
98
98
|
CRON_SECRET=
|
|
99
99
|
|
|
100
|
-
#
|
|
101
|
-
KV_REST_API_URL=
|
|
102
|
-
KV_REST_API_TOKEN=
|
|
100
|
+
# Optional local overrides (Vercel Blob OIDC is automatic when a store is linked)
|
|
103
101
|
BLOB_READ_WRITE_TOKEN=
|
|
104
|
-
PLASM_STATE_BACKEND=kv
|
|
105
|
-
PLASM_ARCHIVE_BACKEND=vercel
|
|
106
102
|
|
|
107
103
|
# Optional overrides
|
|
108
104
|
PLASM_AGENT_MODEL=anthropic/claude-sonnet-4.6
|
package/src/index.ts
CHANGED
|
@@ -52,10 +52,11 @@ export type {
|
|
|
52
52
|
export { tryHandleChannelRoute, listChannelRoutes } from "./authoring/channel-dispatch.js";
|
|
53
53
|
export {
|
|
54
54
|
exportScheduleCronManifest,
|
|
55
|
+
exportScheduleTaskManifest,
|
|
55
56
|
startScheduleTimers,
|
|
56
|
-
|
|
57
|
+
tryHandleScheduleDevDispatch,
|
|
57
58
|
} from "./authoring/schedule-manager.js";
|
|
58
|
-
export type { ScheduleCronManifest, ScheduleHandle } from "./authoring/schedule-manager.js";
|
|
59
|
+
export type { ScheduleCronManifest, ScheduleHandle, ScheduleTaskManifest } from "./authoring/schedule-manager.js";
|
|
59
60
|
export {
|
|
60
61
|
createSubagentRegistry,
|
|
61
62
|
loadSubagents,
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import type { LoadedProjectSlots } from "../authoring/slot-loader.js";
|
|
5
5
|
import { listChannelRoutes } from "../authoring/channel-dispatch.js";
|
|
6
|
-
import {
|
|
6
|
+
import { exportScheduleTaskManifest } from "../authoring/schedule-manager.js";
|
|
7
7
|
import type { AgentDefinition } from "../define-agent.js";
|
|
8
8
|
import type { ProjectDiscovery } from "../discovery/project-walker.js";
|
|
9
9
|
import { frameworkPackageVersion } from "../package-version.js";
|
|
@@ -78,7 +78,7 @@ export function buildPlasmAgentSummary(options: {
|
|
|
78
78
|
})),
|
|
79
79
|
);
|
|
80
80
|
|
|
81
|
-
const scheduleManifest =
|
|
81
|
+
const scheduleManifest = exportScheduleTaskManifest(loadedSlots.schedules);
|
|
82
82
|
|
|
83
83
|
const diagnostics = [...loadedSlots.diagnostics, ...discovery.diagnostics];
|
|
84
84
|
|
|
@@ -92,12 +92,12 @@ export function buildPlasmAgentSummary(options: {
|
|
|
92
92
|
modelId: definition.model,
|
|
93
93
|
},
|
|
94
94
|
instructions,
|
|
95
|
-
schedules: scheduleManifest.
|
|
96
|
-
name:
|
|
97
|
-
cron: cron
|
|
95
|
+
schedules: scheduleManifest.tasks.map((task) => ({
|
|
96
|
+
name: task.name,
|
|
97
|
+
cron: task.cron,
|
|
98
98
|
logicalPath: rel(
|
|
99
|
-
loadedSlots.schedules.find((s) => s.definition.name ===
|
|
100
|
-
path.join(agentRoot, "schedules", `${
|
|
99
|
+
loadedSlots.schedules.find((s) => s.definition.name === task.name)?.sourcePath ??
|
|
100
|
+
path.join(agentRoot, "schedules", `${task.name}.ts`),
|
|
101
101
|
),
|
|
102
102
|
})),
|
|
103
103
|
tools: [],
|
|
@@ -11,6 +11,9 @@ import { copyVercelFunctionAssets } from "./copy-vercel-function-assets.js";
|
|
|
11
11
|
import { isVercelBuildEnvironment, vercelOutputDir } from "./paths.js";
|
|
12
12
|
import { preparePlasmHost } from "./prepare-host.js";
|
|
13
13
|
import { writePlasmNitroCatchAllRoute } from "./write-nitro-entry.js";
|
|
14
|
+
import { ensureWorkflowBuilderDirs } from "./ensure-workflow-builder-dirs.js";
|
|
15
|
+
import { writeNitroScheduleTasks } from "./write-nitro-schedule-tasks.js";
|
|
16
|
+
import { writeWorkflowDispatchRoute } from "./write-workflow-dispatch-route.js";
|
|
14
17
|
|
|
15
18
|
export interface PlasmApplicationBuildResult {
|
|
16
19
|
outputDir: string;
|
|
@@ -36,6 +39,26 @@ export async function buildPlasmApplication(options: {
|
|
|
36
39
|
|
|
37
40
|
await writePlasmNitroCatchAllRoute(projectRoot, compiledSlots);
|
|
38
41
|
|
|
42
|
+
const loadedSlotsForTasks = await loadAuthoredSlots({
|
|
43
|
+
discovery,
|
|
44
|
+
agentRoot,
|
|
45
|
+
projectRoot,
|
|
46
|
+
compiledSlots,
|
|
47
|
+
});
|
|
48
|
+
if (loadedSlotsForTasks.schedules.length > 0) {
|
|
49
|
+
await writeNitroScheduleTasks({
|
|
50
|
+
projectRoot,
|
|
51
|
+
agentRoot,
|
|
52
|
+
schedules: loadedSlotsForTasks.schedules,
|
|
53
|
+
compiledSlots,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (host.workflowEnabled) {
|
|
58
|
+
await ensureWorkflowBuilderDirs(projectRoot);
|
|
59
|
+
await writeWorkflowDispatchRoute(projectRoot);
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
const nitro = await createPlasmNitro(host, false);
|
|
40
63
|
try {
|
|
41
64
|
const outputDir = await buildNitroOutput(nitro);
|
|
@@ -86,6 +109,26 @@ export async function startPlasmNitroDev(options: {
|
|
|
86
109
|
const host = await preparePlasmHost(options);
|
|
87
110
|
await writePlasmNitroCatchAllRoute(options.projectRoot, options.compiledSlots);
|
|
88
111
|
|
|
112
|
+
const loadedSlotsForTasks = await loadAuthoredSlots({
|
|
113
|
+
discovery: options.discovery,
|
|
114
|
+
agentRoot: options.agentRoot,
|
|
115
|
+
projectRoot: options.projectRoot,
|
|
116
|
+
compiledSlots: options.compiledSlots,
|
|
117
|
+
});
|
|
118
|
+
if (loadedSlotsForTasks.schedules.length > 0) {
|
|
119
|
+
await writeNitroScheduleTasks({
|
|
120
|
+
projectRoot: options.projectRoot,
|
|
121
|
+
agentRoot: options.agentRoot,
|
|
122
|
+
schedules: loadedSlotsForTasks.schedules,
|
|
123
|
+
compiledSlots: options.compiledSlots,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (host.workflowEnabled) {
|
|
128
|
+
await ensureWorkflowBuilderDirs(options.projectRoot);
|
|
129
|
+
await writeWorkflowDispatchRoute(options.projectRoot);
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
const nitro = await createPlasmNitro(host, true);
|
|
90
133
|
const server = createDevServer(nitro);
|
|
91
134
|
|