@mgsoftwarebv/mg-dashboard-mcp 7.4.13 → 7.4.14
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 +779 -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 {
|
|
@@ -3793,10 +3813,10 @@ var ZodObject = class _ZodObject extends ZodType {
|
|
|
3793
3813
|
// }) as any;
|
|
3794
3814
|
// return merged;
|
|
3795
3815
|
// }
|
|
3796
|
-
catchall(
|
|
3816
|
+
catchall(index19) {
|
|
3797
3817
|
return new _ZodObject({
|
|
3798
3818
|
...this._def,
|
|
3799
|
-
catchall:
|
|
3819
|
+
catchall: index19
|
|
3800
3820
|
});
|
|
3801
3821
|
}
|
|
3802
3822
|
pick(mask) {
|
|
@@ -4114,9 +4134,9 @@ function mergeValues(a, b) {
|
|
|
4114
4134
|
return { valid: false };
|
|
4115
4135
|
}
|
|
4116
4136
|
const newArray = [];
|
|
4117
|
-
for (let
|
|
4118
|
-
const itemA = a[
|
|
4119
|
-
const itemB = b[
|
|
4137
|
+
for (let index19 = 0; index19 < a.length; index19++) {
|
|
4138
|
+
const itemA = a[index19];
|
|
4139
|
+
const itemB = b[index19];
|
|
4120
4140
|
const sharedValue = mergeValues(itemA, itemB);
|
|
4121
4141
|
if (!sharedValue.valid) {
|
|
4122
4142
|
return { valid: false };
|
|
@@ -4322,10 +4342,10 @@ var ZodMap = class extends ZodType {
|
|
|
4322
4342
|
}
|
|
4323
4343
|
const keyType = this._def.keyType;
|
|
4324
4344
|
const valueType = this._def.valueType;
|
|
4325
|
-
const pairs = [...ctx.data.entries()].map(([key, value],
|
|
4345
|
+
const pairs = [...ctx.data.entries()].map(([key, value], index19) => {
|
|
4326
4346
|
return {
|
|
4327
|
-
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [
|
|
4328
|
-
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [
|
|
4347
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index19, "key"])),
|
|
4348
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index19, "value"]))
|
|
4329
4349
|
};
|
|
4330
4350
|
});
|
|
4331
4351
|
if (ctx.common.async) {
|
|
@@ -5340,13 +5360,19 @@ pgTable(
|
|
|
5340
5360
|
verifyNote: text("verify_note"),
|
|
5341
5361
|
dirty: boolean("dirty").notNull().default(false),
|
|
5342
5362
|
vaultSyncedAt: timestamp("vault_synced_at", { withTimezone: true }),
|
|
5363
|
+
/** Reviewer / itemized-phase lease holder. */
|
|
5364
|
+
claimedBy: text("claimed_by"),
|
|
5365
|
+
claimedAt: timestamp("claimed_at", { withTimezone: true }),
|
|
5366
|
+
/** Expired leases are reclaimable by any runner. */
|
|
5367
|
+
leaseExpiresAt: timestamp("lease_expires_at", { withTimezone: true }),
|
|
5343
5368
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5344
5369
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5345
5370
|
},
|
|
5346
5371
|
(table) => [
|
|
5347
5372
|
index("idx_wiki_page_status").on(table.status),
|
|
5348
5373
|
index("idx_wiki_page_last_verified").on(table.lastVerifiedOn),
|
|
5349
|
-
index("idx_wiki_page_dirty").on(table.dirty)
|
|
5374
|
+
index("idx_wiki_page_dirty").on(table.dirty),
|
|
5375
|
+
index("idx_wiki_page_lease_expires").on(table.leaseExpiresAt).where(sql`${table.leaseExpiresAt} IS NOT NULL`)
|
|
5350
5376
|
]
|
|
5351
5377
|
);
|
|
5352
5378
|
pgTable(
|
|
@@ -5391,11 +5417,9 @@ pgTable(
|
|
|
5391
5417
|
resolvedAt: timestamp("resolved_at", { withTimezone: true })
|
|
5392
5418
|
},
|
|
5393
5419
|
(table) => [
|
|
5394
|
-
uniqueIndex(
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
table.evidenceRef
|
|
5398
|
-
),
|
|
5420
|
+
uniqueIndex(
|
|
5421
|
+
"wiki_page_evidence_page_slug_evidence_type_evidence_ref_key"
|
|
5422
|
+
).on(table.pageSlug, table.evidenceType, table.evidenceRef),
|
|
5399
5423
|
index("idx_wiki_page_evidence_created").on(table.createdAt)
|
|
5400
5424
|
]
|
|
5401
5425
|
);
|
|
@@ -5450,6 +5474,65 @@ pgTable(
|
|
|
5450
5474
|
)
|
|
5451
5475
|
]
|
|
5452
5476
|
);
|
|
5477
|
+
pgTable(
|
|
5478
|
+
"wiki_invariant_violation",
|
|
5479
|
+
{
|
|
5480
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5481
|
+
slug: text("slug").notNull(),
|
|
5482
|
+
code: text("code").notNull(),
|
|
5483
|
+
severity: text("severity").notNull().default("warning"),
|
|
5484
|
+
detail: text("detail").notNull().default(""),
|
|
5485
|
+
suggestedFix: text("suggested_fix"),
|
|
5486
|
+
suggestedSlug: text("suggested_slug"),
|
|
5487
|
+
occurrenceCount: integer("occurrence_count").notNull().default(1),
|
|
5488
|
+
reopenCount: integer("reopen_count").notNull().default(0),
|
|
5489
|
+
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5490
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5491
|
+
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
5492
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5493
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5494
|
+
},
|
|
5495
|
+
(table) => [
|
|
5496
|
+
uniqueIndex("wiki_invariant_violation_slug_code_key").on(
|
|
5497
|
+
table.slug,
|
|
5498
|
+
table.code
|
|
5499
|
+
),
|
|
5500
|
+
index("idx_wiki_invariant_violation_severity").on(
|
|
5501
|
+
table.severity,
|
|
5502
|
+
table.resolvedAt
|
|
5503
|
+
)
|
|
5504
|
+
]
|
|
5505
|
+
);
|
|
5506
|
+
pgTable(
|
|
5507
|
+
"wiki_pipeline_rule",
|
|
5508
|
+
{
|
|
5509
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5510
|
+
scope: text("scope").notNull(),
|
|
5511
|
+
ruleKey: text("rule_key").notNull(),
|
|
5512
|
+
text: text("text").notNull(),
|
|
5513
|
+
/** A = deterministic auto-applied, B = judgement, gated by insight accept. */
|
|
5514
|
+
tier: text("tier").notNull().default("B"),
|
|
5515
|
+
status: text("status").notNull().default("proposed"),
|
|
5516
|
+
sourceCode: text("source_code"),
|
|
5517
|
+
evidenceRefs: text("evidence_refs").array().notNull().default([]),
|
|
5518
|
+
violationCount: integer("violation_count").notNull().default(0),
|
|
5519
|
+
lastViolationAt: timestamp("last_violation_at", { withTimezone: true }),
|
|
5520
|
+
activatedAt: timestamp("activated_at", { withTimezone: true }),
|
|
5521
|
+
retiredAt: timestamp("retired_at", { withTimezone: true }),
|
|
5522
|
+
retiredReason: text("retired_reason"),
|
|
5523
|
+
rejectedAt: timestamp("rejected_at", { withTimezone: true }),
|
|
5524
|
+
rejectedReason: text("rejected_reason"),
|
|
5525
|
+
approvedBy: text("approved_by"),
|
|
5526
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5527
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5528
|
+
},
|
|
5529
|
+
(table) => [
|
|
5530
|
+
uniqueIndex("wiki_pipeline_rule_scope_key_key").on(
|
|
5531
|
+
table.scope,
|
|
5532
|
+
table.ruleKey
|
|
5533
|
+
)
|
|
5534
|
+
]
|
|
5535
|
+
);
|
|
5453
5536
|
pgTable(
|
|
5454
5537
|
"wiki_search_log",
|
|
5455
5538
|
{
|
|
@@ -5477,6 +5560,69 @@ pgTable(
|
|
|
5477
5560
|
},
|
|
5478
5561
|
(table) => [index("idx_team_memory_search_log_created").on(table.createdAt)]
|
|
5479
5562
|
);
|
|
5563
|
+
pgTable(
|
|
5564
|
+
"wiki_agent_run",
|
|
5565
|
+
{
|
|
5566
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5567
|
+
phase: text("phase").notNull(),
|
|
5568
|
+
model: text("model"),
|
|
5569
|
+
claimed: jsonb("claimed").$type(),
|
|
5570
|
+
proven: jsonb("proven").$type(),
|
|
5571
|
+
violationCount: integer("violation_count").notNull().default(0),
|
|
5572
|
+
violations: jsonb("violations").$type(),
|
|
5573
|
+
outcome: text("outcome").notNull().default("ok"),
|
|
5574
|
+
agentId: text("agent_id"),
|
|
5575
|
+
runId: text("run_id"),
|
|
5576
|
+
passId: text("pass_id"),
|
|
5577
|
+
changedPaths: jsonb("changed_paths").$type(),
|
|
5578
|
+
summary: text("summary"),
|
|
5579
|
+
userVerdict: text("user_verdict"),
|
|
5580
|
+
userVerdictNote: text("user_verdict_note"),
|
|
5581
|
+
verdictAt: timestamp("verdict_at", { withTimezone: true }),
|
|
5582
|
+
reviewed: boolean("reviewed").notNull().default(false),
|
|
5583
|
+
reviewNotes: text("review_notes"),
|
|
5584
|
+
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
5585
|
+
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
5586
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5587
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5588
|
+
},
|
|
5589
|
+
(table) => [
|
|
5590
|
+
index("idx_wiki_agent_run_phase_created").on(table.phase, table.createdAt),
|
|
5591
|
+
index("idx_wiki_agent_run_created").on(table.createdAt),
|
|
5592
|
+
index("idx_wiki_agent_run_reviewed").on(table.reviewed),
|
|
5593
|
+
index("idx_wiki_agent_run_user_verdict").on(table.userVerdict),
|
|
5594
|
+
index("idx_wiki_agent_run_pass_id").on(table.passId)
|
|
5595
|
+
]
|
|
5596
|
+
);
|
|
5597
|
+
pgTable(
|
|
5598
|
+
"wiki_agent_review_suggestion",
|
|
5599
|
+
{
|
|
5600
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5601
|
+
status: text("status").notNull().default("proposed"),
|
|
5602
|
+
implementationClass: text("implementation_class").notNull(),
|
|
5603
|
+
phase: text("phase"),
|
|
5604
|
+
scope: text("scope"),
|
|
5605
|
+
title: text("title").notNull(),
|
|
5606
|
+
summary: text("summary").notNull(),
|
|
5607
|
+
targetFile: text("target_file"),
|
|
5608
|
+
targetKey: text("target_key"),
|
|
5609
|
+
beforeText: text("before_text"),
|
|
5610
|
+
afterText: text("after_text"),
|
|
5611
|
+
evidenceRunIds: uuid("evidence_run_ids").array().notNull().default([]),
|
|
5612
|
+
evidenceRefs: text("evidence_refs").array().notNull().default([]),
|
|
5613
|
+
dedupeKey: text("dedupe_key").notNull(),
|
|
5614
|
+
source: text("source").notNull().default("auto"),
|
|
5615
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5616
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5617
|
+
},
|
|
5618
|
+
(table) => [
|
|
5619
|
+
uniqueIndex("wiki_agent_review_suggestion_dedupe_key").on(table.dedupeKey),
|
|
5620
|
+
index("idx_wiki_agent_review_suggestion_status_created").on(
|
|
5621
|
+
table.status,
|
|
5622
|
+
table.createdAt
|
|
5623
|
+
)
|
|
5624
|
+
]
|
|
5625
|
+
);
|
|
5480
5626
|
|
|
5481
5627
|
// ../db/src/schema/agent-memory.ts
|
|
5482
5628
|
pgTable(
|
|
@@ -5521,6 +5667,83 @@ pgTable(
|
|
|
5521
5667
|
index("idx_agent_memory_category").on(table.category)
|
|
5522
5668
|
]
|
|
5523
5669
|
);
|
|
5670
|
+
var agentWorldAgent = pgTable(
|
|
5671
|
+
"agent_world_agent",
|
|
5672
|
+
{
|
|
5673
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5674
|
+
name: text("name").notNull(),
|
|
5675
|
+
color: text("color").notNull(),
|
|
5676
|
+
/** Personality sliders 0–100: curiosity, sociability, diligence, calm. */
|
|
5677
|
+
personality: jsonb("personality").$type().notNull().default({
|
|
5678
|
+
curiosity: 50,
|
|
5679
|
+
sociability: 50,
|
|
5680
|
+
diligence: 50,
|
|
5681
|
+
calm: 50
|
|
5682
|
+
}),
|
|
5683
|
+
/** Needs 0–100: higher = more urgent. */
|
|
5684
|
+
needs: jsonb("needs").$type().notNull().default({ energy: 40, coffee: 30, focus: 35, social: 25 }),
|
|
5685
|
+
position: jsonb("position").$type().notNull().default({ x: 0, y: 0, z: 0 }),
|
|
5686
|
+
target: jsonb("target").$type(),
|
|
5687
|
+
activity: text("activity").notNull().default("idle"),
|
|
5688
|
+
waypoint: text("waypoint"),
|
|
5689
|
+
memories: jsonb("memories").$type().notNull().default([]),
|
|
5690
|
+
diaryCount: integer("diary_count").notNull().default(0),
|
|
5691
|
+
lastSay: text("last_say"),
|
|
5692
|
+
lastSayAt: timestamp("last_say_at", { withTimezone: true }),
|
|
5693
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
5694
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5695
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5696
|
+
},
|
|
5697
|
+
(table) => [index("idx_agent_world_agent_sort").on(table.sortOrder)]
|
|
5698
|
+
);
|
|
5699
|
+
pgTable(
|
|
5700
|
+
"agent_world_event",
|
|
5701
|
+
{
|
|
5702
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5703
|
+
/** say | diary | think | god | activity | memo | system */
|
|
5704
|
+
type: text("type").notNull(),
|
|
5705
|
+
agentId: uuid("agent_id").references(() => agentWorldAgent.id, {
|
|
5706
|
+
onDelete: "set null"
|
|
5707
|
+
}),
|
|
5708
|
+
message: text("message").notNull(),
|
|
5709
|
+
meta: jsonb("meta").$type().notNull().default({}),
|
|
5710
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
5711
|
+
},
|
|
5712
|
+
(table) => [
|
|
5713
|
+
index("idx_agent_world_event_created").on(table.createdAt),
|
|
5714
|
+
index("idx_agent_world_event_type").on(table.type),
|
|
5715
|
+
index("idx_agent_world_event_agent").on(table.agentId)
|
|
5716
|
+
]
|
|
5717
|
+
);
|
|
5718
|
+
pgTable(
|
|
5719
|
+
"agent_world_memo",
|
|
5720
|
+
{
|
|
5721
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
5722
|
+
agentId: uuid("agent_id").references(() => agentWorldAgent.id, {
|
|
5723
|
+
onDelete: "set null"
|
|
5724
|
+
}),
|
|
5725
|
+
title: text("title").notNull(),
|
|
5726
|
+
content: text("content").notNull(),
|
|
5727
|
+
location: text("location").notNull().default("whiteboard"),
|
|
5728
|
+
reads: integer("reads").notNull().default(0),
|
|
5729
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
5730
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5731
|
+
},
|
|
5732
|
+
(table) => [
|
|
5733
|
+
index("idx_agent_world_memo_created").on(table.createdAt),
|
|
5734
|
+
index("idx_agent_world_memo_location").on(table.location)
|
|
5735
|
+
]
|
|
5736
|
+
);
|
|
5737
|
+
pgTable("agent_world_state", {
|
|
5738
|
+
id: text("id").primaryKey().default("world"),
|
|
5739
|
+
tick: integer("tick").notNull().default(0),
|
|
5740
|
+
/** Simulated minutes since day start (0–1439 wraps). */
|
|
5741
|
+
simMinutes: integer("sim_minutes").notNull().default(540),
|
|
5742
|
+
paused: boolean("paused").notNull().default(false),
|
|
5743
|
+
lastThinkAt: timestamp("last_think_at", { withTimezone: true }),
|
|
5744
|
+
lastThinkAgentId: uuid("last_think_agent_id"),
|
|
5745
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
5746
|
+
});
|
|
5524
5747
|
var contentSourceType = pgEnum("content_source_type", [
|
|
5525
5748
|
"own_site",
|
|
5526
5749
|
"client_site",
|
|
@@ -6086,7 +6309,11 @@ pgTable(
|
|
|
6086
6309
|
table.resourceId,
|
|
6087
6310
|
table.metric,
|
|
6088
6311
|
table.sampledAt
|
|
6089
|
-
)
|
|
6312
|
+
),
|
|
6313
|
+
// Retention cleanup (monitoring-sample-retention-cleanup) filters only on
|
|
6314
|
+
// sampled_at. Without this index Postgres seq-scans the full firehose.
|
|
6315
|
+
// Live DB name uses idx_ prefix (see migration 20260731070000).
|
|
6316
|
+
index("idx_monitoring_sample_sampled_at").on(table.sampledAt)
|
|
6090
6317
|
]
|
|
6091
6318
|
);
|
|
6092
6319
|
var appLogSource = pgTable(
|
|
@@ -6420,7 +6647,8 @@ var directorySubmissionStatus = pgEnum(
|
|
|
6420
6647
|
"rejected",
|
|
6421
6648
|
"failed",
|
|
6422
6649
|
"manual",
|
|
6423
|
-
"captcha"
|
|
6650
|
+
"captcha",
|
|
6651
|
+
"skipped"
|
|
6424
6652
|
]
|
|
6425
6653
|
);
|
|
6426
6654
|
var directoryAccountStatus = pgEnum("directory_account_status", [
|
|
@@ -6444,6 +6672,13 @@ var linkDirectory = pgTable(
|
|
|
6444
6672
|
automationLevel: directoryAutomationLevel("automation_level").notNull().default("auto"),
|
|
6445
6673
|
enabled: boolean("enabled").notNull().default(false),
|
|
6446
6674
|
formMapping: jsonb("form_mapping").$type(),
|
|
6675
|
+
/** Set when a submission for this directory reached live/submitted end-to-end. */
|
|
6676
|
+
provenAt: timestamp("proven_at", { withTimezone: true }),
|
|
6677
|
+
successCount: integer("success_count").notNull().default(0),
|
|
6678
|
+
failCount: integer("fail_count").notNull().default(0),
|
|
6679
|
+
/** Hard captcha (even after residential) — disable after CAPTCHA_STRIKE_LIMIT. */
|
|
6680
|
+
captchaStrike: integer("captcha_strike").notNull().default(0),
|
|
6681
|
+
lastOutcome: text("last_outcome"),
|
|
6447
6682
|
lastCheckedAt: timestamp("last_checked_at", { withTimezone: true }),
|
|
6448
6683
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6449
6684
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -6451,7 +6686,8 @@ var linkDirectory = pgTable(
|
|
|
6451
6686
|
(table) => [
|
|
6452
6687
|
uniqueIndex("link_directory_submit_url_uidx").on(table.submitUrl),
|
|
6453
6688
|
index("link_directory_enabled_dr_idx").on(table.enabled, table.dr),
|
|
6454
|
-
index("link_directory_region_idx").on(table.region)
|
|
6689
|
+
index("link_directory_region_idx").on(table.region),
|
|
6690
|
+
index("link_directory_proven_at_idx").on(table.provenAt)
|
|
6455
6691
|
]
|
|
6456
6692
|
);
|
|
6457
6693
|
var linkBuildingClient = pgTable(
|
|
@@ -6463,19 +6699,26 @@ var linkBuildingClient = pgTable(
|
|
|
6463
6699
|
mailboxAddress: text("mailbox_address"),
|
|
6464
6700
|
refrontMailAccountId: text("refront_mail_account_id"),
|
|
6465
6701
|
profile: jsonb("profile").$type().notNull().default({}),
|
|
6466
|
-
weeklyPace: integer("weekly_pace").notNull().default(
|
|
6702
|
+
weeklyPace: integer("weekly_pace").notNull().default(35),
|
|
6467
6703
|
/** Target package size (number of directory backlinks sold). */
|
|
6468
6704
|
packageSize: integer("package_size").notNull().default(100),
|
|
6469
|
-
autoSubmit: boolean("auto_submit").notNull().default(
|
|
6705
|
+
autoSubmit: boolean("auto_submit").notNull().default(true),
|
|
6470
6706
|
startingDr: doublePrecision("starting_dr"),
|
|
6471
6707
|
currentDr: doublePrecision("current_dr"),
|
|
6472
6708
|
isActive: boolean("is_active").notNull().default(true),
|
|
6709
|
+
/** Opaque token for public customer progress reports (backlinking.eu). */
|
|
6710
|
+
reportToken: text("report_token"),
|
|
6711
|
+
/** External shop order id from backlinking.eu checkout. */
|
|
6712
|
+
shopOrderId: text("shop_order_id"),
|
|
6713
|
+
shopCustomerEmail: text("shop_customer_email"),
|
|
6473
6714
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6474
6715
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6475
6716
|
},
|
|
6476
6717
|
(table) => [
|
|
6477
6718
|
uniqueIndex("link_building_client_website_uidx").on(table.websiteUrl),
|
|
6478
|
-
index("link_building_client_active_idx").on(table.isActive)
|
|
6719
|
+
index("link_building_client_active_idx").on(table.isActive),
|
|
6720
|
+
uniqueIndex("link_building_client_report_token_uidx").on(table.reportToken),
|
|
6721
|
+
index("link_building_client_shop_order_idx").on(table.shopOrderId)
|
|
6479
6722
|
]
|
|
6480
6723
|
);
|
|
6481
6724
|
var directorySubmission = pgTable(
|
|
@@ -6583,6 +6826,78 @@ pgTable(
|
|
|
6583
6826
|
)
|
|
6584
6827
|
]
|
|
6585
6828
|
);
|
|
6829
|
+
var networkListingStatus = pgEnum("network_listing_status", [
|
|
6830
|
+
"pending",
|
|
6831
|
+
"queued",
|
|
6832
|
+
"placing",
|
|
6833
|
+
"live",
|
|
6834
|
+
"failed",
|
|
6835
|
+
"expired",
|
|
6836
|
+
"cancelled"
|
|
6837
|
+
]);
|
|
6838
|
+
var networkSite = pgTable(
|
|
6839
|
+
"network_site",
|
|
6840
|
+
{
|
|
6841
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6842
|
+
name: text("name").notNull(),
|
|
6843
|
+
domain: text("domain").notNull(),
|
|
6844
|
+
websiteUrl: text("website_url").notNull(),
|
|
6845
|
+
dr: integer("dr").notNull().default(0),
|
|
6846
|
+
theme: text("theme").notNull().default("general"),
|
|
6847
|
+
/** Yearly listing price in euro cents. */
|
|
6848
|
+
priceYearCents: integer("price_year_cents").notNull().default(9900),
|
|
6849
|
+
categories: jsonb("categories").$type().notNull().default([]),
|
|
6850
|
+
/** GitHub repo for placement commits, e.g. MGSoftwareBV/catalogcompany.nl */
|
|
6851
|
+
repoFullName: text("repo_full_name"),
|
|
6852
|
+
/** Path in repo for partner/listing data (relative). */
|
|
6853
|
+
listingDataPath: text("listing_data_path"),
|
|
6854
|
+
dofollow: boolean("dofollow").notNull().default(true),
|
|
6855
|
+
enabled: boolean("enabled").notNull().default(false),
|
|
6856
|
+
maxListings: integer("max_listings").notNull().default(50),
|
|
6857
|
+
notes: text("notes"),
|
|
6858
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6859
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6860
|
+
},
|
|
6861
|
+
(table) => [
|
|
6862
|
+
uniqueIndex("network_site_domain_uidx").on(table.domain),
|
|
6863
|
+
index("network_site_enabled_dr_idx").on(table.enabled, table.dr),
|
|
6864
|
+
index("network_site_theme_idx").on(table.theme)
|
|
6865
|
+
]
|
|
6866
|
+
);
|
|
6867
|
+
pgTable(
|
|
6868
|
+
"network_listing",
|
|
6869
|
+
{
|
|
6870
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6871
|
+
siteId: uuid("site_id").notNull().references(() => networkSite.id, { onDelete: "cascade" }),
|
|
6872
|
+
clientId: uuid("client_id").references(() => linkBuildingClient.id, {
|
|
6873
|
+
onDelete: "set null"
|
|
6874
|
+
}),
|
|
6875
|
+
customerName: text("customer_name").notNull(),
|
|
6876
|
+
customerEmail: text("customer_email").notNull(),
|
|
6877
|
+
targetUrl: text("target_url").notNull(),
|
|
6878
|
+
anchorText: text("anchor_text").notNull(),
|
|
6879
|
+
categorySlug: text("category_slug").notNull().default("general"),
|
|
6880
|
+
status: networkListingStatus("status").notNull().default("pending"),
|
|
6881
|
+
listingUrl: text("listing_url"),
|
|
6882
|
+
/** External shop order reference from backlinking.eu */
|
|
6883
|
+
shopOrderId: text("shop_order_id"),
|
|
6884
|
+
shopOrderItemId: text("shop_order_item_id"),
|
|
6885
|
+
priceCents: integer("price_cents").notNull().default(0),
|
|
6886
|
+
startsAt: timestamp("starts_at", { withTimezone: true }),
|
|
6887
|
+
renewsAt: timestamp("renews_at", { withTimezone: true }),
|
|
6888
|
+
placedAt: timestamp("placed_at", { withTimezone: true }),
|
|
6889
|
+
lastError: text("last_error"),
|
|
6890
|
+
log: jsonb("log").$type().notNull().default({}),
|
|
6891
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6892
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6893
|
+
},
|
|
6894
|
+
(table) => [
|
|
6895
|
+
index("network_listing_site_status_idx").on(table.siteId, table.status),
|
|
6896
|
+
index("network_listing_client_idx").on(table.clientId),
|
|
6897
|
+
index("network_listing_renews_at_idx").on(table.renewsAt),
|
|
6898
|
+
index("network_listing_shop_order_idx").on(table.shopOrderId)
|
|
6899
|
+
]
|
|
6900
|
+
);
|
|
6586
6901
|
pgTable("github_api_budget", {
|
|
6587
6902
|
tokenKey: text("token_key").primaryKey(),
|
|
6588
6903
|
windowStartedAt: timestamp("window_started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
@@ -6611,6 +6926,7 @@ pgTable(
|
|
|
6611
6926
|
requestCount: integer("request_count").notNull().default(0),
|
|
6612
6927
|
errorCount: integer("error_count").notNull().default(0),
|
|
6613
6928
|
cacheHitCount: integer("cache_hit_count").notNull().default(0),
|
|
6929
|
+
breakerHitCount: integer("breaker_hit_count").notNull().default(0),
|
|
6614
6930
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6615
6931
|
},
|
|
6616
6932
|
(table) => [
|
|
@@ -6655,11 +6971,66 @@ pgTable(
|
|
|
6655
6971
|
index("github_webhook_secret_owner_idx").on(table.owner)
|
|
6656
6972
|
]
|
|
6657
6973
|
);
|
|
6974
|
+
var gscOpportunityPass = pgTable(
|
|
6975
|
+
"gsc_opportunity_pass",
|
|
6976
|
+
{
|
|
6977
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6978
|
+
siteKey: text("site_key").notNull(),
|
|
6979
|
+
gscProperty: text("gsc_property").notNull(),
|
|
6980
|
+
passedAt: timestamp("passed_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6981
|
+
kind: text("kind").notNull(),
|
|
6982
|
+
headline: text("headline"),
|
|
6983
|
+
windowFrom: date("window_from"),
|
|
6984
|
+
windowTo: date("window_to"),
|
|
6985
|
+
canvasPath: text("canvas_path"),
|
|
6986
|
+
createdBy: text("created_by"),
|
|
6987
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
6988
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
6989
|
+
},
|
|
6990
|
+
(table) => [
|
|
6991
|
+
index("idx_gsc_opportunity_pass_site_passed").on(
|
|
6992
|
+
table.siteKey,
|
|
6993
|
+
table.passedAt
|
|
6994
|
+
),
|
|
6995
|
+
index("idx_gsc_opportunity_pass_kind").on(table.siteKey, table.kind)
|
|
6996
|
+
]
|
|
6997
|
+
);
|
|
6998
|
+
pgTable(
|
|
6999
|
+
"gsc_opportunity_item",
|
|
7000
|
+
{
|
|
7001
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
7002
|
+
passId: uuid("pass_id").notNull().references(() => gscOpportunityPass.id, { onDelete: "cascade" }),
|
|
7003
|
+
siteKey: text("site_key").notNull(),
|
|
7004
|
+
url: text("url").notNull(),
|
|
7005
|
+
queryCluster: text("query_cluster"),
|
|
7006
|
+
status: text("status").notNull().default("awaiting"),
|
|
7007
|
+
changeSummary: text("change_summary"),
|
|
7008
|
+
shippedAt: date("shipped_at"),
|
|
7009
|
+
expectEffectAfter: date("expect_effect_after"),
|
|
7010
|
+
sourceUrls: jsonb("source_urls").$type().notNull().default([]),
|
|
7011
|
+
notes: text("notes"),
|
|
7012
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
7013
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
7014
|
+
},
|
|
7015
|
+
(table) => [
|
|
7016
|
+
index("idx_gsc_opportunity_item_site_status").on(
|
|
7017
|
+
table.siteKey,
|
|
7018
|
+
table.status
|
|
7019
|
+
),
|
|
7020
|
+
index("idx_gsc_opportunity_item_pass").on(table.passId),
|
|
7021
|
+
index("idx_gsc_opportunity_item_url").on(table.siteKey, table.url)
|
|
7022
|
+
]
|
|
7023
|
+
);
|
|
6658
7024
|
pgTable(
|
|
6659
7025
|
"handoff_loop_run",
|
|
6660
7026
|
{
|
|
6661
7027
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
6662
7028
|
runSlug: text("run_slug").notNull(),
|
|
7029
|
+
/**
|
|
7030
|
+
* Skill pack that produced the run. Default is the plan-handoff loop;
|
|
7031
|
+
* quality leaves push mg-strict-truth / mg-seo-strict.
|
|
7032
|
+
*/
|
|
7033
|
+
skill: text("skill").notNull().default("mg-plan-handoff-loop"),
|
|
6663
7034
|
userId: text("user_id"),
|
|
6664
7035
|
gitRepository: text("git_repository"),
|
|
6665
7036
|
machine: text("machine"),
|
|
@@ -6676,12 +7047,23 @@ pgTable(
|
|
|
6676
7047
|
checklistProposed: integer("checklist_proposed").notNull().default(0),
|
|
6677
7048
|
checklistAccepted: integer("checklist_accepted").notNull().default(0),
|
|
6678
7049
|
checklistVerified: integer("checklist_verified").notNull().default(0),
|
|
7050
|
+
/** Items resolved with evidence instead of a check (e.g. no browser session). */
|
|
7051
|
+
checklistNa: integer("checklist_na").notNull().default(0),
|
|
6679
7052
|
inputTokens: bigint("input_tokens", { mode: "number" }).notNull().default(0),
|
|
6680
7053
|
outputTokens: bigint("output_tokens", { mode: "number" }).notNull().default(0),
|
|
6681
7054
|
cacheReadTokens: bigint("cache_read_tokens", { mode: "number" }).notNull().default(0),
|
|
6682
7055
|
totalDurationMs: bigint("total_duration_ms", { mode: "number" }).notNull().default(0),
|
|
6683
7056
|
retro: text("retro"),
|
|
6684
7057
|
metrics: jsonb("metrics"),
|
|
7058
|
+
/**
|
|
7059
|
+
* Human end-verdict: good | mixed | bad. The orchestrator asks once at
|
|
7060
|
+
* Phase 4 and the AI Agents table offers a second capture point, so the
|
|
7061
|
+
* review skill can score runs against the user instead of the loop's own
|
|
7062
|
+
* self-report.
|
|
7063
|
+
*/
|
|
7064
|
+
userVerdict: text("user_verdict"),
|
|
7065
|
+
userVerdictNote: text("user_verdict_note"),
|
|
7066
|
+
verdictAt: timestamp("verdict_at", { withTimezone: true }),
|
|
6685
7067
|
reviewed: boolean("reviewed").notNull().default(false),
|
|
6686
7068
|
reviewNotes: text("review_notes"),
|
|
6687
7069
|
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
@@ -6694,8 +7076,10 @@ pgTable(
|
|
|
6694
7076
|
uniqueIndex("uq_handoff_loop_run_slug").on(table.runSlug),
|
|
6695
7077
|
index("idx_handoff_loop_run_user").on(table.userId),
|
|
6696
7078
|
index("idx_handoff_loop_run_outcome").on(table.outcome),
|
|
7079
|
+
index("idx_handoff_loop_run_skill").on(table.skill),
|
|
6697
7080
|
index("idx_handoff_loop_run_reviewed").on(table.reviewed),
|
|
6698
|
-
index("idx_handoff_loop_run_created").on(table.createdAt)
|
|
7081
|
+
index("idx_handoff_loop_run_created").on(table.createdAt),
|
|
7082
|
+
index("idx_handoff_loop_run_verdict").on(table.userVerdict)
|
|
6699
7083
|
]
|
|
6700
7084
|
);
|
|
6701
7085
|
var managedSiteSyncMode = pgEnum("managed_site_sync_mode", [
|
|
@@ -7052,6 +7436,8 @@ var TRANSIENT_GITHUB_STATUSES = /* @__PURE__ */ new Set([
|
|
|
7052
7436
|
524
|
|
7053
7437
|
]);
|
|
7054
7438
|
var DEFAULT_BUDGET_PER_MINUTE = 75;
|
|
7439
|
+
var DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS = 5 * 6e4;
|
|
7440
|
+
var DEFAULT_INTERACTIVE_STALE_MAX_AGE_MS = 30 * 6e4;
|
|
7055
7441
|
var BUDGET_STATE_KEY = "github_api_budget_per_minute";
|
|
7056
7442
|
var DEFAULT_BLOCK_MS = 15 * 60 * 1e3;
|
|
7057
7443
|
var MAX_ATTEMPTS = 4;
|
|
@@ -7059,6 +7445,7 @@ var BASE_DELAY_MS = 750;
|
|
|
7059
7445
|
var RATE_LIMIT_FLOOR = 50;
|
|
7060
7446
|
var BUDGET_WAIT_ATTEMPTS = 4;
|
|
7061
7447
|
var BUDGET_WAIT_CAP_MS = 15e3;
|
|
7448
|
+
var INTERACTIVE_BUDGET_WAIT_CAP_MS = 1e3;
|
|
7062
7449
|
var GitHubGatewayBlockedError = class extends Error {
|
|
7063
7450
|
blockedUntil;
|
|
7064
7451
|
constructor(blockedUntil, message) {
|
|
@@ -7095,6 +7482,31 @@ function summarizeGitHubErrorBody(body, maxLen = 240) {
|
|
|
7095
7482
|
function isTransientGitHubStatus(status) {
|
|
7096
7483
|
return TRANSIENT_GITHUB_STATUSES.has(status);
|
|
7097
7484
|
}
|
|
7485
|
+
var TRANSIENT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
7486
|
+
"ECONNRESET",
|
|
7487
|
+
"ECONNREFUSED",
|
|
7488
|
+
"ETIMEDOUT",
|
|
7489
|
+
"ENOTFOUND",
|
|
7490
|
+
"EAI_AGAIN",
|
|
7491
|
+
"ENETUNREACH",
|
|
7492
|
+
"EHOSTUNREACH",
|
|
7493
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
7494
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
7495
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
7496
|
+
"UND_ERR_SOCKET"
|
|
7497
|
+
]);
|
|
7498
|
+
function isTransientNetworkError(err) {
|
|
7499
|
+
if (!(err instanceof Error)) return false;
|
|
7500
|
+
const message = err.message.toLowerCase();
|
|
7501
|
+
if (message.includes("fetch failed") || message.includes("socket hang up") || message.includes("network") || message.includes("other side closed")) {
|
|
7502
|
+
return true;
|
|
7503
|
+
}
|
|
7504
|
+
const code = err.code;
|
|
7505
|
+
if (code && TRANSIENT_NETWORK_CODES.has(code)) return true;
|
|
7506
|
+
const cause = err.cause;
|
|
7507
|
+
if (cause && cause !== err) return isTransientNetworkError(cause);
|
|
7508
|
+
return false;
|
|
7509
|
+
}
|
|
7098
7510
|
function hourBucket(now = /* @__PURE__ */ new Date()) {
|
|
7099
7511
|
return new Date(
|
|
7100
7512
|
Date.UTC(
|
|
@@ -7111,6 +7523,55 @@ function hourBucket(now = /* @__PURE__ */ new Date()) {
|
|
|
7111
7523
|
function cacheKeyFor(url, tokenKey) {
|
|
7112
7524
|
return createHash("sha256").update(`${tokenKey}:${url}`).digest("hex");
|
|
7113
7525
|
}
|
|
7526
|
+
function toDate(value) {
|
|
7527
|
+
if (value == null) return null;
|
|
7528
|
+
const date3 = typeof value === "string" ? new Date(value) : value;
|
|
7529
|
+
return Number.isNaN(date3.getTime()) ? null : date3;
|
|
7530
|
+
}
|
|
7531
|
+
function isSoftCacheFresh(input) {
|
|
7532
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
7533
|
+
const expiresAt = toDate(input.expiresAt ?? null);
|
|
7534
|
+
if (expiresAt && expiresAt.getTime() > nowMs) return true;
|
|
7535
|
+
if (input.maxAgeMs == null || input.maxAgeMs <= 0) return false;
|
|
7536
|
+
const updatedAt = toDate(input.updatedAt ?? null);
|
|
7537
|
+
if (!updatedAt) return false;
|
|
7538
|
+
return nowMs - updatedAt.getTime() <= input.maxAgeMs;
|
|
7539
|
+
}
|
|
7540
|
+
function canInteractiveSoftServe(input) {
|
|
7541
|
+
if (isSoftCacheFresh({
|
|
7542
|
+
expiresAt: input.expiresAt,
|
|
7543
|
+
updatedAt: input.updatedAt,
|
|
7544
|
+
maxAgeMs: input.maxAgeMs,
|
|
7545
|
+
nowMs: input.nowMs
|
|
7546
|
+
})) {
|
|
7547
|
+
return true;
|
|
7548
|
+
}
|
|
7549
|
+
const staleMaxAgeMs = input.staleMaxAgeMs ?? DEFAULT_INTERACTIVE_STALE_MAX_AGE_MS;
|
|
7550
|
+
if (staleMaxAgeMs <= 0) return false;
|
|
7551
|
+
return isSoftCacheFresh({
|
|
7552
|
+
expiresAt: null,
|
|
7553
|
+
updatedAt: input.updatedAt,
|
|
7554
|
+
maxAgeMs: staleMaxAgeMs,
|
|
7555
|
+
nowMs: input.nowMs
|
|
7556
|
+
});
|
|
7557
|
+
}
|
|
7558
|
+
function resolveMaxAgeMs(options, priority, useCache) {
|
|
7559
|
+
if (!useCache) return void 0;
|
|
7560
|
+
if (options.maxAgeMs !== void 0) {
|
|
7561
|
+
return options.maxAgeMs > 0 ? options.maxAgeMs : void 0;
|
|
7562
|
+
}
|
|
7563
|
+
if (priority === "interactive") return DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS;
|
|
7564
|
+
return void 0;
|
|
7565
|
+
}
|
|
7566
|
+
function cachedJsonResponse(body, status, cacheHeader) {
|
|
7567
|
+
return new Response(body, {
|
|
7568
|
+
status,
|
|
7569
|
+
headers: {
|
|
7570
|
+
"x-github-gateway-cache": cacheHeader,
|
|
7571
|
+
"content-type": "application/json"
|
|
7572
|
+
}
|
|
7573
|
+
});
|
|
7574
|
+
}
|
|
7114
7575
|
async function isGithubSyncPaused() {
|
|
7115
7576
|
try {
|
|
7116
7577
|
const rows = await getDb().execute(sql`
|
|
@@ -7133,9 +7594,9 @@ async function getBlockedUntil(tokenKey) {
|
|
|
7133
7594
|
);
|
|
7134
7595
|
const raw = rows[0]?.blocked_until;
|
|
7135
7596
|
if (!raw) return null;
|
|
7136
|
-
const
|
|
7137
|
-
if (Number.isNaN(
|
|
7138
|
-
return
|
|
7597
|
+
const date3 = typeof raw === "string" ? new Date(raw) : raw;
|
|
7598
|
+
if (Number.isNaN(date3.getTime()) || date3.getTime() <= Date.now()) return null;
|
|
7599
|
+
return date3;
|
|
7139
7600
|
}
|
|
7140
7601
|
async function setGithubCircuitBreaker(tokenKey, blockedUntil) {
|
|
7141
7602
|
await getDb().execute(sql`
|
|
@@ -7215,7 +7676,15 @@ async function consumeBudgetOrWait(input) {
|
|
|
7215
7676
|
return;
|
|
7216
7677
|
}
|
|
7217
7678
|
if (input.priority === "interactive") {
|
|
7218
|
-
|
|
7679
|
+
if (attempt >= 2) {
|
|
7680
|
+
await forceConsumeBudget(input.tokenKey);
|
|
7681
|
+
return;
|
|
7682
|
+
}
|
|
7683
|
+
const waitMs2 = Math.min(
|
|
7684
|
+
await msUntilBudgetWindowReset(input.tokenKey),
|
|
7685
|
+
INTERACTIVE_BUDGET_WAIT_CAP_MS
|
|
7686
|
+
);
|
|
7687
|
+
await sleep(waitMs2);
|
|
7219
7688
|
continue;
|
|
7220
7689
|
}
|
|
7221
7690
|
if (attempt === BUDGET_WAIT_ATTEMPTS) {
|
|
@@ -7229,40 +7698,76 @@ async function consumeBudgetOrWait(input) {
|
|
|
7229
7698
|
}
|
|
7230
7699
|
throw new GitHubGatewayBudgetError(input.tokenKey);
|
|
7231
7700
|
}
|
|
7701
|
+
async function forceConsumeBudget(tokenKey) {
|
|
7702
|
+
const updated = await getDb().execute(sql`
|
|
7703
|
+
UPDATE github_api_budget
|
|
7704
|
+
SET request_count = request_count + 1, updated_at = now()
|
|
7705
|
+
WHERE token_key = ${tokenKey}
|
|
7706
|
+
RETURNING request_count
|
|
7707
|
+
`);
|
|
7708
|
+
if (updated[0]) return;
|
|
7709
|
+
await getDb().execute(sql`
|
|
7710
|
+
INSERT INTO github_api_budget (token_key, window_started_at, request_count, updated_at)
|
|
7711
|
+
VALUES (${tokenKey}, now(), 1, now())
|
|
7712
|
+
ON CONFLICT (token_key) DO UPDATE SET
|
|
7713
|
+
request_count = github_api_budget.request_count + 1,
|
|
7714
|
+
updated_at = now()
|
|
7715
|
+
`);
|
|
7716
|
+
}
|
|
7232
7717
|
async function recordStat(input) {
|
|
7233
7718
|
const bucket = hourBucket().toISOString();
|
|
7234
7719
|
await getDb().execute(sql`
|
|
7235
7720
|
INSERT INTO github_api_stat (
|
|
7236
|
-
token_key, job_id, hour_bucket, request_count, error_count,
|
|
7721
|
+
token_key, job_id, hour_bucket, request_count, error_count,
|
|
7722
|
+
cache_hit_count, breaker_hit_count, updated_at
|
|
7237
7723
|
)
|
|
7238
7724
|
VALUES (
|
|
7239
7725
|
${input.tokenKey},
|
|
7240
7726
|
${input.jobId},
|
|
7241
7727
|
${bucket}::timestamptz,
|
|
7242
|
-
1,
|
|
7728
|
+
${input.breakerHit ? 0 : 1},
|
|
7243
7729
|
${input.error ? 1 : 0},
|
|
7244
7730
|
${input.cacheHit ? 1 : 0},
|
|
7731
|
+
${input.breakerHit ? 1 : 0},
|
|
7245
7732
|
now()
|
|
7246
7733
|
)
|
|
7247
7734
|
ON CONFLICT (token_key, job_id, hour_bucket) DO UPDATE SET
|
|
7248
|
-
request_count = github_api_stat.request_count + 1,
|
|
7735
|
+
request_count = github_api_stat.request_count + ${input.breakerHit ? 0 : 1},
|
|
7249
7736
|
error_count = github_api_stat.error_count + ${input.error ? 1 : 0},
|
|
7250
7737
|
cache_hit_count = github_api_stat.cache_hit_count + ${input.cacheHit ? 1 : 0},
|
|
7738
|
+
breaker_hit_count = github_api_stat.breaker_hit_count + ${input.breakerHit ? 1 : 0},
|
|
7251
7739
|
updated_at = now()
|
|
7252
7740
|
`);
|
|
7253
7741
|
}
|
|
7742
|
+
async function recordBreakerHit(input) {
|
|
7743
|
+
await recordStat({
|
|
7744
|
+
tokenKey: input.tokenKey,
|
|
7745
|
+
jobId: input.jobId,
|
|
7746
|
+
breakerHit: true
|
|
7747
|
+
});
|
|
7748
|
+
}
|
|
7254
7749
|
async function loadEtagCache(key) {
|
|
7255
7750
|
const rows = await getDb().execute(sql`
|
|
7256
|
-
SELECT etag, body, status
|
|
7751
|
+
SELECT etag, body, status, expires_at, updated_at
|
|
7752
|
+
FROM github_api_cache
|
|
7257
7753
|
WHERE cache_key = ${key}
|
|
7258
7754
|
LIMIT 1
|
|
7259
7755
|
`);
|
|
7260
|
-
|
|
7756
|
+
const row = rows[0];
|
|
7757
|
+
if (!row) return null;
|
|
7758
|
+
return {
|
|
7759
|
+
etag: row.etag,
|
|
7760
|
+
body: row.body,
|
|
7761
|
+
status: row.status,
|
|
7762
|
+
expiresAt: row.expires_at,
|
|
7763
|
+
updatedAt: row.updated_at
|
|
7764
|
+
};
|
|
7261
7765
|
}
|
|
7262
7766
|
async function saveEtagCache(input) {
|
|
7767
|
+
const expiresAt = input.maxAgeMs != null && input.maxAgeMs > 0 ? new Date(Date.now() + input.maxAgeMs).toISOString() : null;
|
|
7263
7768
|
await getDb().execute(sql`
|
|
7264
7769
|
INSERT INTO github_api_cache (
|
|
7265
|
-
cache_key, etag, body, status, content_type, updated_at
|
|
7770
|
+
cache_key, etag, body, status, content_type, expires_at, updated_at
|
|
7266
7771
|
)
|
|
7267
7772
|
VALUES (
|
|
7268
7773
|
${input.key},
|
|
@@ -7270,6 +7775,7 @@ async function saveEtagCache(input) {
|
|
|
7270
7775
|
${input.body},
|
|
7271
7776
|
${input.status},
|
|
7272
7777
|
${input.contentType},
|
|
7778
|
+
${expiresAt}::timestamptz,
|
|
7273
7779
|
now()
|
|
7274
7780
|
)
|
|
7275
7781
|
ON CONFLICT (cache_key) DO UPDATE SET
|
|
@@ -7277,6 +7783,7 @@ async function saveEtagCache(input) {
|
|
|
7277
7783
|
body = EXCLUDED.body,
|
|
7278
7784
|
status = EXCLUDED.status,
|
|
7279
7785
|
content_type = EXCLUDED.content_type,
|
|
7786
|
+
expires_at = EXCLUDED.expires_at,
|
|
7280
7787
|
updated_at = now()
|
|
7281
7788
|
`);
|
|
7282
7789
|
}
|
|
@@ -7319,21 +7826,65 @@ function tripBreakerFromResponse(response) {
|
|
|
7319
7826
|
}
|
|
7320
7827
|
async function githubGatewayFetch(url, init, options) {
|
|
7321
7828
|
const priority = options.priority ?? "background";
|
|
7322
|
-
const budgetPerMinute = await resolveGithubBudgetPerMinute(
|
|
7323
|
-
options.budgetPerMinute
|
|
7324
|
-
);
|
|
7325
7829
|
const method = (init.method ?? "GET").toUpperCase();
|
|
7326
7830
|
const useCache = options.useEtagCache !== false && method === "GET";
|
|
7831
|
+
const maxAgeMs = resolveMaxAgeMs(options, priority, useCache);
|
|
7327
7832
|
if (priority !== "interactive" && await isGithubSyncPaused()) {
|
|
7328
7833
|
throw new GitHubGatewayPausedError();
|
|
7329
7834
|
}
|
|
7835
|
+
const key = useCache ? cacheKeyFor(url, options.tokenKey) : null;
|
|
7836
|
+
if (key && !options.bypassSoftCache) {
|
|
7837
|
+
const soft = await loadEtagCache(key);
|
|
7838
|
+
const softOk = soft?.body != null && (priority === "interactive" ? canInteractiveSoftServe({
|
|
7839
|
+
expiresAt: soft.expiresAt,
|
|
7840
|
+
updatedAt: soft.updatedAt,
|
|
7841
|
+
maxAgeMs
|
|
7842
|
+
}) : isSoftCacheFresh({
|
|
7843
|
+
expiresAt: soft.expiresAt,
|
|
7844
|
+
updatedAt: soft.updatedAt,
|
|
7845
|
+
maxAgeMs
|
|
7846
|
+
}));
|
|
7847
|
+
if (softOk && soft?.body != null) {
|
|
7848
|
+
const isFresh = isSoftCacheFresh({
|
|
7849
|
+
expiresAt: soft.expiresAt,
|
|
7850
|
+
updatedAt: soft.updatedAt,
|
|
7851
|
+
maxAgeMs
|
|
7852
|
+
});
|
|
7853
|
+
await recordStat({
|
|
7854
|
+
tokenKey: options.tokenKey,
|
|
7855
|
+
jobId: options.jobId,
|
|
7856
|
+
cacheHit: true
|
|
7857
|
+
});
|
|
7858
|
+
if (priority === "interactive" && !isFresh) {
|
|
7859
|
+
void githubGatewayFetch(url, init, {
|
|
7860
|
+
...options,
|
|
7861
|
+
bypassSoftCache: true,
|
|
7862
|
+
priority: "background",
|
|
7863
|
+
maxAgeMs: maxAgeMs ?? DEFAULT_INTERACTIVE_CACHE_MAX_AGE_MS
|
|
7864
|
+
}).catch(() => {
|
|
7865
|
+
});
|
|
7866
|
+
}
|
|
7867
|
+
return cachedJsonResponse(soft.body, soft.status ?? 200, "soft");
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
const budgetPerMinute = await resolveGithubBudgetPerMinute(
|
|
7871
|
+
options.budgetPerMinute
|
|
7872
|
+
);
|
|
7330
7873
|
const blocked = await getBlockedUntil(options.tokenKey);
|
|
7331
7874
|
if (blocked) {
|
|
7332
7875
|
if (priority === "interactive") {
|
|
7333
7876
|
if (blocked.getTime() - Date.now() > 6e4) {
|
|
7877
|
+
await recordBreakerHit({
|
|
7878
|
+
tokenKey: options.tokenKey,
|
|
7879
|
+
jobId: options.jobId
|
|
7880
|
+
});
|
|
7334
7881
|
throw new GitHubGatewayBlockedError(blocked);
|
|
7335
7882
|
}
|
|
7336
7883
|
} else {
|
|
7884
|
+
await recordBreakerHit({
|
|
7885
|
+
tokenKey: options.tokenKey,
|
|
7886
|
+
jobId: options.jobId
|
|
7887
|
+
});
|
|
7337
7888
|
throw new GitHubGatewayBlockedError(blocked);
|
|
7338
7889
|
}
|
|
7339
7890
|
}
|
|
@@ -7352,14 +7903,32 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7352
7903
|
if (!headers.has("X-GitHub-Api-Version")) {
|
|
7353
7904
|
headers.set("X-GitHub-Api-Version", "2022-11-28");
|
|
7354
7905
|
}
|
|
7355
|
-
const key = useCache ? cacheKeyFor(url, options.tokenKey) : null;
|
|
7356
7906
|
if (key) {
|
|
7357
7907
|
const cached = await loadEtagCache(key);
|
|
7358
7908
|
if (cached?.etag) headers.set("If-None-Match", cached.etag);
|
|
7359
7909
|
}
|
|
7360
7910
|
let lastError = null;
|
|
7361
7911
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
7362
|
-
|
|
7912
|
+
let response;
|
|
7913
|
+
try {
|
|
7914
|
+
response = await fetch(url, {
|
|
7915
|
+
...init,
|
|
7916
|
+
headers,
|
|
7917
|
+
signal: init.signal ?? AbortSignal.timeout(2e4)
|
|
7918
|
+
});
|
|
7919
|
+
} catch (err) {
|
|
7920
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
7921
|
+
await recordStat({
|
|
7922
|
+
tokenKey: options.tokenKey,
|
|
7923
|
+
jobId: options.jobId,
|
|
7924
|
+
error: true
|
|
7925
|
+
});
|
|
7926
|
+
if (!isTransientNetworkError(err) || attempt === MAX_ATTEMPTS) {
|
|
7927
|
+
throw lastError;
|
|
7928
|
+
}
|
|
7929
|
+
await sleep(BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
7930
|
+
continue;
|
|
7931
|
+
}
|
|
7363
7932
|
await updateRateLimitHeaders(options.tokenKey, response);
|
|
7364
7933
|
if (response.status === 304 && key) {
|
|
7365
7934
|
const cached = await loadEtagCache(key);
|
|
@@ -7368,13 +7937,11 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7368
7937
|
jobId: options.jobId,
|
|
7369
7938
|
cacheHit: true
|
|
7370
7939
|
});
|
|
7371
|
-
return
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
}
|
|
7377
|
-
});
|
|
7940
|
+
return cachedJsonResponse(
|
|
7941
|
+
cached?.body ?? "",
|
|
7942
|
+
cached?.status ?? 200,
|
|
7943
|
+
"hit"
|
|
7944
|
+
);
|
|
7378
7945
|
}
|
|
7379
7946
|
if (response.status === 403 || response.status === 429 || isTransientGitHubStatus(response.status)) {
|
|
7380
7947
|
const bodyText = summarizeGitHubErrorBody(
|
|
@@ -7393,6 +7960,10 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7393
7960
|
error: true
|
|
7394
7961
|
});
|
|
7395
7962
|
if (attempt === MAX_ATTEMPTS || secondary) {
|
|
7963
|
+
await recordBreakerHit({
|
|
7964
|
+
tokenKey: options.tokenKey,
|
|
7965
|
+
jobId: options.jobId
|
|
7966
|
+
});
|
|
7396
7967
|
throw new GitHubGatewayBlockedError(until, lastError.message);
|
|
7397
7968
|
}
|
|
7398
7969
|
const retryAfter = Number(response.headers.get("retry-after"));
|
|
@@ -7409,7 +7980,8 @@ async function githubGatewayFetch(url, init, options) {
|
|
|
7409
7980
|
etag,
|
|
7410
7981
|
body,
|
|
7411
7982
|
status: response.status,
|
|
7412
|
-
contentType: response.headers.get("content-type")
|
|
7983
|
+
contentType: response.headers.get("content-type"),
|
|
7984
|
+
maxAgeMs
|
|
7413
7985
|
});
|
|
7414
7986
|
}
|
|
7415
7987
|
await recordStat({
|
|
@@ -8840,7 +9412,7 @@ function getEncryptionKey() {
|
|
|
8840
9412
|
throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
|
|
8841
9413
|
return buf;
|
|
8842
9414
|
}
|
|
8843
|
-
function encrypt(
|
|
9415
|
+
function encrypt(text22) {
|
|
8844
9416
|
const key = getEncryptionKey();
|
|
8845
9417
|
const iv = randomBytes(ENC_IV_LENGTH);
|
|
8846
9418
|
const cipher = createCipheriv(
|
|
@@ -8848,7 +9420,7 @@ function encrypt(text19) {
|
|
|
8848
9420
|
new Uint8Array(key),
|
|
8849
9421
|
new Uint8Array(iv)
|
|
8850
9422
|
);
|
|
8851
|
-
let encrypted = cipher.update(
|
|
9423
|
+
let encrypted = cipher.update(text22, "utf8", "hex");
|
|
8852
9424
|
encrypted += cipher.final("hex");
|
|
8853
9425
|
const authTag = cipher.getAuthTag();
|
|
8854
9426
|
return Buffer.concat([
|
|
@@ -9568,10 +10140,10 @@ async function r2GetObjectRange(bucket, key, range) {
|
|
|
9568
10140
|
const body = result.Body;
|
|
9569
10141
|
if (!body?.transformToString)
|
|
9570
10142
|
throw new Error("R2 returned no readable body");
|
|
9571
|
-
const
|
|
10143
|
+
const text22 = await body.transformToString();
|
|
9572
10144
|
const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
|
|
9573
10145
|
return `${header}
|
|
9574
|
-
${
|
|
10146
|
+
${text22}`;
|
|
9575
10147
|
} catch (e) {
|
|
9576
10148
|
throw r2WrapError(bucket, key, e);
|
|
9577
10149
|
}
|
|
@@ -9934,15 +10506,15 @@ async function sftpRead(opts, filePath, proxy, options) {
|
|
|
9934
10506
|
clearTimeout(timer);
|
|
9935
10507
|
cleanup?.();
|
|
9936
10508
|
cleanup = void 0;
|
|
9937
|
-
const
|
|
10509
|
+
const text22 = Buffer.concat(
|
|
9938
10510
|
chunks.map((ch) => new Uint8Array(ch))
|
|
9939
10511
|
).toString("utf-8");
|
|
9940
10512
|
if (!isWholeFileRequest) {
|
|
9941
10513
|
const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
|
|
9942
10514
|
resolve(`${header}
|
|
9943
|
-
${
|
|
10515
|
+
${text22}`);
|
|
9944
10516
|
} else {
|
|
9945
|
-
resolve(
|
|
10517
|
+
resolve(text22);
|
|
9946
10518
|
}
|
|
9947
10519
|
});
|
|
9948
10520
|
rs.on("error", (e) => {
|
|
@@ -9990,7 +10562,9 @@ var RAW_JSON_TOOLS = /* @__PURE__ */ new Set([
|
|
|
9990
10562
|
"managed-site",
|
|
9991
10563
|
"content-sync",
|
|
9992
10564
|
// Skill registry metadata only (no file bodies) — must stay valid JSON.
|
|
9993
|
-
"cursor-skill"
|
|
10565
|
+
"cursor-skill",
|
|
10566
|
+
// GSC opportunity store (pass + items)
|
|
10567
|
+
"gsc-opportunity"
|
|
9994
10568
|
]);
|
|
9995
10569
|
var TOOL_CACHE_TTL_MS = {
|
|
9996
10570
|
"list-servers": 6e4,
|
|
@@ -10091,11 +10665,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
|
|
|
10091
10665
|
if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
|
|
10092
10666
|
return e.names;
|
|
10093
10667
|
}
|
|
10094
|
-
function truncateForLLM(
|
|
10095
|
-
const totalBytes = Buffer.byteLength(
|
|
10668
|
+
function truncateForLLM(text22, maxBytes) {
|
|
10669
|
+
const totalBytes = Buffer.byteLength(text22, "utf8");
|
|
10096
10670
|
if (totalBytes <= maxBytes)
|
|
10097
|
-
return { text:
|
|
10098
|
-
const buf = Buffer.from(
|
|
10671
|
+
return { text: text22, truncated: false, totalBytes, shownBytes: totalBytes };
|
|
10672
|
+
const buf = Buffer.from(text22, "utf8");
|
|
10099
10673
|
let cut = maxBytes;
|
|
10100
10674
|
while (cut > 0 && (buf[cut] & 192) === 128) cut--;
|
|
10101
10675
|
const head = buf.subarray(0, cut).toString("utf8");
|
|
@@ -10126,10 +10700,10 @@ function postprocessResult(result, meta) {
|
|
|
10126
10700
|
if (!result.content?.length) return result;
|
|
10127
10701
|
if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
|
|
10128
10702
|
const block = result.content[0];
|
|
10129
|
-
let
|
|
10130
|
-
const trunc = truncateForLLM(
|
|
10703
|
+
let text22 = String(block.text ?? "");
|
|
10704
|
+
const trunc = truncateForLLM(text22, RESPONSE_MAX_BYTES);
|
|
10131
10705
|
if (trunc.truncated) {
|
|
10132
|
-
|
|
10706
|
+
text22 = trunc.text + "\n\n... " + buildTruncationHint(
|
|
10133
10707
|
meta.toolName,
|
|
10134
10708
|
meta.args,
|
|
10135
10709
|
trunc.totalBytes,
|
|
@@ -10143,11 +10717,11 @@ function postprocessResult(result, meta) {
|
|
|
10143
10717
|
const parts = [`took ${tookStr}`, sizeStr];
|
|
10144
10718
|
if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
|
|
10145
10719
|
if (meta.cached) parts.push("cached");
|
|
10146
|
-
|
|
10720
|
+
text22 = `${text22}
|
|
10147
10721
|
|
|
10148
10722
|
[${parts.join(", ")}]`;
|
|
10149
10723
|
}
|
|
10150
|
-
return { ...result, content: [{ ...block, text:
|
|
10724
|
+
return { ...result, content: [{ ...block, text: text22 }] };
|
|
10151
10725
|
}
|
|
10152
10726
|
function buildPipelineScript(commands, shell, marker, stopOnError) {
|
|
10153
10727
|
if (shell === "powershell") {
|
|
@@ -10933,11 +11507,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
10933
11507
|
applied_by TEXT
|
|
10934
11508
|
);
|
|
10935
11509
|
`.trim();
|
|
10936
|
-
function normaliseMigrationSql(
|
|
10937
|
-
return
|
|
11510
|
+
function normaliseMigrationSql(sql31) {
|
|
11511
|
+
return sql31.replace(/\r\n/g, "\n").trim() + "\n";
|
|
10938
11512
|
}
|
|
10939
|
-
function migrationSha256(
|
|
10940
|
-
return createHash("sha256").update(
|
|
11513
|
+
function migrationSha256(sql31) {
|
|
11514
|
+
return createHash("sha256").update(sql31.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
10941
11515
|
}
|
|
10942
11516
|
function dollarQuoteTag(value) {
|
|
10943
11517
|
let tag = "_mcp";
|
|
@@ -12803,6 +13377,87 @@ var TOOLS = [
|
|
|
12803
13377
|
}
|
|
12804
13378
|
},
|
|
12805
13379
|
// ----- Shared Cursor skills (metadata only; install via downloads ZIP) -----
|
|
13380
|
+
{
|
|
13381
|
+
name: "gsc-opportunity",
|
|
13382
|
+
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).',
|
|
13383
|
+
inputSchema: {
|
|
13384
|
+
type: "object",
|
|
13385
|
+
properties: {
|
|
13386
|
+
action: {
|
|
13387
|
+
type: "string",
|
|
13388
|
+
enum: ["list", "upsert", "close"],
|
|
13389
|
+
description: "list | upsert | close"
|
|
13390
|
+
},
|
|
13391
|
+
siteKey: {
|
|
13392
|
+
type: "string",
|
|
13393
|
+
description: "Stable site id, e.g. solarfast (required for all actions)."
|
|
13394
|
+
},
|
|
13395
|
+
status: {
|
|
13396
|
+
type: "string",
|
|
13397
|
+
enum: ["awaiting", "monitor", "pending_reindex", "skipped", "closed"],
|
|
13398
|
+
description: "action=list: single status filter."
|
|
13399
|
+
},
|
|
13400
|
+
statuses: {
|
|
13401
|
+
type: "array",
|
|
13402
|
+
items: {
|
|
13403
|
+
type: "string",
|
|
13404
|
+
enum: ["awaiting", "monitor", "pending_reindex", "skipped", "closed"]
|
|
13405
|
+
},
|
|
13406
|
+
description: "action=list: multi status filter."
|
|
13407
|
+
},
|
|
13408
|
+
kind: {
|
|
13409
|
+
type: "string",
|
|
13410
|
+
enum: ["analysis", "ctr_polish", "reindex", "cls", "other"],
|
|
13411
|
+
description: "action=list/upsert: pass kind."
|
|
13412
|
+
},
|
|
13413
|
+
includeClosed: {
|
|
13414
|
+
type: "boolean",
|
|
13415
|
+
description: "action=list: include closed items (default false)."
|
|
13416
|
+
},
|
|
13417
|
+
limit: { type: "number", description: "action=list: max items (default 50)." },
|
|
13418
|
+
offset: { type: "number", description: "action=list: pagination offset." },
|
|
13419
|
+
gscProperty: {
|
|
13420
|
+
type: "string",
|
|
13421
|
+
description: "action=upsert: e.g. sc-domain:solarfast.nl"
|
|
13422
|
+
},
|
|
13423
|
+
passId: {
|
|
13424
|
+
type: "string",
|
|
13425
|
+
description: "action=upsert: update existing pass uuid."
|
|
13426
|
+
},
|
|
13427
|
+
headline: { type: "string", description: "action=upsert: pass headline." },
|
|
13428
|
+
windowFrom: { type: "string", description: "action=upsert: YYYY-MM-DD." },
|
|
13429
|
+
windowTo: { type: "string", description: "action=upsert: YYYY-MM-DD." },
|
|
13430
|
+
canvasPath: { type: "string", description: "action=upsert: optional canvas path." },
|
|
13431
|
+
createdBy: { type: "string", description: "action=upsert: agent/user label." },
|
|
13432
|
+
passedAt: {
|
|
13433
|
+
type: "string",
|
|
13434
|
+
description: "action=upsert: ISO datetime for the pass."
|
|
13435
|
+
},
|
|
13436
|
+
items: {
|
|
13437
|
+
type: "array",
|
|
13438
|
+
description: "action=upsert: items [{url, queryCluster?, status?, changeSummary?, shippedAt?, expectEffectAfter?, sourceUrls?, notes?, id?}]",
|
|
13439
|
+
items: { type: "object" }
|
|
13440
|
+
},
|
|
13441
|
+
id: {
|
|
13442
|
+
type: "string",
|
|
13443
|
+
description: "action=close: item uuid (preferred)."
|
|
13444
|
+
},
|
|
13445
|
+
url: {
|
|
13446
|
+
type: "string",
|
|
13447
|
+
description: "action=close: item url when id omitted."
|
|
13448
|
+
},
|
|
13449
|
+
queryCluster: {
|
|
13450
|
+
type: "string",
|
|
13451
|
+
description: "action=close: optional cluster disambiguator."
|
|
13452
|
+
},
|
|
13453
|
+
notes: {
|
|
13454
|
+
type: "string",
|
|
13455
|
+
description: "action=close/upsert item notes."
|
|
13456
|
+
}
|
|
13457
|
+
},
|
|
13458
|
+
required: ["action", "siteKey"]
|
|
13459
|
+
}
|
|
13460
|
+
},
|
|
12806
13461
|
{
|
|
12807
13462
|
name: "cursor-skill",
|
|
12808
13463
|
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 +13488,7 @@ var TOOLS = [
|
|
|
12833
13488
|
// ----- Trigger.dev -----
|
|
12834
13489
|
...TRIGGER_TOOLS
|
|
12835
13490
|
];
|
|
12836
|
-
var MCP_VERSION = "7.4.
|
|
13491
|
+
var MCP_VERSION = "7.4.14";
|
|
12837
13492
|
async function handleListTools() {
|
|
12838
13493
|
if (!authContext) return { tools: TOOLS };
|
|
12839
13494
|
const allowedTools = authContext.allowedTools;
|
|
@@ -13050,6 +13705,39 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13050
13705
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
13051
13706
|
};
|
|
13052
13707
|
}
|
|
13708
|
+
case "gsc-opportunity": {
|
|
13709
|
+
const action = typeof a.action === "string" ? a.action.trim() : "";
|
|
13710
|
+
if (!action) {
|
|
13711
|
+
return {
|
|
13712
|
+
content: [{ type: "text", text: "Error: action is required" }]
|
|
13713
|
+
};
|
|
13714
|
+
}
|
|
13715
|
+
const { action: _action, ...params } = a;
|
|
13716
|
+
void _action;
|
|
13717
|
+
const res = await fetch(`${dashboardBaseUrl}/api/tools/gsc-opportunity`, {
|
|
13718
|
+
method: "POST",
|
|
13719
|
+
headers: {
|
|
13720
|
+
"content-type": "application/json",
|
|
13721
|
+
authorization: `Bearer ${apiKey}`
|
|
13722
|
+
},
|
|
13723
|
+
body: JSON.stringify({ action, params })
|
|
13724
|
+
});
|
|
13725
|
+
if (!res.ok) {
|
|
13726
|
+
const detail = await res.text().catch(() => "");
|
|
13727
|
+
return {
|
|
13728
|
+
content: [
|
|
13729
|
+
{
|
|
13730
|
+
type: "text",
|
|
13731
|
+
text: `Error: gsc-opportunity failed (${res.status}). ${detail.slice(0, 400)}`
|
|
13732
|
+
}
|
|
13733
|
+
]
|
|
13734
|
+
};
|
|
13735
|
+
}
|
|
13736
|
+
const data = await res.json();
|
|
13737
|
+
return {
|
|
13738
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
13739
|
+
};
|
|
13740
|
+
}
|
|
13053
13741
|
// ----- Team memory -----
|
|
13054
13742
|
case "search-team-memory": {
|
|
13055
13743
|
const query = typeof a.query === "string" ? a.query.trim() : "";
|
|
@@ -13091,7 +13779,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13091
13779
|
]
|
|
13092
13780
|
};
|
|
13093
13781
|
}
|
|
13094
|
-
const lines = data.hits.map((hit,
|
|
13782
|
+
const lines = data.hits.map((hit, index19) => {
|
|
13095
13783
|
const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
|
|
13096
13784
|
const repo = hit.repo ?? "unknown repo";
|
|
13097
13785
|
const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
|
|
@@ -13101,7 +13789,7 @@ async function executeToolCall(name, a, _serverId) {
|
|
|
13101
13789
|
...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
|
|
13102
13790
|
].filter(Boolean);
|
|
13103
13791
|
const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
|
|
13104
|
-
return `${
|
|
13792
|
+
return `${index19 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
|
|
13105
13793
|
id: ${hit.id}
|
|
13106
13794
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
|
|
13107
13795
|
});
|
|
@@ -13308,12 +13996,12 @@ Searchable via search-team-memory once indexing completes (usually immediate).`
|
|
|
13308
13996
|
]
|
|
13309
13997
|
};
|
|
13310
13998
|
}
|
|
13311
|
-
const lines = data.hits.map((hit,
|
|
13999
|
+
const lines = data.hits.map((hit, index19) => {
|
|
13312
14000
|
const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
|
|
13313
14001
|
const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
|
|
13314
14002
|
const sources = hit.sources.length ? `
|
|
13315
14003
|
sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
|
|
13316
|
-
return `${
|
|
14004
|
+
return `${index19 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
|
|
13317
14005
|
slug: ${hit.slug}${sources}
|
|
13318
14006
|
${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
|
|
13319
14007
|
});
|
|
@@ -13370,8 +14058,8 @@ Call get-wiki-page with a slug for the full page; follow its sources (team-memor
|
|
|
13370
14058
|
const formatSource = (s) => {
|
|
13371
14059
|
const base = `${s.type}:${s.ref}`;
|
|
13372
14060
|
if (s.title) {
|
|
13373
|
-
const
|
|
13374
|
-
return `${base} \u2014 "${s.title}"${
|
|
14061
|
+
const date3 = s.date ? ` (${s.date})` : "";
|
|
14062
|
+
return `${base} \u2014 "${s.title}"${date3}`;
|
|
13375
14063
|
}
|
|
13376
14064
|
return base;
|
|
13377
14065
|
};
|
|
@@ -14570,8 +15258,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
14570
15258
|
};
|
|
14571
15259
|
const filtered = sortRows(applyFilter(only.rows));
|
|
14572
15260
|
if (format === "json") {
|
|
14573
|
-
const
|
|
14574
|
-
return { content: [{ type: "text", text:
|
|
15261
|
+
const text23 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
|
|
15262
|
+
return { content: [{ type: "text", text: text23 }] };
|
|
14575
15263
|
}
|
|
14576
15264
|
if (groupByProject) {
|
|
14577
15265
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -14598,8 +15286,8 @@ ${sample.join("\n")}${files.length > 5 ? `
|
|
|
14598
15286
|
}
|
|
14599
15287
|
const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
|
|
14600
15288
|
const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
|
|
14601
|
-
const
|
|
14602
|
-
return { content: [{ type: "text", text:
|
|
15289
|
+
const text22 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
|
|
15290
|
+
return { content: [{ type: "text", text: text22 }] };
|
|
14603
15291
|
}
|
|
14604
15292
|
if (format === "json") {
|
|
14605
15293
|
const lines = [];
|
package/package.json
CHANGED