agent-relay-server 0.62.0 → 0.62.2
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/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-BP0Ym3xb.js → MessageBubble-DRrxfjyT.js} +2 -2
- package/public/assets/{MessageBubble-BP0Ym3xb.js.map → MessageBubble-DRrxfjyT.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-DKc3gdou.js → PlanGraphPanel-Dp2eYazz.js} +2 -2
- package/public/assets/{PlanGraphPanel-DKc3gdou.js.map → PlanGraphPanel-Dp2eYazz.js.map} +1 -1
- package/public/assets/{activity-CRLG6ICe.js → activity--t0U2yzq.js} +2 -2
- package/public/assets/{activity-CRLG6ICe.js.map → activity--t0U2yzq.js.map} +1 -1
- package/public/assets/{agent-profiles-DItyEkXP.js → agent-profiles-BMtJYzxn.js} +2 -2
- package/public/assets/{agent-profiles-DItyEkXP.js.map → agent-profiles-BMtJYzxn.js.map} +1 -1
- package/public/assets/{agents-5mKiZ1rz.js → agents-Ck9AePlc.js} +2 -2
- package/public/assets/{agents-5mKiZ1rz.js.map → agents-Ck9AePlc.js.map} +1 -1
- package/public/assets/{analytics-J5pPcS-v.js → analytics-CzfK387L.js} +2 -2
- package/public/assets/{analytics-J5pPcS-v.js.map → analytics-CzfK387L.js.map} +1 -1
- package/public/assets/automation-Cgw8vtE8.js +2 -0
- package/public/assets/automation-Cgw8vtE8.js.map +1 -0
- package/public/assets/{branch-state-badge-BZMoxs65.js → branch-state-badge-DnEEPtOP.js} +2 -2
- package/public/assets/{branch-state-badge-BZMoxs65.js.map → branch-state-badge-DnEEPtOP.js.map} +1 -1
- package/public/assets/{channels-B2PeqiRT.js → channels-Dc3L2MQs.js} +2 -2
- package/public/assets/{channels-B2PeqiRT.js.map → channels-Dc3L2MQs.js.map} +1 -1
- package/public/assets/{chat-CLRJVagA.js → chat-Fc6iJ7sa.js} +2 -2
- package/public/assets/{chat-CLRJVagA.js.map → chat-Fc6iJ7sa.js.map} +1 -1
- package/public/assets/{connectors-Bzvkxsgo.js → connectors-Dc2ttGWs.js} +2 -2
- package/public/assets/{connectors-Bzvkxsgo.js.map → connectors-Dc2ttGWs.js.map} +1 -1
- package/public/assets/display-DzNvOu1j.js.map +1 -1
- package/public/assets/{formatted-body-impl-QqHxdMGP.js → formatted-body-impl-Bg-gx7rG.js} +2 -2
- package/public/assets/{formatted-body-impl-QqHxdMGP.js.map → formatted-body-impl-Bg-gx7rG.js.map} +1 -1
- package/public/assets/index-BeDncjN4.js +22 -0
- package/public/assets/index-BeDncjN4.js.map +1 -0
- package/public/assets/{insights-D-EyDfoZ.js → insights-c08aW-7n.js} +2 -2
- package/public/assets/{insights-D-EyDfoZ.js.map → insights-c08aW-7n.js.map} +1 -1
- package/public/assets/{integrations-D1kc-yNi.js → integrations-BwpgcqnF.js} +2 -2
- package/public/assets/{integrations-D1kc-yNi.js.map → integrations-BwpgcqnF.js.map} +1 -1
- package/public/assets/{maintenance-COqID7aq.js → maintenance-CTKP0kWB.js} +2 -2
- package/public/assets/{maintenance-COqID7aq.js.map → maintenance-CTKP0kWB.js.map} +1 -1
- package/public/assets/{managed-agents-b4B8qewQ.js → managed-agents-BofCxo8d.js} +2 -2
- package/public/assets/{managed-agents-b4B8qewQ.js.map → managed-agents-BofCxo8d.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-D7o8jS07.js → markdown-preview-impl-CQ-6BIb8.js} +2 -2
- package/public/assets/{markdown-preview-impl-D7o8jS07.js.map → markdown-preview-impl-CQ-6BIb8.js.map} +1 -1
- package/public/assets/{memory-Q0qF7_i3.js → memory-DvfXjMoi.js} +2 -2
- package/public/assets/{memory-Q0qF7_i3.js.map → memory-DvfXjMoi.js.map} +1 -1
- package/public/assets/{messages-m4EpkK6s.js → messages-C9VsPzZ_.js} +2 -2
- package/public/assets/{messages-m4EpkK6s.js.map → messages-C9VsPzZ_.js.map} +1 -1
- package/public/assets/orchestrators-MffVueeE.js +2 -0
- package/public/assets/{orchestrators-CnAtGfUN.js.map → orchestrators-MffVueeE.js.map} +1 -1
- package/public/assets/{overview-D7CF88ik.js → overview-BSVajefM.js} +2 -2
- package/public/assets/{overview-D7CF88ik.js.map → overview-BSVajefM.js.map} +1 -1
- package/public/assets/{pairs-Q6PlFJtH.js → pairs-B5711-Cs.js} +2 -2
- package/public/assets/{pairs-Q6PlFJtH.js.map → pairs-B5711-Cs.js.map} +1 -1
- package/public/assets/{security-BeV-6SdA.js → security-DkUwK9Vj.js} +2 -2
- package/public/assets/{security-BeV-6SdA.js.map → security-DkUwK9Vj.js.map} +1 -1
- package/public/assets/{select-Dchc0_Jg.js → select-DrM2RRiT.js} +2 -2
- package/public/assets/{select-Dchc0_Jg.js.map → select-DrM2RRiT.js.map} +1 -1
- package/public/assets/{settings-hPwL-31u.js → settings-DdKCb-vy.js} +3 -3
- package/public/assets/{settings-hPwL-31u.js.map → settings-DdKCb-vy.js.map} +1 -1
- package/public/assets/{store-Buwkqv_7.js → store-iTiheuTD.js} +4 -4
- package/public/assets/store-iTiheuTD.js.map +1 -0
- package/public/assets/{tasks-DKUMPEgk.js → tasks-e1y1PFfO.js} +2 -2
- package/public/assets/{tasks-DKUMPEgk.js.map → tasks-e1y1PFfO.js.map} +1 -1
- package/public/assets/{teams-T5fFsdOX.js → teams-BUcxoGC_.js} +2 -2
- package/public/assets/{teams-T5fFsdOX.js.map → teams-BUcxoGC_.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-Bk0u_Mav.js → terminal-viewer-impl-CmZjQOB2.js} +2 -2
- package/public/assets/{terminal-viewer-impl-Bk0u_Mav.js.map → terminal-viewer-impl-CmZjQOB2.js.map} +1 -1
- package/public/assets/{work-queue-D26jok09.js → work-queue-CXBQx43n.js} +2 -2
- package/public/assets/{work-queue-D26jok09.js.map → work-queue-CXBQx43n.js.map} +1 -1
- package/public/assets/{workspaces-BMj6zx23.js → workspaces-D2EfZh9a.js} +2 -2
- package/public/assets/{workspaces-BMj6zx23.js.map → workspaces-D2EfZh9a.js.map} +1 -1
- package/public/index.html +2 -2
- package/src/branch-landed.ts +7 -1
- package/src/bus.ts +3 -1
- package/src/db/agents.ts +14 -5
- package/src/db/migrations.ts +6 -0
- package/src/db/schema.ts +3 -1
- package/src/routes/commands.ts +3 -0
- package/src/routes/orchestrator-bootstrap.ts +2 -1
- package/src/routes/tokens.ts +1 -1
- package/src/runtime-tokens.ts +15 -11
- package/src/security.ts +43 -3
- package/src/services/issue-lifecycle.ts +100 -22
- package/src/token-db.ts +24 -4
- package/public/assets/automation-BG2HWK5N.js +0 -2
- package/public/assets/automation-BG2HWK5N.js.map +0 -1
- package/public/assets/index-CCp1V35c.js +0 -22
- package/public/assets/index-CCp1V35c.js.map +0 -1
- package/public/assets/orchestrators-CnAtGfUN.js +0 -2
- package/public/assets/store-Buwkqv_7.js.map +0 -1
package/src/routes/tokens.ts
CHANGED
|
@@ -337,6 +337,6 @@ export const deleteTokenProfileRoute: Handler = (_req, params) => {
|
|
|
337
337
|
};
|
|
338
338
|
|
|
339
339
|
export const postTokenRevoke: Handler = (_req, params) => {
|
|
340
|
-
if (!revokeToken(params.jti
|
|
340
|
+
if (!revokeToken(params.jti!, "admin")) return error("token not found or already revoked", 404);
|
|
341
341
|
return json({ ok: true });
|
|
342
342
|
};
|
package/src/runtime-tokens.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createToken, getTokenProfile, revokeToken } from "./token-db";
|
|
1
|
+
import { createToken, getTokenProfile, hardenStaleRevocation, revokeToken } from "./token-db";
|
|
2
2
|
import { verifyComponentTokenAllowExpired } from "./security";
|
|
3
3
|
import { spawnGrantForProfile } from "./config-store";
|
|
4
4
|
import type { TokenRecord } from "./types";
|
|
@@ -26,20 +26,22 @@ interface RuntimeTokenResult {
|
|
|
26
26
|
// a long-dead token to a sane window — a live session re-mints well within this.
|
|
27
27
|
const REMINT_MAX_EXPIRED_AGE_SECONDS = 30 * 24 * 60 * 60; // 30 days
|
|
28
28
|
|
|
29
|
-
// Orchestrator-mediated re-mint: given a runner's current (possibly expired
|
|
30
|
-
// and the calling orchestrator's id, verify the token belongs
|
|
31
|
-
// orchestrator and issue a fresh provider-agent token cloning
|
|
32
|
-
// token is revoked
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// runner self-heal design: the relay
|
|
36
|
-
//
|
|
29
|
+
// Orchestrator-mediated re-mint: given a runner's current (possibly expired OR
|
|
30
|
+
// stale-revoked) token and the calling orchestrator's id, verify the token belongs
|
|
31
|
+
// to a runner of THIS orchestrator and issue a fresh provider-agent token cloning
|
|
32
|
+
// its scope. The old token is then hard-revoked so it cannot be re-minted twice.
|
|
33
|
+
// The caller MUST already be authenticated/authorized as the orchestrator — this
|
|
34
|
+
// only establishes that the presented token is a genuine runner token owned by that
|
|
35
|
+
// orchestrator. See approach #1/#2 in the runner self-heal design (#484): the relay
|
|
36
|
+
// stays strict, the orchestrator's standing privilege is the authorization, the
|
|
37
|
+
// signed token is the identity. A token revoked as "stale" (the restart-strand of a
|
|
38
|
+
// still-live process) is heal-able here; a hard kill (admin/rotation/removal) is not.
|
|
37
39
|
export function reissueRunnerRuntimeToken(input: {
|
|
38
40
|
expiredToken: string;
|
|
39
41
|
orchestratorId: string;
|
|
40
42
|
createdBy?: string;
|
|
41
43
|
}): RuntimeTokenResult | { error: string } {
|
|
42
|
-
const payload = verifyComponentTokenAllowExpired(input.expiredToken);
|
|
44
|
+
const payload = verifyComponentTokenAllowExpired(input.expiredToken, { allowStaleRevoked: true });
|
|
43
45
|
if (!payload) return { error: "invalid or revoked runner token" };
|
|
44
46
|
if (payload.role !== "provider" || !payload.sub.startsWith("runner:")) {
|
|
45
47
|
return { error: "not a runner token" };
|
|
@@ -60,7 +62,9 @@ export function reissueRunnerRuntimeToken(input: {
|
|
|
60
62
|
constraints: payload.constraints,
|
|
61
63
|
createdBy: input.createdBy ?? `remint:${payload.jti ?? "unknown"}`,
|
|
62
64
|
});
|
|
63
|
-
|
|
65
|
+
// Hard-revoke the consumed token so it can't mint again. revokeToken no-ops on an
|
|
66
|
+
// already-revoked (stale) token, so harden that case explicitly to "rotation".
|
|
67
|
+
if (payload.jti && !revokeToken(payload.jti, "rotation")) hardenStaleRevocation(payload.jti);
|
|
64
68
|
return reissued;
|
|
65
69
|
}
|
|
66
70
|
|
package/src/security.ts
CHANGED
|
@@ -339,20 +339,60 @@ export function verifyComponentToken(token: string, nowSeconds = Math.floor(Date
|
|
|
339
339
|
return payload;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// Why a token was revoked (#484). The reason decides whether an orchestrator may
|
|
343
|
+
// re-mint it: a "stale" revocation (relay marked an agent stale across its own
|
|
344
|
+
// restart/reconnect) is recoverable for a process the orchestrator can confirm is
|
|
345
|
+
// alive; every other reason is a hard kill. This is the single home for the
|
|
346
|
+
// taxonomy — callers import the type, never re-declare the strings.
|
|
347
|
+
export type TokenRevokeReason = "stale" | "rotation" | "admin" | "agent-removed" | "pruned";
|
|
348
|
+
|
|
349
|
+
// The ONLY revocation reason an orchestrator-mediated re-mint may heal. A genuine
|
|
350
|
+
// kill — admin revoke, rotation, agent removal/prune, or a legacy NULL reason —
|
|
351
|
+
// stays an unconditional hard kill.
|
|
352
|
+
const REMINTABLE_REVOKE_REASONS: ReadonlySet<TokenRevokeReason> = new Set(["stale"]);
|
|
353
|
+
|
|
354
|
+
// Returns undefined if the token is not revoked, otherwise its reason string
|
|
355
|
+
// (NULL/legacy → null, treated as a hard kill).
|
|
356
|
+
function tokenRevocationReason(jti: string): string | null | undefined {
|
|
357
|
+
try {
|
|
358
|
+
const row = getDb().query("SELECT revoked_at, revoked_reason FROM tokens WHERE jti = ?").get(jti) as
|
|
359
|
+
| { revoked_at?: number | null; revoked_reason?: string | null }
|
|
360
|
+
| undefined;
|
|
361
|
+
if (!row?.revoked_at) return undefined;
|
|
362
|
+
return row.revoked_reason ?? null;
|
|
363
|
+
} catch {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
342
368
|
// Verify a component token's signature and revocation WITHOUT enforcing expiry.
|
|
343
369
|
// Used only for orchestrator-mediated runner-token re-minting: an expired runner
|
|
344
370
|
// token is unforgeable proof of identity (it is HMAC-signed by this relay), even
|
|
345
371
|
// though it can no longer authenticate a request. The caller (an authenticated
|
|
346
372
|
// orchestrator) supplies the authorization; this just establishes which runner.
|
|
347
|
-
// Revoked tokens are
|
|
348
|
-
|
|
373
|
+
// Revoked tokens are rejected as a hard kill EXCEPT when the caller opts into
|
|
374
|
+
// `allowStaleRevoked` AND the token was revoked as "stale" (#484) — that case is the
|
|
375
|
+
// restart-strand of a still-live process, which the orchestrator heals by re-minting.
|
|
376
|
+
export function verifyComponentTokenAllowExpired(
|
|
377
|
+
token: string,
|
|
378
|
+
options?: { allowStaleRevoked?: boolean },
|
|
379
|
+
): ComponentToken | null {
|
|
349
380
|
const parts = token.split(".");
|
|
350
381
|
if (parts.length !== 3) return null;
|
|
351
382
|
const [headerRaw, payloadRaw, signature] = parts as [string, string, string];
|
|
352
383
|
if (!safeEqual(signature, hmac(`${headerRaw}.${payloadRaw}`))) return null;
|
|
353
384
|
const payload = parseBase64urlJson(payloadRaw);
|
|
354
385
|
if (!isComponentToken(payload)) return null;
|
|
355
|
-
if (payload.jti
|
|
386
|
+
if (payload.jti) {
|
|
387
|
+
const reason = tokenRevocationReason(payload.jti);
|
|
388
|
+
if (reason !== undefined) {
|
|
389
|
+
const remintable =
|
|
390
|
+
options?.allowStaleRevoked === true &&
|
|
391
|
+
typeof reason === "string" &&
|
|
392
|
+
REMINTABLE_REVOKE_REASONS.has(reason as TokenRevokeReason);
|
|
393
|
+
if (!remintable) return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
356
396
|
return payload;
|
|
357
397
|
}
|
|
358
398
|
|
|
@@ -22,6 +22,13 @@ export interface LandedIssueLifecycleInput {
|
|
|
22
22
|
mergedSha?: string;
|
|
23
23
|
subject?: string;
|
|
24
24
|
source?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The land happened via a pull request (the squash/merge subject carries a
|
|
27
|
+
* trailing `(#PR)` that GitHub appends — the PR number, NOT an issue). Bare-backlink
|
|
28
|
+
* parsing is suppressed in this mode so relay never mistakes the PR number for an
|
|
29
|
+
* issue to close; GitHub-native keywords + durable association still apply (#482).
|
|
30
|
+
*/
|
|
31
|
+
prLand?: boolean;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export interface ReopenIssueLifecycleInput {
|
|
@@ -44,8 +51,10 @@ export interface IssueLifecycleOptions {
|
|
|
44
51
|
log?: LogFn;
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
export type LandIssueRefSource = "association" | "commit" | "backlink";
|
|
55
|
+
|
|
47
56
|
export type CloseIssueLifecycleResult =
|
|
48
|
-
| { issueRef: IssueRef; action: "closed"; source:
|
|
57
|
+
| { issueRef: IssueRef; action: "closed"; source: LandIssueRefSource; landKey: string }
|
|
49
58
|
| { issueRef: IssueRef; action: "skipped"; reason: "already-closed" | "already-processed"; landKey: string }
|
|
50
59
|
| { issueRef: IssueRef; action: "failed"; error: string; landKey: string };
|
|
51
60
|
|
|
@@ -73,6 +82,51 @@ function defaultIssueRepo(): string | undefined {
|
|
|
73
82
|
return process.env.AGENT_RELAY_DEFAULT_ISSUE_REPO || process.env.GITHUB_REPOSITORY || undefined;
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
/**
|
|
86
|
+
* `owner/repo` out of any git remote URL form: `git@github.com:owner/repo.git`,
|
|
87
|
+
* `https://github.com/owner/repo.git`, `ssh://git@host/owner/repo`, trailing slash/`.git`
|
|
88
|
+
* optional. Pure — exported for tests.
|
|
89
|
+
*/
|
|
90
|
+
export function parseOwnerRepoFromRemote(url: string | undefined): string | undefined {
|
|
91
|
+
if (!url) return undefined;
|
|
92
|
+
const match = url.trim().match(/[:/]([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/);
|
|
93
|
+
return match?.[1];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let remoteRepoCache: Map<string, string | undefined> | undefined;
|
|
97
|
+
|
|
98
|
+
function repoFromGitRemote(repoRoot: string | undefined): string | undefined {
|
|
99
|
+
if (!repoRoot) return undefined;
|
|
100
|
+
if (!remoteRepoCache) remoteRepoCache = new Map();
|
|
101
|
+
if (remoteRepoCache.has(repoRoot)) return remoteRepoCache.get(repoRoot);
|
|
102
|
+
let repo: string | undefined;
|
|
103
|
+
try {
|
|
104
|
+
const proc = Bun.spawnSync(["git", "-C", repoRoot, "remote", "get-url", "origin"], {
|
|
105
|
+
stdin: "ignore",
|
|
106
|
+
stdout: "pipe",
|
|
107
|
+
stderr: "ignore",
|
|
108
|
+
});
|
|
109
|
+
if (proc.exitCode === 0) repo = parseOwnerRepoFromRemote(Buffer.from(proc.stdout).toString("utf8"));
|
|
110
|
+
} catch {
|
|
111
|
+
repo = undefined;
|
|
112
|
+
}
|
|
113
|
+
remoteRepoCache.set(repoRoot, repo);
|
|
114
|
+
return repo;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function __resetRemoteRepoCacheForTests(): void {
|
|
118
|
+
remoteRepoCache = undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The repo a bare `#NNN` in a land's commit subject resolves against. Derived per-workspace
|
|
123
|
+
* from its own git remote so a single relay serving many repos closes the RIGHT repo's issue;
|
|
124
|
+
* env (`AGENT_RELAY_DEFAULT_ISSUE_REPO`/`GITHUB_REPOSITORY`) is an override/fallback.
|
|
125
|
+
*/
|
|
126
|
+
function landDefaultRepo(workspace: LifecycleWorkspace): string | undefined {
|
|
127
|
+
return repoFromGitRemote(workspace.repoRoot) ?? defaultIssueRepo();
|
|
128
|
+
}
|
|
129
|
+
|
|
76
130
|
function cleanText(value: string | undefined): string | undefined {
|
|
77
131
|
const cleaned = value?.trim();
|
|
78
132
|
return cleaned || undefined;
|
|
@@ -116,33 +170,57 @@ const CLOSING_KEYWORD_RE = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+([^\n\
|
|
|
116
170
|
const ISSUE_REF_AT_START_RE = /^(https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/issues\/[0-9]+(?:[/?#][^\s,)]*)?|[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+#[0-9]+|#[0-9]+)/;
|
|
117
171
|
const REF_SEPARATOR_RE = /^(?:\s*,\s*|\s+(?:and|&)\s+)/i;
|
|
118
172
|
|
|
173
|
+
// A run of issue refs ("#1, #2 and owner/repo#3") starting at the head of `segment`.
|
|
174
|
+
// Bare refs resolve against `fallbackDefaultRepo`; unresolvable ones are skipped so a
|
|
175
|
+
// land never fails here.
|
|
176
|
+
function collectRefRun(segment: string, fallbackDefaultRepo: string | undefined, into: IssueRef[]): void {
|
|
177
|
+
let rest = segment;
|
|
178
|
+
while (rest.trim()) {
|
|
179
|
+
rest = rest.trimStart();
|
|
180
|
+
const refMatch = rest.match(ISSUE_REF_AT_START_RE);
|
|
181
|
+
if (!refMatch) break;
|
|
182
|
+
try {
|
|
183
|
+
into.push(resolveIssueRefFromString({ ref: refMatch[1]!, defaultRepo: fallbackDefaultRepo }));
|
|
184
|
+
} catch {
|
|
185
|
+
// Invalid or bare-without-default refs are ignored; the land itself must never fail here.
|
|
186
|
+
}
|
|
187
|
+
rest = rest.slice(refMatch[0].length);
|
|
188
|
+
const separator = rest.match(REF_SEPARATOR_RE);
|
|
189
|
+
if (!separator) break;
|
|
190
|
+
rest = rest.slice(separator[0].length);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
119
194
|
export function parseClosingIssueRefs(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): IssueRef[] {
|
|
120
195
|
if (!text) return [];
|
|
121
196
|
const refs: IssueRef[] = [];
|
|
122
|
-
for (const match of text.matchAll(CLOSING_KEYWORD_RE))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (!separator) break;
|
|
136
|
-
rest = rest.slice(separator[0].length);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
197
|
+
for (const match of text.matchAll(CLOSING_KEYWORD_RE)) collectRefRun(match[1] ?? "", fallbackDefaultRepo, refs);
|
|
198
|
+
return issueRefMap(refs);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Parenthesized backlink groups: the trailing `(#NNN)` convention agents (and a squash merge)
|
|
202
|
+
// stamp on a subject without a closing keyword. Non-issue parens (conventional-commit scopes
|
|
203
|
+
// like `fix(tokens)`, notes like `(see also)`) carry no `#` and fall out.
|
|
204
|
+
const BACKLINK_GROUP_RE = /\(([^()]*#[0-9]+[^()]*)\)/g;
|
|
205
|
+
|
|
206
|
+
export function parseBacklinkIssueRefs(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): IssueRef[] {
|
|
207
|
+
if (!text) return [];
|
|
208
|
+
const refs: IssueRef[] = [];
|
|
209
|
+
for (const group of text.matchAll(BACKLINK_GROUP_RE)) collectRefRun(group[1] ?? "", fallbackDefaultRepo, refs);
|
|
139
210
|
return issueRefMap(refs);
|
|
140
211
|
}
|
|
141
212
|
|
|
142
|
-
export function resolveIssueRefsForLand(input: LandedIssueLifecycleInput): { source:
|
|
213
|
+
export function resolveIssueRefsForLand(input: LandedIssueLifecycleInput): { source: LandIssueRefSource; refs: IssueRef[] } {
|
|
143
214
|
const associated = associatedIssueRefs(input.workspace);
|
|
144
215
|
if (associated.length > 0) return { source: "association", refs: associated };
|
|
145
|
-
|
|
216
|
+
const fallbackDefaultRepo = landDefaultRepo(input.workspace);
|
|
217
|
+
const keyworded = parseClosingIssueRefs(input.subject, fallbackDefaultRepo);
|
|
218
|
+
if (keyworded.length > 0) return { source: "commit", refs: keyworded };
|
|
219
|
+
// Bare `(#NNN)` is closing intent in relay-land mode (no PR), but the SAME shape is the
|
|
220
|
+
// PR number GitHub appends to a squash subject — never close that. Suppress on PR lands;
|
|
221
|
+
// those rely on GitHub-native keywords + durable association instead (#482).
|
|
222
|
+
if (input.prLand) return { source: "commit", refs: [] };
|
|
223
|
+
return { source: "backlink", refs: parseBacklinkIssueRefs(input.subject, fallbackDefaultRepo) };
|
|
146
224
|
}
|
|
147
225
|
|
|
148
226
|
function closeAuditComment(input: LandedIssueLifecycleInput): string {
|
|
@@ -152,7 +230,7 @@ function closeAuditComment(input: LandedIssueLifecycleInput): string {
|
|
|
152
230
|
|
|
153
231
|
async function closeIssueRefForLand(
|
|
154
232
|
issueRef: IssueRef,
|
|
155
|
-
source:
|
|
233
|
+
source: LandIssueRefSource,
|
|
156
234
|
input: LandedIssueLifecycleInput,
|
|
157
235
|
opts: IssueLifecycleOptions,
|
|
158
236
|
): Promise<CloseIssueLifecycleResult> {
|
|
@@ -237,7 +315,7 @@ async function reopenIssuesForRevert(input: LandedIssueLifecycleInput, opts: Iss
|
|
|
237
315
|
}
|
|
238
316
|
|
|
239
317
|
export async function handleLandedIssueLifecycle(input: LandedIssueLifecycleInput, opts: IssueLifecycleOptions = {}): Promise<{
|
|
240
|
-
source:
|
|
318
|
+
source: LandIssueRefSource;
|
|
241
319
|
closed: CloseIssueLifecycleResult[];
|
|
242
320
|
reopened: ReopenIssueLifecycleResult[];
|
|
243
321
|
skipped: string[];
|
package/src/token-db.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDb } from "./db";
|
|
3
|
-
import { signComponentToken } from "./security";
|
|
3
|
+
import { signComponentToken, type TokenRevokeReason } from "./security";
|
|
4
4
|
import type { ComponentToken, CreateTokenProfileInput, TokenConstraints, TokenProfile, TokenRecord, UpdateTokenProfileInput } from "./types";
|
|
5
5
|
|
|
6
6
|
interface TokenRow {
|
|
@@ -13,6 +13,7 @@ interface TokenRow {
|
|
|
13
13
|
issued_at: number;
|
|
14
14
|
expires_at: number | null;
|
|
15
15
|
revoked_at: number | null;
|
|
16
|
+
revoked_reason: string | null;
|
|
16
17
|
created_by: string | null;
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -180,8 +181,26 @@ export function getToken(jti: string): TokenRecord | null {
|
|
|
180
181
|
return row ? rowToToken(row) : null;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
// Revoke a token, optionally tagging WHY (#484). The reason gates self-heal:
|
|
185
|
+
// `verifyComponentTokenAllowExpired` lets the orchestrator re-mint a token revoked
|
|
186
|
+
// as "stale" (the restart-strand case) but never one revoked as a hard kill
|
|
187
|
+
// (admin/rotation/agent-removed/pruned). A NULL reason (legacy/unspecified) is a
|
|
188
|
+
// hard kill. Only flips an un-revoked token — already-revoked rows are left as-is
|
|
189
|
+
// (so the 404-on-double-revoke contract in routes/tokens.ts holds).
|
|
190
|
+
export function revokeToken(jti: string, reason?: TokenRevokeReason): boolean {
|
|
191
|
+
return getDb()
|
|
192
|
+
.query("UPDATE tokens SET revoked_at = ?, revoked_reason = ? WHERE jti = ? AND revoked_at IS NULL")
|
|
193
|
+
.run(Math.floor(Date.now() / 1000), reason ?? null, jti).changes > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Downgrade a "stale"-revoked token to a hard "rotation" revocation so it can no
|
|
197
|
+
// longer be re-minted (#484). Called after a successful orchestrator re-mint: the
|
|
198
|
+
// fresh token now carries the identity, and the consumed stale token must not be
|
|
199
|
+
// re-mintable a second time (bounds replay of a captured stale token).
|
|
200
|
+
export function hardenStaleRevocation(jti: string): void {
|
|
201
|
+
getDb()
|
|
202
|
+
.query("UPDATE tokens SET revoked_reason = 'rotation' WHERE jti = ? AND revoked_reason = 'stale'")
|
|
203
|
+
.run(jti);
|
|
185
204
|
}
|
|
186
205
|
|
|
187
206
|
export function renewToken(input: {
|
|
@@ -201,7 +220,7 @@ export function renewToken(input: {
|
|
|
201
220
|
profileId: previous.profileId,
|
|
202
221
|
createdBy: input.createdBy ?? `renew:${previous.jti}`,
|
|
203
222
|
});
|
|
204
|
-
revokeToken(previous.jti);
|
|
223
|
+
revokeToken(previous.jti, "rotation");
|
|
205
224
|
return { ...renewed, previous };
|
|
206
225
|
}
|
|
207
226
|
|
|
@@ -342,6 +361,7 @@ function rowToToken(row: TokenRow): TokenRecord {
|
|
|
342
361
|
issuedAt: row.issued_at,
|
|
343
362
|
expiresAt: row.expires_at ?? undefined,
|
|
344
363
|
revokedAt: row.revoked_at ?? undefined,
|
|
364
|
+
revokedReason: row.revoked_reason ?? undefined,
|
|
345
365
|
createdBy: row.created_by ?? undefined,
|
|
346
366
|
};
|
|
347
367
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{B as t,L as n,Ln as r,Mt as i,O as a,Yn as o,gn as s,k as c,m as l,z as u}from"./lucide-react-DWYjU-AF.js";import{a as d,s as f,t as p}from"./store-Buwkqv_7.js";import{U as m,h}from"./display-DzNvOu1j.js";import{t as g}from"./badge-CZhIdlTJ.js";import{t as _}from"./button-2taQeNHW.js";import{t as v}from"./input-BsNqlONJ.js";import{P as y,n as b,p as x,r as S}from"./index-CCp1V35c.js";import{a as C,o as w,r as T,t as E}from"./card-DzIsAI41.js";var D=e(o(),1),O=r(),k={scheduled:`bg-blue-500/10 text-blue-400 border-blue-500/20`,dispatching:`bg-sky-500/10 text-sky-400 border-sky-500/20`,waiting_agent:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,running:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,succeeded:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,failed:`bg-red-500/10 text-red-400 border-red-500/20`,canceled:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,timed_out:`bg-red-500/10 text-red-400 border-red-500/20`},A=new Set([`scheduled`,`dispatching`,`waiting_agent`,`running`]);function j(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`budget elapsed`:e.status}function M(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`:k[e.status]||``}function N(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?void 0:e.error}function P(e){return A.has(e.status)}function F(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function I(e){return typeof e==`string`&&e.length>0?e:void 0}function L(e){let t=Math.max(0,Math.round(e/1e3)),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n?`${n}h ${r}m`:r?`${r}m ${i}s`:`${i}s`}function R(e){return F(e.meta?.runtimeBudgetDeadlineAt)??F(e.result?.deadlineAt)}function z(e,t){if(!e.startedAt)return`-`;let n=e.finishedAt||(P(e)?t:e.updatedAt);return L(Number(n)-Number(e.startedAt))}function B(e,t){let n=R(e);if(!(!n||!P(e)))return L(n-t)}function V(e){return e.status===`timed_out`&&e.result?.runtimeBudgetElapsed===!0?`runtime budget elapsed`:e.error}function H(e,t,n){let r=e.targetAgentId||e.spawnedAgentId||I(e.meta?.runtimeAgentId),i=r?t.find(e=>e.id===r):void 0,a=r?n.flatMap(e=>e.managedAgents.map(t=>({orchestrator:e,managedAgent:t}))).find(({managedAgent:e})=>e.agentId===r):void 0,o=I(e.meta?.terminalSession)||I(e.meta?.tmuxSession)||I(e.meta?.sessionName)||I(i?.meta?.terminalSession)||I(i?.meta?.tmuxSession)||I(i?.meta?.sessionName)||a?.managedAgent.terminalSession||a?.managedAgent.tmuxSession||a?.managedAgent.sessionName,s=I(e.meta?.orchestratorId)||a?.orchestrator.id||e.orchestratorId;return o?{orchestratorId:s,session:o}:void 0}function U(e=``){return{name:``,description:``,enabled:!0,schedule:`0 8 * * *`,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||`UTC`,orchestratorId:e,targetMode:`existing_agent`,provider:`codex`,model:d.codex.defaultModel||``,effort:``,profile:`default-relay`,label:``,tags:``,capabilities:``,ifNoMatch:`fail`,cwd:``,approvalMode:`guarded`,workspaceMode:`inherit`,keepAlive:!1,budgetMinutes:``,warningMinutes:``,warningMessage:``,concurrencyPolicy:`skip`,title:``,body:``,severity:`info`,dedupeKey:``,externalUrl:``}}function W(e){let t=e.split(`,`).map(e=>e.trim()).filter(Boolean);return t.length?t:void 0}function G(e){let t=e.targetPolicy;return{...U(e.orchestratorId),id:e.id,name:e.name,description:e.description||``,enabled:e.enabled,schedule:e.schedule,timezone:e.timezone,orchestratorId:e.orchestratorId,targetMode:t.mode,provider:t.mode===`on_demand_agent`?t.provider:t.selector.provider||`codex`,model:t.mode===`on_demand_agent`&&(t.model||d[t.provider].defaultModel)||``,effort:t.mode===`on_demand_agent`&&t.effort||``,profile:t.mode===`on_demand_agent`&&t.profile||`default-relay`,label:t.mode===`existing_agent`&&t.selector.label||``,tags:t.mode===`existing_agent`?(t.selector.tags||[]).join(`, `):``,capabilities:t.mode===`existing_agent`?(t.selector.capabilities||[]).join(`, `):``,ifNoMatch:t.mode===`existing_agent`&&t.ifNoMatch||`fail`,cwd:t.mode===`on_demand_agent`&&t.cwd||``,approvalMode:t.mode===`on_demand_agent`&&t.approvalMode||`guarded`,workspaceMode:t.mode===`on_demand_agent`&&t.workspaceMode||`inherit`,keepAlive:t.mode===`on_demand_agent`?!!t.keepAlive:!1,budgetMinutes:t.mode===`on_demand_agent`&&t.runtimeBudget?String(Math.round(t.runtimeBudget.maxRuntimeMs/6e4)):``,warningMinutes:t.mode===`on_demand_agent`&&t.runtimeBudget?.warnAtMs!==void 0?String(Math.round(t.runtimeBudget.warnAtMs/6e4)):``,warningMessage:t.mode===`on_demand_agent`&&t.runtimeBudget?.warningMessage||``,concurrencyPolicy:e.concurrencyPolicy,title:e.taskTemplate.title,body:e.taskTemplate.body,severity:e.taskTemplate.severity||`info`,dedupeKey:e.taskTemplate.dedupeKey||``,externalUrl:e.taskTemplate.externalUrl||``}}function K(e){let t=Number(e.budgetMinutes),n=Number(e.warningMinutes),r=e.targetMode===`on_demand_agent`&&e.budgetMinutes.trim()&&Number.isFinite(t)?{maxRuntimeMs:Math.round(t*6e4),warnAtMs:e.warningMinutes.trim()&&Number.isFinite(n)?Math.round(n*6e4):void 0,warningMessage:e.warningMessage.trim()||void 0}:void 0,i=e.targetMode===`existing_agent`?{mode:`existing_agent`,selector:{provider:e.provider||void 0,label:e.label.trim()||void 0,tags:W(e.tags),capabilities:W(e.capabilities)},ifNoMatch:e.ifNoMatch}:{mode:`on_demand_agent`,provider:e.provider,model:e.model||void 0,effort:e.effort?e.effort:void 0,profile:e.profile||void 0,cwd:e.cwd.trim()||void 0,approvalMode:e.approvalMode,workspaceMode:e.workspaceMode,keepAlive:e.keepAlive,runtimeBudget:r};return{kind:`scheduled_task`,name:e.name.trim(),description:e.description.trim()||void 0,enabled:e.enabled,schedule:e.schedule.trim(),timezone:e.timezone.trim()||`UTC`,catchUpPolicy:`skip`,concurrencyPolicy:e.concurrencyPolicy,orchestratorId:e.orchestratorId,targetPolicy:i,taskTemplate:{title:e.title.trim(),body:e.body.trim(),severity:e.severity,dedupeKey:e.dedupeKey.trim()||void 0,externalUrl:e.externalUrl.trim()||void 0}}}function q(){let e=f(),t=p(e=>e.automations),r=p(e=>e.automationRuns),i=p(e=>e.orchestrators),a=p(e=>e.agents),o=p(e=>e.apiCall),c=p(e=>e.showError),l=p(e=>e.openConfirm),u=p(e=>e.fetchAutomations),d=p(e=>e.fetchAutomationRuns),m=p(e=>e.fetchTasks),h=p(e=>e.agentProfiles),[v,y]=(0,D.useState)(``),[b,x]=(0,D.useState)(()=>U(i[0]?.id||``)),[S,C]=(0,D.useState)(!1),[w,T]=(0,D.useState)(null),E=t.find(e=>e.id===v),k=i.filter(e=>e.status===`online`),A=r.filter(P);(0,D.useEffect)(()=>{Promise.all([u(),d()])},[]),(0,D.useEffect)(()=>{!b.orchestratorId&&i[0]&&x(e=>({...e,orchestratorId:i[0].id}))},[i]);let j=(0,D.useMemo)(()=>{let e=new Map;for(let t of r){let n=e.get(t.automationId)||[];n.push(t),e.set(t.automationId,n)}return e},[r]);function M(){y(``),x(U(i[0]?.id||``))}function N(e){y(e.id),x(G(e))}async function F(){C(!0);try{let e=K(b);if(b.id)await o(`PATCH`,`/automations/`+encodeURIComponent(b.id),e);else{let t=await o(`POST`,`/automations`,e);y(t.id),x(G(t))}await Promise.all([u(),d()])}catch(e){c(`Save Failed`,e.message)}finally{C(!1)}}async function I(e,t){try{await o(`PATCH`,`/automations/`+encodeURIComponent(e.id),t),await u()}catch(e){c(`Update Failed`,e.message)}}async function L(e){try{await o(`POST`,`/automations/`+encodeURIComponent(e.id)+`/run`),await Promise.all([d(),m()])}catch(e){c(`Run Failed`,e.message)}}function R(e){l(`Delete Automation`,`Delete automation "${e.name}"?`,async()=>{try{await o(`DELETE`,`/automations/`+encodeURIComponent(e.id)),v===e.id&&M(),await Promise.all([u(),d()])}catch(e){c(`Delete Failed`,e.message)}})}return(0,O.jsxs)(`div`,{className:`space-y-4`,children:[(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,O.jsx)(s,{className:`w-5 h-5 text-muted-foreground`}),(0,O.jsxs)(`div`,{children:[(0,O.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Automation`}),(0,O.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Scheduled tasks`})]}),(0,O.jsxs)(`div`,{className:`ml-auto flex flex-wrap items-center gap-2`,children:[(0,O.jsxs)(g,{variant:`outline`,className:`text-xs`,children:[t.length,` schedules`]}),(0,O.jsxs)(g,{variant:`outline`,className:`text-xs`,children:[A.length,` running`]}),(0,O.jsxs)(_,{size:`sm`,className:`gap-1`,onClick:M,children:[(0,O.jsx)(n,{className:`w-3.5 h-3.5`}),`New`]})]})]}),A.length>0&&(0,O.jsx)(J,{runs:A,automations:t,agents:a,orchestrators:i,now:e,logRunId:w,onToggleLogs:e=>T(t=>t===e?null:e)}),(0,O.jsxs)(`div`,{className:`grid gap-4 xl:grid-cols-[minmax(0,1fr)_420px]`,children:[(0,O.jsx)(`div`,{className:`space-y-3`,children:t.length===0?(0,O.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No automations`}):t.map(t=>(0,O.jsx)(Y,{automation:t,runs:j.get(t.id)||[],now:e,selected:v===t.id,onEdit:()=>N(t),onRun:()=>L(t),onToggle:()=>I(t,{enabled:!t.enabled}),onDelete:()=>R(t)},t.id))}),(0,O.jsx)(X,{form:b,agentProfiles:h,selected:E,saving:S,orchestrators:k.length?k:i,onChange:e=>x(t=>({...t,...e})),onSave:F})]}),(0,O.jsx)(Q,{runs:r,automations:t,agents:a,orchestrators:i,now:e,logRunId:w,onToggleLogs:e=>T(t=>t===e?null:e)})]})}function J({runs:e,automations:t,agents:n,orchestrators:r,now:a,logRunId:o,onToggleLogs:s}){let c=new Map(t.map(e=>[e.id,e.name]));return(0,O.jsxs)(`div`,{className:`rounded-lg border border-border bg-card/40`,children:[(0,O.jsx)(`div`,{className:`border-b border-border px-3 py-2 text-sm font-medium`,children:`Active runs`}),(0,O.jsx)(`div`,{className:`divide-y divide-border`,children:e.map(e=>{let t=H(e,n,r),l=B(e,a);return(0,O.jsxs)(`div`,{className:`space-y-3 px-3 py-3`,children:[(0,O.jsxs)(`div`,{className:`grid gap-3 text-xs md:grid-cols-[minmax(12rem,1fr)_7rem_7rem_7rem_8rem_auto] md:items-center`,children:[(0,O.jsxs)(`div`,{className:`min-w-0`,children:[(0,O.jsx)(`div`,{className:`truncate text-sm font-medium`,children:c.get(e.automationId)||e.automationId.slice(0,12)}),(0,O.jsx)(`div`,{className:`truncate font-mono text-muted-foreground`,children:e.targetAgentId||e.spawnedAgentId||I(e.meta?.onDemandLabel)||`-`})]}),(0,O.jsx)(g,{variant:`outline`,className:`border text-[10px] w-fit ${M(e)}`,children:j(e)}),(0,O.jsxs)(`div`,{children:[(0,O.jsx)(`span`,{className:`text-muted-foreground`,children:`elapsed `}),z(e,a)]}),(0,O.jsxs)(`div`,{children:[(0,O.jsx)(`span`,{className:`text-muted-foreground`,children:`left `}),l||`-`]}),(0,O.jsx)(`div`,{className:`font-mono text-muted-foreground`,children:e.taskId?`task #${e.taskId}`:`-`}),t&&(0,O.jsxs)(_,{type:`button`,size:`sm`,variant:`outline`,className:`h-7 gap-1 text-xs`,onClick:()=>s(e.id),children:[(0,O.jsx)(i,{className:`w-3 h-3`}),`Logs`]})]}),o===e.id&&t&&(0,O.jsx)(x,{orchestratorId:t.orchestratorId,session:t.session})]},e.id)})})]})}function Y({automation:e,runs:n,now:r,selected:i,onEdit:a,onRun:o,onToggle:s,onDelete:c}){let d=n[0],f=e.targetPolicy,p=f.mode===`existing_agent`?[f.selector.provider,f.selector.label?`label:${f.selector.label}`:``,...(f.selector.tags||[]).map(e=>`tag:${e}`),...(f.selector.capabilities||[]).map(e=>`cap:${e}`)].filter(Boolean).join(` `):`${f.provider} on demand`;return(0,O.jsxs)(E,{className:`${i?`ring-1 ring-primary/50`:``} ${e.enabled?``:`opacity-60`}`,children:[(0,O.jsx)(C,{className:`px-4 py-3`,children:(0,O.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,O.jsx)(y,{checked:e.enabled,onCheckedChange:s,className:`mt-0.5`}),(0,O.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,O.jsx)(w,{className:`text-sm truncate`,children:e.name}),(0,O.jsxs)(`div`,{className:`mt-1 flex flex-wrap gap-1.5 text-xs text-muted-foreground`,children:[(0,O.jsx)(`span`,{className:`font-mono`,children:e.schedule}),(0,O.jsx)(`span`,{children:e.timezone}),(0,O.jsx)(`span`,{children:e.orchestratorId}),(0,O.jsx)(`span`,{className:`truncate max-w-[28rem]`,children:p})]})]}),(0,O.jsxs)(`div`,{className:`flex gap-1`,children:[(0,O.jsx)(_,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Run now`,onClick:o,children:(0,O.jsx)(u,{className:`w-3.5 h-3.5`})}),(0,O.jsx)(_,{size:`icon`,variant:`ghost`,className:`h-7 w-7`,title:`Edit`,onClick:a,children:(0,O.jsx)(t,{className:`w-3.5 h-3.5`})}),(0,O.jsx)(_,{size:`icon`,variant:`ghost`,className:`h-7 w-7 text-red-400 hover:text-red-300`,title:`Delete`,onClick:c,children:(0,O.jsx)(l,{className:`w-3.5 h-3.5`})})]})]})}),(0,O.jsx)(T,{className:`px-4 pb-3 pt-0`,children:(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2 text-xs text-muted-foreground`,children:[(0,O.jsxs)(`span`,{title:e.nextRunAt?h(e.nextRunAt):void 0,children:[`next `,e.nextRunAt?m(r,e.nextRunAt):`disabled`]}),d&&(0,O.jsx)(g,{variant:`outline`,className:`border text-[10px] ${M(d)}`,children:j(d)}),d?.taskId&&(0,O.jsxs)(`span`,{children:[`task #`,d.taskId]}),d&&N(d)&&(0,O.jsx)(`span`,{className:`text-red-400 truncate`,children:N(d)})]})})]})}function X({form:e,selected:t,saving:n,orchestrators:r,agentProfiles:i,onChange:o,onSave:s}){let l=r.find(t=>t.id===e.orchestratorId)?.providers||[],u=d[e.provider]?.models||[],f=u.find(t=>t.alias===e.model)?.efforts||[];function p(t){let n=r.find(e=>e.id===t)?.providers[0]||e.provider;o({orchestratorId:t,provider:n,model:d[n]?.defaultModel||``,effort:``})}function m(e){o({provider:e,model:d[e]?.defaultModel||``,effort:``})}return(0,O.jsxs)(`div`,{className:`rounded-lg border border-border bg-card p-4 space-y-4 h-fit xl:sticky xl:top-4`,children:[(0,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`h3`,{className:`text-sm font-semibold`,children:t?`Edit schedule`:`New schedule`}),(0,O.jsx)(g,{variant:e.enabled?`default`:`secondary`,className:`ml-auto text-[10px]`,children:e.enabled?`enabled`:`disabled`})]}),(0,O.jsxs)(`div`,{className:`grid gap-3`,children:[(0,O.jsx)(Z,{label:`Name`,children:(0,O.jsx)(v,{value:e.name,onChange:e=>o({name:e.target.value})})}),(0,O.jsx)(Z,{label:`Description`,children:(0,O.jsx)(v,{value:e.description,onChange:e=>o({description:e.target.value})})}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Cron`,children:(0,O.jsx)(v,{value:e.schedule,onChange:e=>o({schedule:e.target.value}),className:`font-mono`})}),(0,O.jsx)(Z,{label:`Timezone`,children:(0,O.jsx)(v,{value:e.timezone,onChange:e=>o({timezone:e.target.value})})})]}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Orchestrator`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.orchestratorId,onChange:e=>p(e.target.value),children:[(0,O.jsx)(`option`,{value:``,children:`Select`}),r.map(e=>(0,O.jsxs)(`option`,{value:e.id,children:[e.hostname,` (`,e.id,`)`]},e.id))]})}),(0,O.jsx)(Z,{label:`Concurrency`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.concurrencyPolicy,onChange:e=>o({concurrencyPolicy:e.target.value}),children:[(0,O.jsx)(`option`,{value:`skip`,children:`skip`}),(0,O.jsx)(`option`,{value:`replace`,children:`replace`}),(0,O.jsx)(`option`,{value:`queue`,children:`queue`})]})})]}),(0,O.jsxs)(`label`,{className:`flex items-center justify-between rounded-md border border-border px-3 py-2`,children:[(0,O.jsx)(`span`,{className:`text-sm`,children:`Enabled`}),(0,O.jsx)(y,{checked:e.enabled,onCheckedChange:e=>o({enabled:e})})]})]}),(0,O.jsxs)(`div`,{className:`space-y-3`,children:[(0,O.jsx)(b,{children:`Target`}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-2`,children:[(0,O.jsx)(_,{type:`button`,variant:e.targetMode===`existing_agent`?`default`:`outline`,onClick:()=>o({targetMode:`existing_agent`}),children:`Existing`}),(0,O.jsx)(_,{type:`button`,variant:e.targetMode===`on_demand_agent`?`default`:`outline`,onClick:()=>o({targetMode:`on_demand_agent`}),children:`On demand`})]}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Provider`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.provider,onChange:e=>m(e.target.value),disabled:!l.length,children:[!l.length&&(0,O.jsx)(`option`,{value:e.provider,children:`No providers available`}),l.map(e=>(0,O.jsx)(`option`,{value:e,children:e},e))]})}),e.targetMode===`existing_agent`?(0,O.jsx)(Z,{label:`If no match`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.ifNoMatch,onChange:e=>o({ifNoMatch:e.target.value}),children:[(0,O.jsx)(`option`,{value:`fail`,children:`fail`}),(0,O.jsx)(`option`,{value:`spawn`,children:`spawn`})]})}):(0,O.jsx)(Z,{label:`Approval`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.approvalMode,onChange:e=>o({approvalMode:e.target.value}),children:[(0,O.jsx)(`option`,{value:`guarded`,children:`guarded`}),(0,O.jsx)(`option`,{value:`read-only`,children:`read-only`}),(0,O.jsx)(`option`,{value:`open`,children:`open`})]})})]}),e.targetMode===`on_demand_agent`&&(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Model`,children:(0,O.jsx)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.model,onChange:e=>o({model:e.target.value,effort:``}),children:u.map(e=>(0,O.jsx)(`option`,{value:e.alias,children:e.label},e.alias))})}),(0,O.jsx)(Z,{label:`Effort`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.effort,onChange:e=>o({effort:e.target.value}),disabled:!f.length,children:[(0,O.jsx)(`option`,{value:``,children:`Default`}),f.map(e=>(0,O.jsx)(`option`,{value:e,children:e},e))]})}),(0,O.jsx)(Z,{label:`Agent profile`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.profile||`default-relay`,onChange:e=>o({profile:e.target.value}),children:[!i.length&&(0,O.jsx)(`option`,{value:`default-relay`,children:`default-relay`}),i.map(e=>(0,O.jsx)(`option`,{value:e.name,children:e.name},e.name))]})}),(0,O.jsx)(Z,{label:`Workspace`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.workspaceMode,onChange:e=>o({workspaceMode:e.target.value}),children:[(0,O.jsx)(`option`,{value:`inherit`,children:`inherit (default → isolated worktree)`}),(0,O.jsx)(`option`,{value:`shared`,children:`shared (run in the repo, commit to its branch)`}),(0,O.jsx)(`option`,{value:`isolated`,children:`isolated (fresh git worktree per run)`})]})})]}),e.targetMode===`existing_agent`?(0,O.jsxs)(`div`,{className:`grid gap-3`,children:[(0,O.jsx)(Z,{label:`Label`,children:(0,O.jsx)(v,{value:e.label,onChange:e=>o({label:e.target.value})})}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Tags`,children:(0,O.jsx)(v,{value:e.tags,onChange:e=>o({tags:e.target.value})})}),(0,O.jsx)(Z,{label:`Capabilities`,children:(0,O.jsx)(v,{value:e.capabilities,onChange:e=>o({capabilities:e.target.value})})})]})]}):(0,O.jsxs)(`div`,{className:`grid gap-3`,children:[(0,O.jsx)(Z,{label:`Cwd`,children:(0,O.jsx)(v,{value:e.cwd,onChange:e=>o({cwd:e.target.value})})}),(0,O.jsxs)(`label`,{className:`flex items-center justify-between rounded-md border border-border px-3 py-2`,children:[(0,O.jsx)(`span`,{className:`text-sm`,children:`Keep alive`}),(0,O.jsx)(y,{checked:e.keepAlive,onCheckedChange:e=>o({keepAlive:e})})]}),(0,O.jsxs)(`div`,{className:`grid grid-cols-2 gap-3`,children:[(0,O.jsx)(Z,{label:`Budget minutes`,children:(0,O.jsx)(v,{type:`number`,min:1,step:1,value:e.budgetMinutes,onChange:e=>o({budgetMinutes:e.target.value})})}),(0,O.jsx)(Z,{label:`Warning minute`,children:(0,O.jsx)(v,{type:`number`,min:0,step:1,value:e.warningMinutes,onChange:e=>o({warningMinutes:e.target.value})})})]}),(0,O.jsx)(Z,{label:`Warning message`,children:(0,O.jsx)(S,{value:e.warningMessage,onChange:e=>o({warningMessage:e.target.value}),className:`min-h-20`})})]})]}),(0,O.jsxs)(`div`,{className:`space-y-3`,children:[(0,O.jsx)(b,{children:`Task`}),(0,O.jsx)(Z,{label:`Title`,children:(0,O.jsx)(v,{value:e.title,onChange:e=>o({title:e.target.value})})}),(0,O.jsx)(Z,{label:`Body`,children:(0,O.jsx)(S,{value:e.body,onChange:e=>o({body:e.target.value}),className:`min-h-28`})}),(0,O.jsxs)(`div`,{className:`grid grid-cols-3 gap-3`,children:[(0,O.jsx)(Z,{label:`Severity`,children:(0,O.jsxs)(`select`,{className:`h-9 w-full rounded-md border border-input bg-background px-3 text-sm`,value:e.severity,onChange:e=>o({severity:e.target.value}),children:[(0,O.jsx)(`option`,{value:`info`,children:`info`}),(0,O.jsx)(`option`,{value:`warning`,children:`warning`}),(0,O.jsx)(`option`,{value:`critical`,children:`critical`})]})}),(0,O.jsx)(Z,{label:`Dedupe key`,children:(0,O.jsx)(v,{value:e.dedupeKey,onChange:e=>o({dedupeKey:e.target.value})})}),(0,O.jsx)(Z,{label:`External URL`,children:(0,O.jsx)(v,{value:e.externalUrl,onChange:e=>o({externalUrl:e.target.value})})})]})]}),(0,O.jsxs)(_,{className:`w-full gap-2`,onClick:s,disabled:n||!e.name||!e.schedule||!e.orchestratorId||!e.title||!e.body,children:[n?(0,O.jsx)(c,{className:`w-4 h-4 animate-spin`}):(0,O.jsx)(a,{className:`w-4 h-4`}),`Save`]})]})}function Z({label:e,children:t}){return(0,O.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,O.jsx)(b,{className:`text-xs text-muted-foreground`,children:e}),t]})}function Q({runs:e,automations:t,agents:n,orchestrators:r,now:a,logRunId:o,onToggleLogs:s}){let c=new Map(t.map(e=>[e.id,e.name]));return(0,O.jsxs)(`div`,{className:`space-y-2`,children:[(0,O.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,O.jsx)(`h3`,{className:`text-sm font-semibold`,children:`Runs`}),(0,O.jsx)(g,{variant:`secondary`,className:`text-[10px]`,children:e.length})]}),(0,O.jsxs)(`div`,{className:`rounded-lg border border-border overflow-x-auto`,children:[(0,O.jsxs)(`div`,{className:`grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 bg-muted/30 px-3 py-2 text-xs text-muted-foreground`,children:[(0,O.jsx)(`span`,{children:`Automation`}),(0,O.jsx)(`span`,{children:`Status`}),(0,O.jsx)(`span`,{children:`Agent`}),(0,O.jsx)(`span`,{children:`Runtime`}),(0,O.jsx)(`span`,{children:`Updated`}),(0,O.jsx)(`span`,{children:`Logs`})]}),e.length===0?(0,O.jsx)(`div`,{className:`px-3 py-10 text-center text-sm text-muted-foreground`,children:`No runs`}):e.slice(0,80).map(e=>{let t=H(e,n,r),l=B(e,a),u=V(e);return(0,O.jsxs)(`div`,{className:`border-t border-border`,children:[(0,O.jsxs)(`div`,{className:`grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 px-3 py-2 text-xs items-center`,children:[(0,O.jsxs)(`div`,{className:`min-w-0`,children:[(0,O.jsx)(`div`,{className:`truncate`,children:c.get(e.automationId)||e.automationId.slice(0,12)}),(0,O.jsxs)(`div`,{className:`truncate text-muted-foreground`,children:[e.taskId?`task #${e.taskId}`:`no task`,l?` · ${l} left`:``,u?` · ${u}`:``]})]}),(0,O.jsx)(g,{variant:`outline`,className:`border text-[10px] w-fit ${M(e)}`,children:j(e)}),(0,O.jsx)(`span`,{className:`truncate font-mono text-muted-foreground`,children:e.targetAgentId||e.spawnedAgentId||`-`}),(0,O.jsx)(`span`,{className:`text-muted-foreground`,children:z(e,a)}),(0,O.jsx)(`span`,{className:`text-muted-foreground`,title:h(e.updatedAt),children:m(a,e.updatedAt)}),t?(0,O.jsx)(_,{type:`button`,size:`sm`,variant:`ghost`,className:`h-7 w-7 p-0`,title:`Open logs`,onClick:()=>s(e.id),children:(0,O.jsx)(i,{className:`w-3.5 h-3.5`})}):(0,O.jsx)(`span`,{className:`text-muted-foreground`,children:`-`})]}),o===e.id&&t&&(0,O.jsx)(`div`,{className:`border-t border-border px-3 py-3`,children:(0,O.jsx)(x,{orchestratorId:t.orchestratorId,session:t.session})})]},e.id)})]})]})}export{q as AutomationView};
|
|
2
|
-
//# sourceMappingURL=automation-BG2HWK5N.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"automation-BG2HWK5N.js","names":[],"sources":["../../dashboard/src/components/views/automation.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type ReactNode } from 'react'\nimport { useRelayStore, useNow } from '@/store'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Switch } from '@/components/ui/switch'\nimport { Textarea } from '@/components/ui/textarea'\nimport { fmtTime, timeAgo } from '@/lib/display'\nimport { LogViewer } from '@/components/shared/log-viewer'\nimport { PROVIDER_CATALOG } from 'agent-relay-sdk/provider-catalog'\nimport { CalendarClock, FileText, Pencil, Play, Plus, RotateCw, Save, Trash2 } from 'lucide-react'\nimport type {\n Agent,\n Automation,\n AutomationConcurrencyPolicy,\n AutomationRun,\n AutomationTargetPolicy,\n CreateAutomationInput,\n Orchestrator,\n SpawnApprovalMode,\n SpawnEffort,\n SpawnProvider,\n TaskSeverity,\n WorkspaceMode,\n} from '@/types'\n\nconst RUN_STATUS_COLORS: Record<string, string> = {\n scheduled: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n dispatching: 'bg-sky-500/10 text-sky-400 border-sky-500/20',\n waiting_agent: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n running: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n succeeded: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n failed: 'bg-red-500/10 text-red-400 border-red-500/20',\n canceled: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n timed_out: 'bg-red-500/10 text-red-400 border-red-500/20',\n}\n\nconst ACTIVE_RUN_STATUSES = new Set(['scheduled', 'dispatching', 'waiting_agent', 'running'])\n\nfunction runStatusLabel(run: AutomationRun): string {\n return run.status === 'timed_out' && run.result?.runtimeBudgetElapsed === true ? 'budget elapsed' : run.status\n}\n\nfunction runStatusClass(run: AutomationRun): string {\n if (run.status === 'timed_out' && run.result?.runtimeBudgetElapsed === true) return 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n return RUN_STATUS_COLORS[run.status] || ''\n}\n\nfunction runErrorText(run: AutomationRun): string | undefined {\n return run.status === 'timed_out' && run.result?.runtimeBudgetElapsed === true ? undefined : run.error\n}\n\nfunction isActiveRun(run: AutomationRun): boolean {\n return ACTIVE_RUN_STATUSES.has(run.status)\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction duration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000))\n const hours = Math.floor(total / 3600)\n const minutes = Math.floor((total % 3600) / 60)\n const seconds = total % 60\n if (hours) return `${hours}h ${minutes}m`\n if (minutes) return `${minutes}m ${seconds}s`\n return `${seconds}s`\n}\n\nfunction runDeadline(run: AutomationRun): number | undefined {\n return num(run.meta?.runtimeBudgetDeadlineAt) ?? num(run.result?.deadlineAt)\n}\n\nfunction elapsedText(run: AutomationRun, now: number): string {\n if (!run.startedAt) return '-'\n const end = run.finishedAt || (isActiveRun(run) ? now : run.updatedAt)\n return duration(Number(end) - Number(run.startedAt))\n}\n\nfunction remainingText(run: AutomationRun, now: number): string | undefined {\n const deadline = runDeadline(run)\n if (!deadline || !isActiveRun(run)) return undefined\n return duration(deadline - now)\n}\n\nfunction shutdownText(run: AutomationRun): string | undefined {\n if (run.status === 'timed_out' && run.result?.runtimeBudgetElapsed === true) return 'runtime budget elapsed'\n return run.error\n}\n\nfunction runLogTarget(run: AutomationRun, agents: Agent[], orchestrators: Orchestrator[]): { orchestratorId: string; session: string } | undefined {\n const agentId = run.targetAgentId || run.spawnedAgentId || str(run.meta?.runtimeAgentId)\n const agent = agentId ? agents.find((candidate) => candidate.id === agentId) : undefined\n const managed = agentId\n ? orchestrators.flatMap((orchestrator) => orchestrator.managedAgents.map((managedAgent) => ({ orchestrator, managedAgent })))\n .find(({ managedAgent }) => managedAgent.agentId === agentId)\n : undefined\n const session = str(run.meta?.terminalSession)\n || str(run.meta?.tmuxSession)\n || str(run.meta?.sessionName)\n || str(agent?.meta?.terminalSession)\n || str(agent?.meta?.tmuxSession)\n || str(agent?.meta?.sessionName)\n || managed?.managedAgent.terminalSession\n || managed?.managedAgent.tmuxSession\n || managed?.managedAgent.sessionName\n const orchestratorId = str(run.meta?.orchestratorId) || managed?.orchestrator.id || run.orchestratorId\n return session ? { orchestratorId, session } : undefined\n}\n\ntype TargetMode = 'existing_agent' | 'on_demand_agent'\n\ninterface AutomationForm {\n id?: string\n name: string\n description: string\n enabled: boolean\n schedule: string\n timezone: string\n orchestratorId: string\n targetMode: TargetMode\n provider: SpawnProvider\n model: string\n effort: string\n profile: string\n label: string\n tags: string\n capabilities: string\n ifNoMatch: 'fail' | 'spawn'\n cwd: string\n approvalMode: SpawnApprovalMode\n workspaceMode: WorkspaceMode\n keepAlive: boolean\n budgetMinutes: string\n warningMinutes: string\n warningMessage: string\n concurrencyPolicy: AutomationConcurrencyPolicy\n title: string\n body: string\n severity: TaskSeverity\n dedupeKey: string\n externalUrl: string\n}\n\nfunction blankForm(orchestratorId = ''): AutomationForm {\n return {\n name: '',\n description: '',\n enabled: true,\n schedule: '0 8 * * *',\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',\n orchestratorId,\n targetMode: 'existing_agent',\n provider: 'codex',\n model: PROVIDER_CATALOG.codex.defaultModel || '',\n effort: '',\n profile: 'default-relay',\n label: '',\n tags: '',\n capabilities: '',\n ifNoMatch: 'fail',\n cwd: '',\n approvalMode: 'guarded',\n workspaceMode: 'inherit',\n keepAlive: false,\n budgetMinutes: '',\n warningMinutes: '',\n warningMessage: '',\n concurrencyPolicy: 'skip',\n title: '',\n body: '',\n severity: 'info',\n dedupeKey: '',\n externalUrl: '',\n }\n}\n\nfunction splitCsv(value: string): string[] | undefined {\n const items = value.split(',').map((item) => item.trim()).filter(Boolean)\n return items.length ? items : undefined\n}\n\nfunction automationToForm(automation: Automation): AutomationForm {\n const policy = automation.targetPolicy\n return {\n ...blankForm(automation.orchestratorId),\n id: automation.id,\n name: automation.name,\n description: automation.description || '',\n enabled: automation.enabled,\n schedule: automation.schedule,\n timezone: automation.timezone,\n orchestratorId: automation.orchestratorId,\n targetMode: policy.mode,\n provider: policy.mode === 'on_demand_agent' ? policy.provider : policy.selector.provider || 'codex',\n model: policy.mode === 'on_demand_agent' ? policy.model || PROVIDER_CATALOG[policy.provider].defaultModel || '' : '',\n effort: policy.mode === 'on_demand_agent' ? policy.effort || '' : '',\n profile: policy.mode === 'on_demand_agent' ? policy.profile || 'default-relay' : 'default-relay',\n label: policy.mode === 'existing_agent' ? policy.selector.label || '' : '',\n tags: policy.mode === 'existing_agent' ? (policy.selector.tags || []).join(', ') : '',\n capabilities: policy.mode === 'existing_agent' ? (policy.selector.capabilities || []).join(', ') : '',\n ifNoMatch: policy.mode === 'existing_agent' ? policy.ifNoMatch || 'fail' : 'fail',\n cwd: policy.mode === 'on_demand_agent' ? policy.cwd || '' : '',\n approvalMode: policy.mode === 'on_demand_agent' ? policy.approvalMode || 'guarded' : 'guarded',\n workspaceMode: policy.mode === 'on_demand_agent' ? policy.workspaceMode || 'inherit' : 'inherit',\n keepAlive: policy.mode === 'on_demand_agent' ? Boolean(policy.keepAlive) : false,\n budgetMinutes: policy.mode === 'on_demand_agent' && policy.runtimeBudget ? String(Math.round(policy.runtimeBudget.maxRuntimeMs / 60_000)) : '',\n warningMinutes: policy.mode === 'on_demand_agent' && policy.runtimeBudget?.warnAtMs !== undefined ? String(Math.round(policy.runtimeBudget.warnAtMs / 60_000)) : '',\n warningMessage: policy.mode === 'on_demand_agent' ? policy.runtimeBudget?.warningMessage || '' : '',\n concurrencyPolicy: automation.concurrencyPolicy,\n title: automation.taskTemplate.title,\n body: automation.taskTemplate.body,\n severity: automation.taskTemplate.severity || 'info',\n dedupeKey: automation.taskTemplate.dedupeKey || '',\n externalUrl: automation.taskTemplate.externalUrl || '',\n }\n}\n\nfunction formToInput(form: AutomationForm): CreateAutomationInput {\n const budgetMinutes = Number(form.budgetMinutes)\n const warningMinutes = Number(form.warningMinutes)\n const runtimeBudget = form.targetMode === 'on_demand_agent' && form.budgetMinutes.trim() && Number.isFinite(budgetMinutes)\n ? {\n maxRuntimeMs: Math.round(budgetMinutes * 60_000),\n warnAtMs: form.warningMinutes.trim() && Number.isFinite(warningMinutes) ? Math.round(warningMinutes * 60_000) : undefined,\n warningMessage: form.warningMessage.trim() || undefined,\n }\n : undefined\n const targetPolicy: AutomationTargetPolicy = form.targetMode === 'existing_agent'\n ? {\n mode: 'existing_agent',\n selector: {\n provider: form.provider || undefined,\n label: form.label.trim() || undefined,\n tags: splitCsv(form.tags),\n capabilities: splitCsv(form.capabilities),\n },\n ifNoMatch: form.ifNoMatch,\n }\n : {\n mode: 'on_demand_agent',\n provider: form.provider,\n model: form.model || undefined,\n effort: form.effort ? form.effort as SpawnEffort : undefined,\n profile: form.profile || undefined,\n cwd: form.cwd.trim() || undefined,\n approvalMode: form.approvalMode,\n workspaceMode: form.workspaceMode,\n keepAlive: form.keepAlive,\n runtimeBudget,\n }\n\n return {\n kind: 'scheduled_task',\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n enabled: form.enabled,\n schedule: form.schedule.trim(),\n timezone: form.timezone.trim() || 'UTC',\n catchUpPolicy: 'skip',\n concurrencyPolicy: form.concurrencyPolicy,\n orchestratorId: form.orchestratorId,\n targetPolicy,\n taskTemplate: {\n title: form.title.trim(),\n body: form.body.trim(),\n severity: form.severity,\n dedupeKey: form.dedupeKey.trim() || undefined,\n externalUrl: form.externalUrl.trim() || undefined,\n },\n }\n}\n\nexport function AutomationView() {\n const now = useNow()\n const automations = useRelayStore((s) => s.automations)\n const automationRuns = useRelayStore((s) => s.automationRuns)\n const orchestrators = useRelayStore((s) => s.orchestrators)\n const agents = useRelayStore((s) => s.agents)\n const apiCall = useRelayStore((s) => s.apiCall)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const fetchAutomations = useRelayStore((s) => s.fetchAutomations)\n const fetchAutomationRuns = useRelayStore((s) => s.fetchAutomationRuns)\n const fetchTasks = useRelayStore((s) => s.fetchTasks)\n const agentProfiles = useRelayStore((s) => s.agentProfiles)\n const [selectedId, setSelectedId] = useState<string>('')\n const [form, setForm] = useState<AutomationForm>(() => blankForm(orchestrators[0]?.id || ''))\n const [saving, setSaving] = useState(false)\n const [logRunId, setLogRunId] = useState<string | null>(null)\n\n const selected = automations.find((automation) => automation.id === selectedId)\n const onlineOrchestrators = orchestrators.filter((orchestrator) => orchestrator.status === 'online')\n const activeRuns = automationRuns.filter(isActiveRun)\n\n useEffect(() => {\n void Promise.all([fetchAutomations(), fetchAutomationRuns()])\n }, [])\n\n useEffect(() => {\n if (!form.orchestratorId && orchestrators[0]) {\n setForm((current) => ({ ...current, orchestratorId: orchestrators[0]!.id }))\n }\n }, [orchestrators])\n\n const runsByAutomation = useMemo(() => {\n const grouped = new Map<string, AutomationRun[]>()\n for (const run of automationRuns) {\n const list = grouped.get(run.automationId) || []\n list.push(run)\n grouped.set(run.automationId, list)\n }\n return grouped\n }, [automationRuns])\n\n function newAutomation() {\n setSelectedId('')\n setForm(blankForm(orchestrators[0]?.id || ''))\n }\n\n function editAutomation(automation: Automation) {\n setSelectedId(automation.id)\n setForm(automationToForm(automation))\n }\n\n async function saveAutomation() {\n setSaving(true)\n try {\n const input = formToInput(form)\n if (form.id) await apiCall('PATCH', '/automations/' + encodeURIComponent(form.id), input)\n else {\n const created = await apiCall<Automation>('POST', '/automations', input)\n setSelectedId(created.id)\n setForm(automationToForm(created))\n }\n await Promise.all([fetchAutomations(), fetchAutomationRuns()])\n } catch (e) {\n showError('Save Failed', (e as Error).message)\n } finally {\n setSaving(false)\n }\n }\n\n async function patchAutomation(automation: Automation, partial: Partial<Automation>) {\n try {\n await apiCall('PATCH', '/automations/' + encodeURIComponent(automation.id), partial)\n await fetchAutomations()\n } catch (e) {\n showError('Update Failed', (e as Error).message)\n }\n }\n\n async function runNow(automation: Automation) {\n try {\n await apiCall('POST', '/automations/' + encodeURIComponent(automation.id) + '/run')\n await Promise.all([fetchAutomationRuns(), fetchTasks()])\n } catch (e) {\n showError('Run Failed', (e as Error).message)\n }\n }\n\n function deleteAutomation(automation: Automation) {\n openConfirm('Delete Automation', `Delete automation \"${automation.name}\"?`, async () => {\n try {\n await apiCall('DELETE', '/automations/' + encodeURIComponent(automation.id))\n if (selectedId === automation.id) newAutomation()\n await Promise.all([fetchAutomations(), fetchAutomationRuns()])\n } catch (e) {\n showError('Delete Failed', (e as Error).message)\n }\n })\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <CalendarClock className=\"w-5 h-5 text-muted-foreground\" />\n <div>\n <h2 className=\"text-lg font-semibold\">Automation</h2>\n <p className=\"text-xs text-muted-foreground\">Scheduled tasks</p>\n </div>\n <div className=\"ml-auto flex flex-wrap items-center gap-2\">\n <Badge variant=\"outline\" className=\"text-xs\">{automations.length} schedules</Badge>\n <Badge variant=\"outline\" className=\"text-xs\">{activeRuns.length} running</Badge>\n <Button size=\"sm\" className=\"gap-1\" onClick={newAutomation}>\n <Plus className=\"w-3.5 h-3.5\" />\n New\n </Button>\n </div>\n </div>\n\n {activeRuns.length > 0 && (\n <ActiveRuns\n runs={activeRuns}\n automations={automations}\n agents={agents}\n orchestrators={orchestrators}\n now={now}\n logRunId={logRunId}\n onToggleLogs={(runId) => setLogRunId((current) => current === runId ? null : runId)}\n />\n )}\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1fr)_420px]\">\n <div className=\"space-y-3\">\n {automations.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">No automations</div>\n ) : automations.map((automation) => (\n <AutomationCard\n key={automation.id}\n automation={automation}\n runs={runsByAutomation.get(automation.id) || []}\n now={now}\n selected={selectedId === automation.id}\n onEdit={() => editAutomation(automation)}\n onRun={() => runNow(automation)}\n onToggle={() => patchAutomation(automation, { enabled: !automation.enabled })}\n onDelete={() => deleteAutomation(automation)}\n />\n ))}\n </div>\n\n <AutomationEditor\n form={form}\n agentProfiles={agentProfiles}\n selected={selected}\n saving={saving}\n orchestrators={onlineOrchestrators.length ? onlineOrchestrators : orchestrators}\n onChange={(patch) => setForm((current) => ({ ...current, ...patch }))}\n onSave={saveAutomation}\n />\n </div>\n\n <RunHistory\n runs={automationRuns}\n automations={automations}\n agents={agents}\n orchestrators={orchestrators}\n now={now}\n logRunId={logRunId}\n onToggleLogs={(runId) => setLogRunId((current) => current === runId ? null : runId)}\n />\n </div>\n )\n}\n\nfunction ActiveRuns({\n runs,\n automations,\n agents,\n orchestrators,\n now,\n logRunId,\n onToggleLogs,\n}: {\n runs: AutomationRun[]\n automations: Automation[]\n agents: Agent[]\n orchestrators: Orchestrator[]\n now: number\n logRunId: string | null\n onToggleLogs: (runId: string) => void\n}) {\n const names = new Map(automations.map((automation) => [automation.id, automation.name]))\n return (\n <div className=\"rounded-lg border border-border bg-card/40\">\n <div className=\"border-b border-border px-3 py-2 text-sm font-medium\">Active runs</div>\n <div className=\"divide-y divide-border\">\n {runs.map((run) => {\n const logTarget = runLogTarget(run, agents, orchestrators)\n const remaining = remainingText(run, now)\n return (\n <div key={run.id} className=\"space-y-3 px-3 py-3\">\n <div className=\"grid gap-3 text-xs md:grid-cols-[minmax(12rem,1fr)_7rem_7rem_7rem_8rem_auto] md:items-center\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{names.get(run.automationId) || run.automationId.slice(0, 12)}</div>\n <div className=\"truncate font-mono text-muted-foreground\">{run.targetAgentId || run.spawnedAgentId || str(run.meta?.onDemandLabel) || '-'}</div>\n </div>\n <Badge variant=\"outline\" className={`border text-[10px] w-fit ${runStatusClass(run)}`}>{runStatusLabel(run)}</Badge>\n <div><span className=\"text-muted-foreground\">elapsed </span>{elapsedText(run, now)}</div>\n <div><span className=\"text-muted-foreground\">left </span>{remaining || '-'}</div>\n <div className=\"font-mono text-muted-foreground\">{run.taskId ? `task #${run.taskId}` : '-'}</div>\n {logTarget && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" className=\"h-7 gap-1 text-xs\" onClick={() => onToggleLogs(run.id)}>\n <FileText className=\"w-3 h-3\" />\n Logs\n </Button>\n )}\n </div>\n {logRunId === run.id && logTarget && <LogViewer orchestratorId={logTarget.orchestratorId} session={logTarget.session} />}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\nfunction AutomationCard({\n automation,\n runs,\n now,\n selected,\n onEdit,\n onRun,\n onToggle,\n onDelete,\n}: {\n automation: Automation\n runs: AutomationRun[]\n now: number\n selected: boolean\n onEdit: () => void\n onRun: () => void\n onToggle: () => void\n onDelete: () => void\n}) {\n const latestRun = runs[0]\n const policy = automation.targetPolicy\n const target = policy.mode === 'existing_agent'\n ? [\n policy.selector.provider,\n policy.selector.label ? `label:${policy.selector.label}` : '',\n ...(policy.selector.tags || []).map((tag) => `tag:${tag}`),\n ...(policy.selector.capabilities || []).map((cap) => `cap:${cap}`),\n ].filter(Boolean).join(' ')\n : `${policy.provider} on demand`\n\n return (\n <Card className={`${selected ? 'ring-1 ring-primary/50' : ''} ${automation.enabled ? '' : 'opacity-60'}`}>\n <CardHeader className=\"px-4 py-3\">\n <div className=\"flex items-start gap-3\">\n <Switch checked={automation.enabled} onCheckedChange={onToggle} className=\"mt-0.5\" />\n <div className=\"min-w-0 flex-1\">\n <CardTitle className=\"text-sm truncate\">{automation.name}</CardTitle>\n <div className=\"mt-1 flex flex-wrap gap-1.5 text-xs text-muted-foreground\">\n <span className=\"font-mono\">{automation.schedule}</span>\n <span>{automation.timezone}</span>\n <span>{automation.orchestratorId}</span>\n <span className=\"truncate max-w-[28rem]\">{target}</span>\n </div>\n </div>\n <div className=\"flex gap-1\">\n <Button size=\"icon\" variant=\"ghost\" className=\"h-7 w-7\" title=\"Run now\" onClick={onRun}>\n <Play className=\"w-3.5 h-3.5\" />\n </Button>\n <Button size=\"icon\" variant=\"ghost\" className=\"h-7 w-7\" title=\"Edit\" onClick={onEdit}>\n <Pencil className=\"w-3.5 h-3.5\" />\n </Button>\n <Button size=\"icon\" variant=\"ghost\" className=\"h-7 w-7 text-red-400 hover:text-red-300\" title=\"Delete\" onClick={onDelete}>\n <Trash2 className=\"w-3.5 h-3.5\" />\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"px-4 pb-3 pt-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs text-muted-foreground\">\n <span title={automation.nextRunAt ? fmtTime(automation.nextRunAt) : undefined}>\n next {automation.nextRunAt ? timeAgo(now, automation.nextRunAt) : 'disabled'}\n </span>\n {latestRun && (\n <Badge variant=\"outline\" className={`border text-[10px] ${runStatusClass(latestRun)}`}>\n {runStatusLabel(latestRun)}\n </Badge>\n )}\n {latestRun?.taskId && <span>task #{latestRun.taskId}</span>}\n {latestRun && runErrorText(latestRun) && <span className=\"text-red-400 truncate\">{runErrorText(latestRun)}</span>}\n </div>\n </CardContent>\n </Card>\n )\n}\n\nfunction AutomationEditor({\n form,\n selected,\n saving,\n orchestrators,\n agentProfiles,\n onChange,\n onSave,\n}: {\n form: AutomationForm\n selected?: Automation\n saving: boolean\n orchestrators: Array<{ id: string; hostname: string; providers: SpawnProvider[] }>\n agentProfiles: Array<{ name: string }>\n onChange: (patch: Partial<AutomationForm>) => void\n onSave: () => void\n}) {\n const selectedOrchestrator = orchestrators.find((orch) => orch.id === form.orchestratorId)\n const providers = selectedOrchestrator?.providers || []\n const models = PROVIDER_CATALOG[form.provider]?.models || []\n const selectedModel = models.find((model) => model.alias === form.model)\n const efforts = selectedModel?.efforts || []\n\n function selectOrchestrator(orchestratorId: string) {\n const orchestrator = orchestrators.find((orch) => orch.id === orchestratorId)\n const provider = orchestrator?.providers[0] || form.provider\n onChange({ orchestratorId, provider, model: PROVIDER_CATALOG[provider]?.defaultModel || '', effort: '' })\n }\n\n function selectProvider(provider: SpawnProvider) {\n onChange({ provider, model: PROVIDER_CATALOG[provider]?.defaultModel || '', effort: '' })\n }\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-4 space-y-4 h-fit xl:sticky xl:top-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold\">{selected ? 'Edit schedule' : 'New schedule'}</h3>\n <Badge variant={form.enabled ? 'default' : 'secondary'} className=\"ml-auto text-[10px]\">\n {form.enabled ? 'enabled' : 'disabled'}\n </Badge>\n </div>\n\n <div className=\"grid gap-3\">\n <Field label=\"Name\">\n <Input value={form.name} onChange={(e) => onChange({ name: e.target.value })} />\n </Field>\n <Field label=\"Description\">\n <Input value={form.description} onChange={(e) => onChange({ description: e.target.value })} />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Cron\">\n <Input value={form.schedule} onChange={(e) => onChange({ schedule: e.target.value })} className=\"font-mono\" />\n </Field>\n <Field label=\"Timezone\">\n <Input value={form.timezone} onChange={(e) => onChange({ timezone: e.target.value })} />\n </Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Orchestrator\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.orchestratorId} onChange={(e) => selectOrchestrator(e.target.value)}>\n <option value=\"\">Select</option>\n {orchestrators.map((orch) => <option key={orch.id} value={orch.id}>{orch.hostname} ({orch.id})</option>)}\n </select>\n </Field>\n <Field label=\"Concurrency\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.concurrencyPolicy} onChange={(e) => onChange({ concurrencyPolicy: e.target.value as AutomationConcurrencyPolicy })}>\n <option value=\"skip\">skip</option>\n <option value=\"replace\">replace</option>\n <option value=\"queue\">queue</option>\n </select>\n </Field>\n </div>\n <label className=\"flex items-center justify-between rounded-md border border-border px-3 py-2\">\n <span className=\"text-sm\">Enabled</span>\n <Switch checked={form.enabled} onCheckedChange={(enabled) => onChange({ enabled })} />\n </label>\n </div>\n\n <div className=\"space-y-3\">\n <Label>Target</Label>\n <div className=\"grid grid-cols-2 gap-2\">\n <Button type=\"button\" variant={form.targetMode === 'existing_agent' ? 'default' : 'outline'} onClick={() => onChange({ targetMode: 'existing_agent' })}>\n Existing\n </Button>\n <Button type=\"button\" variant={form.targetMode === 'on_demand_agent' ? 'default' : 'outline'} onClick={() => onChange({ targetMode: 'on_demand_agent' })}>\n On demand\n </Button>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Provider\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.provider} onChange={(e) => selectProvider(e.target.value as SpawnProvider)} disabled={!providers.length}>\n {!providers.length && <option value={form.provider}>No providers available</option>}\n {providers.map((provider) => <option key={provider} value={provider}>{provider}</option>)}\n </select>\n </Field>\n {form.targetMode === 'existing_agent' ? (\n <Field label=\"If no match\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.ifNoMatch} onChange={(e) => onChange({ ifNoMatch: e.target.value as 'fail' | 'spawn' })}>\n <option value=\"fail\">fail</option>\n <option value=\"spawn\">spawn</option>\n </select>\n </Field>\n ) : (\n <Field label=\"Approval\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.approvalMode} onChange={(e) => onChange({ approvalMode: e.target.value as SpawnApprovalMode })}>\n <option value=\"guarded\">guarded</option>\n <option value=\"read-only\">read-only</option>\n <option value=\"open\">open</option>\n </select>\n </Field>\n )}\n </div>\n\n {form.targetMode === 'on_demand_agent' && (\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Model\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.model} onChange={(e) => onChange({ model: e.target.value, effort: '' })}>\n {models.map((model) => <option key={model.alias} value={model.alias}>{model.label}</option>)}\n </select>\n </Field>\n <Field label=\"Effort\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.effort} onChange={(e) => onChange({ effort: e.target.value })} disabled={!efforts.length}>\n <option value=\"\">Default</option>\n {efforts.map((level) => <option key={level} value={level}>{level}</option>)}\n </select>\n </Field>\n <Field label=\"Agent profile\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.profile || 'default-relay'} onChange={(e) => onChange({ profile: e.target.value })}>\n {!agentProfiles.length && <option value=\"default-relay\">default-relay</option>}\n {agentProfiles.map((profile) => <option key={profile.name} value={profile.name}>{profile.name}</option>)}\n </select>\n </Field>\n <Field label=\"Workspace\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.workspaceMode} onChange={(e) => onChange({ workspaceMode: e.target.value as WorkspaceMode })}>\n <option value=\"inherit\">inherit (default → isolated worktree)</option>\n <option value=\"shared\">shared (run in the repo, commit to its branch)</option>\n <option value=\"isolated\">isolated (fresh git worktree per run)</option>\n </select>\n </Field>\n </div>\n )}\n\n {form.targetMode === 'existing_agent' ? (\n <div className=\"grid gap-3\">\n <Field label=\"Label\">\n <Input value={form.label} onChange={(e) => onChange({ label: e.target.value })} />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Tags\">\n <Input value={form.tags} onChange={(e) => onChange({ tags: e.target.value })} />\n </Field>\n <Field label=\"Capabilities\">\n <Input value={form.capabilities} onChange={(e) => onChange({ capabilities: e.target.value })} />\n </Field>\n </div>\n </div>\n ) : (\n <div className=\"grid gap-3\">\n <Field label=\"Cwd\">\n <Input value={form.cwd} onChange={(e) => onChange({ cwd: e.target.value })} />\n </Field>\n <label className=\"flex items-center justify-between rounded-md border border-border px-3 py-2\">\n <span className=\"text-sm\">Keep alive</span>\n <Switch checked={form.keepAlive} onCheckedChange={(keepAlive) => onChange({ keepAlive })} />\n </label>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Budget minutes\">\n <Input type=\"number\" min={1} step={1} value={form.budgetMinutes} onChange={(e) => onChange({ budgetMinutes: e.target.value })} />\n </Field>\n <Field label=\"Warning minute\">\n <Input type=\"number\" min={0} step={1} value={form.warningMinutes} onChange={(e) => onChange({ warningMinutes: e.target.value })} />\n </Field>\n </div>\n <Field label=\"Warning message\">\n <Textarea value={form.warningMessage} onChange={(e) => onChange({ warningMessage: e.target.value })} className=\"min-h-20\" />\n </Field>\n </div>\n )}\n </div>\n\n <div className=\"space-y-3\">\n <Label>Task</Label>\n <Field label=\"Title\">\n <Input value={form.title} onChange={(e) => onChange({ title: e.target.value })} />\n </Field>\n <Field label=\"Body\">\n <Textarea value={form.body} onChange={(e) => onChange({ body: e.target.value })} className=\"min-h-28\" />\n </Field>\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Severity\">\n <select className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\" value={form.severity} onChange={(e) => onChange({ severity: e.target.value as TaskSeverity })}>\n <option value=\"info\">info</option>\n <option value=\"warning\">warning</option>\n <option value=\"critical\">critical</option>\n </select>\n </Field>\n <Field label=\"Dedupe key\">\n <Input value={form.dedupeKey} onChange={(e) => onChange({ dedupeKey: e.target.value })} />\n </Field>\n <Field label=\"External URL\">\n <Input value={form.externalUrl} onChange={(e) => onChange({ externalUrl: e.target.value })} />\n </Field>\n </div>\n </div>\n\n <Button className=\"w-full gap-2\" onClick={onSave} disabled={saving || !form.name || !form.schedule || !form.orchestratorId || !form.title || !form.body}>\n {saving ? <RotateCw className=\"w-4 h-4 animate-spin\" /> : <Save className=\"w-4 h-4\" />}\n Save\n </Button>\n </div>\n )\n}\n\nfunction Field({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"space-y-1.5\">\n <Label className=\"text-xs text-muted-foreground\">{label}</Label>\n {children}\n </div>\n )\n}\n\nfunction RunHistory({\n runs,\n automations,\n agents,\n orchestrators,\n now,\n logRunId,\n onToggleLogs,\n}: {\n runs: AutomationRun[]\n automations: Automation[]\n agents: Agent[]\n orchestrators: Orchestrator[]\n now: number\n logRunId: string | null\n onToggleLogs: (runId: string) => void\n}) {\n const names = new Map(automations.map((automation) => [automation.id, automation.name]))\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-sm font-semibold\">Runs</h3>\n <Badge variant=\"secondary\" className=\"text-[10px]\">{runs.length}</Badge>\n </div>\n <div className=\"rounded-lg border border-border overflow-x-auto\">\n <div className=\"grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <span>Automation</span>\n <span>Status</span>\n <span>Agent</span>\n <span>Runtime</span>\n <span>Updated</span>\n <span>Logs</span>\n </div>\n {runs.length === 0 ? (\n <div className=\"px-3 py-10 text-center text-sm text-muted-foreground\">No runs</div>\n ) : runs.slice(0, 80).map((run) => {\n const logTarget = runLogTarget(run, agents, orchestrators)\n const remaining = remainingText(run, now)\n const shutdown = shutdownText(run)\n return (\n <div key={run.id} className=\"border-t border-border\">\n <div className=\"grid grid-cols-[minmax(10rem,1fr)_8rem_8rem_7rem_7rem_5rem] gap-3 px-3 py-2 text-xs items-center\">\n <div className=\"min-w-0\">\n <div className=\"truncate\">{names.get(run.automationId) || run.automationId.slice(0, 12)}</div>\n <div className=\"truncate text-muted-foreground\">\n {run.taskId ? `task #${run.taskId}` : 'no task'}\n {remaining ? ` · ${remaining} left` : ''}\n {shutdown ? ` · ${shutdown}` : ''}\n </div>\n </div>\n <Badge variant=\"outline\" className={`border text-[10px] w-fit ${runStatusClass(run)}`}>{runStatusLabel(run)}</Badge>\n <span className=\"truncate font-mono text-muted-foreground\">{run.targetAgentId || run.spawnedAgentId || '-'}</span>\n <span className=\"text-muted-foreground\">{elapsedText(run, now)}</span>\n <span className=\"text-muted-foreground\" title={fmtTime(run.updatedAt)}>{timeAgo(now, run.updatedAt)}</span>\n {logTarget ? (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" className=\"h-7 w-7 p-0\" title=\"Open logs\" onClick={() => onToggleLogs(run.id)}>\n <FileText className=\"w-3.5 h-3.5\" />\n </Button>\n ) : <span className=\"text-muted-foreground\">-</span>}\n </div>\n {logRunId === run.id && logTarget && (\n <div className=\"border-t border-border px-3 py-3\">\n <LogViewer orchestratorId={logTarget.orchestratorId} session={logTarget.session} />\n </div>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|