@mulmoclaude/core 0.27.0 → 0.29.0
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/assets/helps/collection-skills.md +4 -2
- package/assets/helps/wiki.md +47 -2
- package/dist/collection/core/ontologyGraph.d.ts +6 -2
- package/dist/collection/index.cjs +4 -4
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.js +4 -4
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +2 -2
- package/dist/collection/registry/server/index.js +2 -2
- package/dist/collection/server/backendAvailability.d.ts +5 -0
- package/dist/collection/server/csvStore.d.ts +0 -5
- package/dist/collection/server/index.cjs +4 -2
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/manageTool.d.ts +4 -0
- package/dist/collection/server/schemaDocs.d.ts +21 -0
- package/dist/collection/server/store.d.ts +37 -0
- package/dist/collection/server/watchFs.d.ts +17 -0
- package/dist/collection-watchers/index.cjs +114 -203
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.d.ts +1 -1
- package/dist/collection-watchers/index.js +114 -202
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/collection-watchers/watcher.d.ts +8 -13
- package/dist/{discovery-B9Vfojyy.js → discovery-BbsJwVEq.js} +128 -15
- package/dist/discovery-BbsJwVEq.js.map +1 -0
- package/dist/{discovery-BxbJy2VN.cjs → discovery-Bklck7Ck.cjs} +137 -12
- package/dist/discovery-Bklck7Ck.cjs.map +1 -0
- package/dist/feeds/server/index.cjs +2 -2
- package/dist/feeds/server/index.js +2 -2
- package/dist/google/index.cjs +1 -1
- package/dist/google/index.js +1 -1
- package/dist/{promptSafety-0ZKHX-6J.cjs → promptSafety-BFt2g_wn.cjs} +2 -2
- package/dist/promptSafety-BFt2g_wn.cjs.map +1 -0
- package/dist/{promptSafety-RE1SPxBN.js → promptSafety-cZIeiZtB.js} +3 -3
- package/dist/promptSafety-cZIeiZtB.js.map +1 -0
- package/dist/remote-view/index.cjs +8 -0
- package/dist/remote-view/index.cjs.map +1 -1
- package/dist/remote-view/index.d.ts +7 -0
- package/dist/remote-view/index.js +8 -1
- package/dist/remote-view/index.js.map +1 -1
- package/dist/{server-DOGKfUuD.cjs → server-CghbYZFY.cjs} +199 -22
- package/dist/server-CghbYZFY.cjs.map +1 -0
- package/dist/{server-sGNe-msA.js → server-DlG6ydHO.js} +200 -23
- package/dist/server-DlG6ydHO.js.map +1 -0
- package/dist/whisper/client.cjs.map +1 -1
- package/dist/whisper/client.js.map +1 -1
- package/package.json +1 -1
- package/dist/discovery-B9Vfojyy.js.map +0 -1
- package/dist/discovery-BxbJy2VN.cjs.map +0 -1
- package/dist/promptSafety-0ZKHX-6J.cjs.map +0 -1
- package/dist/promptSafety-RE1SPxBN.js.map +0 -1
- package/dist/server-DOGKfUuD.cjs.map +0 -1
- package/dist/server-sGNe-msA.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as errorMessage } from "./errors-eid6Mes3.js";
|
|
2
|
-
import { f as storageKindFor, l as embedTargetId, o as COMPUTED_TYPES, p as fieldText, u as isFieldDrivenEvery } from "./ids-D1M1T6KJ.js";
|
|
3
|
-
import { D as rollupValue, E as projectBacklinkRow, O as actionVisible, S as itemIsDone, T as coerceNumeric, b as resolveIcon, d as parseIsoDateTime, g as deriveAll, t as defangForPrompt, u as parseIsoDate, w as backlinkRows, x as selectDynamicRecord, y as firstDateField } from "./promptSafety-
|
|
4
|
-
import {
|
|
2
|
+
import { f as storageKindFor, l as embedTargetId, m as fieldTextOrNull, o as COMPUTED_TYPES, p as fieldText, u as isFieldDrivenEvery } from "./ids-D1M1T6KJ.js";
|
|
3
|
+
import { D as rollupValue, E as projectBacklinkRow, O as actionVisible, S as itemIsDone, T as coerceNumeric, b as resolveIcon, d as parseIsoDateTime, g as deriveAll, t as defangForPrompt, u as parseIsoDate, w as backlinkRows, x as selectDynamicRecord, y as firstDateField } from "./promptSafety-cZIeiZtB.js";
|
|
4
|
+
import { B as resolveCreateItemId, C as compileJsonlQuery, H as writeFileAtomic, J as resolveTemplatePath, N as isRegularFile, O as isBackendUnavailable, U as SCHEMA_FILE$1, W as isContainedInRoot, X as safeSlugName, Z as archiveDir, at as skillsStagingDir, b as normalizeCsvValue, d as checkpointSqliteDatabase, et as getWorkspaceRoot, h as cacheDir, l as readOnlyRefusal, n as discoverCollections, nt as log, o as CollectionSchemaZ, q as resolveDataDir, r as loadCollection, s as resolveMutateSet, tt as isPresetSlug$1, u as storeFor, w as CollectionQueryZ, x as queryCsv } from "./discovery-BbsJwVEq.js";
|
|
5
5
|
import { dataSkillDir, mirrorSkillWrite } from "./skill-bridge/index.js";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -148,7 +148,7 @@ function toRefRecords(linked) {
|
|
|
148
148
|
function projectBacklinks(field, schema, enriched, linked) {
|
|
149
149
|
const source = linked[field.from];
|
|
150
150
|
if (!source) return [];
|
|
151
|
-
return backlinkRows(field,
|
|
151
|
+
return backlinkRows(field, fieldText(enriched[schema.primaryKey]), Object.values(source.byId)).map((row) => projectBacklinkRow(row, field.display, source.schema.primaryKey));
|
|
152
152
|
}
|
|
153
153
|
/** Project the computed (never-stored) field kinds onto one derived
|
|
154
154
|
* record: `toggle` → boolean off its enum, `embed` → the target record
|
|
@@ -178,7 +178,7 @@ function projectRollups(schema, record, linked) {
|
|
|
178
178
|
if (field.type !== "rollup") continue;
|
|
179
179
|
if (out === record) out = { ...record };
|
|
180
180
|
const source = linked[field.from];
|
|
181
|
-
const selfId =
|
|
181
|
+
const selfId = fieldText(record[schema.primaryKey]);
|
|
182
182
|
out[key] = source ? rollupValue(field, selfId, Object.values(source.byId)) : null;
|
|
183
183
|
}
|
|
184
184
|
return out;
|
|
@@ -349,7 +349,7 @@ async function validateStoreRecords(collection, opts) {
|
|
|
349
349
|
const issues = [];
|
|
350
350
|
for (const item of items) {
|
|
351
351
|
if (issues.length >= MAX_ISSUES) break;
|
|
352
|
-
const itemId =
|
|
352
|
+
const itemId = fieldText(item[collection.schema.primaryKey]);
|
|
353
353
|
const problem = validateRecordObject(item, itemId, collection.schema, "strict");
|
|
354
354
|
if (problem) issues.push({
|
|
355
355
|
file: itemId,
|
|
@@ -396,6 +396,15 @@ async function inspectRecord(fullPath, name, schema) {
|
|
|
396
396
|
problem
|
|
397
397
|
} : null;
|
|
398
398
|
}
|
|
399
|
+
/** What a non-string primary key actually is, for the error message. Names the
|
|
400
|
+
* shape rather than stringifying the value — "[object Object]" tells the reader
|
|
401
|
+
* nothing about what is wrong. */
|
|
402
|
+
function describeIdType(value) {
|
|
403
|
+
if (value === null) return "null";
|
|
404
|
+
if (value === void 0) return "missing";
|
|
405
|
+
if (Array.isArray(value)) return "an array";
|
|
406
|
+
return `a ${typeof value}`;
|
|
407
|
+
}
|
|
399
408
|
/** First schema problem on an in-memory record (primaryKey↔id mismatch,
|
|
400
409
|
* then the compiled per-field checks — see `../core/recordZ` for the two
|
|
401
410
|
* tiers), or null when it's fine. One issue per record keeps the report
|
|
@@ -407,7 +416,8 @@ async function inspectRecord(fullPath, name, schema) {
|
|
|
407
416
|
* report-only surfaces. */
|
|
408
417
|
function validateRecordObject(record, itemId, schema, tier = "enforced") {
|
|
409
418
|
const idValue = record[schema.primaryKey];
|
|
410
|
-
if (typeof idValue !== "string"
|
|
419
|
+
if (typeof idValue !== "string") return `'${schema.primaryKey}' must be a string, but is ${describeIdType(idValue)} — must equal the filename ('${itemId}'), or the record can't be opened`;
|
|
420
|
+
if (idValue !== itemId) return `'${schema.primaryKey}' is '${idValue}' but must equal the filename ('${itemId}'), or the record can't be opened`;
|
|
411
421
|
return firstRecordProblem(record, schema, tier);
|
|
412
422
|
}
|
|
413
423
|
//#endregion
|
|
@@ -533,7 +543,8 @@ async function countRecordFiles(dataDir, workspaceRoot) {
|
|
|
533
543
|
async function countRecords(collection, workspaceRoot) {
|
|
534
544
|
if (storageKindFor(collection.schema) !== "file") try {
|
|
535
545
|
return (await storeFor(collection, { workspaceRoot }).page({ limit: 0 })).total;
|
|
536
|
-
} catch {
|
|
546
|
+
} catch (err) {
|
|
547
|
+
if (isBackendUnavailable(err)) return null;
|
|
537
548
|
return 0;
|
|
538
549
|
}
|
|
539
550
|
return countRecordFiles(collection.dataDir, workspaceRoot);
|
|
@@ -575,7 +586,7 @@ function buildRecordsById(items, primaryKey) {
|
|
|
575
586
|
* ties pick a different record — and thus a different icon — between
|
|
576
587
|
* reconciles. A stable id sort pins one answer. */
|
|
577
588
|
function sortByPrimaryKey(items, primaryKey) {
|
|
578
|
-
return [...items].sort((left, right) =>
|
|
589
|
+
return [...items].sort((left, right) => fieldText(left[primaryKey]).localeCompare(fieldText(right[primaryKey])));
|
|
579
590
|
}
|
|
580
591
|
/** Compute the effective launcher icon for `collection`: its static
|
|
581
592
|
* `schema.icon` when it declares no `dynamicIcon`, else the icon
|
|
@@ -711,7 +722,8 @@ function successorId(sourceId, next) {
|
|
|
711
722
|
function matchesWhen(when, schema, item) {
|
|
712
723
|
if (when) {
|
|
713
724
|
const raw = item[when.field];
|
|
714
|
-
|
|
725
|
+
const text = fieldTextOrNull(raw);
|
|
726
|
+
return text !== null && when.in.includes(text);
|
|
715
727
|
}
|
|
716
728
|
return itemIsDone(schema, item);
|
|
717
729
|
}
|
|
@@ -725,7 +737,8 @@ function resolveEvery(every, sourceItem) {
|
|
|
725
737
|
if (!isFieldDrivenEvery(every)) return every;
|
|
726
738
|
const raw = sourceItem[every.fromField];
|
|
727
739
|
if (raw === void 0 || raw === null || raw === "") return null;
|
|
728
|
-
|
|
740
|
+
const key = fieldTextOrNull(raw);
|
|
741
|
+
return key === null ? null : every.map[key] ?? null;
|
|
729
742
|
}
|
|
730
743
|
/** Build the successor record purely from (schema, source record, source
|
|
731
744
|
* id). Returns null when the schema has no spawn/triggerField or the
|
|
@@ -1112,6 +1125,151 @@ async function deleteCustomView(collection, viewId, opts = {}) {
|
|
|
1112
1125
|
viewId
|
|
1113
1126
|
};
|
|
1114
1127
|
}
|
|
1128
|
+
/** Ceiling for ANY assembled reply — topic or default — safely inside
|
|
1129
|
+
* the agent's per-result limit (the full doc is what overflowed it).
|
|
1130
|
+
* A matched section too large to fit degrades to its own prose plus a
|
|
1131
|
+
* list of its subsections to fetch individually; an oversized leaf is
|
|
1132
|
+
* clipped outright; the default drops whole core sections into a
|
|
1133
|
+
* pointer note. Sized so the CURRENT bundled doc's default reply
|
|
1134
|
+
* (~33KB) fits without degrading. */
|
|
1135
|
+
var SCHEMA_DOCS_RESULT_BUDGET = 36e3;
|
|
1136
|
+
/** Hard truncation for a body no sectioning trick can shrink further.
|
|
1137
|
+
* The marker tells the agent it did NOT see everything and how to get
|
|
1138
|
+
* the rest, so a silent cut can't read as complete coverage. The limit
|
|
1139
|
+
* is clamped at zero: a negative limit would make `slice(0, limit)`
|
|
1140
|
+
* count from the END and return almost the whole body — the exact
|
|
1141
|
+
* overflow this function exists to prevent. */
|
|
1142
|
+
function clip(text, limit) {
|
|
1143
|
+
const max = Math.max(0, limit);
|
|
1144
|
+
if (text.length <= max) return text;
|
|
1145
|
+
return `${text.slice(0, max)}\n\n[…clipped ${text.length - max} chars — fetch a narrower \`topic\`, or read the doc file directly]`;
|
|
1146
|
+
}
|
|
1147
|
+
/** Slack a budget-spending assembler must reserve for what it adds
|
|
1148
|
+
* AROUND the budgeted pieces — clip markers, join separators, pointer
|
|
1149
|
+
* notes — so the final hard cap doesn't eat the reply's own tail (in
|
|
1150
|
+
* renderDefault's case, the TOC). */
|
|
1151
|
+
var ASSEMBLY_RESERVE = 500;
|
|
1152
|
+
/** The sections every schema author needs, matched against headings so
|
|
1153
|
+
* the doc can be reorganised without touching this list (an unmatched
|
|
1154
|
+
* pattern is simply skipped): what a collection IS, the schema DSL and
|
|
1155
|
+
* its field types, and the create/edit walkthroughs. Advanced sections
|
|
1156
|
+
* (actions, bells, views, dataSource, storage) stay TOC-only. */
|
|
1157
|
+
var CORE_SECTION_PATTERNS = [
|
|
1158
|
+
"anatomy",
|
|
1159
|
+
"skill.md",
|
|
1160
|
+
"the dsl",
|
|
1161
|
+
"field types",
|
|
1162
|
+
"end-to-end",
|
|
1163
|
+
"editing an existing"
|
|
1164
|
+
];
|
|
1165
|
+
/** Backticks stripped + lowercased, so `topic: "dataSource"` matches the
|
|
1166
|
+
* heading "External data (CSV) collections — `dataSource`". */
|
|
1167
|
+
var normalize = (text) => text.toLowerCase().replace(/`/g, "");
|
|
1168
|
+
var sliceLines = (lines, from, until) => lines.slice(from, until).join("\n").trim();
|
|
1169
|
+
/** All `#`–`###` headings, skipping fenced code blocks (a `# comment`
|
|
1170
|
+
* inside an example must not become a section boundary). */
|
|
1171
|
+
function headingLines(lines) {
|
|
1172
|
+
const found = [];
|
|
1173
|
+
let fenced = false;
|
|
1174
|
+
lines.forEach((line, index) => {
|
|
1175
|
+
if (line.trimStart().startsWith("```")) fenced = !fenced;
|
|
1176
|
+
const match = fenced ? null : /^(#{1,3}) (.+)$/.exec(line);
|
|
1177
|
+
if (match) found.push({
|
|
1178
|
+
index,
|
|
1179
|
+
level: match[1].length,
|
|
1180
|
+
heading: match[2].trim()
|
|
1181
|
+
});
|
|
1182
|
+
});
|
|
1183
|
+
return found;
|
|
1184
|
+
}
|
|
1185
|
+
function parseSections(lines) {
|
|
1186
|
+
const heads = headingLines(lines);
|
|
1187
|
+
return heads.map((head, i) => {
|
|
1188
|
+
const closer = heads.slice(i + 1).find((other) => other.level <= head.level);
|
|
1189
|
+
return {
|
|
1190
|
+
level: head.level,
|
|
1191
|
+
heading: head.heading,
|
|
1192
|
+
start: head.index,
|
|
1193
|
+
ownEnd: heads[i + 1]?.index ?? lines.length,
|
|
1194
|
+
deepEnd: closer?.index ?? lines.length
|
|
1195
|
+
};
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
/** Bounded to half the reply budget: every render path appends the TOC,
|
|
1199
|
+
* so a doc with thousands of headings must not blow the ceiling through
|
|
1200
|
+
* the TOC itself — and renderDefault subtracts the TOC's length from
|
|
1201
|
+
* its budget, which must stay positive. */
|
|
1202
|
+
function tableOfContents(sections) {
|
|
1203
|
+
return clip(`Sections (call schemaDocs with \`topic: "<heading>"\` for any of them; \`topic: "all"\` for the full document):\n${sections.map((section) => `${" ".repeat(section.level - 1)}- ${section.heading}`).join("\n")}`, Math.floor(SCHEMA_DOCS_RESULT_BUDGET / 2));
|
|
1204
|
+
}
|
|
1205
|
+
/** The no-topic reply: the doc's intro + the core authoring sections
|
|
1206
|
+
* (own prose only — a core parent's advanced subsections stay TOC-only),
|
|
1207
|
+
* closed by the full table of contents. Budgeted: a core section that
|
|
1208
|
+
* no longer fits is dropped into a pointer note (the first is clipped
|
|
1209
|
+
* instead, so the reply is never bodiless) — an oversized workspace
|
|
1210
|
+
* copy must not recreate the overflow this module exists to prevent. */
|
|
1211
|
+
function renderDefault(lines, sections) {
|
|
1212
|
+
const isCore = (section, index) => index === 0 || CORE_SECTION_PATTERNS.some((pattern) => normalize(section.heading).includes(pattern));
|
|
1213
|
+
const toc = tableOfContents(sections);
|
|
1214
|
+
const parts = [];
|
|
1215
|
+
const skipped = [];
|
|
1216
|
+
let remaining = SCHEMA_DOCS_RESULT_BUDGET - toc.length - ASSEMBLY_RESERVE;
|
|
1217
|
+
for (const section of sections.filter(isCore)) {
|
|
1218
|
+
const body = sliceLines(lines, section.start, section.ownEnd);
|
|
1219
|
+
if (body.length > remaining && parts.length > 0) {
|
|
1220
|
+
skipped.push(section.heading);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const fitted = clip(body, remaining);
|
|
1224
|
+
parts.push(fitted);
|
|
1225
|
+
remaining -= fitted.length;
|
|
1226
|
+
}
|
|
1227
|
+
const note = skipped.length > 0 ? `\n\n[Omitted for size — fetch each with \`topic\`: ${skipped.join(" · ")}]` : "";
|
|
1228
|
+
return `${parts.join("\n\n")}${note}\n\n---\n\n${toc}`;
|
|
1229
|
+
}
|
|
1230
|
+
/** Case-insensitive substring match on headings, minus any match already
|
|
1231
|
+
* contained in another match's subtree (its parent's deep body covers it). */
|
|
1232
|
+
function matchSections(sections, topic) {
|
|
1233
|
+
const needle = normalize(topic).trim();
|
|
1234
|
+
const matched = sections.filter((section) => normalize(section.heading).includes(needle));
|
|
1235
|
+
return matched.filter((section) => !matched.some((other) => other !== section && other.start < section.start && section.deepEnd <= other.deepEnd));
|
|
1236
|
+
}
|
|
1237
|
+
/** One matched section: its whole subtree when that fits the budget,
|
|
1238
|
+
* otherwise its own prose (budget-clipped) + a pointer list of
|
|
1239
|
+
* subsections to fetch — the list clipped to what the prose left over
|
|
1240
|
+
* (thousands of child headings must not overflow through the list
|
|
1241
|
+
* itself) — and a leaf with no subsections to offer is simply clipped. */
|
|
1242
|
+
function renderSection(lines, sections, section, budget) {
|
|
1243
|
+
const deep = sliceLines(lines, section.start, section.deepEnd);
|
|
1244
|
+
if (deep.length <= budget) return deep;
|
|
1245
|
+
const children = sections.filter((child) => child.start > section.start && child.start < section.deepEnd && child.level === section.level + 1);
|
|
1246
|
+
const own = clip(sliceLines(lines, section.start, section.ownEnd), budget);
|
|
1247
|
+
if (children.length === 0) return own;
|
|
1248
|
+
return `${own}\n\nSubsections (too large to include together — fetch each with \`topic\`):\n${clip(children.map((child) => `- ${child.heading}`).join("\n"), budget - own.length)}`;
|
|
1249
|
+
}
|
|
1250
|
+
function renderTopic(lines, sections, topic) {
|
|
1251
|
+
const matched = matchSections(sections, topic);
|
|
1252
|
+
if (matched.length === 0) return `manageCollection: no schemaDocs section matches '${defangForPrompt(topic)}'.\n\n${tableOfContents(sections)}`;
|
|
1253
|
+
const perMatch = Math.floor(SCHEMA_DOCS_RESULT_BUDGET / matched.length);
|
|
1254
|
+
return matched.map((section) => renderSection(lines, sections, section, perMatch)).join("\n\n");
|
|
1255
|
+
}
|
|
1256
|
+
/** Render the authoring reference for one schemaDocs call. Small docs and
|
|
1257
|
+
* docs without headings pass through verbatim — there is nothing better
|
|
1258
|
+
* to key a section off. The assembled reply gets one final hard cap: the
|
|
1259
|
+
* inner budgeting degrades gracefully, but per-piece overheads (clip
|
|
1260
|
+
* markers on thousands of matches, join separators, pointer notes) add
|
|
1261
|
+
* up outside any single piece's budget, and the ceiling must hold no
|
|
1262
|
+
* matter what shape of document arrives. Only the explicit \`"all"\`
|
|
1263
|
+
* opt-in and the small-doc verbatim path may exceed it. */
|
|
1264
|
+
function renderSchemaDocs(doc, topic) {
|
|
1265
|
+
const requested = topic?.trim() ?? "";
|
|
1266
|
+
if (normalize(requested) === "all") return doc;
|
|
1267
|
+
if (doc.length <= 2e4) return doc;
|
|
1268
|
+
const lines = doc.split("\n");
|
|
1269
|
+
const sections = parseSections(lines);
|
|
1270
|
+
if (sections.length === 0) return doc;
|
|
1271
|
+
return clip(requested ? renderTopic(lines, sections, requested) : renderDefault(lines, sections), SCHEMA_DOCS_RESULT_BUDGET);
|
|
1272
|
+
}
|
|
1115
1273
|
/** True for a launcher-managed preset slug (`mc-<something>`). The sync logic uses
|
|
1116
1274
|
* this to bound which active skills it may refresh/prune, so user-authored skills are
|
|
1117
1275
|
* never touched. */
|
|
@@ -1183,7 +1341,10 @@ async function loadRequestedItems(collection, ids, deps) {
|
|
|
1183
1341
|
const items = [];
|
|
1184
1342
|
const missing = [];
|
|
1185
1343
|
for (const recordId of ids) {
|
|
1186
|
-
const item = await store.read(recordId).catch(() =>
|
|
1344
|
+
const item = await store.read(recordId).catch((err) => {
|
|
1345
|
+
if (isBackendUnavailable(err)) throw err;
|
|
1346
|
+
return null;
|
|
1347
|
+
});
|
|
1187
1348
|
if (item) items.push(item);
|
|
1188
1349
|
else missing.push(recordId);
|
|
1189
1350
|
}
|
|
@@ -1244,7 +1405,8 @@ async function mergeWithExisting(collection, store, record, itemId) {
|
|
|
1244
1405
|
let existing;
|
|
1245
1406
|
try {
|
|
1246
1407
|
existing = await store.read(itemId);
|
|
1247
|
-
} catch {
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
if (isBackendUnavailable(err)) throw err;
|
|
1248
1410
|
return `'${itemId}' has a malformed stored file — mode "merge" needs to read it; fix the file (Read → correct → Write) or replace it whole with "upsert"`;
|
|
1249
1411
|
}
|
|
1250
1412
|
if (!existing) return `'${itemId}' not found — mode "merge" updates an existing record; use "upsert" or "create" to add it`;
|
|
@@ -1376,14 +1538,17 @@ async function handleGetOntology(deps) {
|
|
|
1376
1538
|
collections
|
|
1377
1539
|
});
|
|
1378
1540
|
}
|
|
1379
|
-
/** Return the collection-authoring reference (`collection-skills.md`)
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
|
|
1541
|
+
/** Return the collection-authoring reference (`collection-skills.md`),
|
|
1542
|
+
* rendered by `renderSchemaDocs` — the full doc overflows the agent's
|
|
1543
|
+
* per-result limit, so the default reply is the core guide + a table of
|
|
1544
|
+
* contents, and `topic` fetches individual sections. Workspace copy
|
|
1545
|
+
* first (reflects user edits), bundled asset as the always-present
|
|
1546
|
+
* fallback. Both reads guarded; if neither resolves the agent still
|
|
1547
|
+
* gets an actionable message instead of a thrown call. */
|
|
1548
|
+
async function handleSchemaDocs(deps, topic) {
|
|
1384
1549
|
const candidates = [path.join(resolveBase(deps), HELPS_DIR, SCHEMA_DOCS_FILE), ...deps.bundledHelpsDir ? [path.join(deps.bundledHelpsDir(), SCHEMA_DOCS_FILE)] : []];
|
|
1385
1550
|
for (const candidate of candidates) try {
|
|
1386
|
-
return await readFile(candidate, "utf-8");
|
|
1551
|
+
return renderSchemaDocs(await readFile(candidate, "utf-8"), topic);
|
|
1387
1552
|
} catch {}
|
|
1388
1553
|
return `manageCollection: could not read the collection-authoring reference (${SCHEMA_DOCS_FILE}).`;
|
|
1389
1554
|
}
|
|
@@ -1462,7 +1627,7 @@ async function handlePutSchema(slug, schemaArg, deps) {
|
|
|
1462
1627
|
written: true
|
|
1463
1628
|
});
|
|
1464
1629
|
}
|
|
1465
|
-
var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference,
|
|
1630
|
+
var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference — the default reply is the core authoring guide plus a table of contents; fetch advanced sections (actions, bells, calendar/kanban views, dataSource, storage) by passing their heading as `topic` rather than dumping `topic: \"all\"`. Then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` validates every row against the schema before writing (required fields, enum values, primaryKey = record id) and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits. `deleteItems` removes records by id and returns `{ deleted, rejected }`; an id that doesn't exist comes back rejected rather than counted as deleted, so check `rejected` before reporting a deletion as done. Answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` on ANY collection — on a dataSource (CSV) collection it scans the whole file (getItems is row-capped, so aggregates computed from its output can be silently wrong on large files); on a file-backed collection it aggregates the enriched records, so computed fields (derived/rollup/toggle) are queryable columns.";
|
|
1466
1631
|
/** Validate getItems' optional `ids`/`fields` args, then delegate. */
|
|
1467
1632
|
async function dispatchGetItems(collection, args, deps) {
|
|
1468
1633
|
const ids = optionalStringArray(args.ids, "ids");
|
|
@@ -1496,8 +1661,16 @@ async function dispatchRecordAction(action, collection, args, deps) {
|
|
|
1496
1661
|
return typeof parsed === "string" ? parsed : handlePutItems(collection, parsed, deps);
|
|
1497
1662
|
}
|
|
1498
1663
|
async function manageCollectionHandler(deps, args) {
|
|
1664
|
+
try {
|
|
1665
|
+
return await dispatchManageCollection(deps, args);
|
|
1666
|
+
} catch (err) {
|
|
1667
|
+
if (isBackendUnavailable(err)) return `manageCollection: ${err.message}`;
|
|
1668
|
+
throw err;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
async function dispatchManageCollection(deps, args) {
|
|
1499
1672
|
const action = typeof args.action === "string" ? args.action : "";
|
|
1500
|
-
if (action === "schemaDocs") return handleSchemaDocs(deps);
|
|
1673
|
+
if (action === "schemaDocs") return handleSchemaDocs(deps, typeof args.topic === "string" ? args.topic : void 0);
|
|
1501
1674
|
if (action === "getOntology") return handleGetOntology(deps);
|
|
1502
1675
|
const slug = typeof args.slug === "string" ? args.slug.trim() : "";
|
|
1503
1676
|
if (!slug) return "manageCollection: `slug` is required (the collection's slug).";
|
|
@@ -1510,7 +1683,7 @@ async function manageCollectionHandler(deps, args) {
|
|
|
1510
1683
|
}
|
|
1511
1684
|
var MANAGE_COLLECTION_DEFINITION = {
|
|
1512
1685
|
name: "manageCollection",
|
|
1513
|
-
description: "Read and write a schema-driven collection through the host — both its records and its structure. getItems returns records WITH computed values (derived formulas, toggles, embeds) the stored JSON files don't contain; putItems validates each row against the schema before writing; deleteItems removes records by id. getOntology maps the whole workspace: every collection with its record count and outbound ref/embed relations — call it first for cross-collection questions. schemaDocs returns the collection-authoring reference; getSchema/putSchema read and validate-then-write the collection's schema.json. Prefer it over raw file I/O on collections.",
|
|
1686
|
+
description: "Read and write a schema-driven collection through the host — both its records and its structure. getItems returns records WITH computed values (derived formulas, toggles, embeds) the stored JSON files don't contain; putItems validates each row against the schema before writing; deleteItems removes records by id. getOntology maps the whole workspace: every collection with its record count and outbound ref/embed relations — call it first for cross-collection questions. schemaDocs returns the collection-authoring reference — the core guide plus a table of contents by default; pass `topic` for a specific section. getSchema/putSchema read and validate-then-write the collection's schema.json. Prefer it over raw file I/O on collections.",
|
|
1514
1687
|
inputSchema: {
|
|
1515
1688
|
type: "object",
|
|
1516
1689
|
properties: {
|
|
@@ -1560,6 +1733,10 @@ var MANAGE_COLLECTION_DEFINITION = {
|
|
|
1560
1733
|
type: "object",
|
|
1561
1734
|
description: "putSchema: the full collection schema object (same shape as schema.json — title, icon, dataPath, primaryKey, fields, …). Call getSchema first for the current one, and schemaDocs for the field DSL."
|
|
1562
1735
|
},
|
|
1736
|
+
topic: {
|
|
1737
|
+
type: "string",
|
|
1738
|
+
description: "schemaDocs: fetch one section of the reference by heading (case-insensitive substring — e.g. \"field types\", \"kanban\", \"calendar\", \"dataSource\"). Omit for the core authoring guide plus a table of contents of every section; \"all\" returns the full document (large — it can exceed your tool-result limit)."
|
|
1739
|
+
},
|
|
1563
1740
|
query: {
|
|
1564
1741
|
type: "object",
|
|
1565
1742
|
description: "queryItems: a structured aggregation query — `{ groupBy?: [\"col\"], aggregates?: { alias: { op: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\", column? } }, where?: [{ field, op, value }], orderBy?: [{ field, dir? }], limit? }`. At least one of groupBy/aggregates. On a dataSource (CSV) collection it scans the WHOLE file uncapped (unlike getItems); on a file-backed collection it aggregates the ENRICHED records, so computed fields (derived/rollup/toggle) are queryable columns. Use it for counts / sums / averages / group-bys instead of arithmetic over getItems output."
|
|
@@ -1579,4 +1756,4 @@ function makeManageCollectionTool(deps = {}) {
|
|
|
1579
1756
|
//#endregion
|
|
1580
1757
|
export { validateRecordObject as C, enrichItems as D, runCollectionQuery as E, runQueryOverRows as O, validateCollectionRecords as S, recordFieldProblem as T, computeCollectionIcon as _, deleteCollection as a, applyMutateAction as b, computeSuccessor as c, isTriggerDue as d, maybeSpawnSuccessor as f, ONE_SECOND_MS as g, successorId as h, deleteCustomView as i, daysInMonth as l, resolveEvery as m, MAX_UNSELECTIVE_ITEMS as n, deleteCollectionRefusalMessage as o, parseCivil as p, makeManageCollectionTool as r, advanceTriggerDate as s, MAX_SCHEMA_ISSUES as t, formatCivil as u, buildWorkspaceOntology as v, compileRecordZ as w, firstMutateParamProblem as x, schemaRelations as y };
|
|
1581
1758
|
|
|
1582
|
-
//# sourceMappingURL=server-
|
|
1759
|
+
//# sourceMappingURL=server-DlG6ydHO.js.map
|