@mandujs/core 0.53.1 → 0.53.3
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/package.json +1 -1
- package/src/bundler/build.ts +54 -26
- package/src/bundler/dev.ts +21 -17
- package/src/bundler/types.ts +8 -3
- package/src/config/validate.ts +3 -3
- package/src/db/index.ts +138 -51
- package/src/db/migrations/lock.ts +67 -12
- package/src/db/migrations/runner.ts +118 -101
- package/src/diagnose/__tests__/checks.test.ts +74 -3
- package/src/diagnose/checks.ts +112 -0
- package/src/diagnose/index.ts +1 -0
- package/src/diagnose/run.ts +2 -0
- package/src/kitchen/api/agent-devtools-api.ts +544 -0
- package/src/kitchen/kitchen-handler.ts +33 -16
- package/src/kitchen/kitchen-ui.ts +346 -62
- package/src/resource/__tests__/generator.test.ts +32 -15
- package/src/resource/ddl/__tests__/emit.test.ts +24 -0
- package/src/resource/ddl/emit.ts +12 -1
- package/src/resource/generator-repo.ts +40 -20
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
|
@@ -57,12 +57,13 @@ export function renderKitchenHTML(): string {
|
|
|
57
57
|
</div>
|
|
58
58
|
</section>
|
|
59
59
|
|
|
60
|
-
<nav class="tabs">
|
|
61
|
-
<button class="tab" data-panel="activity">Activity</button>
|
|
62
|
-
<button class="tab
|
|
63
|
-
<button class="tab" data-panel="
|
|
64
|
-
<button class="tab" data-panel="
|
|
65
|
-
<button class="tab" data-panel="
|
|
60
|
+
<nav class="tabs">
|
|
61
|
+
<button class="tab" data-panel="activity">Activity</button>
|
|
62
|
+
<button class="tab" data-panel="agent">Agent</button>
|
|
63
|
+
<button class="tab active" data-panel="routes">Routes</button>
|
|
64
|
+
<button class="tab" data-panel="guard">Guard</button>
|
|
65
|
+
<button class="tab" data-panel="preview">Preview</button>
|
|
66
|
+
<button class="tab" data-panel="contracts">Contracts</button>
|
|
66
67
|
<button class="tab" data-panel="requests">Requests</button>
|
|
67
68
|
<button class="tab" data-panel="mcp-activity">MCP</button>
|
|
68
69
|
<button class="tab" data-panel="cache">Cache</button>
|
|
@@ -70,24 +71,37 @@ export function renderKitchenHTML(): string {
|
|
|
70
71
|
</nav>
|
|
71
72
|
|
|
72
73
|
<main class="panels">
|
|
73
|
-
<section id="panel-activity" class="panel">
|
|
74
|
-
<div class="panel-header">
|
|
75
|
-
<div>
|
|
76
|
-
<h2>Activity Stream</h2>
|
|
77
|
-
<p class="panel-subtitle">Recent Kitchen events and MCP activity.</p>
|
|
74
|
+
<section id="panel-activity" class="panel">
|
|
75
|
+
<div class="panel-header">
|
|
76
|
+
<div>
|
|
77
|
+
<h2>Activity Stream</h2>
|
|
78
|
+
<p class="panel-subtitle">Recent Kitchen events and MCP activity.</p>
|
|
78
79
|
</div>
|
|
79
80
|
<button id="clear-activity" class="btn-sm">Clear</button>
|
|
80
81
|
</div>
|
|
81
82
|
<div id="activity-list" class="activity-list">
|
|
82
83
|
<div class="empty-state">Waiting for MCP activity...</div>
|
|
83
|
-
</div>
|
|
84
|
-
</section>
|
|
85
|
-
|
|
86
|
-
<section id="panel-
|
|
87
|
-
<div class="panel-header">
|
|
88
|
-
<div>
|
|
89
|
-
<h2>
|
|
90
|
-
<p class="panel-subtitle">
|
|
84
|
+
</div>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<section id="panel-agent" class="panel">
|
|
88
|
+
<div class="panel-header">
|
|
89
|
+
<div>
|
|
90
|
+
<h2>Agent Supervisor</h2>
|
|
91
|
+
<p class="panel-subtitle">Situation brief, tool routing, prompt pack, and next safe action for supervised agents.</p>
|
|
92
|
+
</div>
|
|
93
|
+
<button id="refresh-agent" class="btn-sm">Refresh</button>
|
|
94
|
+
</div>
|
|
95
|
+
<div id="agent-content" class="agent-content">
|
|
96
|
+
<div class="empty-state">Open Agent to build a context pack.</div>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<section id="panel-routes" class="panel active">
|
|
101
|
+
<div class="panel-header">
|
|
102
|
+
<div>
|
|
103
|
+
<h2>Routes</h2>
|
|
104
|
+
<p class="panel-subtitle">Current filesystem routes, slots, contracts, and hydration hints.</p>
|
|
91
105
|
</div>
|
|
92
106
|
<div id="routes-summary" class="summary"></div>
|
|
93
107
|
</div>
|
|
@@ -1188,11 +1202,141 @@ const CSS = /* css */ `
|
|
|
1188
1202
|
box-shadow: 0 12px 28px rgba(30, 42, 58, 0.22);
|
|
1189
1203
|
}
|
|
1190
1204
|
|
|
1191
|
-
.requests-list, .mcp-list, .cache-content, .metrics-content {
|
|
1192
|
-
display: flex;
|
|
1193
|
-
flex-direction: column;
|
|
1194
|
-
gap: 6px;
|
|
1195
|
-
}
|
|
1205
|
+
.requests-list, .mcp-list, .cache-content, .metrics-content, .agent-content {
|
|
1206
|
+
display: flex;
|
|
1207
|
+
flex-direction: column;
|
|
1208
|
+
gap: 6px;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
.agent-grid {
|
|
1212
|
+
display: grid;
|
|
1213
|
+
grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
|
|
1214
|
+
gap: 14px;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.agent-stack {
|
|
1218
|
+
display: flex;
|
|
1219
|
+
flex-direction: column;
|
|
1220
|
+
gap: 12px;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.agent-card {
|
|
1224
|
+
padding: 14px;
|
|
1225
|
+
border: 1px solid var(--line);
|
|
1226
|
+
border-radius: 8px;
|
|
1227
|
+
background: rgba(255, 255, 255, 0.72);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.agent-card-title {
|
|
1231
|
+
display: flex;
|
|
1232
|
+
align-items: center;
|
|
1233
|
+
justify-content: space-between;
|
|
1234
|
+
gap: 10px;
|
|
1235
|
+
margin-bottom: 8px;
|
|
1236
|
+
color: var(--ink);
|
|
1237
|
+
font-size: 14px;
|
|
1238
|
+
font-weight: 700;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.agent-card-body {
|
|
1242
|
+
color: var(--muted);
|
|
1243
|
+
font-size: 13px;
|
|
1244
|
+
line-height: 1.55;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.agent-pill-row {
|
|
1248
|
+
display: flex;
|
|
1249
|
+
flex-wrap: wrap;
|
|
1250
|
+
gap: 6px;
|
|
1251
|
+
margin-top: 10px;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.agent-pill {
|
|
1255
|
+
padding: 4px 8px;
|
|
1256
|
+
border-radius: 6px;
|
|
1257
|
+
background: rgba(30, 42, 58, 0.08);
|
|
1258
|
+
color: var(--ink);
|
|
1259
|
+
font-family: "IBM Plex Mono", "Cascadia Code", monospace;
|
|
1260
|
+
font-size: 11px;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.agent-severity {
|
|
1264
|
+
padding: 3px 7px;
|
|
1265
|
+
border-radius: 6px;
|
|
1266
|
+
font-size: 11px;
|
|
1267
|
+
font-weight: 700;
|
|
1268
|
+
text-transform: uppercase;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
.agent-severity.info { background: rgba(46, 102, 184, 0.12); color: var(--info); }
|
|
1272
|
+
.agent-severity.warn { background: rgba(173, 122, 18, 0.14); color: var(--warning); }
|
|
1273
|
+
.agent-severity.error { background: rgba(188, 61, 61, 0.12); color: var(--danger); }
|
|
1274
|
+
|
|
1275
|
+
.agent-stat-grid {
|
|
1276
|
+
display: grid;
|
|
1277
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1278
|
+
gap: 8px;
|
|
1279
|
+
margin-top: 12px;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.agent-stat {
|
|
1283
|
+
padding: 10px;
|
|
1284
|
+
border-radius: 8px;
|
|
1285
|
+
border: 1px solid rgba(220, 207, 186, 0.7);
|
|
1286
|
+
background: rgba(255, 253, 250, 0.84);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.agent-stat-label {
|
|
1290
|
+
color: var(--muted);
|
|
1291
|
+
font-size: 11px;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
.agent-stat-value {
|
|
1295
|
+
color: var(--ink);
|
|
1296
|
+
font-size: 19px;
|
|
1297
|
+
font-weight: 700;
|
|
1298
|
+
margin-top: 3px;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.agent-rec {
|
|
1302
|
+
display: grid;
|
|
1303
|
+
grid-template-columns: 150px 1fr;
|
|
1304
|
+
gap: 10px;
|
|
1305
|
+
padding: 10px 0;
|
|
1306
|
+
border-top: 1px solid rgba(220, 207, 186, 0.65);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.agent-rec:first-child {
|
|
1310
|
+
border-top: 0;
|
|
1311
|
+
padding-top: 0;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.agent-rec-title {
|
|
1315
|
+
color: var(--ink);
|
|
1316
|
+
font-weight: 700;
|
|
1317
|
+
font-size: 13px;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.agent-rec-detail {
|
|
1321
|
+
color: var(--muted);
|
|
1322
|
+
font-size: 12px;
|
|
1323
|
+
line-height: 1.5;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.agent-prompt {
|
|
1327
|
+
width: 100%;
|
|
1328
|
+
min-height: 220px;
|
|
1329
|
+
margin-top: 10px;
|
|
1330
|
+
padding: 12px;
|
|
1331
|
+
resize: vertical;
|
|
1332
|
+
border-radius: 8px;
|
|
1333
|
+
border: 1px solid var(--line);
|
|
1334
|
+
background: #fffdfa;
|
|
1335
|
+
color: var(--ink);
|
|
1336
|
+
font-family: "IBM Plex Mono", "Cascadia Code", monospace;
|
|
1337
|
+
font-size: 12px;
|
|
1338
|
+
line-height: 1.45;
|
|
1339
|
+
}
|
|
1196
1340
|
|
|
1197
1341
|
.req-row {
|
|
1198
1342
|
display: grid;
|
|
@@ -1313,13 +1457,17 @@ const CSS = /* css */ `
|
|
|
1313
1457
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1314
1458
|
}
|
|
1315
1459
|
|
|
1316
|
-
.contracts-layout {
|
|
1317
|
-
grid-template-columns: 1fr;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
.
|
|
1321
|
-
grid-template-columns:
|
|
1322
|
-
}
|
|
1460
|
+
.contracts-layout {
|
|
1461
|
+
grid-template-columns: 1fr;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.agent-grid {
|
|
1465
|
+
grid-template-columns: 1fr;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
.guard-summary {
|
|
1469
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1470
|
+
}
|
|
1323
1471
|
}
|
|
1324
1472
|
|
|
1325
1473
|
@media (max-width: 720px) {
|
|
@@ -1345,12 +1493,20 @@ const CSS = /* css */ `
|
|
|
1345
1493
|
width: calc(50% - 4px);
|
|
1346
1494
|
justify-content: center;
|
|
1347
1495
|
}
|
|
1348
|
-
|
|
1349
|
-
.panel-header {
|
|
1350
|
-
flex-direction: column;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1496
|
+
|
|
1497
|
+
.panel-header {
|
|
1498
|
+
flex-direction: column;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
.agent-stat-grid {
|
|
1502
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
.agent-rec {
|
|
1506
|
+
grid-template-columns: 1fr;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
`;
|
|
1354
1510
|
|
|
1355
1511
|
// ─── JavaScript ──────────────────────────────────
|
|
1356
1512
|
|
|
@@ -1367,12 +1523,12 @@ const JS = /* js */ `
|
|
|
1367
1523
|
console.log('[Kitchen]', msg);
|
|
1368
1524
|
}
|
|
1369
1525
|
|
|
1370
|
-
function escapeHtml(str) {
|
|
1371
|
-
if (
|
|
1372
|
-
var d = document.createElement('div');
|
|
1373
|
-
d.appendChild(document.createTextNode(String(str)));
|
|
1374
|
-
return d.innerHTML;
|
|
1375
|
-
}
|
|
1526
|
+
function escapeHtml(str) {
|
|
1527
|
+
if (str === null || str === undefined) return '';
|
|
1528
|
+
var d = document.createElement('div');
|
|
1529
|
+
d.appendChild(document.createTextNode(String(str)));
|
|
1530
|
+
return d.innerHTML;
|
|
1531
|
+
}
|
|
1376
1532
|
|
|
1377
1533
|
function setMetric(id, value) {
|
|
1378
1534
|
var el = document.getElementById(id);
|
|
@@ -1893,9 +2049,10 @@ const JS = /* js */ `
|
|
|
1893
2049
|
|
|
1894
2050
|
loadContracts();
|
|
1895
2051
|
|
|
1896
|
-
// ─── Requests Tab ─────────────────────────────
|
|
1897
|
-
var requestsListEl = document.getElementById('requests-list');
|
|
1898
|
-
var requestsDetailEl = document.getElementById('requests-detail');
|
|
2052
|
+
// ─── Requests Tab ─────────────────────────────
|
|
2053
|
+
var requestsListEl = document.getElementById('requests-list');
|
|
2054
|
+
var requestsDetailEl = document.getElementById('requests-detail');
|
|
2055
|
+
var agentContentEl = document.getElementById('agent-content');
|
|
1899
2056
|
|
|
1900
2057
|
function fetchJson(url, cb) {
|
|
1901
2058
|
var xhr = new XMLHttpRequest();
|
|
@@ -1910,16 +2067,142 @@ const JS = /* js */ `
|
|
|
1910
2067
|
xhr.send();
|
|
1911
2068
|
}
|
|
1912
2069
|
|
|
1913
|
-
function statusClass(s) {
|
|
1914
|
-
if (!s) return '';
|
|
1915
|
-
if (s >= 500) return 's5';
|
|
1916
|
-
if (s >= 400) return 's4';
|
|
1917
|
-
if (s >= 300) return 's3';
|
|
1918
|
-
return 's2';
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2070
|
+
function statusClass(s) {
|
|
2071
|
+
if (!s) return '';
|
|
2072
|
+
if (s >= 500) return 's5';
|
|
2073
|
+
if (s >= 400) return 's4';
|
|
2074
|
+
if (s >= 300) return 's3';
|
|
2075
|
+
return 's2';
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
// ─── Agent Supervisor Tab ─────────────────────
|
|
2079
|
+
function renderAgentPills(items) {
|
|
2080
|
+
if (!items || !items.length) return '';
|
|
2081
|
+
var html = '<div class="agent-pill-row">';
|
|
2082
|
+
for (var i = 0; i < items.length; i++) {
|
|
2083
|
+
html += '<span class="agent-pill">' + escapeHtml(items[i]) + '</span>';
|
|
2084
|
+
}
|
|
2085
|
+
return html + '</div>';
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
function renderAgentDetails(items) {
|
|
2089
|
+
if (!items || !items.length) return '';
|
|
2090
|
+
var html = '<ul>';
|
|
2091
|
+
for (var i = 0; i < items.length; i++) {
|
|
2092
|
+
html += '<li>' + escapeHtml(items[i]) + '</li>';
|
|
2093
|
+
}
|
|
2094
|
+
return html + '</ul>';
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
function renderAgentContext(data) {
|
|
2098
|
+
var situation = data.situation || {};
|
|
2099
|
+
var summary = data.summary || {};
|
|
2100
|
+
var routes = summary.routes || {};
|
|
2101
|
+
var status = data.agentStatus || {};
|
|
2102
|
+
var brain = status.brain || {};
|
|
2103
|
+
var action = data.nextSafeAction || {};
|
|
2104
|
+
var prompt = data.prompt || {};
|
|
2105
|
+
var recs = data.toolRecommendations || [];
|
|
2106
|
+
var cards = data.knowledgeCards || [];
|
|
2107
|
+
var promptText = prompt.copyText || '';
|
|
2108
|
+
|
|
2109
|
+
var html = '<div class="agent-grid">' +
|
|
2110
|
+
'<div class="agent-stack">' +
|
|
2111
|
+
'<div class="agent-card">' +
|
|
2112
|
+
'<div class="agent-card-title">' +
|
|
2113
|
+
'<span>' + escapeHtml(situation.title || 'No situation') + '</span>' +
|
|
2114
|
+
'<span class="agent-severity ' + escapeHtml(situation.severity || 'info') + '">' + escapeHtml(situation.category || 'agent') + '</span>' +
|
|
2115
|
+
'</div>' +
|
|
2116
|
+
'<div class="agent-card-body">' + renderAgentDetails(situation.details || []) + '</div>' +
|
|
2117
|
+
'<div class="agent-stat-grid">' +
|
|
2118
|
+
'<div class="agent-stat"><div class="agent-stat-label">Routes</div><div class="agent-stat-value">' + escapeHtml(routes.total || 0) + '</div></div>' +
|
|
2119
|
+
'<div class="agent-stat"><div class="agent-stat-label">Islands</div><div class="agent-stat-value">' + escapeHtml(routes.islands || 0) + '</div></div>' +
|
|
2120
|
+
'<div class="agent-stat"><div class="agent-stat-label">Contracts</div><div class="agent-stat-value">' + escapeHtml(routes.contracts || 0) + '</div></div>' +
|
|
2121
|
+
'<div class="agent-stat"><div class="agent-stat-label">MCP Calls</div><div class="agent-stat-value">' + escapeHtml(status.observedToolCalls || 0) + '</div></div>' +
|
|
2122
|
+
'</div>' +
|
|
2123
|
+
'</div>' +
|
|
2124
|
+
'<div class="agent-card">' +
|
|
2125
|
+
'<div class="agent-card-title"><span>Next Safe Action</span><span class="agent-severity info">' + escapeHtml(action.mode || 'observe') + '</span></div>' +
|
|
2126
|
+
'<div class="agent-card-body"><strong>' + escapeHtml(action.title || '-') + '</strong><br>' + escapeHtml(action.reason || '') + '</div>' +
|
|
2127
|
+
renderAgentPills([action.tool || '', action.command || ''].filter(Boolean)) +
|
|
2128
|
+
renderAgentPills(action.validation || []) +
|
|
2129
|
+
'</div>' +
|
|
2130
|
+
'<div class="agent-card">' +
|
|
2131
|
+
'<div class="agent-card-title"><span>Prompt Pack</span><button id="copy-agent-prompt" class="btn-sm">Copy</button></div>' +
|
|
2132
|
+
'<textarea id="agent-prompt-text" class="agent-prompt" readonly>' + escapeHtml(promptText) + '</textarea>' +
|
|
2133
|
+
'</div>' +
|
|
2134
|
+
'</div>' +
|
|
2135
|
+
'<div class="agent-stack">' +
|
|
2136
|
+
'<div class="agent-card">' +
|
|
2137
|
+
'<div class="agent-card-title"><span>Brain</span><span class="agent-severity info">' + escapeHtml(brain.oauth || 'unknown') + '</span></div>' +
|
|
2138
|
+
'<div class="agent-card-body">' + escapeHtml(brain.note || '') + '</div>' +
|
|
2139
|
+
renderAgentPills([brain.statusTool || 'mandu.brain.status']) +
|
|
2140
|
+
'</div>' +
|
|
2141
|
+
'<div class="agent-card">' +
|
|
2142
|
+
'<div class="agent-card-title"><span>Tool Router</span></div>';
|
|
2143
|
+
|
|
2144
|
+
for (var i = 0; i < recs.length; i++) {
|
|
2145
|
+
var r = recs[i];
|
|
2146
|
+
html += '<div class="agent-rec">' +
|
|
2147
|
+
'<div class="agent-rec-title">' + escapeHtml(r.skill) + '</div>' +
|
|
2148
|
+
'<div class="agent-rec-detail">' +
|
|
2149
|
+
'<strong>' + escapeHtml(r.task) + '</strong><br>' +
|
|
2150
|
+
escapeHtml(r.useWhen) +
|
|
2151
|
+
renderAgentPills(r.mcpTools || []) +
|
|
2152
|
+
'<div style="margin-top:6px;">Fallback: <code>' + escapeHtml(r.cliFallback) + '</code></div>' +
|
|
2153
|
+
'</div>' +
|
|
2154
|
+
'</div>';
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
html += '</div><div class="agent-card">' +
|
|
2158
|
+
'<div class="agent-card-title"><span>Knowledge</span></div>';
|
|
2159
|
+
|
|
2160
|
+
for (var c = 0; c < cards.length; c++) {
|
|
2161
|
+
var card = cards[c];
|
|
2162
|
+
html += '<div class="agent-rec">' +
|
|
2163
|
+
'<div class="agent-rec-title">' + escapeHtml(card.title) + '</div>' +
|
|
2164
|
+
'<div class="agent-rec-detail">' + escapeHtml(card.body) + renderAgentPills(card.references || []) + '</div>' +
|
|
2165
|
+
'</div>';
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
html += '</div></div></div>';
|
|
2169
|
+
agentContentEl.innerHTML = html;
|
|
2170
|
+
|
|
2171
|
+
var copyBtn = document.getElementById('copy-agent-prompt');
|
|
2172
|
+
if (copyBtn) {
|
|
2173
|
+
copyBtn.addEventListener('click', function() {
|
|
2174
|
+
var textEl = document.getElementById('agent-prompt-text');
|
|
2175
|
+
var text = textEl ? textEl.value : promptText;
|
|
2176
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
2177
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
2178
|
+
copyBtn.textContent = 'Copied';
|
|
2179
|
+
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 900);
|
|
2180
|
+
}).catch(function() {
|
|
2181
|
+
textEl && textEl.select();
|
|
2182
|
+
});
|
|
2183
|
+
} else if (textEl) {
|
|
2184
|
+
textEl.select();
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
function loadAgentContext() {
|
|
2191
|
+
if (!agentContentEl) return;
|
|
2192
|
+
agentContentEl.innerHTML = '<div class="empty-state">Building agent context...</div>';
|
|
2193
|
+
fetchJson('/__kitchen/api/agent-context', function(err, data) {
|
|
2194
|
+
if (err) {
|
|
2195
|
+
agentContentEl.innerHTML = '<div class="empty-state">Failed: ' + escapeHtml(err.message) + '</div>';
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
renderAgentContext(data);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
document.getElementById('refresh-agent').addEventListener('click', loadAgentContext);
|
|
2203
|
+
|
|
2204
|
+
function loadRequests() {
|
|
2205
|
+
requestsListEl.innerHTML = '<div class="empty-state">Loading requests...</div>';
|
|
1923
2206
|
fetchJson('/__kitchen/api/requests?limit=100', function(err, data) {
|
|
1924
2207
|
if (err) {
|
|
1925
2208
|
requestsListEl.innerHTML = '<div class="empty-state">Failed: ' + escapeHtml(err.message) + '</div>';
|
|
@@ -2116,12 +2399,13 @@ const JS = /* js */ `
|
|
|
2116
2399
|
document.getElementById('refresh-metrics').addEventListener('click', loadMetrics);
|
|
2117
2400
|
|
|
2118
2401
|
// Lazy-load new tab data when clicked (avoids loading everything up front)
|
|
2119
|
-
var tabLoaders = {
|
|
2120
|
-
'
|
|
2121
|
-
'
|
|
2122
|
-
'
|
|
2123
|
-
'
|
|
2124
|
-
|
|
2402
|
+
var tabLoaders = {
|
|
2403
|
+
'agent': loadAgentContext,
|
|
2404
|
+
'requests': loadRequests,
|
|
2405
|
+
'mcp-activity': loadMcpActivity,
|
|
2406
|
+
'cache': loadCacheStats,
|
|
2407
|
+
'metrics': loadMetrics
|
|
2408
|
+
};
|
|
2125
2409
|
var loadedTabs = {};
|
|
2126
2410
|
for (var t = 0; t < tabs.length; t++) {
|
|
2127
2411
|
tabs[t].addEventListener('click', function() {
|
|
@@ -574,7 +574,7 @@ describe("generateRepoSource — emission shape", () => {
|
|
|
574
574
|
expect(generateRepoSource(nonPersistent, { enable: false })).toBeNull();
|
|
575
575
|
});
|
|
576
576
|
|
|
577
|
-
test("postgres/sqlite emit INSERT ... RETURNING, mysql emits INSERT +
|
|
577
|
+
test("postgres/sqlite emit INSERT ... RETURNING, mysql emits INSERT + primary-key re-select", () => {
|
|
578
578
|
// Assert on SQL in the emitted code rather than on comments —
|
|
579
579
|
// comments reference "RETURNING" as prose across all three providers.
|
|
580
580
|
// The discriminator we check is the actual SQL keyword on an insert
|
|
@@ -585,20 +585,37 @@ describe("generateRepoSource — emission shape", () => {
|
|
|
585
585
|
const sqlite = generateRepoSource(persistentResource("user", "sqlite"))!;
|
|
586
586
|
expect(sqlite).toMatch(/INSERT INTO[\s\S]*?RETURNING/);
|
|
587
587
|
|
|
588
|
-
const mysql = generateRepoSource(persistentResource("user", "mysql"))!;
|
|
589
|
-
expect(mysql).not.toMatch(/INSERT INTO[\s\S]*?RETURNING/);
|
|
590
|
-
expect(mysql).toContain("
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
expect(mysql).
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
expect(pg).
|
|
601
|
-
|
|
588
|
+
const mysql = generateRepoSource(persistentResource("user", "mysql"))!;
|
|
589
|
+
expect(mysql).not.toMatch(/INSERT INTO[\s\S]*?RETURNING/);
|
|
590
|
+
expect(mysql).toContain("WHERE \\`id\\` = ${input.id}");
|
|
591
|
+
expect(mysql).not.toContain("WHERE \\`id\\` = LAST_INSERT_ID()");
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("mysql repo escapes backtick identifiers for generated template literals, postgres uses double quotes", () => {
|
|
595
|
+
const mysql = generateRepoSource(persistentResource("user", "mysql"))!;
|
|
596
|
+
expect(mysql).toContain("\\`users\\`");
|
|
597
|
+
expect(mysql).not.toContain(`"users"`);
|
|
598
|
+
|
|
599
|
+
const pg = generateRepoSource(persistentResource("user", "postgres"))!;
|
|
600
|
+
expect(pg).toContain(`"users"`);
|
|
601
|
+
expect(pg).not.toContain("\\`users\\`");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test("create input includes primary key unless the primary key has a DB default", () => {
|
|
605
|
+
const callerSuppliedPk = generateRepoSource(persistentResource("user", "postgres"))!;
|
|
606
|
+
expect(callerSuppliedPk).toContain("async create(input: User)");
|
|
607
|
+
expect(callerSuppliedPk).toContain("${input.id}");
|
|
608
|
+
expect(callerSuppliedPk).not.toContain('async create(input: Omit<User, "id">)');
|
|
609
|
+
|
|
610
|
+
const dbGeneratedPk = persistentResource("token", "postgres");
|
|
611
|
+
dbGeneratedPk.definition.fields.id = {
|
|
612
|
+
...dbGeneratedPk.definition.fields.id,
|
|
613
|
+
default: "gen_random_uuid()",
|
|
614
|
+
};
|
|
615
|
+
const src = generateRepoSource(dbGeneratedPk)!;
|
|
616
|
+
expect(src).toContain('async create(input: Omit<Token, "id">)');
|
|
617
|
+
expect(src).not.toMatch(/INSERT INTO[\s\S]*"id"[\s\S]*VALUES/);
|
|
618
|
+
});
|
|
602
619
|
|
|
603
620
|
test("shouldEmitRepo returns true only when persistence is declared", () => {
|
|
604
621
|
const persistent = persistentResource("user");
|
|
@@ -419,6 +419,30 @@ describe("emitChange — add-column", () => {
|
|
|
419
419
|
expect(emitChange(change, "sqlite"))
|
|
420
420
|
.toBe('ALTER TABLE "users" ADD COLUMN "age" REAL;');
|
|
421
421
|
});
|
|
422
|
+
|
|
423
|
+
test("indexed:true field emits ADD COLUMN plus auto CREATE INDEX", () => {
|
|
424
|
+
const indexed: Change = {
|
|
425
|
+
kind: "add-column",
|
|
426
|
+
resourceName: "users",
|
|
427
|
+
field: field({ name: "role", type: "string", indexed: true }),
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
expect(emitChange(indexed, "postgres")).toBe(
|
|
431
|
+
[
|
|
432
|
+
'ALTER TABLE "users" ADD COLUMN "role" TEXT NOT NULL;',
|
|
433
|
+
'CREATE INDEX "idx_users_role" ON "users" ("role");',
|
|
434
|
+
].join("\n"),
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("indexed:true unique or primary add-column does not create redundant index", () => {
|
|
439
|
+
const unique: Change = {
|
|
440
|
+
kind: "add-column",
|
|
441
|
+
resourceName: "users",
|
|
442
|
+
field: field({ name: "slug", type: "string", indexed: true, unique: true }),
|
|
443
|
+
};
|
|
444
|
+
expect(emitChange(unique, "postgres")).not.toContain("idx_users_slug");
|
|
445
|
+
});
|
|
422
446
|
});
|
|
423
447
|
|
|
424
448
|
describe("emitChange — drop-column", () => {
|
package/src/resource/ddl/emit.ts
CHANGED
|
@@ -345,7 +345,18 @@ function emitAddColumn(
|
|
|
345
345
|
): string {
|
|
346
346
|
const table = quoteIdent(resourceName, provider);
|
|
347
347
|
const columnDef = emitColumnDef(field, provider);
|
|
348
|
-
|
|
348
|
+
const addColumn = `ALTER TABLE ${table} ADD COLUMN ${columnDef};`;
|
|
349
|
+
if (!field.indexed || field.unique || field.primary) return addColumn;
|
|
350
|
+
return [
|
|
351
|
+
addColumn,
|
|
352
|
+
emitCreateIndex(
|
|
353
|
+
resourceName,
|
|
354
|
+
autoIndexName(resourceName, field.name),
|
|
355
|
+
[field.name],
|
|
356
|
+
false,
|
|
357
|
+
provider,
|
|
358
|
+
),
|
|
359
|
+
].join("\n");
|
|
349
360
|
}
|
|
350
361
|
|
|
351
362
|
/**
|