@mgsoftwarebv/mg-dashboard-mcp 7.4.13 → 7.4.15
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 +915 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import crypto, { randomUUID, createHash, randomBytes, createCipheriv, createDeci
|
|
|
13
13
|
import { readFile, mkdtemp, writeFile, rm } from 'fs/promises';
|
|
14
14
|
import { createServer } from 'http';
|
|
15
15
|
import { tmpdir } from 'os';
|
|
16
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
16
17
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
17
18
|
import postgres from 'postgres';
|
|
18
19
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -454,6 +455,12 @@ var init_db_ssh_tunnel = __esm({
|
|
|
454
455
|
"src/db-ssh-tunnel.ts"() {
|
|
455
456
|
}
|
|
456
457
|
});
|
|
458
|
+
new AsyncLocalStorage();
|
|
459
|
+
var reportingContext = new AsyncLocalStorage();
|
|
460
|
+
function maybeReportSlowQuery(durationMs, query) {
|
|
461
|
+
if (reportingContext.getStore()) return;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
457
464
|
function getConnectionConfig() {
|
|
458
465
|
return {
|
|
459
466
|
prepare: false,
|
|
@@ -490,7 +497,20 @@ function getPool() {
|
|
|
490
497
|
}
|
|
491
498
|
function getDb() {
|
|
492
499
|
if (!_db) {
|
|
493
|
-
|
|
500
|
+
const raw = drizzle(getPool(), { casing: "snake_case" });
|
|
501
|
+
const originalExecute = raw.execute.bind(raw);
|
|
502
|
+
raw.execute = ((query) => {
|
|
503
|
+
const start = performance.now();
|
|
504
|
+
const result = originalExecute(query);
|
|
505
|
+
if (result && typeof result.then === "function") {
|
|
506
|
+
return result.finally(() => {
|
|
507
|
+
maybeReportSlowQuery(performance.now() - start);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
maybeReportSlowQuery(performance.now() - start);
|
|
511
|
+
return result;
|
|
512
|
+
});
|
|
513
|
+
_db = raw;
|
|
494
514
|
}
|
|
495
515
|
return _db;
|
|
496
516
|
}
|
|
@@ -703,10 +723,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
|
|
|
703
723
|
"trigger-env": "settings"
|
|
704
724
|
};
|
|
705
725
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
706
|
-
const
|
|
726
|
+
const sql31 = `SELECT re.\\"apiKey\\" || '~~' || p.\\"externalRef\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
|
|
707
727
|
const cmd = [
|
|
708
728
|
`PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
|
|
709
|
-
`ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
729
|
+
`ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql31}" 2>/dev/null | tr -d '[:space:]')`,
|
|
710
730
|
'echo "$PORT|$ROW"'
|
|
711
731
|
].join(" && ");
|
|
712
732
|
const result = await sshExec2(conn, cmd, proxy);
|
|
@@ -728,8 +748,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
728
748
|
return { port, apiKey: apiKey2, projectRef: projectRef || "" };
|
|
729
749
|
}
|
|
730
750
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
731
|
-
const
|
|
732
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
751
|
+
const sql31 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
|
|
752
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql31}" 2>/dev/null`;
|
|
733
753
|
const result = await sshExec2(conn, cmd, proxy);
|
|
734
754
|
const output = result.stdout.trim();
|
|
735
755
|
if (!output) return "";
|
|
@@ -811,8 +831,8 @@ async function handleTriggerTool(name, args2, deps) {
|
|
|
811
831
|
switch (name) {
|
|
812
832
|
// -----------------------------------------------------------------
|
|
813
833
|
case "trigger-list": {
|
|
814
|
-
const
|
|
815
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
834
|
+
const sql31 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
|
|
835
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql31}" 2>/dev/null`;
|
|
816
836
|
const result = await sshExec2(conn, cmd, proxy);
|
|
817
837
|
const output = result.stdout.trim();
|
|
818
838
|
if (!output) {
|
|
@@ -990,9 +1010,9 @@ ${raw2.substring(0, 500)}` }] };
|
|
|
990
1010
|
return { content: [{ type: "text", text: `No environment variables for ${project}/${env}.` }] };
|
|
991
1011
|
}
|
|
992
1012
|
const lines = vars.map((v) => `${v.isSecret ? "[secret]" : " "} ${v.name}`).sort();
|
|
993
|
-
const
|
|
1013
|
+
const text22 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
|
|
994
1014
|
` + "-".repeat(50) + "\n" + lines.join("\n");
|
|
995
|
-
return { content: [{ type: "text", text:
|
|
1015
|
+
return { content: [{ type: "text", text: text22 }] };
|
|
996
1016
|
}
|
|
997
1017
|
if (action === "get") {
|
|
998
1018
|
const key2 = String(args2.key ?? "");
|
|
@@ -1079,9 +1099,9 @@ async function fetchAndFormatRun(conn, proxy, sshExec2, instance, runId) {
|
|
|
1079
1099
|
return { content: [{ type: "text", text: `Invalid API response:
|
|
1080
1100
|
${rawJson.substring(0, 500)}` }] };
|
|
1081
1101
|
}
|
|
1082
|
-
let
|
|
1083
|
-
if (logs)
|
|
1084
|
-
return { content: [{ type: "text", text:
|
|
1102
|
+
let text22 = formatRunDetail(run);
|
|
1103
|
+
if (logs) text22 += "\n\n--- Logs ---\n" + logs;
|
|
1104
|
+
return { content: [{ type: "text", text: text22 }] };
|
|
1085
1105
|
}
|
|
1086
1106
|
async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSeconds) {
|
|
1087
1107
|
const pollInterval = 3e3;
|
|
@@ -1103,10 +1123,10 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
|
|
|
1103
1123
|
continue;
|
|
1104
1124
|
}
|
|
1105
1125
|
if (TERMINAL_STATUSES.has(run.status)) {
|
|
1106
|
-
let
|
|
1126
|
+
let text22 = formatRunDetail(run);
|
|
1107
1127
|
const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
|
|
1108
|
-
if (logs)
|
|
1109
|
-
return { content: [{ type: "text", text:
|
|
1128
|
+
if (logs) text22 += "\n\n--- Logs ---\n" + logs;
|
|
1129
|
+
return { content: [{ type: "text", text: text22 }] };
|
|
1110
1130
|
}
|
|
1111
1131
|
}
|
|
1112
1132
|
return {
|
|
@@ -1116,6 +1136,128 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
|
|
|
1116
1136
|
}]
|
|
1117
1137
|
};
|
|
1118
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
// src/github-code-access-tools.ts
|
|
1141
|
+
var GITHUB_CODE_ACCESS_TOOLS = [
|
|
1142
|
+
{
|
|
1143
|
+
name: "get_mg_dashboard_file",
|
|
1144
|
+
description: "Read ONE source file at a git ref for an allowlisted GitHub org/repo (CODE_BATTLE_GITHUB_ORGS, default MGSoftwareBV). Use after get_mg_dashboard_commits when commit subjects are not enough to judge exists / polish / new. Returns metadata + text content (secrets redacted). Credential paths (.env, keys) are denied. Binary files return { binary: true } with no payload. Full diffs are never attached to get_mg_dashboard_commits \u2014 use get_mg_dashboard_diff for a compact patch.",
|
|
1145
|
+
inputSchema: {
|
|
1146
|
+
type: "object",
|
|
1147
|
+
properties: {
|
|
1148
|
+
repository: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
description: "owner/repo, e.g. MGSoftwareBV/mg-dashboard. Org must be allowlisted."
|
|
1151
|
+
},
|
|
1152
|
+
ref: {
|
|
1153
|
+
type: "string",
|
|
1154
|
+
description: "Commit sha, branch, or tag."
|
|
1155
|
+
},
|
|
1156
|
+
path: {
|
|
1157
|
+
type: "string",
|
|
1158
|
+
description: "File path inside the repo (not a directory)."
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
required: ["repository", "ref", "path"]
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
name: "get_mg_dashboard_diff",
|
|
1166
|
+
description: "Fetch a COMPACT unified diff for one commit sha XOR one pull request number, same org allowlist as get_mg_dashboard_file. Returns per-file path/status/+/- and a truncated patch (secrets redacted). Binary files are omitted. Does not change get_mg_dashboard_commits (metadata-only by design).",
|
|
1167
|
+
inputSchema: {
|
|
1168
|
+
type: "object",
|
|
1169
|
+
properties: {
|
|
1170
|
+
repository: {
|
|
1171
|
+
type: "string",
|
|
1172
|
+
description: "owner/repo, e.g. MGSoftwareBV/mg-dashboard. Org must be allowlisted."
|
|
1173
|
+
},
|
|
1174
|
+
sha: {
|
|
1175
|
+
type: "string",
|
|
1176
|
+
description: "Commit sha. Provide exactly one of sha or pullNumber."
|
|
1177
|
+
},
|
|
1178
|
+
pullNumber: {
|
|
1179
|
+
type: "number",
|
|
1180
|
+
description: "Pull request number. Provide exactly one of sha or pullNumber."
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
required: ["repository"]
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
];
|
|
1187
|
+
async function handleGithubCodeAccessTool(name, args2, ctx) {
|
|
1188
|
+
if (name === "get_mg_dashboard_file") {
|
|
1189
|
+
const repository = typeof args2.repository === "string" ? args2.repository.trim() : "";
|
|
1190
|
+
const ref = typeof args2.ref === "string" ? args2.ref.trim() : "";
|
|
1191
|
+
const path = typeof args2.path === "string" ? args2.path.trim() : "";
|
|
1192
|
+
if (!repository || !ref || !path) {
|
|
1193
|
+
return {
|
|
1194
|
+
content: [
|
|
1195
|
+
{
|
|
1196
|
+
type: "text",
|
|
1197
|
+
text: "Error: repository, ref and path are required"
|
|
1198
|
+
}
|
|
1199
|
+
]
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
return proxyJson(ctx, "/api/activity/file", { repository, ref, path });
|
|
1203
|
+
}
|
|
1204
|
+
if (name === "get_mg_dashboard_diff") {
|
|
1205
|
+
const repository = typeof args2.repository === "string" ? args2.repository.trim() : "";
|
|
1206
|
+
if (!repository) {
|
|
1207
|
+
return {
|
|
1208
|
+
content: [
|
|
1209
|
+
{ type: "text", text: "Error: repository is required" }
|
|
1210
|
+
]
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
const sha = typeof args2.sha === "string" ? args2.sha.trim() : void 0;
|
|
1214
|
+
const pullNumber = typeof args2.pullNumber === "number" ? args2.pullNumber : void 0;
|
|
1215
|
+
const hasSha = Boolean(sha);
|
|
1216
|
+
const hasPr = pullNumber != null;
|
|
1217
|
+
if (hasSha === hasPr) {
|
|
1218
|
+
return {
|
|
1219
|
+
content: [
|
|
1220
|
+
{
|
|
1221
|
+
type: "text",
|
|
1222
|
+
text: "Error: provide exactly one of sha or pullNumber"
|
|
1223
|
+
}
|
|
1224
|
+
]
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
return proxyJson(ctx, "/api/activity/diff", {
|
|
1228
|
+
repository,
|
|
1229
|
+
...hasSha ? { sha } : { pullNumber }
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
content: [{ type: "text", text: `Error: unknown tool ${name}` }]
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
async function proxyJson(ctx, route, body) {
|
|
1237
|
+
const res = await fetch(`${ctx.dashboardBaseUrl}${route}`, {
|
|
1238
|
+
method: "POST",
|
|
1239
|
+
headers: {
|
|
1240
|
+
"content-type": "application/json",
|
|
1241
|
+
authorization: `Bearer ${ctx.apiKey}`
|
|
1242
|
+
},
|
|
1243
|
+
body: JSON.stringify(body)
|
|
1244
|
+
});
|
|
1245
|
+
if (!res.ok) {
|
|
1246
|
+
const detail = await res.text().catch(() => "");
|
|
1247
|
+
return {
|
|
1248
|
+
content: [
|
|
1249
|
+
{
|
|
1250
|
+
type: "text",
|
|
1251
|
+
text: `Error: mg-dashboard ${route} failed (${res.status}). ${detail.slice(0, 300)}`
|
|
1252
|
+
}
|
|
1253
|
+
]
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
const data = await res.json();
|
|
1257
|
+
return {
|
|
1258
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1119
1261
|
var ALGORITHM = "aes-256-gcm";
|
|
1120
1262
|
var IV_LENGTH = 16;
|
|
1121
1263
|
var AUTH_TAG_LENGTH = 16;
|
|
@@ -3793,10 +3935,10 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
3793
3935
|
// }) as any;
|
|
3794
3936
|
// return merged;
|
|
3795
3937
|
// }
|
|
3796
|
-
catchall(
|
|
3938
|
+
catchall(index19) {
|
|
3797
3939
|
return new _ZodObject({
|
|
3798
3940
|
...this._def,
|
|
3799
|
-
catchall:
|
|
3941
|
+
catchall: index19
|
|
3800
3942
|
});
|
|
3801
3943
|
}
|
|
3802
3944
|
pick(mask) {
|
|
@@ -4114,9 +4256,9 @@ function mergeValues(a, b) {
|
|
|
4114
4256
|
return { valid: false };
|
|
4115
4257
|
}
|
|
4116
4258
|
const newArray = [];
|
|
4117
|
-
for (let
|
|
4118
|
-
const itemA = a[
|
|
4119
|
-
const itemB = b[
|
|
4259
|
+
for (let index19 = 0; index19 < a.length; index19++) {
|
|
4260
|
+
const itemA = a[index19];
|
|
4261
|
+
const itemB = b[index19];
|
|
4120
4262
|
const sharedValue = mergeValues(itemA, itemB);
|
|
4121
4263
|
if (!sharedValue.valid) {
|
|
4122
4264
|
return { valid: false };
|
|
@@ -4322,10 +4464,10 @@ var ZodMap = class extends ZodType {
|
|
|
4322
4464
|
}
|
|
4323
4465
|
const keyType = this._def.keyType;
|
|
4324
4466
|
const valueType = this._def.valueType;
|
|
4325
|
-
const pairs = [...ctx.data.entries()].map(([key, value],
|
|
4467
|
+
const pairs = [...ctx.data.entries()].map(([key, value], index19) => {
|
|
4326
4468
|
return {
|
|
4327
|
-
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [
|
|
4328
|
-
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [
|
|
4469
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index19, "key"])),
|
|
4470
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index19, "value"]))
|
|
4329
4471
|
};
|
|
4330
4472
|
});
|
|
4331
4473
|
if (ctx.common.async) {
|
|
@@ -5340,13 +5482,19 @@ pgTable(
|
|
|
5340
5482
|
verifyNote: text("verify_note"),
|
|
5341
5483
|
dirty: boolean("dirty").notNull().default(false),
|
|
5342
5484
|
vaultSyncedAt: timestamp("vault_synced_at", { withTimezone: true }),
|
|
5485
|
+
/** Reviewer / itemized-phase lease holder. */
|
|
5486
|
+
claimedBy: text("claimed_by"),
|
|
5487
|
+
claimedAt: timestamp("claimed_at", { withTimezone: true }),
|
|
5488
|
+
/** Expired leases are reclaimable by any runner. */
|
|
5489
|
+
leaseExpiresAt: timestamp("lease_expires_at", { withTimezone: true }),
|
|
5343
5490
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5344
5491
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5345
5492
|
},
|
|
5346
5493
|
(table) => [
|
|
5347
5494
|
index("idx_wiki_page_status").on(table.status),
|
|
5348
5495
|
index("idx_wiki_page_last_verified").on(table.lastVerifiedOn),
|
|
5349
|
-
index("idx_wiki_page_dirty").on(table.dirty)
|
|
5496
|
+
index("idx_wiki_page_dirty").on(table.dirty),
|
|
5497
|
+
index("idx_wiki_page_lease_expires").on(table.leaseExpiresAt).where(sql`${table.leaseExpiresAt} IS NOT NULL`)
|
|
5350
5498
|
]
|
|
5351
5499
|
);
|
|
5352
5500
|
pgTable(
|
|
@@ -5391,11 +5539,9 @@ pgTable(
|
|
|
5391
5539
|
resolvedAt: timestamp("resolved_at", { withTimezone: true })
|
|
5392
5540
|
},
|
|
5393
5541
|
(table) => [
|
|
5394
|
-
uniqueIndex(
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
table.evidenceRef
|
|
5398
|
-
),
|
|
5542
|
+
uniqueIndex(
|
|
5543
|
+
"wiki_page_evidence_page_slug_evidence_type_evidence_ref_key"
|
|
5544
|
+
).on(table.pageSlug, table.evidenceType, table.evidenceRef),
|
|
5399
5545
|
index("idx_wiki_page_evidence_created").on(table.createdAt)
|
|
5400
5546
|
]
|
|
5401
5547
|
);
|
|
@@ -5450,6 +5596,65 @@ pgTable(
|
|
|
5450
5596
|
)
|
|
5451
5597
|
]
|
|
5452
5598
|
);
|
|
5599
|
+
pgTable(
|
|
5600
|
+
"wiki_invariant_violation",
|
|
5601
|
+
{
|
|
5602
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5603
|
+
slug: text("slug").notNull(),
|
|
5604
|
+
code: text("code").notNull(),
|
|
5605
|
+
severity: text("severity").notNull().default("warning"),
|
|
5606
|
+
detail: text("detail").notNull().default(""),
|
|
5607
|
+
suggestedFix: text("suggested_fix"),
|
|
5608
|
+
suggestedSlug: text("suggested_slug"),
|
|
5609
|
+
occurrenceCount: integer("occurrence_count").notNull().default(1),
|
|
5610
|
+
reopenCount: integer("reopen_count").notNull().default(0),
|
|
5611
|
+
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5612
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5613
|
+
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
5614
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5615
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5616
|
+
},
|
|
5617
|
+
(table) => [
|
|
5618
|
+
uniqueIndex("wiki_invariant_violation_slug_code_key").on(
|
|
5619
|
+
table.slug,
|
|
5620
|
+
table.code
|
|
5621
|
+
),
|
|
5622
|
+
index("idx_wiki_invariant_violation_severity").on(
|
|
5623
|
+
table.severity,
|
|
5624
|
+
table.resolvedAt
|
|
5625
|
+
)
|
|
5626
|
+
]
|
|
5627
|
+
);
|
|
5628
|
+
pgTable(
|
|
5629
|
+
"wiki_pipeline_rule",
|
|
5630
|
+
{
|
|
5631
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5632
|
+
scope: text("scope").notNull(),
|
|
5633
|
+
ruleKey: text("rule_key").notNull(),
|
|
5634
|
+
text: text("text").notNull(),
|
|
5635
|
+
/** A = deterministic auto-applied, B = judgement, gated by insight accept. */
|
|
5636
|
+
tier: text("tier").notNull().default("B"),
|
|
5637
|
+
status: text("status").notNull().default("proposed"),
|
|
5638
|
+
sourceCode: text("source_code"),
|
|
5639
|
+
evidenceRefs: text("evidence_refs").array().notNull().default([]),
|
|
5640
|
+
violationCount: integer("violation_count").notNull().default(0),
|
|
5641
|
+
lastViolationAt: timestamp("last_violation_at", { withTimezone: true }),
|
|
5642
|
+
activatedAt: timestamp("activated_at", { withTimezone: true }),
|
|
5643
|
+
retiredAt: timestamp("retired_at", { withTimezone: true }),
|
|
5644
|
+
retiredReason: text("retired_reason"),
|
|
5645
|
+
rejectedAt: timestamp("rejected_at", { withTimezone: true }),
|
|
5646
|
+
rejectedReason: text("rejected_reason"),
|
|
5647
|
+
approvedBy: text("approved_by"),
|
|
5648
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5649
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5650
|
+
},
|
|
5651
|
+
(table) => [
|
|
5652
|
+
uniqueIndex("wiki_pipeline_rule_scope_key_key").on(
|
|
5653
|
+
table.scope,
|
|
5654
|
+
table.ruleKey
|
|
5655
|
+
)
|
|
5656
|
+
]
|
|
5657
|
+
);
|
|
5453
5658
|
pgTable(
|
|
5454
5659
|
"wiki_search_log",
|
|
5455
5660
|
{
|
|
@@ -5477,6 +5682,69 @@ pgTable(
|
|
|
5477
5682
|
},
|
|
5478
5683
|
(table) => [index("idx_team_memory_search_log_created").on(table.createdAt)]
|
|
5479
5684
|
);
|
|
5685
|
+
pgTable(
|
|
5686
|
+
"wiki_agent_run",
|
|
5687
|
+
{
|
|
5688
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5689
|
+
phase: text("phase").notNull(),
|
|
5690
|
+
model: text("model"),
|
|
5691
|
+
claimed: jsonb("claimed").$type(),
|
|
5692
|
+
proven: jsonb("proven").$type(),
|
|
5693
|
+
violationCount: integer("violation_count").notNull().default(0),
|
|
5694
|
+
violations: jsonb("violations").$type(),
|
|
5695
|
+
outcome: text("outcome").notNull().default("ok"),
|
|
5696
|
+
agentId: text("agent_id"),
|
|
5697
|
+
runId: text("run_id"),
|
|
5698
|
+
passId: text("pass_id"),
|
|
5699
|
+
changedPaths: jsonb("changed_paths").$type(),
|
|
5700
|
+
summary: text("summary"),
|
|
5701
|
+
userVerdict: text("user_verdict"),
|
|
5702
|
+
userVerdictNote: text("user_verdict_note"),
|
|
5703
|
+
verdictAt: timestamp("verdict_at", { withTimezone: true }),
|
|
5704
|
+
reviewed: boolean("reviewed").notNull().default(false),
|
|
5705
|
+
reviewNotes: text("review_notes"),
|
|
5706
|
+
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
5707
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
5708
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5709
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5710
|
+
},
|
|
5711
|
+
(table) => [
|
|
5712
|
+
index("idx_wiki_agent_run_phase_created").on(table.phase, table.createdAt),
|
|
5713
|
+
index("idx_wiki_agent_run_created").on(table.createdAt),
|
|
5714
|
+
index("idx_wiki_agent_run_reviewed").on(table.reviewed),
|
|
5715
|
+
index("idx_wiki_agent_run_user_verdict").on(table.userVerdict),
|
|
5716
|
+
index("idx_wiki_agent_run_pass_id").on(table.passId)
|
|
5717
|
+
]
|
|
5718
|
+
);
|
|
5719
|
+
pgTable(
|
|
5720
|
+
"wiki_agent_review_suggestion",
|
|
5721
|
+
{
|
|
5722
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5723
|
+
status: text("status").notNull().default("proposed"),
|
|
5724
|
+
implementationClass: text("implementation_class").notNull(),
|
|
5725
|
+
phase: text("phase"),
|
|
5726
|
+
scope: text("scope"),
|
|
5727
|
+
title: text("title").notNull(),
|
|
5728
|
+
summary: text("summary").notNull(),
|
|
5729
|
+
targetFile: text("target_file"),
|
|
5730
|
+
targetKey: text("target_key"),
|
|
5731
|
+
beforeText: text("before_text"),
|
|
5732
|
+
afterText: text("after_text"),
|
|
5733
|
+
evidenceRunIds: uuid("evidence_run_ids").array().notNull().default([]),
|
|
5734
|
+
evidenceRefs: text("evidence_refs").array().notNull().default([]),
|
|
5735
|
+
dedupeKey: text("dedupe_key").notNull(),
|
|
5736
|
+
source: text("source").notNull().default("auto"),
|
|
5737
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5738
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5739
|
+
},
|
|
5740
|
+
(table) => [
|
|
5741
|
+
uniqueIndex("wiki_agent_review_suggestion_dedupe_key").on(table.dedupeKey),
|
|
5742
|
+
index("idx_wiki_agent_review_suggestion_status_created").on(
|
|
5743
|
+
table.status,
|
|
5744
|
+
table.createdAt
|
|
5745
|
+
)
|
|
5746
|
+
]
|
|
5747
|
+
);
|
|
5480
5748
|
|
|
5481
5749
|
// ../db/src/schema/agent-memory.ts
|
|
5482
5750
|
pgTable(
|
|
@@ -5521,6 +5789,88 @@ pgTable(
|
|
|
5521
5789
|
index("idx_agent_memory_category").on(table.category)
|
|
5522
5790
|
]
|
|
5523
5791
|
);
|
|
5792
|
+
var agentWorldAgent = pgTable(
|
|
5793
|
+
"agent_world_agent",
|
|
5794
|
+
{
|
|
5795
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5796
|
+
name: text("name").notNull(),
|
|
5797
|
+
color: text("color").notNull(),
|
|
5798
|
+
/** Personality sliders 0–100: curiosity, sociability, diligence, calm. */
|
|
5799
|
+
personality: jsonb("personality").$type().notNull().default({
|
|
5800
|
+
curiosity: 50,
|
|
5801
|
+
sociability: 50,
|
|
5802
|
+
diligence: 50,
|
|
5803
|
+
calm: 50
|
|
5804
|
+
}),
|
|
5805
|
+
/** Needs 0–100: higher = more urgent. */
|
|
5806
|
+
needs: jsonb("needs").$type().notNull().default({ energy: 40, coffee: 30, focus: 35, social: 25 }),
|
|
5807
|
+
position: jsonb("position").$type().notNull().default({ x: 0, y: 0, z: 0 }),
|
|
5808
|
+
target: jsonb("target").$type(),
|
|
5809
|
+
activity: text("activity").notNull().default("idle"),
|
|
5810
|
+
waypoint: text("waypoint"),
|
|
5811
|
+
memories: jsonb("memories").$type().notNull().default([]),
|
|
5812
|
+
diaryCount: integer("diary_count").notNull().default(0),
|
|
5813
|
+
lastSay: text("last_say"),
|
|
5814
|
+
lastSayAt: timestamp("last_say_at", { withTimezone: true }),
|
|
5815
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
5816
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5817
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5818
|
+
},
|
|
5819
|
+
(table) => [index("idx_agent_world_agent_sort").on(table.sortOrder)]
|
|
5820
|
+
);
|
|
5821
|
+
pgTable(
|
|
5822
|
+
"agent_world_event",
|
|
5823
|
+
{
|
|
5824
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5825
|
+
/** say | diary | think | god | activity | memo | system */
|
|
5826
|
+
type: text("type").notNull(),
|
|
5827
|
+
agentId: uuid("agent_id").references(() => agentWorldAgent.id, {
|
|
5828
|
+
onDelete: "set null"
|
|
5829
|
+
}),
|
|
5830
|
+
message: text("message").notNull(),
|
|
5831
|
+
meta: jsonb("meta").$type().notNull().default({}),
|
|
5832
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5833
|
+
},
|
|
5834
|
+
(table) => [
|
|
5835
|
+
index("idx_agent_world_event_created").on(table.createdAt),
|
|
5836
|
+
index("idx_agent_world_event_type").on(table.type),
|
|
5837
|
+
index("idx_agent_world_event_agent").on(table.agentId),
|
|
5838
|
+
// Diary / per-agent feed: WHERE agent_id ORDER BY created_at DESC LIMIT N
|
|
5839
|
+
index("idx_agent_world_event_agent_created").on(
|
|
5840
|
+
table.agentId,
|
|
5841
|
+
table.createdAt
|
|
5842
|
+
)
|
|
5843
|
+
]
|
|
5844
|
+
);
|
|
5845
|
+
pgTable(
|
|
5846
|
+
"agent_world_memo",
|
|
5847
|
+
{
|
|
5848
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5849
|
+
agentId: uuid("agent_id").references(() => agentWorldAgent.id, {
|
|
5850
|
+
onDelete: "set null"
|
|
5851
|
+
}),
|
|
5852
|
+
title: text("title").notNull(),
|
|
5853
|
+
content: text("content").notNull(),
|
|
5854
|
+
location: text("location").notNull().default("whiteboard"),
|
|
5855
|
+
reads: integer("reads").notNull().default(0),
|
|
5856
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5857
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5858
|
+
},
|
|
5859
|
+
(table) => [
|
|
5860
|
+
index("idx_agent_world_memo_created").on(table.createdAt),
|
|
5861
|
+
index("idx_agent_world_memo_location").on(table.location)
|
|
5862
|
+
]
|
|
5863
|
+
);
|
|
5864
|
+
pgTable("agent_world_state", {
|
|
5865
|
+
id: text("id").primaryKey().default("world"),
|
|
5866
|
+
tick: integer("tick").notNull().default(0),
|
|
5867
|
+
/** Simulated minutes since day start (0–1439 wraps). */
|
|
5868
|
+
simMinutes: integer("sim_minutes").notNull().default(540),
|
|
5869
|
+
paused: boolean("paused").notNull().default(false),
|
|
5870
|
+
lastThinkAt: timestamp("last_think_at", { withTimezone: true }),
|
|
5871
|
+
lastThinkAgentId: uuid("last_think_agent_id"),
|
|
5872
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5873
|
+
});
|
|
5524
5874
|
var contentSourceType = pgEnum("content_source_type", [
|
|
5525
5875
|
"own_site",
|
|
5526
5876
|
"client_site",
|
|
@@ -6086,7 +6436,11 @@ pgTable(
|
|
|
6086
6436
|
table.resourceId,
|
|
6087
6437
|
table.metric,
|
|
6088
6438
|
table.sampledAt
|
|
6089
|
-
)
|
|
6439
|
+
),
|
|
6440
|
+
// Retention cleanup (monitoring-sample-retention-cleanup) filters only on
|
|
6441
|
+
// sampled_at. Without this index Postgres seq-scans the full firehose.
|
|
6442
|
+
// Live DB name uses idx_ prefix (see migration 20260731070000).
|
|
6443
|
+
index("idx_monitoring_sample_sampled_at").on(table.sampledAt)
|
|
6090
6444
|
]
|
|
6091
6445
|
);
|
|
6092
6446
|
var appLogSource = pgTable(
|
|
@@ -6420,7 +6774,8 @@ var directorySubmissionStatus = pgEnum(
|
|
|
6420
6774
|
"rejected",
|
|
6421
6775
|
"failed",
|
|
6422
6776
|
"manual",
|
|
6423
|
-
"captcha"
|
|
6777
|
+
"captcha",
|
|
6778
|
+
"skipped"
|
|
6424
6779
|
]
|
|
6425
6780
|
);
|
|
6426
6781
|
var directoryAccountStatus = pgEnum("directory_account_status", [
|
|
@@ -6444,6 +6799,13 @@ var linkDirectory = pgTable(
|
|
|
6444
6799
|
automationLevel: directoryAutomationLevel("automation_level").notNull().default("auto"),
|
|
6445
6800
|
enabled: boolean("enabled").notNull().default(false),
|
|
6446
6801
|
formMapping: jsonb("form_mapping").$type(),
|
|
6802
|
+
/** Set when a submission for this directory reached live/submitted end-to-end. */
|
|
6803
|
+
provenAt: timestamp("proven_at", { withTimezone: true }),
|
|
6804
|
+
successCount: integer("success_count").notNull().default(0),
|
|
6805
|
+
failCount: integer("fail_count").notNull().default(0),
|
|
6806
|
+
/** Hard captcha (even after residential) — disable after CAPTCHA_STRIKE_LIMIT. */
|
|
6807
|
+
captchaStrike: integer("captcha_strike").notNull().default(0),
|
|
6808
|
+
lastOutcome: text("last_outcome"),
|
|
6447
6809
|
lastCheckedAt: timestamp("last_checked_at", { withTimezone: true }),
|
|
6448
6810
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6449
6811
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -6451,7 +6813,8 @@ var linkDirectory = pgTable(
|
|
|
6451
6813
|
(table) => [
|
|
6452
6814
|
uniqueIndex("link_directory_submit_url_uidx").on(table.submitUrl),
|
|
6453
6815
|
index("link_directory_enabled_dr_idx").on(table.enabled, table.dr),
|
|
6454
|
-
index("link_directory_region_idx").on(table.region)
|
|
6816
|
+
index("link_directory_region_idx").on(table.region),
|
|
6817
|
+
index("link_directory_proven_at_idx").on(table.provenAt)
|
|
6455
6818
|
]
|
|
6456
6819
|
);
|
|
6457
6820
|
var linkBuildingClient = pgTable(
|
|
@@ -6463,19 +6826,26 @@ var linkBuildingClient = pgTable(
|
|
|
6463
6826
|
mailboxAddress: text("mailbox_address"),
|
|
6464
6827
|
refrontMailAccountId: text("refront_mail_account_id"),
|
|
6465
6828
|
profile: jsonb("profile").$type().notNull().default({}),
|
|
6466
|
-
weeklyPace: integer("weekly_pace").notNull().default(
|
|
6829
|
+
weeklyPace: integer("weekly_pace").notNull().default(35),
|
|
6467
6830
|
/** Target package size (number of directory backlinks sold). */
|
|
6468
6831
|
packageSize: integer("package_size").notNull().default(100),
|
|
6469
|
-
autoSubmit: boolean("auto_submit").notNull().default(
|
|
6832
|
+
autoSubmit: boolean("auto_submit").notNull().default(true),
|
|
6470
6833
|
startingDr: doublePrecision("starting_dr"),
|
|
6471
6834
|
currentDr: doublePrecision("current_dr"),
|
|
6472
6835
|
isActive: boolean("is_active").notNull().default(true),
|
|
6836
|
+
/** Opaque token for public customer progress reports (backlinking.eu). */
|
|
6837
|
+
reportToken: text("report_token"),
|
|
6838
|
+
/** External shop order id from backlinking.eu checkout. */
|
|
6839
|
+
shopOrderId: text("shop_order_id"),
|
|
6840
|
+
shopCustomerEmail: text("shop_customer_email"),
|
|
6473
6841
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6474
6842
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6475
6843
|
},
|
|
6476
6844
|
(table) => [
|
|
6477
6845
|
uniqueIndex("link_building_client_website_uidx").on(table.websiteUrl),
|
|
6478
|
-
index("link_building_client_active_idx").on(table.isActive)
|
|
6846
|
+
index("link_building_client_active_idx").on(table.isActive),
|
|
6847
|
+
uniqueIndex("link_building_client_report_token_uidx").on(table.reportToken),
|
|
6848
|
+
index("link_building_client_shop_order_idx").on(table.shopOrderId)
|
|
6479
6849
|
]
|
|
6480
6850
|
);
|
|
6481
6851
|
var directorySubmission = pgTable(
|
|
@@ -6583,6 +6953,78 @@ pgTable(
|
|
|
6583
6953
|
)
|
|
6584
6954
|
]
|
|
6585
6955
|
);
|
|
6956
|
+
var networkListingStatus = pgEnum("network_listing_status", [
|
|
6957
|
+
"pending",
|
|
6958
|
+
"queued",
|
|
6959
|
+
"placing",
|
|
6960
|
+
"live",
|
|
6961
|
+
"failed",
|
|
6962
|
+
"expired",
|
|
6963
|
+
"cancelled"
|
|
6964
|
+
]);
|
|
6965
|
+
var networkSite = pgTable(
|
|
6966
|
+
"network_site",
|
|
6967
|
+
{
|
|
6968
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6969
|
+
name: text("name").notNull(),
|
|
6970
|
+
domain: text("domain").notNull(),
|
|
6971
|
+
websiteUrl: text("website_url").notNull(),
|
|
6972
|
+
dr: integer("dr").notNull().default(0),
|
|
6973
|
+
theme: text("theme").notNull().default("general"),
|
|
6974
|
+
/** Yearly listing price in euro cents. */
|
|
6975
|
+
priceYearCents: integer("price_year_cents").notNull().default(9900),
|
|
6976
|
+
categories: jsonb("categories").$type().notNull().default([]),
|
|
6977
|
+
/** GitHub repo for placement commits, e.g. MGSoftwareBV/catalogcompany.nl */
|
|
6978
|
+
repoFullName: text("repo_full_name"),
|
|
6979
|
+
/** Path in repo for partner/listing data (relative). */
|
|
6980
|
+
listingDataPath: text("listing_data_path"),
|
|
6981
|
+
dofollow: boolean("dofollow").notNull().default(true),
|
|
6982
|
+
enabled: boolean("enabled").notNull().default(false),
|
|
6983
|
+
maxListings: integer("max_listings").notNull().default(50),
|
|
6984
|
+
notes: text("notes"),
|
|
6985
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6986
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6987
|
+
},
|
|
6988
|
+
(table) => [
|
|
6989
|
+
uniqueIndex("network_site_domain_uidx").on(table.domain),
|
|
6990
|
+
index("network_site_enabled_dr_idx").on(table.enabled, table.dr),
|
|
6991
|
+
index("network_site_theme_idx").on(table.theme)
|
|
6992
|
+
]
|
|
6993
|
+
);
|
|
6994
|
+
pgTable(
|
|
6995
|
+
"network_listing",
|
|
6996
|
+
{
|
|
6997
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6998
|
+
siteId: uuid("site_id").notNull().references(() => networkSite.id, { onDelete: "cascade" }),
|
|
6999
|
+
clientId: uuid("client_id").references(() => linkBuildingClient.id, {
|
|
7000
|
+
onDelete: "set null"
|
|
7001
|
+
}),
|
|
7002
|
+
customerName: text("customer_name").notNull(),
|
|
7003
|
+
customerEmail: text("customer_email").notNull(),
|
|
7004
|
+
targetUrl: text("target_url").notNull(),
|
|
7005
|
+
anchorText: text("anchor_text").notNull(),
|
|
7006
|
+
categorySlug: text("category_slug").notNull().default("general"),
|
|
7007
|
+
status: networkListingStatus("status").notNull().default("pending"),
|
|
7008
|
+
listingUrl: text("listing_url"),
|
|
7009
|
+
/** External shop order reference from backlinking.eu */
|
|
7010
|
+
shopOrderId: text("shop_order_id"),
|
|
7011
|
+
shopOrderItemId: text("shop_order_item_id"),
|
|
7012
|
+
priceCents: integer("price_cents").notNull().default(0),
|
|
7013
|
+
startsAt: timestamp("starts_at", { withTimezone: true }),
|
|
7014
|
+
renewsAt: timestamp("renews_at", { withTimezone: true }),
|
|
7015
|
+
placedAt: timestamp("placed_at", { withTimezone: true }),
|
|
7016
|
+
lastError: text("last_error"),
|
|
7017
|
+
log: jsonb("log").$type().notNull().default({}),
|
|
7018
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7019
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
7020
|
+
},
|
|
7021
|
+
(table) => [
|
|
7022
|
+
index("network_listing_site_status_idx").on(table.siteId, table.status),
|
|
7023
|
+
index("network_listing_client_idx").on(table.clientId),
|
|
7024
|
+
index("network_listing_renews_at_idx").on(table.renewsAt),
|
|
7025
|
+
index("network_listing_shop_order_idx").on(table.shopOrderId)
|
|
7026
|
+
]
|
|
7027
|
+
);
|
|
6586
7028
|
pgTable("github_api_budget", {
|
|
6587
7029
|
tokenKey: text("token_key").primaryKey(),
|
|
6588
7030
|
windowStartedAt: timestamp("window_started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
@@ -6611,6 +7053,7 @@ pgTable(
|
|
|
6611
7053
|
requestCount: integer("request_count").notNull().default(0),
|
|
6612
7054
|
errorCount: integer("error_count").notNull().default(0),
|
|
6613
7055
|
cacheHitCount: integer("cache_hit_count").notNull().default(0),
|
|
7056
|
+
breakerHitCount: integer("breaker_hit_count").notNull().default(0),
|
|
6614
7057
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6615
7058
|
},
|
|
6616
7059
|
(table) => [
|
|
@@ -6655,11 +7098,66 @@ pgTable(
|
|
|
6655
7098
|
index("github_webhook_secret_owner_idx").on(table.owner)
|
|
6656
7099
|
]
|
|
6657
7100
|
);
|
|
7101
|
+
var gscOpportunityPass = pgTable(
|
|
7102
|
+
"gsc_opportunity_pass",
|
|
7103
|
+
{
|
|
7104
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
7105
|
+
siteKey: text("site_key").notNull(),
|
|
7106
|
+
gscProperty: text("gsc_property").notNull(),
|
|
7107
|
+
passedAt: timestamp("passed_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7108
|
+
kind: text("kind").notNull(),
|
|
7109
|
+
headline: text("headline"),
|
|
7110
|
+
windowFrom: date("window_from"),
|
|
7111
|
+
windowTo: date("window_to"),
|
|
7112
|
+
canvasPath: text("canvas_path"),
|
|
7113
|
+
createdBy: text("created_by"),
|
|
7114
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7115
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
7116
|
+
},
|
|
7117
|
+
(table) => [
|
|
7118
|
+
index("idx_gsc_opportunity_pass_site_passed").on(
|
|
7119
|
+
table.siteKey,
|
|
7120
|
+
table.passedAt
|
|
7121
|
+
),
|
|
7122
|
+
index("idx_gsc_opportunity_pass_kind").on(table.siteKey, table.kind)
|
|
7123
|
+
]
|
|
7124
|
+
);
|
|
7125
|
+
pgTable(
|
|
7126
|
+
"gsc_opportunity_item",
|
|
7127
|
+
{
|
|
7128
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
7129
|
+
passId: uuid("pass_id").notNull().references(() => gscOpportunityPass.id, { onDelete: "cascade" }),
|
|
7130
|
+
siteKey: text("site_key").notNull(),
|
|
7131
|
+
url: text("url").notNull(),
|
|
7132
|
+
queryCluster: text("query_cluster"),
|
|
7133
|
+
status: text("status").notNull().default("awaiting"),
|
|
7134
|
+
changeSummary: text("change_summary"),
|
|
7135
|
+
shippedAt: date("shipped_at"),
|
|
7136
|
+
expectEffectAfter: date("expect_effect_after"),
|
|
7137
|
+
sourceUrls: jsonb("source_urls").$type().notNull().default([]),
|
|
7138
|
+
notes: text("notes"),
|
|
7139
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7140
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
7141
|
+
},
|
|
7142
|
+
(table) => [
|
|
7143
|
+
index("idx_gsc_opportunity_item_site_status").on(
|
|
7144
|
+
table.siteKey,
|
|
7145
|
+
table.status
|
|
7146
|
+
),
|
|
7147
|
+
index("idx_gsc_opportunity_item_pass").on(table.passId),
|
|
7148
|
+
index("idx_gsc_opportunity_item_url").on(table.siteKey, table.url)
|
|
7149
|
+
]
|
|
7150
|
+
);
|
|
6658
7151
|
pgTable(
|
|
6659
7152
|
"handoff_loop_run",
|
|
6660
7153
|
{
|
|
6661
7154
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
6662
7155
|
runSlug: text("run_slug").notNull(),
|
|
7156
|
+
/**
|
|
7157
|
+
* Skill pack that produced the run. Default is the plan-handoff loop;
|
|
7158
|
+
* quality leaves push mg-strict-truth / mg-seo-strict.
|
|
7159
|
+
*/
|
|
7160
|
+
skill: text("skill").notNull().default("mg-plan-handoff-loop"),
|
|
6663
7161
|
userId: text("user_id"),
|
|
6664
7162
|
gitRepository: text("git_repository"),
|
|
6665
7163
|
machine: text("machine"),
|
|
@@ -6676,12 +7174,23 @@ pgTable(
|
|
|
6676
7174
|
checklistProposed: integer("checklist_proposed").notNull().default(0),
|
|
6677
7175
|
checklistAccepted: integer("checklist_accepted").notNull().default(0),
|
|
6678
7176
|
checklistVerified: integer("checklist_verified").notNull().default(0),
|
|
7177
|
+
/** Items resolved with evidence instead of a check (e.g. no browser session). */
|
|
7178
|
+
checklistNa: integer("checklist_na").notNull().default(0),
|
|
6679
7179
|
inputTokens: bigint("input_tokens", { mode: "number" }).notNull().default(0),
|
|
6680
7180
|
outputTokens: bigint("output_tokens", { mode: "number" }).notNull().default(0),
|
|
6681
7181
|
cacheReadTokens: bigint("cache_read_tokens", { mode: "number" }).notNull().default(0),
|
|
6682
7182
|
totalDurationMs: bigint("total_duration_ms", { mode: "number" }).notNull().default(0),
|
|
6683
7183
|
retro: text("retro"),
|
|
6684
7184
|
metrics: jsonb("metrics"),
|
|
7185
|
+
/**
|
|
7186
|
+
* Human end-verdict: good | mixed | bad. The orchestrator asks once at
|
|
7187
|
+
* Phase 4 and the AI Agents table offers a second capture point, so the
|
|
7188
|
+
* review skill can score runs against the user instead of the loop's own
|
|
7189
|
+
* self-report.
|
|
7190
|
+
*/
|
|
7191
|
+
userVerdict: text("user_verdict"),
|
|
7192
|
+
userVerdictNote: text("user_verdict_note"),
|
|
7193
|
+
verdictAt: timestamp("verdict_at", { withTimezone: true }),
|
|
6685
7194
|
reviewed: boolean("reviewed").notNull().default(false),
|
|
6686
7195
|
reviewNotes: text("review_notes"),
|
|
6687
7196
|
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
@@ -6694,8 +7203,10 @@ pgTable(
|
|
|
6694
7203
|
uniqueIndex("uq_handoff_loop_run_slug").on(table.runSlug),
|
|
6695
7204
|
index("idx_handoff_loop_run_user").on(table.userId),
|
|
6696
7205
|
index("idx_handoff_loop_run_outcome").on(table.outcome),
|
|
7206
|
+
index("idx_handoff_loop_run_skill").on(table.skill),
|
|
6697
7207
|
index("idx_handoff_loop_run_reviewed").on(table.reviewed),
|
|
6698
|
-
index("idx_handoff_loop_run_created").on(table.createdAt)
|
|
7208
|
+
index("idx_handoff_loop_run_created").on(table.createdAt),
|
|
7209
|
+
index("idx_handoff_loop_run_verdict").on(table.userVerdict)
|
|
6699
7210
|
]
|
|
6700
7211
|
);
|
|
6701
7212
|
var managedSiteSyncMode = pgEnum("managed_site_sync_mode", [
|
|
@@ -7052,6 +7563,8 @@ var TRANSIENT_GITHUB_STATUSES = /* @__PURE__ */ new Set([
|
|
|
7052
7563
|
524
|
|
7053
7564
|
]);
|
|
7054
7565
|
var DEFAULT_BUDGET_PER_MINUTE = 75;
|
|
7566
|
+
var DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS = 5 * 6e4;
|
|
7567
|
+
var DEFAULT_INTERACTIVE_STALE_MAX_AGE_MS = 30 * 6e4;
|
|
7055
7568
|
var BUDGET_STATE_KEY = "github_api_budget_per_minute";
|
|
7056
7569
|
var DEFAULT_BLOCK_MS = 15 * 60 * 1e3;
|
|
7057
7570
|
var MAX_ATTEMPTS = 4;
|
|
@@ -7059,6 +7572,7 @@ var BASE_DELAY_MS = 750;
|
|
|
7059
7572
|
var RATE_LIMIT_FLOOR = 50;
|
|
7060
7573
|
var BUDGET_WAIT_ATTEMPTS = 4;
|
|
7061
7574
|
var BUDGET_WAIT_CAP_MS = 15e3;
|
|
7575
|
+
var INTERACTIVE_BUDGET_WAIT_CAP_MS = 1e3;
|
|
7062
7576
|
var GitHubGatewayBlockedError = class extends Error {
|
|
7063
7577
|
blockedUntil;
|
|
7064
7578
|
constructor(blockedUntil, message) {
|
|
@@ -7095,6 +7609,31 @@ function summarizeGitHubErrorBody(body, maxLen = 240) {
|
|
|
7095
7609
|
function isTransientGitHubStatus(status) {
|
|
7096
7610
|
return TRANSIENT_GITHUB_STATUSES.has(status);
|
|
7097
7611
|
}
|
|
7612
|
+
var TRANSIENT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
7613
|
+
"ECONNRESET",
|
|
7614
|
+
"ECONNREFUSED",
|
|
7615
|
+
"ETIMEDOUT",
|
|
7616
|
+
"ENOTFOUND",
|
|
7617
|
+
"EAI_AGAIN",
|
|
7618
|
+
"ENETUNREACH",
|
|
7619
|
+
"EHOSTUNREACH",
|
|
7620
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
7621
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
7622
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
7623
|
+
"UND_ERR_SOCKET"
|
|
7624
|
+
]);
|
|
7625
|
+
function isTransientNetworkError(err) {
|
|
7626
|
+
if (!(err instanceof Error)) return false;
|
|
7627
|
+
const message = err.message.toLowerCase();
|
|
7628
|
+
if (message.includes("fetch failed") || message.includes("socket hang up") || message.includes("network") || message.includes("other side closed")) {
|
|
7629
|
+
return true;
|
|
7630
|
+
}
|
|
7631
|
+
const code = err.code;
|
|
7632
|
+
if (code && TRANSIENT_NETWORK_CODES.has(code)) return true;
|
|
7633
|
+
const cause = err.cause;
|
|
7634
|
+
if (cause && cause !== err) return isTransientNetworkError(cause);
|
|
7635
|
+
return false;
|
|
7636
|
+
}
|
|
7098
7637
|
function hourBucket(now = /* @__PURE__ */ new Date()) {
|
|
7099
7638
|
return new Date(
|
|
7100
7639
|
Date.UTC(
|
|
@@ -7111,6 +7650,55 @@ function hourBucket(now = /* @__PURE__ */ new Date()) {
|
|
|
7111
7650
|
function cacheKeyFor(url, tokenKey) {
|
|
7112
7651
|
return createHash("sha256").update(`${tokenKey}:${url}`).digest("hex");
|
|
7113
7652
|
}
|
|
7653
|
+
function toDate(value) {
|
|
7654
|
+
if (value == null) return null;
|
|
7655
|
+
const date3 = typeof value === "string" ? new Date(value) : value;
|
|
7656
|
+
return Number.isNaN(date3.getTime()) ? null : date3;
|
|
7657
|
+
}
|
|
7658
|
+
function isSoftCacheFresh(input) {
|
|
7659
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
7660
|
+
const expiresAt = toDate(input.expiresAt ?? null);
|
|
7661
|
+
if (expiresAt && expiresAt.getTime() > nowMs) return true;
|
|
7662
|
+
if (input.maxAgeMs == null || input.maxAgeMs <= 0) return false;
|
|
7663
|
+
const updatedAt = toDate(input.updatedAt ?? null);
|
|
7664
|
+
if (!updatedAt) return false;
|
|
7665
|
+
return nowMs - updatedAt.getTime() <= input.maxAgeMs;
|
|
7666
|
+
}
|
|
7667
|
+
function canInteractiveSoftServe(input) {
|
|
7668
|
+
if (isSoftCacheFresh({
|
|
7669
|
+
expiresAt: input.expiresAt,
|
|
7670
|
+
updatedAt: input.updatedAt,
|
|
7671
|
+
maxAgeMs: input.maxAgeMs,
|
|
7672
|
+
nowMs: input.nowMs
|
|
7673
|
+
})) {
|
|
7674
|
+
return true;
|
|
7675
|
+
}
|
|
7676
|
+
const staleMaxAgeMs = input.staleMaxAgeMs ?? DEFAULT_INTERACTIVE_STALE_MAX_AGE_MS;
|
|
7677
|
+
if (staleMaxAgeMs <= 0) return false;
|
|
7678
|
+
return isSoftCacheFresh({
|
|
7679
|
+
expiresAt: null,
|
|
7680
|
+
updatedAt: input.updatedAt,
|
|
7681
|
+
maxAgeMs: staleMaxAgeMs,
|
|
7682
|
+
nowMs: input.nowMs
|
|
7683
|
+
});
|
|
7684
|
+
}
|
|
7685
|
+
function resolveMaxAgeMs(options, priority, useCache) {
|
|
7686
|
+
if (!useCache) return void 0;
|
|
7687
|
+
if (options.maxAgeMs !== void 0) {
|
|
7688
|
+
return options.maxAgeMs > 0 ? options.maxAgeMs : void 0;
|
|
7689
|
+
}
|
|
7690
|
+
if (priority === "interactive") return DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS;
|
|
7691
|
+
return void 0;
|
|
7692
|
+
}
|
|
7693
|
+
function cachedJsonResponse(body, status, cacheHeader) {
|
|
7694
|
+
return new Response(body, {
|
|
7695
|
+
status,
|
|
7696
|
+
headers: {
|
|
7697
|
+
"x-github-gateway-cache": cacheHeader,
|
|
7698
|
+
"content-type": "application/json"
|
|
7699
|
+
}
|
|
7700
|
+
});
|
|
7701
|
+
}
|
|
7114
7702
|
async function isGithubSyncPaused() {
|
|
7115
7703
|
try {
|
|
7116
7704
|
const rows = await getDb().execute(sql`
|
|
@@ -7133,9 +7721,9 @@ async function getBlockedUntil(tokenKey) {
|
|
|
7133
7721
|
);
|
|
7134
7722
|
const raw = rows[0]?.blocked_until;
|
|
7135
7723
|
if (!raw) return null;
|
|
7136
|
-
const
|
|
7137
|
-
if (Number.isNaN(
|
|
7138
|
-
return
|
|
7724
|
+
const date3 = typeof raw === "string" ? new Date(raw) : raw;
|
|
7725
|
+
if (Number.isNaN(date3.getTime()) || date3.getTime() <= Date.now()) return null;
|
|
7726
|
+
return date3;
|
|
7139
7727
|
}
|
|
7140
7728
|
async function setGithubCircuitBreaker(tokenKey, blockedUntil) {
|
|
7141
7729
|
await getDb().execute(sql`
|
|
@@ -7215,7 +7803,15 @@ async function consumeBudgetOrWait(input) {
|
|
|
7215
7803
|
return;
|
|
7216
7804
|
}
|
|
7217
7805
|
if (input.priority === "interactive") {
|
|
7218
|
-
|
|
7806
|
+
if (attempt >= 2) {
|
|
7807
|
+
await forceConsumeBudget(input.tokenKey);
|
|
7808
|
+
return;
|
|
7809
|
+
}
|
|
7810
|
+
const waitMs2 = Math.min(
|
|
7811
|
+
await msUntilBudgetWindowReset(input.tokenKey),
|
|
7812
|
+
INTERACTIVE_BUDGET_WAIT_CAP_MS
|
|
7813
|
+
);
|
|
7814
|
+
await sleep(waitMs2);
|
|
7219
7815
|
continue;
|
|
7220
7816
|
}
|
|
7221
7817
|
if (attempt === BUDGET_WAIT_ATTEMPTS) {
|
|
@@ -7229,40 +7825,76 @@ async function consumeBudgetOrWait(input) {
|
|
|
7229
7825
|
}
|
|
7230
7826
|
throw new GitHubGatewayBudgetError(input.tokenKey);
|
|
7231
7827
|
}
|
|
7828
|
+
async function forceConsumeBudget(tokenKey) {
|
|
7829
|
+
const updated = await getDb().execute(sql`
|
|
7830
|
+
UPDATE github_api_budget
|
|
7831
|
+
SET request_count = request_count + 1, updated_at = now()
|
|
7832
|
+
WHERE token_key = ${tokenKey}
|
|
7833
|
+
RETURNING request_count
|
|
7834
|
+
`);
|
|
7835
|
+
if (updated[0]) return;
|
|
7836
|
+
await getDb().execute(sql`
|
|
7837
|
+
INSERT INTO github_api_budget (token_key, window_started_at, request_count, updated_at)
|
|
7838
|
+
VALUES (${tokenKey}, now(), 1, now())
|
|
7839
|
+
ON CONFLICT (token_key) DO UPDATE SET
|
|
7840
|
+
request_count = github_api_budget.request_count + 1,
|
|
7841
|
+
updated_at = now()
|
|
7842
|
+
`);
|
|
7843
|
+
}
|
|
7232
7844
|
async function recordStat(input) {
|
|
7233
7845
|
const bucket = hourBucket().toISOString();
|
|
7234
7846
|
await getDb().execute(sql`
|
|
7235
7847
|
INSERT INTO github_api_stat (
|
|
7236
|
-
token_key, job_id, hour_bucket, request_count, error_count,
|
|
7848
|
+
token_key, job_id, hour_bucket, request_count, error_count,
|
|
7849
|
+
cache_hit_count, breaker_hit_count, updated_at
|
|
7237
7850
|
)
|
|
7238
7851
|
VALUES (
|
|
7239
7852
|
${input.tokenKey},
|
|
7240
7853
|
${input.jobId},
|
|
7241
7854
|
${bucket}::timestamptz,
|
|
7242
|
-
1,
|
|
7855
|
+
${input.breakerHit ? 0 : 1},
|
|
7243
7856
|
${input.error ? 1 : 0},
|
|
7244
7857
|
${input.cacheHit ? 1 : 0},
|
|
7858
|
+
${input.breakerHit ? 1 : 0},
|
|
7245
7859
|
now()
|
|
7246
7860
|
)
|
|
7247
7861
|
ON CONFLICT (token_key, job_id, hour_bucket) DO UPDATE SET
|
|
7248
|
-
request_count = github_api_stat.request_count + 1,
|
|
7862
|
+
request_count = github_api_stat.request_count + ${input.breakerHit ? 0 : 1},
|
|
7249
7863
|
error_count = github_api_stat.error_count + ${input.error ? 1 : 0},
|
|
7250
7864
|
cache_hit_count = github_api_stat.cache_hit_count + ${input.cacheHit ? 1 : 0},
|
|
7865
|
+
breaker_hit_count = github_api_stat.breaker_hit_count + ${input.breakerHit ? 1 : 0},
|
|
7251
7866
|
updated_at = now()
|
|
7252
7867
|
`);
|
|
7253
7868
|
}
|
|
7869
|
+
async function recordBreakerHit(input) {
|
|
7870
|
+
await recordStat({
|
|
7871
|
+
tokenKey: input.tokenKey,
|
|
7872
|
+
jobId: input.jobId,
|
|
7873
|
+
breakerHit: true
|
|
7874
|
+
});
|
|
7875
|
+
}
|
|
7254
7876
|
async function loadEtagCache(key) {
|
|
7255
7877
|
const rows = await getDb().execute(sql`
|
|
7256
|
-
SELECT etag, body, status
|
|
7878
|
+
SELECT etag, body, status, expires_at, updated_at
|
|
7879
|
+
FROM github_api_cache
|
|
7257
7880
|
WHERE cache_key = ${key}
|
|
7258
7881
|
LIMIT 1
|
|
7259
7882
|
`);
|
|
7260
|
-
|
|
7883
|
+
const row = rows[0];
|
|
7884
|
+
if (!row) return null;
|
|
7885
|
+
return {
|
|
7886
|
+
etag: row.etag,
|
|
7887
|
+
body: row.body,
|
|
7888
|
+
status: row.status,
|
|
7889
|
+
expiresAt: row.expires_at,
|
|
7890
|
+
updatedAt: row.updated_at
|
|
7891
|
+
};
|
|
7261
7892
|
}
|
|
7262
7893
|
async function saveEtagCache(input) {
|
|
7894
|
+
const expiresAt = input.maxAgeMs != null && input.maxAgeMs > 0 ? new Date(Date.now() + input.maxAgeMs).toISOString() : null;
|
|
7263
7895
|
await getDb().execute(sql`
|
|
7264
7896
|
INSERT INTO github_api_cache (
|
|
7265
|
-
cache_key, etag, body, status, content_type, updated_at
|
|
7897
|
+
cache_key, etag, body, status, content_type, expires_at, updated_at
|
|
7266
7898
|
)
|
|
7267
7899
|
VALUES (
|
|
7268
7900
|
${input.key},
|
|
@@ -7270,6 +7902,7 @@ async function saveEtagCache(input) {
|
|
|
7270
7902
|
${input.body},
|
|
7271
7903
|
${input.status},
|
|
7272
7904
|
${input.contentType},
|
|
7905
|
+
${expiresAt}::timestamptz,
|
|
7273
7906
|
now()
|
|
7274
7907
|
)
|
|
7275
7908
|
ON CONFLICT (cache_key) DO UPDATE SET
|
|
@@ -7277,6 +7910,7 @@ async function saveEtagCache(input) {
|
|
|
7277
7910
|
body = EXCLUDED.body,
|
|
7278
7911
|
status = EXCLUDED.status,
|
|
7279
7912
|
content_type = EXCLUDED.content_type,
|
|
7913
|
+
expires_at = EXCLUDED.expires_at,
|
|
7280
7914
|
updated_at = now()
|
|
7281
7915
|
`);
|
|
7282
7916
|
}
|
|
@@ -7319,21 +7953,65 @@ function tripBreakerFromResponse(response) {
|
|
|
7319
7953
|
}
|
|
7320
7954
|
async function githubGatewayFetch(url, init, options) {
|
|
7321
7955
|
const priority = options.priority ?? "background";
|
|
7322
|
-
const budgetPerMinute = await resolveGithubBudgetPerMinute(
|
|
7323
|
-
options.budgetPerMinute
|
|
7324
|
-
);
|
|
7325
7956
|
const method = (init.method ?? "GET").toUpperCase();
|
|
7326
7957
|
const useCache = options.useEtagCache !== false && method === "GET";
|
|
7958
|
+
const maxAgeMs = resolveMaxAgeMs(options, priority, useCache);
|
|
7327
7959
|
if (priority !== "interactive" && await isGithubSyncPaused()) {
|
|
7328
7960
|
throw new GitHubGatewayPausedError();
|
|
7329
7961
|
}
|
|
7962
|
+
const key = useCache ? cacheKeyFor(url, options.tokenKey) : null;
|
|
7963
|
+
if (key && !options.bypassSoftCache) {
|
|
7964
|
+
const soft = await loadEtagCache(key);
|
|
7965
|
+
const softOk = soft?.body != null && (priority === "interactive" ? canInteractiveSoftServe({
|
|
7966
|
+
expiresAt: soft.expiresAt,
|
|
7967
|
+
updatedAt: soft.updatedAt,
|
|
7968
|
+
maxAgeMs
|
|
7969
|
+
}) : isSoftCacheFresh({
|
|
7970
|
+
expiresAt: soft.expiresAt,
|
|
7971
|
+
updatedAt: soft.updatedAt,
|
|
7972
|
+
maxAgeMs
|
|
7973
|
+
}));
|
|
7974
|
+
if (softOk && soft?.body != null) {
|
|
7975
|
+
const isFresh = isSoftCacheFresh({
|
|
7976
|
+
expiresAt: soft.expiresAt,
|
|
7977
|
+
updatedAt: soft.updatedAt,
|
|
7978
|
+
maxAgeMs
|
|
7979
|
+
});
|
|
7980
|
+
await recordStat({
|
|
7981
|
+
tokenKey: options.tokenKey,
|
|
7982
|
+
jobId: options.jobId,
|
|
7983
|
+
cacheHit: true
|
|
7984
|
+
});
|
|
7985
|
+
if (priority === "interactive" && !isFresh) {
|
|
7986
|
+
void githubGatewayFetch(url, init, {
|
|
7987
|
+
...options,
|
|
7988
|
+
bypassSoftCache: true,
|
|
7989
|
+
priority: "background",
|
|
7990
|
+
maxAgeMs: maxAgeMs ?? DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS
|
|
7991
|
+
}).catch(() => {
|
|
7992
|
+
});
|
|
7993
|
+
}
|
|
7994
|
+
return cachedJsonResponse(soft.body, soft.status ?? 200, "soft");
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
const budgetPerMinute = await resolveGithubBudgetPerMinute(
|
|
7998
|
+
options.budgetPerMinute
|
|
7999
|
+
);
|
|
7330
8000
|
const blocked = await getBlockedUntil(options.tokenKey);
|
|
7331
8001
|
if (blocked) {
|
|
7332
8002
|
if (priority === "interactive") {
|
|
7333
8003
|
if (blocked.getTime() - Date.now() > 6e4) {
|
|
8004
|
+
await recordBreakerHit({
|
|
8005
|
+
tokenKey: options.tokenKey,
|
|
8006
|
+
jobId: options.jobId
|
|
8007
|
+
});
|
|
7334
8008
|
throw new GitHubGatewayBlockedError(blocked);
|
|
7335
8009
|
}
|
|
7336
8010
|
} else {
|
|
8011
|
+
await recordBreakerHit({
|
|
8012
|
+
tokenKey: options.tokenKey,
|
|
8013
|
+
jobId: options.jobId
|
|
8014
|
+
});
|
|
7337
8015
|
throw new GitHubGatewayBlockedError(blocked);
|
|
7338
8016
|
}
|
|
7339
8017
|
}
|
|
@@ -7352,14 +8030,32 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7352
8030
|
if (!headers.has("X-GitHub-Api-Version")) {
|
|
7353
8031
|
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
7354
8032
|
}
|
|
7355
|
-
const key = useCache ? cacheKeyFor(url, options.tokenKey) : null;
|
|
7356
8033
|
if (key) {
|
|
7357
8034
|
const cached = await loadEtagCache(key);
|
|
7358
8035
|
if (cached?.etag) headers.set("If-None-Match", cached.etag);
|
|
7359
8036
|
}
|
|
7360
8037
|
let lastError = null;
|
|
7361
8038
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
7362
|
-
|
|
8039
|
+
let response;
|
|
8040
|
+
try {
|
|
8041
|
+
response = await fetch(url, {
|
|
8042
|
+
...init,
|
|
8043
|
+
headers,
|
|
8044
|
+
signal: init.signal ?? AbortSignal.timeout(2e4)
|
|
8045
|
+
});
|
|
8046
|
+
} catch (err) {
|
|
8047
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
8048
|
+
await recordStat({
|
|
8049
|
+
tokenKey: options.tokenKey,
|
|
8050
|
+
jobId: options.jobId,
|
|
8051
|
+
error: true
|
|
8052
|
+
});
|
|
8053
|
+
if (!isTransientNetworkError(err) || attempt === MAX_ATTEMPTS) {
|
|
8054
|
+
throw lastError;
|
|
8055
|
+
}
|
|
8056
|
+
await sleep(BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
8057
|
+
continue;
|
|
8058
|
+
}
|
|
7363
8059
|
await updateRateLimitHeaders(options.tokenKey, response);
|
|
7364
8060
|
if (response.status === 304 && key) {
|
|
7365
8061
|
const cached = await loadEtagCache(key);
|
|
@@ -7368,13 +8064,11 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7368
8064
|
jobId: options.jobId,
|
|
7369
8065
|
cacheHit: true
|
|
7370
8066
|
});
|
|
7371
|
-
return
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
}
|
|
7377
|
-
});
|
|
8067
|
+
return cachedJsonResponse(
|
|
8068
|
+
cached?.body ?? "",
|
|
8069
|
+
cached?.status ?? 200,
|
|
8070
|
+
"hit"
|
|
8071
|
+
);
|
|
7378
8072
|
}
|
|
7379
8073
|
if (response.status === 403 || response.status === 429 || isTransientGitHubStatus(response.status)) {
|
|
7380
8074
|
const bodyText = summarizeGitHubErrorBody(
|
|
@@ -7393,6 +8087,10 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7393
8087
|
error: true
|
|
7394
8088
|
});
|
|
7395
8089
|
if (attempt === MAX_ATTEMPTS || secondary) {
|
|
8090
|
+
await recordBreakerHit({
|
|
8091
|
+
tokenKey: options.tokenKey,
|
|
8092
|
+
jobId: options.jobId
|
|
8093
|
+
});
|
|
7396
8094
|
throw new GitHubGatewayBlockedError(until, lastError.message);
|
|
7397
8095
|
}
|
|
7398
8096
|
const retryAfter = Number(response.headers.get("retry-after"));
|
|
@@ -7409,7 +8107,8 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7409
8107
|
etag,
|
|
7410
8108
|
body,
|
|
7411
8109
|
status: response.status,
|
|
7412
|
-
contentType: response.headers.get("content-type")
|
|
8110
|
+
contentType: response.headers.get("content-type"),
|
|
8111
|
+
maxAgeMs
|
|
7413
8112
|
});
|
|
7414
8113
|
}
|
|
7415
8114
|
await recordStat({
|
|
@@ -8840,7 +9539,7 @@ function getEncryptionKey() {
|
|
|
8840
9539
|
throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
|
|
8841
9540
|
return buf;
|
|
8842
9541
|
}
|
|
8843
|
-
function encrypt(
|
|
9542
|
+
function encrypt(text22) {
|
|
8844
9543
|
const key = getEncryptionKey();
|
|
8845
9544
|
const iv = randomBytes(ENC_IV_LENGTH);
|
|
8846
9545
|
const cipher = createCipheriv(
|
|
@@ -8848,7 +9547,7 @@ function encrypt(text19) {
|
|
|
8848
9547
|
new Uint8Array(key),
|
|
8849
9548
|
new Uint8Array(iv)
|
|
8850
9549
|
);
|
|
8851
|
-
let encrypted = cipher.update(
|
|
9550
|
+
let encrypted = cipher.update(text22, "utf8", "hex");
|
|
8852
9551
|
encrypted += cipher.final("hex");
|
|
8853
9552
|
const authTag = cipher.getAuthTag();
|
|
8854
9553
|
return Buffer.concat([
|
|
@@ -9568,10 +10267,10 @@ async function r2GetObjectRange(bucket, key, range) {
|
|
|
9568
10267
|
const body = result.Body;
|
|
9569
10268
|
if (!body?.transformToString)
|
|
9570
10269
|
throw new Error("R2 returned no readable body");
|
|
9571
|
-
const
|
|
10270
|
+
const text22 = await body.transformToString();
|
|
9572
10271
|
const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
|
|
9573
10272
|
return `${header}
|
|
9574
|
-
${
|
|
10273
|
+
${text22}`;
|
|
9575
10274
|
} catch (e) {
|
|
9576
10275
|
throw r2WrapError(bucket, key, e);
|
|
9577
10276
|
}
|
|
@@ -9934,15 +10633,15 @@ async function sftpRead(opts, filePath, proxy, options) {
|
|
|
9934
10633
|
clearTimeout(timer);
|
|
9935
10634
|
cleanup?.();
|
|
9936
10635
|
cleanup = void 0;
|
|
9937
|
-
const
|
|
10636
|
+
const text22 = Buffer.concat(
|
|
9938
10637
|
chunks.map((ch) => new Uint8Array(ch))
|
|
9939
10638
|
).toString("utf-8");
|
|
9940
10639
|
if (!isWholeFileRequest) {
|
|
9941
10640
|
const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
|
|
9942
10641
|
resolve(`${header}
|
|
9943
|
-
${
|
|
10642
|
+
${text22}`);
|
|
9944
10643
|
} else {
|
|
9945
|
-
resolve(
|
|
10644
|
+
resolve(text22);
|
|
9946
10645
|
}
|
|
9947
10646
|
});
|
|
9948
10647
|
rs.on("error", (e) => {
|
|
@@ -9975,6 +10674,8 @@ var RESPONSE_MAX_BYTES = 8192;
|
|
|
9975
10674
|
var NO_FOOTER_TOOLS = /* @__PURE__ */ new Set();
|
|
9976
10675
|
var RAW_JSON_TOOLS = /* @__PURE__ */ new Set([
|
|
9977
10676
|
"get_mg_dashboard_commits",
|
|
10677
|
+
"get_mg_dashboard_file",
|
|
10678
|
+
"get_mg_dashboard_diff",
|
|
9978
10679
|
"extract_article",
|
|
9979
10680
|
// Content family (2026-DASMG-040/041/042) — compact JSON consumed verbatim.
|
|
9980
10681
|
// Consolidated action-based tools (the Cursor surface); each proxies to the
|
|
@@ -9990,7 +10691,9 @@ var RAW_JSON_TOOLS = /* @__PURE__ */ new Set([
|
|
|
9990
10691
|
"managed-site",
|
|
9991
10692
|
"content-sync",
|
|
9992
10693
|
// Skill registry metadata only (no file bodies) — must stay valid JSON.
|
|
9993
|
-
"cursor-skill"
|
|
10694
|
+
"cursor-skill",
|
|
10695
|
+
// GSC opportunity store (pass + items)
|
|
10696
|
+
"gsc-opportunity"
|
|
9994
10697
|
]);
|
|
9995
10698
|
var TOOL_CACHE_TTL_MS = {
|
|
9996
10699
|
"list-servers": 6e4,
|
|
@@ -10091,11 +10794,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
|
|
|
10091
10794
|
if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
|
|
10092
10795
|
return e.names;
|
|
10093
10796
|
}
|
|
10094
|
-
function truncateForLLM(
|
|
10095
|
-
const totalBytes = Buffer.byteLength(
|
|
10797
|
+
function truncateForLLM(text22, maxBytes) {
|
|
10798
|
+
const totalBytes = Buffer.byteLength(text22, "utf8");
|
|
10096
10799
|
if (totalBytes <= maxBytes)
|
|
10097
|
-
return { text:
|
|
10098
|
-
const buf = Buffer.from(
|
|
10800
|
+
return { text: text22, truncated: false, totalBytes, shownBytes: totalBytes };
|
|
10801
|
+
const buf = Buffer.from(text22, "utf8");
|
|
10099
10802
|
let cut = maxBytes;
|
|
10100
10803
|
while (cut > 0 && (buf[cut] & 192) === 128) cut--;
|
|
10101
10804
|
const head = buf.subarray(0, cut).toString("utf8");
|
|
@@ -10126,10 +10829,10 @@ function postprocessResult(result, meta) {
|
|
|
10126
10829
|
if (!result.content?.length) return result;
|
|
10127
10830
|
if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
|
|
10128
10831
|
const block = result.content[0];
|
|
10129
|
-
let
|
|
10130
|
-
const trunc = truncateForLLM(
|
|
10832
|
+
let text22 = String(block.text ?? "");
|
|
10833
|
+
const trunc = truncateForLLM(text22, RESPONSE_MAX_BYTES);
|
|
10131
10834
|
if (trunc.truncated) {
|
|
10132
|
-
|
|
10835
|
+
text22 = trunc.text + "\n\n... " + buildTruncationHint(
|
|
10133
10836
|
meta.toolName,
|
|
10134
10837
|
meta.args,
|
|
10135
10838
|
trunc.totalBytes,
|
|
@@ -10143,11 +10846,11 @@ function postprocessResult(result, meta) {
|
|
|
10143
10846
|
const parts = [`took ${tookStr}`, sizeStr];
|
|
10144
10847
|
if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
|
|
10145
10848
|
if (meta.cached) parts.push("cached");
|
|
10146
|
-
|
|
10849
|
+
text22 = `${text22}
|
|
10147
10850
|
|
|
10148
10851
|
[${parts.join(", ")}]`;
|
|
10149
10852
|
}
|
|
10150
|
-
return { ...result, content: [{ ...block, text:
|
|
10853
|
+
return { ...result, content: [{ ...block, text: text22 }] };
|
|
10151
10854
|
}
|
|
10152
10855
|
function buildPipelineScript(commands, shell, marker, stopOnError) {
|
|
10153
10856
|
if (shell === "powershell") {
|
|
@@ -10933,11 +11636,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
10933
11636
|
applied_by TEXT
|
|
10934
11637
|
);
|
|
10935
11638
|
`.trim();
|
|
10936
|
-
function normaliseMigrationSql(
|
|
10937
|
-
return
|
|
11639
|
+
function normaliseMigrationSql(sql31) {
|
|
11640
|
+
return sql31.replace(/\r\n/g, "\n").trim() + "\n";
|
|
10938
11641
|
}
|
|
10939
|
-
function migrationSha256(
|
|
10940
|
-
return createHash("sha256").update(
|
|
11642
|
+
function migrationSha256(sql31) {
|
|
11643
|
+
return createHash("sha256").update(sql31.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
10941
11644
|
}
|
|
10942
11645
|
function dollarQuoteTag(value) {
|
|
10943
11646
|
let tag = "_mcp";
|
|
@@ -12143,6 +12846,7 @@ var TOOLS = [
|
|
|
12143
12846
|
required: ["actor", "dateFrom", "dateTo"]
|
|
12144
12847
|
}
|
|
12145
12848
|
},
|
|
12849
|
+
...GITHUB_CODE_ACCESS_TOOLS,
|
|
12146
12850
|
{
|
|
12147
12851
|
name: "extract_article",
|
|
12148
12852
|
description: "Extract readable article/content data from ONE PUBLIC URL, with a browser-render fallback for pages that block a normal fetch (403) or only render with JavaScript (ticket 2026-DASMG-039). Returns title, author, publishedAt, source, summary, facts[], optional rawText (capped), canonicalUrl, the `extractionMethod` used (http | browser | jsonld | rss | amp | metadata_only) and an explicit `limitations` array (e.g. blocked_without_browser, paywall_detected, partial_content, rawtext_truncated). PUBLIC URLs only \u2014 SSRF-guarded (private/loopback/metadata addresses rejected), no paywall bypass, no credentials; the source/canonical URL is always preserved. Use it to turn a news/article link into safe factual source material for agents; it never auto-publishes or rewrites. For several sources + cross-checking, use `research` with action=topic.",
|
|
@@ -12803,6 +13507,87 @@ var TOOLS = [
|
|
|
12803
13507
|
}
|
|
12804
13508
|
},
|
|
12805
13509
|
// ----- Shared Cursor skills (metadata only; install via downloads ZIP) -----
|
|
13510
|
+
{
|
|
13511
|
+
name: "gsc-opportunity",
|
|
13512
|
+
description: 'GSC opportunity store (shared SoT across MG-managed sites). Pick `action`:\n- "list": open items/passes for a siteKey (filter by status/statuses/kind). Default excludes closed.\n- "upsert": create/update a pass + optional items (url, queryCluster, status, expectEffectAfter, sourceUrls).\n- "close": mark an item closed by id or siteKey+url(+queryCluster).\nUse before/after gsc-opportunities skill passes instead of growing markdown logs. On CTR/policy polish upserts, include sourceUrls (official claim URLs).',
|
|
13513
|
+
inputSchema: {
|
|
13514
|
+
type: "object",
|
|
13515
|
+
properties: {
|
|
13516
|
+
action: {
|
|
13517
|
+
type: "string",
|
|
13518
|
+
enum: ["list", "upsert", "close"],
|
|
13519
|
+
description: "list | upsert | close"
|
|
13520
|
+
},
|
|
13521
|
+
siteKey: {
|
|
13522
|
+
type: "string",
|
|
13523
|
+
description: "Stable site id, e.g. solarfast (required for all actions)."
|
|
13524
|
+
},
|
|
13525
|
+
status: {
|
|
13526
|
+
type: "string",
|
|
13527
|
+
enum: ["awaiting", "monitor", "pending_reindex", "skipped", "closed"],
|
|
13528
|
+
description: "action=list: single status filter."
|
|
13529
|
+
},
|
|
13530
|
+
statuses: {
|
|
13531
|
+
type: "array",
|
|
13532
|
+
items: {
|
|
13533
|
+
type: "string",
|
|
13534
|
+
enum: ["awaiting", "monitor", "pending_reindex", "skipped", "closed"]
|
|
13535
|
+
},
|
|
13536
|
+
description: "action=list: multi status filter."
|
|
13537
|
+
},
|
|
13538
|
+
kind: {
|
|
13539
|
+
type: "string",
|
|
13540
|
+
enum: ["analysis", "ctr_polish", "reindex", "cls", "other"],
|
|
13541
|
+
description: "action=list/upsert: pass kind."
|
|
13542
|
+
},
|
|
13543
|
+
includeClosed: {
|
|
13544
|
+
type: "boolean",
|
|
13545
|
+
description: "action=list: include closed items (default false)."
|
|
13546
|
+
},
|
|
13547
|
+
limit: { type: "number", description: "action=list: max items (default 50)." },
|
|
13548
|
+
offset: { type: "number", description: "action=list: pagination offset." },
|
|
13549
|
+
gscProperty: {
|
|
13550
|
+
type: "string",
|
|
13551
|
+
description: "action=upsert: e.g. sc-domain:solarfast.nl"
|
|
13552
|
+
},
|
|
13553
|
+
passId: {
|
|
13554
|
+
type: "string",
|
|
13555
|
+
description: "action=upsert: update existing pass uuid."
|
|
13556
|
+
},
|
|
13557
|
+
headline: { type: "string", description: "action=upsert: pass headline." },
|
|
13558
|
+
windowFrom: { type: "string", description: "action=upsert: YYYY-MM-DD." },
|
|
13559
|
+
windowTo: { type: "string", description: "action=upsert: YYYY-MM-DD." },
|
|
13560
|
+
canvasPath: { type: "string", description: "action=upsert: optional canvas path." },
|
|
13561
|
+
createdBy: { type: "string", description: "action=upsert: agent/user label." },
|
|
13562
|
+
passedAt: {
|
|
13563
|
+
type: "string",
|
|
13564
|
+
description: "action=upsert: ISO datetime for the pass."
|
|
13565
|
+
},
|
|
13566
|
+
items: {
|
|
13567
|
+
type: "array",
|
|
13568
|
+
description: "action=upsert: items [{url, queryCluster?, status?, changeSummary?, shippedAt?, expectEffectAfter?, sourceUrls?, notes?, id?}]",
|
|
13569
|
+
items: { type: "object" }
|
|
13570
|
+
},
|
|
13571
|
+
id: {
|
|
13572
|
+
type: "string",
|
|
13573
|
+
description: "action=close: item uuid (preferred)."
|
|
13574
|
+
},
|
|
13575
|
+
url: {
|
|
13576
|
+
type: "string",
|
|
13577
|
+
description: "action=close: item url when id omitted."
|
|
13578
|
+
},
|
|
13579
|
+
queryCluster: {
|
|
13580
|
+
type: "string",
|
|
13581
|
+
description: "action=close: optional cluster disambiguator."
|
|
13582
|
+
},
|
|
13583
|
+
notes: {
|
|
13584
|
+
type: "string",
|
|
13585
|
+
description: "action=close/upsert item notes."
|
|
13586
|
+
}
|
|
13587
|
+
},
|
|
13588
|
+
required: ["action", "siteKey"]
|
|
13589
|
+
}
|
|
13590
|
+
},
|
|
12806
13591
|
{
|
|
12807
13592
|
name: "cursor-skill",
|
|
12808
13593
|
description: 'Shared Cursor skill registry (metadata only \u2014 never returns file bodies; MCP truncates large text). Actions: list (slugs+versions), status (localVersion vs tip), pull (install pointer only). To install bytes: GET https://dashboard.mgsoftware.nl/api/downloads/versions.txt (skills/<slug>.zip=N) then GET \u2026/api/downloads/skills/<slug>.zip and expand into .cursor/skills/<slug>/. Publish from mg-dashboard: bun scripts/skills/pack-and-publish.ts --slug <slug> --message "\u2026". No push via this tool.',
|
|
@@ -12833,7 +13618,7 @@ var TOOLS = [
|
|
|
12833
13618
|
// ----- Trigger.dev -----
|
|
12834
13619
|
...TRIGGER_TOOLS
|
|
12835
13620
|
];
|
|
12836
|
-
var MCP_VERSION = "7.4.
|
|
13621
|
+
var MCP_VERSION = "7.4.14";
|
|
12837
13622
|
async function handleListTools() {
|
|
12838
13623
|
if (!authContext) return { tools: TOOLS };
|
|
12839
13624
|
const allowedTools = authContext.allowedTools;
|
|
@@ -12974,6 +13759,12 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
12974
13759
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
12975
13760
|
};
|
|
12976
13761
|
}
|
|
13762
|
+
case "get_mg_dashboard_file":
|
|
13763
|
+
case "get_mg_dashboard_diff":
|
|
13764
|
+
return handleGithubCodeAccessTool(name, a, {
|
|
13765
|
+
dashboardBaseUrl,
|
|
13766
|
+
apiKey: apiKey ?? ""
|
|
13767
|
+
});
|
|
12977
13768
|
// ----- Public-web extraction (article tools) -----
|
|
12978
13769
|
case "extract_article": {
|
|
12979
13770
|
const url = typeof a.url === "string" ? a.url.trim() : "";
|
|
@@ -13050,6 +13841,39 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13050
13841
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
13051
13842
|
};
|
|
13052
13843
|
}
|
|
13844
|
+
case "gsc-opportunity": {
|
|
13845
|
+
const action = typeof a.action === "string" ? a.action.trim() : "";
|
|
13846
|
+
if (!action) {
|
|
13847
|
+
return {
|
|
13848
|
+
content: [{ type: "text", text: "Error: action is required" }]
|
|
13849
|
+
};
|
|
13850
|
+
}
|
|
13851
|
+
const { action: _action, ...params } = a;
|
|
13852
|
+
void _action;
|
|
13853
|
+
const res = await fetch(`${dashboardBaseUrl}/api/tools/gsc-opportunity`, {
|
|
13854
|
+
method: "POST",
|
|
13855
|
+
headers: {
|
|
13856
|
+
"content-type": "application/json",
|
|
13857
|
+
authorization: `Bearer ${apiKey}`
|
|
13858
|
+
},
|
|
13859
|
+
body: JSON.stringify({ action, params })
|
|
13860
|
+
});
|
|
13861
|
+
if (!res.ok) {
|
|
13862
|
+
const detail = await res.text().catch(() => "");
|
|
13863
|
+
return {
|
|
13864
|
+
content: [
|
|
13865
|
+
{
|
|
13866
|
+
type: "text",
|
|
13867
|
+
text: `Error: gsc-opportunity failed (${res.status}). ${detail.slice(0, 400)}`
|
|
13868
|
+
}
|
|
13869
|
+
]
|
|
13870
|
+
};
|
|
13871
|
+
}
|
|
13872
|
+
const data = await res.json();
|
|
13873
|
+
return {
|
|
13874
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
13875
|
+
};
|
|
13876
|
+
}
|
|
13053
13877
|
// ----- Team memory -----
|
|
13054
13878
|
case "search-team-memory": {
|
|
13055
13879
|
const query = typeof a.query === "string" ? a.query.trim() : "";
|
|
@@ -13091,7 +13915,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13091
13915
|
]
|
|
13092
13916
|
};
|
|
13093
13917
|
}
|
|
13094
|
-
const lines = data.hits.map((hit,
|
|
13918
|
+
const lines = data.hits.map((hit, index19) => {
|
|
13095
13919
|
const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
|
|
13096
13920
|
const repo = hit.repo ?? "unknown repo";
|
|
13097
13921
|
const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
|
|
@@ -13101,7 +13925,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13101
13925
|
...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
|
|
13102
13926
|
].filter(Boolean);
|
|
13103
13927
|
const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
|
|
13104
|
-
return `${
|
|
13928
|
+
return `${index19 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
|
|
13105
13929
|
id: ${hit.id}
|
|
13106
13930
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
|
|
13107
13931
|
});
|
|
@@ -13308,12 +14132,12 @@ Searchable via search-team-memory once indexing completes (usually immediate).`
|
|
|
13308
14132
|
]
|
|
13309
14133
|
};
|
|
13310
14134
|
}
|
|
13311
|
-
const lines = data.hits.map((hit,
|
|
14135
|
+
const lines = data.hits.map((hit, index19) => {
|
|
13312
14136
|
const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
|
|
13313
14137
|
const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
|
|
13314
14138
|
const sources = hit.sources.length ? `
|
|
13315
14139
|
sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
|
|
13316
|
-
return `${
|
|
14140
|
+
return `${index19 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
|
|
13317
14141
|
slug: ${hit.slug}${sources}
|
|
13318
14142
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
|
|
13319
14143
|
});
|
|
@@ -13370,8 +14194,8 @@ Call get-wiki-page with a slug for the full page; follow its sources (team-memor
|
|
|
13370
14194
|
const formatSource = (s) => {
|
|
13371
14195
|
const base = `${s.type}:${s.ref}`;
|
|
13372
14196
|
if (s.title) {
|
|
13373
|
-
const
|
|
13374
|
-
return `${base} \u2014 "${s.title}"${
|
|
14197
|
+
const date3 = s.date ? ` (${s.date})` : "";
|
|
14198
|
+
return `${base} \u2014 "${s.title}"${date3}`;
|
|
13375
14199
|
}
|
|
13376
14200
|
return base;
|
|
13377
14201
|
};
|
|
@@ -14570,8 +15394,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
14570
15394
|
};
|
|
14571
15395
|
const filtered = sortRows(applyFilter(only.rows));
|
|
14572
15396
|
if (format === "json") {
|
|
14573
|
-
const
|
|
14574
|
-
return { content: [{ type: "text", text:
|
|
15397
|
+
const text23 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
|
|
15398
|
+
return { content: [{ type: "text", text: text23 }] };
|
|
14575
15399
|
}
|
|
14576
15400
|
if (groupByProject) {
|
|
14577
15401
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -14598,8 +15422,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
14598
15422
|
}
|
|
14599
15423
|
const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
|
|
14600
15424
|
const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
|
|
14601
|
-
const
|
|
14602
|
-
return { content: [{ type: "text", text:
|
|
15425
|
+
const text22 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
|
|
15426
|
+
return { content: [{ type: "text", text: text22 }] };
|
|
14603
15427
|
}
|
|
14604
15428
|
if (format === "json") {
|
|
14605
15429
|
const lines = [];
|