@olares/lares-core 0.15.6 → 0.15.9

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.
Files changed (99) hide show
  1. package/README.md +2 -1
  2. package/brand/identity.js +33 -3
  3. package/drive/cat.js +62 -0
  4. package/drive/download.js +3 -3
  5. package/drive/execute.js +8 -2
  6. package/drive/files-path.js +33 -0
  7. package/drive/flowstudio-files.js +87 -0
  8. package/drive/ls.js +3 -3
  9. package/drive/paths.js +23 -13
  10. package/drive/tools.js +4 -2
  11. package/entrance/surface.js +96 -0
  12. package/files/app-link.js +64 -0
  13. package/files/backend-client.js +266 -0
  14. package/files/deliverables.js +55 -1
  15. package/files/intake.js +0 -63
  16. package/files/mention.js +1 -51
  17. package/files/preview-groups.js +71 -3
  18. package/files/preview-local.js +69 -0
  19. package/files/preview-workspace.js +114 -13
  20. package/files/preview.js +171 -16
  21. package/files/produced-gate.js +197 -0
  22. package/files/produced-mentions.js +19 -0
  23. package/files/published-tools.js +69 -0
  24. package/files/published-turn-data.js +84 -0
  25. package/i18n/chat-model.js +5 -5
  26. package/i18n/mcp.js +83 -0
  27. package/i18n/mobile.js +100 -2
  28. package/i18n/preview.js +10 -0
  29. package/i18n/search.js +13 -7
  30. package/i18n/skills.js +31 -0
  31. package/i18n/voice.js +88 -19
  32. package/larepass/chat.js +9 -0
  33. package/larepass/host.js +7 -2
  34. package/larepass/mux.js +210 -2
  35. package/larepass/process-fold.js +239 -0
  36. package/larepass/queue.js +44 -0
  37. package/larepass/rpc.js +50 -3
  38. package/larepass/runtime.js +215 -83
  39. package/larepass/session-cache.js +6 -0
  40. package/larepass/settings.js +15 -7
  41. package/larepass/transcript.js +49 -11
  42. package/mcp/approval.js +152 -0
  43. package/mcp/client.js +38 -0
  44. package/mcp/config.js +139 -0
  45. package/media/catalog-row.js +244 -0
  46. package/media/generation.js +707 -0
  47. package/media/tool.js +92 -0
  48. package/olares/entrance.js +67 -15
  49. package/olares/identity.js +13 -136
  50. package/olares/session-identity.js +12 -12
  51. package/olares/trusted-host.js +0 -29
  52. package/package.json +48 -4
  53. package/router/catalog-cache.d.ts +1 -1
  54. package/router/catalog-cache.js +25 -14
  55. package/router/catalog-sync.js +8 -3
  56. package/router/catalog.d.ts +6 -1
  57. package/router/catalog.js +18 -2
  58. package/router/gateway.d.ts +9 -1
  59. package/router/gateway.js +40 -4
  60. package/router/gemini-schema.js +125 -0
  61. package/router/generation-guard.js +82 -0
  62. package/router/models.d.ts +6 -0
  63. package/router/models.js +22 -3
  64. package/router/search.js +10 -2
  65. package/router/session-model.js +53 -0
  66. package/router/shim.js +104 -3
  67. package/router/sref.js +126 -0
  68. package/router/stt.js +43 -12
  69. package/router/tts.js +118 -0
  70. package/search/client.js +5 -3
  71. package/search/config.js +53 -39
  72. package/search/menu.js +45 -10
  73. package/skills/client.js +42 -0
  74. package/skills/packs.js +40 -0
  75. package/skills/state.d.ts +22 -0
  76. package/skills/state.js +142 -0
  77. package/tools/crypto-uuid.js +3 -6
  78. package/tools/head-script.js +15 -0
  79. package/voice/agent.js +50 -0
  80. package/voice/capture.js +250 -0
  81. package/voice/client.js +126 -24
  82. package/voice/config.js +11 -1
  83. package/voice/duplex.js +44 -0
  84. package/voice/endpoint.js +57 -0
  85. package/voice/host/bluetooth-ready.py +1196 -0
  86. package/voice/host/install-host.sh +102 -0
  87. package/voice/input.js +4 -0
  88. package/voice/install.js +146 -0
  89. package/voice/languages.js +11 -1
  90. package/voice/link.js +86 -0
  91. package/voice/pcm.js +100 -0
  92. package/voice/playback.js +7 -0
  93. package/voice/problem.js +46 -0
  94. package/voice/recorder.js +79 -4
  95. package/voice/script.js +35 -0
  96. package/voice/service.js +253 -10
  97. package/voice/session.js +128 -0
  98. package/voice/speech.js +93 -0
  99. package/files/preview-cache.js +0 -100
