@plasm_lang/vercel-agent 0.3.112 → 0.3.114

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.
Files changed (36) hide show
  1. package/package.json +5 -4
  2. package/src/archive/prod-archive-store.ts +3 -2
  3. package/src/instrumentation.ts +8 -13
  4. package/src/load-env.ts +50 -2
  5. package/src/nitro/create-plasm-nitro.ts +5 -0
  6. package/src/nitro/write-workflow-dispatch-route.ts +2 -1
  7. package/src/runtime/agent-runtime.ts +4 -0
  8. package/src/server/plasm-handler.ts +12 -6
  9. package/src/server/resolve-app-options.ts +33 -0
  10. package/src/stubs/plasm-value-emitter.ts +21 -0
  11. package/src/stubs/stub-symbols.ts +34 -0
  12. package/templates/mcp-radar/README.md +11 -6
  13. package/templates/mcp-radar/agent/channels/mcp-radar.ts +21 -14
  14. package/templates/mcp-radar/agent/hooks/proof-audit.ts +16 -2
  15. package/templates/mcp-radar/agent/instructions.md +23 -11
  16. package/templates/mcp-radar/agent/instrumentation.ts +3 -4
  17. package/templates/mcp-radar/agent/skills/proof-publish.md +15 -0
  18. package/templates/mcp-radar/lib/hn-algolia-preflight.ts +42 -0
  19. package/templates/mcp-radar/lib/instructions.md +36 -0
  20. package/templates/mcp-radar/lib/mcp-radar-scan.ts +28 -0
  21. package/templates/mcp-radar/lib/mcp-radar.ts +92 -0
  22. package/templates/mcp-radar/lib/proof-extract.ts +43 -0
  23. package/templates/mcp-radar/lib/proof-store.ts +38 -0
  24. package/templates/mcp-radar/lib/provision-vercel.mjs +98 -0
  25. package/templates/mcp-radar/lib/radar-state.ts +53 -0
  26. package/templates/mcp-radar/lib/run-audit.ts +41 -0
  27. package/templates/mcp-radar/lib/run-radar.ts +37 -139
  28. package/templates/mcp-radar/lib/smoke-channel.ts +42 -0
  29. package/templates/mcp-radar/lib/start-mcp-radar.ts +1 -0
  30. package/templates/mcp-radar/lib/test-hn-algolia-preflight.mjs +27 -0
  31. package/templates/mcp-radar/package.json +21 -3
  32. package/templates/mcp-radar/scripts/provision-vercel.mjs +111 -0
  33. package/templates/mcp-radar/scripts/test-hn-algolia-preflight.mjs +27 -0
  34. package/templates/mcp-radar/scripts/test-proof-extract.mjs +24 -0
  35. package/templates/mcp-radar/workflows/mcp-radar-scan.ts +4 -4
  36. package/templates/scaffold/agent/instrumentation.ts +3 -11
@@ -1,214 +1,112 @@
1
- import {
2
- type AuthoringContext,
3
- } from "@plasm_lang/vercel-agent";
1
+ import { type AuthoringContext } from "@plasm_lang/vercel-agent";
4
2
 
3
+ import { drainRunAudit, resetRunAudit } from "./run-audit.js";
5
4
  import {
6
- addSeenIds,
7
- appendProofRun,
8
5
  gatewayConfigured,
9
6
  loadLastRun,
10
- loadSeenState,
11
7
  saveLastRun,
12
- saveSeenState,
13
8
  tavilyConfigured,
14
- } from "./proof-store.js";
9
+ } from "./radar-state.js";
15
10
 
11
+ /** Stable intent — same string on every radar turn (`plasm_context` session reuse). */
16
12
  export const MCP_RADAR_INTENT =
17
13
  "track MCP innovations from Hacker News and corroborate with Tavily web search";
18
14
 
