@spencerboucher/switchyard 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/bridge/switchyard-terminal.js +527 -0
- package/dist/switchyard.mjs +7154 -5028
- package/dist/terminal/holder.js +305 -0
- package/package.json +6 -3
- package/web-dist/assets/{AutomationView-Bt_v8JDx.js → AutomationView-CFohp0SQ.js} +2 -2
- package/web-dist/assets/{AutomationView-Bt_v8JDx.js.map → AutomationView-CFohp0SQ.js.map} +1 -1
- package/web-dist/assets/{InteractionCard-BRI9KgrO.js → InteractionCard-B-xmpTNG.js} +2 -2
- package/web-dist/assets/{InteractionCard-BRI9KgrO.js.map → InteractionCard-B-xmpTNG.js.map} +1 -1
- package/web-dist/assets/Menu-C4GUY_ym.js +2 -0
- package/web-dist/assets/Menu-C4GUY_ym.js.map +1 -0
- package/web-dist/assets/{Overlay-B9h5oAe6.js → Overlay--v0ur2lj.js} +2 -2
- package/web-dist/assets/{Overlay-B9h5oAe6.js.map → Overlay--v0ur2lj.js.map} +1 -1
- package/web-dist/assets/{attention-VkuGuoSf.js → attention-q6_xlnT4.js} +2 -2
- package/web-dist/assets/{attention-VkuGuoSf.js.map → attention-q6_xlnT4.js.map} +1 -1
- package/web-dist/assets/{automations-BY88rXFv.js → automations-CyA83OAD.js} +2 -2
- package/web-dist/assets/{automations-BY88rXFv.js.map → automations-CyA83OAD.js.map} +1 -1
- package/web-dist/assets/{board-OzxtnPgh.js → board-BFvNcFhk.js} +2 -2
- package/web-dist/assets/{board-OzxtnPgh.js.map → board-BFvNcFhk.js.map} +1 -1
- package/web-dist/assets/code-DavnmlQN.js +8 -0
- package/web-dist/assets/code-DavnmlQN.js.map +1 -0
- package/web-dist/assets/{describe-CRXLn3O6.js → describe-BirsjlUl.js} +2 -2
- package/web-dist/assets/{describe-CRXLn3O6.js.map → describe-BirsjlUl.js.map} +1 -1
- package/web-dist/assets/dist-C-h8FC-n.js +56 -0
- package/web-dist/assets/dist-C-h8FC-n.js.map +1 -0
- package/web-dist/assets/{enroll-C9-fBIKO.js → enroll-C7UBC20s.js} +2 -2
- package/web-dist/assets/{enroll-C9-fBIKO.js.map → enroll-C7UBC20s.js.map} +1 -1
- package/web-dist/assets/index-ByiuDoG0.css +2 -0
- package/web-dist/assets/{index-B11DJFHW.js → index-IdzOP9GE.js} +3 -3
- package/web-dist/assets/{index-B11DJFHW.js.map → index-IdzOP9GE.js.map} +1 -1
- package/web-dist/assets/{more-BUvq82hI.js → more-syTnSpW3.js} +2 -2
- package/web-dist/assets/{more-BUvq82hI.js.map → more-syTnSpW3.js.map} +1 -1
- package/web-dist/assets/{now-DR2wt1DG.js → now-BqsLS-tK.js} +2 -2
- package/web-dist/assets/{now-DR2wt1DG.js.map → now-BqsLS-tK.js.map} +1 -1
- package/web-dist/assets/settings-DRj7UPtP.js +8 -0
- package/web-dist/assets/settings-DRj7UPtP.js.map +1 -0
- package/web-dist/assets/{send-CFwekPNi.js → sliders-horizontal-S3tmwxsP.js} +2 -2
- package/web-dist/assets/sliders-horizontal-S3tmwxsP.js.map +1 -0
- package/web-dist/assets/{useNow-ChB9z-N6.js → useNow-BQpDYijR.js} +2 -2
- package/web-dist/assets/{useNow-ChB9z-N6.js.map → useNow-BQpDYijR.js.map} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/sw.js +1 -1
- package/web-dist/sw.js.map +1 -1
- package/web-dist/assets/Menu-Cqil5qVF.js +0 -2
- package/web-dist/assets/Menu-Cqil5qVF.js.map +0 -1
- package/web-dist/assets/code-BR4PKt8F.js +0 -8
- package/web-dist/assets/code-BR4PKt8F.js.map +0 -1
- package/web-dist/assets/dist-C4YmDHPG.js +0 -56
- package/web-dist/assets/dist-C4YmDHPG.js.map +0 -1
- package/web-dist/assets/index-DGGOgcLZ.css +0 -2
- package/web-dist/assets/send-CFwekPNi.js.map +0 -1
- package/web-dist/assets/settings-C4tDpSCU.js +0 -7
- package/web-dist/assets/settings-C4tDpSCU.js.map +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// packages/pi-runtime/dist/terminal/holder.js
|
|
2
|
+
import { chmodSync, existsSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { createServer } from "node:net";
|
|
5
|
+
import { constants } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
var require2 = createRequire(import.meta.url);
|
|
9
|
+
var MAX_BUFFER = 2e4;
|
|
10
|
+
var MAX_TO_EXTENSION = 1e3;
|
|
11
|
+
var EXIT_WAIT_MS = 5 * 6e4;
|
|
12
|
+
function ensureSpawnHelper(req = require2) {
|
|
13
|
+
try {
|
|
14
|
+
const root = dirname(req.resolve("node-pty/package.json"));
|
|
15
|
+
for (const p of [
|
|
16
|
+
join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
|
|
17
|
+
join(root, "build", "Release", "spawn-helper")
|
|
18
|
+
]) {
|
|
19
|
+
if (existsSync(p) && (statSync(p).mode & 73) === 0)
|
|
20
|
+
chmodSync(p, 493);
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function signalName(signal) {
|
|
26
|
+
for (const [name, n] of Object.entries(constants.signals))
|
|
27
|
+
if (n === signal)
|
|
28
|
+
return name;
|
|
29
|
+
return String(signal);
|
|
30
|
+
}
|
|
31
|
+
function lineReader(socket, onLine) {
|
|
32
|
+
let buf = "";
|
|
33
|
+
socket.setEncoding("utf8");
|
|
34
|
+
socket.on("data", (chunk) => {
|
|
35
|
+
buf += chunk;
|
|
36
|
+
let nl = buf.indexOf("\n");
|
|
37
|
+
while (nl >= 0) {
|
|
38
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
39
|
+
buf = buf.slice(nl + 1);
|
|
40
|
+
if (line)
|
|
41
|
+
onLine(line);
|
|
42
|
+
nl = buf.indexOf("\n");
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function write(socket, line) {
|
|
47
|
+
if (!socket || socket.destroyed)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
socket.write(`${line}
|
|
51
|
+
`);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function runHolder(cfg) {
|
|
56
|
+
ensureSpawnHelper();
|
|
57
|
+
const pty = require2("node-pty");
|
|
58
|
+
const { Terminal } = require2("@xterm/headless");
|
|
59
|
+
const { SerializeAddon } = require2("@xterm/addon-serialize");
|
|
60
|
+
rmSync(cfg.socketPath, { force: true });
|
|
61
|
+
let control = null;
|
|
62
|
+
let extension = null;
|
|
63
|
+
const viewers = /* @__PURE__ */ new Set();
|
|
64
|
+
const buffer = [];
|
|
65
|
+
const toExtension = [];
|
|
66
|
+
let gap = false;
|
|
67
|
+
let exitInfo = null;
|
|
68
|
+
let shuttingDown = false;
|
|
69
|
+
const term = new Terminal({
|
|
70
|
+
cols: cfg.cols,
|
|
71
|
+
rows: cfg.rows,
|
|
72
|
+
scrollback: 2e3,
|
|
73
|
+
allowProposedApi: true
|
|
74
|
+
});
|
|
75
|
+
const serializer = new SerializeAddon();
|
|
76
|
+
term.loadAddon(serializer);
|
|
77
|
+
const proc = pty.spawn(cfg.piBin, cfg.args, {
|
|
78
|
+
name: "xterm-256color",
|
|
79
|
+
cols: cfg.cols,
|
|
80
|
+
rows: cfg.rows,
|
|
81
|
+
cwd: cfg.cwd,
|
|
82
|
+
env: cfg.env
|
|
83
|
+
});
|
|
84
|
+
const resize = (cols, rows) => {
|
|
85
|
+
if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols < 2 || rows < 2)
|
|
86
|
+
return;
|
|
87
|
+
if (exitInfo)
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
proc.resize(cols, rows);
|
|
91
|
+
term.resize(cols, rows);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const toControl = (line) => {
|
|
96
|
+
if (control && !control.destroyed) {
|
|
97
|
+
write(control, line);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
buffer.push(line);
|
|
101
|
+
if (buffer.length > MAX_BUFFER) {
|
|
102
|
+
buffer.shift();
|
|
103
|
+
gap = true;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const sendExtension = (line) => {
|
|
107
|
+
if (extension && !extension.destroyed) {
|
|
108
|
+
write(extension, line);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
toExtension.push(line);
|
|
112
|
+
if (toExtension.length > MAX_TO_EXTENSION)
|
|
113
|
+
toExtension.shift();
|
|
114
|
+
};
|
|
115
|
+
const shutdown = () => {
|
|
116
|
+
if (shuttingDown)
|
|
117
|
+
return;
|
|
118
|
+
shuttingDown = true;
|
|
119
|
+
for (const v of viewers)
|
|
120
|
+
v.end();
|
|
121
|
+
try {
|
|
122
|
+
server.close();
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
rmSync(cfg.socketPath, { force: true });
|
|
126
|
+
setTimeout(() => process.exit(0), 100).unref();
|
|
127
|
+
};
|
|
128
|
+
proc.onData((d) => {
|
|
129
|
+
term.write(d);
|
|
130
|
+
if (viewers.size === 0)
|
|
131
|
+
return;
|
|
132
|
+
const line = JSON.stringify({ t: "out", d: Buffer.from(d, "utf8").toString("base64") });
|
|
133
|
+
for (const v of viewers)
|
|
134
|
+
write(v, line);
|
|
135
|
+
});
|
|
136
|
+
proc.onExit(({ exitCode, signal }) => {
|
|
137
|
+
exitInfo = { code: exitCode ?? null, signal: signal ? signalName(signal) : null };
|
|
138
|
+
const closed = JSON.stringify({ t: "closed", reason: "Pi exited" });
|
|
139
|
+
for (const v of viewers) {
|
|
140
|
+
write(v, closed);
|
|
141
|
+
v.end();
|
|
142
|
+
}
|
|
143
|
+
viewers.clear();
|
|
144
|
+
if (control && !control.destroyed) {
|
|
145
|
+
write(control, JSON.stringify({ type: "holder_exit", ...exitInfo }));
|
|
146
|
+
setTimeout(shutdown, 1e3).unref();
|
|
147
|
+
} else {
|
|
148
|
+
setTimeout(shutdown, EXIT_WAIT_MS).unref();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
const server = createServer((socket) => {
|
|
152
|
+
let role = null;
|
|
153
|
+
socket.on("error", () => {
|
|
154
|
+
});
|
|
155
|
+
lineReader(socket, (line) => {
|
|
156
|
+
if (role === null) {
|
|
157
|
+
let hello;
|
|
158
|
+
try {
|
|
159
|
+
hello = JSON.parse(line);
|
|
160
|
+
} catch {
|
|
161
|
+
socket.destroy();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (hello?.role === "extension") {
|
|
165
|
+
role = "extension";
|
|
166
|
+
if (extension && extension !== socket)
|
|
167
|
+
extension.destroy();
|
|
168
|
+
extension = socket;
|
|
169
|
+
for (const l of toExtension.splice(0))
|
|
170
|
+
write(socket, l);
|
|
171
|
+
} else if (hello?.role === "control") {
|
|
172
|
+
role = "control";
|
|
173
|
+
if (control && control !== socket)
|
|
174
|
+
control.destroy();
|
|
175
|
+
control = socket;
|
|
176
|
+
write(socket, JSON.stringify({ type: "holder_status", piPid: proc.pid }));
|
|
177
|
+
if (gap) {
|
|
178
|
+
write(socket, JSON.stringify({ type: "holder_gap" }));
|
|
179
|
+
gap = false;
|
|
180
|
+
}
|
|
181
|
+
for (const l of buffer.splice(0))
|
|
182
|
+
write(socket, l);
|
|
183
|
+
if (exitInfo) {
|
|
184
|
+
write(socket, JSON.stringify({ type: "holder_exit", ...exitInfo }));
|
|
185
|
+
setTimeout(shutdown, 1e3).unref();
|
|
186
|
+
}
|
|
187
|
+
if (extension && !extension.destroyed) {
|
|
188
|
+
write(extension, JSON.stringify({ type: "control_attached" }));
|
|
189
|
+
}
|
|
190
|
+
} else if (hello?.role === "viewer") {
|
|
191
|
+
role = "viewer";
|
|
192
|
+
if (exitInfo) {
|
|
193
|
+
write(socket, JSON.stringify({ t: "closed", reason: "Pi exited" }));
|
|
194
|
+
socket.end();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
viewers.add(socket);
|
|
198
|
+
resize(Number(hello.cols), Number(hello.rows));
|
|
199
|
+
setImmediate(() => {
|
|
200
|
+
try {
|
|
201
|
+
const screen = serializer.serialize();
|
|
202
|
+
write(socket, JSON.stringify({ t: "out", d: Buffer.from(screen, "utf8").toString("base64") }));
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
socket.destroy();
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (role === "extension") {
|
|
212
|
+
let rec = null;
|
|
213
|
+
try {
|
|
214
|
+
rec = JSON.parse(line);
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
if (rec?.type === "holder_keys") {
|
|
218
|
+
if (!exitInfo && typeof rec.data === "string")
|
|
219
|
+
proc.write(rec.data);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
toControl(line);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (role === "control") {
|
|
226
|
+
let rec = null;
|
|
227
|
+
try {
|
|
228
|
+
rec = JSON.parse(line);
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
if (rec?.type === "holder_kill") {
|
|
232
|
+
try {
|
|
233
|
+
proc.kill(typeof rec.signal === "string" ? rec.signal : "SIGTERM");
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
sendExtension(line);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let msg;
|
|
242
|
+
try {
|
|
243
|
+
msg = JSON.parse(line);
|
|
244
|
+
} catch {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (msg?.t === "in" && typeof msg.d === "string") {
|
|
248
|
+
if (!exitInfo)
|
|
249
|
+
proc.write(Buffer.from(msg.d, "base64").toString("utf8"));
|
|
250
|
+
} else if (msg?.t === "resize") {
|
|
251
|
+
resize(Number(msg.cols), Number(msg.rows));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
socket.on("close", () => {
|
|
255
|
+
if (role === "viewer")
|
|
256
|
+
viewers.delete(socket);
|
|
257
|
+
else if (role === "control" && control === socket)
|
|
258
|
+
control = null;
|
|
259
|
+
else if (role === "extension" && extension === socket)
|
|
260
|
+
extension = null;
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
server.listen(cfg.socketPath, () => {
|
|
264
|
+
try {
|
|
265
|
+
chmodSync(cfg.socketPath, 384);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
process.on("uncaughtException", (err) => {
|
|
270
|
+
process.stderr.write(`switchyard holder: ${err.stack ?? err.message}
|
|
271
|
+
`);
|
|
272
|
+
try {
|
|
273
|
+
proc.kill();
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
rmSync(cfg.socketPath, { force: true });
|
|
277
|
+
process.exit(1);
|
|
278
|
+
});
|
|
279
|
+
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
280
|
+
process.on(sig, () => {
|
|
281
|
+
try {
|
|
282
|
+
proc.kill(sig);
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function isMain() {
|
|
289
|
+
if (process.env.SWITCHYARD_HOLDER_CONFIG === void 0 || !process.argv[1])
|
|
290
|
+
return false;
|
|
291
|
+
try {
|
|
292
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (isMain()) {
|
|
298
|
+
const raw = process.env.SWITCHYARD_HOLDER_CONFIG ?? "";
|
|
299
|
+
delete process.env.SWITCHYARD_HOLDER_CONFIG;
|
|
300
|
+
runHolder(JSON.parse(raw));
|
|
301
|
+
}
|
|
302
|
+
export {
|
|
303
|
+
ensureSpawnHelper,
|
|
304
|
+
runHolder
|
|
305
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spencerboucher/switchyard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Self-hosted hub for Pi coding agents: run them on your machines, drive them from your phone, browser or terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,20 +34,23 @@
|
|
|
34
34
|
"@fastify/static": "^10.1.7",
|
|
35
35
|
"@fastify/websocket": "^11.3.4",
|
|
36
36
|
"@simplewebauthn/server": "^14.0.3",
|
|
37
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
38
|
+
"@xterm/headless": "^6.0.0",
|
|
37
39
|
"commander": "^15.0.0",
|
|
38
40
|
"croner": "^11.0.0",
|
|
39
41
|
"fastify": "^5.12.7",
|
|
40
|
-
"ink": "^8.0.0",
|
|
41
42
|
"openid-client": "^6.8.8",
|
|
42
43
|
"pino": "^10.4.0",
|
|
43
44
|
"qrcode": "^1.5.4",
|
|
44
|
-
"react": "^19.3.1",
|
|
45
45
|
"semver": "^7.8.5",
|
|
46
46
|
"ulid": "^3.0.2",
|
|
47
47
|
"web-push": "^3.6.7",
|
|
48
48
|
"ws": "^8.22.0",
|
|
49
49
|
"zod": "^4.6.7"
|
|
50
50
|
},
|
|
51
|
+
"optionalDependencies": {
|
|
52
|
+
"node-pty": "^1.1.0"
|
|
53
|
+
},
|
|
51
54
|
"publishConfig": {
|
|
52
55
|
"access": "public"
|
|
53
56
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as e,f as t,i as n,m as r,n as i,p as a,t as o}from"./jsx-runtime-D13aM9HY.js";import{A as s,B as c,E as l,G as u,H as d,P as f,U as p,V as m,W as ee,c as h,g,h as te,i as ne,l as _,n as v,o as y,p as b,r as re,t as x}from"./states-DrTQuYXw.js";import{t as ie}from"./arrow-left-C6YWM6y6.js";import{t as S}from"./calendar-clock-B8zE4JsX.js";import{t as C}from"./copy-oWgGHmkS.js";import{t as w}from"./key-round-VDbE9E4X.js";import{A as T,C as ae,D as E,E as oe,F as se,M as D,N as O,O as k,S as ce,T as A,a as j,b as le,d as ue,f as M,g as de,h as fe,i as pe,j as N,k as P,l as me,m as F,n as he,o as I,p as L,r as ge,s as R,t as z,u as _e,v as B,x as V,y as H}from"./describe-CRXLn3O6.js";import{t as ve}from"./play-CjoDg3ws.js";import{t as U}from"./plus-BLP-wIYD.js";import{t as ye}from"./save-DEvZPWlS.js";import{t as be}from"./trash-C6X2OLrI.js";import{E as W,F as G,I as xe,P as Se,_ as K,c as q,d as Ce,g as we,n as Te,w as Ee}from"./index-B11DJFHW.js";import{n as J,t as Y}from"./TextArea-BsQHOQMe.js";import{i as De,r as Oe,s as ke}from"./dist-C4YmDHPG.js";import{t as Ae}from"./SegmentedControl-ChdVilXb.js";import{i as je,t as Me}from"./Overlay-B9h5oAe6.js";var Ne={name:`scroll-text`,size:24,node:[[`path`,{d:`M15 12h-5`,key:`r7krc0`}],[`path`,{d:`M15 8h-5`,key:`1khuty`}],[`path`,{d:`M19 17V5a2 2 0 0 0-2-2H4`,key:`zz82l3`}],[`path`,{d:`M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3`,key:`1ph1d7`}]]};Ne.node;var Pe=i(Ne),X=e(r(),1),Z=o(),Fe=e=>`/tasks/${encodeURIComponent(e)}`;function Q(e){return u(e)&&(e.status===404||e.status===405||e.status===501)}function Ie({summary:e,errorCount:t}){return(0,Z.jsxs)(k,{title:`Summary`,testId:`automation-summary`,children:[(0,Z.jsx)(`p`,{className:`text-sm break-words`,children:e}),t>0&&(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-sm text-danger`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-4 shrink-0`}),t===1?`1 field needs attention`:`${t} fields need attention`]})]})}async function Le(e,t,n){return e&&!n?m(N(e,`/preview`)):p(`/api/automations/preview`,t)}function Re({automationId:e,dirty:t,getInput:n,currentKey:r}){let[i,a]=(0,X.useState)({status:`idle`}),o=async()=>{let r=n();if(!r)q.error(`Fix the highlighted fields to preview the task`);else{a({status:`loading`});try{let n=await Le(e,r,t);a({status:`done`,preview:n,key:JSON.stringify(r)})}catch(e){a(Q(e)?{status:`unsupported`}:{status:`error`,error:e})}}};return(0,Z.jsxs)(k,{title:`Preview`,description:`See the task an occurrence would create, and when it would run next.`,testId:`automation-preview`,children:[(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{icon:(0,Z.jsx)(W,{className:`size-4`}),busy:i.status===`loading`,onClick:()=>void o(),children:`Preview resulting task`})}),i.status===`unsupported`&&(0,Z.jsx)(h,{capability:`Task preview`,reason:`This coordinator does not offer automation previews.`,fallback:`Read the summary above, or save and use Run now to see a real task.`}),i.status===`error`&&(0,Z.jsx)(v,{compact:!0,title:`Could not preview`,error:i.error,onRetry:()=>void o()}),i.status===`done`&&(0,Z.jsx)(ze,{preview:i.preview,stale:i.key!==r})]})}function ze({preview:e,stale:t}){let{task:n}=e;return(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-3`,children:[t&&(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-xs text-warning`,role:`status`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-3.5 shrink-0`}),`The form changed since this preview. Preview again to refresh it.`]}),(0,Z.jsx)(`p`,{className:`text-sm break-words text-muted`,children:e.summary}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-2 rounded-md border border-border bg-surface-2 p-3`,children:[(0,Z.jsxs)(`div`,{className:`flex min-w-0 items-start justify-between gap-2`,children:[(0,Z.jsx)(`h3`,{className:`min-w-0 font-medium break-words`,children:n.title}),(0,Z.jsxs)(`span`,{className:`inline-flex shrink-0 items-center gap-1 text-xs text-muted`,children:[(0,Z.jsx)(E,{priority:n.priority,className:`size-3.5`}),ke[n.priority]]})]}),n.objective?(0,Z.jsx)(`p`,{className:`text-sm break-words whitespace-pre-wrap`,children:n.objective}):(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No objective.`}),n.acceptanceCriteria.length>0&&(0,Z.jsxs)(`div`,{children:[(0,Z.jsxs)(`h4`,{className:`flex items-center gap-1 text-xs font-medium text-muted`,children:[(0,Z.jsx)(Ee,{"aria-hidden":!0,className:`size-3.5`}),`Acceptance criteria`]}),(0,Z.jsx)(`ul`,{className:`mt-1 list-disc pl-5 text-sm`,children:n.acceptanceCriteria.map((e,t)=>(0,Z.jsx)(`li`,{className:`break-words`,children:e},t))})]})]}),(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`h3`,{className:`text-sm font-medium`,children:`Next occurrences`}),e.next.length===0?(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No scheduled times: this trigger fires on demand, on a webhook, or when a task is done.`}):(0,Z.jsx)(`ol`,{className:`mt-1 flex flex-col gap-0.5 text-sm`,children:e.next.slice(0,5).map(e=>(0,Z.jsxs)(`li`,{className:`flex flex-wrap gap-x-2`,children:[(0,Z.jsx)(`time`,{dateTime:e,children:L(e)}),(0,Z.jsx)(`span`,{className:`text-muted`,children:b(e)})]},e))})]})]})}async function Be(e,t){try{await navigator.clipboard.writeText(e),q.success(`${t} copied`)}catch{q.error(`Could not copy the ${t.toLowerCase()}; select it and copy manually`)}}function Ve({label:e,value:t,mono:n=!0}){return(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1`,children:[(0,Z.jsx)(`span`,{className:`text-xs font-medium text-muted`,children:e}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 items-start gap-2`,children:[(0,Z.jsx)(`code`,{className:`min-w-0 flex-1 rounded border border-border bg-surface px-2 py-1.5 text-xs break-all select-all ${n?`font-mono`:``}`,children:t}),(0,Z.jsx)(_,{size:`sm`,icon:(0,Z.jsx)(C,{className:`size-3.5`}),onClick:()=>void Be(t,e),"aria-label":`Copy ${e.toLowerCase()}`,children:`Copy`})]})]})}function He({automation:e}){let[t,n]=(0,X.useState)(!1),[r,i]=(0,X.useState)(!1),[a,o]=(0,X.useState)(null),[s,c]=(0,X.useState)(!1),l=e.webhookTokenSet||a!==null,u=async()=>{i(!1),n(!0);try{let t=await p(N(e.id,`/webhook-token`)),n=Oe.safeParse(t);if(!n.success)throw Error(`The coordinator returned an unexpected token response`);let r=new URL(n.data.url,window.location.origin).toString();o({token:n.data.token,url:r}),q.success(l?`Webhook token rotated`:`Webhook token created`)}catch(e){Q(e)?c(!0):q.error(`Could not issue a webhook token: ${ee(e)}`)}finally{n(!1)}};return(0,Z.jsxs)(k,{title:`Webhook`,description:`POST JSON to the URL with the token as a Bearer header. Filters decide which payloads fire.`,testId:`webhook-token`,children:[(0,Z.jsx)(`p`,{className:`flex items-center gap-1.5 text-sm`,children:l?(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(xe,{"aria-hidden":!0,className:`size-4 shrink-0 text-success`}),`A token is set`]}):(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(G,{"aria-hidden":!0,className:`size-4 shrink-0 text-muted`}),`No token yet: webhook calls are rejected until you create one`]})}),s?(0,Z.jsx)(h,{capability:`Webhook tokens`,reason:`This coordinator does not issue webhook tokens.`}):(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{icon:(0,Z.jsx)(w,{className:`size-4`}),busy:t,onClick:()=>l?i(!0):void u(),children:l?`Rotate token`:`Create token`})}),a&&(0,Z.jsxs)(`div`,{role:`alert`,className:`flex min-w-0 flex-col gap-3 rounded-md border border-warning bg-warning-soft p-3`,children:[(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-sm font-medium`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-4 shrink-0 text-warning`}),`This token is shown once. Copy it now.`]}),(0,Z.jsx)(Ve,{label:`Token`,value:a.token}),(0,Z.jsx)(Ve,{label:`URL`,value:a.url}),(0,Z.jsx)(Ve,{label:`Example`,value:I(a.token,a.url)}),(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{size:`sm`,onClick:()=>o(null),children:`I have copied it`})})]}),(0,Z.jsx)(Me,{open:r,onClose:()=>i(!1),title:`Rotate webhook token?`,description:`The current token stops working immediately. Update every sender with the new token.`,footer:(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{onClick:()=>i(!1),children:`Cancel`}),(0,Z.jsx)(_,{variant:`danger`,onClick:()=>void u(),children:`Rotate token`})]})})]})}var Ue=[];function We({automationId:e,refreshKey:t,onRun:r}){let i=f(t=>t.occurrences[e]??Ue),a=f(e=>e.tasks),[o,s]=(0,X.useState)({status:`loading`}),c=(0,X.useCallback)(async t=>{s(e=>e.status===`done`?e:{status:`loading`});try{let n=await m(N(e,`/history`),{},t);s({status:`done`,items:B(n)})}catch(e){if(t?.aborted)return;s(Q(e)?{status:`unsupported`}:{status:`error`,error:e})}},[e]);(0,X.useEffect)(()=>{let e=new AbortController;return c(e.signal),()=>e.abort()},[c,t]);let l=de(o.status===`done`?o.items:[],i);return(0,Z.jsxs)(k,{title:`History`,description:`Occurrences of this automation. Routine successes are hidden from the board and appear only here.`,testId:`automation-history`,children:[o.status===`unsupported`&&(0,Z.jsx)(h,{capability:`Automation history`,reason:`This coordinator does not serve stored history; only occurrences seen live in this session are shown.`}),o.status===`error`&&(0,Z.jsx)(v,{compact:!0,title:`Could not load history`,error:o.error,onRetry:()=>void c()}),o.status===`loading`&&l.length===0?(0,Z.jsx)(y,{label:`Loading history…`,className:`py-4`}):l.length===0&&o.status!==`error`?(0,Z.jsx)(x,{className:`py-4`,icon:(0,Z.jsx)(Pe,{className:`size-6`}),title:`No occurrences yet`,description:`Occurrences appear here when the trigger fires or you run it.`,action:{label:`Run now`,onClick:r,icon:(0,Z.jsx)(ve,{className:`size-4`})}}):(0,Z.jsx)(`ol`,{className:`flex flex-col divide-y divide-border`,children:l.map(e=>{let t=H(e),r=e.taskId?a[e.taskId]:void 0,i=[e.reason,e.outcome].filter(Boolean).join(` · `);return(0,Z.jsxs)(`li`,{className:`flex min-w-0 flex-col gap-1 py-2 first:pt-0 last:pb-0`,children:[(0,Z.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-2 gap-y-1`,children:[(0,Z.jsx)(Ce,{kind:`occurrence`,value:e.state,size:`sm`}),(0,Z.jsx)(`time`,{dateTime:t,className:`text-sm`,children:L(t)}),(0,Z.jsx)(`span`,{className:`text-xs text-muted`,children:b(t)})]}),i&&(0,Z.jsx)(`p`,{className:`text-sm break-words text-muted`,children:i}),e.taskId&&(0,Z.jsxs)(n,{to:Fe(e.taskId),className:`tap inline-flex min-w-0 items-center gap-1 self-start text-sm text-accent hover:underline`,children:[(0,Z.jsx)(`span`,{className:`min-w-0 truncate`,children:r?`Task: ${r.title}`:`Open task`}),r?.hiddenFromBoard&&(0,Z.jsx)(`span`,{className:`shrink-0 text-xs text-muted`,children:`(hidden from board)`})]})]},e.id)})})]})}var Ge=`/automations`,Ke=e=>`/automations/${encodeURIComponent(e)}`,qe=`sy:automation-draft`;function Je(){let{automationId:e=`new`}=a();return(0,Z.jsx)(Ye,{automationId:e},e)}function Ye({automationId:e}){let t=e===`new`,n=f(e=>e.hydrated),r=O(t?void 0:e),[i,a]=(0,X.useState)({status:`idle`}),o=(0,X.useCallback)(async()=>{a({status:`loading`});try{let t=await m(N(e)),n=De.safeParse(t);if(!n.success){a({status:`fatal`,error:Error(`The coordinator returned this automation in an unexpected format.`)});return}D.getState().put(n.data),a({status:`idle`})}catch(e){u(e)&&e.status===404?a({status:`notfound`}):a({status:`error`,error:e})}},[e]),s=!t&&!r;return(0,X.useEffect)(()=>{s&&i.status===`idle`&&o()},[s,i.status,o]),t?(0,Z.jsx)($e,{automation:null}):r?(0,Z.jsx)($e,{automation:r}):i.status===`notfound`?(0,Z.jsx)(x,{icon:(0,Z.jsx)(K,{className:`size-8`}),title:`Automation not found`,description:`It may have been deleted, or the link is wrong.`,action:{label:`Back to automations`,to:Ge}}):i.status===`error`?(0,Z.jsx)(v,{title:`Could not load this automation`,error:i.error,onRetry:()=>void o()}):i.status===`fatal`?(0,Z.jsx)(re,{error:i.error}):(0,Z.jsx)(y,{label:n?`Loading automation…`:`Loading…`})}function Xe(e){let t=ue({projectId:e,timezone:fe()});try{let e=sessionStorage.getItem(qe);if(!e)return t;let n=JSON.parse(e);return!n||typeof n!=`object`?t:{...t,...n,filters:Array.isArray(n.filters)?n.filters:[]}}catch{return t}}function Ze(e){try{e?sessionStorage.setItem(qe,JSON.stringify(e)):sessionStorage.removeItem(qe)}catch{}}var $=e=>JSON.stringify(_e(e));function Qe(e){if(!u(e))return{};let t=e.details,n=Array.isArray(t)?t:t&&typeof t==`object`&&Array.isArray(t.issues)?t.issues:[],r={};for(let e of n){if(!e||typeof e!=`object`)continue;let{path:t,message:n}=e;if(!Array.isArray(t)||typeof n!=`string`)continue;let i=M(t);i!==`form`&&!r[i]&&(r[i]=n)}return r}function $e({automation:e}){let r=t(),i=Te(),a=i===`desktop`,o=s(),u=l(),m=f(e=>e.hosts),h=f(e=>e.profiles),v=f(e=>e.tasks),y=f(e=>e.projects),b=e===null,re=(0,X.useId)(),x=(0,X.useRef)(null),[S,C]=(0,X.useState)(()=>e?me(e):Xe(o[0]?.id)),[w,T]=(0,X.useState)(()=>$(S)),[E,O]=(0,X.useState)(!1),[k,A]=(0,X.useState)({}),[j,M]=(0,X.useState)(!1),[de,pe]=(0,X.useState)(!1),[P,F]=(0,X.useState)(!1),[he,I]=(0,X.useState)(!1),[L,ge]=(0,X.useState)(0),R=(0,X.useRef)(e?.updatedAt),z=$(S),B=z!==w;(0,X.useEffect)(()=>{if(!e||e.updatedAt===R.current)return;R.current=e.updatedAt;let t=me(e),n=$(t);z===w?(C(t),I(!1)):n!==z&&I(!0),T(n)},[e,z,w]),(0,X.useEffect)(()=>{b&&Ze(S)},[b,S]),(0,X.useEffect)(()=>{b&&!S.projectId&&o[0]&&C(e=>e.projectId?e:{...e,projectId:o[0].id})},[b,S.projectId,o]);let V=(0,X.useCallback)((e,t)=>{C(n=>({...n,[e]:t})),A(e=>Object.keys(e).length?{}:e)},[]),H=(0,X.useMemo)(()=>ce(S),[S]),U={...k,...E?H.errors:{}},W=e=>U[e],G=y[S.projectId],xe=S.hostId||G?.defaultHostId||null,K=xe?m[xe]:void 0,Ce=S.profileId||G?.defaultProfileId||null,Ee=(0,X.useCallback)(e=>v[e]?.title,[v]),J=(0,X.useMemo)(()=>le(_e(S),{projectName:G?.name,hostName:S.hostId?m[S.hostId]?.name??null:null,profileName:S.profileId?h[S.profileId]?.name??null:null,taskTitle:Ee}),[S,G?.name,m,h,Ee]),Y=()=>{requestAnimationFrame(()=>{x.current?.querySelector(`[aria-invalid="true"]`)?.focus()})},De=()=>(O(!0),H.input||Y(),H.input),Oe=async()=>{let t=De();if(!t)q.error(`Fix the highlighted fields before saving`);else{M(!0);try{if(b){let e=await p(`/api/automations`,t);if(!e||typeof e!=`object`||!e.id)throw Error(`The coordinator did not return the new automation`);D.getState().put(e),Ze(null),q.success(`Created ${e.name}`),r(Ke(e.id),{replace:!0});return}let n=await d(N(e.id),t);if(n&&typeof n==`object`&&n.id){D.getState().put(n),R.current=n.updatedAt;let e=me(n);C(e),T($(e))}else T(z);I(!1),O(!1),q.success(`Saved ${t.name}`)}catch(e){let t=Qe(e);A(t),Object.keys(t).length&&Y(),q.error(`Could not save: ${ee(e)}. Your changes are kept.`)}finally{M(!1)}}},ke=async()=>{if(e){pe(!0);try{await c(N(e.id)),F(!1),q.success(`Deleted ${e.name}`),r(Ge,{replace:!0}),D.getState().remove(e.id)}catch(e){q.error(`Could not delete: ${ee(e)}`)}finally{pe(!1)}}},{busy:Ae,run:je}=se(()=>ge(e=>e+1)),Ne=e?!!Ae[e.id]:!1,Pe=()=>e&&void je(e),Fe=()=>{let e=ue({projectId:o[0]?.id,timezone:fe()});C(e),O(!1),Ze(null)},Q=b?`New automation`:e.name,Le=(0,Z.jsxs)(Z.Fragment,{children:[!b&&(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{icon:(0,Z.jsx)(ve,{className:`size-4`}),busy:Ne,onClick:Pe,title:B?`Runs the saved version`:void 0,children:`Run now`}),(0,Z.jsx)(_,{variant:`ghost`,icon:(0,Z.jsx)(be,{className:`size-4`}),onClick:()=>F(!0),className:`text-danger`,children:`Delete`})]}),b&&B&&(0,Z.jsx)(_,{variant:`ghost`,icon:(0,Z.jsx)(we,{className:`size-4`}),onClick:Fe,children:`Discard draft`})]}),ze=e=>(0,Z.jsx)(_,{type:`submit`,form:re,variant:`primary`,busy:j,block:e,icon:(0,Z.jsx)(ye,{className:`size-4`}),children:b?`Create automation`:`Save`}),Be=(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(Ie,{summary:J,errorCount:E?Object.keys(H.errors).length:0}),(0,Z.jsx)(Re,{automationId:e?.id??null,dirty:B,getInput:De,currentKey:JSON.stringify(H.input)}),e&&e.trigger.kind===`webhook`&&(0,Z.jsx)(He,{automation:e}),e&&(0,Z.jsx)(We,{automationId:e.id,refreshKey:L,onRun:Pe})]});return(0,Z.jsxs)(`div`,{"data-testid":`automation-editor`,className:`mx-auto flex w-full max-w-6xl min-w-0 flex-col gap-4 px-4 pt-4 md:px-6`,children:[(0,Z.jsxs)(`header`,{className:`flex min-w-0 flex-col gap-2`,children:[(0,Z.jsxs)(n,{to:Ge,className:`tap inline-flex items-center gap-1 self-start rounded text-sm text-muted hover:text-text`,children:[(0,Z.jsx)(ie,{"aria-hidden":!0,className:`size-4`}),`Automations`]}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center justify-between gap-2`,children:[(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,Z.jsx)(`h1`,{className:`min-w-0 text-lg font-semibold break-words`,children:Q}),B&&(0,Z.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-xs text-muted`,children:[(0,Z.jsx)(Se,{"aria-hidden":!0,className:`size-3.5`}),`Unsaved changes`]})]}),(0,Z.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[Le,a&&ze()]})]})]}),(0,Z.jsx)(ae,{}),K&&K.connectivity!==`connected`&&(0,Z.jsx)(ne,{compact:!0,hostName:K.name,connectivity:K.connectivity,lastSeenAt:K.lastSeenAt,detail:`Occurrences are still recorded; their tasks wait until the host is reachable.`}),he&&(0,Z.jsxs)(`p`,{role:`status`,className:`flex items-start gap-1.5 rounded-md border border-border bg-warning-soft px-3 py-2 text-sm`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`mt-0.5 size-4 shrink-0 text-warning`}),`This automation changed elsewhere. Saving overwrites those changes with yours.`]}),(0,Z.jsxs)(`div`,{className:te(`grid min-w-0 gap-4`,a?`grid-cols-[minmax(0,1fr)_minmax(320px,400px)] items-start`:`grid-cols-1`),children:[(0,Z.jsxs)(`form`,{id:re,ref:x,noValidate:!0,"aria-label":Q,onSubmit:e=>{e.preventDefault(),Oe()},className:`flex min-w-0 flex-col gap-4`,children:[(0,Z.jsx)(et,{draft:S,update:V,err:W,projects:o}),(0,Z.jsx)(rt,{draft:S,update:V,err:W,tasks:v,projectsById:y,savedAutomation:e,segmented:i!==`mobile`}),(0,Z.jsx)(it,{draft:S,setDraft:C,err:W}),(0,Z.jsx)(at,{draft:S,update:V,err:W}),(0,Z.jsx)(st,{draft:S,update:V,err:W,hosts:u,profiles:Object.values(h).sort((e,t)=>e.name.localeCompare(t.name)),defaultHostName:G?.defaultHostId?m[G.defaultHostId]?.name:void 0,defaultProfileName:G?.defaultProfileId?h[G.defaultProfileId]?.name:void 0,selectedProfileMissing:!!Ce&&!h[Ce]}),(0,Z.jsx)(ct,{draft:S,update:V}),(0,Z.jsx)(lt,{draft:S,update:V,err:W}),(0,Z.jsx)(ut,{draft:S,update:V,err:W,projectChecks:G?.checks}),U.form&&(0,Z.jsx)(oe,{children:U.form})]}),(0,Z.jsx)(`aside`,{"aria-label":`Summary, preview and history`,className:`flex min-w-0 flex-col gap-4`,children:Be})]}),a?(0,Z.jsx)(`div`,{className:`h-4`}):(0,Z.jsxs)(`div`,{className:`sticky bottom-0 z-10 -mx-4 flex items-center gap-3 border-t border-border bg-surface px-4 py-2 safe-pb md:-mx-6 md:px-6`,children:[(0,Z.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-sm text-muted`,children:B?`Unsaved changes`:b?`Not saved yet`:`All changes saved`}),ze()]}),(0,Z.jsx)(Me,{open:P,onClose:()=>F(!1),title:`Delete ${e?.name??`automation`}?`,description:`Future occurrences stop. Tasks it already created stay on the board; its history is removed.`,footer:(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{onClick:()=>F(!1),children:`Cancel`}),(0,Z.jsx)(_,{variant:`danger`,busy:de,icon:(0,Z.jsx)(be,{className:`size-4`}),onClick:()=>void ke(),children:`Delete automation`})]})})]})}function et({draft:e,update:t,err:r,projects:i}){return(0,Z.jsxs)(k,{title:`Basics`,children:[(0,Z.jsx)(A,{label:`Name`,error:r(`name`),children:n=>(0,Z.jsx)(J,{...n,value:e.name,onChange:e=>t(`name`,e.target.value),placeholder:`Daily dependency check`,autoComplete:`off`})}),(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Project`,error:r(`projectId`),hint:i.length===0?(0,Z.jsxs)(Z.Fragment,{children:[`No projects yet. `,(0,Z.jsx)(n,{to:`/settings/projects`,children:`Add a project in Settings`}),`.`]}):void 0,children:n=>(0,Z.jsxs)(P,{...n,value:e.projectId,onChange:e=>t(`projectId`,e.target.value),children:[(0,Z.jsx)(`option`,{value:``,children:`Choose a project`}),i.map(e=>(0,Z.jsx)(`option`,{value:e.id,children:e.name},e.id))]})}),(0,Z.jsx)(A,{label:`Status`,hint:`Disabled automations keep their history and can still run manually.`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.enabled,onChange:e=>t(`enabled`,e),label:`Enabled`,onText:`Enabled`,offText:`Disabled`,className:`self-start`})})]})]})}var tt=[`manual`,`once`,`cron`,`webhook`,`dependency`],nt={manual:`Runs only when you press Run now (or from the CLI).`,once:`Runs a single time at the chosen moment.`,cron:`Runs on a timezone-aware cron schedule.`,webhook:`Runs when an authenticated JSON POST arrives at this automation's webhook URL.`,dependency:`Runs when the chosen task is done.`};function rt({draft:e,update:t,err:n,tasks:r,projectsById:i,savedAutomation:a,segmented:o}){let s=e.triggerKind===`cron`?V(e.cronExpr):null,c=e.triggerKind===`cron`&&!s?R(e.cronExpr,e.cronTimezone.trim()||void 0)??`Valid expression (no plain-language description available)`:null,l=(0,X.useMemo)(()=>{try{return Intl.supportedValuesOf?.(`timeZone`)}catch{return}},[]),u=(0,X.useId)(),d=fe(),f=(0,X.useMemo)(()=>Object.values(r).filter(t=>t.state!==`canceled`||t.id===e.dependencyTaskId).sort((e,t)=>e.title.localeCompare(t.title)),[r,e.dependencyTaskId]),p=e.triggerKind===`once`?F(e.onceAt):``;return(0,Z.jsxs)(k,{title:`Trigger`,description:nt[e.triggerKind],children:[o?(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1`,children:[(0,Z.jsx)(`span`,{"aria-hidden":!0,className:`text-sm font-medium`,children:`Trigger kind`}),(0,Z.jsx)(`div`,{className:`overflow-x-auto`,children:(0,Z.jsx)(Ae,{label:`Trigger kind`,value:e.triggerKind,onChange:e=>t(`triggerKind`,e),options:tt.map(e=>({value:e,label:j[e]}))})})]}):(0,Z.jsx)(A,{label:`Trigger kind`,children:n=>(0,Z.jsx)(P,{...n,value:e.triggerKind,onChange:e=>t(`triggerKind`,e.target.value),children:tt.map(e=>(0,Z.jsx)(`option`,{value:e,children:j[e]},e))})}),e.triggerKind===`once`&&(0,Z.jsx)(A,{label:`Run at`,error:n(`onceAt`),hint:`Local time (${d})${p?`; saved as ${p}`:``}.`,children:n=>(0,Z.jsx)(J,{...n,type:`datetime-local`,value:e.onceAt,onChange:e=>t(`onceAt`,e.target.value)})}),e.triggerKind===`cron`&&(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Cron expression`,error:n(`cronExpr`)??s??void 0,hint:`minute hour day-of-month month day-of-week, e.g. 0 9 * * 1-5`,children:n=>(0,Z.jsx)(J,{...n,"aria-invalid":s?!0:n[`aria-invalid`],value:e.cronExpr,onChange:e=>t(`cronExpr`,e.target.value),className:`font-mono`,autoComplete:`off`,spellCheck:!1,autoCapitalize:`off`})}),(0,Z.jsx)(A,{label:`Timezone`,error:n(`cronTimezone`),hint:e.cronTimezone.trim()===d?`IANA timezone name; daylight saving is handled.`:(0,Z.jsxs)(`button`,{type:`button`,className:`tap text-accent hover:underline`,onClick:()=>t(`cronTimezone`,d),children:[`Use this device's timezone (`,d,`)`]}),children:n=>(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(J,{...n,list:l?u:void 0,value:e.cronTimezone,onChange:e=>t(`cronTimezone`,e.target.value),autoComplete:`off`,spellCheck:!1,autoCapitalize:`off`}),l&&(0,Z.jsx)(`datalist`,{id:u,children:l.map(e=>(0,Z.jsx)(`option`,{value:e},e))})]})}),c&&(0,Z.jsxs)(`p`,{className:`flex items-start gap-1.5 text-sm sm:col-span-2`,"aria-live":`polite`,"data-testid":`cron-description`,children:[(0,Z.jsx)(S,{"aria-hidden":!0,className:`mt-0.5 size-4 shrink-0 text-muted`}),(0,Z.jsx)(`span`,{className:`min-w-0 break-words`,children:c})]})]}),e.triggerKind===`webhook`&&(0,Z.jsxs)(`div`,{className:`flex flex-col gap-1 text-sm text-muted`,children:[(0,Z.jsxs)(`p`,{children:[`Senders POST a JSON body (up to 256 KB) with`,` `,(0,Z.jsx)(`code`,{children:`Authorization: Bearer <token>`}),`. Use the filters below to fire only for matching payloads, and `,(0,Z.jsx)(`code`,{children:`{{payload.<path>}}`}),` to copy payload values into the task.`]}),a?a.trigger.kind===`webhook`?(0,Z.jsxs)(`p`,{children:[`Token:`,` `,a.webhookTokenSet?`set (see Webhook below to rotate)`:`not created yet`]}):(0,Z.jsx)(`p`,{children:`Save to switch this automation to a webhook trigger, then create a token.`}):(0,Z.jsx)(`p`,{children:`Save the automation to create its webhook token and URL.`})]}),e.triggerKind===`dependency`&&(0,Z.jsx)(A,{label:`When this task is done`,error:n(`dependencyTaskId`),hint:`Fires once each time the task enters Done.`,children:n=>(0,Z.jsxs)(P,{...n,value:e.dependencyTaskId,onChange:e=>t(`dependencyTaskId`,e.target.value),children:[(0,Z.jsx)(`option`,{value:``,children:`Choose a task`}),e.dependencyTaskId&&!r[e.dependencyTaskId]&&(0,Z.jsxs)(`option`,{value:e.dependencyTaskId,children:[`Task `,e.dependencyTaskId]}),f.map(e=>(0,Z.jsxs)(`option`,{value:e.id,children:[e.title,i[e.projectId]?` (${i[e.projectId].name})`:``,e.state===`done`?` - done`:``]},e.id))]})})]})}function it({draft:e,setDraft:t,err:n}){let r=(e,n)=>t(t=>({...t,filters:t.filters.map((t,r)=>r===e?{...t,...n}:t)})),i=e.triggerKind===`webhook`;return(0,Z.jsxs)(k,{title:`Filters`,description:`Filters apply to webhook payloads. Each row compares a dot path (for example pull_request.base.ref) with an exact value; all rows must match. Payloads that do not match are recorded as Filtered out.`,children:[!i&&(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`Filters are ignored unless the trigger is Webhook.`}),e.filters.length===0&&(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No filters: every payload fires.`}),e.filters.map((e,i)=>(0,Z.jsxs)(`div`,{className:`grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-end gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]`,children:[(0,Z.jsx)(A,{label:`Path ${i+1}`,error:n(`filters.${i}.path`),children:t=>(0,Z.jsx)(J,{...t,value:e.path,onChange:e=>r(i,{path:e.target.value}),placeholder:`action`,className:`font-mono`,autoCapitalize:`off`,spellCheck:!1})}),(0,Z.jsx)(A,{label:`Equals ${i+1}`,error:n(`filters.${i}.equals`),className:`col-start-1 row-start-2 sm:col-start-auto sm:row-start-auto`,children:t=>(0,Z.jsx)(J,{...t,value:e.equals,onChange:e=>r(i,{equals:e.target.value}),placeholder:`opened`})}),(0,Z.jsx)(je,{label:`Remove filter ${i+1}`,icon:(0,Z.jsx)(we,{className:`size-4`}),onClick:()=>t(e=>({...e,filters:e.filters.filter((e,t)=>t!==i)}))})]},i)),(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{size:`sm`,icon:(0,Z.jsx)(U,{className:`size-3.5`}),onClick:()=>t(e=>({...e,filters:[...e.filters,{path:``,equals:``}]})),children:`Add filter`})})]})}function at({draft:e,update:t,err:n}){return(0,Z.jsxs)(k,{title:`Task template`,description:`Use ${pe.join(`, `)} in the title, objective, criteria and grouping key.`,children:[(0,Z.jsx)(A,{label:`Task title`,error:n(`title`),children:n=>(0,Z.jsx)(J,{...n,value:e.title,onChange:e=>t(`title`,e.target.value),placeholder:`Triage new issues for {{date}}`})}),(0,Z.jsx)(A,{label:`Objective`,error:n(`objective`),children:n=>(0,Z.jsx)(Y,{...n,minRows:4,value:e.objective,onChange:e=>t(`objective`,e.target.value)})}),(0,Z.jsx)(A,{label:`Acceptance criteria`,error:n(`criteria`),hint:`One criterion per line.`,optional:!0,children:n=>(0,Z.jsx)(Y,{...n,minRows:3,value:e.criteria,onChange:e=>t(`criteria`,e.target.value)})}),(0,Z.jsx)(A,{label:`Priority`,error:n(`priority`),children:n=>(0,Z.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,Z.jsx)(E,{priority:e.priority}),(0,Z.jsx)(P,{...n,value:String(e.priority),onChange:e=>t(`priority`,Number(e.target.value)),className:`sm:max-w-48`,children:ke.map((e,t)=>(0,Z.jsx)(`option`,{value:t,children:e},e))})]})})]})}function ot({legend:e,value:t,options:n,onChange:r,hint:i}){let a=(0,X.useId)();return(0,Z.jsxs)(`fieldset`,{className:`flex min-w-0 flex-col gap-2`,children:[(0,Z.jsx)(`legend`,{className:`mb-1 text-sm font-medium`,children:e}),(0,Z.jsx)(`div`,{className:`grid min-w-0 gap-2 sm:grid-cols-3`,children:n.map(e=>(0,Z.jsxs)(`label`,{className:te(`tap flex min-w-0 cursor-pointer items-start gap-2 rounded-md border p-2.5 text-sm`,`has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-1`,t===e.value?`border-accent bg-accent-soft`:`border-border hover:bg-surface-2`),children:[(0,Z.jsx)(`input`,{type:`radio`,name:a,value:e.value,checked:t===e.value,onChange:()=>r(e.value),className:`mt-0.5 size-4 shrink-0 accent-[var(--color-accent)]`}),(0,Z.jsxs)(`span`,{className:`min-w-0`,children:[(0,Z.jsx)(`span`,{className:`block font-medium`,children:e.label}),(0,Z.jsx)(`span`,{className:`block text-xs text-muted`,children:e.explanation})]})]},e.value))}),i&&(0,Z.jsx)(`p`,{className:`text-xs text-muted`,children:i})]})}function st({draft:e,update:t,err:n,hosts:r,profiles:i,defaultHostName:a,defaultProfileName:o,selectedProfileMissing:s}){return(0,Z.jsxs)(k,{title:`Where and how it runs`,children:[(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Host`,error:n(`hostId`),children:n=>(0,Z.jsxs)(P,{...n,value:e.hostId,onChange:e=>t(`hostId`,e.target.value),children:[(0,Z.jsxs)(`option`,{value:``,children:[`Project default`,a?` (${a})`:``]}),e.hostId&&!r.some(t=>t.id===e.hostId)&&(0,Z.jsx)(`option`,{value:e.hostId,children:`Unknown host`}),r.map(e=>(0,Z.jsxs)(`option`,{value:e.id,children:[e.name,e.connectivity===`connected`?``:` (${e.connectivity})`]},e.id))]})}),(0,Z.jsx)(A,{label:`Profile`,error:n(`profileId`),hint:s?`The selected profile is no longer available.`:void 0,children:n=>(0,Z.jsxs)(P,{...n,value:e.profileId,onChange:e=>t(`profileId`,e.target.value),children:[(0,Z.jsxs)(`option`,{value:``,children:[`Project default`,o?` (${o})`:``]}),e.profileId&&!i.some(t=>t.id===e.profileId)&&(0,Z.jsx)(`option`,{value:e.profileId,children:`Unknown profile`}),i.map(e=>(0,Z.jsx)(`option`,{value:e.id,children:e.name},e.id))]})})]}),(0,Z.jsx)(ot,{legend:`Mode`,value:e.mode,options:he,onChange:e=>t(`mode`,e)}),(0,Z.jsx)(A,{label:`Grouping key`,optional:e.mode!==`continue`,error:n(`groupingKey`),hint:`Occurrences with the same rendered key are the same work: Continue reuses that task and overlap checks compare it. Supports template variables, e.g. pr-{{payload.number}}.`,children:n=>(0,Z.jsx)(J,{...n,value:e.groupingKey,onChange:e=>t(`groupingKey`,e.target.value),className:`font-mono`,autoCapitalize:`off`,spellCheck:!1})})]})}function ct({draft:e,update:t}){let n=e.triggerKind===`cron`||e.triggerKind===`once`;return(0,Z.jsxs)(k,{title:`Overlap and missed runs`,children:[(0,Z.jsx)(ot,{legend:`When previous work is still open`,value:e.overlap,options:ge,onChange:e=>t(`overlap`,e)}),(0,Z.jsx)(ot,{legend:`Missed runs`,value:e.missed,options:z,onChange:e=>t(`missed`,e),hint:n?`A run is missed when the coordinator was down at its scheduled time.`:`Only applies to Schedule and Once triggers.`})]})}function lt({draft:e,update:t,err:n}){return(0,Z.jsxs)(k,{title:`Limits`,description:`Continuation prompts the agent to keep going after a turn ends without finishing. It is off by default.`,children:[(0,Z.jsx)(A,{label:`Automatic continuation`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.continuationEnabled,onChange:e=>t(`continuationEnabled`,e),label:`Automatic continuation`,className:`self-start`})}),e.continuationEnabled&&(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(A,{label:`Continuation objective`,error:n(`continuationObjective`),hint:`What the agent must achieve before it reports done.`,children:n=>(0,Z.jsx)(Y,{...n,minRows:2,value:e.continuationObjective,onChange:e=>t(`continuationObjective`,e.target.value)})}),(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Max continuations`,error:n(`maxContinuations`),hint:`1–50`,children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,max:50,step:1,value:e.maxContinuations,onChange:e=>t(`maxContinuations`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max elapsed minutes`,error:n(`maxElapsedMinutes`),hint:`1–1440`,children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,max:1440,step:1,value:e.maxElapsedMinutes,onChange:e=>t(`maxElapsedMinutes`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max tokens`,optional:!0,error:n(`maxTokens`),children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,step:1,value:e.maxTokens,onChange:e=>t(`maxTokens`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max cost (USD)`,optional:!0,error:n(`maxCostUsd`),children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`decimal`,min:.01,step:.01,value:e.maxCostUsd,onChange:e=>t(`maxCostUsd`,e.target.value)})})]}),(0,Z.jsx)(`p`,{className:`text-xs text-muted`,children:`Token and cost limits are enforced only when the provider reports accounting; otherwise the run shows that accounting is unavailable and only the count and time limits apply.`})]})]})}function ut({draft:e,update:t,err:n,projectChecks:r}){let i=r&&r.length>0?`the project's checks (${r.join(`, `)})`:`the project's checks`;return(0,Z.jsxs)(k,{title:`Review`,children:[(0,Z.jsx)(A,{label:`Checks`,optional:!0,error:n(`checks`),hint:`One command per line. Leave empty to use ${i}.`,children:n=>(0,Z.jsx)(Y,{...n,minRows:2,value:e.checks,onChange:e=>t(`checks`,e.target.value),className:`font-mono`,spellCheck:!1,autoCapitalize:`off`})}),(0,Z.jsx)(A,{label:`Human review`,hint:`When on, finished work waits in Review for you instead of completing on passing checks.`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.requireHumanReview,onChange:e=>t(`requireHumanReview`,e),label:`Require human review`,onText:`Required`,offText:`Not required`,className:`self-start`})})]})}export{Je as default};
|
|
2
|
-
//# sourceMappingURL=AutomationView-
|
|
1
|
+
import{_ as e,f as t,i as n,m as r,n as i,p as a,t as o}from"./jsx-runtime-D13aM9HY.js";import{A as s,B as c,E as l,G as u,H as d,P as f,U as p,V as m,W as ee,c as h,g,h as te,i as ne,l as _,n as v,o as y,p as b,r as re,t as x}from"./states-DrTQuYXw.js";import{t as ie}from"./arrow-left-C6YWM6y6.js";import{t as S}from"./calendar-clock-B8zE4JsX.js";import{t as C}from"./copy-oWgGHmkS.js";import{t as w}from"./key-round-VDbE9E4X.js";import{A as T,C as ae,D as E,E as oe,F as se,M as D,N as O,O as k,S as ce,T as A,a as j,b as le,d as ue,f as M,g as de,h as fe,i as pe,j as N,k as P,l as me,m as F,n as he,o as I,p as L,r as ge,s as R,t as z,u as _e,v as B,x as V,y as H}from"./describe-BirsjlUl.js";import{t as ve}from"./play-CjoDg3ws.js";import{t as U}from"./plus-BLP-wIYD.js";import{t as ye}from"./save-DEvZPWlS.js";import{t as be}from"./trash-C6X2OLrI.js";import{E as W,F as G,I as xe,P as Se,_ as K,c as q,d as Ce,g as we,n as Te,w as Ee}from"./index-IdzOP9GE.js";import{n as J,t as Y}from"./TextArea-BsQHOQMe.js";import{a as De,c as Oe,r as ke}from"./dist-C-h8FC-n.js";import{t as Ae}from"./SegmentedControl-ChdVilXb.js";import{i as je,t as Me}from"./Overlay--v0ur2lj.js";var Ne={name:`scroll-text`,size:24,node:[[`path`,{d:`M15 12h-5`,key:`r7krc0`}],[`path`,{d:`M15 8h-5`,key:`1khuty`}],[`path`,{d:`M19 17V5a2 2 0 0 0-2-2H4`,key:`zz82l3`}],[`path`,{d:`M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3`,key:`1ph1d7`}]]};Ne.node;var Pe=i(Ne),X=e(r(),1),Z=o(),Fe=e=>`/tasks/${encodeURIComponent(e)}`;function Q(e){return u(e)&&(e.status===404||e.status===405||e.status===501)}function Ie({summary:e,errorCount:t}){return(0,Z.jsxs)(k,{title:`Summary`,testId:`automation-summary`,children:[(0,Z.jsx)(`p`,{className:`text-sm break-words`,children:e}),t>0&&(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-sm text-danger`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-4 shrink-0`}),t===1?`1 field needs attention`:`${t} fields need attention`]})]})}async function Le(e,t,n){return e&&!n?m(N(e,`/preview`)):p(`/api/automations/preview`,t)}function Re({automationId:e,dirty:t,getInput:n,currentKey:r}){let[i,a]=(0,X.useState)({status:`idle`}),o=async()=>{let r=n();if(!r)q.error(`Fix the highlighted fields to preview the task`);else{a({status:`loading`});try{let n=await Le(e,r,t);a({status:`done`,preview:n,key:JSON.stringify(r)})}catch(e){a(Q(e)?{status:`unsupported`}:{status:`error`,error:e})}}};return(0,Z.jsxs)(k,{title:`Preview`,description:`See the task an occurrence would create, and when it would run next.`,testId:`automation-preview`,children:[(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{icon:(0,Z.jsx)(W,{className:`size-4`}),busy:i.status===`loading`,onClick:()=>void o(),children:`Preview resulting task`})}),i.status===`unsupported`&&(0,Z.jsx)(h,{capability:`Task preview`,reason:`This coordinator does not offer automation previews.`,fallback:`Read the summary above, or save and use Run now to see a real task.`}),i.status===`error`&&(0,Z.jsx)(v,{compact:!0,title:`Could not preview`,error:i.error,onRetry:()=>void o()}),i.status===`done`&&(0,Z.jsx)(ze,{preview:i.preview,stale:i.key!==r})]})}function ze({preview:e,stale:t}){let{task:n}=e;return(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-3`,children:[t&&(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-xs text-warning`,role:`status`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-3.5 shrink-0`}),`The form changed since this preview. Preview again to refresh it.`]}),(0,Z.jsx)(`p`,{className:`text-sm break-words text-muted`,children:e.summary}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-2 rounded-md border border-border bg-surface-2 p-3`,children:[(0,Z.jsxs)(`div`,{className:`flex min-w-0 items-start justify-between gap-2`,children:[(0,Z.jsx)(`h3`,{className:`min-w-0 font-medium break-words`,children:n.title}),(0,Z.jsxs)(`span`,{className:`inline-flex shrink-0 items-center gap-1 text-xs text-muted`,children:[(0,Z.jsx)(E,{priority:n.priority,className:`size-3.5`}),Oe[n.priority]]})]}),n.objective?(0,Z.jsx)(`p`,{className:`text-sm break-words whitespace-pre-wrap`,children:n.objective}):(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No objective.`}),n.acceptanceCriteria.length>0&&(0,Z.jsxs)(`div`,{children:[(0,Z.jsxs)(`h4`,{className:`flex items-center gap-1 text-xs font-medium text-muted`,children:[(0,Z.jsx)(Ee,{"aria-hidden":!0,className:`size-3.5`}),`Acceptance criteria`]}),(0,Z.jsx)(`ul`,{className:`mt-1 list-disc pl-5 text-sm`,children:n.acceptanceCriteria.map((e,t)=>(0,Z.jsx)(`li`,{className:`break-words`,children:e},t))})]})]}),(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`h3`,{className:`text-sm font-medium`,children:`Next occurrences`}),e.next.length===0?(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No scheduled times: this trigger fires on demand, on a webhook, or when a task is done.`}):(0,Z.jsx)(`ol`,{className:`mt-1 flex flex-col gap-0.5 text-sm`,children:e.next.slice(0,5).map(e=>(0,Z.jsxs)(`li`,{className:`flex flex-wrap gap-x-2`,children:[(0,Z.jsx)(`time`,{dateTime:e,children:L(e)}),(0,Z.jsx)(`span`,{className:`text-muted`,children:b(e)})]},e))})]})]})}async function Be(e,t){try{await navigator.clipboard.writeText(e),q.success(`${t} copied`)}catch{q.error(`Could not copy the ${t.toLowerCase()}; select it and copy manually`)}}function Ve({label:e,value:t,mono:n=!0}){return(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1`,children:[(0,Z.jsx)(`span`,{className:`text-xs font-medium text-muted`,children:e}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 items-start gap-2`,children:[(0,Z.jsx)(`code`,{className:`min-w-0 flex-1 rounded border border-border bg-surface px-2 py-1.5 text-xs break-all select-all ${n?`font-mono`:``}`,children:t}),(0,Z.jsx)(_,{size:`sm`,icon:(0,Z.jsx)(C,{className:`size-3.5`}),onClick:()=>void Be(t,e),"aria-label":`Copy ${e.toLowerCase()}`,children:`Copy`})]})]})}function He({automation:e}){let[t,n]=(0,X.useState)(!1),[r,i]=(0,X.useState)(!1),[a,o]=(0,X.useState)(null),[s,c]=(0,X.useState)(!1),l=e.webhookTokenSet||a!==null,u=async()=>{i(!1),n(!0);try{let t=await p(N(e.id,`/webhook-token`)),n=ke.safeParse(t);if(!n.success)throw Error(`The coordinator returned an unexpected token response`);let r=new URL(n.data.url,window.location.origin).toString();o({token:n.data.token,url:r}),q.success(l?`Webhook token rotated`:`Webhook token created`)}catch(e){Q(e)?c(!0):q.error(`Could not issue a webhook token: ${ee(e)}`)}finally{n(!1)}};return(0,Z.jsxs)(k,{title:`Webhook`,description:`POST JSON to the URL with the token as a Bearer header. Filters decide which payloads fire.`,testId:`webhook-token`,children:[(0,Z.jsx)(`p`,{className:`flex items-center gap-1.5 text-sm`,children:l?(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(xe,{"aria-hidden":!0,className:`size-4 shrink-0 text-success`}),`A token is set`]}):(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(G,{"aria-hidden":!0,className:`size-4 shrink-0 text-muted`}),`No token yet: webhook calls are rejected until you create one`]})}),s?(0,Z.jsx)(h,{capability:`Webhook tokens`,reason:`This coordinator does not issue webhook tokens.`}):(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{icon:(0,Z.jsx)(w,{className:`size-4`}),busy:t,onClick:()=>l?i(!0):void u(),children:l?`Rotate token`:`Create token`})}),a&&(0,Z.jsxs)(`div`,{role:`alert`,className:`flex min-w-0 flex-col gap-3 rounded-md border border-warning bg-warning-soft p-3`,children:[(0,Z.jsxs)(`p`,{className:`flex items-center gap-1.5 text-sm font-medium`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`size-4 shrink-0 text-warning`}),`This token is shown once. Copy it now.`]}),(0,Z.jsx)(Ve,{label:`Token`,value:a.token}),(0,Z.jsx)(Ve,{label:`URL`,value:a.url}),(0,Z.jsx)(Ve,{label:`Example`,value:I(a.token,a.url)}),(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{size:`sm`,onClick:()=>o(null),children:`I have copied it`})})]}),(0,Z.jsx)(Me,{open:r,onClose:()=>i(!1),title:`Rotate webhook token?`,description:`The current token stops working immediately. Update every sender with the new token.`,footer:(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{onClick:()=>i(!1),children:`Cancel`}),(0,Z.jsx)(_,{variant:`danger`,onClick:()=>void u(),children:`Rotate token`})]})})]})}var Ue=[];function We({automationId:e,refreshKey:t,onRun:r}){let i=f(t=>t.occurrences[e]??Ue),a=f(e=>e.tasks),[o,s]=(0,X.useState)({status:`loading`}),c=(0,X.useCallback)(async t=>{s(e=>e.status===`done`?e:{status:`loading`});try{let n=await m(N(e,`/history`),{},t);s({status:`done`,items:B(n)})}catch(e){if(t?.aborted)return;s(Q(e)?{status:`unsupported`}:{status:`error`,error:e})}},[e]);(0,X.useEffect)(()=>{let e=new AbortController;return c(e.signal),()=>e.abort()},[c,t]);let l=de(o.status===`done`?o.items:[],i);return(0,Z.jsxs)(k,{title:`History`,description:`Occurrences of this automation. Routine successes are hidden from the board and appear only here.`,testId:`automation-history`,children:[o.status===`unsupported`&&(0,Z.jsx)(h,{capability:`Automation history`,reason:`This coordinator does not serve stored history; only occurrences seen live in this session are shown.`}),o.status===`error`&&(0,Z.jsx)(v,{compact:!0,title:`Could not load history`,error:o.error,onRetry:()=>void c()}),o.status===`loading`&&l.length===0?(0,Z.jsx)(y,{label:`Loading history…`,className:`py-4`}):l.length===0&&o.status!==`error`?(0,Z.jsx)(x,{className:`py-4`,icon:(0,Z.jsx)(Pe,{className:`size-6`}),title:`No occurrences yet`,description:`Occurrences appear here when the trigger fires or you run it.`,action:{label:`Run now`,onClick:r,icon:(0,Z.jsx)(ve,{className:`size-4`})}}):(0,Z.jsx)(`ol`,{className:`flex flex-col divide-y divide-border`,children:l.map(e=>{let t=H(e),r=e.taskId?a[e.taskId]:void 0,i=[e.reason,e.outcome].filter(Boolean).join(` · `);return(0,Z.jsxs)(`li`,{className:`flex min-w-0 flex-col gap-1 py-2 first:pt-0 last:pb-0`,children:[(0,Z.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-2 gap-y-1`,children:[(0,Z.jsx)(Ce,{kind:`occurrence`,value:e.state,size:`sm`}),(0,Z.jsx)(`time`,{dateTime:t,className:`text-sm`,children:L(t)}),(0,Z.jsx)(`span`,{className:`text-xs text-muted`,children:b(t)})]}),i&&(0,Z.jsx)(`p`,{className:`text-sm break-words text-muted`,children:i}),e.taskId&&(0,Z.jsxs)(n,{to:Fe(e.taskId),className:`tap inline-flex min-w-0 items-center gap-1 self-start text-sm text-accent hover:underline`,children:[(0,Z.jsx)(`span`,{className:`min-w-0 truncate`,children:r?`Task: ${r.title}`:`Open task`}),r?.hiddenFromBoard&&(0,Z.jsx)(`span`,{className:`shrink-0 text-xs text-muted`,children:`(hidden from board)`})]})]},e.id)})})]})}var Ge=`/automations`,Ke=e=>`/automations/${encodeURIComponent(e)}`,qe=`sy:automation-draft`;function Je(){let{automationId:e=`new`}=a();return(0,Z.jsx)(Ye,{automationId:e},e)}function Ye({automationId:e}){let t=e===`new`,n=f(e=>e.hydrated),r=O(t?void 0:e),[i,a]=(0,X.useState)({status:`idle`}),o=(0,X.useCallback)(async()=>{a({status:`loading`});try{let t=await m(N(e)),n=De.safeParse(t);if(!n.success){a({status:`fatal`,error:Error(`The coordinator returned this automation in an unexpected format.`)});return}D.getState().put(n.data),a({status:`idle`})}catch(e){u(e)&&e.status===404?a({status:`notfound`}):a({status:`error`,error:e})}},[e]),s=!t&&!r;return(0,X.useEffect)(()=>{s&&i.status===`idle`&&o()},[s,i.status,o]),t?(0,Z.jsx)($e,{automation:null}):r?(0,Z.jsx)($e,{automation:r}):i.status===`notfound`?(0,Z.jsx)(x,{icon:(0,Z.jsx)(K,{className:`size-8`}),title:`Automation not found`,description:`It may have been deleted, or the link is wrong.`,action:{label:`Back to automations`,to:Ge}}):i.status===`error`?(0,Z.jsx)(v,{title:`Could not load this automation`,error:i.error,onRetry:()=>void o()}):i.status===`fatal`?(0,Z.jsx)(re,{error:i.error}):(0,Z.jsx)(y,{label:n?`Loading automation…`:`Loading…`})}function Xe(e){let t=ue({projectId:e,timezone:fe()});try{let e=sessionStorage.getItem(qe);if(!e)return t;let n=JSON.parse(e);return!n||typeof n!=`object`?t:{...t,...n,filters:Array.isArray(n.filters)?n.filters:[]}}catch{return t}}function Ze(e){try{e?sessionStorage.setItem(qe,JSON.stringify(e)):sessionStorage.removeItem(qe)}catch{}}var $=e=>JSON.stringify(_e(e));function Qe(e){if(!u(e))return{};let t=e.details,n=Array.isArray(t)?t:t&&typeof t==`object`&&Array.isArray(t.issues)?t.issues:[],r={};for(let e of n){if(!e||typeof e!=`object`)continue;let{path:t,message:n}=e;if(!Array.isArray(t)||typeof n!=`string`)continue;let i=M(t);i!==`form`&&!r[i]&&(r[i]=n)}return r}function $e({automation:e}){let r=t(),i=Te(),a=i===`desktop`,o=s(),u=l(),m=f(e=>e.hosts),h=f(e=>e.profiles),v=f(e=>e.tasks),y=f(e=>e.projects),b=e===null,re=(0,X.useId)(),x=(0,X.useRef)(null),[S,C]=(0,X.useState)(()=>e?me(e):Xe(o[0]?.id)),[w,T]=(0,X.useState)(()=>$(S)),[E,O]=(0,X.useState)(!1),[k,A]=(0,X.useState)({}),[j,M]=(0,X.useState)(!1),[de,pe]=(0,X.useState)(!1),[P,F]=(0,X.useState)(!1),[he,I]=(0,X.useState)(!1),[L,ge]=(0,X.useState)(0),R=(0,X.useRef)(e?.updatedAt),z=$(S),B=z!==w;(0,X.useEffect)(()=>{if(!e||e.updatedAt===R.current)return;R.current=e.updatedAt;let t=me(e),n=$(t);z===w?(C(t),I(!1)):n!==z&&I(!0),T(n)},[e,z,w]),(0,X.useEffect)(()=>{b&&Ze(S)},[b,S]),(0,X.useEffect)(()=>{b&&!S.projectId&&o[0]&&C(e=>e.projectId?e:{...e,projectId:o[0].id})},[b,S.projectId,o]);let V=(0,X.useCallback)((e,t)=>{C(n=>({...n,[e]:t})),A(e=>Object.keys(e).length?{}:e)},[]),H=(0,X.useMemo)(()=>ce(S),[S]),U={...k,...E?H.errors:{}},W=e=>U[e],G=y[S.projectId],xe=S.hostId||G?.defaultHostId||null,K=xe?m[xe]:void 0,Ce=S.profileId||G?.defaultProfileId||null,Ee=(0,X.useCallback)(e=>v[e]?.title,[v]),J=(0,X.useMemo)(()=>le(_e(S),{projectName:G?.name,hostName:S.hostId?m[S.hostId]?.name??null:null,profileName:S.profileId?h[S.profileId]?.name??null:null,taskTitle:Ee}),[S,G?.name,m,h,Ee]),Y=()=>{requestAnimationFrame(()=>{x.current?.querySelector(`[aria-invalid="true"]`)?.focus()})},De=()=>(O(!0),H.input||Y(),H.input),Oe=async()=>{let t=De();if(!t)q.error(`Fix the highlighted fields before saving`);else{M(!0);try{if(b){let e=await p(`/api/automations`,t);if(!e||typeof e!=`object`||!e.id)throw Error(`The coordinator did not return the new automation`);D.getState().put(e),Ze(null),q.success(`Created ${e.name}`),r(Ke(e.id),{replace:!0});return}let n=await d(N(e.id),t);if(n&&typeof n==`object`&&n.id){D.getState().put(n),R.current=n.updatedAt;let e=me(n);C(e),T($(e))}else T(z);I(!1),O(!1),q.success(`Saved ${t.name}`)}catch(e){let t=Qe(e);A(t),Object.keys(t).length&&Y(),q.error(`Could not save: ${ee(e)}. Your changes are kept.`)}finally{M(!1)}}},ke=async()=>{if(e){pe(!0);try{await c(N(e.id)),F(!1),q.success(`Deleted ${e.name}`),r(Ge,{replace:!0}),D.getState().remove(e.id)}catch(e){q.error(`Could not delete: ${ee(e)}`)}finally{pe(!1)}}},{busy:Ae,run:je}=se(()=>ge(e=>e+1)),Ne=e?!!Ae[e.id]:!1,Pe=()=>e&&void je(e),Fe=()=>{let e=ue({projectId:o[0]?.id,timezone:fe()});C(e),O(!1),Ze(null)},Q=b?`New automation`:e.name,Le=(0,Z.jsxs)(Z.Fragment,{children:[!b&&(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{icon:(0,Z.jsx)(ve,{className:`size-4`}),busy:Ne,onClick:Pe,title:B?`Runs the saved version`:void 0,children:`Run now`}),(0,Z.jsx)(_,{variant:`ghost`,icon:(0,Z.jsx)(be,{className:`size-4`}),onClick:()=>F(!0),className:`text-danger`,children:`Delete`})]}),b&&B&&(0,Z.jsx)(_,{variant:`ghost`,icon:(0,Z.jsx)(we,{className:`size-4`}),onClick:Fe,children:`Discard draft`})]}),ze=e=>(0,Z.jsx)(_,{type:`submit`,form:re,variant:`primary`,busy:j,block:e,icon:(0,Z.jsx)(ye,{className:`size-4`}),children:b?`Create automation`:`Save`}),Be=(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(Ie,{summary:J,errorCount:E?Object.keys(H.errors).length:0}),(0,Z.jsx)(Re,{automationId:e?.id??null,dirty:B,getInput:De,currentKey:JSON.stringify(H.input)}),e&&e.trigger.kind===`webhook`&&(0,Z.jsx)(He,{automation:e}),e&&(0,Z.jsx)(We,{automationId:e.id,refreshKey:L,onRun:Pe})]});return(0,Z.jsxs)(`div`,{"data-testid":`automation-editor`,className:`mx-auto flex w-full max-w-6xl min-w-0 flex-col gap-4 px-4 pt-4 md:px-6`,children:[(0,Z.jsxs)(`header`,{className:`flex min-w-0 flex-col gap-2`,children:[(0,Z.jsxs)(n,{to:Ge,className:`tap inline-flex items-center gap-1 self-start rounded text-sm text-muted hover:text-text`,children:[(0,Z.jsx)(ie,{"aria-hidden":!0,className:`size-4`}),`Automations`]}),(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center justify-between gap-2`,children:[(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,Z.jsx)(`h1`,{className:`min-w-0 text-lg font-semibold break-words`,children:Q}),B&&(0,Z.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-xs text-muted`,children:[(0,Z.jsx)(Se,{"aria-hidden":!0,className:`size-3.5`}),`Unsaved changes`]})]}),(0,Z.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[Le,a&&ze()]})]})]}),(0,Z.jsx)(ae,{}),K&&K.connectivity!==`connected`&&(0,Z.jsx)(ne,{compact:!0,hostName:K.name,connectivity:K.connectivity,lastSeenAt:K.lastSeenAt,detail:`Occurrences are still recorded; their tasks wait until the host is reachable.`}),he&&(0,Z.jsxs)(`p`,{role:`status`,className:`flex items-start gap-1.5 rounded-md border border-border bg-warning-soft px-3 py-2 text-sm`,children:[(0,Z.jsx)(g,{"aria-hidden":!0,className:`mt-0.5 size-4 shrink-0 text-warning`}),`This automation changed elsewhere. Saving overwrites those changes with yours.`]}),(0,Z.jsxs)(`div`,{className:te(`grid min-w-0 gap-4`,a?`grid-cols-[minmax(0,1fr)_minmax(320px,400px)] items-start`:`grid-cols-1`),children:[(0,Z.jsxs)(`form`,{id:re,ref:x,noValidate:!0,"aria-label":Q,onSubmit:e=>{e.preventDefault(),Oe()},className:`flex min-w-0 flex-col gap-4`,children:[(0,Z.jsx)(et,{draft:S,update:V,err:W,projects:o}),(0,Z.jsx)(rt,{draft:S,update:V,err:W,tasks:v,projectsById:y,savedAutomation:e,segmented:i!==`mobile`}),(0,Z.jsx)(it,{draft:S,setDraft:C,err:W}),(0,Z.jsx)(at,{draft:S,update:V,err:W}),(0,Z.jsx)(st,{draft:S,update:V,err:W,hosts:u,profiles:Object.values(h).sort((e,t)=>e.name.localeCompare(t.name)),defaultHostName:G?.defaultHostId?m[G.defaultHostId]?.name:void 0,defaultProfileName:G?.defaultProfileId?h[G.defaultProfileId]?.name:void 0,selectedProfileMissing:!!Ce&&!h[Ce]}),(0,Z.jsx)(ct,{draft:S,update:V}),(0,Z.jsx)(lt,{draft:S,update:V,err:W}),(0,Z.jsx)(ut,{draft:S,update:V,err:W,projectChecks:G?.checks}),U.form&&(0,Z.jsx)(oe,{children:U.form})]}),(0,Z.jsx)(`aside`,{"aria-label":`Summary, preview and history`,className:`flex min-w-0 flex-col gap-4`,children:Be})]}),a?(0,Z.jsx)(`div`,{className:`h-4`}):(0,Z.jsxs)(`div`,{className:`sticky bottom-0 z-10 -mx-4 flex items-center gap-3 border-t border-border bg-surface px-4 py-2 safe-pb md:-mx-6 md:px-6`,children:[(0,Z.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-sm text-muted`,children:B?`Unsaved changes`:b?`Not saved yet`:`All changes saved`}),ze()]}),(0,Z.jsx)(Me,{open:P,onClose:()=>F(!1),title:`Delete ${e?.name??`automation`}?`,description:`Future occurrences stop. Tasks it already created stay on the board; its history is removed.`,footer:(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(_,{onClick:()=>F(!1),children:`Cancel`}),(0,Z.jsx)(_,{variant:`danger`,busy:de,icon:(0,Z.jsx)(be,{className:`size-4`}),onClick:()=>void ke(),children:`Delete automation`})]})})]})}function et({draft:e,update:t,err:r,projects:i}){return(0,Z.jsxs)(k,{title:`Basics`,children:[(0,Z.jsx)(A,{label:`Name`,error:r(`name`),children:n=>(0,Z.jsx)(J,{...n,value:e.name,onChange:e=>t(`name`,e.target.value),placeholder:`Daily dependency check`,autoComplete:`off`})}),(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Project`,error:r(`projectId`),hint:i.length===0?(0,Z.jsxs)(Z.Fragment,{children:[`No projects yet. `,(0,Z.jsx)(n,{to:`/settings/projects`,children:`Add a project in Settings`}),`.`]}):void 0,children:n=>(0,Z.jsxs)(P,{...n,value:e.projectId,onChange:e=>t(`projectId`,e.target.value),children:[(0,Z.jsx)(`option`,{value:``,children:`Choose a project`}),i.map(e=>(0,Z.jsx)(`option`,{value:e.id,children:e.name},e.id))]})}),(0,Z.jsx)(A,{label:`Status`,hint:`Disabled automations keep their history and can still run manually.`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.enabled,onChange:e=>t(`enabled`,e),label:`Enabled`,onText:`Enabled`,offText:`Disabled`,className:`self-start`})})]})]})}var tt=[`manual`,`once`,`cron`,`webhook`,`dependency`],nt={manual:`Runs only when you press Run now (or from the CLI).`,once:`Runs a single time at the chosen moment.`,cron:`Runs on a timezone-aware cron schedule.`,webhook:`Runs when an authenticated JSON POST arrives at this automation's webhook URL.`,dependency:`Runs when the chosen task is done.`};function rt({draft:e,update:t,err:n,tasks:r,projectsById:i,savedAutomation:a,segmented:o}){let s=e.triggerKind===`cron`?V(e.cronExpr):null,c=e.triggerKind===`cron`&&!s?R(e.cronExpr,e.cronTimezone.trim()||void 0)??`Valid expression (no plain-language description available)`:null,l=(0,X.useMemo)(()=>{try{return Intl.supportedValuesOf?.(`timeZone`)}catch{return}},[]),u=(0,X.useId)(),d=fe(),f=(0,X.useMemo)(()=>Object.values(r).filter(t=>t.state!==`canceled`||t.id===e.dependencyTaskId).sort((e,t)=>e.title.localeCompare(t.title)),[r,e.dependencyTaskId]),p=e.triggerKind===`once`?F(e.onceAt):``;return(0,Z.jsxs)(k,{title:`Trigger`,description:nt[e.triggerKind],children:[o?(0,Z.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1`,children:[(0,Z.jsx)(`span`,{"aria-hidden":!0,className:`text-sm font-medium`,children:`Trigger kind`}),(0,Z.jsx)(`div`,{className:`overflow-x-auto`,children:(0,Z.jsx)(Ae,{label:`Trigger kind`,value:e.triggerKind,onChange:e=>t(`triggerKind`,e),options:tt.map(e=>({value:e,label:j[e]}))})})]}):(0,Z.jsx)(A,{label:`Trigger kind`,children:n=>(0,Z.jsx)(P,{...n,value:e.triggerKind,onChange:e=>t(`triggerKind`,e.target.value),children:tt.map(e=>(0,Z.jsx)(`option`,{value:e,children:j[e]},e))})}),e.triggerKind===`once`&&(0,Z.jsx)(A,{label:`Run at`,error:n(`onceAt`),hint:`Local time (${d})${p?`; saved as ${p}`:``}.`,children:n=>(0,Z.jsx)(J,{...n,type:`datetime-local`,value:e.onceAt,onChange:e=>t(`onceAt`,e.target.value)})}),e.triggerKind===`cron`&&(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Cron expression`,error:n(`cronExpr`)??s??void 0,hint:`minute hour day-of-month month day-of-week, e.g. 0 9 * * 1-5`,children:n=>(0,Z.jsx)(J,{...n,"aria-invalid":s?!0:n[`aria-invalid`],value:e.cronExpr,onChange:e=>t(`cronExpr`,e.target.value),className:`font-mono`,autoComplete:`off`,spellCheck:!1,autoCapitalize:`off`})}),(0,Z.jsx)(A,{label:`Timezone`,error:n(`cronTimezone`),hint:e.cronTimezone.trim()===d?`IANA timezone name; daylight saving is handled.`:(0,Z.jsxs)(`button`,{type:`button`,className:`tap text-accent hover:underline`,onClick:()=>t(`cronTimezone`,d),children:[`Use this device's timezone (`,d,`)`]}),children:n=>(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(J,{...n,list:l?u:void 0,value:e.cronTimezone,onChange:e=>t(`cronTimezone`,e.target.value),autoComplete:`off`,spellCheck:!1,autoCapitalize:`off`}),l&&(0,Z.jsx)(`datalist`,{id:u,children:l.map(e=>(0,Z.jsx)(`option`,{value:e},e))})]})}),c&&(0,Z.jsxs)(`p`,{className:`flex items-start gap-1.5 text-sm sm:col-span-2`,"aria-live":`polite`,"data-testid":`cron-description`,children:[(0,Z.jsx)(S,{"aria-hidden":!0,className:`mt-0.5 size-4 shrink-0 text-muted`}),(0,Z.jsx)(`span`,{className:`min-w-0 break-words`,children:c})]})]}),e.triggerKind===`webhook`&&(0,Z.jsxs)(`div`,{className:`flex flex-col gap-1 text-sm text-muted`,children:[(0,Z.jsxs)(`p`,{children:[`Senders POST a JSON body (up to 256 KB) with`,` `,(0,Z.jsx)(`code`,{children:`Authorization: Bearer <token>`}),`. Use the filters below to fire only for matching payloads, and `,(0,Z.jsx)(`code`,{children:`{{payload.<path>}}`}),` to copy payload values into the task.`]}),a?a.trigger.kind===`webhook`?(0,Z.jsxs)(`p`,{children:[`Token:`,` `,a.webhookTokenSet?`set (see Webhook below to rotate)`:`not created yet`]}):(0,Z.jsx)(`p`,{children:`Save to switch this automation to a webhook trigger, then create a token.`}):(0,Z.jsx)(`p`,{children:`Save the automation to create its webhook token and URL.`})]}),e.triggerKind===`dependency`&&(0,Z.jsx)(A,{label:`When this task is done`,error:n(`dependencyTaskId`),hint:`Fires once each time the task enters Done.`,children:n=>(0,Z.jsxs)(P,{...n,value:e.dependencyTaskId,onChange:e=>t(`dependencyTaskId`,e.target.value),children:[(0,Z.jsx)(`option`,{value:``,children:`Choose a task`}),e.dependencyTaskId&&!r[e.dependencyTaskId]&&(0,Z.jsxs)(`option`,{value:e.dependencyTaskId,children:[`Task `,e.dependencyTaskId]}),f.map(e=>(0,Z.jsxs)(`option`,{value:e.id,children:[e.title,i[e.projectId]?` (${i[e.projectId].name})`:``,e.state===`done`?` - done`:``]},e.id))]})})]})}function it({draft:e,setDraft:t,err:n}){let r=(e,n)=>t(t=>({...t,filters:t.filters.map((t,r)=>r===e?{...t,...n}:t)})),i=e.triggerKind===`webhook`;return(0,Z.jsxs)(k,{title:`Filters`,description:`Filters apply to webhook payloads. Each row compares a dot path (for example pull_request.base.ref) with an exact value; all rows must match. Payloads that do not match are recorded as Filtered out.`,children:[!i&&(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`Filters are ignored unless the trigger is Webhook.`}),e.filters.length===0&&(0,Z.jsx)(`p`,{className:`text-sm text-muted`,children:`No filters: every payload fires.`}),e.filters.map((e,i)=>(0,Z.jsxs)(`div`,{className:`grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-end gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]`,children:[(0,Z.jsx)(A,{label:`Path ${i+1}`,error:n(`filters.${i}.path`),children:t=>(0,Z.jsx)(J,{...t,value:e.path,onChange:e=>r(i,{path:e.target.value}),placeholder:`action`,className:`font-mono`,autoCapitalize:`off`,spellCheck:!1})}),(0,Z.jsx)(A,{label:`Equals ${i+1}`,error:n(`filters.${i}.equals`),className:`col-start-1 row-start-2 sm:col-start-auto sm:row-start-auto`,children:t=>(0,Z.jsx)(J,{...t,value:e.equals,onChange:e=>r(i,{equals:e.target.value}),placeholder:`opened`})}),(0,Z.jsx)(je,{label:`Remove filter ${i+1}`,icon:(0,Z.jsx)(we,{className:`size-4`}),onClick:()=>t(e=>({...e,filters:e.filters.filter((e,t)=>t!==i)}))})]},i)),(0,Z.jsx)(`div`,{children:(0,Z.jsx)(_,{size:`sm`,icon:(0,Z.jsx)(U,{className:`size-3.5`}),onClick:()=>t(e=>({...e,filters:[...e.filters,{path:``,equals:``}]})),children:`Add filter`})})]})}function at({draft:e,update:t,err:n}){return(0,Z.jsxs)(k,{title:`Task template`,description:`Use ${pe.join(`, `)} in the title, objective, criteria and grouping key.`,children:[(0,Z.jsx)(A,{label:`Task title`,error:n(`title`),children:n=>(0,Z.jsx)(J,{...n,value:e.title,onChange:e=>t(`title`,e.target.value),placeholder:`Triage new issues for {{date}}`})}),(0,Z.jsx)(A,{label:`Objective`,error:n(`objective`),children:n=>(0,Z.jsx)(Y,{...n,minRows:4,value:e.objective,onChange:e=>t(`objective`,e.target.value)})}),(0,Z.jsx)(A,{label:`Acceptance criteria`,error:n(`criteria`),hint:`One criterion per line.`,optional:!0,children:n=>(0,Z.jsx)(Y,{...n,minRows:3,value:e.criteria,onChange:e=>t(`criteria`,e.target.value)})}),(0,Z.jsx)(A,{label:`Priority`,error:n(`priority`),children:n=>(0,Z.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,Z.jsx)(E,{priority:e.priority}),(0,Z.jsx)(P,{...n,value:String(e.priority),onChange:e=>t(`priority`,Number(e.target.value)),className:`sm:max-w-48`,children:Oe.map((e,t)=>(0,Z.jsx)(`option`,{value:t,children:e},e))})]})})]})}function ot({legend:e,value:t,options:n,onChange:r,hint:i}){let a=(0,X.useId)();return(0,Z.jsxs)(`fieldset`,{className:`flex min-w-0 flex-col gap-2`,children:[(0,Z.jsx)(`legend`,{className:`mb-1 text-sm font-medium`,children:e}),(0,Z.jsx)(`div`,{className:`grid min-w-0 gap-2 sm:grid-cols-3`,children:n.map(e=>(0,Z.jsxs)(`label`,{className:te(`tap flex min-w-0 cursor-pointer items-start gap-2 rounded-md border p-2.5 text-sm`,`has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-1`,t===e.value?`border-accent bg-accent-soft`:`border-border hover:bg-surface-2`),children:[(0,Z.jsx)(`input`,{type:`radio`,name:a,value:e.value,checked:t===e.value,onChange:()=>r(e.value),className:`mt-0.5 size-4 shrink-0 accent-[var(--color-accent)]`}),(0,Z.jsxs)(`span`,{className:`min-w-0`,children:[(0,Z.jsx)(`span`,{className:`block font-medium`,children:e.label}),(0,Z.jsx)(`span`,{className:`block text-xs text-muted`,children:e.explanation})]})]},e.value))}),i&&(0,Z.jsx)(`p`,{className:`text-xs text-muted`,children:i})]})}function st({draft:e,update:t,err:n,hosts:r,profiles:i,defaultHostName:a,defaultProfileName:o,selectedProfileMissing:s}){return(0,Z.jsxs)(k,{title:`Where and how it runs`,children:[(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Host`,error:n(`hostId`),children:n=>(0,Z.jsxs)(P,{...n,value:e.hostId,onChange:e=>t(`hostId`,e.target.value),children:[(0,Z.jsxs)(`option`,{value:``,children:[`Project default`,a?` (${a})`:``]}),e.hostId&&!r.some(t=>t.id===e.hostId)&&(0,Z.jsx)(`option`,{value:e.hostId,children:`Unknown host`}),r.map(e=>(0,Z.jsxs)(`option`,{value:e.id,children:[e.name,e.connectivity===`connected`?``:` (${e.connectivity})`]},e.id))]})}),(0,Z.jsx)(A,{label:`Profile`,error:n(`profileId`),hint:s?`The selected profile is no longer available.`:void 0,children:n=>(0,Z.jsxs)(P,{...n,value:e.profileId,onChange:e=>t(`profileId`,e.target.value),children:[(0,Z.jsxs)(`option`,{value:``,children:[`Project default`,o?` (${o})`:``]}),e.profileId&&!i.some(t=>t.id===e.profileId)&&(0,Z.jsx)(`option`,{value:e.profileId,children:`Unknown profile`}),i.map(e=>(0,Z.jsx)(`option`,{value:e.id,children:e.name},e.id))]})})]}),(0,Z.jsx)(ot,{legend:`Mode`,value:e.mode,options:he,onChange:e=>t(`mode`,e)}),(0,Z.jsx)(A,{label:`Grouping key`,optional:e.mode!==`continue`,error:n(`groupingKey`),hint:`Occurrences with the same rendered key are the same work: Continue reuses that task and overlap checks compare it. Supports template variables, e.g. pr-{{payload.number}}.`,children:n=>(0,Z.jsx)(J,{...n,value:e.groupingKey,onChange:e=>t(`groupingKey`,e.target.value),className:`font-mono`,autoCapitalize:`off`,spellCheck:!1})})]})}function ct({draft:e,update:t}){let n=e.triggerKind===`cron`||e.triggerKind===`once`;return(0,Z.jsxs)(k,{title:`Overlap and missed runs`,children:[(0,Z.jsx)(ot,{legend:`When previous work is still open`,value:e.overlap,options:ge,onChange:e=>t(`overlap`,e)}),(0,Z.jsx)(ot,{legend:`Missed runs`,value:e.missed,options:z,onChange:e=>t(`missed`,e),hint:n?`A run is missed when the coordinator was down at its scheduled time.`:`Only applies to Schedule and Once triggers.`})]})}function lt({draft:e,update:t,err:n}){return(0,Z.jsxs)(k,{title:`Limits`,description:`Continuation prompts the agent to keep going after a turn ends without finishing. It is off by default.`,children:[(0,Z.jsx)(A,{label:`Automatic continuation`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.continuationEnabled,onChange:e=>t(`continuationEnabled`,e),label:`Automatic continuation`,className:`self-start`})}),e.continuationEnabled&&(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsx)(A,{label:`Continuation objective`,error:n(`continuationObjective`),hint:`What the agent must achieve before it reports done.`,children:n=>(0,Z.jsx)(Y,{...n,minRows:2,value:e.continuationObjective,onChange:e=>t(`continuationObjective`,e.target.value)})}),(0,Z.jsxs)(`div`,{className:`grid gap-4 sm:grid-cols-2`,children:[(0,Z.jsx)(A,{label:`Max continuations`,error:n(`maxContinuations`),hint:`1–50`,children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,max:50,step:1,value:e.maxContinuations,onChange:e=>t(`maxContinuations`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max elapsed minutes`,error:n(`maxElapsedMinutes`),hint:`1–1440`,children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,max:1440,step:1,value:e.maxElapsedMinutes,onChange:e=>t(`maxElapsedMinutes`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max tokens`,optional:!0,error:n(`maxTokens`),children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`numeric`,min:1,step:1,value:e.maxTokens,onChange:e=>t(`maxTokens`,e.target.value)})}),(0,Z.jsx)(A,{label:`Max cost (USD)`,optional:!0,error:n(`maxCostUsd`),children:n=>(0,Z.jsx)(J,{...n,type:`number`,inputMode:`decimal`,min:.01,step:.01,value:e.maxCostUsd,onChange:e=>t(`maxCostUsd`,e.target.value)})})]}),(0,Z.jsx)(`p`,{className:`text-xs text-muted`,children:`Token and cost limits are enforced only when the provider reports accounting; otherwise the run shows that accounting is unavailable and only the count and time limits apply.`})]})]})}function ut({draft:e,update:t,err:n,projectChecks:r}){let i=r&&r.length>0?`the project's checks (${r.join(`, `)})`:`the project's checks`;return(0,Z.jsxs)(k,{title:`Review`,children:[(0,Z.jsx)(A,{label:`Checks`,optional:!0,error:n(`checks`),hint:`One command per line. Leave empty to use ${i}.`,children:n=>(0,Z.jsx)(Y,{...n,minRows:2,value:e.checks,onChange:e=>t(`checks`,e.target.value),className:`font-mono`,spellCheck:!1,autoCapitalize:`off`})}),(0,Z.jsx)(A,{label:`Human review`,hint:`When on, finished work waits in Review for you instead of completing on passing checks.`,children:n=>(0,Z.jsx)(T,{id:n.id,checked:e.requireHumanReview,onChange:e=>t(`requireHumanReview`,e),label:`Require human review`,onText:`Required`,offText:`Not required`,className:`self-start`})})]})}export{Je as default};
|
|
2
|
+
//# sourceMappingURL=AutomationView-CFohp0SQ.js.map
|