@@ -0,0 +1,244 @@
1
+ import { routerGatewayUrl, routerHeaders } from "../router/gateway.js";
2
+
3
+ /**
4
+ * The Router catalog row decides a generation's contract: which route it takes,
5
+ * whether it edits, which media it needs, and which parameters it accepts.
6
+ * FlowStudio publishes that per workflow (param schema, reference / mask needs,
7
+ * operations) and Router carries it on `/models?detail=capabilities`, so the
8
+ * tool reads it at call time instead of hard-coding any of it.
9
+ */
10
+
11
+ /** Row mode → Router route for the family's default generation. */
12
+ export const MEDIA_GENERATION_ROUTES = Object.freeze({
13
+ image_generation: "images/generations",
14
+ video_generation: "videos",
15
+ music_generation: "music/generations",
16
+ model3d_generation: "generations",
17
+ });
18
+
19
+ const IMAGE_FIELDS = new Set(["inputs.images", "reference_images", "input_images", "inputImages"]);
20
+ const MASK_FIELDS = new Set(["inputs.mask", "mask_image", "maskImage"]);
21
+
22
+ export class MediaContractError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "MediaContractError";
26
+ }
27
+ }
28
+
29
+ function list(value) {
30
+ return Array.isArray(value) ? value : [];
31
+ }
32
+
33
+ function fieldName(field) {
34
+ if (typeof field === "string") return field.trim();
35
+ if (field && typeof field === "object") return String(field.name ?? field.field ?? field.key ?? "").trim();
36
+ return "";
37
+ }
38
+
39
+ function flag(...values) {
40
+ for (const value of values) if (typeof value === "boolean") return value;
41
+ return undefined;
42
+ }
43
+
44
+ function option(entry) {
45
+ if (entry && typeof entry === "object") {
46
+ const value = entry.value ?? entry.id ?? entry.label;
47
+ return { value, label: String(entry.label ?? entry.name ?? value ?? "") };
48
+ }
49
+ return { value: entry, label: String(entry ?? "") };
50
+ }
51
+
52
+ /** One declared parameter, whatever spelling Router used for its attributes. */
53
+ function parameter(entry) {
54
+ if (!entry || typeof entry !== "object") return null;
55
+ const key = String(entry.key ?? entry.name ?? "").trim();
56
+ if (!key) return null;
57
+ return {
58
+ key,
59
+ label: String(entry.label ?? key),
60
+ type: String(entry.type ?? "").trim().toLowerCase(),
61
+ required: entry.required === true,
62
+ default: entry.default,
63
+ min: typeof entry.min === "number" ? entry.min : undefined,
64
+ max: typeof entry.max === "number" ? entry.max : undefined,
65
+ options: list(entry.options).map(option),
66
+ role: String(entry.associateRole ?? "").trim(),
67
+ // A media slot FlowStudio exported: what it takes and how its value is written.
68
+ media: String(entry.media ?? "").trim(),
69
+ valueFormat: String(entry.valueFormat ?? "").trim(),
70
+ };
71
+ }
72
+
73
+ /**
74
+ * The contract of one catalog row.
75
+ * @returns {{
76
+ * id: string, name: string, mode: string, enabled: boolean,
77
+ * operations: string[], parameters: ReturnType<typeof parameter>[],
78
+ * images: "required" | "accepted" | "none" | "unknown",
79
+ * mask: "required" | "accepted" | "none" | "unknown",
80
+ * }}
81
+ */
82
+ export function rowContract(row) {
83
+ const flowstudio = row?.flowstudio && typeof row.flowstudio === "object" ? row.flowstudio : null;
84
+ const canonical = new Set(list(row?.canonical_fields ?? row?.canonicalFields).map(fieldName).filter(Boolean));
85
+ const operations = list(row?.creative?.operations ?? row?.operations)
86
+ .map((op) => String(op ?? "").trim().toLowerCase())
87
+ .filter(Boolean);
88
+ const needsReference = flag(flowstudio?.needs_reference, flowstudio?.needsReference, row?.needs_reference);
89
+ const needsMask = flag(flowstudio?.needs_mask, flowstudio?.needsMask, row?.needs_mask);
90
+ const takesImages = [...canonical].some((name) => IMAGE_FIELDS.has(name)) || operations.includes("edit");
91
+ const takesMask = [...canonical].some((name) => MASK_FIELDS.has(name));
92
+ // A row FlowStudio described says outright what it needs; for any other row
93
+ // silence is not a refusal, so the tool leaves the decision to Router.
94
+ const described = needsReference !== undefined || flowstudio !== null || canonical.size > 0;
95
+ // Router states it per row: a row that only edits (image edit, I2V, upscale)
96
+ // cannot run without its source, whatever family it belongs to.
97
+ const editOnly = operations.length > 0 && operations.includes("edit") && !operations.includes("generate");
98
+ const need = (required, accepted) => {
99
+ if (required === true) return "required";
100
+ if (accepted) return "accepted";
101
+ return described ? "none" : "unknown";
102
+ };
103
+ return {
104
+ id: String(row?.id ?? "").trim(),
105
+ name: String(row?.name ?? row?.id ?? "").trim(),
106
+ mode: String(row?.mode ?? "").trim().toLowerCase(),
107
+ enabled: row?.enabled !== false && row?.disabled !== true,
108
+ operations,
109
+ parameters: list(flowstudio?.parameters).map(parameter).filter(Boolean),
110
+ images: need(needsReference ?? (editOnly && takesImages ? true : undefined), takesImages),
111
+ mask: need(needsMask, takesMask),
112
+ };
113
+ }
114
+
115
+ /** The media slots a FlowStudio row exports among its parameters. */
116
+ export function mediaSlots(contract) {
117
+ return contract.parameters.filter((p) => p.media);
118
+ }
119
+
120
+ /** The key FlowStudio marked as this workflow's prompt (`associateRole: "prompt"`), if any. */
121
+ export function promptParameterKey(contract) {
122
+ return contract.parameters.find((p) => p.role === "prompt")?.key ?? "";
123
+ }
124
+
125
+ /** Compact "key (label, type, range / options)" list for a model-facing error. */
126
+ export function describeParameters(parameters) {
127
+ if (parameters.length === 0) return "this row declares no parameters";
128
+ return parameters.map((p) => {
129
+ const facts = [p.label !== p.key ? p.label : "", p.type].filter(Boolean);
130
+ if (p.min !== undefined || p.max !== undefined) facts.push(`${p.min ?? "…"}–${p.max ?? "…"}`);
131
+ if (p.options.length > 0) facts.push(`one of ${p.options.map((o) => JSON.stringify(o.value)).join(", ")}`);
132
+ if (p.default !== undefined) facts.push(`default ${JSON.stringify(p.default)}`);
133
+ if (p.role === "prompt") facts.push("the prompt");
134
+ if (p.media) facts.push(`${p.media} slot: ${p.valueFormat || "filesPath"}`);
135
+ if (p.required) facts.push("required");
136
+ return facts.length ? `${p.key} (${facts.join(", ")})` : p.key;
137
+ }).join("; ");
138
+ }
139
+
140
+ /**
141
+ * Check caller params against the row's `flowstudio.parameters` and return
142
+ * them unchanged for `flowstudio.params`. FlowStudio owns this vocabulary: the
143
+ * only rule here is that every key is one it declared. Values are never
144
+ * converted, clamped, or mapped — FlowStudio validates what it receives.
145
+ */
146
+ export function bindParams(contract, params = {}) {
147
+ if (params === null || typeof params !== "object" || Array.isArray(params)) {
148
+ throw new MediaContractError("params must be an object keyed by the row's flowstudio.parameters keys");
149
+ }
150
+ const declared = new Set(contract.parameters.map((p) => p.key));
151
+ for (const key of Object.keys(params)) {
152
+ if (!declared.has(key)) {
153
+ throw new MediaContractError(
154
+ `params.${key} is not a parameter of ${contract.name || contract.id}; it accepts: ${describeParameters(contract.parameters)}`,
155
+ );
156
+ }
157
+ }
158
+ return { ...params };
159
+ }
160
+
161
+ /**
162
+ * Route, operation, and media placement for one submission, all from the row.
163
+ * @returns {{ route: string, operation: string, canonical: boolean }}
164
+ */
165
+ export function planGeneration(contract, { images = 0, mask = false, slotMedia = false } = {}) {
166
+ const released = MEDIA_GENERATION_ROUTES[contract.mode];
167
+ if (!released) {
168
+ throw new MediaContractError(`${contract.id} is a ${contract.mode || "unknown"} row, not an image, video, music, or 3D generation`);
169
+ }
170
+ if (!contract.enabled) throw new MediaContractError(`${contract.id} is disabled in the Router catalog`);
171
+ const slots = mediaSlots(contract);
172
+ if (slots.length > 0) {
173
+ // FlowStudio names every media input as a slot and checks them itself.
174
+ if (images > 0 || mask) {
175
+ throw new MediaContractError(
176
+ `${contract.name || contract.id} takes its media in params by slot: ${describeParameters(slots)}`,
177
+ );
178
+ }
179
+ const ops = contract.operations;
180
+ const edits = ops.includes("edit") && (!ops.includes("generate") || slotMedia);
181
+ const operation = contract.mode === "image_generation" && edits ? "edit" : "";
182
+ return {
183
+ route: operation === "edit" ? "generations" : released,
184
+ operation,
185
+ canonical: operation === "edit",
186
+ // Router refuses an edit row without inputs.images, and a required editMask
187
+ // without inputs.mask, before it forwards. These placeholders only pass
188
+ // that check; FlowStudio ignores them once the slots are in params.
189
+ passCheck: {
190
+ images: ops.includes("edit") && !ops.includes("generate"),
191
+ mask: slots.some((p) => p.type === "editmask" && p.required),
192
+ },
193
+ };
194
+ }
195
+ if (contract.images === "required" && images === 0) {
196
+ throw new MediaContractError(`${contract.name || contract.id} needs a source image in reference_images`);
197
+ }
198
+ if (contract.images === "none" && images > 0) {
199
+ throw new MediaContractError(`${contract.name || contract.id} takes no reference image; pick a row that edits or animates one`);
200
+ }
201
+ if (contract.mask === "required" && !mask) {
202
+ throw new MediaContractError(`${contract.name || contract.id} needs a mask in mask_image`);
203
+ }
204
+ if (contract.mask === "none" && mask) {
205
+ throw new MediaContractError(`${contract.name || contract.id} takes no mask`);
206
+ }
207
+ const ops = contract.operations;
208
+ let operation = "";
209
+ if (contract.mode === "image_generation") {
210
+ if (images > 0) {
211
+ if (ops.length > 0 && !ops.includes("edit")) {
212
+ throw new MediaContractError(`${contract.name || contract.id} does not edit images (operations: ${ops.join(", ")})`);
213
+ }
214
+ operation = "edit";
215
+ } else if (ops.length > 0 && !ops.includes("generate")) {
216
+ throw new MediaContractError(`${contract.name || contract.id} only ${ops.join(", ")}s; it needs a source image in reference_images`);
217
+ }
218
+ }
219
+ const canonical = operation === "edit";
220
+ return { route: canonical ? "generations" : released, operation, canonical, passCheck: { images: false, mask: false } };
221
+ }
222
+
223
+ /** Read one row from Router's catalog as the logged-in user. */
224
+ export async function fetchCatalogRow(model, deps = {}) {
225
+ const env = deps.env ?? process.env;
226
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
227
+ const response = await fetchImpl(`${routerGatewayUrl(env)}/models?detail=capabilities`, {
228
+ headers: { ...routerHeaders(env), ...(deps.headers ?? {}) },
229
+ signal: deps.signal,
230
+ });
231
+ if (!response.ok) {
232
+ throw new MediaContractError(`Router catalog answered HTTP ${response.status}; cannot check ${model}`);
233
+ }
234
+ let payload;
235
+ try {
236
+ payload = JSON.parse(await response.text());
237
+ } catch {
238
+ throw new MediaContractError("Router catalog did not answer JSON");
239
+ }
240
+ const rows = list(payload?.data ?? payload?.items);
241
+ const row = rows.find((item) => String(item?.id ?? "").trim() === model);
242
+ if (!row) throw new MediaContractError(`${model} is not in the Router catalog; list the family again and pick a listed row`);
243
+ return row;
244
+ }