@mgsoftwarebv/mg-dashboard-mcp 7.4.9 → 7.4.10
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 +749 -87
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { sql } from 'drizzle-orm';
|
|
|
20
20
|
import { once } from 'events';
|
|
21
21
|
import { lookup } from 'dns/promises';
|
|
22
22
|
import { connect } from 'tls';
|
|
23
|
-
import { pgTable, timestamp,
|
|
23
|
+
import { pgTable, timestamp, uuid, jsonb, boolean, text, pgEnum, integer, uniqueIndex, index, check, bigint, doublePrecision, primaryKey, foreignKey, date, real } from 'drizzle-orm/pg-core';
|
|
24
24
|
import { HeadObjectCommand, ListObjectsV2Command, DeleteObjectsCommand, S3Client, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
|
|
25
25
|
|
|
26
26
|
var __defProp = Object.defineProperty;
|
|
@@ -703,10 +703,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
|
|
|
703
703
|
"trigger-env": "settings"
|
|
704
704
|
};
|
|
705
705
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
706
|
-
const
|
|
706
|
+
const sql29 = `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
707
|
const cmd = [
|
|
708
708
|
`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 "${
|
|
709
|
+
`ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql29}" 2>/dev/null | tr -d '[:space:]')`,
|
|
710
710
|
'echo "$PORT|$ROW"'
|
|
711
711
|
].join(" && ");
|
|
712
712
|
const result = await sshExec2(conn, cmd, proxy);
|
|
@@ -728,8 +728,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
728
728
|
return { port, apiKey: apiKey2, projectRef: projectRef || "" };
|
|
729
729
|
}
|
|
730
730
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
731
|
-
const
|
|
732
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
731
|
+
const sql29 = `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`;
|
|
732
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql29}" 2>/dev/null`;
|
|
733
733
|
const result = await sshExec2(conn, cmd, proxy);
|
|
734
734
|
const output = result.stdout.trim();
|
|
735
735
|
if (!output) return "";
|
|
@@ -811,8 +811,8 @@ async function handleTriggerTool(name, args2, deps) {
|
|
|
811
811
|
switch (name) {
|
|
812
812
|
// -----------------------------------------------------------------
|
|
813
813
|
case "trigger-list": {
|
|
814
|
-
const
|
|
815
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
814
|
+
const sql29 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
|
|
815
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql29}" 2>/dev/null`;
|
|
816
816
|
const result = await sshExec2(conn, cmd, proxy);
|
|
817
817
|
const output = result.stdout.trim();
|
|
818
818
|
if (!output) {
|
|
@@ -990,9 +990,9 @@ ${raw2.substring(0, 500)}` }] };
|
|
|
990
990
|
return { content: [{ type: "text", text: `No environment variables for ${project}/${env}.` }] };
|
|
991
991
|
}
|
|
992
992
|
const lines = vars.map((v) => `${v.isSecret ? "[secret]" : " "} ${v.name}`).sort();
|
|
993
|
-
const
|
|
993
|
+
const text16 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
|
|
994
994
|
` + "-".repeat(50) + "\n" + lines.join("\n");
|
|
995
|
-
return { content: [{ type: "text", text:
|
|
995
|
+
return { content: [{ type: "text", text: text16 }] };
|
|
996
996
|
}
|
|
997
997
|
if (action === "get") {
|
|
998
998
|
const key2 = String(args2.key ?? "");
|
|
@@ -1079,9 +1079,9 @@ async function fetchAndFormatRun(conn, proxy, sshExec2, instance, runId) {
|
|
|
1079
1079
|
return { content: [{ type: "text", text: `Invalid API response:
|
|
1080
1080
|
${rawJson.substring(0, 500)}` }] };
|
|
1081
1081
|
}
|
|
1082
|
-
let
|
|
1083
|
-
if (logs)
|
|
1084
|
-
return { content: [{ type: "text", text:
|
|
1082
|
+
let text16 = formatRunDetail(run);
|
|
1083
|
+
if (logs) text16 += "\n\n--- Logs ---\n" + logs;
|
|
1084
|
+
return { content: [{ type: "text", text: text16 }] };
|
|
1085
1085
|
}
|
|
1086
1086
|
async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSeconds) {
|
|
1087
1087
|
const pollInterval = 3e3;
|
|
@@ -1103,10 +1103,10 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
|
|
|
1103
1103
|
continue;
|
|
1104
1104
|
}
|
|
1105
1105
|
if (TERMINAL_STATUSES.has(run.status)) {
|
|
1106
|
-
let
|
|
1106
|
+
let text16 = formatRunDetail(run);
|
|
1107
1107
|
const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
|
|
1108
|
-
if (logs)
|
|
1109
|
-
return { content: [{ type: "text", text:
|
|
1108
|
+
if (logs) text16 += "\n\n--- Logs ---\n" + logs;
|
|
1109
|
+
return { content: [{ type: "text", text: text16 }] };
|
|
1110
1110
|
}
|
|
1111
1111
|
}
|
|
1112
1112
|
return {
|
|
@@ -3793,10 +3793,10 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
3793
3793
|
// }) as any;
|
|
3794
3794
|
// return merged;
|
|
3795
3795
|
// }
|
|
3796
|
-
catchall(
|
|
3796
|
+
catchall(index13) {
|
|
3797
3797
|
return new _ZodObject({
|
|
3798
3798
|
...this._def,
|
|
3799
|
-
catchall:
|
|
3799
|
+
catchall: index13
|
|
3800
3800
|
});
|
|
3801
3801
|
}
|
|
3802
3802
|
pick(mask) {
|
|
@@ -4114,9 +4114,9 @@ function mergeValues(a, b) {
|
|
|
4114
4114
|
return { valid: false };
|
|
4115
4115
|
}
|
|
4116
4116
|
const newArray = [];
|
|
4117
|
-
for (let
|
|
4118
|
-
const itemA = a[
|
|
4119
|
-
const itemB = b[
|
|
4117
|
+
for (let index13 = 0; index13 < a.length; index13++) {
|
|
4118
|
+
const itemA = a[index13];
|
|
4119
|
+
const itemB = b[index13];
|
|
4120
4120
|
const sharedValue = mergeValues(itemA, itemB);
|
|
4121
4121
|
if (!sharedValue.valid) {
|
|
4122
4122
|
return { valid: false };
|
|
@@ -4322,10 +4322,10 @@ var ZodMap = class extends ZodType {
|
|
|
4322
4322
|
}
|
|
4323
4323
|
const keyType = this._def.keyType;
|
|
4324
4324
|
const valueType = this._def.valueType;
|
|
4325
|
-
const pairs = [...ctx.data.entries()].map(([key, value],
|
|
4325
|
+
const pairs = [...ctx.data.entries()].map(([key, value], index13) => {
|
|
4326
4326
|
return {
|
|
4327
|
-
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [
|
|
4328
|
-
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [
|
|
4327
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index13, "key"])),
|
|
4328
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index13, "value"]))
|
|
4329
4329
|
};
|
|
4330
4330
|
});
|
|
4331
4331
|
if (ctx.common.async) {
|
|
@@ -5247,6 +5247,8 @@ var users = pgTable("user", {
|
|
|
5247
5247
|
emailVerified: boolean("email_verified").notNull().default(false),
|
|
5248
5248
|
roleId: uuid("role_id"),
|
|
5249
5249
|
permissions: jsonb("permissions"),
|
|
5250
|
+
/** Linked Refront user id (matched by email during refront-crm-sync). */
|
|
5251
|
+
refrontUserId: uuid("refront_user_id"),
|
|
5250
5252
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5251
5253
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5252
5254
|
});
|
|
@@ -6150,7 +6152,8 @@ var directorySubmissionStatus = pgEnum(
|
|
|
6150
6152
|
"live",
|
|
6151
6153
|
"rejected",
|
|
6152
6154
|
"failed",
|
|
6153
|
-
"manual"
|
|
6155
|
+
"manual",
|
|
6156
|
+
"captcha"
|
|
6154
6157
|
]
|
|
6155
6158
|
);
|
|
6156
6159
|
var directoryAccountStatus = pgEnum("directory_account_status", [
|
|
@@ -6194,6 +6197,8 @@ var linkBuildingClient = pgTable(
|
|
|
6194
6197
|
refrontMailAccountId: text("refront_mail_account_id"),
|
|
6195
6198
|
profile: jsonb("profile").$type().notNull().default({}),
|
|
6196
6199
|
weeklyPace: integer("weekly_pace").notNull().default(12),
|
|
6200
|
+
/** Target package size (number of directory backlinks sold). */
|
|
6201
|
+
packageSize: integer("package_size").notNull().default(100),
|
|
6197
6202
|
autoSubmit: boolean("auto_submit").notNull().default(false),
|
|
6198
6203
|
startingDr: doublePrecision("starting_dr"),
|
|
6199
6204
|
currentDr: doublePrecision("current_dr"),
|
|
@@ -6206,7 +6211,7 @@ var linkBuildingClient = pgTable(
|
|
|
6206
6211
|
index("link_building_client_active_idx").on(table.isActive)
|
|
6207
6212
|
]
|
|
6208
6213
|
);
|
|
6209
|
-
pgTable(
|
|
6214
|
+
var directorySubmission = pgTable(
|
|
6210
6215
|
"directory_submission",
|
|
6211
6216
|
{
|
|
6212
6217
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
@@ -6221,6 +6226,7 @@ pgTable(
|
|
|
6221
6226
|
submittedAt: timestamp("submitted_at", { withTimezone: true }),
|
|
6222
6227
|
verifiedAt: timestamp("verified_at", { withTimezone: true }),
|
|
6223
6228
|
lastError: text("last_error"),
|
|
6229
|
+
fixAttempts: integer("fix_attempts").notNull().default(0),
|
|
6224
6230
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6225
6231
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6226
6232
|
},
|
|
@@ -6277,6 +6283,78 @@ pgTable(
|
|
|
6277
6283
|
index("directory_account_status_idx").on(table.status)
|
|
6278
6284
|
]
|
|
6279
6285
|
);
|
|
6286
|
+
pgTable(
|
|
6287
|
+
"link_building_agent_job",
|
|
6288
|
+
{
|
|
6289
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6290
|
+
submissionId: uuid("submission_id").references(
|
|
6291
|
+
() => directorySubmission.id,
|
|
6292
|
+
{ onDelete: "set null" }
|
|
6293
|
+
),
|
|
6294
|
+
hostId: uuid("host_id").notNull(),
|
|
6295
|
+
commandId: uuid("command_id"),
|
|
6296
|
+
status: text("status").$type().notNull().default("pending"),
|
|
6297
|
+
requestJson: jsonb("request_json").$type().notNull().default({}),
|
|
6298
|
+
resultJson: jsonb("result_json").$type(),
|
|
6299
|
+
browserSessionId: text("browser_session_id"),
|
|
6300
|
+
hostName: text("host_name"),
|
|
6301
|
+
errorMessage: text("error_message"),
|
|
6302
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
6303
|
+
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
6304
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6305
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6306
|
+
},
|
|
6307
|
+
(table) => [
|
|
6308
|
+
index("idx_link_building_agent_job_status_created").on(
|
|
6309
|
+
table.status,
|
|
6310
|
+
table.createdAt
|
|
6311
|
+
),
|
|
6312
|
+
index("idx_link_building_agent_job_submission").on(table.submissionId),
|
|
6313
|
+
index("idx_link_building_agent_job_host_status").on(
|
|
6314
|
+
table.hostId,
|
|
6315
|
+
table.status
|
|
6316
|
+
)
|
|
6317
|
+
]
|
|
6318
|
+
);
|
|
6319
|
+
pgTable("github_api_budget", {
|
|
6320
|
+
tokenKey: text("token_key").primaryKey(),
|
|
6321
|
+
windowStartedAt: timestamp("window_started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6322
|
+
requestCount: integer("request_count").notNull().default(0),
|
|
6323
|
+
blockedUntil: timestamp("blocked_until", { withTimezone: true }),
|
|
6324
|
+
lastRemaining: integer("last_remaining"),
|
|
6325
|
+
lastResetAt: timestamp("last_reset_at", { withTimezone: true }),
|
|
6326
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6327
|
+
});
|
|
6328
|
+
pgTable("github_api_cache", {
|
|
6329
|
+
cacheKey: text("cache_key").primaryKey(),
|
|
6330
|
+
etag: text("etag"),
|
|
6331
|
+
body: text("body"),
|
|
6332
|
+
status: integer("status").notNull().default(200),
|
|
6333
|
+
contentType: text("content_type"),
|
|
6334
|
+
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
6335
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6336
|
+
});
|
|
6337
|
+
pgTable(
|
|
6338
|
+
"github_api_stat",
|
|
6339
|
+
{
|
|
6340
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6341
|
+
tokenKey: text("token_key").notNull(),
|
|
6342
|
+
jobId: text("job_id").notNull().default("unknown"),
|
|
6343
|
+
hourBucket: timestamp("hour_bucket", { withTimezone: true }).notNull(),
|
|
6344
|
+
requestCount: integer("request_count").notNull().default(0),
|
|
6345
|
+
errorCount: integer("error_count").notNull().default(0),
|
|
6346
|
+
cacheHitCount: integer("cache_hit_count").notNull().default(0),
|
|
6347
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6348
|
+
},
|
|
6349
|
+
(table) => [
|
|
6350
|
+
uniqueIndex("github_api_stat_token_job_hour_key").on(
|
|
6351
|
+
table.tokenKey,
|
|
6352
|
+
table.jobId,
|
|
6353
|
+
table.hourBucket
|
|
6354
|
+
),
|
|
6355
|
+
index("idx_github_api_stat_hour").on(table.hourBucket)
|
|
6356
|
+
]
|
|
6357
|
+
);
|
|
6280
6358
|
var githubToken = pgTable(
|
|
6281
6359
|
"github_token",
|
|
6282
6360
|
{
|
|
@@ -6448,6 +6526,34 @@ pgTable(
|
|
|
6448
6526
|
index("idx_refront_project_code").on(table.code)
|
|
6449
6527
|
]
|
|
6450
6528
|
);
|
|
6529
|
+
var refrontMember = pgTable(
|
|
6530
|
+
"refront_member",
|
|
6531
|
+
{
|
|
6532
|
+
/** Refront user id. */
|
|
6533
|
+
id: uuid("id").primaryKey(),
|
|
6534
|
+
name: text("name"),
|
|
6535
|
+
role: text("role"),
|
|
6536
|
+
teamId: text("team_id"),
|
|
6537
|
+
teamName: text("team_name"),
|
|
6538
|
+
/** True when the member has no explicit project assignments in Refront. */
|
|
6539
|
+
unrestricted: boolean("unrestricted").notNull().default(true),
|
|
6540
|
+
/** Derived wiki customer folder prefixes (empty when unrestricted). */
|
|
6541
|
+
wikiFolders: text("wiki_folders").array().notNull().default([]),
|
|
6542
|
+
syncedAt: timestamp("synced_at", { withTimezone: true }).notNull(),
|
|
6543
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6544
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6545
|
+
},
|
|
6546
|
+
(table) => [index("idx_refront_member_team").on(table.teamId)]
|
|
6547
|
+
);
|
|
6548
|
+
pgTable(
|
|
6549
|
+
"refront_member_project_access",
|
|
6550
|
+
{
|
|
6551
|
+
memberId: uuid("member_id").notNull().references(() => refrontMember.id, { onDelete: "cascade" }),
|
|
6552
|
+
projectId: uuid("project_id").notNull(),
|
|
6553
|
+
syncedAt: timestamp("synced_at", { withTimezone: true }).notNull()
|
|
6554
|
+
},
|
|
6555
|
+
(table) => [primaryKey({ columns: [table.memberId, table.projectId] })]
|
|
6556
|
+
);
|
|
6451
6557
|
pgTable(
|
|
6452
6558
|
"refront_source_sync_state",
|
|
6453
6559
|
{
|
|
@@ -6553,14 +6659,29 @@ pgTable(
|
|
|
6553
6659
|
lastVerifiedOn: date("last_verified_on"),
|
|
6554
6660
|
verifyCount: integer("verify_count").notNull().default(0),
|
|
6555
6661
|
verifyNote: text("verify_note"),
|
|
6662
|
+
dirty: boolean("dirty").notNull().default(false),
|
|
6663
|
+
vaultSyncedAt: timestamp("vault_synced_at", { withTimezone: true }),
|
|
6556
6664
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6557
6665
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6558
6666
|
},
|
|
6559
6667
|
(table) => [
|
|
6560
6668
|
index("idx_wiki_page_status").on(table.status),
|
|
6561
|
-
index("idx_wiki_page_last_verified").on(table.lastVerifiedOn)
|
|
6669
|
+
index("idx_wiki_page_last_verified").on(table.lastVerifiedOn),
|
|
6670
|
+
index("idx_wiki_page_dirty").on(table.dirty)
|
|
6562
6671
|
]
|
|
6563
6672
|
);
|
|
6673
|
+
pgTable(
|
|
6674
|
+
"wiki_change_log",
|
|
6675
|
+
{
|
|
6676
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6677
|
+
authorJob: text("author_job").notNull(),
|
|
6678
|
+
summary: text("summary").notNull().default(""),
|
|
6679
|
+
slugs: text("slugs").array().notNull().default([]),
|
|
6680
|
+
syncedAt: timestamp("synced_at", { withTimezone: true }),
|
|
6681
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
6682
|
+
},
|
|
6683
|
+
(table) => [index("idx_wiki_change_log_created").on(table.createdAt)]
|
|
6684
|
+
);
|
|
6564
6685
|
pgTable(
|
|
6565
6686
|
"wiki_page_flag",
|
|
6566
6687
|
{
|
|
@@ -6568,6 +6689,7 @@ pgTable(
|
|
|
6568
6689
|
pageSlug: text("page_slug").notNull(),
|
|
6569
6690
|
kind: text("kind").notNull(),
|
|
6570
6691
|
detail: text("detail").notNull().default(""),
|
|
6692
|
+
sourceRef: text("source_ref"),
|
|
6571
6693
|
createdBy: text("created_by"),
|
|
6572
6694
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6573
6695
|
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
@@ -6604,6 +6726,7 @@ pgTable(
|
|
|
6604
6726
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
6605
6727
|
slug: text("slug").notNull(),
|
|
6606
6728
|
caller: text("caller"),
|
|
6729
|
+
includedArchive: boolean("included_archive").notNull().default(false),
|
|
6607
6730
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
6608
6731
|
},
|
|
6609
6732
|
(table) => [
|
|
@@ -6629,7 +6752,479 @@ pgTable(
|
|
|
6629
6752
|
)
|
|
6630
6753
|
]
|
|
6631
6754
|
);
|
|
6755
|
+
var wikiActor = pgTable(
|
|
6756
|
+
"wiki_actor",
|
|
6757
|
+
{
|
|
6758
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6759
|
+
displayName: text("display_name").notNull(),
|
|
6760
|
+
kind: text("kind").notNull().default("external"),
|
|
6761
|
+
notes: text("notes"),
|
|
6762
|
+
wikiPageSlug: text("wiki_page_slug"),
|
|
6763
|
+
refrontCustomerId: uuid("refront_customer_id"),
|
|
6764
|
+
refrontMemberId: uuid("refront_member_id"),
|
|
6765
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6766
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6767
|
+
},
|
|
6768
|
+
(table) => [
|
|
6769
|
+
index("idx_wiki_actor_kind").on(table.kind),
|
|
6770
|
+
index("idx_wiki_actor_wiki_page_slug").on(table.wikiPageSlug)
|
|
6771
|
+
]
|
|
6772
|
+
);
|
|
6773
|
+
pgTable(
|
|
6774
|
+
"wiki_actor_identity",
|
|
6775
|
+
{
|
|
6776
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6777
|
+
actorId: uuid("actor_id").notNull().references(() => wikiActor.id, { onDelete: "cascade" }),
|
|
6778
|
+
identityHash: text("identity_hash").notNull(),
|
|
6779
|
+
kind: text("kind").notNull(),
|
|
6780
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
6781
|
+
},
|
|
6782
|
+
(table) => [
|
|
6783
|
+
uniqueIndex("wiki_actor_identity_kind_hash").on(
|
|
6784
|
+
table.kind,
|
|
6785
|
+
table.identityHash
|
|
6786
|
+
),
|
|
6787
|
+
index("idx_wiki_actor_identity_actor").on(table.actorId)
|
|
6788
|
+
]
|
|
6789
|
+
);
|
|
6790
|
+
pgTable(
|
|
6791
|
+
"wiki_actor_affiliation",
|
|
6792
|
+
{
|
|
6793
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6794
|
+
actorId: uuid("actor_id").notNull().references(() => wikiActor.id, { onDelete: "cascade" }),
|
|
6795
|
+
customerFolder: text("customer_folder"),
|
|
6796
|
+
refrontCustomerId: uuid("refront_customer_id"),
|
|
6797
|
+
refrontProjectId: uuid("refront_project_id"),
|
|
6798
|
+
affiliationKey: text("affiliation_key").notNull().unique(),
|
|
6799
|
+
signalCount: integer("signal_count").notNull().default(1),
|
|
6800
|
+
firstSeen: timestamp("first_seen", { withTimezone: true }).notNull().defaultNow(),
|
|
6801
|
+
lastSeen: timestamp("last_seen", { withTimezone: true }).notNull().defaultNow(),
|
|
6802
|
+
sampleSources: text("sample_sources").array().notNull().default([]),
|
|
6803
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6804
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6805
|
+
},
|
|
6806
|
+
(table) => [
|
|
6807
|
+
index("idx_wiki_actor_affiliation_actor").on(table.actorId),
|
|
6808
|
+
index("idx_wiki_actor_affiliation_customer_folder").on(
|
|
6809
|
+
table.customerFolder
|
|
6810
|
+
)
|
|
6811
|
+
]
|
|
6812
|
+
);
|
|
6813
|
+
pgTable(
|
|
6814
|
+
"intake_identity_unresolved",
|
|
6815
|
+
{
|
|
6816
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6817
|
+
identityHash: text("identity_hash").notNull(),
|
|
6818
|
+
kind: text("kind").notNull(),
|
|
6819
|
+
seenCount: integer("seen_count").notNull().default(1),
|
|
6820
|
+
sampleSignalIds: uuid("sample_signal_ids").array().notNull().default([]),
|
|
6821
|
+
sampleContext: text("sample_context").array().notNull().default([]),
|
|
6822
|
+
sampleSources: text("sample_sources").array().notNull().default([]),
|
|
6823
|
+
lastSeen: timestamp("last_seen", { withTimezone: true }).notNull().defaultNow(),
|
|
6824
|
+
insightFiledAt: timestamp("insight_filed_at", { withTimezone: true }),
|
|
6825
|
+
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
6826
|
+
resolvedActorId: uuid("resolved_actor_id").references(() => wikiActor.id, {
|
|
6827
|
+
onDelete: "set null"
|
|
6828
|
+
}),
|
|
6829
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
6830
|
+
},
|
|
6831
|
+
(table) => [
|
|
6832
|
+
uniqueIndex("intake_identity_unresolved_kind_hash").on(
|
|
6833
|
+
table.kind,
|
|
6834
|
+
table.identityHash
|
|
6835
|
+
)
|
|
6836
|
+
]
|
|
6837
|
+
);
|
|
6632
6838
|
var GITHUB_API = "https://api.github.com";
|
|
6839
|
+
var TRANSIENT_GITHUB_STATUSES = /* @__PURE__ */ new Set([
|
|
6840
|
+
429,
|
|
6841
|
+
502,
|
|
6842
|
+
503,
|
|
6843
|
+
504,
|
|
6844
|
+
520,
|
|
6845
|
+
521,
|
|
6846
|
+
522,
|
|
6847
|
+
523,
|
|
6848
|
+
524
|
|
6849
|
+
]);
|
|
6850
|
+
var DEFAULT_BUDGET_PER_MINUTE = 75;
|
|
6851
|
+
var BUDGET_STATE_KEY = "github_api_budget_per_minute";
|
|
6852
|
+
var DEFAULT_BLOCK_MS = 15 * 60 * 1e3;
|
|
6853
|
+
var MAX_ATTEMPTS = 4;
|
|
6854
|
+
var BASE_DELAY_MS = 750;
|
|
6855
|
+
var RATE_LIMIT_FLOOR = 50;
|
|
6856
|
+
var BUDGET_WAIT_ATTEMPTS = 4;
|
|
6857
|
+
var BUDGET_WAIT_CAP_MS = 15e3;
|
|
6858
|
+
var GitHubGatewayBlockedError = class extends Error {
|
|
6859
|
+
blockedUntil;
|
|
6860
|
+
constructor(blockedUntil, message) {
|
|
6861
|
+
super(
|
|
6862
|
+
message ?? `GitHub API circuit breaker open until ${blockedUntil.toISOString()}`
|
|
6863
|
+
);
|
|
6864
|
+
this.name = "GitHubGatewayBlockedError";
|
|
6865
|
+
this.blockedUntil = blockedUntil;
|
|
6866
|
+
}
|
|
6867
|
+
};
|
|
6868
|
+
var GitHubGatewayPausedError = class extends Error {
|
|
6869
|
+
constructor() {
|
|
6870
|
+
super("GitHub sync is paused (github_sync_paused app_setting)");
|
|
6871
|
+
this.name = "GitHubGatewayPausedError";
|
|
6872
|
+
}
|
|
6873
|
+
};
|
|
6874
|
+
var GitHubGatewayBudgetError = class extends Error {
|
|
6875
|
+
constructor(tokenKey) {
|
|
6876
|
+
super(`GitHub API budget exhausted for token ${tokenKey}`);
|
|
6877
|
+
this.name = "GitHubGatewayBudgetError";
|
|
6878
|
+
}
|
|
6879
|
+
};
|
|
6880
|
+
function sleep(ms) {
|
|
6881
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6882
|
+
}
|
|
6883
|
+
function summarizeGitHubErrorBody(body, maxLen = 240) {
|
|
6884
|
+
const trimmed = body.trim();
|
|
6885
|
+
if (!trimmed) return "";
|
|
6886
|
+
const looksLikeHtml = trimmed.startsWith("<!") || trimmed.startsWith("<html") || /<title>\s*Unicorn!/i.test(trimmed);
|
|
6887
|
+
if (looksLikeHtml) return "[GitHub HTML error page truncated]";
|
|
6888
|
+
if (trimmed.length <= maxLen) return trimmed;
|
|
6889
|
+
return `${trimmed.slice(0, maxLen)}\u2026`;
|
|
6890
|
+
}
|
|
6891
|
+
function isTransientGitHubStatus(status) {
|
|
6892
|
+
return TRANSIENT_GITHUB_STATUSES.has(status);
|
|
6893
|
+
}
|
|
6894
|
+
function hourBucket(now = /* @__PURE__ */ new Date()) {
|
|
6895
|
+
return new Date(
|
|
6896
|
+
Date.UTC(
|
|
6897
|
+
now.getUTCFullYear(),
|
|
6898
|
+
now.getUTCMonth(),
|
|
6899
|
+
now.getUTCDate(),
|
|
6900
|
+
now.getUTCHours(),
|
|
6901
|
+
0,
|
|
6902
|
+
0,
|
|
6903
|
+
0
|
|
6904
|
+
)
|
|
6905
|
+
);
|
|
6906
|
+
}
|
|
6907
|
+
function cacheKeyFor(url, tokenKey) {
|
|
6908
|
+
return createHash("sha256").update(`${tokenKey}:${url}`).digest("hex");
|
|
6909
|
+
}
|
|
6910
|
+
async function isGithubSyncPaused() {
|
|
6911
|
+
try {
|
|
6912
|
+
const rows = await getDb().execute(sql`
|
|
6913
|
+
SELECT value FROM wiki_sync_state
|
|
6914
|
+
WHERE key = 'github_sync_paused'
|
|
6915
|
+
LIMIT 1
|
|
6916
|
+
`);
|
|
6917
|
+
return Boolean(rows[0]?.value?.paused);
|
|
6918
|
+
} catch {
|
|
6919
|
+
return false;
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
async function getBlockedUntil(tokenKey) {
|
|
6923
|
+
const rows = await getDb().execute(
|
|
6924
|
+
sql`
|
|
6925
|
+
SELECT blocked_until FROM github_api_budget
|
|
6926
|
+
WHERE token_key = ${tokenKey}
|
|
6927
|
+
LIMIT 1
|
|
6928
|
+
`
|
|
6929
|
+
);
|
|
6930
|
+
const raw = rows[0]?.blocked_until;
|
|
6931
|
+
if (!raw) return null;
|
|
6932
|
+
const date2 = typeof raw === "string" ? new Date(raw) : raw;
|
|
6933
|
+
if (Number.isNaN(date2.getTime()) || date2.getTime() <= Date.now()) return null;
|
|
6934
|
+
return date2;
|
|
6935
|
+
}
|
|
6936
|
+
async function setGithubCircuitBreaker(tokenKey, blockedUntil) {
|
|
6937
|
+
await getDb().execute(sql`
|
|
6938
|
+
INSERT INTO github_api_budget (token_key, blocked_until, updated_at)
|
|
6939
|
+
VALUES (${tokenKey}, ${blockedUntil.toISOString()}::timestamptz, now())
|
|
6940
|
+
ON CONFLICT (token_key) DO UPDATE SET
|
|
6941
|
+
blocked_until = EXCLUDED.blocked_until,
|
|
6942
|
+
updated_at = now()
|
|
6943
|
+
`);
|
|
6944
|
+
}
|
|
6945
|
+
async function tryConsumeBudget(tokenKey, budgetPerMinute) {
|
|
6946
|
+
const updated = await getDb().execute(sql`
|
|
6947
|
+
UPDATE github_api_budget
|
|
6948
|
+
SET
|
|
6949
|
+
window_started_at = CASE
|
|
6950
|
+
WHEN window_started_at < now() - interval '60 seconds'
|
|
6951
|
+
THEN now()
|
|
6952
|
+
ELSE window_started_at
|
|
6953
|
+
END,
|
|
6954
|
+
request_count = CASE
|
|
6955
|
+
WHEN window_started_at < now() - interval '60 seconds'
|
|
6956
|
+
THEN 1
|
|
6957
|
+
ELSE request_count + 1
|
|
6958
|
+
END,
|
|
6959
|
+
updated_at = now()
|
|
6960
|
+
WHERE token_key = ${tokenKey}
|
|
6961
|
+
AND (
|
|
6962
|
+
window_started_at < now() - interval '60 seconds'
|
|
6963
|
+
OR request_count < ${budgetPerMinute}
|
|
6964
|
+
)
|
|
6965
|
+
RETURNING request_count
|
|
6966
|
+
`);
|
|
6967
|
+
if (updated[0]) return true;
|
|
6968
|
+
const inserted = await getDb().execute(sql`
|
|
6969
|
+
INSERT INTO github_api_budget (token_key, window_started_at, request_count, updated_at)
|
|
6970
|
+
VALUES (${tokenKey}, now(), 1, now())
|
|
6971
|
+
ON CONFLICT (token_key) DO NOTHING
|
|
6972
|
+
RETURNING request_count
|
|
6973
|
+
`);
|
|
6974
|
+
if (inserted[0]) return true;
|
|
6975
|
+
return false;
|
|
6976
|
+
}
|
|
6977
|
+
async function msUntilBudgetWindowReset(tokenKey) {
|
|
6978
|
+
const rows = await getDb().execute(sql`
|
|
6979
|
+
SELECT window_started_at FROM github_api_budget
|
|
6980
|
+
WHERE token_key = ${tokenKey}
|
|
6981
|
+
LIMIT 1
|
|
6982
|
+
`);
|
|
6983
|
+
const raw = rows[0]?.window_started_at;
|
|
6984
|
+
if (!raw) return 1e3;
|
|
6985
|
+
const started = typeof raw === "string" ? new Date(raw).getTime() : raw.getTime();
|
|
6986
|
+
if (!Number.isFinite(started)) return 1e3;
|
|
6987
|
+
const resetAt = started + 6e4;
|
|
6988
|
+
return Math.max(250, resetAt - Date.now());
|
|
6989
|
+
}
|
|
6990
|
+
async function resolveGithubBudgetPerMinute(override) {
|
|
6991
|
+
if (typeof override === "number" && override > 0) return Math.floor(override);
|
|
6992
|
+
const fromEnv = Number(process.env.GITHUB_API_BUDGET_PER_MINUTE);
|
|
6993
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0) return Math.floor(fromEnv);
|
|
6994
|
+
try {
|
|
6995
|
+
const rows = await getDb().execute(sql`
|
|
6996
|
+
SELECT value FROM wiki_sync_state
|
|
6997
|
+
WHERE key = ${BUDGET_STATE_KEY}
|
|
6998
|
+
LIMIT 1
|
|
6999
|
+
`);
|
|
7000
|
+
const perMinute = rows[0]?.value?.perMinute;
|
|
7001
|
+
if (typeof perMinute === "number" && perMinute > 0) {
|
|
7002
|
+
return Math.floor(perMinute);
|
|
7003
|
+
}
|
|
7004
|
+
} catch {
|
|
7005
|
+
}
|
|
7006
|
+
return DEFAULT_BUDGET_PER_MINUTE;
|
|
7007
|
+
}
|
|
7008
|
+
async function consumeBudgetOrWait(input) {
|
|
7009
|
+
for (let attempt = 1; attempt <= BUDGET_WAIT_ATTEMPTS; attempt++) {
|
|
7010
|
+
if (await tryConsumeBudget(input.tokenKey, input.budgetPerMinute)) {
|
|
7011
|
+
return;
|
|
7012
|
+
}
|
|
7013
|
+
if (input.priority === "interactive") {
|
|
7014
|
+
await sleep(BASE_DELAY_MS);
|
|
7015
|
+
continue;
|
|
7016
|
+
}
|
|
7017
|
+
if (attempt === BUDGET_WAIT_ATTEMPTS) {
|
|
7018
|
+
throw new GitHubGatewayBudgetError(input.tokenKey);
|
|
7019
|
+
}
|
|
7020
|
+
const waitMs = Math.min(
|
|
7021
|
+
await msUntilBudgetWindowReset(input.tokenKey),
|
|
7022
|
+
BUDGET_WAIT_CAP_MS
|
|
7023
|
+
);
|
|
7024
|
+
await sleep(waitMs);
|
|
7025
|
+
}
|
|
7026
|
+
throw new GitHubGatewayBudgetError(input.tokenKey);
|
|
7027
|
+
}
|
|
7028
|
+
async function recordStat(input) {
|
|
7029
|
+
const bucket = hourBucket().toISOString();
|
|
7030
|
+
await getDb().execute(sql`
|
|
7031
|
+
INSERT INTO github_api_stat (
|
|
7032
|
+
token_key, job_id, hour_bucket, request_count, error_count, cache_hit_count, updated_at
|
|
7033
|
+
)
|
|
7034
|
+
VALUES (
|
|
7035
|
+
${input.tokenKey},
|
|
7036
|
+
${input.jobId},
|
|
7037
|
+
${bucket}::timestamptz,
|
|
7038
|
+
1,
|
|
7039
|
+
${input.error ? 1 : 0},
|
|
7040
|
+
${input.cacheHit ? 1 : 0},
|
|
7041
|
+
now()
|
|
7042
|
+
)
|
|
7043
|
+
ON CONFLICT (token_key, job_id, hour_bucket) DO UPDATE SET
|
|
7044
|
+
request_count = github_api_stat.request_count + 1,
|
|
7045
|
+
error_count = github_api_stat.error_count + ${input.error ? 1 : 0},
|
|
7046
|
+
cache_hit_count = github_api_stat.cache_hit_count + ${input.cacheHit ? 1 : 0},
|
|
7047
|
+
updated_at = now()
|
|
7048
|
+
`);
|
|
7049
|
+
}
|
|
7050
|
+
async function loadEtagCache(key) {
|
|
7051
|
+
const rows = await getDb().execute(sql`
|
|
7052
|
+
SELECT etag, body, status FROM github_api_cache
|
|
7053
|
+
WHERE cache_key = ${key}
|
|
7054
|
+
LIMIT 1
|
|
7055
|
+
`);
|
|
7056
|
+
return rows[0] ?? null;
|
|
7057
|
+
}
|
|
7058
|
+
async function saveEtagCache(input) {
|
|
7059
|
+
await getDb().execute(sql`
|
|
7060
|
+
INSERT INTO github_api_cache (
|
|
7061
|
+
cache_key, etag, body, status, content_type, updated_at
|
|
7062
|
+
)
|
|
7063
|
+
VALUES (
|
|
7064
|
+
${input.key},
|
|
7065
|
+
${input.etag},
|
|
7066
|
+
${input.body},
|
|
7067
|
+
${input.status},
|
|
7068
|
+
${input.contentType},
|
|
7069
|
+
now()
|
|
7070
|
+
)
|
|
7071
|
+
ON CONFLICT (cache_key) DO UPDATE SET
|
|
7072
|
+
etag = EXCLUDED.etag,
|
|
7073
|
+
body = EXCLUDED.body,
|
|
7074
|
+
status = EXCLUDED.status,
|
|
7075
|
+
content_type = EXCLUDED.content_type,
|
|
7076
|
+
updated_at = now()
|
|
7077
|
+
`);
|
|
7078
|
+
}
|
|
7079
|
+
async function updateRateLimitHeaders(tokenKey, response) {
|
|
7080
|
+
const remaining = Number(response.headers.get("x-ratelimit-remaining"));
|
|
7081
|
+
const reset = Number(response.headers.get("x-ratelimit-reset"));
|
|
7082
|
+
if (!Number.isFinite(remaining) && !Number.isFinite(reset)) return;
|
|
7083
|
+
await getDb().execute(sql`
|
|
7084
|
+
INSERT INTO github_api_budget (
|
|
7085
|
+
token_key, last_remaining, last_reset_at, updated_at
|
|
7086
|
+
)
|
|
7087
|
+
VALUES (
|
|
7088
|
+
${tokenKey},
|
|
7089
|
+
${Number.isFinite(remaining) ? remaining : null},
|
|
7090
|
+
${Number.isFinite(reset) ? sql`to_timestamp(${reset})` : sql`NULL`},
|
|
7091
|
+
now()
|
|
7092
|
+
)
|
|
7093
|
+
ON CONFLICT (token_key) DO UPDATE SET
|
|
7094
|
+
last_remaining = COALESCE(EXCLUDED.last_remaining, github_api_budget.last_remaining),
|
|
7095
|
+
last_reset_at = COALESCE(EXCLUDED.last_reset_at, github_api_budget.last_reset_at),
|
|
7096
|
+
updated_at = now()
|
|
7097
|
+
`);
|
|
7098
|
+
if (Number.isFinite(remaining) && remaining < RATE_LIMIT_FLOOR) {
|
|
7099
|
+
const until = Number.isFinite(reset) ? new Date(reset * 1e3) : new Date(Date.now() + DEFAULT_BLOCK_MS);
|
|
7100
|
+
if (until.getTime() > Date.now()) {
|
|
7101
|
+
await setGithubCircuitBreaker(tokenKey, until);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
function tripBreakerFromResponse(response) {
|
|
7106
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
7107
|
+
if (Number.isFinite(retryAfter) && retryAfter > 0) {
|
|
7108
|
+
return new Date(Date.now() + retryAfter * 1e3);
|
|
7109
|
+
}
|
|
7110
|
+
const reset = Number(response.headers.get("x-ratelimit-reset"));
|
|
7111
|
+
if (Number.isFinite(reset) && reset * 1e3 > Date.now()) {
|
|
7112
|
+
return new Date(reset * 1e3);
|
|
7113
|
+
}
|
|
7114
|
+
return new Date(Date.now() + DEFAULT_BLOCK_MS);
|
|
7115
|
+
}
|
|
7116
|
+
async function githubGatewayFetch(url, init, options) {
|
|
7117
|
+
const priority = options.priority ?? "background";
|
|
7118
|
+
const budgetPerMinute = await resolveGithubBudgetPerMinute(
|
|
7119
|
+
options.budgetPerMinute
|
|
7120
|
+
);
|
|
7121
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
7122
|
+
const useCache = options.useEtagCache !== false && method === "GET";
|
|
7123
|
+
if (priority !== "interactive" && await isGithubSyncPaused()) {
|
|
7124
|
+
throw new GitHubGatewayPausedError();
|
|
7125
|
+
}
|
|
7126
|
+
const blocked = await getBlockedUntil(options.tokenKey);
|
|
7127
|
+
if (blocked) {
|
|
7128
|
+
if (priority === "interactive") {
|
|
7129
|
+
if (blocked.getTime() - Date.now() > 6e4) {
|
|
7130
|
+
throw new GitHubGatewayBlockedError(blocked);
|
|
7131
|
+
}
|
|
7132
|
+
} else {
|
|
7133
|
+
throw new GitHubGatewayBlockedError(blocked);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
await consumeBudgetOrWait({
|
|
7137
|
+
tokenKey: options.tokenKey,
|
|
7138
|
+
budgetPerMinute,
|
|
7139
|
+
priority
|
|
7140
|
+
});
|
|
7141
|
+
const headers = new Headers(init.headers);
|
|
7142
|
+
if (!headers.has("Authorization")) {
|
|
7143
|
+
headers.set("Authorization", `Bearer ${options.token}`);
|
|
7144
|
+
}
|
|
7145
|
+
if (!headers.has("Accept")) {
|
|
7146
|
+
headers.set("Accept", "application/vnd.github+json");
|
|
7147
|
+
}
|
|
7148
|
+
if (!headers.has("X-GitHub-Api-Version")) {
|
|
7149
|
+
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
7150
|
+
}
|
|
7151
|
+
const key = useCache ? cacheKeyFor(url, options.tokenKey) : null;
|
|
7152
|
+
if (key) {
|
|
7153
|
+
const cached = await loadEtagCache(key);
|
|
7154
|
+
if (cached?.etag) headers.set("If-None-Match", cached.etag);
|
|
7155
|
+
}
|
|
7156
|
+
let lastError = null;
|
|
7157
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
7158
|
+
const response = await fetch(url, { ...init, headers });
|
|
7159
|
+
await updateRateLimitHeaders(options.tokenKey, response);
|
|
7160
|
+
if (response.status === 304 && key) {
|
|
7161
|
+
const cached = await loadEtagCache(key);
|
|
7162
|
+
await recordStat({
|
|
7163
|
+
tokenKey: options.tokenKey,
|
|
7164
|
+
jobId: options.jobId,
|
|
7165
|
+
cacheHit: true
|
|
7166
|
+
});
|
|
7167
|
+
return new Response(cached?.body ?? "", {
|
|
7168
|
+
status: cached?.status ?? 200,
|
|
7169
|
+
headers: {
|
|
7170
|
+
"x-github-gateway-cache": "hit",
|
|
7171
|
+
"content-type": "application/json"
|
|
7172
|
+
}
|
|
7173
|
+
});
|
|
7174
|
+
}
|
|
7175
|
+
if (response.status === 403 || response.status === 429 || isTransientGitHubStatus(response.status)) {
|
|
7176
|
+
const bodyText = summarizeGitHubErrorBody(
|
|
7177
|
+
await response.clone().text().catch(() => "")
|
|
7178
|
+
);
|
|
7179
|
+
const secondary = response.status === 403 && /secondary rate limit|abuse detection/i.test(bodyText);
|
|
7180
|
+
if (response.status === 429 || secondary || isTransientGitHubStatus(response.status)) {
|
|
7181
|
+
const until = tripBreakerFromResponse(response);
|
|
7182
|
+
await setGithubCircuitBreaker(options.tokenKey, until);
|
|
7183
|
+
lastError = new Error(
|
|
7184
|
+
`GitHub ${response.status}${bodyText ? `: ${bodyText}` : ""}`
|
|
7185
|
+
);
|
|
7186
|
+
await recordStat({
|
|
7187
|
+
tokenKey: options.tokenKey,
|
|
7188
|
+
jobId: options.jobId,
|
|
7189
|
+
error: true
|
|
7190
|
+
});
|
|
7191
|
+
if (attempt === MAX_ATTEMPTS || secondary) {
|
|
7192
|
+
throw new GitHubGatewayBlockedError(until, lastError.message);
|
|
7193
|
+
}
|
|
7194
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
7195
|
+
const delayMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1e3 : BASE_DELAY_MS * 2 ** (attempt - 1);
|
|
7196
|
+
await sleep(delayMs);
|
|
7197
|
+
continue;
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
if (key && response.ok) {
|
|
7201
|
+
const etag = response.headers.get("etag");
|
|
7202
|
+
const body = await response.clone().text();
|
|
7203
|
+
await saveEtagCache({
|
|
7204
|
+
key,
|
|
7205
|
+
etag,
|
|
7206
|
+
body,
|
|
7207
|
+
status: response.status,
|
|
7208
|
+
contentType: response.headers.get("content-type")
|
|
7209
|
+
});
|
|
7210
|
+
}
|
|
7211
|
+
await recordStat({
|
|
7212
|
+
tokenKey: options.tokenKey,
|
|
7213
|
+
jobId: options.jobId,
|
|
7214
|
+
error: !response.ok
|
|
7215
|
+
});
|
|
7216
|
+
return response;
|
|
7217
|
+
}
|
|
7218
|
+
throw lastError ?? new Error("GitHub request failed after retries");
|
|
7219
|
+
}
|
|
7220
|
+
function gatewayOptions(token) {
|
|
7221
|
+
return {
|
|
7222
|
+
tokenKey: "default",
|
|
7223
|
+
token,
|
|
7224
|
+
jobId: "release-profile-quick-create",
|
|
7225
|
+
priority: "background"
|
|
7226
|
+
};
|
|
7227
|
+
}
|
|
6633
7228
|
var ReleaseProfileQuickCreateError = class extends Error {
|
|
6634
7229
|
constructor(message, code = "INTERNAL_SERVER_ERROR") {
|
|
6635
7230
|
super(message);
|
|
@@ -6656,18 +7251,20 @@ async function scanRepoForApps(repoFullName, githubToken2, ref = "main") {
|
|
|
6656
7251
|
"Content-Type": "application/json"
|
|
6657
7252
|
};
|
|
6658
7253
|
async function fetchContents(path) {
|
|
6659
|
-
const res = await
|
|
7254
|
+
const res = await githubGatewayFetch(
|
|
6660
7255
|
`${GITHUB_API}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
6661
|
-
{ headers }
|
|
7256
|
+
{ headers },
|
|
7257
|
+
gatewayOptions(githubToken2)
|
|
6662
7258
|
);
|
|
6663
7259
|
if (!res.ok) return null;
|
|
6664
7260
|
const data = await res.json();
|
|
6665
7261
|
return Array.isArray(data) ? data : null;
|
|
6666
7262
|
}
|
|
6667
7263
|
async function fileExists(path) {
|
|
6668
|
-
const res = await
|
|
7264
|
+
const res = await githubGatewayFetch(
|
|
6669
7265
|
`${GITHUB_API}/repos/${repoFullName}/contents/${path}?ref=${ref}`,
|
|
6670
|
-
{ headers, method: "HEAD" }
|
|
7266
|
+
{ headers, method: "HEAD" },
|
|
7267
|
+
gatewayOptions(githubToken2)
|
|
6671
7268
|
);
|
|
6672
7269
|
return res.ok;
|
|
6673
7270
|
}
|
|
@@ -6804,31 +7401,36 @@ async function ensureGitHubBranchExists(repoFullName, branchName, fromBranch, gi
|
|
|
6804
7401
|
Authorization: `Bearer ${githubToken2}`,
|
|
6805
7402
|
"Content-Type": "application/json"
|
|
6806
7403
|
};
|
|
6807
|
-
const checkRes = await
|
|
7404
|
+
const checkRes = await githubGatewayFetch(
|
|
6808
7405
|
`${GITHUB_API}/repos/${repoFullName}/git/ref/heads/${branchName}`,
|
|
6809
|
-
{ headers }
|
|
7406
|
+
{ headers },
|
|
7407
|
+
gatewayOptions(githubToken2)
|
|
6810
7408
|
);
|
|
6811
7409
|
if (checkRes.ok) return;
|
|
6812
7410
|
let sha;
|
|
6813
|
-
const sourceRes = await
|
|
7411
|
+
const sourceRes = await githubGatewayFetch(
|
|
6814
7412
|
`${GITHUB_API}/repos/${repoFullName}/git/ref/heads/${fromBranch}`,
|
|
6815
|
-
{ headers }
|
|
7413
|
+
{ headers },
|
|
7414
|
+
gatewayOptions(githubToken2)
|
|
6816
7415
|
);
|
|
6817
7416
|
if (sourceRes.ok) {
|
|
6818
7417
|
const data = await sourceRes.json();
|
|
6819
7418
|
sha = data.object?.sha;
|
|
6820
7419
|
} else {
|
|
6821
|
-
const repoRes = await
|
|
6822
|
-
|
|
6823
|
-
|
|
7420
|
+
const repoRes = await githubGatewayFetch(
|
|
7421
|
+
`${GITHUB_API}/repos/${repoFullName}`,
|
|
7422
|
+
{ headers },
|
|
7423
|
+
gatewayOptions(githubToken2)
|
|
7424
|
+
);
|
|
6824
7425
|
if (!repoRes.ok) {
|
|
6825
7426
|
throw new Error(`Could not access repository ${repoFullName}`);
|
|
6826
7427
|
}
|
|
6827
7428
|
const repo = await repoRes.json();
|
|
6828
7429
|
const defaultBranch = repo.default_branch ?? "main";
|
|
6829
|
-
const defaultRes = await
|
|
7430
|
+
const defaultRes = await githubGatewayFetch(
|
|
6830
7431
|
`${GITHUB_API}/repos/${repoFullName}/git/ref/heads/${defaultBranch}`,
|
|
6831
|
-
{ headers }
|
|
7432
|
+
{ headers },
|
|
7433
|
+
gatewayOptions(githubToken2)
|
|
6832
7434
|
);
|
|
6833
7435
|
if (!defaultRes.ok) {
|
|
6834
7436
|
throw new Error(`Could not resolve a source branch for ${repoFullName}`);
|
|
@@ -6839,13 +7441,14 @@ async function ensureGitHubBranchExists(repoFullName, branchName, fromBranch, gi
|
|
|
6839
7441
|
if (!sha) {
|
|
6840
7442
|
throw new Error(`Could not determine SHA to create branch ${branchName}`);
|
|
6841
7443
|
}
|
|
6842
|
-
const createRes = await
|
|
7444
|
+
const createRes = await githubGatewayFetch(
|
|
6843
7445
|
`${GITHUB_API}/repos/${repoFullName}/git/refs`,
|
|
6844
7446
|
{
|
|
6845
7447
|
method: "POST",
|
|
6846
7448
|
headers,
|
|
6847
7449
|
body: JSON.stringify({ ref: `refs/heads/${branchName}`, sha })
|
|
6848
|
-
}
|
|
7450
|
+
},
|
|
7451
|
+
gatewayOptions(githubToken2)
|
|
6849
7452
|
);
|
|
6850
7453
|
if (!createRes.ok && createRes.status !== 422) {
|
|
6851
7454
|
const body = await createRes.text().catch(() => "");
|
|
@@ -8032,7 +8635,7 @@ function getEncryptionKey() {
|
|
|
8032
8635
|
throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
|
|
8033
8636
|
return buf;
|
|
8034
8637
|
}
|
|
8035
|
-
function encrypt(
|
|
8638
|
+
function encrypt(text16) {
|
|
8036
8639
|
const key = getEncryptionKey();
|
|
8037
8640
|
const iv = randomBytes(ENC_IV_LENGTH);
|
|
8038
8641
|
const cipher = createCipheriv(
|
|
@@ -8040,7 +8643,7 @@ function encrypt(text14) {
|
|
|
8040
8643
|
new Uint8Array(key),
|
|
8041
8644
|
new Uint8Array(iv)
|
|
8042
8645
|
);
|
|
8043
|
-
let encrypted = cipher.update(
|
|
8646
|
+
let encrypted = cipher.update(text16, "utf8", "hex");
|
|
8044
8647
|
encrypted += cipher.final("hex");
|
|
8045
8648
|
const authTag = cipher.getAuthTag();
|
|
8046
8649
|
return Buffer.concat([
|
|
@@ -8760,10 +9363,10 @@ async function r2GetObjectRange(bucket, key, range) {
|
|
|
8760
9363
|
const body = result.Body;
|
|
8761
9364
|
if (!body?.transformToString)
|
|
8762
9365
|
throw new Error("R2 returned no readable body");
|
|
8763
|
-
const
|
|
9366
|
+
const text16 = await body.transformToString();
|
|
8764
9367
|
const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
|
|
8765
9368
|
return `${header}
|
|
8766
|
-
${
|
|
9369
|
+
${text16}`;
|
|
8767
9370
|
} catch (e) {
|
|
8768
9371
|
throw r2WrapError(bucket, key, e);
|
|
8769
9372
|
}
|
|
@@ -9126,15 +9729,15 @@ async function sftpRead(opts, filePath, proxy, options) {
|
|
|
9126
9729
|
clearTimeout(timer);
|
|
9127
9730
|
cleanup?.();
|
|
9128
9731
|
cleanup = void 0;
|
|
9129
|
-
const
|
|
9732
|
+
const text16 = Buffer.concat(
|
|
9130
9733
|
chunks.map((ch) => new Uint8Array(ch))
|
|
9131
9734
|
).toString("utf-8");
|
|
9132
9735
|
if (!isWholeFileRequest) {
|
|
9133
9736
|
const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
|
|
9134
9737
|
resolve(`${header}
|
|
9135
|
-
${
|
|
9738
|
+
${text16}`);
|
|
9136
9739
|
} else {
|
|
9137
|
-
resolve(
|
|
9740
|
+
resolve(text16);
|
|
9138
9741
|
}
|
|
9139
9742
|
});
|
|
9140
9743
|
rs.on("error", (e) => {
|
|
@@ -9187,7 +9790,7 @@ var TOOL_CACHE_TTL_MS = {
|
|
|
9187
9790
|
"docker-list": 3e4
|
|
9188
9791
|
};
|
|
9189
9792
|
var TOOL_CACHE = /* @__PURE__ */ new Map();
|
|
9190
|
-
function
|
|
9793
|
+
function cacheKeyFor2(name, args2) {
|
|
9191
9794
|
const { noCache: _ignored, ...rest } = args2;
|
|
9192
9795
|
const sorted = Object.fromEntries(
|
|
9193
9796
|
Object.entries(rest).sort(([a], [b]) => a.localeCompare(b))
|
|
@@ -9198,7 +9801,7 @@ function getCachedTool(name, args2) {
|
|
|
9198
9801
|
const ttl = TOOL_CACHE_TTL_MS[name];
|
|
9199
9802
|
if (!ttl || ttl <= 0) return void 0;
|
|
9200
9803
|
if (args2.noCache === true) return void 0;
|
|
9201
|
-
const k =
|
|
9804
|
+
const k = cacheKeyFor2(name, args2);
|
|
9202
9805
|
const e = TOOL_CACHE.get(k);
|
|
9203
9806
|
if (!e) return void 0;
|
|
9204
9807
|
if (e.expiresAt < Date.now()) {
|
|
@@ -9211,7 +9814,7 @@ function setCachedTool(name, args2, value) {
|
|
|
9211
9814
|
const ttl = TOOL_CACHE_TTL_MS[name];
|
|
9212
9815
|
if (!ttl || ttl <= 0) return;
|
|
9213
9816
|
if (args2.noCache === true) return;
|
|
9214
|
-
TOOL_CACHE.set(
|
|
9817
|
+
TOOL_CACHE.set(cacheKeyFor2(name, args2), {
|
|
9215
9818
|
value,
|
|
9216
9819
|
expiresAt: Date.now() + ttl
|
|
9217
9820
|
});
|
|
@@ -9281,11 +9884,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
|
|
|
9281
9884
|
if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
|
|
9282
9885
|
return e.names;
|
|
9283
9886
|
}
|
|
9284
|
-
function truncateForLLM(
|
|
9285
|
-
const totalBytes = Buffer.byteLength(
|
|
9887
|
+
function truncateForLLM(text16, maxBytes) {
|
|
9888
|
+
const totalBytes = Buffer.byteLength(text16, "utf8");
|
|
9286
9889
|
if (totalBytes <= maxBytes)
|
|
9287
|
-
return { text:
|
|
9288
|
-
const buf = Buffer.from(
|
|
9890
|
+
return { text: text16, truncated: false, totalBytes, shownBytes: totalBytes };
|
|
9891
|
+
const buf = Buffer.from(text16, "utf8");
|
|
9289
9892
|
let cut = maxBytes;
|
|
9290
9893
|
while (cut > 0 && (buf[cut] & 192) === 128) cut--;
|
|
9291
9894
|
const head = buf.subarray(0, cut).toString("utf8");
|
|
@@ -9316,10 +9919,10 @@ function postprocessResult(result, meta) {
|
|
|
9316
9919
|
if (!result.content?.length) return result;
|
|
9317
9920
|
if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
|
|
9318
9921
|
const block = result.content[0];
|
|
9319
|
-
let
|
|
9320
|
-
const trunc = truncateForLLM(
|
|
9922
|
+
let text16 = String(block.text ?? "");
|
|
9923
|
+
const trunc = truncateForLLM(text16, RESPONSE_MAX_BYTES);
|
|
9321
9924
|
if (trunc.truncated) {
|
|
9322
|
-
|
|
9925
|
+
text16 = trunc.text + "\n\n... " + buildTruncationHint(
|
|
9323
9926
|
meta.toolName,
|
|
9324
9927
|
meta.args,
|
|
9325
9928
|
trunc.totalBytes,
|
|
@@ -9333,11 +9936,11 @@ function postprocessResult(result, meta) {
|
|
|
9333
9936
|
const parts = [`took ${tookStr}`, sizeStr];
|
|
9334
9937
|
if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
|
|
9335
9938
|
if (meta.cached) parts.push("cached");
|
|
9336
|
-
|
|
9939
|
+
text16 = `${text16}
|
|
9337
9940
|
|
|
9338
9941
|
[${parts.join(", ")}]`;
|
|
9339
9942
|
}
|
|
9340
|
-
return { ...result, content: [{ ...block, text:
|
|
9943
|
+
return { ...result, content: [{ ...block, text: text16 }] };
|
|
9341
9944
|
}
|
|
9342
9945
|
function buildPipelineScript(commands, shell, marker, stopOnError) {
|
|
9343
9946
|
if (shell === "powershell") {
|
|
@@ -10123,11 +10726,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
10123
10726
|
applied_by TEXT
|
|
10124
10727
|
);
|
|
10125
10728
|
`.trim();
|
|
10126
|
-
function normaliseMigrationSql(
|
|
10127
|
-
return
|
|
10729
|
+
function normaliseMigrationSql(sql29) {
|
|
10730
|
+
return sql29.replace(/\r\n/g, "\n").trim() + "\n";
|
|
10128
10731
|
}
|
|
10129
|
-
function migrationSha256(
|
|
10130
|
-
return createHash("sha256").update(
|
|
10732
|
+
function migrationSha256(sql29) {
|
|
10733
|
+
return createHash("sha256").update(sql29.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
10131
10734
|
}
|
|
10132
10735
|
function dollarQuoteTag(value) {
|
|
10133
10736
|
let tag = "_mcp";
|
|
@@ -10393,7 +10996,7 @@ var DNS_MIN_INTERVAL_MS = 750;
|
|
|
10393
10996
|
var DNS_RETRY_BACKOFF_MS = 1500;
|
|
10394
10997
|
var dnsZoneQueue = /* @__PURE__ */ new Map();
|
|
10395
10998
|
var dnsLastPutAt = /* @__PURE__ */ new Map();
|
|
10396
|
-
function
|
|
10999
|
+
function sleep2(ms) {
|
|
10397
11000
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10398
11001
|
}
|
|
10399
11002
|
async function withDnsZoneLock(domain, fn) {
|
|
@@ -10402,7 +11005,7 @@ async function withDnsZoneLock(domain, fn) {
|
|
|
10402
11005
|
const run = previous.then(async () => {
|
|
10403
11006
|
const last = dnsLastPutAt.get(key) ?? 0;
|
|
10404
11007
|
const wait = DNS_MIN_INTERVAL_MS - (Date.now() - last);
|
|
10405
|
-
if (wait > 0) await
|
|
11008
|
+
if (wait > 0) await sleep2(wait);
|
|
10406
11009
|
return fn();
|
|
10407
11010
|
});
|
|
10408
11011
|
const queued = run.finally(() => {
|
|
@@ -11894,13 +12497,17 @@ var TOOLS = [
|
|
|
11894
12497
|
},
|
|
11895
12498
|
{
|
|
11896
12499
|
name: "get-wiki-page",
|
|
11897
|
-
description: "Fetch one full knowledge-wiki page (markdown + frontmatter + sources) by the `slug` from a search-wiki result.
|
|
12500
|
+
description: "Fetch one full knowledge-wiki page (markdown + frontmatter + sources) by the `slug` from a search-wiki result. Active sources are capped (~10) and enriched with title/date for team-memory refs so you can pick 1\u20132 relevant transcripts via get-team-memory instead of fetching blindly. If the page itself does not answer your question, call again with `include_all_sources: true` to see the full active list plus the archive, then fetch only the refs you need. If an archived source held the real answer, flag the page with kind `archive-solution` + that `source_ref` so the reviewer can promote it.",
|
|
11898
12501
|
inputSchema: {
|
|
11899
12502
|
type: "object",
|
|
11900
12503
|
properties: {
|
|
11901
12504
|
slug: {
|
|
11902
12505
|
type: "string",
|
|
11903
12506
|
description: "Page slug from a search-wiki hit, e.g. 'wiki/engineering/team-memory'."
|
|
12507
|
+
},
|
|
12508
|
+
include_all_sources: {
|
|
12509
|
+
type: "boolean",
|
|
12510
|
+
description: "Return every active source plus the archived history (also enriched). Default false \u2014 compact active list only."
|
|
11904
12511
|
}
|
|
11905
12512
|
},
|
|
11906
12513
|
required: ["slug"]
|
|
@@ -11908,7 +12515,7 @@ var TOOLS = [
|
|
|
11908
12515
|
},
|
|
11909
12516
|
{
|
|
11910
12517
|
name: "flag-wiki-page",
|
|
11911
|
-
description: "Report that a knowledge-wiki page was WRONG, outdated or incomplete when you actually used it during real work. This is the wiki's feedback loop: open flags jump the daily reviewer queue and show in the backoffice review panel, so the page gets fixed instead of misleading the next agent. Call it right after discovering the mismatch, with a concrete `detail` of what was wrong and what turned out to be true.",
|
|
12518
|
+
description: "Report that a knowledge-wiki page was WRONG, outdated or incomplete when you actually used it during real work. This is the wiki's feedback loop: open flags jump the daily reviewer queue and show in the backoffice review panel, so the page gets fixed instead of misleading the next agent. Call it right after discovering the mismatch, with a concrete `detail` of what was wrong and what turned out to be true. Use kind `archive-solution` (with `source_ref`) when the page itself missed the answer but an archived source had it \u2014 the reviewer will promote that source and update the page.",
|
|
11912
12519
|
inputSchema: {
|
|
11913
12520
|
type: "object",
|
|
11914
12521
|
properties: {
|
|
@@ -11923,13 +12530,18 @@ var TOOLS = [
|
|
|
11923
12530
|
"stale",
|
|
11924
12531
|
"duplicate",
|
|
11925
12532
|
"unverified",
|
|
11926
|
-
"obsolete-candidate"
|
|
12533
|
+
"obsolete-candidate",
|
|
12534
|
+
"archive-solution"
|
|
11927
12535
|
],
|
|
11928
|
-
description: "feedback (default) = wrong/incomplete in practice; stale = outdated vs current reality; duplicate = covers the same thing as another page; obsolete-candidate = the described system/practice appears no longer in use."
|
|
12536
|
+
description: "feedback (default) = wrong/incomplete in practice; stale = outdated vs current reality; duplicate = covers the same thing as another page; obsolete-candidate = the described system/practice appears no longer in use; archive-solution = answer was found in an archived source (pass source_ref)."
|
|
11929
12537
|
},
|
|
11930
12538
|
detail: {
|
|
11931
12539
|
type: "string",
|
|
11932
12540
|
description: "What was wrong and what is actually true (be concrete: the claim, the observed reality, ticket/commit refs if any). Min 5 chars."
|
|
12541
|
+
},
|
|
12542
|
+
source_ref: {
|
|
12543
|
+
type: "string",
|
|
12544
|
+
description: "For archive-solution: the archived source that held the answer, e.g. 'team-memory:<uuid>' or 'ticket:2026-DASMG-032'."
|
|
11933
12545
|
}
|
|
11934
12546
|
},
|
|
11935
12547
|
required: ["slug", "detail"]
|
|
@@ -12244,7 +12856,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
12244
12856
|
]
|
|
12245
12857
|
};
|
|
12246
12858
|
}
|
|
12247
|
-
const lines = data.hits.map((hit,
|
|
12859
|
+
const lines = data.hits.map((hit, index13) => {
|
|
12248
12860
|
const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
|
|
12249
12861
|
const repo = hit.repo ?? "unknown repo";
|
|
12250
12862
|
const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
|
|
@@ -12254,7 +12866,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
12254
12866
|
...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
|
|
12255
12867
|
].filter(Boolean);
|
|
12256
12868
|
const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
|
|
12257
|
-
return `${
|
|
12869
|
+
return `${index13 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
|
|
12258
12870
|
id: ${hit.id}
|
|
12259
12871
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
|
|
12260
12872
|
});
|
|
@@ -12461,12 +13073,12 @@ Searchable via search-team-memory once indexing completes (usually immediate).`
|
|
|
12461
13073
|
]
|
|
12462
13074
|
};
|
|
12463
13075
|
}
|
|
12464
|
-
const lines = data.hits.map((hit,
|
|
13076
|
+
const lines = data.hits.map((hit, index13) => {
|
|
12465
13077
|
const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
|
|
12466
13078
|
const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
|
|
12467
13079
|
const sources = hit.sources.length ? `
|
|
12468
13080
|
sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
|
|
12469
|
-
return `${
|
|
13081
|
+
return `${index13 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
|
|
12470
13082
|
slug: ${hit.slug}${sources}
|
|
12471
13083
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
|
|
12472
13084
|
});
|
|
@@ -12488,13 +13100,17 @@ Call get-wiki-page with a slug for the full page; follow its sources (team-memor
|
|
|
12488
13100
|
if (!slug) {
|
|
12489
13101
|
return { content: [{ type: "text", text: "Error: slug is required" }] };
|
|
12490
13102
|
}
|
|
13103
|
+
const includeAllSources = a.include_all_sources === true;
|
|
12491
13104
|
const res = await fetch(`${dashboardBaseUrl}/api/wiki/page`, {
|
|
12492
13105
|
method: "POST",
|
|
12493
13106
|
headers: {
|
|
12494
13107
|
"content-type": "application/json",
|
|
12495
13108
|
authorization: `Bearer ${apiKey}`
|
|
12496
13109
|
},
|
|
12497
|
-
body: JSON.stringify({
|
|
13110
|
+
body: JSON.stringify({
|
|
13111
|
+
slug,
|
|
13112
|
+
include_all_sources: includeAllSources
|
|
13113
|
+
})
|
|
12498
13114
|
});
|
|
12499
13115
|
if (!res.ok) {
|
|
12500
13116
|
const detail = await res.text().catch(() => "");
|
|
@@ -12516,14 +13132,52 @@ Call get-wiki-page with a slug for the full page; follow its sources (team-memor
|
|
|
12516
13132
|
};
|
|
12517
13133
|
}
|
|
12518
13134
|
const page = data.page;
|
|
12519
|
-
const
|
|
12520
|
-
|
|
13135
|
+
const formatSource = (s) => {
|
|
13136
|
+
const base = `${s.type}:${s.ref}`;
|
|
13137
|
+
if (s.title) {
|
|
13138
|
+
const date2 = s.date ? ` (${s.date})` : "";
|
|
13139
|
+
return `${base} \u2014 "${s.title}"${date2}`;
|
|
13140
|
+
}
|
|
13141
|
+
return base;
|
|
13142
|
+
};
|
|
13143
|
+
const totalActive = page.sourcesTotalActive ?? page.sources.length;
|
|
13144
|
+
const totalArchived = page.sourcesTotalArchived ?? 0;
|
|
13145
|
+
const sourceLines = page.sources.map(formatSource);
|
|
13146
|
+
let sourcesBlock = "";
|
|
13147
|
+
if (sourceLines.length > 0) {
|
|
13148
|
+
const header = page.sourcesTruncated ? `Sources (${sourceLines.length} of ${totalActive} active):` : `Sources (${totalActive} active):`;
|
|
13149
|
+
sourcesBlock = `
|
|
13150
|
+
${header}
|
|
13151
|
+
${sourceLines.map((l) => `- ${l}`).join("\n")}`;
|
|
13152
|
+
if (page.sourcesTruncated || !includeAllSources && totalArchived > 0) {
|
|
13153
|
+
const moreActive = Math.max(0, totalActive - sourceLines.length);
|
|
13154
|
+
const parts = [];
|
|
13155
|
+
if (moreActive > 0) parts.push(`+${moreActive} more active`);
|
|
13156
|
+
if (totalArchived > 0) parts.push(`+${totalArchived} archived`);
|
|
13157
|
+
sourcesBlock += `
|
|
13158
|
+
\u2026 ${parts.join(", ")} (call again with include_all_sources=true). If an archived source answers your question, flag-wiki-page kind=archive-solution with that source_ref.`;
|
|
13159
|
+
}
|
|
13160
|
+
} else if (totalArchived > 0 && !includeAllSources) {
|
|
13161
|
+
sourcesBlock = `
|
|
13162
|
+
Sources: none active, ${totalArchived} archived (call with include_all_sources=true to list them).`;
|
|
13163
|
+
}
|
|
13164
|
+
const activeKeys = new Set(
|
|
13165
|
+
page.sources.map((s) => `${s.type}:${s.ref}`)
|
|
13166
|
+
);
|
|
13167
|
+
const archiveOnly = (page.sourcesArchive ?? []).filter(
|
|
13168
|
+
(s) => !activeKeys.has(`${s.type}:${s.ref}`)
|
|
13169
|
+
);
|
|
13170
|
+
if (includeAllSources && archiveOnly.length > 0) {
|
|
13171
|
+
sourcesBlock += `
|
|
13172
|
+
Archived sources (${archiveOnly.length}):
|
|
13173
|
+
` + archiveOnly.map((s) => `- ${formatSource(s)}`).join("\n");
|
|
13174
|
+
}
|
|
12521
13175
|
return {
|
|
12522
13176
|
content: [
|
|
12523
13177
|
{
|
|
12524
13178
|
type: "text",
|
|
12525
13179
|
text: `# ${page.title}
|
|
12526
|
-
[${page.slug} \xB7 ${page.status} \xB7 updated ${page.pageUpdatedOn ?? "unknown"}` + (page.tags.length ? ` \xB7 ${page.tags.join(", ")}` : "") + `]${
|
|
13180
|
+
[${page.slug} \xB7 ${page.status} \xB7 updated ${page.pageUpdatedOn ?? "unknown"}` + (page.tags.length ? ` \xB7 ${page.tags.join(", ")}` : "") + `]${sourcesBlock}
|
|
12527
13181
|
|
|
12528
13182
|
` + page.content
|
|
12529
13183
|
}
|
|
@@ -12549,15 +13203,22 @@ Sources: ${page.sources.map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
|
|
|
12549
13203
|
"stale",
|
|
12550
13204
|
"duplicate",
|
|
12551
13205
|
"unverified",
|
|
12552
|
-
"obsolete-candidate"
|
|
13206
|
+
"obsolete-candidate",
|
|
13207
|
+
"archive-solution"
|
|
12553
13208
|
].includes(a.kind) ? a.kind : "feedback";
|
|
13209
|
+
const sourceRef = typeof a.source_ref === "string" ? a.source_ref.trim() : "";
|
|
12554
13210
|
const res = await fetch(`${dashboardBaseUrl}/api/wiki/flag`, {
|
|
12555
13211
|
method: "POST",
|
|
12556
13212
|
headers: {
|
|
12557
13213
|
"content-type": "application/json",
|
|
12558
13214
|
authorization: `Bearer ${apiKey}`
|
|
12559
13215
|
},
|
|
12560
|
-
body: JSON.stringify({
|
|
13216
|
+
body: JSON.stringify({
|
|
13217
|
+
slug,
|
|
13218
|
+
kind,
|
|
13219
|
+
detail,
|
|
13220
|
+
...sourceRef ? { source_ref: sourceRef } : {}
|
|
13221
|
+
})
|
|
12561
13222
|
});
|
|
12562
13223
|
if (!res.ok) {
|
|
12563
13224
|
const errDetail = await res.text().catch(() => "");
|
|
@@ -12581,11 +13242,12 @@ Sources: ${page.sources.map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
|
|
|
12581
13242
|
]
|
|
12582
13243
|
};
|
|
12583
13244
|
}
|
|
13245
|
+
const dedupeNote = data.flag.deduped ? " (already had an open flag for this source \u2014 reused)" : "";
|
|
12584
13246
|
return {
|
|
12585
13247
|
content: [
|
|
12586
13248
|
{
|
|
12587
13249
|
type: "text",
|
|
12588
|
-
text: `Flagged ${data.flag.slug} (${kind}). The daily wiki reviewer will pick it up next run; it also shows in the backoffice review queue.`
|
|
13250
|
+
text: `Flagged ${data.flag.slug} (${kind}${sourceRef ? `, ${sourceRef}` : ""})${dedupeNote}. The daily wiki reviewer will pick it up next run; it also shows in the backoffice review queue.`
|
|
12589
13251
|
}
|
|
12590
13252
|
]
|
|
12591
13253
|
};
|
|
@@ -13673,8 +14335,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
13673
14335
|
};
|
|
13674
14336
|
const filtered = sortRows(applyFilter(only.rows));
|
|
13675
14337
|
if (format === "json") {
|
|
13676
|
-
const
|
|
13677
|
-
return { content: [{ type: "text", text:
|
|
14338
|
+
const text17 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
|
|
14339
|
+
return { content: [{ type: "text", text: text17 }] };
|
|
13678
14340
|
}
|
|
13679
14341
|
if (groupByProject) {
|
|
13680
14342
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -13701,8 +14363,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
13701
14363
|
}
|
|
13702
14364
|
const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
|
|
13703
14365
|
const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
|
|
13704
|
-
const
|
|
13705
|
-
return { content: [{ type: "text", text:
|
|
14366
|
+
const text16 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
|
|
14367
|
+
return { content: [{ type: "text", text: text16 }] };
|
|
13706
14368
|
}
|
|
13707
14369
|
if (format === "json") {
|
|
13708
14370
|
const lines = [];
|
|
@@ -15226,7 +15888,7 @@ ${lines.join("\n")}`
|
|
|
15226
15888
|
);
|
|
15227
15889
|
let current = await fetchZone();
|
|
15228
15890
|
if (current.data.records.length === 0) {
|
|
15229
|
-
await
|
|
15891
|
+
await sleep2(DNS_RETRY_BACKOFF_MS);
|
|
15230
15892
|
current = await fetchZone();
|
|
15231
15893
|
}
|
|
15232
15894
|
let nextRecords;
|
|
@@ -15320,7 +15982,7 @@ ${lines.join("\n")}`
|
|
|
15320
15982
|
);
|
|
15321
15983
|
} catch (err) {
|
|
15322
15984
|
if (!isTransientMijnhostError(err)) throw err;
|
|
15323
|
-
await
|
|
15985
|
+
await sleep2(DNS_RETRY_BACKOFF_MS);
|
|
15324
15986
|
const refetched = await fetchZone();
|
|
15325
15987
|
const stillNeedsApply = action === "create" ? !refetched.data.records.some(
|
|
15326
15988
|
(r) => r.type === type && r.name === dnsName && r.value === (typeof a.value === "string" ? a.value : "")
|
package/package.json
CHANGED