@matteai/stma-server 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1684 -636
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ function loadEnv(overrides = {}) {
|
|
|
39
39
|
devMode,
|
|
40
40
|
localAuth: e.AUTH_LOCAL !== "0",
|
|
41
41
|
signupsOpen: e.SIGNUPS_OPEN !== "0",
|
|
42
|
+
publicMode: e.SITE_MODE === "teaser" ? "teaser" : "full",
|
|
42
43
|
hosted: e.STMA_HOSTED === "1",
|
|
43
44
|
demoLogins: parseDemoLogins(e.DEMO_LOGINS),
|
|
44
45
|
embeddedDb: e.EMBEDDED_DB === "1",
|
|
@@ -84,7 +85,7 @@ import { serve } from "@hono/node-server";
|
|
|
84
85
|
|
|
85
86
|
// src/app.tsx
|
|
86
87
|
import { sql as sql19 } from "drizzle-orm";
|
|
87
|
-
import { Hono as
|
|
88
|
+
import { Hono as Hono20 } from "hono";
|
|
88
89
|
import { bodyLimit } from "hono/body-limit";
|
|
89
90
|
|
|
90
91
|
// src/auth/session.ts
|
|
@@ -1015,9 +1016,9 @@ function detectClaimConflicts(current, existing) {
|
|
|
1015
1016
|
});
|
|
1016
1017
|
}
|
|
1017
1018
|
}
|
|
1018
|
-
const
|
|
1019
|
+
const rank3 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
1019
1020
|
return out.sort(
|
|
1020
|
-
(a, b) =>
|
|
1021
|
+
(a, b) => rank3[a.severity] - rank3[b.severity] || a.current.resourceKey.localeCompare(b.current.resourceKey) || a.existing.runId.localeCompare(b.existing.runId)
|
|
1021
1022
|
);
|
|
1022
1023
|
}
|
|
1023
1024
|
|
|
@@ -1296,19 +1297,22 @@ var ONBOARD_RULES_VERSION = 2;
|
|
|
1296
1297
|
|
|
1297
1298
|
// src/lib/rail.ts
|
|
1298
1299
|
import { and, count, desc, eq, gt, inArray, isNotNull, isNull, ne, or, sql as sql2 } from "drizzle-orm";
|
|
1300
|
+
var RAIL_TEAMS = 8;
|
|
1299
1301
|
var EMPTY_RAIL = {
|
|
1300
1302
|
runs: 0,
|
|
1301
1303
|
sessions: 0,
|
|
1302
1304
|
drift: 0,
|
|
1303
1305
|
team: null,
|
|
1304
1306
|
role: null,
|
|
1305
|
-
teams: 0
|
|
1307
|
+
teams: 0,
|
|
1308
|
+
projects: 0,
|
|
1309
|
+
list: []
|
|
1306
1310
|
};
|
|
1307
1311
|
async function ensureRail(db, user) {
|
|
1308
1312
|
if (!user.rail) user.rail = await railFor(db, user.id);
|
|
1309
1313
|
}
|
|
1310
1314
|
async function railFor(db, userId) {
|
|
1311
|
-
const mine = await db.select({ id: teams.id, slug: teams.slug, role: memberships.role }).from(memberships).innerJoin(teams, eq(memberships.teamId, teams.id)).where(eq(memberships.userId, userId)).orderBy(desc(teams.createdAt));
|
|
1315
|
+
const mine = await db.select({ id: teams.id, slug: teams.slug, name: teams.name, role: memberships.role }).from(memberships).innerJoin(teams, eq(memberships.teamId, teams.id)).where(eq(memberships.userId, userId)).orderBy(desc(teams.createdAt));
|
|
1312
1316
|
if (mine.length === 0) return EMPTY_RAIL;
|
|
1313
1317
|
const ids = mine.map((t) => t.id);
|
|
1314
1318
|
const runs = await db.select({ n: count() }).from(agentRuns).where(
|
|
@@ -1333,13 +1337,17 @@ async function railFor(db, userId) {
|
|
|
1333
1337
|
)
|
|
1334
1338
|
);
|
|
1335
1339
|
const drift = await db.select({ n: count() }).from(policyReceipts).innerJoin(agentRuns, eq(policyReceipts.runId, agentRuns.id)).where(and(inArray(agentRuns.teamId, ids), eq(policyReceipts.drift, true)));
|
|
1340
|
+
const projects2 = mine[0] ? await db.select({ n: count() }).from(projects).where(eq(projects.teamId, mine[0].id)) : [];
|
|
1336
1341
|
return {
|
|
1337
1342
|
runs: runs[0]?.n ?? 0,
|
|
1338
1343
|
sessions: Number(unread[0]?.n ?? 0),
|
|
1339
1344
|
drift: drift[0]?.n ?? 0,
|
|
1340
1345
|
team: mine[0]?.slug ?? null,
|
|
1341
1346
|
role: mine[0]?.role ?? null,
|
|
1342
|
-
teams: mine.length
|
|
1347
|
+
teams: mine.length,
|
|
1348
|
+
projects: projects2[0]?.n ?? 0,
|
|
1349
|
+
// Bounded: somebody in thirty teams gets the newest few and the picker.
|
|
1350
|
+
list: mine.slice(0, RAIL_TEAMS).map((t) => ({ slug: t.slug, name: t.name, role: t.role }))
|
|
1343
1351
|
};
|
|
1344
1352
|
}
|
|
1345
1353
|
|
|
@@ -2053,6 +2061,33 @@ var RailLink = ({
|
|
|
2053
2061
|
badge && badge > 0 ? /* @__PURE__ */ jsx("span", { class: "rail-badge", children: NUM(badge) }) : null
|
|
2054
2062
|
] });
|
|
2055
2063
|
var teamHref = (team, suffix) => team ? `/app/teams/${team}${suffix}` : "/app";
|
|
2064
|
+
var teamInitials = (name) => name.split(/[\s-]+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("") || "?";
|
|
2065
|
+
var TeamSwitch = ({ rail }) => {
|
|
2066
|
+
if (!rail.team) return null;
|
|
2067
|
+
const current = rail.list.find((t) => t.slug === rail.team);
|
|
2068
|
+
const name = current?.name ?? rail.team;
|
|
2069
|
+
const face = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2070
|
+
/* @__PURE__ */ jsx("span", { class: "tile tile-28 tile-green", style: "width:22px;height:22px;font-size:9px", children: teamInitials(name) }),
|
|
2071
|
+
/* @__PURE__ */ jsx("span", { class: "tname", children: name })
|
|
2072
|
+
] });
|
|
2073
|
+
if (rail.list.length < 2) {
|
|
2074
|
+
return /* @__PURE__ */ jsx("a", { class: "teamswitch", href: `/app/teams/${rail.team}`, title: "This team", children: face });
|
|
2075
|
+
}
|
|
2076
|
+
return /* @__PURE__ */ jsxs("details", { class: "teampick", children: [
|
|
2077
|
+
/* @__PURE__ */ jsxs("summary", { class: "teamswitch", title: "Switch team", children: [
|
|
2078
|
+
face,
|
|
2079
|
+
/* @__PURE__ */ jsx("span", { class: "caret", children: "\u25BE" })
|
|
2080
|
+
] }),
|
|
2081
|
+
/* @__PURE__ */ jsxs("div", { class: "teammenu", children: [
|
|
2082
|
+
rail.list.map((t) => /* @__PURE__ */ jsxs("a", { class: t.slug === rail.team ? "on" : void 0, href: `/app/teams/${t.slug}`, children: [
|
|
2083
|
+
/* @__PURE__ */ jsx("span", { style: "min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap", children: t.name }),
|
|
2084
|
+
/* @__PURE__ */ jsx("span", { class: "r", children: t.role })
|
|
2085
|
+
] })),
|
|
2086
|
+
/* @__PURE__ */ jsx("span", { class: "sep" }),
|
|
2087
|
+
/* @__PURE__ */ jsx("a", { href: "/app", children: "All teams" })
|
|
2088
|
+
] })
|
|
2089
|
+
] });
|
|
2090
|
+
};
|
|
2056
2091
|
var Rail = ({ user, active }) => {
|
|
2057
2092
|
const rail = user.rail ?? EMPTY_RAIL;
|
|
2058
2093
|
return /* @__PURE__ */ jsxs("nav", { class: "rail", children: [
|
|
@@ -2060,9 +2095,19 @@ var Rail = ({ user, active }) => {
|
|
|
2060
2095
|
/* @__PURE__ */ jsx(Logo, { inv: true }),
|
|
2061
2096
|
/* @__PURE__ */ jsx("span", { class: "name", children: "STMA" })
|
|
2062
2097
|
] }),
|
|
2098
|
+
/* @__PURE__ */ jsx(TeamSwitch, { rail }),
|
|
2063
2099
|
/* @__PURE__ */ jsxs("div", { class: "rail-nav", children: [
|
|
2064
2100
|
/* @__PURE__ */ jsx("span", { class: "rail-group", children: "Control" }),
|
|
2065
2101
|
/* @__PURE__ */ jsx(RailLink, { href: "/app/agents", label: "Agent map", active: active === "agents", badge: rail.runs }),
|
|
2102
|
+
/* @__PURE__ */ jsx(
|
|
2103
|
+
RailLink,
|
|
2104
|
+
{
|
|
2105
|
+
href: teamHref(rail.team, "/projects"),
|
|
2106
|
+
label: "Projects",
|
|
2107
|
+
active: active === "projects",
|
|
2108
|
+
badge: rail.projects
|
|
2109
|
+
}
|
|
2110
|
+
),
|
|
2066
2111
|
/* @__PURE__ */ jsx(
|
|
2067
2112
|
RailLink,
|
|
2068
2113
|
{
|
|
@@ -2107,6 +2152,14 @@ var Rail = ({ user, active }) => {
|
|
|
2107
2152
|
/* @__PURE__ */ jsx("span", { class: "rail-group later", children: "Workspace" }),
|
|
2108
2153
|
/* @__PURE__ */ jsx(RailLink, { href: "/app", label: "Teams", active: active === "teams", badge: rail.teams > 1 ? rail.teams : void 0 }),
|
|
2109
2154
|
/* @__PURE__ */ jsx(RailLink, { href: "/app/sessions", label: "Sessions", active: active === "sessions", badge: rail.sessions }),
|
|
2155
|
+
/* @__PURE__ */ jsx(
|
|
2156
|
+
RailLink,
|
|
2157
|
+
{
|
|
2158
|
+
href: "/app/notifications",
|
|
2159
|
+
label: "Notifications",
|
|
2160
|
+
active: active === "notifications"
|
|
2161
|
+
}
|
|
2162
|
+
),
|
|
2110
2163
|
/* @__PURE__ */ jsx(RailLink, { href: "/app/tokens", label: "Tokens", active: active === "tokens" }),
|
|
2111
2164
|
/* @__PURE__ */ jsx(RailLink, { href: "/docs", label: "Docs", active: active === "docs" }),
|
|
2112
2165
|
user.isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -2115,12 +2168,14 @@ var Rail = ({ user, active }) => {
|
|
|
2115
2168
|
] }) : null
|
|
2116
2169
|
] }),
|
|
2117
2170
|
/* @__PURE__ */ jsxs("div", { class: "rail-foot", children: [
|
|
2118
|
-
/* @__PURE__ */
|
|
2119
|
-
|
|
2120
|
-
/* @__PURE__ */
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2171
|
+
/* @__PURE__ */ jsxs("a", { class: "railme", href: "/app/account", title: "Account", children: [
|
|
2172
|
+
/* @__PURE__ */ jsx("span", { class: "avatar", children: initials(user.username) }),
|
|
2173
|
+
/* @__PURE__ */ jsxs("div", { class: "rail-who", children: [
|
|
2174
|
+
/* @__PURE__ */ jsx("div", { class: "n", children: user.username }),
|
|
2175
|
+
/* @__PURE__ */ jsxs("div", { class: "r", children: [
|
|
2176
|
+
rail.role ? `${rail.role} \xB7 ` : "",
|
|
2177
|
+
rail.team ?? "no team yet"
|
|
2178
|
+
] })
|
|
2124
2179
|
] })
|
|
2125
2180
|
] }),
|
|
2126
2181
|
/* @__PURE__ */ jsx("form", { method: "post", action: "/logout", class: "m0", children: /* @__PURE__ */ jsx("button", { class: "rail-out", type: "submit", title: "Sign out", children: "out" }) })
|
|
@@ -3349,6 +3404,79 @@ a.lrow.sel:hover { background: #eef5f1; }
|
|
|
3349
3404
|
.features-grid { grid-template-columns: 1fr; }
|
|
3350
3405
|
.appnav-user { display: none; }
|
|
3351
3406
|
}
|
|
3407
|
+
|
|
3408
|
+
/* ---------- v2: the team switcher, page tabs, stat strips and reading layouts ---------- */
|
|
3409
|
+
/* Ported from the "STMA v2" prototype. Everything above is unchanged: v2 keeps
|
|
3410
|
+
the whole design system and adds the components the new screens are built
|
|
3411
|
+
from. What is deliberately NOT here is the prototype's own scaffolding \u2014 the
|
|
3412
|
+
floating screen switcher and the padding it needed \u2014 because that exists to
|
|
3413
|
+
demo twenty screens in one file, not to ship. */
|
|
3414
|
+
|
|
3415
|
+
.teamswitch { margin: 10px 10px 0; display: flex; align-items: center; gap: 8px; padding: 7px 9px; border: 1px solid var(--dark-line-2); border-radius: 6px; background: #1c2023; cursor: pointer; width: calc(100% - 20px); }
|
|
3416
|
+
.teamswitch:hover { border-color: #4a5157; }
|
|
3417
|
+
.teamswitch .tname { flex: 1; min-width: 0; text-align: left; font: 500 13px/1 var(--sans); color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3418
|
+
.teamswitch .caret { color: var(--dark-mut-2); font-size: 10px; }
|
|
3419
|
+
/* The open state the prototype had no need for: a real switcher has to list the
|
|
3420
|
+
teams. A details/summary pair, so it works with no script at all. */
|
|
3421
|
+
.teamswitch::-webkit-details-marker { display: none; }
|
|
3422
|
+
.teamswitch::marker { content: ''; }
|
|
3423
|
+
.teampick[open] .teamswitch { border-color: #4a5157; }
|
|
3424
|
+
.teampick[open] .teamswitch .caret { transform: rotate(180deg); }
|
|
3425
|
+
.teammenu { margin: 4px 10px 0; padding: 4px; border: 1px solid var(--dark-line-2); border-radius: 6px; background: #1c2023; display: flex; flex-direction: column; gap: 1px; }
|
|
3426
|
+
.teammenu a { display: flex; align-items: baseline; gap: 6px; padding: 6px 8px; border-radius: 4px; font: 400 13px/1.2 var(--sans); color: var(--dark-txt-2); }
|
|
3427
|
+
.teammenu a:hover { background: #22262a; color: #fff; text-decoration: none; }
|
|
3428
|
+
.teammenu a.on { color: #fff; }
|
|
3429
|
+
.teammenu a .r { font: 400 11px/1 var(--mono); color: var(--dark-mut-2); }
|
|
3430
|
+
.teammenu .sep { height: 1px; margin: 3px 2px; background: var(--dark-line-2); }
|
|
3431
|
+
|
|
3432
|
+
.railme { display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0; }
|
|
3433
|
+
.railme:hover { text-decoration: none; }
|
|
3434
|
+
.railme:hover .n { color: #fff; }
|
|
3435
|
+
|
|
3436
|
+
.pagetabs { background: #fff; border-bottom: 1px solid var(--line); padding: 0 20px; display: flex; gap: 2px; }
|
|
3437
|
+
|
|
3438
|
+
.introw { display: flex; align-items: center; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--line-2); }
|
|
3439
|
+
.introw:last-child { border-bottom: none; }
|
|
3440
|
+
.introw .who { flex: 1; min-width: 0; }
|
|
3441
|
+
.introw .who .t { font: 500 14px/1.3 var(--sans); }
|
|
3442
|
+
.introw .who .s { margin-top: 2px; font: 400 12px/1.4 var(--sans); color: var(--mut); }
|
|
3443
|
+
|
|
3444
|
+
.stat3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); background: #fff; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
|
|
3445
|
+
.stat3 > div { padding: 14px 16px; border-right: 1px solid var(--line-2); display: flex; flex-direction: column; gap: 7px; }
|
|
3446
|
+
.stat3 > div:last-child { border-right: none; }
|
|
3447
|
+
.stat3 .n { font: 600 20px/1 var(--mono); }
|
|
3448
|
+
.stat3 .n.ok { color: var(--green-strong); }
|
|
3449
|
+
.stat3 .n.bad { color: var(--red); }
|
|
3450
|
+
|
|
3451
|
+
.docgrid { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 32px; align-items: start; }
|
|
3452
|
+
.sidetoc { position: sticky; top: 16px; display: flex; flex-direction: column; gap: 2px; }
|
|
3453
|
+
.sidetoc a { padding: 6px 10px; border-left: 2px solid var(--line-2); font: 400 13px/1.3 var(--sans); color: var(--txt-3); }
|
|
3454
|
+
.sidetoc a:hover { color: var(--ink); text-decoration: none; }
|
|
3455
|
+
.sidetoc a.on { color: var(--green-strong); font-weight: 500; border-left-color: var(--green); }
|
|
3456
|
+
|
|
3457
|
+
.toolrow { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 14px; padding: 10px 18px; border-bottom: 1px solid var(--line-2); }
|
|
3458
|
+
.toolrow:last-child { border-bottom: none; }
|
|
3459
|
+
.toolrow .tn { font: 400 13px/1.4 var(--mono); color: var(--txt-3); }
|
|
3460
|
+
.toolrow .td { font: 400 13px/1.5 var(--sans); }
|
|
3461
|
+
|
|
3462
|
+
/* Editor layout: the document on the left, what it will produce on the right. */
|
|
3463
|
+
.edgrid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); gap: 20px; align-items: start; }
|
|
3464
|
+
|
|
3465
|
+
/* The ledger stops being squeezed before the inspector does. */
|
|
3466
|
+
.cbody .cmain { min-width: 520px; }
|
|
3467
|
+
@media (max-width: 1200px) {
|
|
3468
|
+
.cbody { overflow-x: auto; }
|
|
3469
|
+
.inspector { width: 320px; min-width: 320px; }
|
|
3470
|
+
}
|
|
3471
|
+
@media (max-width: 960px) {
|
|
3472
|
+
.cbody .cmain { min-width: 420px; }
|
|
3473
|
+
.lrow.grid-runs, .lhead.grid-runs { grid-template-columns: 26px minmax(0, 1.4fr) minmax(0, 1.6fr) 92px; }
|
|
3474
|
+
.grid-runs > *:nth-child(4), .grid-runs > *:nth-child(5) { display: none; }
|
|
3475
|
+
.docgrid { grid-template-columns: 1fr; gap: 18px; }
|
|
3476
|
+
.sidetoc { position: static; flex-direction: row; flex-wrap: wrap; }
|
|
3477
|
+
.edgrid { grid-template-columns: 1fr; }
|
|
3478
|
+
.toolrow { grid-template-columns: 1fr; gap: 4px; }
|
|
3479
|
+
}
|
|
3352
3480
|
`
|
|
3353
3481
|
);
|
|
3354
3482
|
|
|
@@ -3590,7 +3718,7 @@ activityRoutes.get("/app/teams/:slug/activity", async (c) => {
|
|
|
3590
3718
|
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
3591
3719
|
/* @__PURE__ */ jsx4("th", { children: "When" }),
|
|
3592
3720
|
/* @__PURE__ */ jsx4("th", { children: "User" }),
|
|
3593
|
-
/* @__PURE__ */ jsx4("th", { children: "Agent
|
|
3721
|
+
/* @__PURE__ */ jsx4("th", { children: "Agent" }),
|
|
3594
3722
|
/* @__PURE__ */ jsx4("th", { children: "Action" }),
|
|
3595
3723
|
/* @__PURE__ */ jsx4("th", { children: "Project" }),
|
|
3596
3724
|
/* @__PURE__ */ jsx4("th", { children: "Detail" })
|
|
@@ -3718,7 +3846,7 @@ async function connectDb(env2) {
|
|
|
3718
3846
|
}
|
|
3719
3847
|
|
|
3720
3848
|
// src/lib/counters.ts
|
|
3721
|
-
async function hitCounter(db, bucket, subject2, windowMs,
|
|
3849
|
+
async function hitCounter(db, bucket, subject2, windowMs, max8, by = 1) {
|
|
3722
3850
|
const now = Date.now();
|
|
3723
3851
|
const windowStart = Math.floor(now / windowMs) * windowMs;
|
|
3724
3852
|
const resetAt = new Date(windowStart + windowMs);
|
|
@@ -3727,8 +3855,8 @@ async function hitCounter(db, bucket, subject2, windowMs, max7, by = 1) {
|
|
|
3727
3855
|
target: rateCounters.key,
|
|
3728
3856
|
set: { count: sql4`${rateCounters.count} + ${by}` }
|
|
3729
3857
|
}).returning({ count: rateCounters.count });
|
|
3730
|
-
const
|
|
3731
|
-
return { count:
|
|
3858
|
+
const count14 = rows[0]?.count ?? by;
|
|
3859
|
+
return { count: count14, resetAt, exceeded: count14 > max8 };
|
|
3732
3860
|
}
|
|
3733
3861
|
async function readCounter(db, bucket, subject2, windowMs) {
|
|
3734
3862
|
const windowStart = Math.floor(Date.now() / windowMs) * windowMs;
|
|
@@ -4725,8 +4853,8 @@ var CRM_STATUSES = [
|
|
|
4725
4853
|
];
|
|
4726
4854
|
var PIPELINE_STATUSES = ["lead", "contacted", "demo", "onboarding", "active"];
|
|
4727
4855
|
var ARCHIVED_STATUSES = ["churned", "lost"];
|
|
4728
|
-
var optField = (v,
|
|
4729
|
-
const s = typeof v === "string" ? v.trim().slice(0,
|
|
4856
|
+
var optField = (v, max8) => {
|
|
4857
|
+
const s = typeof v === "string" ? v.trim().slice(0, max8) : "";
|
|
4730
4858
|
return s || null;
|
|
4731
4859
|
};
|
|
4732
4860
|
function parseContactForm(body) {
|
|
@@ -5251,8 +5379,8 @@ async function recordRunQuota(db, found, quota) {
|
|
|
5251
5379
|
const source = quota.source;
|
|
5252
5380
|
const measured = source === "measured";
|
|
5253
5381
|
const previous = found.run.quotaState ?? "ok";
|
|
5254
|
-
const
|
|
5255
|
-
const escalated = measured &&
|
|
5382
|
+
const rank3 = { ok: 0, warning: 1, critical: 2 };
|
|
5383
|
+
const escalated = measured && rank3[state] > rank3[previous];
|
|
5256
5384
|
const resetsAt = quota.resetsAt ? new Date(quota.resetsAt) : null;
|
|
5257
5385
|
await db.update(agentRuns).set({
|
|
5258
5386
|
quotaPct: usedPct,
|
|
@@ -5781,7 +5909,7 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
5781
5909
|
)
|
|
5782
5910
|
);
|
|
5783
5911
|
}
|
|
5784
|
-
const
|
|
5912
|
+
const severityByRun2 = /* @__PURE__ */ new Map();
|
|
5785
5913
|
const clashesByRun = /* @__PURE__ */ new Map();
|
|
5786
5914
|
const clashing = /* @__PURE__ */ new Set();
|
|
5787
5915
|
const clashes = [];
|
|
@@ -5795,7 +5923,7 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
5795
5923
|
});
|
|
5796
5924
|
const found = detectClaimConflicts(current, existing);
|
|
5797
5925
|
const highest = found[0]?.severity;
|
|
5798
|
-
if (highest)
|
|
5926
|
+
if (highest) severityByRun2.set(row.run.id, highest);
|
|
5799
5927
|
if (found.length > 0) clashesByRun.set(row.run.id, found);
|
|
5800
5928
|
for (const conflict of found) {
|
|
5801
5929
|
for (const side of [conflict.current, conflict.existing]) {
|
|
@@ -5816,7 +5944,7 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
5816
5944
|
}
|
|
5817
5945
|
const wanted = c.req.query("run");
|
|
5818
5946
|
const selected = (wanted ? rows.find((row) => row.run.id === wanted) : void 0) ?? // Default to the run that most needs attention, then to the newest.
|
|
5819
|
-
rows.find((row) =>
|
|
5947
|
+
rows.find((row) => severityByRun2.get(row.run.id) === "critical") ?? rows[0];
|
|
5820
5948
|
const trail = selected ? await eventsForRun(db, selected.run.id) : [];
|
|
5821
5949
|
const pack = selected ? await evidenceForRun(db, selected.run.id, user.id) : null;
|
|
5822
5950
|
const receipt = selected ? (await db.select().from(policyReceipts).where(eq13(policyReceipts.runId, selected.run.id)).limit(1))[0] : void 0;
|
|
@@ -5945,7 +6073,7 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
5945
6073
|
) : null;
|
|
5946
6074
|
const inspector = selected ? (() => {
|
|
5947
6075
|
const held = claimsByRun.get(selected.run.id) ?? [];
|
|
5948
|
-
const severity =
|
|
6076
|
+
const severity = severityByRun2.get(selected.run.id);
|
|
5949
6077
|
const worst = (clashesByRun.get(selected.run.id) ?? [])[0];
|
|
5950
6078
|
const others = (clashesByRun.get(selected.run.id) ?? []).length - 1;
|
|
5951
6079
|
const fresh = Date.now() - selected.run.lastHeartbeatAt.getTime() < FRESH_MS;
|
|
@@ -5968,7 +6096,16 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
5968
6096
|
/* @__PURE__ */ jsxs6("div", { class: "ins-acts", children: [
|
|
5969
6097
|
/* @__PURE__ */ jsx6("a", { class: "btn btn-sm", href: `/app/teams/${selected.team.slug}/governance`, children: "Governance" }),
|
|
5970
6098
|
/* @__PURE__ */ jsx6("a", { class: "btn btn-sm", href: `/app/teams/${selected.team.slug}/activity`, children: "Activity" }),
|
|
5971
|
-
/* @__PURE__ */ jsx6(
|
|
6099
|
+
selected.projectName ? /* @__PURE__ */ jsx6(
|
|
6100
|
+
"a",
|
|
6101
|
+
{
|
|
6102
|
+
class: "btn btn-sm",
|
|
6103
|
+
href: `/app/teams/${selected.team.slug}/projects/${encodeURIComponent(
|
|
6104
|
+
selected.projectName
|
|
6105
|
+
)}`,
|
|
6106
|
+
children: "Project"
|
|
6107
|
+
}
|
|
6108
|
+
) : /* @__PURE__ */ jsx6("a", { class: "btn btn-sm", href: "/app/sessions", children: "Sessions" })
|
|
5972
6109
|
] }),
|
|
5973
6110
|
/* @__PURE__ */ jsxs6("div", { class: "ins-sec", children: [
|
|
5974
6111
|
/* @__PURE__ */ jsx6("span", { class: "ins-label", children: "Scope held" }),
|
|
@@ -6111,7 +6248,7 @@ agentsRoutes.get("/app/agents", async (c) => {
|
|
|
6111
6248
|
] }),
|
|
6112
6249
|
rows.map((row) => {
|
|
6113
6250
|
const held = claimsByRun.get(row.run.id) ?? [];
|
|
6114
|
-
const severity =
|
|
6251
|
+
const severity = severityByRun2.get(row.run.id);
|
|
6115
6252
|
const fresh = Date.now() - row.run.lastHeartbeatAt.getTime() < FRESH_MS;
|
|
6116
6253
|
return /* @__PURE__ */ jsxs6(
|
|
6117
6254
|
"a",
|
|
@@ -6601,7 +6738,7 @@ savingsRoutes.get("/app/teams/:slug/savings", async (c) => {
|
|
|
6601
6738
|
head: /* @__PURE__ */ jsx7(
|
|
6602
6739
|
PageHead,
|
|
6603
6740
|
{
|
|
6604
|
-
crumb: team.
|
|
6741
|
+
crumb: `/ ${team.slug} / savings`,
|
|
6605
6742
|
title: "Verified savings",
|
|
6606
6743
|
sub: "What STMA prevented, separated from what somebody confirmed it prevented."
|
|
6607
6744
|
}
|
|
@@ -7151,9 +7288,9 @@ var BATCH = 100;
|
|
|
7151
7288
|
var MESSAGE_WINDOW = 20;
|
|
7152
7289
|
var EXCERPT = 280;
|
|
7153
7290
|
var TITLE = 90;
|
|
7154
|
-
function oneLine(text3,
|
|
7291
|
+
function oneLine(text3, max8) {
|
|
7155
7292
|
const flat = text3.replace(/\s+/g, " ").trim();
|
|
7156
|
-
return flat.length >
|
|
7293
|
+
return flat.length > max8 ? `${flat.slice(0, max8 - 1)}\u2026` : flat;
|
|
7157
7294
|
}
|
|
7158
7295
|
var article = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
|
|
7159
7296
|
function attribution(username, via) {
|
|
@@ -7936,9 +8073,9 @@ var LOGIN_FAIL_MAX = 5;
|
|
|
7936
8073
|
var BUCKET = "login-fail";
|
|
7937
8074
|
var subject = (email) => sha256hex(email.trim().toLowerCase()).slice(0, 32);
|
|
7938
8075
|
async function loginGate(db, email) {
|
|
7939
|
-
const
|
|
8076
|
+
const count14 = await readCounter(db, BUCKET, subject(email), LOGIN_FAIL_WINDOW_MS);
|
|
7940
8077
|
const windowStart = Math.floor(Date.now() / LOGIN_FAIL_WINDOW_MS) * LOGIN_FAIL_WINDOW_MS;
|
|
7941
|
-
return { locked:
|
|
8078
|
+
return { locked: count14 >= LOGIN_FAIL_MAX, resetAt: new Date(windowStart + LOGIN_FAIL_WINDOW_MS) };
|
|
7942
8079
|
}
|
|
7943
8080
|
async function recordLoginFailure(db, email) {
|
|
7944
8081
|
const hit = await hitCounter(
|
|
@@ -8823,9 +8960,21 @@ compareRoutes.get("/app/teams/:slug/compare", async (c) => {
|
|
|
8823
8960
|
crumb: `/ ${team.slug} / environments`,
|
|
8824
8961
|
title: "Compare environments",
|
|
8825
8962
|
sub: scopeProject ? `Two machines, one mechanical diff \u2014 comparing each side's newest ${scopeProject.name} snapshot.` : "Two machines, one mechanical diff \u2014 the same comparison your agents get from compare_env.",
|
|
8826
|
-
actions: /* @__PURE__ */ jsxs9(
|
|
8827
|
-
|
|
8828
|
-
|
|
8963
|
+
actions: /* @__PURE__ */ jsxs9(Fragment8, { children: [
|
|
8964
|
+
bSide ? /* @__PURE__ */ jsx9(
|
|
8965
|
+
"a",
|
|
8966
|
+
{
|
|
8967
|
+
class: "btn btn-sm",
|
|
8968
|
+
href: `/app/teams/${team.slug}/compare?a=${encodeURIComponent(
|
|
8969
|
+
sideValue(bSide)
|
|
8970
|
+
)}&b=${encodeURIComponent(sideValue(aSide))}${scopeProject ? `&project=${encodeURIComponent(scopeProject.name)}` : ""}`,
|
|
8971
|
+
children: "\u21C4 Swap sides"
|
|
8972
|
+
}
|
|
8973
|
+
) : null,
|
|
8974
|
+
/* @__PURE__ */ jsxs9("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}`, children: [
|
|
8975
|
+
"Back to ",
|
|
8976
|
+
team.slug
|
|
8977
|
+
] })
|
|
8829
8978
|
] })
|
|
8830
8979
|
}
|
|
8831
8980
|
),
|
|
@@ -10216,6 +10365,7 @@ var docsRoutes = new Hono9();
|
|
|
10216
10365
|
docsRoutes.get("/docs", (c) => {
|
|
10217
10366
|
const env2 = c.get("env");
|
|
10218
10367
|
const user = c.get("user");
|
|
10368
|
+
const showConsole = Boolean(user) || env2.publicMode === "full";
|
|
10219
10369
|
const base2 = env2.baseUrl;
|
|
10220
10370
|
const mcpUrl = `${base2}/mcp`;
|
|
10221
10371
|
const claudeCmd = `claude mcp add --scope user --transport http stma ${mcpUrl} --header "Authorization: Bearer stma_YOUR_TOKEN"`;
|
|
@@ -10242,24 +10392,23 @@ docsRoutes.get("/docs", (c) => {
|
|
|
10242
10392
|
attempts: `Try this three different ways in parallel, one per worktree. Give every run the same stma attempt_group "TASK-fanout" and its own worktree path, so the three of you don't warn each other about touching the same files \u2014 then show me the three diffs side by side.`,
|
|
10243
10393
|
issues: `Call stma list_issues, show me what is open, and when I pick one call start_run with that issue number. Work it on a branch, and when you finish, finish_run so the issue gets the update.`
|
|
10244
10394
|
};
|
|
10245
|
-
const body = /* @__PURE__ */
|
|
10246
|
-
/* @__PURE__ */ jsxs11("
|
|
10395
|
+
const body = /* @__PURE__ */ jsx11(Fragment10, { children: /* @__PURE__ */ jsxs11("div", { class: "docgrid", children: [
|
|
10396
|
+
/* @__PURE__ */ jsxs11("nav", { class: "sidetoc", children: [
|
|
10397
|
+
/* @__PURE__ */ jsx11("a", { href: "#how", children: "How it works" }),
|
|
10398
|
+
showConsole ? /* @__PURE__ */ jsx11("a", { href: "#web", children: "Quick start" }) : null,
|
|
10399
|
+
/* @__PURE__ */ jsx11("a", { href: "#connect", children: "Connect an agent" }),
|
|
10400
|
+
/* @__PURE__ */ jsx11("a", { href: "#control-plane", children: "Agent control plane" }),
|
|
10401
|
+
/* @__PURE__ */ jsx11("a", { href: "#tools", children: "Tool reference" }),
|
|
10402
|
+
/* @__PURE__ */ jsx11("a", { href: "#prompts", children: "Paste-ready prompts" }),
|
|
10403
|
+
showConsole ? /* @__PURE__ */ jsx11("a", { href: "#dashboard", children: "The console" }) : null,
|
|
10404
|
+
/* @__PURE__ */ jsx11("a", { href: "#security", children: "Security" }),
|
|
10405
|
+
/* @__PURE__ */ jsx11("a", { href: "#troubleshooting", children: "Troubleshooting" })
|
|
10406
|
+
] }),
|
|
10407
|
+
/* @__PURE__ */ jsxs11("div", { class: "doc-col", style: "max-width:none", children: [
|
|
10247
10408
|
/* @__PURE__ */ jsxs11("div", { children: [
|
|
10248
10409
|
/* @__PURE__ */ jsx11("h1", { class: "title", style: "font-size:30px", children: "How to use STMA" }),
|
|
10249
10410
|
/* @__PURE__ */ jsx11("p", { class: "sub", style: "max-width:60ch", children: "STMA is the shared control plane for your team's coding agents: it maps each run to its human, project and task, detects overlapping work, distributes global policy, checks environments, and keeps async debug context. This page covers both the human dashboard and agent-facing surfaces." })
|
|
10250
10411
|
] }),
|
|
10251
|
-
/* @__PURE__ */ jsxs11("div", { class: "toc", children: [
|
|
10252
|
-
/* @__PURE__ */ jsx11("a", { href: "#how", children: "How it works" }),
|
|
10253
|
-
/* @__PURE__ */ jsx11("a", { href: "#web", children: "Quick start: web" }),
|
|
10254
|
-
/* @__PURE__ */ jsx11("a", { href: "#terminal", children: "Quick start: terminal" }),
|
|
10255
|
-
/* @__PURE__ */ jsx11("a", { href: "#connect", children: "Connect an agent" }),
|
|
10256
|
-
/* @__PURE__ */ jsx11("a", { href: "#control-plane", children: "Agent control plane" }),
|
|
10257
|
-
/* @__PURE__ */ jsx11("a", { href: "#tools", children: "Tool reference" }),
|
|
10258
|
-
/* @__PURE__ */ jsx11("a", { href: "#prompts", children: "Paste-ready prompts" }),
|
|
10259
|
-
/* @__PURE__ */ jsx11("a", { href: "#dashboard", children: "Dashboard" }),
|
|
10260
|
-
/* @__PURE__ */ jsx11("a", { href: "#security", children: "Security" }),
|
|
10261
|
-
/* @__PURE__ */ jsx11("a", { href: "#troubleshooting", children: "Troubleshooting" })
|
|
10262
|
-
] }),
|
|
10263
10412
|
/* @__PURE__ */ jsxs11("div", { class: "hero-card", children: [
|
|
10264
10413
|
/* @__PURE__ */ jsx11("span", { class: "overline", style: "color:var(--green-strong)", children: "The point" }),
|
|
10265
10414
|
/* @__PURE__ */ jsxs11("p", { children: [
|
|
@@ -10273,44 +10422,42 @@ docsRoutes.get("/docs", (c) => {
|
|
|
10273
10422
|
/* @__PURE__ */ jsx11("p", { children: P.hero }),
|
|
10274
10423
|
/* @__PURE__ */ jsx11("button", { class: "copybtn onlight", type: "button", "data-copy": P.hero, children: "COPY" })
|
|
10275
10424
|
] })
|
|
10276
|
-
] })
|
|
10277
|
-
] }),
|
|
10278
|
-
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "how", children: [
|
|
10279
|
-
/* @__PURE__ */ jsx11("h2", { children: "How it works" }),
|
|
10280
|
-
/* @__PURE__ */ jsxs11("p", { class: "m0 sub", style: "max-width:72ch", children: [
|
|
10281
|
-
"Two halves, one account. Agents reach STMA over ",
|
|
10282
|
-
/* @__PURE__ */ jsx11("b", { children: "MCP" }),
|
|
10283
|
-
" \u2014 and that includes the fleet half: runs, work claims, policy receipts and environment preflight, with nothing installed. The ",
|
|
10284
|
-
/* @__PURE__ */ jsx11("b", { children: "CLI and its lifecycle hooks" }),
|
|
10285
|
-
" report the same things without anyone typing a command, for clients that would rather not think about it. Both land in the same control plane, and everything a human needs to see is a plain server-rendered page."
|
|
10286
10425
|
] }),
|
|
10287
|
-
/* @__PURE__ */ jsxs11("
|
|
10288
|
-
/* @__PURE__ */ jsx11(
|
|
10289
|
-
/* @__PURE__ */ jsxs11("
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10426
|
+
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "how", children: [
|
|
10427
|
+
/* @__PURE__ */ jsx11("h2", { children: "How it works" }),
|
|
10428
|
+
/* @__PURE__ */ jsxs11("p", { class: "m0 sub", style: "max-width:72ch", children: [
|
|
10429
|
+
"Two halves, one account. Agents reach STMA over ",
|
|
10430
|
+
/* @__PURE__ */ jsx11("b", { children: "MCP" }),
|
|
10431
|
+
" \u2014 and that includes the fleet half: runs, work claims, policy receipts and environment preflight, with nothing installed. The ",
|
|
10432
|
+
/* @__PURE__ */ jsx11("b", { children: "CLI and its lifecycle hooks" }),
|
|
10433
|
+
" report the same things without anyone typing a command, for clients that would rather not think about it. Both land in the same control plane, and everything a human needs to see is a plain server-rendered page."
|
|
10434
|
+
] }),
|
|
10435
|
+
/* @__PURE__ */ jsxs11("div", { class: "card card-pad", children: [
|
|
10436
|
+
/* @__PURE__ */ jsx11(SystemDiagram, {}),
|
|
10437
|
+
/* @__PURE__ */ jsxs11("div", { class: "legend", style: "margin-top:16px", children: [
|
|
10438
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
10439
|
+
/* @__PURE__ */ jsx11("span", { class: "sw", style: "background:var(--green-bg);border:1px solid var(--green-line)" }),
|
|
10440
|
+
" ",
|
|
10441
|
+
"agent client"
|
|
10442
|
+
] }),
|
|
10443
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
10444
|
+
/* @__PURE__ */ jsx11("span", { class: "sw", style: "background:var(--dark)" }),
|
|
10445
|
+
" control plane"
|
|
10446
|
+
] }),
|
|
10447
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
10448
|
+
/* @__PURE__ */ jsx11("span", { class: "sw", style: "background:#fff;border:1px solid var(--line-frame)" }),
|
|
10449
|
+
" ",
|
|
10450
|
+
"what people read"
|
|
10451
|
+
] })
|
|
10298
10452
|
] }),
|
|
10299
|
-
/* @__PURE__ */ jsxs11("
|
|
10300
|
-
|
|
10301
|
-
" ",
|
|
10302
|
-
"
|
|
10453
|
+
/* @__PURE__ */ jsxs11("p", { class: "m0 small muted", style: "margin-top:12px", children: [
|
|
10454
|
+
"The same picture covers one person with two machines: alice and bob become your laptop and your desktop, and ",
|
|
10455
|
+
/* @__PURE__ */ jsx11("code", { children: "compare_env" }),
|
|
10456
|
+
' answers "why does it only fail on the Windows box?" with no teammate involved.'
|
|
10303
10457
|
] })
|
|
10304
|
-
] }),
|
|
10305
|
-
/* @__PURE__ */ jsxs11("p", { class: "m0 small muted", style: "margin-top:12px", children: [
|
|
10306
|
-
"The same picture covers one person with two machines: alice and bob become your laptop and your desktop, and ",
|
|
10307
|
-
/* @__PURE__ */ jsx11("code", { children: "compare_env" }),
|
|
10308
|
-
' answers "why does it only fail on the Windows box?" with no teammate involved.'
|
|
10309
10458
|
] })
|
|
10310
|
-
] })
|
|
10311
|
-
|
|
10312
|
-
/* @__PURE__ */ jsxs11("div", { class: "doc-col", children: [
|
|
10313
|
-
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "web", children: [
|
|
10459
|
+
] }),
|
|
10460
|
+
showConsole ? /* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "web", children: [
|
|
10314
10461
|
/* @__PURE__ */ jsx11("h2", { children: "Quick start \u2014 from the web" }),
|
|
10315
10462
|
/* @__PURE__ */ jsxs11("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:14px", children: [
|
|
10316
10463
|
/* @__PURE__ */ jsxs11("div", { class: "step-row", children: [
|
|
@@ -10371,7 +10518,7 @@ docsRoutes.get("/docs", (c) => {
|
|
|
10371
10518
|
] })
|
|
10372
10519
|
] })
|
|
10373
10520
|
] })
|
|
10374
|
-
] }),
|
|
10521
|
+
] }) : null,
|
|
10375
10522
|
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "terminal", children: [
|
|
10376
10523
|
/* @__PURE__ */ jsx11("h2", { children: "Quick start \u2014 from the terminal" }),
|
|
10377
10524
|
/* @__PURE__ */ jsxs11("p", { class: "m0 sub", style: "max-width:64ch", children: [
|
|
@@ -10861,7 +11008,7 @@ Authorization: Bearer stma_YOUR_TOKEN` }),
|
|
|
10861
11008
|
] })
|
|
10862
11009
|
] })
|
|
10863
11010
|
] }),
|
|
10864
|
-
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "dashboard", children: [
|
|
11011
|
+
showConsole ? /* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "dashboard", children: [
|
|
10865
11012
|
/* @__PURE__ */ jsx11("h2", { children: "The console (for humans)" }),
|
|
10866
11013
|
/* @__PURE__ */ jsxs11("p", { class: "m0 sub", style: "max-width:74ch", children: [
|
|
10867
11014
|
"One grammar on every page: a ",
|
|
@@ -10883,12 +11030,28 @@ Authorization: Bearer stma_YOUR_TOKEN` }),
|
|
|
10883
11030
|
/* @__PURE__ */ jsx11("th", { children: "Page" }),
|
|
10884
11031
|
/* @__PURE__ */ jsx11("th", { children: "What you do there" })
|
|
10885
11032
|
] }),
|
|
11033
|
+
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
11034
|
+
/* @__PURE__ */ jsx11("td", { class: "name", children: "Projects" }),
|
|
11035
|
+
/* @__PURE__ */ jsx11("td", { children: "A project per repository, born the first time an agent names one \u2014 nothing to create. The list carries runs now, open sessions, whether a baseline exists, the policy version and the delivery flow; opening one puts that project's live runs, threads, run trail, policy and environment on a single page, each next to the control that changes it." })
|
|
11036
|
+
] }),
|
|
11037
|
+
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
11038
|
+
/* @__PURE__ */ jsx11("td", { class: "name", children: "Account" }),
|
|
11039
|
+
/* @__PURE__ */ jsx11("td", { children: "Your password and account deletion, behind your own name at the foot of the rail. Tokens have their own page \u2014 one per machine \u2014 and what STMA emails you lives on Notifications." })
|
|
11040
|
+
] }),
|
|
10886
11041
|
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
10887
11042
|
/* @__PURE__ */ jsx11("td", { class: "name", children: "Teams" }),
|
|
10888
11043
|
/* @__PURE__ */ jsxs11("td", { children: [
|
|
10889
11044
|
"Create a team from ",
|
|
10890
11045
|
/* @__PURE__ */ jsx11("b", { children: "New team" }),
|
|
10891
|
-
" \u2014 a name is all that is required; the tag (the short id in URLs and agent config) and a team chat webhook are optional and marked as such.
|
|
11046
|
+
" \u2014 a name is all that is required; the tag (the short id in URLs and agent config) and a team chat webhook are optional and marked as such. A team's own page is four tabs: ",
|
|
11047
|
+
/* @__PURE__ */ jsx11("b", { children: "Overview" }),
|
|
11048
|
+
" (projects, team health, what agents share), ",
|
|
11049
|
+
/* @__PURE__ */ jsx11("b", { children: "People" }),
|
|
11050
|
+
" (members and invite links),",
|
|
11051
|
+
/* @__PURE__ */ jsx11("b", { children: "Integrations" }),
|
|
11052
|
+
" (Slack/Discord, inbound CI and GitHub hooks, GitHub, Azure DevOps and Jira connections \u2014 owners) and ",
|
|
11053
|
+
/* @__PURE__ */ jsx11("b", { children: "Settings" }),
|
|
11054
|
+
" (leave, remove a member, delete the team). The tab is in the URL, so a link to one is a link to what you were looking at."
|
|
10892
11055
|
] })
|
|
10893
11056
|
] }),
|
|
10894
11057
|
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
@@ -10897,7 +11060,13 @@ Authorization: Bearer stma_YOUR_TOKEN` }),
|
|
|
10897
11060
|
] }),
|
|
10898
11061
|
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
10899
11062
|
/* @__PURE__ */ jsx11("td", { class: "name", children: "Governance" }),
|
|
10900
|
-
/* @__PURE__ */
|
|
11063
|
+
/* @__PURE__ */ jsxs11("td", { children: [
|
|
11064
|
+
"Did your rules actually reach the agents: the effective policy for the team and each project, receipts showing the hash each run applied against the one the server expected (drift called out), environment baselines, the preflight results agents were given, and a timeline of run events. A project filter in the strip narrows every list to one project \u2014 global stays the default. Owners publish the rulebook and record an environment baseline from this page: one rule per line in a form that opens on whatever is live (scoped to a project, on that project's own additions), and a baseline promoted from a snapshot the team already pushed, picked by person and machine. Owners publish from ",
|
|
11065
|
+
/* @__PURE__ */ jsx11("b", { children: "Edit policy" }),
|
|
11066
|
+
", a page that shows the document on the left and what ",
|
|
11067
|
+
/* @__PURE__ */ jsx11("code", { children: "get_policy" }),
|
|
11068
|
+
" will serve on the right."
|
|
11069
|
+
] })
|
|
10901
11070
|
] }),
|
|
10902
11071
|
/* @__PURE__ */ jsxs11("tr", { children: [
|
|
10903
11072
|
/* @__PURE__ */ jsx11("td", { class: "name", children: "Delivery" }),
|
|
@@ -10932,7 +11101,7 @@ Authorization: Bearer stma_YOUR_TOKEN` }),
|
|
|
10932
11101
|
/* @__PURE__ */ jsx11("td", { children: "The same env diff agents get, as a visual side-by-side report." })
|
|
10933
11102
|
] })
|
|
10934
11103
|
] }) })
|
|
10935
|
-
] }),
|
|
11104
|
+
] }) : null,
|
|
10936
11105
|
/* @__PURE__ */ jsxs11("section", { class: "doc-section", id: "security", children: [
|
|
10937
11106
|
/* @__PURE__ */ jsx11("h2", { children: "Security model" }),
|
|
10938
11107
|
/* @__PURE__ */ jsxs11("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:10px", children: [
|
|
@@ -11076,7 +11245,7 @@ Authorization: Bearer stma_YOUR_TOKEN` }),
|
|
|
11076
11245
|
] })
|
|
11077
11246
|
] })
|
|
11078
11247
|
] })
|
|
11079
|
-
] });
|
|
11248
|
+
] }) });
|
|
11080
11249
|
if (user) {
|
|
11081
11250
|
return c.html(
|
|
11082
11251
|
/* @__PURE__ */ jsx11(AppLayout, { user, active: "docs", title: "Docs", children: body })
|
|
@@ -11556,10 +11725,58 @@ var SnapshotCell = ({ last }) => last ? /* @__PURE__ */ jsxs14("span", { class:
|
|
|
11556
11725
|
/* @__PURE__ */ jsx14("span", { class: "muted", children: timeAgo(last) })
|
|
11557
11726
|
] }) : /* @__PURE__ */ jsx14("span", { style: "color:var(--mut-2)", children: "Agent not connected" });
|
|
11558
11727
|
var DeviceNames = ({ devices }) => devices.length > 1 || devices[0] && devices[0].device !== DEFAULT_DEVICE ? /* @__PURE__ */ jsx14("div", { class: "mono", style: "font-size:11px;color:var(--mut-2);margin-top:2px", children: devices.map((d) => d.device).join(" \xB7 ") }) : null;
|
|
11728
|
+
var TeaserLanding = () => /* @__PURE__ */ jsxs14("html", { lang: "en", children: [
|
|
11729
|
+
/* @__PURE__ */ jsx14(Head, {}),
|
|
11730
|
+
/* @__PURE__ */ jsxs14("body", { children: [
|
|
11731
|
+
/* @__PURE__ */ jsx14("header", { class: "site-head", children: /* @__PURE__ */ jsxs14("div", { class: "container site-head-inner", children: [
|
|
11732
|
+
/* @__PURE__ */ jsxs14("a", { class: "brand", href: "/", children: [
|
|
11733
|
+
/* @__PURE__ */ jsx14(Logo, {}),
|
|
11734
|
+
"Speak to my Agent"
|
|
11735
|
+
] }),
|
|
11736
|
+
/* @__PURE__ */ jsxs14("nav", { class: "site-nav", children: [
|
|
11737
|
+
/* @__PURE__ */ jsx14("a", { class: "plain", href: "/docs", children: "MCP docs" }),
|
|
11738
|
+
/* @__PURE__ */ jsx14("a", { class: "btn btn-sm", href: "/login", children: "Sign in" })
|
|
11739
|
+
] })
|
|
11740
|
+
] }) }),
|
|
11741
|
+
/* @__PURE__ */ jsx14("section", { class: "container hero", style: "grid-template-columns:1fr;max-width:820px", children: /* @__PURE__ */ jsxs14("div", { class: "hero-copy", children: [
|
|
11742
|
+
/* @__PURE__ */ jsx14("span", { class: "pill-beta", children: "Private beta \xB7 invite only" }),
|
|
11743
|
+
/* @__PURE__ */ jsx14("h1", { children: "Coming very soon." }),
|
|
11744
|
+
/* @__PURE__ */ jsx14("p", { class: "lede", children: "STMA \u2014 Speak to my Agent \u2014 is the control plane between a team's AI coding agents: it maps every run to its human, project and task, warns two agents before they touch the same ground, distributes the team's rules, and lets agents hand work over instead of dropping it. The hosted service is in testing with a small group; accounts are invite-only while we learn what it does to a real team's week." }),
|
|
11745
|
+
/* @__PURE__ */ jsx14("p", { class: "lede", style: "margin-top:-6px", children: "The MCP server and the CLI are open source and published today. You do not need an invite to run your own \u2014 one command, an embedded database, no setup:" }),
|
|
11746
|
+
/* @__PURE__ */ jsxs14("div", { class: "cmd", style: "max-width:640px", children: [
|
|
11747
|
+
/* @__PURE__ */ jsx14("code", { children: "npx @matteai/stma serve" }),
|
|
11748
|
+
/* @__PURE__ */ jsx14("button", { class: "copybtn", type: "button", "data-copy": "npx @matteai/stma serve", children: "COPY" })
|
|
11749
|
+
] }),
|
|
11750
|
+
/* @__PURE__ */ jsxs14("div", { class: "row", style: "margin-top:18px", children: [
|
|
11751
|
+
/* @__PURE__ */ jsx14("a", { class: "btn btn-primary btn-lg", href: "/docs", children: "Read the MCP docs" }),
|
|
11752
|
+
/* @__PURE__ */ jsx14("a", { class: "btn btn-lg", href: "/login", children: "I have an invite" })
|
|
11753
|
+
] }),
|
|
11754
|
+
/* @__PURE__ */ jsxs14("div", { class: "hero-note", children: [
|
|
11755
|
+
/* @__PURE__ */ jsx14("span", { class: "dot" }),
|
|
11756
|
+
"Secret values are never collected \u2014 only variable names."
|
|
11757
|
+
] })
|
|
11758
|
+
] }) }),
|
|
11759
|
+
/* @__PURE__ */ jsx14("footer", { class: "site-foot", children: /* @__PURE__ */ jsxs14("div", { class: "container site-foot-inner", children: [
|
|
11760
|
+
/* @__PURE__ */ jsx14("span", { children: "\xA9 2026 STMA \xB7 Speak to my Agent \u2014 private beta" }),
|
|
11761
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
11762
|
+
/* @__PURE__ */ jsx14("a", { class: "plain", href: "/docs", style: "color:var(--mut)", children: "Docs" }),
|
|
11763
|
+
" ",
|
|
11764
|
+
"\xB7",
|
|
11765
|
+
" ",
|
|
11766
|
+
/* @__PURE__ */ jsx14("a", { class: "plain", href: "/terms", style: "color:var(--mut)", children: "Terms" }),
|
|
11767
|
+
" ",
|
|
11768
|
+
"\xB7",
|
|
11769
|
+
" ",
|
|
11770
|
+
/* @__PURE__ */ jsx14("a", { class: "plain", href: "/privacy", style: "color:var(--mut)", children: "Privacy" })
|
|
11771
|
+
] })
|
|
11772
|
+
] }) })
|
|
11773
|
+
] })
|
|
11774
|
+
] });
|
|
11559
11775
|
dashboardRoutes.get("/", (c) => {
|
|
11560
11776
|
const user = c.get("user");
|
|
11561
11777
|
const env2 = c.get("env");
|
|
11562
11778
|
if (user) return c.redirect("/app");
|
|
11779
|
+
if (env2.publicMode === "teaser") return c.html(/* @__PURE__ */ jsx14(TeaserLanding, {}));
|
|
11563
11780
|
return c.html(
|
|
11564
11781
|
/* @__PURE__ */ jsxs14("html", { lang: "en", children: [
|
|
11565
11782
|
/* @__PURE__ */ jsx14(Head, {}),
|
|
@@ -11699,6 +11916,8 @@ dashboardRoutes.get("/app", async (c) => {
|
|
|
11699
11916
|
const teamIds = rows.map((r) => r.team.id);
|
|
11700
11917
|
const lastSnaps = teamIds.length ? await db.select({ teamId: snapshots.teamId, last: max5(snapshots.createdAt) }).from(snapshots).where(inArray10(snapshots.teamId, teamIds)).groupBy(snapshots.teamId) : [];
|
|
11701
11918
|
const lastByTeam = new Map(lastSnaps.map((s) => [s.teamId, s.last]));
|
|
11919
|
+
const projectCounts = teamIds.length ? await db.select({ teamId: projects.teamId, n: count9() }).from(projects).where(inArray10(projects.teamId, teamIds)).groupBy(projects.teamId) : [];
|
|
11920
|
+
const projectsByTeam = new Map(projectCounts.map((r) => [r.teamId, r.n]));
|
|
11702
11921
|
const error = c.req.query("error");
|
|
11703
11922
|
const notice = c.req.query("ok");
|
|
11704
11923
|
const draftName = c.req.query("name");
|
|
@@ -11730,6 +11949,7 @@ dashboardRoutes.get("/app", async (c) => {
|
|
|
11730
11949
|
/* @__PURE__ */ jsx14("th", { children: "Team" }),
|
|
11731
11950
|
/* @__PURE__ */ jsx14("th", { children: "Slug" }),
|
|
11732
11951
|
/* @__PURE__ */ jsx14("th", { children: "Role" }),
|
|
11952
|
+
/* @__PURE__ */ jsx14("th", { children: "Projects" }),
|
|
11733
11953
|
/* @__PURE__ */ jsx14("th", { children: "Last snapshot" }),
|
|
11734
11954
|
/* @__PURE__ */ jsx14("th", {})
|
|
11735
11955
|
] }),
|
|
@@ -11740,6 +11960,7 @@ dashboardRoutes.get("/app", async (c) => {
|
|
|
11740
11960
|
] }) }),
|
|
11741
11961
|
/* @__PURE__ */ jsx14("td", { class: "mono", children: r.team.slug }),
|
|
11742
11962
|
/* @__PURE__ */ jsx14("td", { children: /* @__PURE__ */ jsx14("span", { class: `pill ${r.role === "owner" ? "pill-owner" : "pill-member"}`, children: r.role }) }),
|
|
11963
|
+
/* @__PURE__ */ jsx14("td", { children: /* @__PURE__ */ jsx14("a", { href: `/app/teams/${r.team.slug}/projects`, children: projectsByTeam.get(r.team.id) ?? 0 }) }),
|
|
11743
11964
|
/* @__PURE__ */ jsx14("td", { children: /* @__PURE__ */ jsx14(SnapshotCell, { last: lastByTeam.get(r.team.id) ?? null }) }),
|
|
11744
11965
|
/* @__PURE__ */ jsx14("td", { style: "text-align:right", children: /* @__PURE__ */ jsx14("a", { class: "chev", href: `/app/teams/${r.team.slug}`, children: "\u203A" }) })
|
|
11745
11966
|
] }))
|
|
@@ -11830,7 +12051,7 @@ dashboardRoutes.post("/app/teams", async (c) => {
|
|
|
11830
12051
|
const db = c.get("db");
|
|
11831
12052
|
const env2 = c.get("env");
|
|
11832
12053
|
const body = await c.req.parseBody();
|
|
11833
|
-
const str = (key,
|
|
12054
|
+
const str = (key, max8) => typeof body[key] === "string" ? body[key].trim().slice(0, max8) : "";
|
|
11834
12055
|
const name = str("name", 200);
|
|
11835
12056
|
const tag = str("tag", 60);
|
|
11836
12057
|
const webhookUrl = str("webhook_url", 500);
|
|
@@ -11887,6 +12108,9 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
11887
12108
|
);
|
|
11888
12109
|
}
|
|
11889
12110
|
const { team, role } = found;
|
|
12111
|
+
const TABS = ["overview", "people", "integrations", "settings"];
|
|
12112
|
+
const asked = c.req.query("tab");
|
|
12113
|
+
const tab = TABS.includes(asked ?? "") ? asked : "overview";
|
|
11890
12114
|
const members = await db.select({ member: users, role: memberships.role, joined: memberships.createdAt }).from(memberships).innerJoin(users, eq29(memberships.userId, users.id)).where(eq29(memberships.teamId, team.id)).orderBy(memberships.createdAt);
|
|
11891
12115
|
const owners = members.filter((m) => m.role === "owner").length;
|
|
11892
12116
|
const activeInvites = await db.select({ invite: invites, creator: users.username }).from(invites).leftJoin(users, eq29(invites.createdBy, users.id)).where(and22(eq29(invites.teamId, team.id), gt10(invites.expiresAt, /* @__PURE__ */ new Date()))).orderBy(desc16(invites.createdAt));
|
|
@@ -11909,6 +12133,14 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
11909
12133
|
if (r.pid) agentsByProject.set(r.pid, r.n);
|
|
11910
12134
|
}
|
|
11911
12135
|
}
|
|
12136
|
+
const liveRuns = (await db.select({ n: count9() }).from(agentRuns).where(
|
|
12137
|
+
and22(
|
|
12138
|
+
eq29(agentRuns.teamId, team.id),
|
|
12139
|
+
inArray10(agentRuns.status, [...ACTIVE_AGENT_RUN_STATUSES])
|
|
12140
|
+
)
|
|
12141
|
+
))[0]?.n ?? 0;
|
|
12142
|
+
const driftCount = (await db.select({ n: count9() }).from(policyReceipts).innerJoin(agentRuns, eq29(policyReceipts.runId, agentRuns.id)).where(and22(eq29(agentRuns.teamId, team.id), eq29(policyReceipts.drift, true))))[0]?.n ?? 0;
|
|
12143
|
+
const openSessionCount = (await db.select({ n: count9() }).from(debugSessions).where(and22(eq29(debugSessions.teamId, team.id), eq29(debugSessions.status, "open"))))[0]?.n ?? 0;
|
|
11912
12144
|
const activeAgentRows = await db.select({ n: countDistinct3(activity.tokenId) }).from(activity).where(and22(eq29(activity.teamId, team.id), gt10(activity.createdAt, weekAgo)));
|
|
11913
12145
|
const activeAgents7d = activeAgentRows[0]?.n ?? 0;
|
|
11914
12146
|
const settingsError = c.req.query("error");
|
|
@@ -11971,7 +12203,7 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
11971
12203
|
actions: /* @__PURE__ */ jsxs14(Fragment11, { children: [
|
|
11972
12204
|
/* @__PURE__ */ jsx14("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}/compare`, children: "Compare environments" }),
|
|
11973
12205
|
/* @__PURE__ */ jsx14("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}/governance`, children: "Governance" }),
|
|
11974
|
-
/* @__PURE__ */ jsx14("a", { class: "btn btn-sm btn-primary", href:
|
|
12206
|
+
/* @__PURE__ */ jsx14("a", { class: "btn btn-sm btn-primary", href: `/app/teams/${team.slug}?tab=people#invites`, children: "Invite member" })
|
|
11975
12207
|
] })
|
|
11976
12208
|
}
|
|
11977
12209
|
),
|
|
@@ -11983,13 +12215,21 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
11983
12215
|
children: [
|
|
11984
12216
|
settingsError ? /* @__PURE__ */ jsx14(Banner2, { kind: "error", text: settingsError }) : null,
|
|
11985
12217
|
notice ? /* @__PURE__ */ jsx14(Banner2, { kind: "success", text: notice }) : null,
|
|
11986
|
-
/* @__PURE__ */
|
|
12218
|
+
/* @__PURE__ */ jsx14("div", { class: "pagetabs", style: "margin:0 0 16px;padding:0;border-bottom:1px solid var(--line)", children: TABS.map((key) => /* @__PURE__ */ jsx14(
|
|
12219
|
+
"a",
|
|
12220
|
+
{
|
|
12221
|
+
class: `tab${tab === key ? " active" : ""}`,
|
|
12222
|
+
href: `/app/teams/${team.slug}${key === "overview" ? "" : `?tab=${key}`}`,
|
|
12223
|
+
children: key === "overview" ? "Overview" : key === "people" ? "People" : key === "integrations" ? "Integrations" : "Settings"
|
|
12224
|
+
}
|
|
12225
|
+
)) }),
|
|
12226
|
+
/* @__PURE__ */ jsxs14("div", { class: tab === "overview" ? "grid2" : "col", children: [
|
|
11987
12227
|
/* @__PURE__ */ jsxs14("div", { class: "col", children: [
|
|
11988
|
-
/* @__PURE__ */ jsxs14("div", { class: "card scroll-x", children: [
|
|
12228
|
+
tab === "overview" ? /* @__PURE__ */ jsxs14("div", { class: "card scroll-x", children: [
|
|
11989
12229
|
/* @__PURE__ */ jsxs14("div", { class: "card-head", children: [
|
|
11990
12230
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
11991
12231
|
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Projects" }),
|
|
11992
|
-
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "
|
|
12232
|
+
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "Click one \u2014 policy, environment, flow and sessions live on its page." })
|
|
11993
12233
|
] }),
|
|
11994
12234
|
/* @__PURE__ */ jsx14("span", { class: "mono muted", children: teamProjects.length })
|
|
11995
12235
|
] }),
|
|
@@ -12001,14 +12241,14 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
12001
12241
|
/* @__PURE__ */ jsx14("th", { children: "Last snapshot" })
|
|
12002
12242
|
] }),
|
|
12003
12243
|
teamProjects.map((p) => /* @__PURE__ */ jsxs14("tr", { children: [
|
|
12004
|
-
/* @__PURE__ */ jsx14("td", { class: "name", children: p.name }),
|
|
12244
|
+
/* @__PURE__ */ jsx14("td", { class: "name", children: /* @__PURE__ */ jsx14("a", { href: `/app/teams/${team.slug}/projects/${encodeURIComponent(p.name)}`, children: p.name }) }),
|
|
12005
12245
|
/* @__PURE__ */ jsx14("td", { children: openByProject.get(p.id) ?? 0 }),
|
|
12006
12246
|
/* @__PURE__ */ jsx14("td", { children: agentsByProject.get(p.id) ?? 0 }),
|
|
12007
12247
|
/* @__PURE__ */ jsx14("td", { class: "muted", children: timeAgo(snapByProject.get(p.id) ?? null) ?? "\u2014" })
|
|
12008
12248
|
] }))
|
|
12009
12249
|
] })
|
|
12010
|
-
] }),
|
|
12011
|
-
/* @__PURE__ */ jsxs14("div", { class: "card scroll-x", children: [
|
|
12250
|
+
] }) : null,
|
|
12251
|
+
tab === "people" ? /* @__PURE__ */ jsxs14("div", { class: "card scroll-x", children: [
|
|
12012
12252
|
/* @__PURE__ */ jsxs14("div", { class: "card-head", children: [
|
|
12013
12253
|
/* @__PURE__ */ jsx14("span", { class: "card-title", children: "Members" }),
|
|
12014
12254
|
/* @__PURE__ */ jsx14("span", { class: "mono muted", children: members.length })
|
|
@@ -12072,8 +12312,8 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
12072
12312
|
] }) })
|
|
12073
12313
|
] }))
|
|
12074
12314
|
] })
|
|
12075
|
-
] }),
|
|
12076
|
-
/* @__PURE__ */ jsxs14("div", { class: "card", id: "invites", children: [
|
|
12315
|
+
] }) : null,
|
|
12316
|
+
tab === "people" ? /* @__PURE__ */ jsxs14("div", { class: "card", id: "invites", children: [
|
|
12077
12317
|
/* @__PURE__ */ jsxs14("div", { class: "card-head", children: [
|
|
12078
12318
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12079
12319
|
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Invite links" }),
|
|
@@ -12121,17 +12361,51 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
12121
12361
|
] })
|
|
12122
12362
|
] });
|
|
12123
12363
|
})
|
|
12124
|
-
] })
|
|
12364
|
+
] }) : null
|
|
12125
12365
|
] }),
|
|
12126
12366
|
/* @__PURE__ */ jsxs14("div", { class: "col", children: [
|
|
12127
|
-
/* @__PURE__ */ jsxs14("div", { class: "darkcard", children: [
|
|
12367
|
+
tab === "overview" ? /* @__PURE__ */ jsxs14("div", { class: "darkcard", children: [
|
|
12128
12368
|
/* @__PURE__ */ jsx14("span", { class: "overline", children: "Next step" }),
|
|
12129
12369
|
/* @__PURE__ */ jsx14("h3", { children: "Connect your agent" }),
|
|
12130
12370
|
/* @__PURE__ */ jsx14("p", { children: "Create a personal token, then paste one line into your agent \u2014 it will join this team's bridge and can push its first snapshot." }),
|
|
12131
12371
|
/* @__PURE__ */ jsx14("div", { class: "cmd inner", children: /* @__PURE__ */ jsx14("code", { children: placeholderCmd }) }),
|
|
12132
12372
|
/* @__PURE__ */ jsx14("a", { class: "btn btn-white", href: "/app/tokens", style: "align-self:flex-start", children: "Create a token" })
|
|
12133
|
-
] }),
|
|
12134
|
-
/* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12373
|
+
] }) : null,
|
|
12374
|
+
tab === "overview" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12375
|
+
/* @__PURE__ */ jsx14("span", { class: "card-title", children: "Team health" }),
|
|
12376
|
+
/* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12377
|
+
/* @__PURE__ */ jsx14("span", { class: liveRuns > 0 ? "y" : "n", children: liveRuns > 0 ? "\u2713" : "\xB7" }),
|
|
12378
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12379
|
+
liveRuns,
|
|
12380
|
+
" ",
|
|
12381
|
+
liveRuns === 1 ? "run" : "runs",
|
|
12382
|
+
" live now \u2014",
|
|
12383
|
+
" ",
|
|
12384
|
+
/* @__PURE__ */ jsx14("a", { href: "/app/agents", children: "agent map" })
|
|
12385
|
+
] })
|
|
12386
|
+
] }),
|
|
12387
|
+
/* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12388
|
+
/* @__PURE__ */ jsx14("span", { class: driftCount > 0 ? "n" : "y", children: driftCount > 0 ? "!" : "\u2713" }),
|
|
12389
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12390
|
+
driftCount > 0 ? `${driftCount} run(s) applied rules the server did not serve` : "No policy drift reported",
|
|
12391
|
+
" ",
|
|
12392
|
+
"\u2014 ",
|
|
12393
|
+
/* @__PURE__ */ jsx14("a", { href: `/app/teams/${team.slug}/governance`, children: "governance" })
|
|
12394
|
+
] })
|
|
12395
|
+
] }),
|
|
12396
|
+
/* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12397
|
+
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12398
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12399
|
+
openSessionCount,
|
|
12400
|
+
" open ",
|
|
12401
|
+
openSessionCount === 1 ? "thread" : "threads",
|
|
12402
|
+
" \u2014",
|
|
12403
|
+
" ",
|
|
12404
|
+
/* @__PURE__ */ jsx14("a", { href: "/app/sessions", children: "sessions" })
|
|
12405
|
+
] })
|
|
12406
|
+
] })
|
|
12407
|
+
] }) : null,
|
|
12408
|
+
tab === "overview" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12135
12409
|
/* @__PURE__ */ jsx14("span", { class: "card-title", children: "What agents share here" }),
|
|
12136
12410
|
/* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12137
12411
|
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
@@ -12153,253 +12427,255 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
12153
12427
|
/* @__PURE__ */ jsx14("span", { class: "n", children: "\u2715" }),
|
|
12154
12428
|
/* @__PURE__ */ jsx14("span", { children: "Never: secret values, file contents, source code" })
|
|
12155
12429
|
] })
|
|
12156
|
-
] }),
|
|
12157
|
-
|
|
12158
|
-
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12159
|
-
/* @__PURE__ */
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
/* @__PURE__ */
|
|
12164
|
-
|
|
12430
|
+
] }) : null,
|
|
12431
|
+
tab === "integrations" ? /* @__PURE__ */ jsxs14(Fragment11, { children: [
|
|
12432
|
+
role === "owner" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12433
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12434
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Notifications" }),
|
|
12435
|
+
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "Optional Slack or Discord incoming-webhook URL. New and resolved sessions ping it \u2014 message bodies are never sent." })
|
|
12436
|
+
] }),
|
|
12437
|
+
/* @__PURE__ */ jsxs14("form", { class: "inline", method: "post", action: `/app/teams/${team.slug}/settings`, children: [
|
|
12438
|
+
/* @__PURE__ */ jsx14(
|
|
12439
|
+
"input",
|
|
12440
|
+
{
|
|
12441
|
+
class: "in",
|
|
12442
|
+
style: "flex:1;min-width:200px",
|
|
12443
|
+
type: "text",
|
|
12444
|
+
name: "webhook_url",
|
|
12445
|
+
"aria-label": "Team Slack or Discord webhook URL",
|
|
12446
|
+
value: team.webhookUrl ?? "",
|
|
12447
|
+
placeholder: "https://hooks.slack.com/services/\u2026"
|
|
12448
|
+
}
|
|
12449
|
+
),
|
|
12450
|
+
/* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", children: "Save" })
|
|
12451
|
+
] }),
|
|
12452
|
+
/* @__PURE__ */ jsxs14("div", { style: "border-top:1px solid var(--line-2);padding-top:12px;display:flex;flex-direction:column;gap:8px", children: [
|
|
12453
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12454
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", style: "font-size:14px", children: "Inbound hooks" }),
|
|
12455
|
+
/* @__PURE__ */ jsxs14("div", { class: "card-note", children: [
|
|
12456
|
+
"POST here from CI or a GitHub webhook (push events) to announce to the team. For GitHub, also paste the token as the webhook ",
|
|
12457
|
+
/* @__PURE__ */ jsx14("b", { children: "Secret" }),
|
|
12458
|
+
" \u2014 signed requests are verified, and a bad signature is rejected."
|
|
12459
|
+
] })
|
|
12460
|
+
] }),
|
|
12461
|
+
team.inboundToken ? /* @__PURE__ */ jsxs14(Fragment11, { children: [
|
|
12462
|
+
/* @__PURE__ */ jsx14("div", { class: "cmd inner", children: /* @__PURE__ */ jsx14("code", { children: `${env2.baseUrl}/api/hooks/announce/${team.inboundToken}` }) }),
|
|
12463
|
+
/* @__PURE__ */ jsx14("div", { class: "cmd inner", children: /* @__PURE__ */ jsx14("code", { children: `${env2.baseUrl}/api/hooks/github/${team.inboundToken}` }) })
|
|
12464
|
+
] }) : /* @__PURE__ */ jsx14("p", { class: "m0 small muted", children: "No inbound token yet." }),
|
|
12465
|
+
/* @__PURE__ */ jsx14("form", { method: "post", action: `/app/teams/${team.slug}/inbound-token`, class: "m0", children: /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", children: team.inboundToken ? "Regenerate token" : "Generate hook URLs" }) })
|
|
12466
|
+
] })
|
|
12467
|
+
] }) : null,
|
|
12468
|
+
role === "owner" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12469
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12470
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "GitHub issues" }),
|
|
12471
|
+
/* @__PURE__ */ jsxs14("div", { class: "card-note", children: [
|
|
12472
|
+
"Connect one repository and a run stops being a string. Agents list what is open (",
|
|
12473
|
+
/* @__PURE__ */ jsx14("code", { children: "list_issues" }),
|
|
12474
|
+
"), start a run on a number (",
|
|
12475
|
+
/* @__PURE__ */ jsxs14("code", { children: [
|
|
12476
|
+
"start_run ",
|
|
12477
|
+
"{",
|
|
12478
|
+
'"issue": 42',
|
|
12479
|
+
"}"
|
|
12480
|
+
] }),
|
|
12481
|
+
"), and when that run finishes or is handed off, the issue gets a comment saying so. Needs a token with",
|
|
12482
|
+
" ",
|
|
12483
|
+
/* @__PURE__ */ jsx14("b", { children: "issues: read and write" }),
|
|
12484
|
+
" \u2014 a fine-grained token scoped to this one repository is the right shape."
|
|
12485
|
+
] })
|
|
12486
|
+
] }),
|
|
12487
|
+
github ? /* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12488
|
+
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12489
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12490
|
+
"Connected to ",
|
|
12491
|
+
/* @__PURE__ */ jsx14("b", { children: github.repo }),
|
|
12492
|
+
github.commentOnFinish ? " \u2014 finished runs comment back on the issue." : " \u2014 commenting back is off, so this is read-only."
|
|
12493
|
+
] })
|
|
12494
|
+
] }) : null,
|
|
12495
|
+
/* @__PURE__ */ jsxs14(
|
|
12496
|
+
"form",
|
|
12165
12497
|
{
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12498
|
+
method: "post",
|
|
12499
|
+
action: `/app/teams/${team.slug}/integrations/github`,
|
|
12500
|
+
style: "display:flex;flex-direction:column;gap:10px",
|
|
12501
|
+
children: [
|
|
12502
|
+
/* @__PURE__ */ jsx14(
|
|
12503
|
+
"input",
|
|
12504
|
+
{
|
|
12505
|
+
class: "in",
|
|
12506
|
+
type: "text",
|
|
12507
|
+
name: "repo",
|
|
12508
|
+
"aria-label": "GitHub repository, as owner/name",
|
|
12509
|
+
value: github?.repo ?? "",
|
|
12510
|
+
placeholder: "owner/name"
|
|
12511
|
+
}
|
|
12512
|
+
),
|
|
12513
|
+
/* @__PURE__ */ jsx14(
|
|
12514
|
+
"input",
|
|
12515
|
+
{
|
|
12516
|
+
class: "in",
|
|
12517
|
+
type: "password",
|
|
12518
|
+
name: "token",
|
|
12519
|
+
autocomplete: "off",
|
|
12520
|
+
"aria-label": "GitHub access token with issues read and write",
|
|
12521
|
+
placeholder: github ? "Stored \u2014 type a new token only to replace it" : "github_pat_\u2026 or ghp_\u2026"
|
|
12522
|
+
}
|
|
12523
|
+
),
|
|
12524
|
+
/* @__PURE__ */ jsxs14("label", { class: "checkrow", children: [
|
|
12525
|
+
/* @__PURE__ */ jsx14(
|
|
12526
|
+
"input",
|
|
12527
|
+
{
|
|
12528
|
+
type: "checkbox",
|
|
12529
|
+
name: "comment_on_finish",
|
|
12530
|
+
value: "on",
|
|
12531
|
+
checked: github?.commentOnFinish ?? true
|
|
12532
|
+
}
|
|
12533
|
+
),
|
|
12534
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12535
|
+
/* @__PURE__ */ jsx14("span", { class: "checkrow-label", children: "Comment on the issue when a run ends" }),
|
|
12536
|
+
/* @__PURE__ */ jsx14("span", { class: "checkrow-note", children: "One comment per finished run or handoff, on issues an agent named. Off means STMA only reads." })
|
|
12537
|
+
] })
|
|
12538
|
+
] }),
|
|
12539
|
+
/* @__PURE__ */ jsxs14("div", { style: "display:flex;gap:8px;flex-wrap:wrap", children: [
|
|
12540
|
+
/* @__PURE__ */ jsx14("button", { class: "btn btn-sm btn-primary", type: "submit", name: "action", value: "save", children: github ? "Update connection" : "Connect repository" }),
|
|
12541
|
+
github ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "test", children: "Test access" }) : null,
|
|
12542
|
+
github ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "remove", children: "Disconnect" }) : null
|
|
12543
|
+
] })
|
|
12544
|
+
]
|
|
12173
12545
|
}
|
|
12174
|
-
)
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
/* @__PURE__ */ jsxs14("div", { style: "border-top:1px solid var(--line-2);padding-top:12px;display:flex;flex-direction:column;gap:8px", children: [
|
|
12546
|
+
)
|
|
12547
|
+
] }) : null,
|
|
12548
|
+
role === "owner" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12178
12549
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12179
|
-
/* @__PURE__ */ jsx14("div", { class: "card-title",
|
|
12550
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Azure DevOps" }),
|
|
12180
12551
|
/* @__PURE__ */ jsxs14("div", { class: "card-note", children: [
|
|
12181
|
-
"
|
|
12182
|
-
/* @__PURE__ */ jsx14("
|
|
12183
|
-
"
|
|
12552
|
+
"Where the ",
|
|
12553
|
+
/* @__PURE__ */ jsx14("a", { href: `/app/teams/${team.slug}/delivery`, children: "delivery flow" }),
|
|
12554
|
+
" gets applied: STMA commits the rendered pipeline file into this repository and registers the pipeline. Needs a PAT with ",
|
|
12555
|
+
/* @__PURE__ */ jsx14("b", { children: "Code read & write" }),
|
|
12556
|
+
" and",
|
|
12557
|
+
" ",
|
|
12558
|
+
/* @__PURE__ */ jsx14("b", { children: "Build read & execute" }),
|
|
12559
|
+
", scoped to this one project."
|
|
12184
12560
|
] })
|
|
12185
12561
|
] }),
|
|
12186
|
-
|
|
12187
|
-
/* @__PURE__ */ jsx14("
|
|
12188
|
-
/* @__PURE__ */
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
github ? /* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12213
|
-
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12214
|
-
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12215
|
-
"Connected to ",
|
|
12216
|
-
/* @__PURE__ */ jsx14("b", { children: github.repo }),
|
|
12217
|
-
github.commentOnFinish ? " \u2014 finished runs comment back on the issue." : " \u2014 commenting back is off, so this is read-only."
|
|
12218
|
-
] })
|
|
12219
|
-
] }) : null,
|
|
12220
|
-
/* @__PURE__ */ jsxs14(
|
|
12221
|
-
"form",
|
|
12222
|
-
{
|
|
12223
|
-
method: "post",
|
|
12224
|
-
action: `/app/teams/${team.slug}/integrations/github`,
|
|
12225
|
-
style: "display:flex;flex-direction:column;gap:10px",
|
|
12226
|
-
children: [
|
|
12227
|
-
/* @__PURE__ */ jsx14(
|
|
12228
|
-
"input",
|
|
12229
|
-
{
|
|
12230
|
-
class: "in",
|
|
12231
|
-
type: "text",
|
|
12232
|
-
name: "repo",
|
|
12233
|
-
"aria-label": "GitHub repository, as owner/name",
|
|
12234
|
-
value: github?.repo ?? "",
|
|
12235
|
-
placeholder: "owner/name"
|
|
12236
|
-
}
|
|
12237
|
-
),
|
|
12238
|
-
/* @__PURE__ */ jsx14(
|
|
12239
|
-
"input",
|
|
12240
|
-
{
|
|
12241
|
-
class: "in",
|
|
12242
|
-
type: "password",
|
|
12243
|
-
name: "token",
|
|
12244
|
-
autocomplete: "off",
|
|
12245
|
-
"aria-label": "GitHub access token with issues read and write",
|
|
12246
|
-
placeholder: github ? "Stored \u2014 type a new token only to replace it" : "github_pat_\u2026 or ghp_\u2026"
|
|
12247
|
-
}
|
|
12248
|
-
),
|
|
12249
|
-
/* @__PURE__ */ jsxs14("label", { class: "checkrow", children: [
|
|
12562
|
+
ado ? /* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12563
|
+
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12564
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12565
|
+
"Connected to ",
|
|
12566
|
+
/* @__PURE__ */ jsx14("b", { class: "mono", children: `${ado.organization}/${ado.project}/${ado.repo}` })
|
|
12567
|
+
] })
|
|
12568
|
+
] }) : null,
|
|
12569
|
+
/* @__PURE__ */ jsx14(AdoTokenHelp, {}),
|
|
12570
|
+
/* @__PURE__ */ jsxs14(
|
|
12571
|
+
"form",
|
|
12572
|
+
{
|
|
12573
|
+
method: "post",
|
|
12574
|
+
action: `/app/teams/${team.slug}/integrations/azure-devops`,
|
|
12575
|
+
style: "display:flex;flex-direction:column;gap:10px",
|
|
12576
|
+
children: [
|
|
12577
|
+
/* @__PURE__ */ jsx14(
|
|
12578
|
+
"input",
|
|
12579
|
+
{
|
|
12580
|
+
class: "in",
|
|
12581
|
+
type: "text",
|
|
12582
|
+
name: "locator",
|
|
12583
|
+
"aria-label": "Azure DevOps repository, as organization/project/repo",
|
|
12584
|
+
value: ado ? `${ado.organization}/${ado.project}/${ado.repo}` : "",
|
|
12585
|
+
placeholder: "organization/project/repo \u2014 or paste the repo URL"
|
|
12586
|
+
}
|
|
12587
|
+
),
|
|
12250
12588
|
/* @__PURE__ */ jsx14(
|
|
12251
12589
|
"input",
|
|
12252
12590
|
{
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12591
|
+
class: "in",
|
|
12592
|
+
type: "password",
|
|
12593
|
+
name: "token",
|
|
12594
|
+
autocomplete: "off",
|
|
12595
|
+
"aria-label": "Azure DevOps personal access token",
|
|
12596
|
+
placeholder: ado ? "Stored \u2014 type a new token only to replace it" : "Personal access token"
|
|
12257
12597
|
}
|
|
12258
12598
|
),
|
|
12259
|
-
/* @__PURE__ */ jsxs14("
|
|
12260
|
-
/* @__PURE__ */ jsx14("
|
|
12261
|
-
/* @__PURE__ */ jsx14("
|
|
12599
|
+
/* @__PURE__ */ jsxs14("div", { style: "display:flex;gap:8px;flex-wrap:wrap", children: [
|
|
12600
|
+
/* @__PURE__ */ jsx14("button", { class: "btn btn-sm btn-primary", type: "submit", name: "action", value: "save", children: ado ? "Update connection" : "Connect Azure DevOps" }),
|
|
12601
|
+
ado ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "test", children: "Test access" }) : null,
|
|
12602
|
+
ado ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "remove", children: "Disconnect" }) : null
|
|
12262
12603
|
] })
|
|
12263
|
-
]
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
github ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "test", children: "Test access" }) : null,
|
|
12267
|
-
github ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "remove", children: "Disconnect" }) : null
|
|
12268
|
-
] })
|
|
12269
|
-
]
|
|
12270
|
-
}
|
|
12271
|
-
)
|
|
12272
|
-
] }) : null,
|
|
12273
|
-
role === "owner" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12274
|
-
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12275
|
-
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Azure DevOps" }),
|
|
12276
|
-
/* @__PURE__ */ jsxs14("div", { class: "card-note", children: [
|
|
12277
|
-
"Where the ",
|
|
12278
|
-
/* @__PURE__ */ jsx14("a", { href: `/app/teams/${team.slug}/delivery`, children: "delivery flow" }),
|
|
12279
|
-
" gets applied: STMA commits the rendered pipeline file into this repository and registers the pipeline. Needs a PAT with ",
|
|
12280
|
-
/* @__PURE__ */ jsx14("b", { children: "Code read & write" }),
|
|
12281
|
-
" and",
|
|
12282
|
-
" ",
|
|
12283
|
-
/* @__PURE__ */ jsx14("b", { children: "Build read & execute" }),
|
|
12284
|
-
", scoped to this one project."
|
|
12285
|
-
] })
|
|
12286
|
-
] }),
|
|
12287
|
-
ado ? /* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12288
|
-
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12289
|
-
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12290
|
-
"Connected to ",
|
|
12291
|
-
/* @__PURE__ */ jsx14("b", { class: "mono", children: `${ado.organization}/${ado.project}/${ado.repo}` })
|
|
12292
|
-
] })
|
|
12604
|
+
]
|
|
12605
|
+
}
|
|
12606
|
+
)
|
|
12293
12607
|
] }) : null,
|
|
12294
|
-
/* @__PURE__ */
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
class: "in",
|
|
12364
|
-
type: "text",
|
|
12365
|
-
name: "site",
|
|
12366
|
-
"aria-label": "Jira site, like acme.atlassian.net",
|
|
12367
|
-
value: jira?.site ?? "",
|
|
12368
|
-
placeholder: "acme.atlassian.net"
|
|
12369
|
-
}
|
|
12370
|
-
),
|
|
12371
|
-
/* @__PURE__ */ jsx14(
|
|
12372
|
-
"input",
|
|
12373
|
-
{
|
|
12374
|
-
class: "in",
|
|
12375
|
-
type: "email",
|
|
12376
|
-
name: "email",
|
|
12377
|
-
"aria-label": "Atlassian account email",
|
|
12378
|
-
value: jira?.email ?? "",
|
|
12379
|
-
placeholder: "you@company.com"
|
|
12380
|
-
}
|
|
12381
|
-
),
|
|
12382
|
-
/* @__PURE__ */ jsx14(
|
|
12383
|
-
"input",
|
|
12384
|
-
{
|
|
12385
|
-
class: "in",
|
|
12386
|
-
type: "password",
|
|
12387
|
-
name: "token",
|
|
12388
|
-
autocomplete: "off",
|
|
12389
|
-
"aria-label": "Jira API token",
|
|
12390
|
-
placeholder: jira ? "Stored \u2014 type a new token only to replace it" : "API token"
|
|
12391
|
-
}
|
|
12392
|
-
),
|
|
12393
|
-
/* @__PURE__ */ jsxs14("div", { style: "display:flex;gap:8px;flex-wrap:wrap", children: [
|
|
12394
|
-
/* @__PURE__ */ jsx14("button", { class: "btn btn-sm btn-primary", type: "submit", name: "action", value: "save", children: jira ? "Update connection" : "Connect Jira" }),
|
|
12395
|
-
jira ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "test", children: "Test access" }) : null,
|
|
12396
|
-
jira ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "remove", children: "Disconnect" }) : null
|
|
12397
|
-
] })
|
|
12398
|
-
]
|
|
12399
|
-
}
|
|
12400
|
-
)
|
|
12608
|
+
role === "owner" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12609
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12610
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Jira" }),
|
|
12611
|
+
/* @__PURE__ */ jsxs14("div", { class: "card-note", children: [
|
|
12612
|
+
"For delivery flows whose tickets live in Jira: STMA verifies the connection and reads issue titles \u2014 it never writes to the tracker. Needs the site, the account email, and an API token from",
|
|
12613
|
+
" ",
|
|
12614
|
+
/* @__PURE__ */ jsx14("span", { class: "mono", children: "id.atlassian.com/manage-profile/security/api-tokens" }),
|
|
12615
|
+
"."
|
|
12616
|
+
] })
|
|
12617
|
+
] }),
|
|
12618
|
+
jira ? /* @__PURE__ */ jsxs14("div", { class: "factrow", children: [
|
|
12619
|
+
/* @__PURE__ */ jsx14("span", { class: "y", children: "\u2713" }),
|
|
12620
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
12621
|
+
"Connected to ",
|
|
12622
|
+
/* @__PURE__ */ jsx14("b", { class: "mono", children: jira.site }),
|
|
12623
|
+
" as ",
|
|
12624
|
+
jira.email
|
|
12625
|
+
] })
|
|
12626
|
+
] }) : null,
|
|
12627
|
+
/* @__PURE__ */ jsx14(JiraTokenHelp, {}),
|
|
12628
|
+
/* @__PURE__ */ jsxs14(
|
|
12629
|
+
"form",
|
|
12630
|
+
{
|
|
12631
|
+
method: "post",
|
|
12632
|
+
action: `/app/teams/${team.slug}/integrations/jira`,
|
|
12633
|
+
style: "display:flex;flex-direction:column;gap:10px",
|
|
12634
|
+
children: [
|
|
12635
|
+
/* @__PURE__ */ jsx14(
|
|
12636
|
+
"input",
|
|
12637
|
+
{
|
|
12638
|
+
class: "in",
|
|
12639
|
+
type: "text",
|
|
12640
|
+
name: "site",
|
|
12641
|
+
"aria-label": "Jira site, like acme.atlassian.net",
|
|
12642
|
+
value: jira?.site ?? "",
|
|
12643
|
+
placeholder: "acme.atlassian.net"
|
|
12644
|
+
}
|
|
12645
|
+
),
|
|
12646
|
+
/* @__PURE__ */ jsx14(
|
|
12647
|
+
"input",
|
|
12648
|
+
{
|
|
12649
|
+
class: "in",
|
|
12650
|
+
type: "email",
|
|
12651
|
+
name: "email",
|
|
12652
|
+
"aria-label": "Atlassian account email",
|
|
12653
|
+
value: jira?.email ?? "",
|
|
12654
|
+
placeholder: "you@company.com"
|
|
12655
|
+
}
|
|
12656
|
+
),
|
|
12657
|
+
/* @__PURE__ */ jsx14(
|
|
12658
|
+
"input",
|
|
12659
|
+
{
|
|
12660
|
+
class: "in",
|
|
12661
|
+
type: "password",
|
|
12662
|
+
name: "token",
|
|
12663
|
+
autocomplete: "off",
|
|
12664
|
+
"aria-label": "Jira API token",
|
|
12665
|
+
placeholder: jira ? "Stored \u2014 type a new token only to replace it" : "API token"
|
|
12666
|
+
}
|
|
12667
|
+
),
|
|
12668
|
+
/* @__PURE__ */ jsxs14("div", { style: "display:flex;gap:8px;flex-wrap:wrap", children: [
|
|
12669
|
+
/* @__PURE__ */ jsx14("button", { class: "btn btn-sm btn-primary", type: "submit", name: "action", value: "save", children: jira ? "Update connection" : "Connect Jira" }),
|
|
12670
|
+
jira ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "test", children: "Test access" }) : null,
|
|
12671
|
+
jira ? /* @__PURE__ */ jsx14("button", { class: "btn btn-sm", type: "submit", name: "action", value: "remove", children: "Disconnect" }) : null
|
|
12672
|
+
] })
|
|
12673
|
+
]
|
|
12674
|
+
}
|
|
12675
|
+
)
|
|
12676
|
+
] }) : null
|
|
12401
12677
|
] }) : null,
|
|
12402
|
-
/* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12678
|
+
tab === "settings" ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
12403
12679
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
12404
12680
|
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Danger zone" }),
|
|
12405
12681
|
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "These actions take effect immediately." })
|
|
@@ -12447,7 +12723,7 @@ dashboardRoutes.get("/app/teams/:slug", async (c) => {
|
|
|
12447
12723
|
]
|
|
12448
12724
|
}
|
|
12449
12725
|
) : null
|
|
12450
|
-
] })
|
|
12726
|
+
] }) : null
|
|
12451
12727
|
] })
|
|
12452
12728
|
] })
|
|
12453
12729
|
]
|
|
@@ -12920,7 +13196,7 @@ dashboardRoutes.post("/join/:code", async (c) => {
|
|
|
12920
13196
|
return c.redirect(`/app/teams/${row.team.slug}`);
|
|
12921
13197
|
});
|
|
12922
13198
|
var TokensPage = (props) => {
|
|
12923
|
-
const { user, list, newToken, baseUrl: baseUrl2, confirmByEmail, error, notice } = props;
|
|
13199
|
+
const { user, list, devices, newToken, baseUrl: baseUrl2, confirmByEmail, error, notice } = props;
|
|
12924
13200
|
const tokenValue = newToken ?? "stma_YOUR_TOKEN";
|
|
12925
13201
|
const claudeCmd = connectCmd(baseUrl2, tokenValue);
|
|
12926
13202
|
const tryCmd = "Ask your agent to call the whoami tool \u2014 it should reply with your username and teams.";
|
|
@@ -12939,16 +13215,26 @@ var TokensPage = (props) => {
|
|
|
12939
13215
|
return /* @__PURE__ */ jsxs14(AppLayout, { user, active: "tokens", title: "Tokens", children: [
|
|
12940
13216
|
error ? /* @__PURE__ */ jsx14(Banner2, { kind: "error", text: error }) : null,
|
|
12941
13217
|
notice ? /* @__PURE__ */ jsx14(Banner2, { kind: "success", text: notice }) : null,
|
|
12942
|
-
/* @__PURE__ */
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
13218
|
+
/* @__PURE__ */ jsx14(
|
|
13219
|
+
PageHead,
|
|
13220
|
+
{
|
|
13221
|
+
title: "Personal access tokens",
|
|
13222
|
+
sub: "One token per machine keeps snapshots attributable and easy to revoke. Account settings moved to their own page.",
|
|
13223
|
+
actions: /* @__PURE__ */ jsxs14("form", { class: "inline m0", method: "post", action: "/app/tokens", children: [
|
|
13224
|
+
/* @__PURE__ */ jsx14(
|
|
13225
|
+
"input",
|
|
13226
|
+
{
|
|
13227
|
+
class: "in",
|
|
13228
|
+
style: "width:200px",
|
|
13229
|
+
type: "text",
|
|
13230
|
+
name: "name",
|
|
13231
|
+
placeholder: "e.g. work-laptop"
|
|
13232
|
+
}
|
|
13233
|
+
),
|
|
13234
|
+
/* @__PURE__ */ jsx14("button", { class: "btn btn-primary", type: "submit", children: "New token" })
|
|
13235
|
+
] })
|
|
13236
|
+
}
|
|
13237
|
+
),
|
|
12952
13238
|
newToken ? /* @__PURE__ */ jsxs14("div", { class: "reveal", children: [
|
|
12953
13239
|
/* @__PURE__ */ jsxs14("div", { class: "reveal-head", children: [
|
|
12954
13240
|
/* @__PURE__ */ jsx14("span", { class: "ic", children: "\u2713" }),
|
|
@@ -12969,6 +13255,7 @@ var TokensPage = (props) => {
|
|
|
12969
13255
|
/* @__PURE__ */ jsxs14("tr", { children: [
|
|
12970
13256
|
/* @__PURE__ */ jsx14("th", { children: "Name" }),
|
|
12971
13257
|
/* @__PURE__ */ jsx14("th", { children: "Token" }),
|
|
13258
|
+
/* @__PURE__ */ jsx14("th", { class: "hide-sm", children: "Machine it reports as" }),
|
|
12972
13259
|
/* @__PURE__ */ jsx14("th", { children: "Created" }),
|
|
12973
13260
|
/* @__PURE__ */ jsx14("th", { children: "Last used" }),
|
|
12974
13261
|
/* @__PURE__ */ jsx14("th", { children: "Status" }),
|
|
@@ -12980,6 +13267,7 @@ var TokensPage = (props) => {
|
|
|
12980
13267
|
t.prefix,
|
|
12981
13268
|
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
|
|
12982
13269
|
] }),
|
|
13270
|
+
/* @__PURE__ */ jsx14("td", { class: "mono muted hide-sm", children: devices.get(t.id) ?? "not yet" }),
|
|
12983
13271
|
/* @__PURE__ */ jsx14("td", { class: "muted", children: fmtDate(t.createdAt) }),
|
|
12984
13272
|
/* @__PURE__ */ jsx14("td", { class: "muted", children: timeAgo(t.lastUsedAt) ?? "\u2014" }),
|
|
12985
13273
|
/* @__PURE__ */ jsx14("td", { children: t.revokedAt ? /* @__PURE__ */ jsxs14("span", { class: "pill pill-muted", children: [
|
|
@@ -13056,11 +13344,29 @@ var TokensPage = (props) => {
|
|
|
13056
13344
|
] }),
|
|
13057
13345
|
/* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap", children: [
|
|
13058
13346
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
13059
|
-
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "
|
|
13060
|
-
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "
|
|
13347
|
+
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Everything else about you" }),
|
|
13348
|
+
/* @__PURE__ */ jsx14("div", { class: "card-note", children: "This page is machines. Your password and the end of the road live on Account; what STMA emails you lives on Notifications." })
|
|
13061
13349
|
] }),
|
|
13062
|
-
/* @__PURE__ */
|
|
13063
|
-
|
|
13350
|
+
/* @__PURE__ */ jsxs14("div", { class: "acts", children: [
|
|
13351
|
+
/* @__PURE__ */ jsx14("a", { class: "btn", href: "/app/account", children: "Account" }),
|
|
13352
|
+
/* @__PURE__ */ jsx14("a", { class: "btn", href: "/app/notifications", children: "Notifications" })
|
|
13353
|
+
] })
|
|
13354
|
+
] })
|
|
13355
|
+
] });
|
|
13356
|
+
};
|
|
13357
|
+
var AccountPage = (props) => {
|
|
13358
|
+
const { user, confirmByEmail, error, notice } = props;
|
|
13359
|
+
return /* @__PURE__ */ jsxs14(AppLayout, { user, active: "account", title: "Account", children: [
|
|
13360
|
+
error ? /* @__PURE__ */ jsx14(Banner2, { kind: "error", text: error }) : null,
|
|
13361
|
+
notice ? /* @__PURE__ */ jsx14(Banner2, { kind: "success", text: notice }) : null,
|
|
13362
|
+
/* @__PURE__ */ jsx14(
|
|
13363
|
+
PageHead,
|
|
13364
|
+
{
|
|
13365
|
+
crumb: "/ account",
|
|
13366
|
+
title: "Account",
|
|
13367
|
+
sub: "Your sign-in and the end of the road. Tokens live on their own page, one per machine."
|
|
13368
|
+
}
|
|
13369
|
+
),
|
|
13064
13370
|
user.passwordHash ? /* @__PURE__ */ jsxs14("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:14px", children: [
|
|
13065
13371
|
/* @__PURE__ */ jsxs14("div", { children: [
|
|
13066
13372
|
/* @__PURE__ */ jsx14("div", { class: "card-title", children: "Account" }),
|
|
@@ -13162,17 +13468,44 @@ var TokensPage = (props) => {
|
|
|
13162
13468
|
] })
|
|
13163
13469
|
] });
|
|
13164
13470
|
};
|
|
13471
|
+
dashboardRoutes.get("/app/account", async (c) => {
|
|
13472
|
+
const user = c.get("user");
|
|
13473
|
+
if (!user) return loginRedirect(c);
|
|
13474
|
+
return c.html(
|
|
13475
|
+
/* @__PURE__ */ jsx14(
|
|
13476
|
+
AccountPage,
|
|
13477
|
+
{
|
|
13478
|
+
user,
|
|
13479
|
+
confirmByEmail: c.get("env").twoFactor,
|
|
13480
|
+
error: c.req.query("error"),
|
|
13481
|
+
notice: c.req.query("ok")
|
|
13482
|
+
}
|
|
13483
|
+
)
|
|
13484
|
+
);
|
|
13485
|
+
});
|
|
13165
13486
|
dashboardRoutes.get("/app/tokens", async (c) => {
|
|
13166
13487
|
const user = c.get("user");
|
|
13167
13488
|
if (!user) return loginRedirect(c);
|
|
13168
13489
|
const db = c.get("db");
|
|
13169
13490
|
const list = await db.select().from(tokens).where(eq29(tokens.userId, user.id)).orderBy(desc16(tokens.createdAt));
|
|
13491
|
+
const devices = /* @__PURE__ */ new Map();
|
|
13492
|
+
if (list.length > 0) {
|
|
13493
|
+
for (const row of await db.select({ tokenId: snapshots.tokenId, label: max5(snapshots.deviceLabel) }).from(snapshots).where(
|
|
13494
|
+
inArray10(
|
|
13495
|
+
snapshots.tokenId,
|
|
13496
|
+
list.map((t) => t.id)
|
|
13497
|
+
)
|
|
13498
|
+
).groupBy(snapshots.tokenId)) {
|
|
13499
|
+
if (row.tokenId && row.label) devices.set(row.tokenId, row.label);
|
|
13500
|
+
}
|
|
13501
|
+
}
|
|
13170
13502
|
return c.html(
|
|
13171
13503
|
/* @__PURE__ */ jsx14(
|
|
13172
13504
|
TokensPage,
|
|
13173
13505
|
{
|
|
13174
13506
|
user,
|
|
13175
13507
|
list,
|
|
13508
|
+
devices,
|
|
13176
13509
|
baseUrl: c.get("env").baseUrl,
|
|
13177
13510
|
confirmByEmail: c.get("env").twoFactor,
|
|
13178
13511
|
error: c.req.query("error"),
|
|
@@ -13197,6 +13530,7 @@ dashboardRoutes.post("/app/tokens", async (c) => {
|
|
|
13197
13530
|
{
|
|
13198
13531
|
user,
|
|
13199
13532
|
list,
|
|
13533
|
+
devices: /* @__PURE__ */ new Map(),
|
|
13200
13534
|
newToken: pat.token,
|
|
13201
13535
|
baseUrl: c.get("env").baseUrl,
|
|
13202
13536
|
confirmByEmail: c.get("env").twoFactor
|
|
@@ -13698,9 +14032,9 @@ var toCount = (raw) => {
|
|
|
13698
14032
|
function linesToRuntimes(raw) {
|
|
13699
14033
|
const out = {};
|
|
13700
14034
|
for (const line of linesToList(raw)) {
|
|
13701
|
-
const
|
|
13702
|
-
const name = (
|
|
13703
|
-
const version =
|
|
14035
|
+
const eq40 = line.indexOf("=");
|
|
14036
|
+
const name = (eq40 === -1 ? line : line.slice(0, eq40)).trim();
|
|
14037
|
+
const version = eq40 === -1 ? "" : line.slice(eq40 + 1).trim();
|
|
13704
14038
|
if (name) out[name] = version;
|
|
13705
14039
|
}
|
|
13706
14040
|
return out;
|
|
@@ -13739,9 +14073,9 @@ function linesToEnvironments(raw) {
|
|
|
13739
14073
|
const environments = [];
|
|
13740
14074
|
for (const line of linesToList(raw)) {
|
|
13741
14075
|
const [left, ...flags] = line.split(",").map((part) => part.trim());
|
|
13742
|
-
const
|
|
13743
|
-
const name = (
|
|
13744
|
-
const trigger =
|
|
14076
|
+
const eq40 = left.indexOf("=");
|
|
14077
|
+
const name = (eq40 === -1 ? left : left.slice(0, eq40)).trim();
|
|
14078
|
+
const trigger = eq40 === -1 ? "manual" : left.slice(eq40 + 1).trim();
|
|
13745
14079
|
if (!DEPLOY_TRIGGERS.includes(trigger)) {
|
|
13746
14080
|
return {
|
|
13747
14081
|
error: `environments: "${line}" \u2014 the part after = must be one of ${DEPLOY_TRIGGERS.join(", ")}.`
|
|
@@ -13833,7 +14167,7 @@ function nodesFor(flow) {
|
|
|
13833
14167
|
}
|
|
13834
14168
|
return nodes;
|
|
13835
14169
|
}
|
|
13836
|
-
var clip = (value,
|
|
14170
|
+
var clip = (value, max8) => value.length > max8 ? `${value.slice(0, max8 - 1)}\u2026` : value;
|
|
13837
14171
|
var FlowDiagram = ({ flow }) => {
|
|
13838
14172
|
const nodes = nodesFor(flow);
|
|
13839
14173
|
const width = nodes.length * NODE.w + (nodes.length - 1) * NODE.gap + 8;
|
|
@@ -14865,12 +15199,11 @@ governanceRoutes.get("/app/teams/:slug/governance", async (c) => {
|
|
|
14865
15199
|
}
|
|
14866
15200
|
),
|
|
14867
15201
|
/* @__PURE__ */ jsx17(
|
|
14868
|
-
"
|
|
15202
|
+
"a",
|
|
14869
15203
|
{
|
|
14870
15204
|
class: "btn btn-sm btn-primary",
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
children: "Publish policy"
|
|
15205
|
+
href: `/app/teams/${team.slug}/policy${scopeProject ? `?project=${encodeURIComponent(scopeProject.name)}` : ""}`,
|
|
15206
|
+
children: "Edit policy"
|
|
14874
15207
|
}
|
|
14875
15208
|
)
|
|
14876
15209
|
] }) : null,
|
|
@@ -14954,7 +15287,14 @@ governanceRoutes.get("/app/teams/:slug/governance", async (c) => {
|
|
|
14954
15287
|
visibleScopes.length === 0 ? /* @__PURE__ */ jsxs17("div", { class: "empty", children: [
|
|
14955
15288
|
/* @__PURE__ */ jsx17("h2", { children: "No policy published yet" }),
|
|
14956
15289
|
/* @__PURE__ */ jsx17("p", { children: "Until a team owner publishes one, every agent starts a run with an empty rulebook: nothing denied, nothing requiring approval, no required checks." }),
|
|
14957
|
-
isOwner ? /* @__PURE__ */ jsx17(
|
|
15290
|
+
isOwner ? /* @__PURE__ */ jsx17(
|
|
15291
|
+
"a",
|
|
15292
|
+
{
|
|
15293
|
+
class: "btn btn-primary",
|
|
15294
|
+
href: `/app/teams/${team.slug}/policy${scopeProject ? `?project=${encodeURIComponent(scopeProject.name)}` : ""}`,
|
|
15295
|
+
children: "Edit policy"
|
|
15296
|
+
}
|
|
15297
|
+
) : /* @__PURE__ */ jsx17("p", { class: "muted small", children: "Ask a team owner to publish one \u2014 only owners can write policy." })
|
|
14958
15298
|
] }) : /* @__PURE__ */ jsxs17(Fragment14, { children: [
|
|
14959
15299
|
/* @__PURE__ */ jsx17("div", { class: "scroll-x", children: /* @__PURE__ */ jsxs17("table", { class: "tbl", children: [
|
|
14960
15300
|
/* @__PURE__ */ jsxs17("tr", { children: [
|
|
@@ -15148,112 +15488,6 @@ governanceRoutes.get("/app/teams/:slug/governance", async (c) => {
|
|
|
15148
15488
|
] }))
|
|
15149
15489
|
] }) })
|
|
15150
15490
|
] }),
|
|
15151
|
-
isOwner ? /* @__PURE__ */ jsxs17("dialog", { id: "publish-policy", class: "formdlg wide", children: [
|
|
15152
|
-
/* @__PURE__ */ jsx17("h3", { children: "Publish policy" }),
|
|
15153
|
-
/* @__PURE__ */ jsx17("p", { class: "dlgsub", children: "One rule per line. This is the document every agent receives on its next run \u2014 it publishes as a new version, and the old one stays in the record." }),
|
|
15154
|
-
/* @__PURE__ */ jsxs17("form", { method: "post", action: `/app/teams/${team.slug}/policy`, children: [
|
|
15155
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15156
|
-
/* @__PURE__ */ jsx17("label", { children: "Scope" }),
|
|
15157
|
-
/* @__PURE__ */ jsxs17("select", { class: "in", name: "scope", children: [
|
|
15158
|
-
/* @__PURE__ */ jsx17("option", { value: "team", selected: !scopeProject, children: "Team-wide \u2014 applies to every run" }),
|
|
15159
|
-
teamProjects.map((p) => /* @__PURE__ */ jsxs17("option", { value: p.name, selected: p.name === scopeProject?.name, children: [
|
|
15160
|
-
"Project: ",
|
|
15161
|
-
p.name,
|
|
15162
|
-
" \u2014 merged on top of team policy"
|
|
15163
|
-
] }))
|
|
15164
|
-
] }),
|
|
15165
|
-
/* @__PURE__ */ jsx17("span", { class: "help", children: scopeProject ? `Opened on ${scopeProject.name}'s own additions (the page is scoped to it) \u2014 team rules stay merged underneath.` : "Project policy adds to team policy; it does not replace it. Start team-wide." })
|
|
15166
|
-
] }),
|
|
15167
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15168
|
-
/* @__PURE__ */ jsx17("label", { children: "Guidance" }),
|
|
15169
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "guidance", rows: 3, "aria-label": "Guidance", placeholder: "Ship behind a feature flag\nNever touch another agent's branch \u2014 open a debug session instead", children: draft ? listToLines(draft.guidance) : "" }),
|
|
15170
|
-
/* @__PURE__ */ jsx17("span", { class: "help", children: "Plain sentences the agent reads before it plans." })
|
|
15171
|
-
] }),
|
|
15172
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15173
|
-
/* @__PURE__ */ jsx17("label", { children: "Denied" }),
|
|
15174
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "deny", rows: 3, "aria-label": "Denied", placeholder: "read secret values\npush to main\nrun terraform apply", children: draft ? listToLines(draft.permissions.deny) : "" })
|
|
15175
|
-
] }),
|
|
15176
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15177
|
-
/* @__PURE__ */ jsx17("label", { children: "Requires approval" }),
|
|
15178
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "requireApproval", rows: 2, "aria-label": "Requires approval", placeholder: "schema migrations\nchanges under infra/", children: draft ? listToLines(draft.permissions.requireApproval) : "" }),
|
|
15179
|
-
/* @__PURE__ */ jsx17("span", { class: "help", children: "The agent must ask its human before doing these." })
|
|
15180
|
-
] }),
|
|
15181
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15182
|
-
/* @__PURE__ */ jsx17("label", { children: "Required checks" }),
|
|
15183
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "requiredChecks", rows: 2, "aria-label": "Required checks", placeholder: "npm test\nnpm run typecheck", children: draft ? listToLines(draft.requiredChecks) : "" })
|
|
15184
|
-
] }),
|
|
15185
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15186
|
-
/* @__PURE__ */ jsx17("label", { children: "Protected paths" }),
|
|
15187
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "protectedPaths", rows: 2, "aria-label": "Protected paths", placeholder: "db/migrations/**\ninfra/terraform/**\n.github/workflows/**", children: draft ? listToLines(draft.protectedPaths) : "" })
|
|
15188
|
-
] }),
|
|
15189
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15190
|
-
/* @__PURE__ */ jsx17("label", { children: "Required environment variables" }),
|
|
15191
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "requiredEnvVarNames", rows: 2, "aria-label": "Required environment variable names", placeholder: "DATABASE_URL\nPAYMENTS_WEBHOOK_SECRET", children: draft ? listToLines(draft.environment.requiredEnvVarNames) : "" }),
|
|
15192
|
-
/* @__PURE__ */ jsx17("span", { class: "help", children: "Names only \u2014 STMA never stores a value." })
|
|
15193
|
-
] }),
|
|
15194
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15195
|
-
/* @__PURE__ */ jsx17("label", { children: "Needs a person before changing" }),
|
|
15196
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "requireApprovalFor", rows: 2, placeholder: "migration\ncontract", children: draft ? listToLines(draft.autonomy.requireApprovalFor) : "" }),
|
|
15197
|
-
/* @__PURE__ */ jsxs17("span", { class: "help", children: [
|
|
15198
|
-
"One claim type per line \u2014 ",
|
|
15199
|
-
/* @__PURE__ */ jsx17("code", { children: "path" }),
|
|
15200
|
-
", ",
|
|
15201
|
-
/* @__PURE__ */ jsx17("code", { children: "component" }),
|
|
15202
|
-
",",
|
|
15203
|
-
" ",
|
|
15204
|
-
/* @__PURE__ */ jsx17("code", { children: "contract" }),
|
|
15205
|
-
", ",
|
|
15206
|
-
/* @__PURE__ */ jsx17("code", { children: "migration" }),
|
|
15207
|
-
", ",
|
|
15208
|
-
/* @__PURE__ */ jsx17("code", { children: "config" }),
|
|
15209
|
-
". A run that declares this ground is told to get a human to agree first. It warns; it does not lock."
|
|
15210
|
-
] })
|
|
15211
|
-
] }),
|
|
15212
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15213
|
-
/* @__PURE__ */ jsx17("label", { children: "Change budget" }),
|
|
15214
|
-
/* @__PURE__ */ jsxs17("div", { class: "row", children: [
|
|
15215
|
-
/* @__PURE__ */ jsx17(
|
|
15216
|
-
"input",
|
|
15217
|
-
{
|
|
15218
|
-
class: "in",
|
|
15219
|
-
type: "number",
|
|
15220
|
-
min: "0",
|
|
15221
|
-
name: "maxScopeItems",
|
|
15222
|
-
style: "width:120px",
|
|
15223
|
-
placeholder: "scope",
|
|
15224
|
-
value: draft?.changeBudget.maxScopeItems || ""
|
|
15225
|
-
}
|
|
15226
|
-
),
|
|
15227
|
-
/* @__PURE__ */ jsx17(
|
|
15228
|
-
"input",
|
|
15229
|
-
{
|
|
15230
|
-
class: "in",
|
|
15231
|
-
type: "number",
|
|
15232
|
-
min: "0",
|
|
15233
|
-
name: "maxPaths",
|
|
15234
|
-
style: "width:120px",
|
|
15235
|
-
placeholder: "paths",
|
|
15236
|
-
value: draft?.changeBudget.maxPaths || ""
|
|
15237
|
-
}
|
|
15238
|
-
)
|
|
15239
|
-
] }),
|
|
15240
|
-
/* @__PURE__ */ jsx17("span", { class: "help", children: "Total claims and path claims one run may declare. Blank means no budget. A reviewer reads a small change and skims a big one, so this is the cheapest quality control there is." })
|
|
15241
|
-
] }),
|
|
15242
|
-
/* @__PURE__ */ jsxs17("div", { class: "field", children: [
|
|
15243
|
-
/* @__PURE__ */ jsx17("label", { children: "Expected runtimes" }),
|
|
15244
|
-
/* @__PURE__ */ jsx17("textarea", { class: "in", name: "runtimes", rows: 2, "aria-label": "Expected runtimes", placeholder: "node=22.14.0\npython=3.12.4", children: draft ? runtimesToLines(draft.environment.runtimes) : "" }),
|
|
15245
|
-
/* @__PURE__ */ jsxs17("span", { class: "help", children: [
|
|
15246
|
-
"One ",
|
|
15247
|
-
/* @__PURE__ */ jsx17("code", { children: "name=version" }),
|
|
15248
|
-
" per line. Preflight calls a mismatch critical."
|
|
15249
|
-
] })
|
|
15250
|
-
] }),
|
|
15251
|
-
/* @__PURE__ */ jsxs17("div", { class: "dialog-actions", children: [
|
|
15252
|
-
/* @__PURE__ */ jsx17("button", { class: "btn", type: "button", "data-close-dialog": "t", children: "Cancel" }),
|
|
15253
|
-
/* @__PURE__ */ jsx17("button", { class: "btn btn-primary", type: "submit", children: "Publish" })
|
|
15254
|
-
] })
|
|
15255
|
-
] })
|
|
15256
|
-
] }) : null,
|
|
15257
15491
|
isOwner ? /* @__PURE__ */ jsxs17("dialog", { id: "record-baseline", class: "formdlg wide", children: [
|
|
15258
15492
|
/* @__PURE__ */ jsx17("h3", { children: "Record an environment baseline" }),
|
|
15259
15493
|
/* @__PURE__ */ jsx17("p", { class: "dlgsub", children: "Promote a machine that already works. Preflight compares every other machine against it and tells the agent, before it starts, what is different." }),
|
|
@@ -17552,15 +17786,827 @@ notificationsRoutes.post("/app/notifications/webhook", async (c) => {
|
|
|
17552
17786
|
return back4(`ok=${encodeURIComponent("Personal webhook saved.")}`);
|
|
17553
17787
|
});
|
|
17554
17788
|
|
|
17555
|
-
// src/routes/
|
|
17556
|
-
import { and as and27,
|
|
17789
|
+
// src/routes/policyEditor.tsx
|
|
17790
|
+
import { and as and27, desc as desc21, eq as eq35, inArray as inArray13 } from "drizzle-orm";
|
|
17557
17791
|
import { Hono as Hono16 } from "hono";
|
|
17558
17792
|
import { Fragment as Fragment16, jsx as jsx19, jsxs as jsxs19 } from "hono/jsx/jsx-runtime";
|
|
17559
|
-
var
|
|
17793
|
+
var policyEditorRoutes = new Hono16();
|
|
17794
|
+
var PROJECT_LIMIT3 = 50;
|
|
17795
|
+
function servedText(doc, team, version) {
|
|
17796
|
+
const out = [];
|
|
17797
|
+
out.push(`Team rules for ${team}${version ? ` (v${version}, pending)` : ""}`);
|
|
17798
|
+
for (const line of doc.guidance) out.push(`- ${line}`);
|
|
17799
|
+
if (doc.permissions.deny.length > 0) out.push(`Denied: ${doc.permissions.deny.join(" \xB7 ")}`);
|
|
17800
|
+
if (doc.permissions.requireApproval.length > 0) {
|
|
17801
|
+
out.push(`Requires approval: ${doc.permissions.requireApproval.join(" \xB7 ")}`);
|
|
17802
|
+
}
|
|
17803
|
+
if (doc.requiredChecks.length > 0) out.push(`Checks: ${doc.requiredChecks.join(" \xB7 ")}`);
|
|
17804
|
+
if (doc.protectedPaths.length > 0) out.push(`Protected: ${doc.protectedPaths.join(" \xB7 ")}`);
|
|
17805
|
+
if (doc.environment.requiredEnvVarNames.length > 0) {
|
|
17806
|
+
out.push(`Required env: ${doc.environment.requiredEnvVarNames.join(", ")}`);
|
|
17807
|
+
}
|
|
17808
|
+
const runtimes = Object.entries(doc.environment.runtimes);
|
|
17809
|
+
if (runtimes.length > 0) {
|
|
17810
|
+
out.push(`Runtimes: ${runtimes.map(([n, v]) => v ? `${n} ${v}` : n).join(", ")}`);
|
|
17811
|
+
}
|
|
17812
|
+
if (doc.autonomy.requireApprovalFor.length > 0) {
|
|
17813
|
+
out.push(`A person must approve: ${doc.autonomy.requireApprovalFor.join(" \xB7 ")}`);
|
|
17814
|
+
}
|
|
17815
|
+
const budget = [];
|
|
17816
|
+
if (doc.changeBudget.maxScopeItems > 0) budget.push(`${doc.changeBudget.maxScopeItems} claims`);
|
|
17817
|
+
if (doc.changeBudget.maxPaths > 0) budget.push(`${doc.changeBudget.maxPaths} paths`);
|
|
17818
|
+
if (budget.length > 0) out.push(`Budget: ${budget.join(", ")}`);
|
|
17819
|
+
if (out.length === 1) out.push("- (nothing published yet)");
|
|
17820
|
+
return out.join("\n");
|
|
17821
|
+
}
|
|
17822
|
+
policyEditorRoutes.get("/app/teams/:slug/policy", async (c) => {
|
|
17823
|
+
const user = c.get("user");
|
|
17824
|
+
if (!user) return loginRedirect(c);
|
|
17825
|
+
const db = c.get("db");
|
|
17826
|
+
const found = await teamForUser(db, user.id, c.req.param("slug"));
|
|
17827
|
+
if (!found) return c.notFound();
|
|
17828
|
+
const { team, role } = found;
|
|
17829
|
+
if (!planLimits(team.plan, c.get("env").hosted).governance) return c.notFound();
|
|
17830
|
+
if (role !== "owner") {
|
|
17831
|
+
return c.html(
|
|
17832
|
+
/* @__PURE__ */ jsx19(AppLayout, { user, active: "governance", title: "Policy", children: /* @__PURE__ */ jsxs19("div", { class: "card card-pad joincard", children: [
|
|
17833
|
+
/* @__PURE__ */ jsx19("span", { class: "tile tile-44 tile-gray", children: "\xB7" }),
|
|
17834
|
+
/* @__PURE__ */ jsx19("h2", { class: "title m0", children: "Owners publish policy" }),
|
|
17835
|
+
/* @__PURE__ */ jsx19("p", { class: "m0 sub", children: "You can read every rule in force on the governance page \u2014 publishing a new version is an owner action, because it changes what every agent on the team is told." }),
|
|
17836
|
+
/* @__PURE__ */ jsx19("a", { class: "btn", href: `/app/teams/${team.slug}/governance`, style: "align-self:flex-start", children: "Back to governance" })
|
|
17837
|
+
] }) }),
|
|
17838
|
+
403
|
|
17839
|
+
);
|
|
17840
|
+
}
|
|
17841
|
+
const projectQuery = (c.req.query("project") ?? "").trim();
|
|
17842
|
+
const scopeProject = projectQuery ? await projectForTeam(db, team.id, projectQuery) : void 0;
|
|
17843
|
+
const teamProjects = await db.select({ name: projects.name }).from(projects).where(eq35(projects.teamId, team.id)).orderBy(projects.name).limit(PROJECT_LIMIT3);
|
|
17844
|
+
const scopeKey = scopeProject ? `project:${scopeProject.id}` : "team";
|
|
17845
|
+
const bundles = await db.select().from(policyBundles).where(and27(eq35(policyBundles.teamId, team.id), eq35(policyBundles.scopeKey, scopeKey))).orderBy(desc21(policyBundles.version)).limit(1);
|
|
17846
|
+
const live = bundles[0];
|
|
17847
|
+
const draft = live ? policyDocumentSchema.parse(live.document) : null;
|
|
17848
|
+
const nextVersion = (live?.version ?? 0) + 1;
|
|
17849
|
+
const served = await effectivePolicy(db, user.id, {
|
|
17850
|
+
team: team.slug,
|
|
17851
|
+
project: scopeProject?.name
|
|
17852
|
+
});
|
|
17853
|
+
const servedDoc = "error" in served ? null : served.document;
|
|
17854
|
+
const liveRuns = await db.select({ n: agentRuns.id }).from(agentRuns).where(and27(eq35(agentRuns.teamId, team.id), inArray13(agentRuns.status, ["active", "waiting", "blocked"])));
|
|
17855
|
+
const error = c.req.query("error");
|
|
17856
|
+
const scopeLabel = scopeProject ? `project: ${scopeProject.name}` : "team-wide";
|
|
17857
|
+
return c.html(
|
|
17858
|
+
/* @__PURE__ */ jsxs19(
|
|
17859
|
+
AppLayout,
|
|
17860
|
+
{
|
|
17861
|
+
user,
|
|
17862
|
+
active: "governance",
|
|
17863
|
+
title: `Edit policy \u2014 ${team.name}`,
|
|
17864
|
+
head: /* @__PURE__ */ jsx19(
|
|
17865
|
+
PageHead,
|
|
17866
|
+
{
|
|
17867
|
+
crumb: `/ ${team.slug} / governance / policy`,
|
|
17868
|
+
title: `Edit policy \u2014 ${scopeLabel}`,
|
|
17869
|
+
sub: "A document, not a dialog: write on the left, read what every agent will receive on the right.",
|
|
17870
|
+
actions: /* @__PURE__ */ jsxs19(Fragment16, { children: [
|
|
17871
|
+
/* @__PURE__ */ jsx19("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}/governance`, children: "Discard" }),
|
|
17872
|
+
/* @__PURE__ */ jsxs19("button", { class: "btn btn-sm btn-primary", type: "submit", form: "policy-form", children: [
|
|
17873
|
+
"Publish v",
|
|
17874
|
+
nextVersion
|
|
17875
|
+
] })
|
|
17876
|
+
] })
|
|
17877
|
+
}
|
|
17878
|
+
),
|
|
17879
|
+
keys: [{ k: "Esc", label: "back to governance" }],
|
|
17880
|
+
keysNote: "publishing writes a new version \u2014 the old one is archived, never deleted",
|
|
17881
|
+
children: [
|
|
17882
|
+
error ? /* @__PURE__ */ jsx19("div", { class: "banner banner-error", children: error }) : null,
|
|
17883
|
+
/* @__PURE__ */ jsxs19("div", { class: "edgrid", children: [
|
|
17884
|
+
/* @__PURE__ */ jsxs19(
|
|
17885
|
+
"form",
|
|
17886
|
+
{
|
|
17887
|
+
id: "policy-form",
|
|
17888
|
+
class: "card card-pad",
|
|
17889
|
+
method: "post",
|
|
17890
|
+
action: `/app/teams/${team.slug}/policy`,
|
|
17891
|
+
style: "display:flex;flex-direction:column;gap:14px",
|
|
17892
|
+
children: [
|
|
17893
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17894
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-scope", children: "Scope" }),
|
|
17895
|
+
/* @__PURE__ */ jsxs19("select", { class: "in", id: "pf-scope", name: "scope", children: [
|
|
17896
|
+
/* @__PURE__ */ jsx19("option", { value: "team", selected: !scopeProject, children: "Team-wide \u2014 applies to every run" }),
|
|
17897
|
+
teamProjects.map((p) => /* @__PURE__ */ jsxs19("option", { value: p.name, selected: p.name === scopeProject?.name, children: [
|
|
17898
|
+
"Project: ",
|
|
17899
|
+
p.name,
|
|
17900
|
+
" \u2014 merged on top of team policy"
|
|
17901
|
+
] }))
|
|
17902
|
+
] }),
|
|
17903
|
+
/* @__PURE__ */ jsx19("span", { class: "help", children: "Project policy adds to team policy; it never replaces it. Switching scope here changes what you are editing \u2014 reopen the page to load that scope's document." })
|
|
17904
|
+
] }),
|
|
17905
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17906
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-guidance", children: "Guidance" }),
|
|
17907
|
+
/* @__PURE__ */ jsx19(
|
|
17908
|
+
"textarea",
|
|
17909
|
+
{
|
|
17910
|
+
class: "in",
|
|
17911
|
+
id: "pf-guidance",
|
|
17912
|
+
name: "guidance",
|
|
17913
|
+
rows: 3,
|
|
17914
|
+
placeholder: "Keep migrations backwards compatible.\nNever touch another agent's branch \u2014 open a debug session instead.",
|
|
17915
|
+
children: draft ? listToLines(draft.guidance) : ""
|
|
17916
|
+
}
|
|
17917
|
+
),
|
|
17918
|
+
/* @__PURE__ */ jsx19("span", { class: "help", children: "Plain sentences the agent reads before it plans." })
|
|
17919
|
+
] }),
|
|
17920
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17921
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-deny", children: "Denied" }),
|
|
17922
|
+
/* @__PURE__ */ jsx19(
|
|
17923
|
+
"textarea",
|
|
17924
|
+
{
|
|
17925
|
+
class: "in",
|
|
17926
|
+
id: "pf-deny",
|
|
17927
|
+
name: "deny",
|
|
17928
|
+
rows: 3,
|
|
17929
|
+
placeholder: "read secret values\npush to main",
|
|
17930
|
+
children: draft ? listToLines(draft.permissions.deny) : ""
|
|
17931
|
+
}
|
|
17932
|
+
)
|
|
17933
|
+
] }),
|
|
17934
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17935
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-approval", children: "Requires approval" }),
|
|
17936
|
+
/* @__PURE__ */ jsx19(
|
|
17937
|
+
"textarea",
|
|
17938
|
+
{
|
|
17939
|
+
class: "in",
|
|
17940
|
+
id: "pf-approval",
|
|
17941
|
+
name: "requireApproval",
|
|
17942
|
+
rows: 2,
|
|
17943
|
+
placeholder: "production changes\nschema migrations",
|
|
17944
|
+
children: draft ? listToLines(draft.permissions.requireApproval) : ""
|
|
17945
|
+
}
|
|
17946
|
+
),
|
|
17947
|
+
/* @__PURE__ */ jsx19("span", { class: "help", children: "The agent must ask its human before doing these." })
|
|
17948
|
+
] }),
|
|
17949
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17950
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-checks", children: "Required checks" }),
|
|
17951
|
+
/* @__PURE__ */ jsx19(
|
|
17952
|
+
"textarea",
|
|
17953
|
+
{
|
|
17954
|
+
class: "in",
|
|
17955
|
+
id: "pf-checks",
|
|
17956
|
+
name: "requiredChecks",
|
|
17957
|
+
rows: 2,
|
|
17958
|
+
placeholder: "npm test\nnpm run typecheck",
|
|
17959
|
+
children: draft ? listToLines(draft.requiredChecks) : ""
|
|
17960
|
+
}
|
|
17961
|
+
)
|
|
17962
|
+
] }),
|
|
17963
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17964
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-paths", children: "Protected paths" }),
|
|
17965
|
+
/* @__PURE__ */ jsx19(
|
|
17966
|
+
"textarea",
|
|
17967
|
+
{
|
|
17968
|
+
class: "in",
|
|
17969
|
+
id: "pf-paths",
|
|
17970
|
+
name: "protectedPaths",
|
|
17971
|
+
rows: 2,
|
|
17972
|
+
placeholder: "db/migrations/**\n.github/workflows/**",
|
|
17973
|
+
children: draft ? listToLines(draft.protectedPaths) : ""
|
|
17974
|
+
}
|
|
17975
|
+
)
|
|
17976
|
+
] }),
|
|
17977
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17978
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-env", children: "Required environment variables" }),
|
|
17979
|
+
/* @__PURE__ */ jsx19(
|
|
17980
|
+
"textarea",
|
|
17981
|
+
{
|
|
17982
|
+
class: "in",
|
|
17983
|
+
id: "pf-env",
|
|
17984
|
+
name: "requiredEnvVarNames",
|
|
17985
|
+
rows: 2,
|
|
17986
|
+
placeholder: "DATABASE_URL\nPAYMENTS_WEBHOOK_SECRET",
|
|
17987
|
+
children: draft ? listToLines(draft.environment.requiredEnvVarNames) : ""
|
|
17988
|
+
}
|
|
17989
|
+
),
|
|
17990
|
+
/* @__PURE__ */ jsx19("span", { class: "help", children: "Names only \u2014 STMA never carries a value." })
|
|
17991
|
+
] }),
|
|
17992
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
17993
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-runtimes", children: "Expected runtimes" }),
|
|
17994
|
+
/* @__PURE__ */ jsx19(
|
|
17995
|
+
"textarea",
|
|
17996
|
+
{
|
|
17997
|
+
class: "in",
|
|
17998
|
+
id: "pf-runtimes",
|
|
17999
|
+
name: "runtimes",
|
|
18000
|
+
rows: 2,
|
|
18001
|
+
placeholder: "node=22.14.0\npython=3.12.4",
|
|
18002
|
+
children: draft ? runtimesToLines(draft.environment.runtimes) : ""
|
|
18003
|
+
}
|
|
18004
|
+
),
|
|
18005
|
+
/* @__PURE__ */ jsxs19("span", { class: "help", children: [
|
|
18006
|
+
"One ",
|
|
18007
|
+
/* @__PURE__ */ jsx19("code", { children: "name=version" }),
|
|
18008
|
+
" per line. Preflight calls a mismatch critical."
|
|
18009
|
+
] })
|
|
18010
|
+
] }),
|
|
18011
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", children: [
|
|
18012
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-autonomy", children: "A person must approve" }),
|
|
18013
|
+
/* @__PURE__ */ jsx19(
|
|
18014
|
+
"textarea",
|
|
18015
|
+
{
|
|
18016
|
+
class: "in",
|
|
18017
|
+
id: "pf-autonomy",
|
|
18018
|
+
name: "requireApprovalFor",
|
|
18019
|
+
rows: 2,
|
|
18020
|
+
placeholder: "migration\ncontract",
|
|
18021
|
+
children: draft ? listToLines(draft.autonomy.requireApprovalFor) : ""
|
|
18022
|
+
}
|
|
18023
|
+
),
|
|
18024
|
+
/* @__PURE__ */ jsx19("span", { class: "help", children: "Claim types that need a human before a run takes write access to them." })
|
|
18025
|
+
] }),
|
|
18026
|
+
/* @__PURE__ */ jsxs19("div", { class: "row", style: "gap:12px;flex-wrap:wrap", children: [
|
|
18027
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", style: "flex:1;min-width:150px", children: [
|
|
18028
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-claims", children: "Max claims per run" }),
|
|
18029
|
+
/* @__PURE__ */ jsx19(
|
|
18030
|
+
"input",
|
|
18031
|
+
{
|
|
18032
|
+
class: "in",
|
|
18033
|
+
id: "pf-claims",
|
|
18034
|
+
type: "number",
|
|
18035
|
+
name: "maxScopeItems",
|
|
18036
|
+
min: 0,
|
|
18037
|
+
value: draft?.changeBudget.maxScopeItems || "",
|
|
18038
|
+
placeholder: "0 = unset"
|
|
18039
|
+
}
|
|
18040
|
+
)
|
|
18041
|
+
] }),
|
|
18042
|
+
/* @__PURE__ */ jsxs19("div", { class: "field", style: "flex:1;min-width:150px", children: [
|
|
18043
|
+
/* @__PURE__ */ jsx19("label", { for: "pf-paths-budget", children: "Max paths per run" }),
|
|
18044
|
+
/* @__PURE__ */ jsx19(
|
|
18045
|
+
"input",
|
|
18046
|
+
{
|
|
18047
|
+
class: "in",
|
|
18048
|
+
id: "pf-paths-budget",
|
|
18049
|
+
type: "number",
|
|
18050
|
+
name: "maxPaths",
|
|
18051
|
+
min: 0,
|
|
18052
|
+
value: draft?.changeBudget.maxPaths || "",
|
|
18053
|
+
placeholder: "0 = unset"
|
|
18054
|
+
}
|
|
18055
|
+
)
|
|
18056
|
+
] })
|
|
18057
|
+
] })
|
|
18058
|
+
]
|
|
18059
|
+
}
|
|
18060
|
+
),
|
|
18061
|
+
/* @__PURE__ */ jsxs19("div", { class: "col", children: [
|
|
18062
|
+
/* @__PURE__ */ jsxs19("div", { class: "darkcard", children: [
|
|
18063
|
+
/* @__PURE__ */ jsx19("span", { class: "overline", children: "What get_policy will serve" }),
|
|
18064
|
+
/* @__PURE__ */ jsx19("div", { class: "cmd inner", children: /* @__PURE__ */ jsx19("code", { style: "white-space:pre-wrap", children: servedDoc ? servedText(servedDoc, team.slug, null) : "(nothing published yet)" }) }),
|
|
18065
|
+
/* @__PURE__ */ jsx19("p", { class: "m0 small", style: "color:var(--dark-mut)", children: "The merged document, exactly as an agent receives it \u2014 team rules with this scope's additions on top. It updates when you publish, not while you type." })
|
|
18066
|
+
] }),
|
|
18067
|
+
/* @__PURE__ */ jsxs19("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
18068
|
+
/* @__PURE__ */ jsx19("span", { class: "card-title", children: "On publish" }),
|
|
18069
|
+
/* @__PURE__ */ jsxs19("div", { class: "factrow", children: [
|
|
18070
|
+
/* @__PURE__ */ jsx19("span", { class: "y", children: "\u2713" }),
|
|
18071
|
+
/* @__PURE__ */ jsxs19("span", { children: [
|
|
18072
|
+
"v",
|
|
18073
|
+
live?.version ?? 0,
|
|
18074
|
+
" is archived, not deleted \u2014 the record of what was in force stays."
|
|
18075
|
+
] })
|
|
18076
|
+
] }),
|
|
18077
|
+
/* @__PURE__ */ jsxs19("div", { class: "factrow", children: [
|
|
18078
|
+
/* @__PURE__ */ jsx19("span", { class: "y", children: "\u2713" }),
|
|
18079
|
+
/* @__PURE__ */ jsxs19("span", { children: [
|
|
18080
|
+
"The new hash reaches every agent on its next ",
|
|
18081
|
+
/* @__PURE__ */ jsx19("code", { children: "get_policy" }),
|
|
18082
|
+
"."
|
|
18083
|
+
] })
|
|
18084
|
+
] }),
|
|
18085
|
+
/* @__PURE__ */ jsxs19("div", { class: "factrow", children: [
|
|
18086
|
+
/* @__PURE__ */ jsx19("span", { class: liveRuns.length > 0 ? "n" : "y", children: liveRuns.length > 0 ? "!" : "\u2713" }),
|
|
18087
|
+
/* @__PURE__ */ jsx19("span", { children: liveRuns.length > 0 ? `${liveRuns.length} live ${liveRuns.length === 1 ? "run is" : "runs are"} on the old version \u2014 they read as unconfirmed until they answer with the new hash.` : "No run is in flight, so nothing is holding the old version." })
|
|
18088
|
+
] })
|
|
18089
|
+
] })
|
|
18090
|
+
] })
|
|
18091
|
+
] })
|
|
18092
|
+
]
|
|
18093
|
+
}
|
|
18094
|
+
)
|
|
18095
|
+
);
|
|
18096
|
+
});
|
|
18097
|
+
|
|
18098
|
+
// src/routes/projects.tsx
|
|
18099
|
+
import { and as and28, count as count12, countDistinct as countDistinct5, desc as desc22, eq as eq36, gt as gt13, inArray as inArray14, max as max7 } from "drizzle-orm";
|
|
18100
|
+
import { Hono as Hono17 } from "hono";
|
|
18101
|
+
import { Fragment as Fragment17, jsx as jsx20, jsxs as jsxs20 } from "hono/jsx/jsx-runtime";
|
|
18102
|
+
var projectsRoutes = new Hono17();
|
|
18103
|
+
var RUNS_SHOWN = 6;
|
|
18104
|
+
var SESSIONS_SHOWN = 5;
|
|
18105
|
+
var TRAIL_SHOWN = 8;
|
|
18106
|
+
var WEEK = 7 * 24 * 60 * 60 * 1e3;
|
|
18107
|
+
var rank2 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
18108
|
+
var attemptId2 = (row) => ({
|
|
18109
|
+
ownerId: row.owner.id,
|
|
18110
|
+
attemptGroup: row.run.attemptGroup,
|
|
18111
|
+
taskKey: row.run.taskKey,
|
|
18112
|
+
worktree: row.run.worktree ?? null
|
|
18113
|
+
});
|
|
18114
|
+
async function severityByRun(db, rows) {
|
|
18115
|
+
const out = /* @__PURE__ */ new Map();
|
|
18116
|
+
if (rows.length === 0) return out;
|
|
18117
|
+
const claims = await claimsForRuns(
|
|
18118
|
+
db,
|
|
18119
|
+
rows.map((row) => row.run.id)
|
|
18120
|
+
);
|
|
18121
|
+
const byRun = new Map(rows.map((row) => [row.run.id, row]));
|
|
18122
|
+
const asConflictClaims = claims.map((claim) => {
|
|
18123
|
+
const row = byRun.get(claim.runId);
|
|
18124
|
+
return {
|
|
18125
|
+
runId: claim.runId,
|
|
18126
|
+
owner: row.owner.username,
|
|
18127
|
+
agentName: row.installation.name,
|
|
18128
|
+
taskKey: row.run.taskKey,
|
|
18129
|
+
resourceType: claim.resourceType,
|
|
18130
|
+
resourceKey: claim.resourceKey,
|
|
18131
|
+
access: claim.access
|
|
18132
|
+
};
|
|
18133
|
+
});
|
|
18134
|
+
for (const row of rows) {
|
|
18135
|
+
const mine = asConflictClaims.filter((claim) => claim.runId === row.run.id);
|
|
18136
|
+
const others = asConflictClaims.filter((claim) => {
|
|
18137
|
+
if (claim.runId === row.run.id) return false;
|
|
18138
|
+
const other = byRun.get(claim.runId);
|
|
18139
|
+
if (!other) return false;
|
|
18140
|
+
if (other.run.projectId !== row.run.projectId) return false;
|
|
18141
|
+
return !areAttemptSiblings(attemptId2(row), attemptId2(other));
|
|
18142
|
+
});
|
|
18143
|
+
for (const conflict of detectClaimConflicts(mine, others)) {
|
|
18144
|
+
const seen = out.get(row.run.id);
|
|
18145
|
+
if (!seen || rank2[conflict.severity] < rank2[seen]) out.set(row.run.id, conflict.severity);
|
|
18146
|
+
}
|
|
18147
|
+
}
|
|
18148
|
+
return out;
|
|
18149
|
+
}
|
|
18150
|
+
var claimLabel = (type, key, access) => `${access === "write" ? "w" : "r"}:${type}:${key}`;
|
|
18151
|
+
projectsRoutes.get("/app/teams/:slug/projects", async (c) => {
|
|
18152
|
+
const user = c.get("user");
|
|
18153
|
+
if (!user) return loginRedirect(c);
|
|
18154
|
+
const db = c.get("db");
|
|
18155
|
+
const access = await teamForUser(db, user.id, c.req.param("slug"));
|
|
18156
|
+
if (!access) return c.notFound();
|
|
18157
|
+
const team = access.team;
|
|
18158
|
+
const rows = await db.select().from(projects).where(eq36(projects.teamId, team.id)).orderBy(projects.name);
|
|
18159
|
+
const ids = rows.map((p) => p.id);
|
|
18160
|
+
const openSessions = /* @__PURE__ */ new Map();
|
|
18161
|
+
const lastSnapshot = /* @__PURE__ */ new Map();
|
|
18162
|
+
const baseline = /* @__PURE__ */ new Set();
|
|
18163
|
+
const policyVersion = /* @__PURE__ */ new Map();
|
|
18164
|
+
const flowName = /* @__PURE__ */ new Map();
|
|
18165
|
+
if (ids.length > 0) {
|
|
18166
|
+
for (const r of await db.select({ pid: debugSessions.projectId, n: count12() }).from(debugSessions).where(and28(inArray14(debugSessions.projectId, ids), eq36(debugSessions.status, "open"))).groupBy(debugSessions.projectId)) {
|
|
18167
|
+
if (r.pid) openSessions.set(r.pid, r.n);
|
|
18168
|
+
}
|
|
18169
|
+
for (const r of await db.select({ pid: snapshots.projectId, last: max7(snapshots.createdAt) }).from(snapshots).where(inArray14(snapshots.projectId, ids)).groupBy(snapshots.projectId)) {
|
|
18170
|
+
if (r.pid) lastSnapshot.set(r.pid, r.last);
|
|
18171
|
+
}
|
|
18172
|
+
for (const r of await db.select({ pid: environmentBaselines.projectId }).from(environmentBaselines).where(
|
|
18173
|
+
and28(
|
|
18174
|
+
inArray14(environmentBaselines.projectId, ids),
|
|
18175
|
+
eq36(environmentBaselines.active, true)
|
|
18176
|
+
)
|
|
18177
|
+
)) {
|
|
18178
|
+
if (r.pid) baseline.add(r.pid);
|
|
18179
|
+
}
|
|
18180
|
+
for (const r of await db.select({ scope: policyBundles.scopeKey, version: max7(policyBundles.version) }).from(policyBundles).where(eq36(policyBundles.teamId, team.id)).groupBy(policyBundles.scopeKey)) {
|
|
18181
|
+
const id = r.scope.startsWith("project:") ? r.scope.slice("project:".length) : null;
|
|
18182
|
+
if (id && r.version) policyVersion.set(id, r.version);
|
|
18183
|
+
}
|
|
18184
|
+
for (const r of await db.select({ pid: deliveryFlows.projectId, name: deliveryFlows.name }).from(deliveryFlows).where(and28(eq36(deliveryFlows.teamId, team.id), eq36(deliveryFlows.status, "active")))) {
|
|
18185
|
+
if (r.pid) flowName.set(r.pid, r.name);
|
|
18186
|
+
}
|
|
18187
|
+
}
|
|
18188
|
+
const teamFlow = (await db.select({ name: deliveryFlows.name }).from(deliveryFlows).where(and28(eq36(deliveryFlows.teamId, team.id), eq36(deliveryFlows.status, "active"))).limit(5)).length;
|
|
18189
|
+
const live = (await activeRunsForMember(db, user.id, team.slug)).filter((r) => r.run.projectId);
|
|
18190
|
+
const severity = await severityByRun(db, live);
|
|
18191
|
+
const runsByProject = /* @__PURE__ */ new Map();
|
|
18192
|
+
for (const row of live) {
|
|
18193
|
+
const key = row.run.projectId;
|
|
18194
|
+
runsByProject.set(key, [...runsByProject.get(key) ?? [], row]);
|
|
18195
|
+
}
|
|
18196
|
+
const totalOpen = [...openSessions.values()].reduce((a, b) => a + b, 0);
|
|
18197
|
+
return c.html(
|
|
18198
|
+
/* @__PURE__ */ jsx20(
|
|
18199
|
+
AppLayout,
|
|
18200
|
+
{
|
|
18201
|
+
user,
|
|
18202
|
+
active: "projects",
|
|
18203
|
+
title: `Projects \u2014 ${team.name}`,
|
|
18204
|
+
strip: /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18205
|
+
/* @__PURE__ */ jsx20(Lead, { text: `${rows.length} ${rows.length === 1 ? "project" : "projects"}`, live: rows.length > 0 }),
|
|
18206
|
+
/* @__PURE__ */ jsx20(Vr, {}),
|
|
18207
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18208
|
+
live.length,
|
|
18209
|
+
" running now"
|
|
18210
|
+
] }),
|
|
18211
|
+
/* @__PURE__ */ jsx20(Vr, {}),
|
|
18212
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18213
|
+
totalOpen,
|
|
18214
|
+
" open sessions"
|
|
18215
|
+
] })
|
|
18216
|
+
] }),
|
|
18217
|
+
scope: /* @__PURE__ */ jsxs20("span", { class: "mono muted", children: [
|
|
18218
|
+
"team ",
|
|
18219
|
+
team.slug
|
|
18220
|
+
] }),
|
|
18221
|
+
head: /* @__PURE__ */ jsx20(
|
|
18222
|
+
PageHead,
|
|
18223
|
+
{
|
|
18224
|
+
crumb: `/ ${team.slug} / projects`,
|
|
18225
|
+
title: "Projects",
|
|
18226
|
+
sub: "Born automatically from the repo identifier agents send. Each one owns its policy, baseline, flow and sessions."
|
|
18227
|
+
}
|
|
18228
|
+
),
|
|
18229
|
+
keys: [{ k: "G", label: "agent map" }],
|
|
18230
|
+
keysNote: "a project appears the first time an agent names its repo \u2014 there is nothing to create",
|
|
18231
|
+
children: rows.length === 0 ? /* @__PURE__ */ jsxs20("div", { class: "card card-pad muted small", children: [
|
|
18232
|
+
"No projects yet. One appears the first time an agent names its repository \u2014 through",
|
|
18233
|
+
" ",
|
|
18234
|
+
/* @__PURE__ */ jsx20("code", { children: "start_run" }),
|
|
18235
|
+
", a snapshot, or a session opened with a repo name. Nothing to create here."
|
|
18236
|
+
] }) : /* @__PURE__ */ jsx20("div", { class: "card scroll-x", children: /* @__PURE__ */ jsxs20("table", { class: "tbl", children: [
|
|
18237
|
+
/* @__PURE__ */ jsxs20("tr", { children: [
|
|
18238
|
+
/* @__PURE__ */ jsx20("th", { children: "Project" }),
|
|
18239
|
+
/* @__PURE__ */ jsx20("th", { children: "Runs now" }),
|
|
18240
|
+
/* @__PURE__ */ jsx20("th", { children: "Open sessions" }),
|
|
18241
|
+
/* @__PURE__ */ jsx20("th", { children: "Baseline" }),
|
|
18242
|
+
/* @__PURE__ */ jsx20("th", { children: "Policy" }),
|
|
18243
|
+
/* @__PURE__ */ jsx20("th", { children: "Flow" }),
|
|
18244
|
+
/* @__PURE__ */ jsx20("th", { children: "Last snapshot" }),
|
|
18245
|
+
/* @__PURE__ */ jsx20("th", {})
|
|
18246
|
+
] }),
|
|
18247
|
+
rows.map((p) => {
|
|
18248
|
+
const mine = runsByProject.get(p.id) ?? [];
|
|
18249
|
+
const worst = mine.map((r) => severity.get(r.run.id)).filter(Boolean).sort((a, b) => rank2[a] - rank2[b])[0];
|
|
18250
|
+
return /* @__PURE__ */ jsxs20("tr", { children: [
|
|
18251
|
+
/* @__PURE__ */ jsx20("td", { class: "name", children: /* @__PURE__ */ jsx20("a", { href: `/app/teams/${team.slug}/projects/${encodeURIComponent(p.name)}`, children: p.name }) }),
|
|
18252
|
+
/* @__PURE__ */ jsxs20("td", { children: [
|
|
18253
|
+
mine.length,
|
|
18254
|
+
worst ? /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18255
|
+
" ",
|
|
18256
|
+
/* @__PURE__ */ jsx20("span", { class: `pill ${worst === "critical" ? "pill-danger" : "pill-warn"}`, children: "overlap" })
|
|
18257
|
+
] }) : null
|
|
18258
|
+
] }),
|
|
18259
|
+
/* @__PURE__ */ jsx20("td", { children: openSessions.get(p.id) ?? 0 }),
|
|
18260
|
+
/* @__PURE__ */ jsx20("td", { children: baseline.has(p.id) ? /* @__PURE__ */ jsxs20("span", { class: "mono", children: [
|
|
18261
|
+
/* @__PURE__ */ jsx20("span", { class: "dot" }),
|
|
18262
|
+
" set"
|
|
18263
|
+
] }) : /* @__PURE__ */ jsx20("span", { class: "muted", children: "none" }) }),
|
|
18264
|
+
/* @__PURE__ */ jsx20("td", { class: "mono", children: policyVersion.has(p.id) ? `v${policyVersion.get(p.id)}` : /* @__PURE__ */ jsx20("span", { class: "muted", children: "team only" }) }),
|
|
18265
|
+
/* @__PURE__ */ jsx20("td", { class: "mono", children: flowName.get(p.id) ?? (teamFlow > 0 ? /* @__PURE__ */ jsx20("span", { class: "muted", children: "team-wide" }) : /* @__PURE__ */ jsx20("span", { class: "muted", children: "\u2014" })) }),
|
|
18266
|
+
/* @__PURE__ */ jsx20("td", { class: "muted", children: timeAgo(lastSnapshot.get(p.id) ?? null) ?? "\u2014" }),
|
|
18267
|
+
/* @__PURE__ */ jsx20("td", { class: "chev", children: "\u203A" })
|
|
18268
|
+
] });
|
|
18269
|
+
})
|
|
18270
|
+
] }) })
|
|
18271
|
+
}
|
|
18272
|
+
)
|
|
18273
|
+
);
|
|
18274
|
+
});
|
|
18275
|
+
projectsRoutes.get("/app/teams/:slug/projects/:project", async (c) => {
|
|
18276
|
+
const user = c.get("user");
|
|
18277
|
+
if (!user) return loginRedirect(c);
|
|
18278
|
+
const db = c.get("db");
|
|
18279
|
+
const access = await teamForUser(db, user.id, c.req.param("slug"));
|
|
18280
|
+
if (!access) return c.notFound();
|
|
18281
|
+
const team = access.team;
|
|
18282
|
+
const name = c.req.param("project");
|
|
18283
|
+
const found = await db.select().from(projects).where(and28(eq36(projects.teamId, team.id), eq36(projects.name, name))).limit(1);
|
|
18284
|
+
const project = found[0];
|
|
18285
|
+
if (!project) return c.notFound();
|
|
18286
|
+
const live = (await activeRunsForMember(db, user.id, team.slug)).filter(
|
|
18287
|
+
(r) => r.run.projectId === project.id
|
|
18288
|
+
);
|
|
18289
|
+
const severity = await severityByRun(db, live);
|
|
18290
|
+
const worst = live.map((r) => severity.get(r.run.id)).filter(Boolean).sort((a, b) => rank2[a] - rank2[b])[0];
|
|
18291
|
+
const claims = await claimsForRuns(
|
|
18292
|
+
db,
|
|
18293
|
+
live.map((r) => r.run.id)
|
|
18294
|
+
);
|
|
18295
|
+
const sessions = await db.select({ session: debugSessions }).from(debugSessions).where(and28(eq36(debugSessions.projectId, project.id), eq36(debugSessions.status, "open"))).orderBy(desc22(debugSessions.createdAt)).limit(SESSIONS_SHOWN);
|
|
18296
|
+
const openCount = (await db.select({ n: count12() }).from(debugSessions).where(and28(eq36(debugSessions.projectId, project.id), eq36(debugSessions.status, "open"))))[0]?.n ?? 0;
|
|
18297
|
+
const agents7d = (await db.select({ n: countDistinct5(activity.tokenId) }).from(activity).where(
|
|
18298
|
+
and28(
|
|
18299
|
+
eq36(activity.projectId, project.id),
|
|
18300
|
+
gt13(activity.createdAt, new Date(Date.now() - WEEK))
|
|
18301
|
+
)
|
|
18302
|
+
))[0]?.n ?? 0;
|
|
18303
|
+
const snap = (await db.select({ last: max7(snapshots.createdAt) }).from(snapshots).where(eq36(snapshots.projectId, project.id)))[0]?.last;
|
|
18304
|
+
const policy = await effectivePolicy(db, user.id, { team: team.slug, project: project.name });
|
|
18305
|
+
const baselines = await activeBaselines(db, team.id, 1, project.id);
|
|
18306
|
+
const checks = await recentEnvironmentChecks(db, team.id, 5, project.id);
|
|
18307
|
+
const criticalChecks = checks.filter((row) => row.check.status === "critical").length;
|
|
18308
|
+
const trail = await recentAgentEvents(db, team.id, TRAIL_SHOWN, project.id);
|
|
18309
|
+
const receipts = await recentPolicyReceipts(db, team.id, 10, project.id);
|
|
18310
|
+
const confirmed = receipts.length > 0 ? {
|
|
18311
|
+
total: receipts.length,
|
|
18312
|
+
answered: receipts.filter((row) => row.receipt.reportedHash && !row.receipt.drift).length
|
|
18313
|
+
} : null;
|
|
18314
|
+
const policyDoc = "error" in policy ? null : policy;
|
|
18315
|
+
const rules = [];
|
|
18316
|
+
const projectScope = policyDoc?.sources.find((source) => source.scope.startsWith("project:"));
|
|
18317
|
+
if (policyDoc) {
|
|
18318
|
+
const doc = policyDoc.document;
|
|
18319
|
+
for (const rule of doc.autonomy?.requireApprovalFor ?? []) rules.push(`Needs a person: ${rule}`);
|
|
18320
|
+
for (const rule of doc.permissions?.requireApproval ?? []) rules.push(`Requires approval: ${rule}`);
|
|
18321
|
+
for (const key of doc.environment?.requiredEnvVarNames ?? []) rules.push(`Required env var: ${key}`);
|
|
18322
|
+
for (const check2 of doc.requiredChecks ?? []) rules.push(`Check: ${check2}`);
|
|
18323
|
+
for (const path2 of doc.protectedPaths ?? []) rules.push(`Protected: ${path2}`);
|
|
18324
|
+
for (const line of doc.permissions?.deny ?? []) rules.push(`Denied: ${line}`);
|
|
18325
|
+
}
|
|
18326
|
+
return c.html(
|
|
18327
|
+
/* @__PURE__ */ jsxs20(
|
|
18328
|
+
AppLayout,
|
|
18329
|
+
{
|
|
18330
|
+
user,
|
|
18331
|
+
active: "projects",
|
|
18332
|
+
title: `${project.name} \u2014 ${team.name}`,
|
|
18333
|
+
strip: /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18334
|
+
/* @__PURE__ */ jsx20(
|
|
18335
|
+
Lead,
|
|
18336
|
+
{
|
|
18337
|
+
text: `${live.length} ${live.length === 1 ? "run" : "runs"} on this project`,
|
|
18338
|
+
live: live.length > 0
|
|
18339
|
+
}
|
|
18340
|
+
),
|
|
18341
|
+
worst ? /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18342
|
+
/* @__PURE__ */ jsx20(Vr, {}),
|
|
18343
|
+
/* @__PURE__ */ jsxs20("span", { style: "color:var(--red)", children: [
|
|
18344
|
+
worst,
|
|
18345
|
+
" overlap"
|
|
18346
|
+
] })
|
|
18347
|
+
] }) : null,
|
|
18348
|
+
/* @__PURE__ */ jsx20(Vr, {}),
|
|
18349
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18350
|
+
openCount,
|
|
18351
|
+
" open sessions"
|
|
18352
|
+
] })
|
|
18353
|
+
] }),
|
|
18354
|
+
scope: /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18355
|
+
/* @__PURE__ */ jsx20("a", { class: "linklike", href: `/app/teams/${team.slug}/projects`, children: "\u2190 all projects" }),
|
|
18356
|
+
/* @__PURE__ */ jsx20(Vr, {}),
|
|
18357
|
+
/* @__PURE__ */ jsxs20("span", { class: "mono muted", children: [
|
|
18358
|
+
"project ",
|
|
18359
|
+
project.name
|
|
18360
|
+
] })
|
|
18361
|
+
] }),
|
|
18362
|
+
head: /* @__PURE__ */ jsx20(
|
|
18363
|
+
PageHead,
|
|
18364
|
+
{
|
|
18365
|
+
crumb: `/ ${team.slug} / projects / ${project.name}`,
|
|
18366
|
+
title: project.name,
|
|
18367
|
+
sub: "Everything about this project on one page: live runs, sessions, policy, environment and delivery \u2014 with the way to change each.",
|
|
18368
|
+
actions: /* @__PURE__ */ jsxs20(Fragment17, { children: [
|
|
18369
|
+
/* @__PURE__ */ jsx20(
|
|
18370
|
+
"a",
|
|
18371
|
+
{
|
|
18372
|
+
class: "btn btn-sm",
|
|
18373
|
+
href: `/app/teams/${team.slug}/compare?project=${encodeURIComponent(project.name)}`,
|
|
18374
|
+
children: "Compare machines"
|
|
18375
|
+
}
|
|
18376
|
+
),
|
|
18377
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: "/app/sessions", children: "Open a session" }),
|
|
18378
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm btn-primary", href: "/app/agents", children: "View on agent map" })
|
|
18379
|
+
] })
|
|
18380
|
+
}
|
|
18381
|
+
),
|
|
18382
|
+
band: worst === "critical" ? /* @__PURE__ */ jsx20(
|
|
18383
|
+
Band,
|
|
18384
|
+
{
|
|
18385
|
+
kind: "danger",
|
|
18386
|
+
tag: "critical",
|
|
18387
|
+
actions: /* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: "/app/agents", children: "Open in inspector" }),
|
|
18388
|
+
children: "Two live runs hold the same ground on this project. Claims are advisory \u2014 STMA warns the agents, it does not lock the file."
|
|
18389
|
+
}
|
|
18390
|
+
) : void 0,
|
|
18391
|
+
keys: [{ k: "G", label: "agent map" }],
|
|
18392
|
+
keysNote: "every number here is read from the page that owns it \u2014 the links go there",
|
|
18393
|
+
children: [
|
|
18394
|
+
/* @__PURE__ */ jsxs20("div", { class: "stat3", children: [
|
|
18395
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18396
|
+
/* @__PURE__ */ jsx20("span", { class: "overline", children: "Active runs" }),
|
|
18397
|
+
/* @__PURE__ */ jsx20("span", { class: `n${worst === "critical" ? " bad" : ""}`, children: live.length })
|
|
18398
|
+
] }),
|
|
18399
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18400
|
+
/* @__PURE__ */ jsx20("span", { class: "overline", children: "Open sessions" }),
|
|
18401
|
+
/* @__PURE__ */ jsx20("span", { class: "n", children: openCount })
|
|
18402
|
+
] }),
|
|
18403
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18404
|
+
/* @__PURE__ */ jsx20("span", { class: "overline", children: "Agents (7d)" }),
|
|
18405
|
+
/* @__PURE__ */ jsx20("span", { class: "n", children: agents7d })
|
|
18406
|
+
] }),
|
|
18407
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18408
|
+
/* @__PURE__ */ jsx20("span", { class: "overline", children: "Preflight" }),
|
|
18409
|
+
/* @__PURE__ */ jsx20("span", { class: `n${criticalChecks > 0 ? " bad" : " ok"}`, children: criticalChecks > 0 ? `${criticalChecks} critical` : checks.length > 0 ? "ok" : "\u2014" })
|
|
18410
|
+
] }),
|
|
18411
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18412
|
+
/* @__PURE__ */ jsx20("span", { class: "overline", children: "Last snapshot" }),
|
|
18413
|
+
/* @__PURE__ */ jsx20("span", { class: "n", children: timeAgo(snap ?? null) ?? "\u2014" })
|
|
18414
|
+
] })
|
|
18415
|
+
] }),
|
|
18416
|
+
/* @__PURE__ */ jsxs20("div", { class: "edgrid", style: "margin-top:16px", children: [
|
|
18417
|
+
/* @__PURE__ */ jsxs20("div", { class: "col", children: [
|
|
18418
|
+
/* @__PURE__ */ jsxs20("div", { class: "card", children: [
|
|
18419
|
+
/* @__PURE__ */ jsxs20("div", { class: "card-head", children: [
|
|
18420
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18421
|
+
/* @__PURE__ */ jsx20("div", { class: "card-title", children: "Active runs" }),
|
|
18422
|
+
/* @__PURE__ */ jsx20("div", { class: "card-note", children: "This project only \u2014 the same rows as the agent map." })
|
|
18423
|
+
] }),
|
|
18424
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: "/app/agents", children: "Agent map" })
|
|
18425
|
+
] }),
|
|
18426
|
+
live.length === 0 ? /* @__PURE__ */ jsx20("div", { class: "card-pad muted small", children: "Nothing running here right now. The map is presence, not history \u2014 what these runs did is on Activity and the governance timeline." }) : live.slice(0, RUNS_SHOWN).map((row) => {
|
|
18427
|
+
const held = claims.filter((claim) => claim.runId === row.run.id).map((claim) => claimLabel(claim.resourceType, claim.resourceKey, claim.access));
|
|
18428
|
+
const sev = severity.get(row.run.id);
|
|
18429
|
+
return /* @__PURE__ */ jsxs20("div", { class: "introw", children: [
|
|
18430
|
+
/* @__PURE__ */ jsx20("span", { class: `dot${sev === "critical" ? " red" : ""}` }),
|
|
18431
|
+
/* @__PURE__ */ jsxs20("div", { class: "who", children: [
|
|
18432
|
+
/* @__PURE__ */ jsx20("div", { class: "t", children: row.run.taskKey ?? "no task key" }),
|
|
18433
|
+
/* @__PURE__ */ jsxs20("div", { class: "s", children: [
|
|
18434
|
+
row.owner.username,
|
|
18435
|
+
" \xB7 ",
|
|
18436
|
+
row.installation.name,
|
|
18437
|
+
row.run.branch ? ` \xB7 ${row.run.branch}` : ""
|
|
18438
|
+
] })
|
|
18439
|
+
] }),
|
|
18440
|
+
/* @__PURE__ */ jsx20("span", { class: "mono small muted hide-sm", children: held.join(", ") || "no claims" }),
|
|
18441
|
+
sev ? /* @__PURE__ */ jsx20("span", { class: `pill ${sev === "critical" ? "pill-danger" : "pill-warn"}`, children: sev }) : null
|
|
18442
|
+
] });
|
|
18443
|
+
})
|
|
18444
|
+
] }),
|
|
18445
|
+
/* @__PURE__ */ jsxs20("div", { class: "card", children: [
|
|
18446
|
+
/* @__PURE__ */ jsxs20("div", { class: "card-head", children: [
|
|
18447
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18448
|
+
/* @__PURE__ */ jsx20("div", { class: "card-title", children: "Open sessions" }),
|
|
18449
|
+
/* @__PURE__ */ jsxs20("div", { class: "card-note", children: [
|
|
18450
|
+
"Debug threads tagged ",
|
|
18451
|
+
project.name,
|
|
18452
|
+
"."
|
|
18453
|
+
] })
|
|
18454
|
+
] }),
|
|
18455
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: "/app/sessions", children: "All sessions" })
|
|
18456
|
+
] }),
|
|
18457
|
+
sessions.length === 0 ? /* @__PURE__ */ jsxs20("div", { class: "card-pad muted small", children: [
|
|
18458
|
+
"No open thread on this project. Resolved ones stay searchable \u2014 agents read them through ",
|
|
18459
|
+
/* @__PURE__ */ jsx20("code", { children: "search_past_issues" }),
|
|
18460
|
+
"."
|
|
18461
|
+
] }) : sessions.map(({ session }) => /* @__PURE__ */ jsxs20("div", { class: "introw", children: [
|
|
18462
|
+
/* @__PURE__ */ jsxs20("div", { class: "who", children: [
|
|
18463
|
+
/* @__PURE__ */ jsx20("div", { class: "t", children: /* @__PURE__ */ jsx20("a", { href: `/app/sessions/${session.id}`, children: session.title }) }),
|
|
18464
|
+
/* @__PURE__ */ jsxs20("div", { class: "s", children: [
|
|
18465
|
+
"opened ",
|
|
18466
|
+
timeAgo(session.createdAt) ?? "just now"
|
|
18467
|
+
] })
|
|
18468
|
+
] }),
|
|
18469
|
+
/* @__PURE__ */ jsx20("span", { class: "pill pill-ok", children: "open" })
|
|
18470
|
+
] }))
|
|
18471
|
+
] }),
|
|
18472
|
+
/* @__PURE__ */ jsxs20("div", { class: "card", children: [
|
|
18473
|
+
/* @__PURE__ */ jsxs20("div", { class: "card-head", children: [
|
|
18474
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18475
|
+
/* @__PURE__ */ jsx20("div", { class: "card-title", children: "Recent activity" }),
|
|
18476
|
+
/* @__PURE__ */ jsx20("div", { class: "card-note", children: "The run trail for this project, newest first." })
|
|
18477
|
+
] }),
|
|
18478
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}/activity?project=${encodeURIComponent(project.name)}`, children: "Activity" })
|
|
18479
|
+
] }),
|
|
18480
|
+
trail.length === 0 ? /* @__PURE__ */ jsx20("div", { class: "card-pad muted small", children: "Nothing recorded for this project yet." }) : /* @__PURE__ */ jsxs20("table", { class: "tbl", children: [
|
|
18481
|
+
/* @__PURE__ */ jsxs20("tr", { children: [
|
|
18482
|
+
/* @__PURE__ */ jsx20("th", { children: "When" }),
|
|
18483
|
+
/* @__PURE__ */ jsx20("th", { children: "Event" }),
|
|
18484
|
+
/* @__PURE__ */ jsx20("th", { children: "Run" }),
|
|
18485
|
+
/* @__PURE__ */ jsx20("th", { children: "Owner" })
|
|
18486
|
+
] }),
|
|
18487
|
+
trail.map((row) => /* @__PURE__ */ jsxs20("tr", { children: [
|
|
18488
|
+
/* @__PURE__ */ jsx20("td", { class: "muted", children: timeAgo(row.event.createdAt) ?? "just now" }),
|
|
18489
|
+
/* @__PURE__ */ jsx20("td", { class: "mono", children: row.event.type }),
|
|
18490
|
+
/* @__PURE__ */ jsx20("td", { class: "mono", children: row.run.taskKey ?? "\u2014" }),
|
|
18491
|
+
/* @__PURE__ */ jsx20("td", { children: row.owner })
|
|
18492
|
+
] }))
|
|
18493
|
+
] })
|
|
18494
|
+
] })
|
|
18495
|
+
] }),
|
|
18496
|
+
/* @__PURE__ */ jsxs20("div", { class: "col", children: [
|
|
18497
|
+
/* @__PURE__ */ jsxs20("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
18498
|
+
/* @__PURE__ */ jsxs20("div", { class: "row", style: "justify-content:space-between", children: [
|
|
18499
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18500
|
+
/* @__PURE__ */ jsx20("div", { class: "card-title", children: "Policy" }),
|
|
18501
|
+
/* @__PURE__ */ jsx20("div", { class: "card-note", children: "This project's additions, merged onto team rules." })
|
|
18502
|
+
] }),
|
|
18503
|
+
projectScope ? /* @__PURE__ */ jsxs20("span", { class: "pill pill-active", children: [
|
|
18504
|
+
"v",
|
|
18505
|
+
projectScope.version,
|
|
18506
|
+
confirmed !== null ? " \xB7 confirmed" : ""
|
|
18507
|
+
] }) : /* @__PURE__ */ jsx20("span", { class: "pill", children: "team only" })
|
|
18508
|
+
] }),
|
|
18509
|
+
rules.length === 0 ? /* @__PURE__ */ jsxs20("p", { class: "m0 small muted", children: [
|
|
18510
|
+
"No rules in force for ",
|
|
18511
|
+
project.name,
|
|
18512
|
+
" beyond the team's. A project policy adds to the team document; it never replaces it."
|
|
18513
|
+
] }) : rules.slice(0, 5).map((rule) => /* @__PURE__ */ jsxs20("div", { class: "factrow", children: [
|
|
18514
|
+
/* @__PURE__ */ jsx20("span", { class: "y", children: "\u2713" }),
|
|
18515
|
+
/* @__PURE__ */ jsx20("span", { children: rule })
|
|
18516
|
+
] })),
|
|
18517
|
+
policyDoc ? /* @__PURE__ */ jsxs20("div", { class: "factrow", children: [
|
|
18518
|
+
/* @__PURE__ */ jsx20("span", { class: confirmed && confirmed.answered > 0 ? "y" : "n", children: confirmed && confirmed.answered > 0 ? "\u2713" : "!" }),
|
|
18519
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18520
|
+
"Effective hash ",
|
|
18521
|
+
/* @__PURE__ */ jsx20("span", { class: "mono", children: policyDoc.hash.slice(0, 12) }),
|
|
18522
|
+
confirmed ? ` \u2014 ${confirmed.answered} of ${confirmed.total} recent runs confirmed it.` : " \u2014 no run has reported which rules it applied."
|
|
18523
|
+
] })
|
|
18524
|
+
] }) : null,
|
|
18525
|
+
/* @__PURE__ */ jsxs20("div", { class: "row", style: "gap:8px", children: [
|
|
18526
|
+
/* @__PURE__ */ jsx20(
|
|
18527
|
+
"a",
|
|
18528
|
+
{
|
|
18529
|
+
class: "btn btn-sm",
|
|
18530
|
+
href: `/app/teams/${team.slug}/governance?project=${encodeURIComponent(project.name)}`,
|
|
18531
|
+
children: "Edit policy"
|
|
18532
|
+
}
|
|
18533
|
+
),
|
|
18534
|
+
/* @__PURE__ */ jsx20(
|
|
18535
|
+
"a",
|
|
18536
|
+
{
|
|
18537
|
+
class: "btn btn-sm",
|
|
18538
|
+
href: `/app/teams/${team.slug}/governance?project=${encodeURIComponent(project.name)}`,
|
|
18539
|
+
children: "Receipts"
|
|
18540
|
+
}
|
|
18541
|
+
)
|
|
18542
|
+
] })
|
|
18543
|
+
] }),
|
|
18544
|
+
/* @__PURE__ */ jsxs20("div", { class: "card card-pad", style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
18545
|
+
/* @__PURE__ */ jsxs20("div", { class: "row", style: "justify-content:space-between", children: [
|
|
18546
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
18547
|
+
/* @__PURE__ */ jsx20("div", { class: "card-title", children: "Environment" }),
|
|
18548
|
+
/* @__PURE__ */ jsx20("div", { class: "card-note", children: "Baseline and what preflight told the machines." })
|
|
18549
|
+
] }),
|
|
18550
|
+
criticalChecks > 0 ? /* @__PURE__ */ jsxs20("span", { class: "pill pill-danger", children: [
|
|
18551
|
+
criticalChecks,
|
|
18552
|
+
" critical"
|
|
18553
|
+
] }) : null
|
|
18554
|
+
] }),
|
|
18555
|
+
baselines[0] ? /* @__PURE__ */ jsxs20("div", { class: "factrow", children: [
|
|
18556
|
+
/* @__PURE__ */ jsx20("span", { class: "y", children: "\u2713" }),
|
|
18557
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18558
|
+
"Baseline",
|
|
18559
|
+
" ",
|
|
18560
|
+
/* @__PURE__ */ jsx20("span", { class: "mono", children: baselines[0].baseline.fingerprint.slice(0, 12) }),
|
|
18561
|
+
" from",
|
|
18562
|
+
" ",
|
|
18563
|
+
/* @__PURE__ */ jsx20("b", { children: baselines[0].author ?? "unknown" }),
|
|
18564
|
+
",",
|
|
18565
|
+
" ",
|
|
18566
|
+
timeAgo(baselines[0].baseline.createdAt) ?? "just now",
|
|
18567
|
+
"."
|
|
18568
|
+
] })
|
|
18569
|
+
] }) : /* @__PURE__ */ jsxs20("div", { class: "factrow", children: [
|
|
18570
|
+
/* @__PURE__ */ jsx20("span", { class: "n", children: "!" }),
|
|
18571
|
+
/* @__PURE__ */ jsx20("span", { children: "No baseline recorded. Preflight can only report what a baseline claims, so without one an agent is told nothing about its machine." })
|
|
18572
|
+
] }),
|
|
18573
|
+
checks.slice(0, 2).map((row) => /* @__PURE__ */ jsxs20("div", { class: "factrow", children: [
|
|
18574
|
+
/* @__PURE__ */ jsx20("span", { class: row.check.status === "ok" ? "y" : "n", children: row.check.status === "ok" ? "\u2713" : "!" }),
|
|
18575
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
18576
|
+
/* @__PURE__ */ jsx20("b", { children: row.username ?? "a machine" }),
|
|
18577
|
+
": ",
|
|
18578
|
+
row.check.summary
|
|
18579
|
+
] })
|
|
18580
|
+
] })),
|
|
18581
|
+
/* @__PURE__ */ jsxs20("div", { class: "row", style: "gap:8px", children: [
|
|
18582
|
+
/* @__PURE__ */ jsx20(
|
|
18583
|
+
"a",
|
|
18584
|
+
{
|
|
18585
|
+
class: "btn btn-sm",
|
|
18586
|
+
href: `/app/teams/${team.slug}/compare?project=${encodeURIComponent(project.name)}`,
|
|
18587
|
+
children: "Compare machines"
|
|
18588
|
+
}
|
|
18589
|
+
),
|
|
18590
|
+
/* @__PURE__ */ jsx20("a", { class: "btn btn-sm", href: `/app/teams/${team.slug}/governance`, children: "Update baseline" })
|
|
18591
|
+
] })
|
|
18592
|
+
] })
|
|
18593
|
+
] })
|
|
18594
|
+
] })
|
|
18595
|
+
]
|
|
18596
|
+
}
|
|
18597
|
+
)
|
|
18598
|
+
);
|
|
18599
|
+
});
|
|
18600
|
+
|
|
18601
|
+
// src/routes/sessions.tsx
|
|
18602
|
+
import { and as and29, count as count13, desc as desc23, eq as eq37, ilike as ilike2, inArray as inArray15, or as or7, sql as sql18 } from "drizzle-orm";
|
|
18603
|
+
import { Hono as Hono18 } from "hono";
|
|
18604
|
+
import { Fragment as Fragment18, jsx as jsx21, jsxs as jsxs21 } from "hono/jsx/jsx-runtime";
|
|
18605
|
+
var sessionsRoutes = new Hono18();
|
|
17560
18606
|
var LIST_PAGE_SIZE = 25;
|
|
17561
18607
|
var THREAD_PAGE_SIZE = 100;
|
|
17562
18608
|
async function myTeams(db, userId) {
|
|
17563
|
-
return db.select({ team: teams }).from(memberships).innerJoin(teams,
|
|
18609
|
+
return db.select({ team: teams }).from(memberships).innerJoin(teams, eq37(memberships.teamId, teams.id)).where(eq37(memberships.userId, userId)).orderBy(teams.name);
|
|
17564
18610
|
}
|
|
17565
18611
|
var isKind2 = (k) => typeof k === "string" && MESSAGE_KINDS.includes(k);
|
|
17566
18612
|
sessionsRoutes.get("/app/sessions", async (c) => {
|
|
@@ -17580,23 +18626,23 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17580
18626
|
let openCount = 0;
|
|
17581
18627
|
let resolvedCount = 0;
|
|
17582
18628
|
if (teamIds.length > 0) {
|
|
17583
|
-
const conds = [
|
|
18629
|
+
const conds = [inArray15(debugSessions.teamId, teamIds), eq37(debugSessions.status, status)];
|
|
17584
18630
|
if (status === "resolved" && q) {
|
|
17585
18631
|
const like2 = `%${q}%`;
|
|
17586
18632
|
conds.push(
|
|
17587
18633
|
or7(ilike2(debugSessions.title, like2), sql18`${debugSessions.resolution}::text ilike ${like2}`)
|
|
17588
18634
|
);
|
|
17589
18635
|
}
|
|
17590
|
-
const fetched = await db.select({ s: debugSessions, projectName: projects.name }).from(debugSessions).leftJoin(projects,
|
|
18636
|
+
const fetched = await db.select({ s: debugSessions, projectName: projects.name }).from(debugSessions).leftJoin(projects, eq37(debugSessions.projectId, projects.id)).where(and29(...conds)).orderBy(desc23(debugSessions.createdAt)).limit(win.limit).offset(win.offset);
|
|
17591
18637
|
paged = slicePage(fetched, win);
|
|
17592
18638
|
sessions = paged.items;
|
|
17593
|
-
const countRows = await db.select({ status: debugSessions.status, n:
|
|
18639
|
+
const countRows = await db.select({ status: debugSessions.status, n: count13() }).from(debugSessions).where(inArray15(debugSessions.teamId, teamIds)).groupBy(debugSessions.status);
|
|
17594
18640
|
openCount = countRows.find((r) => r.status === "open")?.n ?? 0;
|
|
17595
18641
|
resolvedCount = countRows.find((r) => r.status === "resolved")?.n ?? 0;
|
|
17596
18642
|
}
|
|
17597
18643
|
const ids = sessions.map((r) => r.s.id);
|
|
17598
18644
|
const { stats, unread } = await sessionStats(db, { userId: user.id }, ids);
|
|
17599
|
-
const participantRows = ids.length ? await db.selectDistinct({ sessionId: messages.sessionId, username: users.username }).from(messages).innerJoin(users,
|
|
18645
|
+
const participantRows = ids.length ? await db.selectDistinct({ sessionId: messages.sessionId, username: users.username }).from(messages).innerJoin(users, eq37(messages.authorId, users.id)).where(inArray15(messages.sessionId, ids)) : [];
|
|
17600
18646
|
const participants = /* @__PURE__ */ new Map();
|
|
17601
18647
|
for (const p of participantRows) {
|
|
17602
18648
|
const list = participants.get(p.sessionId) ?? [];
|
|
@@ -17604,35 +18650,35 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17604
18650
|
participants.set(p.sessionId, list);
|
|
17605
18651
|
}
|
|
17606
18652
|
return c.html(
|
|
17607
|
-
/* @__PURE__ */
|
|
17608
|
-
error ? /* @__PURE__ */
|
|
17609
|
-
/* @__PURE__ */
|
|
17610
|
-
/* @__PURE__ */
|
|
17611
|
-
/* @__PURE__ */
|
|
18653
|
+
/* @__PURE__ */ jsxs21(AppLayout, { user, active: "sessions", title: "Sessions", children: [
|
|
18654
|
+
error ? /* @__PURE__ */ jsxs21("div", { class: "banner banner-error", children: [
|
|
18655
|
+
/* @__PURE__ */ jsx21("span", { class: "ic", children: "!" }),
|
|
18656
|
+
/* @__PURE__ */ jsx21("span", { children: error }),
|
|
18657
|
+
/* @__PURE__ */ jsx21("button", { class: "x", type: "button", "data-dismiss": "t", children: "\xD7" })
|
|
17612
18658
|
] }) : null,
|
|
17613
|
-
/* @__PURE__ */
|
|
17614
|
-
/* @__PURE__ */
|
|
17615
|
-
/* @__PURE__ */
|
|
17616
|
-
/* @__PURE__ */
|
|
18659
|
+
/* @__PURE__ */ jsxs21("div", { class: "page-head", children: [
|
|
18660
|
+
/* @__PURE__ */ jsxs21("div", { children: [
|
|
18661
|
+
/* @__PURE__ */ jsx21("h1", { class: "title", children: "Debug sessions" }),
|
|
18662
|
+
/* @__PURE__ */ jsx21("p", { class: "sub", children: "Topic-based threads where agents (and humans) debug together, asynchronously." })
|
|
17617
18663
|
] }),
|
|
17618
|
-
mine.length > 0 ? /* @__PURE__ */
|
|
18664
|
+
mine.length > 0 ? /* @__PURE__ */ jsx21("button", { class: "btn btn-primary", type: "button", "data-open-dialog": "#new-session", children: "New session" }) : null
|
|
17619
18665
|
] }),
|
|
17620
|
-
win.page === 1 ? /* @__PURE__ */
|
|
17621
|
-
mine.length === 0 ? /* @__PURE__ */
|
|
17622
|
-
/* @__PURE__ */
|
|
17623
|
-
/* @__PURE__ */
|
|
18666
|
+
win.page === 1 ? /* @__PURE__ */ jsx21("div", { "data-autorefresh": "30", style: "display:none" }) : null,
|
|
18667
|
+
mine.length === 0 ? /* @__PURE__ */ jsx21("div", { class: "card", children: /* @__PURE__ */ jsxs21("div", { class: "empty", children: [
|
|
18668
|
+
/* @__PURE__ */ jsx21("h2", { children: "Join a team first" }),
|
|
18669
|
+
/* @__PURE__ */ jsxs21("p", { children: [
|
|
17624
18670
|
"Debug sessions live inside a team. ",
|
|
17625
|
-
/* @__PURE__ */
|
|
18671
|
+
/* @__PURE__ */ jsx21("a", { href: "/app", children: "Create or join a team" }),
|
|
17626
18672
|
", then come back here."
|
|
17627
18673
|
] })
|
|
17628
|
-
] }) }) : /* @__PURE__ */
|
|
17629
|
-
/* @__PURE__ */
|
|
17630
|
-
/* @__PURE__ */
|
|
18674
|
+
] }) }) : /* @__PURE__ */ jsxs21(Fragment18, { children: [
|
|
18675
|
+
/* @__PURE__ */ jsxs21("div", { class: "tabs", children: [
|
|
18676
|
+
/* @__PURE__ */ jsxs21("a", { class: `tab${status === "open" ? " active" : ""}`, href: "/app/sessions", children: [
|
|
17631
18677
|
"Open (",
|
|
17632
18678
|
openCount,
|
|
17633
18679
|
")"
|
|
17634
18680
|
] }),
|
|
17635
|
-
/* @__PURE__ */
|
|
18681
|
+
/* @__PURE__ */ jsxs21(
|
|
17636
18682
|
"a",
|
|
17637
18683
|
{
|
|
17638
18684
|
class: `tab${status === "resolved" ? " active" : ""}`,
|
|
@@ -17645,9 +18691,9 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17645
18691
|
}
|
|
17646
18692
|
)
|
|
17647
18693
|
] }),
|
|
17648
|
-
status === "resolved" ? /* @__PURE__ */
|
|
17649
|
-
/* @__PURE__ */
|
|
17650
|
-
/* @__PURE__ */
|
|
18694
|
+
status === "resolved" ? /* @__PURE__ */ jsxs21("form", { class: "inline m0", method: "get", action: "/app/sessions", children: [
|
|
18695
|
+
/* @__PURE__ */ jsx21("input", { type: "hidden", name: "status", value: "resolved" }),
|
|
18696
|
+
/* @__PURE__ */ jsx21(
|
|
17651
18697
|
"input",
|
|
17652
18698
|
{
|
|
17653
18699
|
class: "in",
|
|
@@ -17658,53 +18704,53 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17658
18704
|
value: q
|
|
17659
18705
|
}
|
|
17660
18706
|
),
|
|
17661
|
-
/* @__PURE__ */
|
|
18707
|
+
/* @__PURE__ */ jsx21("button", { class: "btn", type: "submit", children: "Search" })
|
|
17662
18708
|
] }) : null,
|
|
17663
|
-
sessions.length === 0 && win.page === 1 ? /* @__PURE__ */
|
|
17664
|
-
/* @__PURE__ */
|
|
17665
|
-
/* @__PURE__ */
|
|
17666
|
-
] }) }) : /* @__PURE__ */
|
|
18709
|
+
sessions.length === 0 && win.page === 1 ? /* @__PURE__ */ jsx21("div", { class: "card", children: /* @__PURE__ */ jsxs21("div", { class: "empty", children: [
|
|
18710
|
+
/* @__PURE__ */ jsx21("h2", { children: status === "open" ? "No open sessions" : q ? "No archived match" : "Nothing resolved yet" }),
|
|
18711
|
+
/* @__PURE__ */ jsx21("p", { children: status === "open" ? 'When something "works on my machine", open a session here \u2014 or let your agent do it with the open_session tool.' : "Resolved sessions keep their root cause and fix, searchable for the next time." })
|
|
18712
|
+
] }) }) : /* @__PURE__ */ jsxs21("div", { class: "sesslist", children: [
|
|
17667
18713
|
sessions.map(({ s, projectName }) => {
|
|
17668
18714
|
const st = stats.get(s.id);
|
|
17669
18715
|
const un = unread.get(s.id) ?? 0;
|
|
17670
18716
|
const res = s.resolution ?? null;
|
|
17671
18717
|
const names = participants.get(s.id) ?? [];
|
|
17672
|
-
return /* @__PURE__ */
|
|
18718
|
+
return /* @__PURE__ */ jsxs21(
|
|
17673
18719
|
"a",
|
|
17674
18720
|
{
|
|
17675
18721
|
class: `sesscard${un > 0 ? " unread" : ""}${s.status === "resolved" ? " resolved" : ""}`,
|
|
17676
18722
|
href: `/app/sessions/${s.id}`,
|
|
17677
18723
|
children: [
|
|
17678
|
-
/* @__PURE__ */
|
|
17679
|
-
/* @__PURE__ */
|
|
17680
|
-
s.status === "open" ? /* @__PURE__ */
|
|
17681
|
-
/* @__PURE__ */
|
|
18724
|
+
/* @__PURE__ */ jsxs21("div", { class: "sesscard-head", children: [
|
|
18725
|
+
/* @__PURE__ */ jsx21("span", { class: "sesscard-title", children: s.title }),
|
|
18726
|
+
s.status === "open" ? /* @__PURE__ */ jsxs21("span", { class: "pill pill-open", children: [
|
|
18727
|
+
/* @__PURE__ */ jsx21("span", { class: "dot" }),
|
|
17682
18728
|
"open"
|
|
17683
|
-
] }) : /* @__PURE__ */
|
|
18729
|
+
] }) : /* @__PURE__ */ jsx21("span", { class: "pill pill-muted", children: "resolved" })
|
|
17684
18730
|
] }),
|
|
17685
|
-
s.status === "resolved" && res?.rootCause ? /* @__PURE__ */
|
|
17686
|
-
/* @__PURE__ */
|
|
18731
|
+
s.status === "resolved" && res?.rootCause ? /* @__PURE__ */ jsxs21("p", { class: "sesscard-root", children: [
|
|
18732
|
+
/* @__PURE__ */ jsx21("b", { children: "Root cause:" }),
|
|
17687
18733
|
" ",
|
|
17688
18734
|
res.rootCause.slice(0, 180),
|
|
17689
18735
|
res.rootCause.length > 180 ? "\u2026" : ""
|
|
17690
18736
|
] }) : null,
|
|
17691
|
-
/* @__PURE__ */
|
|
17692
|
-
/* @__PURE__ */
|
|
17693
|
-
names.length > 0 ? /* @__PURE__ */
|
|
17694
|
-
/* @__PURE__ */
|
|
17695
|
-
projectName ? /* @__PURE__ */
|
|
18737
|
+
/* @__PURE__ */ jsxs21("div", { class: "sesscard-meta", children: [
|
|
18738
|
+
/* @__PURE__ */ jsxs21("div", { class: "sesscard-info", children: [
|
|
18739
|
+
names.length > 0 ? /* @__PURE__ */ jsx21("span", { class: "avstack", children: names.map((n) => /* @__PURE__ */ jsx21("span", { class: "avatar light", children: initials(n) })) }) : null,
|
|
18740
|
+
/* @__PURE__ */ jsx21("span", { children: slugById.get(s.teamId) }),
|
|
18741
|
+
projectName ? /* @__PURE__ */ jsxs21("span", { children: [
|
|
17696
18742
|
"\xB7 ",
|
|
17697
18743
|
projectName
|
|
17698
18744
|
] }) : null,
|
|
17699
|
-
/* @__PURE__ */
|
|
18745
|
+
/* @__PURE__ */ jsxs21("span", { children: [
|
|
17700
18746
|
st?.n ?? 0,
|
|
17701
18747
|
" ",
|
|
17702
18748
|
(st?.n ?? 0) === 1 ? "message" : "messages",
|
|
17703
18749
|
st?.last ? ` \xB7 ${timeAgo(st.last)}` : ""
|
|
17704
18750
|
] })
|
|
17705
18751
|
] }),
|
|
17706
|
-
un > 0 ? /* @__PURE__ */
|
|
17707
|
-
/* @__PURE__ */
|
|
18752
|
+
un > 0 ? /* @__PURE__ */ jsxs21("span", { class: "unreadmark", children: [
|
|
18753
|
+
/* @__PURE__ */ jsx21("span", { class: "dot" }),
|
|
17708
18754
|
un,
|
|
17709
18755
|
" unread"
|
|
17710
18756
|
] }) : null
|
|
@@ -17713,8 +18759,8 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17713
18759
|
}
|
|
17714
18760
|
);
|
|
17715
18761
|
}),
|
|
17716
|
-
sessions.length === 0 ? /* @__PURE__ */
|
|
17717
|
-
/* @__PURE__ */
|
|
18762
|
+
sessions.length === 0 ? /* @__PURE__ */ jsx21("div", { class: "card card-pad muted small", children: "Nothing this far back \u2014 the list ends before this page." }) : null,
|
|
18763
|
+
/* @__PURE__ */ jsx21("div", { class: "card", children: /* @__PURE__ */ jsx21(
|
|
17718
18764
|
Pager,
|
|
17719
18765
|
{
|
|
17720
18766
|
path: "/app/sessions",
|
|
@@ -17725,17 +18771,17 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17725
18771
|
}
|
|
17726
18772
|
) })
|
|
17727
18773
|
] }),
|
|
17728
|
-
/* @__PURE__ */
|
|
17729
|
-
/* @__PURE__ */
|
|
17730
|
-
/* @__PURE__ */
|
|
17731
|
-
/* @__PURE__ */
|
|
17732
|
-
mine.length > 1 ? /* @__PURE__ */
|
|
17733
|
-
/* @__PURE__ */
|
|
17734
|
-
/* @__PURE__ */
|
|
17735
|
-
] }) : /* @__PURE__ */
|
|
17736
|
-
/* @__PURE__ */
|
|
17737
|
-
/* @__PURE__ */
|
|
17738
|
-
/* @__PURE__ */
|
|
18774
|
+
/* @__PURE__ */ jsxs21("dialog", { id: "new-session", class: "formdlg", children: [
|
|
18775
|
+
/* @__PURE__ */ jsx21("h3", { children: "New debug session" }),
|
|
18776
|
+
/* @__PURE__ */ jsx21("p", { class: "dlgsub", children: "Describe the problem \u2014 teammates' agents will see it in their inbox." }),
|
|
18777
|
+
/* @__PURE__ */ jsxs21("form", { method: "post", action: "/app/sessions", children: [
|
|
18778
|
+
mine.length > 1 ? /* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18779
|
+
/* @__PURE__ */ jsx21("label", { children: "Team" }),
|
|
18780
|
+
/* @__PURE__ */ jsx21("select", { class: "in", name: "team", children: mine.map((m) => /* @__PURE__ */ jsx21("option", { value: m.team.slug, children: m.team.name })) })
|
|
18781
|
+
] }) : /* @__PURE__ */ jsx21("input", { type: "hidden", name: "team", value: mine[0].team.slug }),
|
|
18782
|
+
/* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18783
|
+
/* @__PURE__ */ jsx21("label", { children: "Title" }),
|
|
18784
|
+
/* @__PURE__ */ jsx21(
|
|
17739
18785
|
"input",
|
|
17740
18786
|
{
|
|
17741
18787
|
class: "in",
|
|
@@ -17747,13 +18793,13 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17747
18793
|
}
|
|
17748
18794
|
)
|
|
17749
18795
|
] }),
|
|
17750
|
-
/* @__PURE__ */
|
|
17751
|
-
/* @__PURE__ */
|
|
17752
|
-
/* @__PURE__ */
|
|
18796
|
+
/* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18797
|
+
/* @__PURE__ */ jsx21("label", { children: "Project / repo (optional)" }),
|
|
18798
|
+
/* @__PURE__ */ jsx21("input", { class: "in", type: "text", name: "repo", placeholder: "e.g. billing-api", maxlength: 120 })
|
|
17753
18799
|
] }),
|
|
17754
|
-
/* @__PURE__ */
|
|
17755
|
-
/* @__PURE__ */
|
|
17756
|
-
/* @__PURE__ */
|
|
18800
|
+
/* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18801
|
+
/* @__PURE__ */ jsx21("label", { children: "First message" }),
|
|
18802
|
+
/* @__PURE__ */ jsx21(
|
|
17757
18803
|
"textarea",
|
|
17758
18804
|
{
|
|
17759
18805
|
class: "in",
|
|
@@ -17763,9 +18809,9 @@ sessionsRoutes.get("/app/sessions", async (c) => {
|
|
|
17763
18809
|
}
|
|
17764
18810
|
)
|
|
17765
18811
|
] }),
|
|
17766
|
-
/* @__PURE__ */
|
|
17767
|
-
/* @__PURE__ */
|
|
17768
|
-
/* @__PURE__ */
|
|
18812
|
+
/* @__PURE__ */ jsxs21("div", { class: "dialog-actions", children: [
|
|
18813
|
+
/* @__PURE__ */ jsx21("button", { class: "btn", type: "button", "data-close-dialog": "t", children: "Cancel" }),
|
|
18814
|
+
/* @__PURE__ */ jsx21("button", { class: "btn btn-primary", type: "submit", children: "Open session" })
|
|
17769
18815
|
] })
|
|
17770
18816
|
] })
|
|
17771
18817
|
] })
|
|
@@ -17830,35 +18876,35 @@ sessionsRoutes.get("/app/sessions/:id", async (c) => {
|
|
|
17830
18876
|
const found = await sessionForMember(db, c.req.param("id"), user.id);
|
|
17831
18877
|
if (!found) {
|
|
17832
18878
|
return c.html(
|
|
17833
|
-
/* @__PURE__ */
|
|
17834
|
-
/* @__PURE__ */
|
|
17835
|
-
/* @__PURE__ */
|
|
17836
|
-
/* @__PURE__ */
|
|
17837
|
-
/* @__PURE__ */
|
|
18879
|
+
/* @__PURE__ */ jsx21(AppLayout, { user, active: "sessions", title: "Not found", children: /* @__PURE__ */ jsxs21("div", { class: "card card-pad joincard", children: [
|
|
18880
|
+
/* @__PURE__ */ jsx21("span", { class: "tile tile-44 tile-gray", children: "\xD7" }),
|
|
18881
|
+
/* @__PURE__ */ jsx21("h2", { class: "title m0", children: "Session not found" }),
|
|
18882
|
+
/* @__PURE__ */ jsx21("p", { class: "m0 sub", children: "Either it does not exist or you are not in its team." }),
|
|
18883
|
+
/* @__PURE__ */ jsx21("a", { class: "btn", href: "/app/sessions", style: "align-self:flex-start", children: "Back to sessions" })
|
|
17838
18884
|
] }) }),
|
|
17839
18885
|
404
|
|
17840
18886
|
);
|
|
17841
18887
|
}
|
|
17842
18888
|
const { session, team } = found;
|
|
17843
18889
|
const win = pageWindow(c.req.query("page"), THREAD_PAGE_SIZE);
|
|
17844
|
-
const fetched = await db.select({ m: messages, author: users.username }).from(messages).leftJoin(users,
|
|
18890
|
+
const fetched = await db.select({ m: messages, author: users.username }).from(messages).leftJoin(users, eq37(messages.authorId, users.id)).where(eq37(messages.sessionId, session.id)).orderBy(desc23(messages.createdAt)).limit(win.limit).offset(win.offset);
|
|
17845
18891
|
const page = slicePage(fetched, win);
|
|
17846
18892
|
const msgs = [...page.items].reverse();
|
|
17847
|
-
const opener = session.openedBy ? (await db.select({ username: users.username }).from(users).where(
|
|
18893
|
+
const opener = session.openedBy ? (await db.select({ username: users.username }).from(users).where(eq37(users.id, session.openedBy)).limit(1))[0]?.username : void 0;
|
|
17848
18894
|
await markRead(db, user.id, session.id);
|
|
17849
18895
|
const resolution = session.resolution ?? null;
|
|
17850
18896
|
return c.html(
|
|
17851
|
-
/* @__PURE__ */
|
|
17852
|
-
/* @__PURE__ */
|
|
17853
|
-
/* @__PURE__ */
|
|
18897
|
+
/* @__PURE__ */ jsxs21(AppLayout, { user, active: "sessions", title: session.title, children: [
|
|
18898
|
+
/* @__PURE__ */ jsxs21("div", { class: "crumb", children: [
|
|
18899
|
+
/* @__PURE__ */ jsx21("a", { href: "/app/sessions", children: "Sessions" }),
|
|
17854
18900
|
" / ",
|
|
17855
18901
|
team.slug
|
|
17856
18902
|
] }),
|
|
17857
|
-
/* @__PURE__ */
|
|
17858
|
-
/* @__PURE__ */
|
|
17859
|
-
/* @__PURE__ */
|
|
17860
|
-
/* @__PURE__ */
|
|
17861
|
-
/* @__PURE__ */
|
|
18903
|
+
/* @__PURE__ */ jsxs21("div", { class: "card", children: [
|
|
18904
|
+
/* @__PURE__ */ jsxs21("div", { class: "card-head", style: "align-items:flex-start", children: [
|
|
18905
|
+
/* @__PURE__ */ jsxs21("div", { children: [
|
|
18906
|
+
/* @__PURE__ */ jsx21("div", { class: "card-title", style: "font-size:17px", children: session.title }),
|
|
18907
|
+
/* @__PURE__ */ jsxs21("div", { class: "card-note mono", children: [
|
|
17862
18908
|
team.slug,
|
|
17863
18909
|
" \xB7 opened by ",
|
|
17864
18910
|
opener ?? "unknown",
|
|
@@ -17866,12 +18912,12 @@ sessionsRoutes.get("/app/sessions/:id", async (c) => {
|
|
|
17866
18912
|
fmtDate(session.createdAt)
|
|
17867
18913
|
] })
|
|
17868
18914
|
] }),
|
|
17869
|
-
session.kind === "announcements" ? /* @__PURE__ */
|
|
17870
|
-
/* @__PURE__ */
|
|
18915
|
+
session.kind === "announcements" ? /* @__PURE__ */ jsxs21("span", { class: "pill pill-open", children: [
|
|
18916
|
+
/* @__PURE__ */ jsx21("span", { class: "dot" }),
|
|
17871
18917
|
"channel"
|
|
17872
|
-
] }) : session.status === "open" ? /* @__PURE__ */
|
|
18918
|
+
] }) : session.status === "open" ? /* @__PURE__ */ jsx21("button", { class: "btn btn-sm", type: "button", "data-open-dialog": "#resolve-dialog", children: "Mark resolved" }) : /* @__PURE__ */ jsx21("span", { class: "pill pill-muted", children: "resolved" })
|
|
17873
18919
|
] }),
|
|
17874
|
-
page.hasMore || win.page > 1 ? /* @__PURE__ */
|
|
18920
|
+
page.hasMore || win.page > 1 ? /* @__PURE__ */ jsx21(
|
|
17875
18921
|
Pager,
|
|
17876
18922
|
{
|
|
17877
18923
|
path: `/app/sessions/${session.id}`,
|
|
@@ -17881,47 +18927,47 @@ sessionsRoutes.get("/app/sessions/:id", async (c) => {
|
|
|
17881
18927
|
note: "Newest first by page; oldest first inside a page."
|
|
17882
18928
|
}
|
|
17883
18929
|
) : null,
|
|
17884
|
-
/* @__PURE__ */
|
|
17885
|
-
msgs.length === 0 ? /* @__PURE__ */
|
|
17886
|
-
msgs.map((r) => /* @__PURE__ */
|
|
17887
|
-
/* @__PURE__ */
|
|
17888
|
-
/* @__PURE__ */
|
|
17889
|
-
/* @__PURE__ */
|
|
17890
|
-
/* @__PURE__ */
|
|
18930
|
+
/* @__PURE__ */ jsxs21("div", { class: "thread", children: [
|
|
18931
|
+
msgs.length === 0 ? /* @__PURE__ */ jsx21("p", { class: "muted small m0", children: "No messages yet." }) : null,
|
|
18932
|
+
msgs.map((r) => /* @__PURE__ */ jsxs21("div", { class: "msg", children: [
|
|
18933
|
+
/* @__PURE__ */ jsx21("span", { class: `avatar ${r.m.authorId === user.id ? "ink" : "light"}`, children: initials(r.author ?? "ci") }),
|
|
18934
|
+
/* @__PURE__ */ jsxs21("div", { class: "msg-main", children: [
|
|
18935
|
+
/* @__PURE__ */ jsxs21("div", { class: "msg-head", children: [
|
|
18936
|
+
/* @__PURE__ */ jsxs21("span", { class: "msg-author", children: [
|
|
17891
18937
|
r.m.via ? `${r.m.via} \xB7 ` : "",
|
|
17892
18938
|
r.author ?? "automation"
|
|
17893
18939
|
] }),
|
|
17894
|
-
/* @__PURE__ */
|
|
17895
|
-
/* @__PURE__ */
|
|
18940
|
+
/* @__PURE__ */ jsx21("span", { class: `kindtag kind-${r.m.kind}`, children: r.m.kind }),
|
|
18941
|
+
/* @__PURE__ */ jsx21("span", { class: "msg-time", children: timeAgo(r.m.createdAt) })
|
|
17896
18942
|
] }),
|
|
17897
|
-
/* @__PURE__ */
|
|
18943
|
+
/* @__PURE__ */ jsx21("p", { class: "msg-text", children: r.m.body }),
|
|
17898
18944
|
(r.m.attachments ?? []).map(
|
|
17899
|
-
(a) => /* @__PURE__ */
|
|
17900
|
-
/* @__PURE__ */
|
|
17901
|
-
/* @__PURE__ */
|
|
18945
|
+
(a) => /* @__PURE__ */ jsxs21("div", { class: "attach", children: [
|
|
18946
|
+
/* @__PURE__ */ jsx21("div", { class: "attach-head", children: /* @__PURE__ */ jsx21("span", { children: a.name }) }),
|
|
18947
|
+
/* @__PURE__ */ jsx21("pre", { children: a.content })
|
|
17902
18948
|
] })
|
|
17903
18949
|
)
|
|
17904
18950
|
] })
|
|
17905
18951
|
] }))
|
|
17906
18952
|
] }),
|
|
17907
|
-
session.status === "resolved" && resolution ? /* @__PURE__ */
|
|
17908
|
-
/* @__PURE__ */
|
|
18953
|
+
session.status === "resolved" && resolution ? /* @__PURE__ */ jsxs21("div", { class: "resolution-note", children: [
|
|
18954
|
+
/* @__PURE__ */ jsxs21("span", { class: "t", children: [
|
|
17909
18955
|
"Resolved",
|
|
17910
18956
|
resolution.resolvedBy ? ` by ${resolution.resolvedBy}` : ""
|
|
17911
18957
|
] }),
|
|
17912
|
-
/* @__PURE__ */
|
|
17913
|
-
/* @__PURE__ */
|
|
18958
|
+
/* @__PURE__ */ jsxs21("p", { children: [
|
|
18959
|
+
/* @__PURE__ */ jsx21("b", { children: "Root cause:" }),
|
|
17914
18960
|
" ",
|
|
17915
18961
|
resolution.rootCause
|
|
17916
18962
|
] }),
|
|
17917
|
-
/* @__PURE__ */
|
|
17918
|
-
/* @__PURE__ */
|
|
18963
|
+
/* @__PURE__ */ jsxs21("p", { children: [
|
|
18964
|
+
/* @__PURE__ */ jsx21("b", { children: "Fix:" }),
|
|
17919
18965
|
" ",
|
|
17920
18966
|
resolution.fix
|
|
17921
18967
|
] })
|
|
17922
18968
|
] }) : null,
|
|
17923
|
-
/* @__PURE__ */
|
|
17924
|
-
/* @__PURE__ */
|
|
18969
|
+
/* @__PURE__ */ jsx21("div", { class: "composer", children: /* @__PURE__ */ jsxs21("form", { method: "post", action: `/app/sessions/${session.id}/messages`, style: "display:flex;flex-direction:column;gap:12px", children: [
|
|
18970
|
+
/* @__PURE__ */ jsx21(
|
|
17925
18971
|
"textarea",
|
|
17926
18972
|
{
|
|
17927
18973
|
class: "in",
|
|
@@ -17931,27 +18977,27 @@ sessionsRoutes.get("/app/sessions/:id", async (c) => {
|
|
|
17931
18977
|
maxlength: 2e4
|
|
17932
18978
|
}
|
|
17933
18979
|
),
|
|
17934
|
-
/* @__PURE__ */
|
|
17935
|
-
/* @__PURE__ */
|
|
17936
|
-
/* @__PURE__ */
|
|
18980
|
+
/* @__PURE__ */ jsxs21("div", { class: "page-head", style: "align-items:center", children: [
|
|
18981
|
+
/* @__PURE__ */ jsx21("select", { class: "in", name: "kind", children: MESSAGE_KINDS.filter((k) => k !== "announcement").map((k) => /* @__PURE__ */ jsx21("option", { value: k, selected: k === "question", children: k })) }),
|
|
18982
|
+
/* @__PURE__ */ jsx21("button", { class: "btn btn-primary", type: "submit", children: "Send" })
|
|
17937
18983
|
] })
|
|
17938
18984
|
] }) })
|
|
17939
18985
|
] }),
|
|
17940
|
-
/* @__PURE__ */
|
|
17941
|
-
/* @__PURE__ */
|
|
17942
|
-
/* @__PURE__ */
|
|
17943
|
-
/* @__PURE__ */
|
|
17944
|
-
/* @__PURE__ */
|
|
17945
|
-
/* @__PURE__ */
|
|
17946
|
-
/* @__PURE__ */
|
|
18986
|
+
/* @__PURE__ */ jsxs21("dialog", { id: "resolve-dialog", class: "formdlg", children: [
|
|
18987
|
+
/* @__PURE__ */ jsx21("h3", { children: "Mark session resolved" }),
|
|
18988
|
+
/* @__PURE__ */ jsx21("p", { class: "dlgsub", children: "Both fields go into the searchable archive, so the next agent hitting this can find it." }),
|
|
18989
|
+
/* @__PURE__ */ jsxs21("form", { method: "post", action: `/app/sessions/${session.id}/resolve`, children: [
|
|
18990
|
+
/* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18991
|
+
/* @__PURE__ */ jsx21("label", { children: "Root cause" }),
|
|
18992
|
+
/* @__PURE__ */ jsx21("textarea", { class: "in", name: "root_cause", required: true, maxlength: 4e3, placeholder: "What was actually wrong?" })
|
|
17947
18993
|
] }),
|
|
17948
|
-
/* @__PURE__ */
|
|
17949
|
-
/* @__PURE__ */
|
|
17950
|
-
/* @__PURE__ */
|
|
18994
|
+
/* @__PURE__ */ jsxs21("div", { class: "field", children: [
|
|
18995
|
+
/* @__PURE__ */ jsx21("label", { children: "Fix" }),
|
|
18996
|
+
/* @__PURE__ */ jsx21("textarea", { class: "in", name: "fix", required: true, maxlength: 4e3, placeholder: "What did you change? (PR link welcome)" })
|
|
17951
18997
|
] }),
|
|
17952
|
-
/* @__PURE__ */
|
|
17953
|
-
/* @__PURE__ */
|
|
17954
|
-
/* @__PURE__ */
|
|
18998
|
+
/* @__PURE__ */ jsxs21("div", { class: "dialog-actions", children: [
|
|
18999
|
+
/* @__PURE__ */ jsx21("button", { class: "btn", type: "button", "data-close-dialog": "t", children: "Cancel" }),
|
|
19000
|
+
/* @__PURE__ */ jsx21("button", { class: "btn btn-primary", type: "submit", children: "Resolve session" })
|
|
17955
19001
|
] })
|
|
17956
19002
|
] })
|
|
17957
19003
|
] })
|
|
@@ -18016,7 +19062,7 @@ sessionsRoutes.post("/app/sessions/:id/resolve", async (c) => {
|
|
|
18016
19062
|
resolvedBy: user.username,
|
|
18017
19063
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18018
19064
|
};
|
|
18019
|
-
await db.update(debugSessions).set({ status: "resolved", resolution, resolvedAt: /* @__PURE__ */ new Date() }).where(
|
|
19065
|
+
await db.update(debugSessions).set({ status: "resolved", resolution, resolvedAt: /* @__PURE__ */ new Date() }).where(eq37(debugSessions.id, found.session.id));
|
|
18020
19066
|
const posted = await db.insert(messages).values({
|
|
18021
19067
|
sessionId: found.session.id,
|
|
18022
19068
|
authorId: user.id,
|
|
@@ -18050,16 +19096,16 @@ Fix: ${resolution.fix}`
|
|
|
18050
19096
|
});
|
|
18051
19097
|
|
|
18052
19098
|
// src/routes/stream.ts
|
|
18053
|
-
import { eq as
|
|
18054
|
-
import { Hono as
|
|
19099
|
+
import { eq as eq38 } from "drizzle-orm";
|
|
19100
|
+
import { Hono as Hono19 } from "hono";
|
|
18055
19101
|
import { streamSSE } from "hono/streaming";
|
|
18056
|
-
var streamRoutes = new
|
|
19102
|
+
var streamRoutes = new Hono19();
|
|
18057
19103
|
var PING_MS = 2e4;
|
|
18058
19104
|
var MAX_LIFETIME_MS = 5 * 6e4;
|
|
18059
19105
|
streamRoutes.get("/app/stream", async (c) => {
|
|
18060
19106
|
const user = c.get("user");
|
|
18061
19107
|
if (!user) return c.text("sign in first", 401);
|
|
18062
|
-
const rows = await c.get("db").select({ teamId: memberships.teamId }).from(memberships).where(
|
|
19108
|
+
const rows = await c.get("db").select({ teamId: memberships.teamId }).from(memberships).where(eq38(memberships.userId, user.id));
|
|
18063
19109
|
const teams4 = new Set(rows.map((r) => r.teamId));
|
|
18064
19110
|
return streamSSE(c, async (stream) => {
|
|
18065
19111
|
const queue = [];
|
|
@@ -18116,45 +19162,45 @@ streamRoutes.get("/app/stream", async (c) => {
|
|
|
18116
19162
|
});
|
|
18117
19163
|
|
|
18118
19164
|
// src/ui/NotFound.tsx
|
|
18119
|
-
import { jsx as
|
|
18120
|
-
var NotFoundPage = ({ user, path: path2 }) => /* @__PURE__ */
|
|
18121
|
-
/* @__PURE__ */
|
|
18122
|
-
/* @__PURE__ */
|
|
18123
|
-
/* @__PURE__ */
|
|
19165
|
+
import { jsx as jsx22, jsxs as jsxs22 } from "hono/jsx/jsx-runtime";
|
|
19166
|
+
var NotFoundPage = ({ user, path: path2 }) => /* @__PURE__ */ jsx22(AppLayout, { user, title: "Not found", children: /* @__PURE__ */ jsxs22("div", { class: "card card-pad joincard", children: [
|
|
19167
|
+
/* @__PURE__ */ jsx22("span", { class: "tile tile-44 tile-gray", children: "\xD7" }),
|
|
19168
|
+
/* @__PURE__ */ jsx22("h2", { class: "title m0", children: "Page not found" }),
|
|
19169
|
+
/* @__PURE__ */ jsxs22("p", { class: "m0 sub", children: [
|
|
18124
19170
|
"Nothing answers ",
|
|
18125
|
-
/* @__PURE__ */
|
|
19171
|
+
/* @__PURE__ */ jsx22("code", { children: path2 }),
|
|
18126
19172
|
". It may have been renamed or deleted, or it may not be yours to see."
|
|
18127
19173
|
] }),
|
|
18128
|
-
/* @__PURE__ */
|
|
18129
|
-
/* @__PURE__ */
|
|
18130
|
-
/* @__PURE__ */
|
|
18131
|
-
/* @__PURE__ */
|
|
19174
|
+
/* @__PURE__ */ jsxs22("div", { class: "row", style: "gap:8px", children: [
|
|
19175
|
+
/* @__PURE__ */ jsx22("a", { class: "btn btn-primary", href: "/app/agents", children: "Agent map" }),
|
|
19176
|
+
/* @__PURE__ */ jsx22("a", { class: "btn", href: "/app", children: "Teams" }),
|
|
19177
|
+
/* @__PURE__ */ jsx22("a", { class: "btn", href: "/docs", children: "Docs" })
|
|
18132
19178
|
] })
|
|
18133
19179
|
] }) });
|
|
18134
|
-
var NotFoundPublic = ({ path: path2 }) => /* @__PURE__ */
|
|
18135
|
-
/* @__PURE__ */
|
|
18136
|
-
/* @__PURE__ */
|
|
18137
|
-
/* @__PURE__ */
|
|
18138
|
-
/* @__PURE__ */
|
|
18139
|
-
/* @__PURE__ */
|
|
18140
|
-
/* @__PURE__ */
|
|
19180
|
+
var NotFoundPublic = ({ path: path2 }) => /* @__PURE__ */ jsxs22("html", { lang: "en", children: [
|
|
19181
|
+
/* @__PURE__ */ jsx22(Head, { title: "Not found" }),
|
|
19182
|
+
/* @__PURE__ */ jsx22("body", { children: /* @__PURE__ */ jsx22("div", { class: "auth-wrap", children: /* @__PURE__ */ jsxs22("div", { class: "auth-card", children: [
|
|
19183
|
+
/* @__PURE__ */ jsx22(Logo, { lg: true }),
|
|
19184
|
+
/* @__PURE__ */ jsxs22("div", { children: [
|
|
19185
|
+
/* @__PURE__ */ jsx22("h1", { children: "Page not found" }),
|
|
19186
|
+
/* @__PURE__ */ jsxs22("p", { class: "lede", children: [
|
|
18141
19187
|
"Nothing answers ",
|
|
18142
|
-
/* @__PURE__ */
|
|
19188
|
+
/* @__PURE__ */ jsx22("code", { children: path2 }),
|
|
18143
19189
|
". If you were sent here by a link, it may have expired."
|
|
18144
19190
|
] })
|
|
18145
19191
|
] }),
|
|
18146
|
-
/* @__PURE__ */
|
|
18147
|
-
/* @__PURE__ */
|
|
19192
|
+
/* @__PURE__ */ jsx22("a", { class: "btn btn-primary", style: "width:100%;height:44px", href: "/", children: "Go to the home page" }),
|
|
19193
|
+
/* @__PURE__ */ jsxs22("p", { class: "finenote", children: [
|
|
18148
19194
|
"Already have an account? ",
|
|
18149
|
-
/* @__PURE__ */
|
|
19195
|
+
/* @__PURE__ */ jsx22("a", { href: "/login", children: "Sign in" }),
|
|
18150
19196
|
" \xB7 ",
|
|
18151
|
-
/* @__PURE__ */
|
|
19197
|
+
/* @__PURE__ */ jsx22("a", { href: "/docs", children: "Docs" })
|
|
18152
19198
|
] })
|
|
18153
19199
|
] }) }) })
|
|
18154
19200
|
] });
|
|
18155
19201
|
|
|
18156
19202
|
// src/app.tsx
|
|
18157
|
-
import { jsx as
|
|
19203
|
+
import { jsx as jsx23 } from "hono/jsx/jsx-runtime";
|
|
18158
19204
|
var originGuard = async (c, next) => {
|
|
18159
19205
|
if (c.req.method === "POST" && !c.req.path.startsWith("/mcp")) {
|
|
18160
19206
|
const origin = c.req.header("origin");
|
|
@@ -18173,7 +19219,7 @@ var originGuard = async (c, next) => {
|
|
|
18173
19219
|
await next();
|
|
18174
19220
|
};
|
|
18175
19221
|
function createApp(deps) {
|
|
18176
|
-
const app = new
|
|
19222
|
+
const app = new Hono20();
|
|
18177
19223
|
setHosted(deps.env.hosted);
|
|
18178
19224
|
app.use("*", async (c, next) => {
|
|
18179
19225
|
c.set("db", deps.db);
|
|
@@ -18254,6 +19300,8 @@ function createApp(deps) {
|
|
|
18254
19300
|
app.route("/", sessionsRoutes);
|
|
18255
19301
|
app.route("/", streamRoutes);
|
|
18256
19302
|
app.route("/", notificationsRoutes);
|
|
19303
|
+
app.route("/", policyEditorRoutes);
|
|
19304
|
+
app.route("/", projectsRoutes);
|
|
18257
19305
|
app.route("/", compareRoutes);
|
|
18258
19306
|
app.route("/", activityRoutes);
|
|
18259
19307
|
app.route("/", governanceRoutes);
|
|
@@ -18270,7 +19318,7 @@ function createApp(deps) {
|
|
|
18270
19318
|
const user = c.get("user");
|
|
18271
19319
|
if (user) await ensureRail(deps.db, user);
|
|
18272
19320
|
return c.html(
|
|
18273
|
-
user ? /* @__PURE__ */
|
|
19321
|
+
user ? /* @__PURE__ */ jsx23(NotFoundPage, { user, path: path2 }) : /* @__PURE__ */ jsx23(NotFoundPublic, { path: path2 }),
|
|
18274
19322
|
404
|
|
18275
19323
|
);
|
|
18276
19324
|
});
|
|
@@ -18304,12 +19352,12 @@ function createApp(deps) {
|
|
|
18304
19352
|
}
|
|
18305
19353
|
|
|
18306
19354
|
// src/lib/cleanup.ts
|
|
18307
|
-
import { and as
|
|
19355
|
+
import { and as and30, eq as eq39, inArray as inArray16, lt as lt4, notInArray as notInArray2, or as or8 } from "drizzle-orm";
|
|
18308
19356
|
var teamsIn = (db, plans) => db.select({ id: teams.id }).from(teams).where(
|
|
18309
19357
|
// A plan id nobody recognises resolves to `free` everywhere else, so it has
|
|
18310
19358
|
// to be swept like `free` here too — otherwise a typo in the column buys a
|
|
18311
19359
|
// team unlimited history.
|
|
18312
|
-
plans.includes("free") ? or8(
|
|
19360
|
+
plans.includes("free") ? or8(inArray16(teams.plan, [...plans]), notInArray2(teams.plan, [...PLAN_IDS])) : inArray16(teams.plan, [...plans])
|
|
18313
19361
|
);
|
|
18314
19362
|
function retentionGroups(env2) {
|
|
18315
19363
|
if (!env2.hosted) {
|
|
@@ -18339,8 +19387,8 @@ async function runCleanupOnce(db, env2) {
|
|
|
18339
19387
|
}
|
|
18340
19388
|
if (env2.sessionRetentionDays > 0) {
|
|
18341
19389
|
const purged = await db.delete(debugSessions).where(
|
|
18342
|
-
|
|
18343
|
-
|
|
19390
|
+
and30(
|
|
19391
|
+
eq39(debugSessions.status, "resolved"),
|
|
18344
19392
|
lt4(debugSessions.resolvedAt, new Date(now.getTime() - env2.sessionRetentionDays * DAY5))
|
|
18345
19393
|
)
|
|
18346
19394
|
);
|
|
@@ -18361,15 +19409,15 @@ async function runCleanupOnce(db, env2) {
|
|
|
18361
19409
|
for (const [days, scope] of retentionGroups(env2)) {
|
|
18362
19410
|
const cutoff = new Date(now.getTime() - days * DAY5);
|
|
18363
19411
|
counts.activity += rowsAffected(
|
|
18364
|
-
await db.delete(activity).where(scope ?
|
|
19412
|
+
await db.delete(activity).where(scope ? and30(lt4(activity.createdAt, cutoff), inArray16(activity.teamId, teamsIn(db, scope))) : lt4(activity.createdAt, cutoff))
|
|
18365
19413
|
);
|
|
18366
19414
|
counts.agentEvents += rowsAffected(
|
|
18367
19415
|
await db.delete(agentEvents).where(
|
|
18368
|
-
scope ?
|
|
19416
|
+
scope ? and30(
|
|
18369
19417
|
lt4(agentEvents.createdAt, cutoff),
|
|
18370
|
-
|
|
19418
|
+
inArray16(
|
|
18371
19419
|
agentEvents.runId,
|
|
18372
|
-
db.select({ id: agentRuns.id }).from(agentRuns).where(
|
|
19420
|
+
db.select({ id: agentRuns.id }).from(agentRuns).where(inArray16(agentRuns.teamId, teamsIn(db, scope)))
|
|
18373
19421
|
)
|
|
18374
19422
|
) : lt4(agentEvents.createdAt, cutoff)
|
|
18375
19423
|
)
|