19
- export const MCP_SEARCH_QUERY = "MCP OR Model Context Protocol";
20
-
21
- export interface HnStoryCandidate {
22
- id: string;
23
- title?: string;
24
- url?: string;
25
- }
26
-
27
15
  export interface RadarRunOptions {
28
16
  force?: boolean;
17
+ reset?: boolean;
29
18
  }
30
19
 
31
20
  export interface RadarRunResult {
32
21
  ok: boolean;
33
22
  skipped: boolean;
34
23
  reason?: string;
35
- candidates: HnStoryCandidate[];
36
- newCandidates: HnStoryCandidate[];
37
24
  agentText?: string;
38
25
  error?: string;
39
26
  }
40
27
 
41
- type HnStubModule = {
42
- item_search?: (
43
- input: { query: string; tags?: string; per_page?: number },
44
- options?: { transport?: unknown },
45
- ) => Promise<Array<{ id?: string | number; title?: string; url?: string }>>;
46
- };
47
-
48
- export async function preflightHnMcpStories(ctx: AuthoringContext): Promise<HnStoryCandidate[]> {
49
- try {
50
- const mod = (await ctx.importStub("hackernews")) as HnStubModule;
51
- if (typeof mod.item_search !== "function") return [];
52
- const rows = await mod.item_search(
53
- { query: MCP_SEARCH_QUERY, tags: "story", per_page: 10 },
54
- );
55
- return rows
56
- .map((row) => ({
57
- id: String(row.id ?? ""),
58
- title: row.title,
59
- url: row.url,
60
- }))
61
- .filter((row) => row.id.length > 0);
62
- } catch (err) {
63
- console.warn("[mcp-radar] preflight HN search failed:", err);
64
- return [];
65
- }
66
- }
67
-
68
- function filterNewCandidates(
69
- candidates: HnStoryCandidate[],
70
- seenIds: Set<string>,
71
- force: boolean,
72
- ): HnStoryCandidate[] {
73
- if (force) return candidates;
74
- return candidates.filter((c) => !seenIds.has(c.id));
75
- }
76
-
77
- function buildAgentGoal(newCandidates: HnStoryCandidate[], tavily: boolean): string {
78
- const lines = newCandidates.map(
79
- (c) => `- id=${c.id} title=${JSON.stringify(c.title ?? "")} url=${JSON.stringify(c.url ?? "")}`,
80
- );
81
- const tavilyNote = tavily
82
- ? "Tavily is configured — corroborate each story with web_search."
83
- : "Tavily is NOT configured — HN-only synthesis; use Confidence: low.";
84
- return [
85
- "Produce MCP proof entries for these NEW Hacker News story candidates:",
86
- ...lines,
28
+ function buildRadarGoal(options: RadarRunOptions): string {
29
+ const lines = [
30
+ "Run one MCP radar scan cycle.",
87
31
  "",
88
- tavilyNote,
32
+ "Use the Plasm tool loop only: `plasm_context` → `plasm` → `plasm_run`.",
33
+ "Follow `agent/instructions.md` and skills `mcp-proof-format`, `proof-publish`.",
34
+ "HN search, Tavily corroboration, and Proof document read/write are Plasm programs in your session — not TypeScript, not stubs, not chat-only markdown.",
89
35
  "",
90
- "Use plasm_context with stable intent for hackernews + tavily, then plan and run programs.",
91
- "Output markdown proof blocks per the mcp-proof-format skill (### headers).",
92
- "Do not skip candidates unless they are clearly unrelated to MCP.",
93
- ].join("\n");
36
+ `Reuse this intent on every plasm_context call: ${JSON.stringify(MCP_RADAR_INTENT)}`,
37
+ ];
38
+ if (options.force === true) {
39
+ lines.push("", "Force: include MCP HN stories even if the Proof doc already mentions them.");
40
+ }
41
+ if (options.reset === true) {
42
+ lines.push("", "Reset: clear the Proof document to `# MCP Innovations Proof Log` before new entries.");
43
+ }
44
+ return lines.join("\n");
94
45
  }
95
46
 
47
+ /** Start the agent loop; all catalog work happens inside Plasm session tools. */
96
48
  export async function runRadar(
97
49
  ctx: AuthoringContext,
98
50
  options: RadarRunOptions = {},
99
51
  ): Promise<RadarRunResult> {
100
- const force = options.force === true;
101
- const seen = await loadSeenState(ctx.agentRoot);
102
- const seenSet = new Set(seen.itemIds);
103
-
104
- const candidates = await preflightHnMcpStories(ctx);
105
- const newCandidates = filterNewCandidates(candidates, seenSet, force);
106
-
107
- if (!newCandidates.length && !force) {
108
- const result: RadarRunResult = {
109
- ok: true,
110
- skipped: true,
111
- reason: candidates.length ? "no_new_stories" : "no_hn_hits",
112
- candidates,
113
- newCandidates: [],
114
- };
115
- await saveSeenState(ctx.agentRoot, {
116
- ...seen,
117
- lastRunAt: new Date().toISOString(),
118
- lastRunStatus: "skipped",
119
- lastNewCount: 0,
120
- });
121
- await saveLastRun(ctx.agentRoot, {
122
- at: new Date().toISOString(),
123
- status: "skipped",
124
- newItems: 0,
125
- message: result.reason,
126
- });
127
- return result;
128
- }
129
-
130
52
  if (!gatewayConfigured()) {
131
- const result: RadarRunResult = {
132
- ok: false,
133
- skipped: true,
134
- reason: "ai_gateway_missing",
135
- candidates,
136
- newCandidates,
137
- error: "AI Gateway is not configured (link project on Vercel or set AI_GATEWAY_API_KEY locally)",
138
- };
53
+ const error =
54
+ "AI Gateway is not configured (link project on Vercel or set AI_GATEWAY_API_KEY locally)";
139
55
  await saveLastRun(ctx.agentRoot, {
140
56
  at: new Date().toISOString(),
141
57
  status: "error",
142
- newItems: 0,
143
- message: result.error,
58
+ message: error,
144
59
  });
145
- return result;
60
+ return { ok: false, skipped: true, reason: "ai_gateway_missing", error };
146
61
  }
147
62
 
148
- const toProcess = newCandidates.length ? newCandidates : candidates;
149
- const goal = buildAgentGoal(toProcess, tavilyConfigured());
150
-
151
63
  try {
64
+ resetRunAudit();
152
65
  const agent = await ctx.getAgent();
153
- const turn = await agent.generate(goal, { resetConversation: false });
66
+ const turn = await agent.generate(buildRadarGoal(options), { resetConversation: false });
154
67
  const runAt = new Date().toISOString();
68
+ const audit = drainRunAudit();
155
69
 
156
- if (turn.text.trim()) {
157
- await appendProofRun(ctx.agentRoot, { runAt, body: turn.text });
158
- }
159
-
160
- const ids = toProcess.map((c) => c.id);
161
- await addSeenIds(ctx.agentRoot, ids);
162
- await saveSeenState(ctx.agentRoot, {
163
- ...(await loadSeenState(ctx.agentRoot)),
164
- lastRunAt: runAt,
165
- lastRunStatus: "ok",
166
- lastNewCount: toProcess.length,
167
- });
168
70
  await saveLastRun(ctx.agentRoot, {
169
71
  at: runAt,
170
72
  status: "ok",
171
- newItems: toProcess.length,
73
+ runIds: audit.runIds,
74
+ logicalSessionRef: audit.logicalSessionRef,
172
75
  });
173
76
 
174
77
  return {
175
78
  ok: true,
176
79
  skipped: false,
177
- candidates,
178
- newCandidates: toProcess,
179
80
  agentText: turn.text,
180
81
  };
181
82
  } catch (err) {
182
83
  const message = String(err);
183
- await saveSeenState(ctx.agentRoot, {
184
- ...(await loadSeenState(ctx.agentRoot)),
185
- lastRunAt: new Date().toISOString(),
186
- lastRunStatus: "error",
187
- });
188
84
  await saveLastRun(ctx.agentRoot, {
189
85
  at: new Date().toISOString(),
190
86
  status: "error",
191
- newItems: 0,
192
87
  message,
193
88
  });
194
89
  return {
195
90
  ok: false,
196
91
  skipped: false,
197
- candidates,
198
- newCandidates: toProcess,
199
92
  error: message,
200
93
  };
201
94
  }
202
95
  }
203
96
 
204
97
  export async function radarStatus(agentRoot: string): Promise<Record<string, unknown>> {
205
- const seen = await loadSeenState(agentRoot);
206
98
  const last = await loadLastRun(agentRoot);
207
99
  return {
208
100
  gateway: gatewayConfigured(),
209
101
  tavily: tavilyConfigured(),
210
- seenCount: seen.itemIds.length,
211
102
  lastRun: last,
212
103
  intent: MCP_RADAR_INTENT,
104
+ observability: {
105
+ operatorRunsUrl: "/operator/runs",
106
+ operatorSessionsUrl: "/operator/sessions",
107
+ operatorArchivesUrl: "/operator/archives",
108
+ runIds: last?.runIds ?? [],
109
+ logicalSessionRef: last?.logicalSessionRef,
110
+ },
213
111
  };
214
112
  }
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Smoke: MCP Radar channel routes (status, proof link, run with gateway check).
4
+ */
5
+ import { createPlasmDevServer } from "@plasm_lang/vercel-agent/dev";
6
+
7
+ import { gatewayConfigured } from "../lib/radar-state.js";
8
+
9
+ async function main(): Promise<void> {
10
+ const handle = await createPlasmDevServer({ port: 0 });
11
+ try {
12
+ const statusRes = await fetch(`${handle.url}/channel/mcp-radar/status`);
13
+ if (!statusRes.ok) throw new Error(`status ${statusRes.status}`);
14
+ const status = (await statusRes.json()) as { gateway?: boolean; intent?: string };
15
+ if (status.intent == null) throw new Error("status missing intent");
16
+
17
+ const proofRes = await fetch(`${handle.url}/channel/mcp-radar/proof`);
18
+ if (proofRes.status === 404) {
19
+ console.log("OK: proof route (not configured locally)");
20
+ } else if (!proofRes.ok) {
21
+ throw new Error(`proof ${proofRes.status}`);
22
+ } else {
23
+ const proof = (await proofRes.json()) as { editor?: string };
24
+ if (proof.editor !== "proof") throw new Error("proof response missing editor=proof");
25
+ }
26
+
27
+ if (gatewayConfigured()) {
28
+ console.log("OK: gateway configured for agent runs");
29
+ } else {
30
+ console.log("SKIP: gateway not configured locally");
31
+ }
32
+
33
+ console.log(`OK: mcp-radar channel smoke (${handle.url})`);
34
+ } finally {
35
+ await handle.close();
36
+ }
37
+ }
38
+
39
+ main().catch((err: unknown) => {
40
+ console.error(err);
41
+ process.exit(1);
42
+ });
@@ -14,5 +14,6 @@ export async function startMcpRadarRun(
14
14
  return start({ workflowId: MCP_RADAR_SCAN_WORKFLOW_ID }, [
15
15
  ctx.agentRoot,
16
16
  options.force === true,
17
+ options.reset === true,
17
18
  ]);
18
19
  }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import assert from "node:assert/strict";
3
+
4
+ import { fetchHnMcpStoriesByDate } from "../lib/hn-algolia-preflight.js";
5
+ import { isMcpRelevant } from "../lib/proof-extract.js";
6
+ import { MCP_SEARCH_QUERY, preflightHnMcpStories } from "../lib/run-radar.js";
7
+
8
+ const rows = await fetchHnMcpStoriesByDate({
9
+ query: MCP_SEARCH_QUERY,
10
+ tags: "story",
11
+ perPage: 10,
12
+ });
13
+ assert.ok(rows.length > 0, "Algolia should return hits for MCP query");
14
+
15
+ const mcpRows = rows.filter((r) => isMcpRelevant(r.title, r.url));
16
+ assert.ok(mcpRows.length > 0, "At least one hit should be MCP-relevant");
17
+ assert.equal(
18
+ mcpRows.some((r) => /hawking|altman|crowdstrike/i.test(r.title ?? "")),
19
+ false,
20
+ "Preflight must not return generic top HN stories",
21
+ );
22
+
23
+ const candidates = await preflightHnMcpStories();
24
+ assert.ok(candidates.length > 0, "preflightHnMcpStories should yield MCP candidates");
25
+ console.log(
26
+ `hn-algolia-preflight: ok (${candidates.length} MCP candidates, first: ${candidates[0]?.title?.slice(0, 60)})`,
27
+ );
@@ -4,11 +4,29 @@
4
4
  "type": "module",
5
5
  "description": "Event-driven MCP innovation radar — Hacker News + Tavily proof log",
6
6
  "scripts": {
7
+ "build": "plasm-agent build",
8
+ "dev": "plasm-agent dev",
9
+ "dev:interactive": "plasm-agent dev --interactive",
10
+ "info": "plasm-agent info",
11
+ "deploy": "vercel deploy",
12
+ "eval": "tsx scripts/run-evals.ts",
7
13
  "smoke:channel": "tsx scripts/smoke-channel.ts",
8
- "eval": "tsx scripts/run-evals.ts"
14
+ "test:proof-extract": "tsx scripts/test-proof-extract.mjs",
15
+ "test:hn-preflight": "tsx scripts/test-hn-algolia-preflight.mjs"
16
+ },
17
+ "dependencies": {
18
+ "@ai-sdk/otel": "1.0.14",
19
+ "@plasm_lang/engine": "0.3.112",
20
+ "@plasm_lang/vercel-agent": "file:../../plasm-oss/packages/plasm-agent",
21
+ "@vercel/blob": "0.27.3",
22
+ "@vercel/functions": "3.7.4",
23
+ "@vercel/otel": "1.14.2",
24
+ "ai": "7.0.14",
25
+ "esbuild": "0.25.12",
26
+ "workflow": "4.5.0",
27
+ "zod": "4.3.6"
9
28
  },
10
- "dependencies": {},
11
29
  "devDependencies": {
12
- "tsx": "^4.19.4"
30
+ "tsx": "4.19.4"
13
31
  }
14
32
  }
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Eve-aligned Vercel bootstrap for mcp-radar:
4
+ * - Sync secrets from monorepo `.env` → Vercel project env
5
+ * - Link Blob store (OIDC at runtime)
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { spawnSync } from "node:child_process";
11
+
12
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
13
+ const projectRoot = path.resolve(scriptDir, "..");
14
+
15
+ function run(command, args, options = {}) {
16
+ const result = spawnSync(command, args, options);
17
+ if (result.status !== 0) {
18
+ process.exit(result.status ?? 1);
19
+ }
20
+ return result;
21
+ }
22
+
23
+ function parseEnvFile(filePath) {
24
+ const vars = new Map();
25
+ if (!existsSync(filePath)) return vars;
26
+ const text = readFileSync(filePath, "utf8");
27
+ for (const line of text.split(/\r?\n/)) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith("#")) continue;
30
+ const withoutExport = trimmed.startsWith("export ")
31
+ ? trimmed.slice("export ".length).trim()
32
+ : trimmed;
33
+ const eq = withoutExport.indexOf("=");
34
+ if (eq <= 0) continue;
35
+ const key = withoutExport.slice(0, eq).trim();
36
+ let value = withoutExport.slice(eq + 1).trim();
37
+ if (
38
+ (value.startsWith('"') && value.endsWith('"')) ||
39
+ (value.startsWith("'") && value.endsWith("'"))
40
+ ) {
41
+ value = value.slice(1, -1);
42
+ }
43
+ vars.set(key, value);
44
+ }
45
+ return vars;
46
+ }
47
+
48
+ function resolveMonorepoEnv() {
49
+ const files = [];
50
+ let dir = projectRoot;
51
+ for (let depth = 0; depth < 8; depth++) {
52
+ const candidate = path.join(dir, ".env");
53
+ if (existsSync(candidate)) files.push(candidate);
54
+ const parent = path.dirname(dir);
55
+ if (parent === dir) break;
56
+ dir = parent;
57
+ }
58
+ const merged = new Map();
59
+ for (const file of files.reverse()) {
60
+ for (const [key, value] of parseEnvFile(file)) {
61
+ if (value.trim()) merged.set(key, value);
62
+ }
63
+ }
64
+ return merged;
65
+ }
66
+
67
+ function envAlreadySet(name) {
68
+ const result = run("vercel", ["env", "ls"], { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] });
69
+ return (result.stdout ?? "").includes(name);
70
+ }
71
+
72
+ function syncEnvVar(name, value) {
73
+ if (!value?.trim()) {
74
+ console.warn(`[provision] skip ${name}: not found in monorepo .env`);
75
+ return;
76
+ }
77
+ if (envAlreadySet(name)) {
78
+ console.log(`[provision] ${name} already set on Vercel — skipping`);
79
+ return;
80
+ }
81
+ for (const target of ["production", "preview"]) {
82
+ console.log(`[provision] adding ${name} → ${target}…`);
83
+ run("vercel", ["env", "add", name, target], {
84
+ input: value,
85
+ stdio: ["pipe", "inherit", "inherit"],
86
+ });
87
+ }
88
+ }
89
+
90
+ const env = resolveMonorepoEnv();
91
+
92
+ console.log("Syncing env from monorepo .env → Vercel project…");
93
+ syncEnvVar("TAVILY_API_TOKEN", env.get("TAVILY_API_TOKEN"));
94
+ syncEnvVar("PLASM_TENANT_SCOPE", env.get("PLASM_TENANT_SCOPE") ?? "mcp-radar");
95
+
96
+ console.log("Linking Vercel Blob store (OIDC, public)…");
97
+ const blobResult = spawnSync(
98
+ "vercel",
99
+ ["blob", "create-store", "mcp-radar-proof", "--access", "public", "--yes"],
100
+ { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] },
101
+ );
102
+ if (blobResult.status !== 0) {
103
+ const err = blobResult.stderr ?? blobResult.stdout ?? "";
104
+ if (err.includes("already exists") || err.includes("(409)")) {
105
+ console.log("[provision] Blob store mcp-radar-proof already linked — skipping");
106
+ } else {
107
+ process.exit(blobResult.status ?? 1);
108
+ }
109
+ }
110
+
111
+ console.log("Done. Redeploy production so env + Blob bindings take effect.");
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import assert from "node:assert/strict";
3
+
4
+ import { fetchHnMcpStoriesByDate } from "../lib/hn-algolia-preflight.js";
5
+ import { isMcpRelevant } from "../lib/proof-extract.js";
6
+ import { MCP_SEARCH_QUERY, preflightHnMcpStories } from "../lib/run-radar.js";
7
+
8
+ const rows = await fetchHnMcpStoriesByDate({
9
+ query: MCP_SEARCH_QUERY,
10
+ tags: "story",
11
+ perPage: 10,
12
+ });
13
+ assert.ok(rows.length > 0, "Algolia should return hits for MCP query");
14
+
15
+ const mcpRows = rows.filter((r) => isMcpRelevant(r.title, r.url));
16
+ assert.ok(mcpRows.length > 0, "At least one hit should be MCP-relevant");
17
+ assert.equal(
18
+ mcpRows.some((r) => /hawking|altman|crowdstrike/i.test(r.title ?? "")),
19
+ false,
20
+ "Preflight must not return generic top HN stories",
21
+ );
22
+
23
+ const candidates = await preflightHnMcpStories();
24
+ assert.ok(candidates.length > 0, "preflightHnMcpStories should yield MCP candidates");
25
+ console.log(
26
+ `hn-algolia-preflight: ok (${candidates.length} MCP candidates, first: ${candidates[0]?.title?.slice(0, 60)})`,
27
+ );
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import assert from "node:assert/strict";
3
+ import { extractProofMarkdown, isMcpRelevant } from "../lib/proof-extract.js";
4
+
5
+ const raw = `
6
+ The session expired after plasm_context. Tavily is unavailable.
7
+
8
+ ### [MCP Server Guide](https://news.ycombinator.com/item?id=123)
9
+ - **HN**: id 123, score 42
10
+ - **Synthesis**: Example MCP tooling.
11
+ - **Confidence**: low
12
+
13
+ Some trailing narration about errors.
14
+ `;
15
+
16
+ const extracted = extractProofMarkdown(raw);
17
+ assert.match(extracted, /^### \[MCP Server Guide\]/);
18
+ assert.doesNotMatch(extracted, /session expired/i);
19
+ assert.doesNotMatch(extracted, /trailing narration/i);
20
+
21
+ assert.equal(isMcpRelevant("New MCP server for agents", undefined), true);
22
+ assert.equal(isMcpRelevant("Stephen Hawking has died", undefined), false);
23
+
24
+ console.log("proof-extract: ok");
@@ -9,17 +9,17 @@ function workflowDispatchUrl(): string {
9
9
  return `http://127.0.0.1:${port}/internal/workflow/dispatch`;
10
10
  }
11
11
 
12
- export async function mcpRadarScanWorkflow(_agentRoot: string, force = false) {
12
+ export async function mcpRadarScanWorkflow(_agentRoot: string, force = false, reset = false) {
13
13
  "use workflow";
14
- return mcpRadarScanStep(force);
14
+ return mcpRadarScanStep(force, reset);
15
15
  }
16
16
 
17
- async function mcpRadarScanStep(force: boolean) {
17
+ async function mcpRadarScanStep(force: boolean, reset: boolean) {
18
18
  "use step";
19
19
  const response = await fetch(workflowDispatchUrl(), {
20
20
  method: "POST",
21
21
  headers: { "content-type": "application/json" },
22
- body: JSON.stringify({ job: "mcp-radar-scan", force }),
22
+ body: JSON.stringify({ job: "mcp-radar-scan", force, reset }),
23
23
  });
24
24
  if (!response.ok) {
25
25
  throw new Error(`workflow dispatch failed: ${response.status} ${await response.text()}`);
@@ -1,18 +1,10 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
1
  import { registerOTel } from "@vercel/otel";
5
2
  import { OpenTelemetry } from "@ai-sdk/otel";
6
- import { registerTelemetryIntegration } from "ai";
7
-
8
- const agentRoot = path.dirname(fileURLToPath(import.meta.url));
9
- const serviceName =
10
- process.env.PLASM_AGENT_NAME?.trim() || path.basename(path.dirname(agentRoot));
3
+ import { registerTelemetry } from "ai";
11
4
 
12
- /** Eve-shaped OTEL bootstrap — optional export to Braintrust/Datadog via registerOTel. */
13
5
  export function register(): void {
14
- registerOTel({ serviceName });
15
- registerTelemetryIntegration(new OpenTelemetry());
6
+ registerOTel({ serviceName: "plasm-agent" });
7
+ registerTelemetry(new OpenTelemetry());
16
8
  }
17
9
 
18
10
  register();