station-kit 1.0.5 → 1.0.6
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/.next/standalone/packages/station-kit/.next/BUILD_ID +1 -1
- package/.next/standalone/packages/station-kit/.next/app-build-manifest.json +15 -15
- package/.next/standalone/packages/station-kit/.next/app-path-routes-manifest.json +2 -2
- package/.next/standalone/packages/station-kit/.next/build-manifest.json +2 -2
- package/.next/standalone/packages/station-kit/.next/prerender-manifest.json +10 -10
- package/.next/standalone/packages/station-kit/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/_not-found.rsc +7 -7
- package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/[id]/page.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/broadcasts/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/broadcasts.rsc +8 -8
- package/.next/standalone/packages/station-kit/.next/server/app/index.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/index.rsc +8 -8
- package/.next/standalone/packages/station-kit/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/runs/[id]/page.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/runs/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/settings.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/settings.rsc +8 -8
- package/.next/standalone/packages/station-kit/.next/server/app/signals/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/signals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/signals.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/app/signals.rsc +8 -8
- package/.next/standalone/packages/station-kit/.next/server/app-paths-manifest.json +2 -2
- package/.next/standalone/packages/station-kit/.next/server/chunks/102.js +1 -1
- package/.next/standalone/packages/station-kit/.next/server/pages/404.html +1 -1
- package/.next/standalone/packages/station-kit/.next/server/pages/500.html +1 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-a0a20cccda13a0e9.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-937eb876f9087bc9.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-68cd71116ba65cd8.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-70b0c0958c03459a.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-01f8040619fe56c5.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-beac11049f90da31.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-931e6a38a4a53d25.js +1 -0
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-6a123a355d93fec5.js +1 -0
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/broadcasts.d.ts.map +1 -1
- package/dist/server/routes/broadcasts.js +23 -0
- package/dist/server/routes/broadcasts.js.map +1 -1
- package/dist/server/routes/runs.d.ts +2 -0
- package/dist/server/routes/runs.d.ts.map +1 -1
- package/dist/server/routes/runs.js +66 -0
- package/dist/server/routes/runs.js.map +1 -1
- package/dist/server/routes/v1/trigger.d.ts +2 -1
- package/dist/server/routes/v1/trigger.d.ts.map +1 -1
- package/dist/server/routes/v1/trigger.js +88 -0
- package/dist/server/routes/v1/trigger.js.map +1 -1
- package/package.json +1 -1
- package/src/app/broadcasts/[id]/broadcast-detail.tsx +22 -1
- package/src/app/hooks/use-api.ts +3 -0
- package/src/app/runs/[id]/run-detail.tsx +45 -1
- package/src/server/index.ts +2 -2
- package/src/server/routes/broadcasts.ts +24 -0
- package/src/server/routes/runs.ts +76 -0
- package/src/server/routes/v1/trigger.ts +105 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/[id]/page-a41371d946262e43.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/broadcasts/page-1b9fa68dcc219e1d.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/layout-9d37f15ea4fdcabf.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/page-0c92bbc0acac1025.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/runs/[id]/page-87e302dcd2f7516b.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/settings/page-39f130df9a05bf2b.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/[name]/page-215ec395e232fb36.js +0 -1
- package/.next/standalone/packages/station-kit/.next/static/chunks/app/signals/page-35b38b909a017693.js +0 -1
- /package/.next/standalone/packages/station-kit/.next/static/{_qcazCz3yLLsuU53Qtacw → kqMlNlRLox_FLRK-2GwrJ}/_buildManifest.js +0 -0
- /package/.next/standalone/packages/station-kit/.next/static/{_qcazCz3yLLsuU53Qtacw → kqMlNlRLox_FLRK-2GwrJ}/_ssgManifest.js +0 -0
|
@@ -40,6 +40,72 @@ export function v1TriggerRoutes(deps) {
|
|
|
40
40
|
data: { id, signalName, status: "pending", createdAt: new Date().toISOString() },
|
|
41
41
|
}, 201);
|
|
42
42
|
});
|
|
43
|
+
app.post("/runs/:id/rerun", async (c) => {
|
|
44
|
+
const id = c.req.param("id");
|
|
45
|
+
if (!deps.signalRunner) {
|
|
46
|
+
return c.json({ error: "unavailable", message: "Station is in read-only mode." }, 503);
|
|
47
|
+
}
|
|
48
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
49
|
+
if (!run) {
|
|
50
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
51
|
+
}
|
|
52
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
53
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled runs can be rerun." }, 400);
|
|
54
|
+
}
|
|
55
|
+
let maxAttempts = run.maxAttempts;
|
|
56
|
+
let timeout = run.timeout;
|
|
57
|
+
if (deps.signalSubscriber) {
|
|
58
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
59
|
+
if (meta) {
|
|
60
|
+
maxAttempts = meta.maxAttempts;
|
|
61
|
+
timeout = meta.timeout;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const newId = deps.signalAdapter.generateId();
|
|
65
|
+
await deps.signalAdapter.addRun({
|
|
66
|
+
id: newId,
|
|
67
|
+
signalName: run.signalName,
|
|
68
|
+
kind: "trigger",
|
|
69
|
+
input: run.input,
|
|
70
|
+
status: "pending",
|
|
71
|
+
attempts: 0,
|
|
72
|
+
maxAttempts,
|
|
73
|
+
timeout,
|
|
74
|
+
createdAt: new Date(),
|
|
75
|
+
});
|
|
76
|
+
return c.json({ data: { id: newId, signalName: run.signalName, status: "pending", createdAt: new Date().toISOString() } }, 201);
|
|
77
|
+
});
|
|
78
|
+
app.post("/runs/:id/retry", async (c) => {
|
|
79
|
+
const id = c.req.param("id");
|
|
80
|
+
if (!deps.signalRunner) {
|
|
81
|
+
return c.json({ error: "unavailable", message: "Station is in read-only mode." }, 503);
|
|
82
|
+
}
|
|
83
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
84
|
+
if (!run) {
|
|
85
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
86
|
+
}
|
|
87
|
+
if (run.status !== "failed") {
|
|
88
|
+
return c.json({ error: "invalid_status", message: "Only failed runs can be retried." }, 400);
|
|
89
|
+
}
|
|
90
|
+
let maxAttempts = run.maxAttempts;
|
|
91
|
+
if (deps.signalSubscriber) {
|
|
92
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
93
|
+
if (meta) {
|
|
94
|
+
maxAttempts = meta.maxAttempts;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
await deps.signalAdapter.updateRun(id, {
|
|
98
|
+
status: "pending",
|
|
99
|
+
attempts: 0,
|
|
100
|
+
maxAttempts,
|
|
101
|
+
error: undefined,
|
|
102
|
+
output: undefined,
|
|
103
|
+
startedAt: undefined,
|
|
104
|
+
completedAt: undefined,
|
|
105
|
+
lastRunAt: undefined,
|
|
106
|
+
});
|
|
107
|
+
return c.json({ data: { retried: true } });
|
|
108
|
+
});
|
|
43
109
|
app.post("/trigger-broadcast", async (c) => {
|
|
44
110
|
if (!deps.broadcastRunner) {
|
|
45
111
|
return c.json({ error: "unavailable", message: "Broadcast runner not configured." }, 503);
|
|
@@ -68,6 +134,28 @@ export function v1TriggerRoutes(deps) {
|
|
|
68
134
|
return c.json({ error: "trigger_failed", message }, 400);
|
|
69
135
|
}
|
|
70
136
|
});
|
|
137
|
+
app.post("/broadcast-runs/:id/rerun", async (c) => {
|
|
138
|
+
if (!deps.broadcastRunner || !deps.broadcastAdapter) {
|
|
139
|
+
return c.json({ error: "unavailable", message: "Broadcast runner not configured." }, 503);
|
|
140
|
+
}
|
|
141
|
+
const id = c.req.param("id");
|
|
142
|
+
const run = await deps.broadcastAdapter.getBroadcastRun(id);
|
|
143
|
+
if (!run) {
|
|
144
|
+
return c.json({ error: "not_found", message: "Broadcast run not found." }, 404);
|
|
145
|
+
}
|
|
146
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
147
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled broadcast runs can be rerun." }, 400);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const input = typeof run.input === "string" ? JSON.parse(run.input) : run.input;
|
|
151
|
+
const newId = await deps.broadcastRunner.trigger(run.broadcastName, input);
|
|
152
|
+
return c.json({ data: { id: newId, broadcastName: run.broadcastName, status: "pending", createdAt: new Date().toISOString() } }, 201);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
156
|
+
return c.json({ error: "rerun_failed", message }, 400);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
71
159
|
return app;
|
|
72
160
|
}
|
|
73
161
|
//# sourceMappingURL=trigger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../../../../src/server/routes/v1/trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../../../../src/server/routes/v1/trigger.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState, useCallback } from "react";
|
|
4
|
-
import { useParams } from "next/navigation";
|
|
4
|
+
import { useParams, useRouter } from "next/navigation";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { useApi, type BroadcastMeta, type SchemaField } from "../../hooks/use-api";
|
|
7
7
|
import { useStation } from "../../hooks/use-station";
|
|
@@ -248,6 +248,7 @@ function BroadcastNameView({ name }: { name: string }) {
|
|
|
248
248
|
|
|
249
249
|
function BroadcastRunView({ id }: { id: string }) {
|
|
250
250
|
const api = useApi();
|
|
251
|
+
const router = useRouter();
|
|
251
252
|
const { events } = useStation();
|
|
252
253
|
const [broadcastRun, setBroadcastRun] = useState<any>(null);
|
|
253
254
|
const [nodeRuns, setNodeRuns] = useState<any[]>([]);
|
|
@@ -255,6 +256,7 @@ function BroadcastRunView({ id }: { id: string }) {
|
|
|
255
256
|
const [logs, setLogs] = useState<BroadcastLogEntry[]>([]);
|
|
256
257
|
const [loading, setLoading] = useState(true);
|
|
257
258
|
const [cancelling, setCancelling] = useState(false);
|
|
259
|
+
const [rerunning, setRerunning] = useState(false);
|
|
258
260
|
const [selectedNode, setSelectedNode] = useState<string | null>(null);
|
|
259
261
|
const signalRunIdsRef = useRef<Set<string>>(new Set());
|
|
260
262
|
const autoSelectedRef = useRef(false);
|
|
@@ -357,6 +359,19 @@ function BroadcastRunView({ id }: { id: string }) {
|
|
|
357
359
|
setCancelling(false);
|
|
358
360
|
}
|
|
359
361
|
|
|
362
|
+
async function handleRerun() {
|
|
363
|
+
setRerunning(true);
|
|
364
|
+
try {
|
|
365
|
+
const res = await api.rerunBroadcastRun(id);
|
|
366
|
+
router.push(`/broadcasts/${res.data.id}`);
|
|
367
|
+
} catch (err: unknown) {
|
|
368
|
+
if (err instanceof Error) {
|
|
369
|
+
console.error("Rerun failed:", err.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
setRerunning(false);
|
|
373
|
+
}
|
|
374
|
+
|
|
360
375
|
// Build deps map from broadcast metadata
|
|
361
376
|
const depsMap = useMemo(() => {
|
|
362
377
|
const map = new Map<string, string[]>();
|
|
@@ -439,6 +454,7 @@ function BroadcastRunView({ id }: { id: string }) {
|
|
|
439
454
|
}
|
|
440
455
|
|
|
441
456
|
const canCancel = broadcastRun.status === "pending" || broadcastRun.status === "running";
|
|
457
|
+
const canRerun = broadcastRun.status === "failed" || broadcastRun.status === "completed" || broadcastRun.status === "cancelled";
|
|
442
458
|
|
|
443
459
|
return (
|
|
444
460
|
<div>
|
|
@@ -458,6 +474,11 @@ function BroadcastRunView({ id }: { id: string }) {
|
|
|
458
474
|
</span>
|
|
459
475
|
</div>
|
|
460
476
|
<div className="page-header-actions">
|
|
477
|
+
{canRerun && (
|
|
478
|
+
<button className="btn btn--primary" onClick={handleRerun} disabled={rerunning}>
|
|
479
|
+
{rerunning ? "Rerunning..." : "Rerun"}
|
|
480
|
+
</button>
|
|
481
|
+
)}
|
|
461
482
|
{canCancel && (
|
|
462
483
|
<button className="btn btn--danger" onClick={handleCancel} disabled={cancelling}>
|
|
463
484
|
{cancelling ? "Cancelling..." : "Cancel"}
|
package/src/app/hooks/use-api.ts
CHANGED
|
@@ -111,6 +111,8 @@ export function useApi() {
|
|
|
111
111
|
getRunSteps: (id: string) => fetchApi<any[]>(`/runs/${id}/steps`),
|
|
112
112
|
getRunLogs: (id: string) => fetchApi<Array<{ runId: string; signalName: string; level: string; message: string; timestamp: string }>>(`/runs/${id}/logs`),
|
|
113
113
|
cancelRun: (id: string) => fetchApi<{ cancelled: boolean }>(`/runs/${id}/cancel`, { method: "POST" }),
|
|
114
|
+
rerunRun: (id: string) => fetchApi<{ id: string; signalName: string; status: string }>(`/runs/${id}/rerun`, { method: "POST" }),
|
|
115
|
+
retryRun: (id: string) => fetchApi<{ retried: boolean }>(`/runs/${id}/retry`, { method: "POST" }),
|
|
114
116
|
|
|
115
117
|
// Broadcasts
|
|
116
118
|
getBroadcasts: () => fetchApi<BroadcastMeta[]>("/broadcasts"),
|
|
@@ -125,6 +127,7 @@ export function useApi() {
|
|
|
125
127
|
getBroadcastRunNodes: (id: string) => fetchApi<any[]>(`/broadcast-runs/${id}/nodes`),
|
|
126
128
|
getBroadcastRunLogs: (id: string) => fetchApi<Array<{ runId: string; signalName: string; level: string; message: string; timestamp: string; nodeName: string }>>(`/broadcast-runs/${id}/logs`),
|
|
127
129
|
cancelBroadcastRun: (id: string) => fetchApi<{ cancelled: boolean }>(`/broadcast-runs/${id}/cancel`, { method: "POST" }),
|
|
130
|
+
rerunBroadcastRun: (id: string) => fetchApi<{ id: string; broadcastName: string; status: string }>(`/broadcast-runs/${id}/rerun`, { method: "POST" }),
|
|
128
131
|
|
|
129
132
|
// API Keys (v1 admin routes — session cookie provides admin scope)
|
|
130
133
|
getApiKeys: () => fetchApi<Array<{ id: string; name: string; keyPrefix: string; scopes: string[]; createdAt: string; lastUsed: string | null; expiresAt: string | null; revoked: boolean }>>("/v1/keys"),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { useParams } from "next/navigation";
|
|
4
|
+
import { useParams, useRouter } from "next/navigation";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { useApi } from "../../hooks/use-api";
|
|
7
7
|
import { useStation } from "../../hooks/use-station";
|
|
@@ -36,6 +36,7 @@ function computeDuration(startedAt?: string, completedAt?: string): string {
|
|
|
36
36
|
export function RunDetail() {
|
|
37
37
|
const params = useParams();
|
|
38
38
|
const id = params.id as string;
|
|
39
|
+
const router = useRouter();
|
|
39
40
|
const api = useApi();
|
|
40
41
|
const { events } = useStation();
|
|
41
42
|
const [run, setRun] = useState<any>(null);
|
|
@@ -43,6 +44,8 @@ export function RunDetail() {
|
|
|
43
44
|
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
44
45
|
const [loading, setLoading] = useState(true);
|
|
45
46
|
const [cancelling, setCancelling] = useState(false);
|
|
47
|
+
const [rerunning, setRerunning] = useState(false);
|
|
48
|
+
const [retrying, setRetrying] = useState(false);
|
|
46
49
|
const [copied, setCopied] = useState(false);
|
|
47
50
|
const logEndRef = useRef<HTMLDivElement>(null);
|
|
48
51
|
|
|
@@ -116,6 +119,35 @@ export function RunDetail() {
|
|
|
116
119
|
setCancelling(false);
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
async function handleRerun() {
|
|
123
|
+
setRerunning(true);
|
|
124
|
+
try {
|
|
125
|
+
const res = await api.rerunRun(id);
|
|
126
|
+
router.push(`/runs/${res.data.id}`);
|
|
127
|
+
} catch (err: unknown) {
|
|
128
|
+
if (err instanceof Error) {
|
|
129
|
+
console.error("Rerun failed:", err.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
setRerunning(false);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function handleRetry() {
|
|
136
|
+
setRetrying(true);
|
|
137
|
+
try {
|
|
138
|
+
await api.retryRun(id);
|
|
139
|
+
const res = await api.getRun(id);
|
|
140
|
+
setRun(res.data);
|
|
141
|
+
const stepsRes = await api.getRunSteps(id);
|
|
142
|
+
setSteps(stepsRes.data);
|
|
143
|
+
} catch (err: unknown) {
|
|
144
|
+
if (err instanceof Error) {
|
|
145
|
+
console.error("Retry failed:", err.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
setRetrying(false);
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
function handleCopyId() {
|
|
120
152
|
navigator.clipboard.writeText(run.id).then(() => {
|
|
121
153
|
setCopied(true);
|
|
@@ -144,6 +176,8 @@ export function RunDetail() {
|
|
|
144
176
|
}
|
|
145
177
|
|
|
146
178
|
const canCancel = run.status === "pending" || run.status === "running";
|
|
179
|
+
const canRerun = run.status === "failed" || run.status === "completed" || run.status === "cancelled";
|
|
180
|
+
const canRetry = run.status === "failed";
|
|
147
181
|
|
|
148
182
|
return (
|
|
149
183
|
<div>
|
|
@@ -155,6 +189,16 @@ export function RunDetail() {
|
|
|
155
189
|
</Link>
|
|
156
190
|
</div>
|
|
157
191
|
<div className="page-header-actions">
|
|
192
|
+
{canRetry && (
|
|
193
|
+
<button className="btn" onClick={handleRetry} disabled={retrying}>
|
|
194
|
+
{retrying ? "Retrying..." : "Retry"}
|
|
195
|
+
</button>
|
|
196
|
+
)}
|
|
197
|
+
{canRerun && (
|
|
198
|
+
<button className="btn btn--primary" onClick={handleRerun} disabled={rerunning}>
|
|
199
|
+
{rerunning ? "Rerunning..." : "Rerun"}
|
|
200
|
+
</button>
|
|
201
|
+
)}
|
|
158
202
|
{canCancel && (
|
|
159
203
|
<button className="btn btn--danger" onClick={handleCancel} disabled={cancelling}>
|
|
160
204
|
{cancelling ? "Cancelling..." : "Cancel"}
|
package/src/server/index.ts
CHANGED
|
@@ -169,7 +169,7 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
|
|
|
169
169
|
|
|
170
170
|
app.route("/api", healthRoutes({ signalAdapter, broadcastAdapter }));
|
|
171
171
|
app.route("/api", signalRoutes({ signalRunner, signalAdapter, signalSubscriber: stationSignalSub }));
|
|
172
|
-
app.route("/api", runRoutes({ signalRunner, signalAdapter, logBuffer, logStore }));
|
|
172
|
+
app.route("/api", runRoutes({ signalRunner, signalAdapter, logBuffer, logStore, signalSubscriber: stationSignalSub }));
|
|
173
173
|
app.route("/api", broadcastRoutes({ broadcastRunner, broadcastAdapter, broadcastSubscriber: stationBroadcastSub, logBuffer, logStore }));
|
|
174
174
|
|
|
175
175
|
// ── v1 API routes (authenticated) ──────────────────────────────────
|
|
@@ -199,7 +199,7 @@ export async function createStation(config: StationConfig, cwd: string, nextPort
|
|
|
199
199
|
// Trigger-scope routes
|
|
200
200
|
const triggerRoutes = new Hono();
|
|
201
201
|
triggerRoutes.use("/*", requireScope("trigger"));
|
|
202
|
-
triggerRoutes.route("/", v1TriggerRoutes({ signalRunner, signalAdapter, broadcastRunner, signalSubscriber: stationSignalSub }));
|
|
202
|
+
triggerRoutes.route("/", v1TriggerRoutes({ signalRunner, signalAdapter, broadcastRunner, broadcastAdapter, signalSubscriber: stationSignalSub }));
|
|
203
203
|
v1.route("/", triggerRoutes);
|
|
204
204
|
|
|
205
205
|
// Cancel-scope routes — only the cancel endpoints
|
|
@@ -146,6 +146,30 @@ export function broadcastRoutes(deps: BroadcastDeps) {
|
|
|
146
146
|
return c.json({ data: { cancelled: true } });
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
// POST /broadcast-runs/:id/rerun
|
|
150
|
+
app.post("/broadcast-runs/:id/rerun", async (c) => {
|
|
151
|
+
const id = c.req.param("id");
|
|
152
|
+
if (!deps.broadcastRunner || !deps.broadcastAdapter) {
|
|
153
|
+
return c.json({ error: "read_only", message: "Station is in read-only mode." }, 403);
|
|
154
|
+
}
|
|
155
|
+
const run = await deps.broadcastAdapter.getBroadcastRun(id);
|
|
156
|
+
if (!run) {
|
|
157
|
+
return c.json({ error: "not_found", message: "Broadcast run not found." }, 404);
|
|
158
|
+
}
|
|
159
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
160
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled broadcast runs can be rerun." }, 400);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const input = typeof run.input === "string" ? JSON.parse(run.input) : run.input;
|
|
165
|
+
const newId = await deps.broadcastRunner.trigger(run.broadcastName, input);
|
|
166
|
+
return c.json({ data: { id: newId, broadcastName: run.broadcastName, status: "pending" } });
|
|
167
|
+
} catch (err: unknown) {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
169
|
+
return c.json({ error: "rerun_failed", message }, 400);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
149
173
|
return app;
|
|
150
174
|
}
|
|
151
175
|
|
|
@@ -2,12 +2,14 @@ import { Hono } from "hono";
|
|
|
2
2
|
import type { SignalRunner, SignalQueueAdapter } from "station-signal";
|
|
3
3
|
import type { LogBuffer } from "../log-buffer.js";
|
|
4
4
|
import type { LogStore } from "../log-store.js";
|
|
5
|
+
import type { StationSignalSubscriber } from "../subscriber.js";
|
|
5
6
|
|
|
6
7
|
export interface RunDeps {
|
|
7
8
|
signalRunner?: SignalRunner;
|
|
8
9
|
signalAdapter: SignalQueueAdapter;
|
|
9
10
|
logBuffer: LogBuffer;
|
|
10
11
|
logStore?: LogStore;
|
|
12
|
+
signalSubscriber?: StationSignalSubscriber;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function runRoutes(deps: RunDeps) {
|
|
@@ -134,6 +136,80 @@ export function runRoutes(deps: RunDeps) {
|
|
|
134
136
|
return c.json({ data: { cancelled: true } });
|
|
135
137
|
});
|
|
136
138
|
|
|
139
|
+
app.post("/runs/:id/rerun", async (c) => {
|
|
140
|
+
const id = c.req.param("id");
|
|
141
|
+
if (!deps.signalRunner) {
|
|
142
|
+
return c.json({ error: "read_only", message: "Station is in read-only mode." }, 403);
|
|
143
|
+
}
|
|
144
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
145
|
+
if (!run) {
|
|
146
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
147
|
+
}
|
|
148
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
149
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled runs can be rerun." }, 400);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let maxAttempts = run.maxAttempts;
|
|
153
|
+
let timeout = run.timeout;
|
|
154
|
+
if (deps.signalSubscriber) {
|
|
155
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
156
|
+
if (meta) {
|
|
157
|
+
maxAttempts = meta.maxAttempts;
|
|
158
|
+
timeout = meta.timeout;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const newId = deps.signalAdapter.generateId();
|
|
163
|
+
await deps.signalAdapter.addRun({
|
|
164
|
+
id: newId,
|
|
165
|
+
signalName: run.signalName,
|
|
166
|
+
kind: "trigger",
|
|
167
|
+
input: run.input,
|
|
168
|
+
status: "pending",
|
|
169
|
+
attempts: 0,
|
|
170
|
+
maxAttempts,
|
|
171
|
+
timeout,
|
|
172
|
+
createdAt: new Date(),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return c.json({ data: { id: newId, signalName: run.signalName, status: "pending" } });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
app.post("/runs/:id/retry", async (c) => {
|
|
179
|
+
const id = c.req.param("id");
|
|
180
|
+
if (!deps.signalRunner) {
|
|
181
|
+
return c.json({ error: "read_only", message: "Station is in read-only mode." }, 403);
|
|
182
|
+
}
|
|
183
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
184
|
+
if (!run) {
|
|
185
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
186
|
+
}
|
|
187
|
+
if (run.status !== "failed") {
|
|
188
|
+
return c.json({ error: "invalid_status", message: "Only failed runs can be retried." }, 400);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let maxAttempts = run.maxAttempts;
|
|
192
|
+
if (deps.signalSubscriber) {
|
|
193
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
194
|
+
if (meta) {
|
|
195
|
+
maxAttempts = meta.maxAttempts;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await deps.signalAdapter.updateRun(id, {
|
|
200
|
+
status: "pending",
|
|
201
|
+
attempts: 0,
|
|
202
|
+
maxAttempts,
|
|
203
|
+
error: undefined,
|
|
204
|
+
output: undefined,
|
|
205
|
+
startedAt: undefined,
|
|
206
|
+
completedAt: undefined,
|
|
207
|
+
lastRunAt: undefined,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return c.json({ data: { retried: true } });
|
|
211
|
+
});
|
|
212
|
+
|
|
137
213
|
return app;
|
|
138
214
|
}
|
|
139
215
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import type { SignalRunner, SignalQueueAdapter } from "station-signal";
|
|
3
|
-
import type { BroadcastRunner } from "station-broadcast";
|
|
3
|
+
import type { BroadcastRunner, BroadcastQueueAdapter } from "station-broadcast";
|
|
4
4
|
import type { StationSignalSubscriber } from "../../subscriber.js";
|
|
5
5
|
|
|
6
6
|
export interface V1TriggerDeps {
|
|
7
7
|
signalRunner?: SignalRunner;
|
|
8
8
|
signalAdapter: SignalQueueAdapter;
|
|
9
9
|
broadcastRunner?: BroadcastRunner;
|
|
10
|
+
broadcastAdapter?: BroadcastQueueAdapter;
|
|
10
11
|
signalSubscriber?: StationSignalSubscriber;
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -66,6 +67,83 @@ export function v1TriggerRoutes(deps: V1TriggerDeps) {
|
|
|
66
67
|
);
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
app.post("/runs/:id/rerun", async (c) => {
|
|
71
|
+
const id = c.req.param("id");
|
|
72
|
+
if (!deps.signalRunner) {
|
|
73
|
+
return c.json({ error: "unavailable", message: "Station is in read-only mode." }, 503);
|
|
74
|
+
}
|
|
75
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
76
|
+
if (!run) {
|
|
77
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
78
|
+
}
|
|
79
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
80
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled runs can be rerun." }, 400);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let maxAttempts = run.maxAttempts;
|
|
84
|
+
let timeout = run.timeout;
|
|
85
|
+
if (deps.signalSubscriber) {
|
|
86
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
87
|
+
if (meta) {
|
|
88
|
+
maxAttempts = meta.maxAttempts;
|
|
89
|
+
timeout = meta.timeout;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const newId = deps.signalAdapter.generateId();
|
|
94
|
+
await deps.signalAdapter.addRun({
|
|
95
|
+
id: newId,
|
|
96
|
+
signalName: run.signalName,
|
|
97
|
+
kind: "trigger",
|
|
98
|
+
input: run.input,
|
|
99
|
+
status: "pending",
|
|
100
|
+
attempts: 0,
|
|
101
|
+
maxAttempts,
|
|
102
|
+
timeout,
|
|
103
|
+
createdAt: new Date(),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return c.json(
|
|
107
|
+
{ data: { id: newId, signalName: run.signalName, status: "pending", createdAt: new Date().toISOString() } },
|
|
108
|
+
201,
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
app.post("/runs/:id/retry", async (c) => {
|
|
113
|
+
const id = c.req.param("id");
|
|
114
|
+
if (!deps.signalRunner) {
|
|
115
|
+
return c.json({ error: "unavailable", message: "Station is in read-only mode." }, 503);
|
|
116
|
+
}
|
|
117
|
+
const run = await deps.signalAdapter.getRun(id);
|
|
118
|
+
if (!run) {
|
|
119
|
+
return c.json({ error: "not_found", message: "Run not found." }, 404);
|
|
120
|
+
}
|
|
121
|
+
if (run.status !== "failed") {
|
|
122
|
+
return c.json({ error: "invalid_status", message: "Only failed runs can be retried." }, 400);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let maxAttempts = run.maxAttempts;
|
|
126
|
+
if (deps.signalSubscriber) {
|
|
127
|
+
const meta = deps.signalSubscriber.getSignalMeta(run.signalName);
|
|
128
|
+
if (meta) {
|
|
129
|
+
maxAttempts = meta.maxAttempts;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await deps.signalAdapter.updateRun(id, {
|
|
134
|
+
status: "pending",
|
|
135
|
+
attempts: 0,
|
|
136
|
+
maxAttempts,
|
|
137
|
+
error: undefined,
|
|
138
|
+
output: undefined,
|
|
139
|
+
startedAt: undefined,
|
|
140
|
+
completedAt: undefined,
|
|
141
|
+
lastRunAt: undefined,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return c.json({ data: { retried: true } });
|
|
145
|
+
});
|
|
146
|
+
|
|
69
147
|
app.post("/trigger-broadcast", async (c) => {
|
|
70
148
|
if (!deps.broadcastRunner) {
|
|
71
149
|
return c.json(
|
|
@@ -107,5 +185,31 @@ export function v1TriggerRoutes(deps: V1TriggerDeps) {
|
|
|
107
185
|
}
|
|
108
186
|
});
|
|
109
187
|
|
|
188
|
+
app.post("/broadcast-runs/:id/rerun", async (c) => {
|
|
189
|
+
if (!deps.broadcastRunner || !deps.broadcastAdapter) {
|
|
190
|
+
return c.json({ error: "unavailable", message: "Broadcast runner not configured." }, 503);
|
|
191
|
+
}
|
|
192
|
+
const id = c.req.param("id");
|
|
193
|
+
const run = await deps.broadcastAdapter.getBroadcastRun(id);
|
|
194
|
+
if (!run) {
|
|
195
|
+
return c.json({ error: "not_found", message: "Broadcast run not found." }, 404);
|
|
196
|
+
}
|
|
197
|
+
if (run.status !== "failed" && run.status !== "completed" && run.status !== "cancelled") {
|
|
198
|
+
return c.json({ error: "invalid_status", message: "Only failed, completed, or cancelled broadcast runs can be rerun." }, 400);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const input = typeof run.input === "string" ? JSON.parse(run.input) : run.input;
|
|
203
|
+
const newId = await deps.broadcastRunner.trigger(run.broadcastName, input);
|
|
204
|
+
return c.json(
|
|
205
|
+
{ data: { id: newId, broadcastName: run.broadcastName, status: "pending", createdAt: new Date().toISOString() } },
|
|
206
|
+
201,
|
|
207
|
+
);
|
|
208
|
+
} catch (err: unknown) {
|
|
209
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
210
|
+
return c.json({ error: "rerun_failed", message }, 400);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
110
214
|
return app;
|
|
111
215
|
}
|