@omg-dev/server 0.4.28 → 0.4.30
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/dist/index.mjs +29 -13
- package/package.json +10 -5
- package/src/agent-url.test.ts +36 -0
- package/src/agent-url.ts +11 -0
- package/src/billing.ts +4 -3
- package/src/storage.ts +4 -5
- package/src/test/build-mode-cron.test.ts +80 -0
- package/src/triggers.ts +23 -5
- package/src/workflows.ts +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1363,6 +1363,18 @@ function buildAutoCrudRoutes(schema) {
|
|
|
1363
1363
|
return routes;
|
|
1364
1364
|
}
|
|
1365
1365
|
//#endregion
|
|
1366
|
+
//#region src/agent-url.ts
|
|
1367
|
+
const DEFAULT_AGENT_URL = "http://localhost:8080";
|
|
1368
|
+
/**
|
|
1369
|
+
* Base URL for the in-VM agent.
|
|
1370
|
+
*
|
|
1371
|
+
* Read lazily so local runtimes can inject the emulator URL before loading
|
|
1372
|
+
* user code without coupling the SDK to a particular process bootstrap.
|
|
1373
|
+
*/
|
|
1374
|
+
function agentBase() {
|
|
1375
|
+
return process.env.OMG_AGENT_URL || process.env.VIBES_AGENT_URL || DEFAULT_AGENT_URL;
|
|
1376
|
+
}
|
|
1377
|
+
//#endregion
|
|
1366
1378
|
//#region src/triggers.ts
|
|
1367
1379
|
/**
|
|
1368
1380
|
* Schedule a function to run on a cron expression (UTC, 5-field syntax).
|
|
@@ -1496,9 +1508,10 @@ async function registerTriggers(entries) {
|
|
|
1496
1508
|
storageHookHandlers.get(e.kind).add(e.handler);
|
|
1497
1509
|
}
|
|
1498
1510
|
}
|
|
1499
|
-
const
|
|
1511
|
+
const inBuild = inBuildMode();
|
|
1512
|
+
const inProcessCron = !inBuild && (vibesMode$1() === "dev" || cronDriver() === "in-process");
|
|
1500
1513
|
if (inProcessCron) scheduleAllCronInProcess();
|
|
1501
|
-
console.log(`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode$1()}, cron=${inProcessCron ? "in-process" : "orchestrator"}`);
|
|
1514
|
+
console.log(`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode$1()}, cron=${inBuild ? "unarmed (build)" : inProcessCron ? "in-process" : "orchestrator"}`);
|
|
1502
1515
|
}
|
|
1503
1516
|
/** Snapshot of the registry — used by the in-dev Inspect endpoints. */
|
|
1504
1517
|
function listTriggers() {
|
|
@@ -1660,7 +1673,8 @@ function emitInProcess(topic, payload) {
|
|
|
1660
1673
|
async function emitToOrchestrator(topic, payload) {
|
|
1661
1674
|
const payloadStr = JSON.stringify(payload ?? null);
|
|
1662
1675
|
if (payloadStr.length > MAX_EMIT_BYTES) throw new Error(`emit("${topic}"): payload exceeds ${MAX_EMIT_BYTES} bytes`);
|
|
1663
|
-
const
|
|
1676
|
+
const url = `${agentBase()}/_emit`;
|
|
1677
|
+
const res = await fetch(url, {
|
|
1664
1678
|
method: "POST",
|
|
1665
1679
|
headers: { "Content-Type": "application/json" },
|
|
1666
1680
|
body: JSON.stringify({
|
|
@@ -1772,7 +1786,8 @@ function updateDevDelivery(id, patch) {
|
|
|
1772
1786
|
async function scheduleToOrchestrator(atMs, topic, payload) {
|
|
1773
1787
|
const payloadStr = JSON.stringify(payload ?? null);
|
|
1774
1788
|
if (payloadStr.length > MAX_EMIT_BYTES) throw new Error(`schedule("${topic}"): payload exceeds ${MAX_EMIT_BYTES} bytes`);
|
|
1775
|
-
const
|
|
1789
|
+
const url = `${agentBase()}/_schedule`;
|
|
1790
|
+
const res = await fetch(url, {
|
|
1776
1791
|
method: "POST",
|
|
1777
1792
|
headers: { "Content-Type": "application/json" },
|
|
1778
1793
|
body: JSON.stringify({
|
|
@@ -1793,7 +1808,7 @@ async function scheduleToOrchestrator(atMs, topic, payload) {
|
|
|
1793
1808
|
};
|
|
1794
1809
|
}
|
|
1795
1810
|
async function cancelOnOrchestrator(eventId) {
|
|
1796
|
-
const url =
|
|
1811
|
+
const url = `${agentBase()}/_schedule/${encodeURIComponent(eventId)}`;
|
|
1797
1812
|
const res = await fetch(url, { method: "DELETE" });
|
|
1798
1813
|
if (!res.ok) {
|
|
1799
1814
|
const text = await res.text().catch(() => "");
|
|
@@ -1907,6 +1922,9 @@ function vibesMode$1() {
|
|
|
1907
1922
|
_vibesMode$1 = ((typeof process !== "undefined" ? process.env?.VIBES_MODE : void 0) ?? "") === "dev" ? "dev" : "prod";
|
|
1908
1923
|
return _vibesMode$1;
|
|
1909
1924
|
}
|
|
1925
|
+
function inBuildMode() {
|
|
1926
|
+
return (typeof process !== "undefined" ? process.env?.VIBES_BUILD : void 0) === "1";
|
|
1927
|
+
}
|
|
1910
1928
|
function devInspectTriggers() {
|
|
1911
1929
|
return Array.from(triggerRegistry.values()).map(({ entry }, i) => ({
|
|
1912
1930
|
id: `trg_dev_${i}_${entry.handler}`,
|
|
@@ -2146,7 +2164,7 @@ function handleWorkflowRequest(req) {
|
|
|
2146
2164
|
return restateFetchHandler(req);
|
|
2147
2165
|
}
|
|
2148
2166
|
async function startViaAgent(name, payloadStr, opts) {
|
|
2149
|
-
const res = await fetch(
|
|
2167
|
+
const res = await fetch(`${agentBase()}/_workflow/start`, {
|
|
2150
2168
|
method: "POST",
|
|
2151
2169
|
headers: { "Content-Type": "application/json" },
|
|
2152
2170
|
body: JSON.stringify({
|
|
@@ -2233,9 +2251,8 @@ const storage = {
|
|
|
2233
2251
|
onUpload,
|
|
2234
2252
|
onDelete
|
|
2235
2253
|
};
|
|
2236
|
-
const AGENT_BASE$1 = "http://localhost:8080";
|
|
2237
2254
|
async function prodPresign(action, key, scope, userId, contentType) {
|
|
2238
|
-
const res = await fetch(`${
|
|
2255
|
+
const res = await fetch(`${agentBase()}/_storage/presign`, {
|
|
2239
2256
|
method: "POST",
|
|
2240
2257
|
headers: { "Content-Type": "application/json" },
|
|
2241
2258
|
body: JSON.stringify({
|
|
@@ -2260,7 +2277,7 @@ async function prodPresign(action, key, scope, userId, contentType) {
|
|
|
2260
2277
|
};
|
|
2261
2278
|
}
|
|
2262
2279
|
async function prodList(scope, userId, prefix, limit) {
|
|
2263
|
-
const res = await fetch(`${
|
|
2280
|
+
const res = await fetch(`${agentBase()}/_storage/list`, {
|
|
2264
2281
|
method: "POST",
|
|
2265
2282
|
headers: { "Content-Type": "application/json" },
|
|
2266
2283
|
body: JSON.stringify({
|
|
@@ -2283,7 +2300,7 @@ async function prodList(scope, userId, prefix, limit) {
|
|
|
2283
2300
|
}));
|
|
2284
2301
|
}
|
|
2285
2302
|
async function prodDelete(key, scope, userId) {
|
|
2286
|
-
const res = await fetch(`${
|
|
2303
|
+
const res = await fetch(`${agentBase()}/_storage/delete`, {
|
|
2287
2304
|
method: "POST",
|
|
2288
2305
|
headers: { "Content-Type": "application/json" },
|
|
2289
2306
|
body: JSON.stringify({
|
|
@@ -2875,7 +2892,6 @@ self.addEventListener("notificationclick", (event) => {
|
|
|
2875
2892
|
}
|
|
2876
2893
|
//#endregion
|
|
2877
2894
|
//#region src/billing.ts
|
|
2878
|
-
const AGENT_BASE = "http://localhost:8080";
|
|
2879
2895
|
const MICROS_PER_UNIT = 1e6;
|
|
2880
2896
|
/**
|
|
2881
2897
|
* Dollars (or whole credit units) → integer micro-units, for the amount
|
|
@@ -2886,7 +2902,7 @@ function usd(amount) {
|
|
|
2886
2902
|
return Math.round(amount * MICROS_PER_UNIT);
|
|
2887
2903
|
}
|
|
2888
2904
|
async function agentPost(path, body, label) {
|
|
2889
|
-
const res = await fetch(`${
|
|
2905
|
+
const res = await fetch(`${agentBase()}${path}`, {
|
|
2890
2906
|
method: "POST",
|
|
2891
2907
|
headers: { "Content-Type": "application/json" },
|
|
2892
2908
|
body: JSON.stringify(body)
|
|
@@ -2898,7 +2914,7 @@ async function agentPost(path, body, label) {
|
|
|
2898
2914
|
return await res.json();
|
|
2899
2915
|
}
|
|
2900
2916
|
async function agentGet(path, label) {
|
|
2901
|
-
const res = await fetch(`${
|
|
2917
|
+
const res = await fetch(`${agentBase()}${path}`, {
|
|
2902
2918
|
method: "GET",
|
|
2903
2919
|
headers: { Accept: "application/json" }
|
|
2904
2920
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omg-dev/server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@restatedev/restate-sdk": "1.14.5",
|
|
17
|
-
"@omg-dev/auth": "0.4.
|
|
18
|
-
"@omg-dev/schema": "0.4.
|
|
19
|
-
"@omg-dev/stream": "0.4.
|
|
17
|
+
"@omg-dev/auth": "0.4.30",
|
|
18
|
+
"@omg-dev/schema": "0.4.30",
|
|
19
|
+
"@omg-dev/stream": "0.4.30",
|
|
20
20
|
"web-push": "^3.6.7"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "vp pack src/index.ts src/trigger-scan.ts --no-dts",
|
|
24
|
-
"test": "vp test run"
|
|
24
|
+
"test": "vp test run",
|
|
25
|
+
"typecheck": "tsc6 --noEmit --allowImportingTsExtensions --types bun"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bun": "^1.0.0",
|
|
29
|
+
"typescript": "npm:@typescript/typescript6@6.0.2"
|
|
25
30
|
},
|
|
26
31
|
"license": "MIT",
|
|
27
32
|
"repository": {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
2
|
+
import { agentBase } from "./agent-url.ts"
|
|
3
|
+
|
|
4
|
+
const originalOmgAgentUrl = process.env.OMG_AGENT_URL
|
|
5
|
+
const originalVibesAgentUrl = process.env.VIBES_AGENT_URL
|
|
6
|
+
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
if (originalOmgAgentUrl === undefined) delete process.env.OMG_AGENT_URL
|
|
9
|
+
else process.env.OMG_AGENT_URL = originalOmgAgentUrl
|
|
10
|
+
|
|
11
|
+
if (originalVibesAgentUrl === undefined) delete process.env.VIBES_AGENT_URL
|
|
12
|
+
else process.env.VIBES_AGENT_URL = originalVibesAgentUrl
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe("agentBase", () => {
|
|
16
|
+
it("preserves the in-VM default when no override is set", () => {
|
|
17
|
+
delete process.env.OMG_AGENT_URL
|
|
18
|
+
delete process.env.VIBES_AGENT_URL
|
|
19
|
+
|
|
20
|
+
expect(agentBase()).toBe("http://localhost:8080")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("uses VIBES_AGENT_URL as the compatibility override", () => {
|
|
24
|
+
delete process.env.OMG_AGENT_URL
|
|
25
|
+
process.env.VIBES_AGENT_URL = "http://127.0.0.1:4100"
|
|
26
|
+
|
|
27
|
+
expect(agentBase()).toBe("http://127.0.0.1:4100")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("prefers OMG_AGENT_URL when both overrides are set", () => {
|
|
31
|
+
process.env.OMG_AGENT_URL = "http://127.0.0.1:4200"
|
|
32
|
+
process.env.VIBES_AGENT_URL = "http://127.0.0.1:4100"
|
|
33
|
+
|
|
34
|
+
expect(agentBase()).toBe("http://127.0.0.1:4200")
|
|
35
|
+
})
|
|
36
|
+
})
|
package/src/agent-url.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const DEFAULT_AGENT_URL = "http://localhost:8080"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base URL for the in-VM agent.
|
|
5
|
+
*
|
|
6
|
+
* Read lazily so local runtimes can inject the emulator URL before loading
|
|
7
|
+
* user code without coupling the SDK to a particular process bootstrap.
|
|
8
|
+
*/
|
|
9
|
+
export function agentBase(): string {
|
|
10
|
+
return process.env.OMG_AGENT_URL || process.env.VIBES_AGENT_URL || DEFAULT_AGENT_URL
|
|
11
|
+
}
|
package/src/billing.ts
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
// return Response.json(result)
|
|
35
35
|
// }
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
import { agentBase } from "./agent-url.ts"
|
|
38
|
+
|
|
38
39
|
const MICROS_PER_UNIT = 1_000_000
|
|
39
40
|
|
|
40
41
|
/**
|
|
@@ -140,7 +141,7 @@ export interface GrantOptions {
|
|
|
140
141
|
// out, throw on non-2xx with a truncated body for diagnosability.
|
|
141
142
|
|
|
142
143
|
async function agentPost<T>(path: string, body: unknown, label: string): Promise<T> {
|
|
143
|
-
const res = await fetch(`${
|
|
144
|
+
const res = await fetch(`${agentBase()}${path}`, {
|
|
144
145
|
method: "POST",
|
|
145
146
|
headers: { "Content-Type": "application/json" },
|
|
146
147
|
body: JSON.stringify(body),
|
|
@@ -153,7 +154,7 @@ async function agentPost<T>(path: string, body: unknown, label: string): Promise
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
async function agentGet<T>(path: string, label: string): Promise<T> {
|
|
156
|
-
const res = await fetch(`${
|
|
157
|
+
const res = await fetch(`${agentBase()}${path}`, {
|
|
157
158
|
method: "GET",
|
|
158
159
|
headers: { Accept: "application/json" },
|
|
159
160
|
})
|
package/src/storage.ts
CHANGED
|
@@ -31,6 +31,7 @@ import path from "node:path"
|
|
|
31
31
|
import fs from "node:fs"
|
|
32
32
|
import crypto from "node:crypto"
|
|
33
33
|
import { ctxStore, ctx } from "./ctx.ts"
|
|
34
|
+
import { agentBase } from "./agent-url.ts"
|
|
34
35
|
|
|
35
36
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
36
37
|
|
|
@@ -192,8 +193,6 @@ export const storage = {
|
|
|
192
193
|
|
|
193
194
|
// ── prod-mode: HTTP to the agent ─────────────────────────────────────────────
|
|
194
195
|
|
|
195
|
-
const AGENT_BASE = "http://localhost:8080"
|
|
196
|
-
|
|
197
196
|
async function prodPresign(
|
|
198
197
|
action: "put" | "get",
|
|
199
198
|
key: string,
|
|
@@ -201,7 +200,7 @@ async function prodPresign(
|
|
|
201
200
|
userId: string,
|
|
202
201
|
contentType?: string,
|
|
203
202
|
): Promise<PresignedUploadResult> {
|
|
204
|
-
const res = await fetch(`${
|
|
203
|
+
const res = await fetch(`${agentBase()}/_storage/presign`, {
|
|
205
204
|
method: "POST",
|
|
206
205
|
headers: { "Content-Type": "application/json" },
|
|
207
206
|
body: JSON.stringify({ action, key, scope, userId, contentType }),
|
|
@@ -221,7 +220,7 @@ async function prodPresign(
|
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
async function prodList(scope: StorageScope, userId: string, prefix: string, limit: number): Promise<StorageItem[]> {
|
|
224
|
-
const res = await fetch(`${
|
|
223
|
+
const res = await fetch(`${agentBase()}/_storage/list`, {
|
|
225
224
|
method: "POST",
|
|
226
225
|
headers: { "Content-Type": "application/json" },
|
|
227
226
|
body: JSON.stringify({ scope, userId, prefix, limit }),
|
|
@@ -247,7 +246,7 @@ async function prodList(scope: StorageScope, userId: string, prefix: string, lim
|
|
|
247
246
|
}
|
|
248
247
|
|
|
249
248
|
async function prodDelete(key: string, scope: StorageScope, userId: string): Promise<void> {
|
|
250
|
-
const res = await fetch(`${
|
|
249
|
+
const res = await fetch(`${agentBase()}/_storage/delete`, {
|
|
251
250
|
method: "POST",
|
|
252
251
|
headers: { "Content-Type": "application/json" },
|
|
253
252
|
body: JSON.stringify({ key, scope, userId }),
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Regression test for the vibes-build hang (confirmed: `[vibes-build] done.`
|
|
2
|
+
// prints, process never exits when the project declares cron()/on()).
|
|
3
|
+
//
|
|
4
|
+
// Root cause: registerTriggers() arms an in-process setTimeout chain for
|
|
5
|
+
// every cron() handler whenever vibesMode()==="dev" or cronDriver()===
|
|
6
|
+
// "in-process" — including when createVibesServer() is constructed by the
|
|
7
|
+
// vibes vite-plugin's configureServer hook, which prerenderApp() (part of
|
|
8
|
+
// `vibes-build`) triggers indirectly via a nested Vite dev server. Those
|
|
9
|
+
// timers keep the event loop alive forever with no caller left to clear
|
|
10
|
+
// them, since a build process has no request loop to eventually close it.
|
|
11
|
+
//
|
|
12
|
+
// This can only be observed as a live-process behavior (does the process
|
|
13
|
+
// exit on its own?), not through a return-value assertion on
|
|
14
|
+
// registerTriggers() — so this test spawns a real Bun subprocess and checks
|
|
15
|
+
// whether it terminates within a bounded window.
|
|
16
|
+
|
|
17
|
+
import { describe, expect, test } from "bun:test"
|
|
18
|
+
import fs from "node:fs"
|
|
19
|
+
import os from "node:os"
|
|
20
|
+
import path from "node:path"
|
|
21
|
+
|
|
22
|
+
const triggersModulePath = path.join(import.meta.dir, "..", "triggers.ts")
|
|
23
|
+
|
|
24
|
+
function writeRegisterScript(): string {
|
|
25
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "vibes-build-mode-cron-"))
|
|
26
|
+
const file = path.join(dir, "register.ts")
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
file,
|
|
29
|
+
[
|
|
30
|
+
`import { registerTriggers } from ${JSON.stringify(triggersModulePath)}`,
|
|
31
|
+
"await registerTriggers([",
|
|
32
|
+
' { handler: "trg.tick", kind: "cron", key: "* * * * *", module: "/dev/null", exportName: "tick", mod: { tick: async () => {} } },',
|
|
33
|
+
' { handler: "trg.onEvt", kind: "on", key: "some.topic", module: "/dev/null", exportName: "onEvt", mod: { onEvt: async () => {} } },',
|
|
34
|
+
"])",
|
|
35
|
+
'console.log("REGISTERED")',
|
|
36
|
+
"",
|
|
37
|
+
].join("\n"),
|
|
38
|
+
)
|
|
39
|
+
return file
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Runs `script` in a fresh Bun subprocess with `env` merged over the current
|
|
43
|
+
// process env. Resolves `{ exited: true }` if the process exits within
|
|
44
|
+
// `timeoutMs` on its own, or `{ exited: false }` (after force-killing it) if
|
|
45
|
+
// it's still alive at the deadline — the exact symptom of the original bug.
|
|
46
|
+
async function runAndWaitForExit(
|
|
47
|
+
script: string,
|
|
48
|
+
env: Record<string, string>,
|
|
49
|
+
timeoutMs: number,
|
|
50
|
+
): Promise<{ exited: boolean }> {
|
|
51
|
+
const proc = Bun.spawn(["bun", "run", script], {
|
|
52
|
+
env: { ...process.env, ...env },
|
|
53
|
+
stdout: "pipe",
|
|
54
|
+
stderr: "pipe",
|
|
55
|
+
})
|
|
56
|
+
let timedOut = false
|
|
57
|
+
const timer = setTimeout(() => {
|
|
58
|
+
timedOut = true
|
|
59
|
+
proc.kill()
|
|
60
|
+
}, timeoutMs)
|
|
61
|
+
await proc.exited
|
|
62
|
+
clearTimeout(timer)
|
|
63
|
+
return { exited: !timedOut }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe("registerTriggers build-mode cron gating", () => {
|
|
67
|
+
test("VIBES_BUILD=1: a cron()+on() project terminates on its own", async () => {
|
|
68
|
+
const script = writeRegisterScript()
|
|
69
|
+
const { exited } = await runAndWaitForExit(script, { VIBES_BUILD: "1", VIBES_MODE: "dev" }, 8000)
|
|
70
|
+
expect(exited).toBe(true)
|
|
71
|
+
}, 15000)
|
|
72
|
+
|
|
73
|
+
test("sanity: without VIBES_BUILD, dev-mode cron is still armed (stays alive)", async () => {
|
|
74
|
+
// Mirrors the fixed case above to prove the guard is scoped to build
|
|
75
|
+
// mode only — normal `bun dev` cron scheduling must be unaffected.
|
|
76
|
+
const script = writeRegisterScript()
|
|
77
|
+
const { exited } = await runAndWaitForExit(script, { VIBES_MODE: "dev" }, 2000)
|
|
78
|
+
expect(exited).toBe(false)
|
|
79
|
+
}, 15000)
|
|
80
|
+
})
|
package/src/triggers.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import path from "node:path"
|
|
26
26
|
import fs from "node:fs"
|
|
27
27
|
import { ctxStore, type VibesCtx } from "./ctx.ts"
|
|
28
|
+
import { agentBase } from "./agent-url.ts"
|
|
28
29
|
|
|
29
30
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
30
31
|
|
|
@@ -242,12 +243,20 @@ export async function registerTriggers(entries: TriggerEntry[]): Promise<void> {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
void registeredFirstTime // suppress unused — kept for future HMR diff
|
|
245
|
-
|
|
246
|
+
// vibes-build sets VIBES_BUILD=1 for its whole process, including the
|
|
247
|
+
// nested Vite dev server it spins up for prerender SSG. A build is not a
|
|
248
|
+
// runtime — nothing should ever arm a live setTimeout chain there, or the
|
|
249
|
+
// process hangs forever after writing its artifacts (confirmed hang: cron
|
|
250
|
+
// schedulers keep the event loop open with no caller left to clear them).
|
|
251
|
+
const inBuild = inBuildMode()
|
|
252
|
+
const inProcessCron = !inBuild && (vibesMode() === "dev" || cronDriver() === "in-process")
|
|
246
253
|
if (inProcessCron) {
|
|
247
254
|
scheduleAllCronInProcess()
|
|
248
255
|
}
|
|
249
256
|
console.log(
|
|
250
|
-
`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode()}, cron=${
|
|
257
|
+
`[vibes:triggers] registered ${entries.length} trigger(s) — mode=${vibesMode()}, cron=${
|
|
258
|
+
inBuild ? "unarmed (build)" : inProcessCron ? "in-process" : "orchestrator"
|
|
259
|
+
}`,
|
|
251
260
|
)
|
|
252
261
|
}
|
|
253
262
|
|
|
@@ -472,7 +481,7 @@ async function emitToOrchestrator(topic: string, payload: unknown): Promise<{ su
|
|
|
472
481
|
if (payloadStr.length > MAX_EMIT_BYTES) {
|
|
473
482
|
throw new Error(`emit("${topic}"): payload exceeds ${MAX_EMIT_BYTES} bytes`)
|
|
474
483
|
}
|
|
475
|
-
const url =
|
|
484
|
+
const url = `${agentBase()}/_emit`
|
|
476
485
|
const res = await fetch(url, {
|
|
477
486
|
method: "POST",
|
|
478
487
|
headers: { "Content-Type": "application/json" },
|
|
@@ -629,7 +638,7 @@ async function scheduleToOrchestrator(
|
|
|
629
638
|
if (payloadStr.length > MAX_EMIT_BYTES) {
|
|
630
639
|
throw new Error(`schedule("${topic}"): payload exceeds ${MAX_EMIT_BYTES} bytes`)
|
|
631
640
|
}
|
|
632
|
-
const url =
|
|
641
|
+
const url = `${agentBase()}/_schedule`
|
|
633
642
|
const res = await fetch(url, {
|
|
634
643
|
method: "POST",
|
|
635
644
|
headers: { "Content-Type": "application/json" },
|
|
@@ -656,7 +665,7 @@ async function scheduleToOrchestrator(
|
|
|
656
665
|
}
|
|
657
666
|
|
|
658
667
|
async function cancelOnOrchestrator(eventId: string): Promise<{ cancelled: number }> {
|
|
659
|
-
const url =
|
|
668
|
+
const url = `${agentBase()}/_schedule/${encodeURIComponent(eventId)}`
|
|
660
669
|
const res = await fetch(url, { method: "DELETE" })
|
|
661
670
|
if (!res.ok) {
|
|
662
671
|
const text = await res.text().catch(() => "")
|
|
@@ -810,6 +819,15 @@ function vibesMode(): "dev" | "prod" {
|
|
|
810
819
|
return _vibesMode
|
|
811
820
|
}
|
|
812
821
|
|
|
822
|
+
// Set for the whole lifetime of `vibes-build` (packages/vite-plugin/src/build.ts).
|
|
823
|
+
// A build process has no caller left to clear timers once it "finishes" —
|
|
824
|
+
// so cron must never be armed in-process while this is set, however
|
|
825
|
+
// createVibesServer() got invoked (directly, or via prerender's nested dev
|
|
826
|
+
// server).
|
|
827
|
+
function inBuildMode(): boolean {
|
|
828
|
+
return (typeof process !== "undefined" ? process.env?.VIBES_BUILD : undefined) === "1"
|
|
829
|
+
}
|
|
830
|
+
|
|
813
831
|
// ── Dev Inspect data accessors ───────────────────────────────────────────────
|
|
814
832
|
//
|
|
815
833
|
// In dev mode, the dashboard's Inspect tabs read the in-memory rings
|
package/src/workflows.ts
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import path from "node:path"
|
|
39
39
|
import fs from "node:fs"
|
|
40
40
|
import { ctxStore, type VibesCtx } from "./ctx.ts"
|
|
41
|
+
import { agentBase } from "./agent-url.ts"
|
|
41
42
|
|
|
42
43
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
43
44
|
|
|
@@ -417,7 +418,7 @@ async function startViaAgent(
|
|
|
417
418
|
payloadStr: string,
|
|
418
419
|
opts: StartWorkflowOptions,
|
|
419
420
|
): Promise<{ runId: string }> {
|
|
420
|
-
const res = await fetch(
|
|
421
|
+
const res = await fetch(`${agentBase()}/_workflow/start`, {
|
|
421
422
|
method: "POST",
|
|
422
423
|
headers: { "Content-Type": "application/json" },
|
|
423
424
|
body: JSON.stringify({
|