@almadar/shell-node 0.0.0-stage → 0.1.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/dist/OrbitalServerRuntime.d.ts +694 -0
- package/dist/OrbitalServerRuntime.js +3 -0
- package/dist/chunk-23YAOI6P.js +42 -0
- package/dist/chunk-A2TIXOLJ.js +1326 -0
- package/dist/chunk-BJA6GYC5.js +2219 -0
- package/dist/chunk-BWMLDDCL.js +21 -0
- package/dist/chunk-QYAPNUBH.js +598 -0
- package/dist/chunk-UBZT5LDW.js +63 -0
- package/dist/chunk-WZFQ7T7V.js +18 -0
- package/dist/createProgramHandlers.d.ts +28 -0
- package/dist/createProgramHandlers.js +6 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +217 -0
- package/dist/kernel-loader-24KWM5C7.js +1 -0
- package/dist/kernel-workspace-host-CU56iOlt.d.ts +135 -0
- package/dist/kernelWorkspace.d.ts +3 -0
- package/dist/kernelWorkspace.js +4 -0
- package/dist/program-35OTIG6Y.js +2 -0
- package/package.json +62 -3
- package/README.md +0 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/program/workspace-host.ts
|
|
2
|
+
var installedHost = null;
|
|
3
|
+
function installWorkspaceHost(host) {
|
|
4
|
+
installedHost = host;
|
|
5
|
+
}
|
|
6
|
+
function uninstallWorkspaceHost() {
|
|
7
|
+
installedHost = null;
|
|
8
|
+
}
|
|
9
|
+
function installedWorkspaceHost() {
|
|
10
|
+
return installedHost;
|
|
11
|
+
}
|
|
12
|
+
function workspaceOwnerSegment(principal) {
|
|
13
|
+
let out = "";
|
|
14
|
+
for (const byte of new TextEncoder().encode(principal)) {
|
|
15
|
+
const ch = String.fromCharCode(byte);
|
|
16
|
+
out += /[A-Za-z0-9-]/.test(ch) ? ch : `_${byte.toString(16).padStart(2, "0")}`;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { installWorkspaceHost, installedWorkspaceHost, uninstallWorkspaceHost, workspaceOwnerSegment };
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { installedWorkspaceHost, workspaceOwnerSegment } from './chunk-BWMLDDCL.js';
|
|
2
|
+
import { execFile, spawnSync } from 'child_process';
|
|
3
|
+
import { mkdtempSync, rmSync, readFileSync, mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import { tmpdir } from 'os';
|
|
6
|
+
import { relative, resolve, isAbsolute, sep, join } from 'path';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { createLogger } from '@almadar/logger';
|
|
9
|
+
import { resolveOrbBinary, discoverBehaviorPackages } from '@almadar/core/behavior-package';
|
|
10
|
+
import { EventPayloadSchema, SExprSchema, CatalogEntrySchema, formatTraitValueLiteral, BehaviorDescriptionSchema, mergeCallSiteConfigOverrides, isRoutePage, defaultPage, importsOf, dependencyGraphOf, DescribeTargetSchema, CatalogScopeSchema, parseProgram, ProgramInspectionSchema, TraitDescriptionSchema } from '@almadar/core';
|
|
11
|
+
import { composeAppFromFiles, orbitalImportResolver, atomTraitResolver, organismOrderResolver } from '@almadar/core/builders';
|
|
12
|
+
|
|
13
|
+
var log = createLogger("almadar:integrations:program");
|
|
14
|
+
function runOrb(args, opts) {
|
|
15
|
+
const bin = opts.orbBin ?? resolveOrbBinary(opts.cwd);
|
|
16
|
+
return new Promise((done) => {
|
|
17
|
+
execFile(bin, [...args], { cwd: opts.cwd, maxBuffer: 1024 * 1024 * 512 }, (error, stdout, stderr) => {
|
|
18
|
+
const code = error === null ? 0 : typeof error.code === "number" ? error.code : 1;
|
|
19
|
+
if (code !== 0) log.warn("orb exited non-zero", { args: args.join(" "), code, stderr });
|
|
20
|
+
done({ stdout, stderr, code });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function runOrbSync(args, opts) {
|
|
25
|
+
const bin = opts.orbBin ?? resolveOrbBinary(opts.cwd);
|
|
26
|
+
const res = spawnSync(bin, [...args], { cwd: opts.cwd, maxBuffer: 1024 * 1024 * 512, encoding: "utf-8" });
|
|
27
|
+
const code = res.error !== void 0 ? 1 : res.status ?? 1;
|
|
28
|
+
const stderr = res.error !== void 0 ? res.error.message : res.stderr;
|
|
29
|
+
if (code !== 0) log.warn("orb exited non-zero", { args: args.join(" "), code, stderr });
|
|
30
|
+
return { stdout: res.stdout, stderr, code };
|
|
31
|
+
}
|
|
32
|
+
var ProgramParser = {
|
|
33
|
+
safeParse(value) {
|
|
34
|
+
try {
|
|
35
|
+
return { success: true, data: parseProgram(value) };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { success: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function parseJson(schema, run, what) {
|
|
42
|
+
if (run.code !== 0) throw new Error(`${what}: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
43
|
+
const parsed = schema.safeParse(JSON.parse(run.stdout));
|
|
44
|
+
if (!parsed.success) throw new Error(`${what}: unexpected orb output: ${parsed.error.message}`);
|
|
45
|
+
return parsed.data;
|
|
46
|
+
}
|
|
47
|
+
function withTempFile(name, content, use) {
|
|
48
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-program-"));
|
|
49
|
+
const path = join(dir, name);
|
|
50
|
+
writeFileSync(path, content);
|
|
51
|
+
return use(path).finally(() => rmSync(dir, { recursive: true, force: true }));
|
|
52
|
+
}
|
|
53
|
+
function withProjectFile(cwd, name, content, use) {
|
|
54
|
+
const path = join(cwd, `.${randomUUID()}-${name}`);
|
|
55
|
+
writeFileSync(path, content);
|
|
56
|
+
return use(path).finally(() => rmSync(path, { force: true }));
|
|
57
|
+
}
|
|
58
|
+
async function behaviorCatalog(scope, opts, options = {}) {
|
|
59
|
+
const exposure = (scope.exposure ?? []).flatMap((e) => ["--exposure", e]);
|
|
60
|
+
const noVectors = options.vectors === false ? ["--no-vectors"] : [];
|
|
61
|
+
const run = await runOrb(["behaviors", "catalog", "--json", ...noVectors, ...scope.paths, ...exposure], opts);
|
|
62
|
+
return parseJson(CatalogEntrySchema.array(), run, "behavior/catalog");
|
|
63
|
+
}
|
|
64
|
+
function behaviorCatalogSync(scope, opts) {
|
|
65
|
+
const exposure = (scope.exposure ?? []).flatMap((e) => ["--exposure", e]);
|
|
66
|
+
return parseJson(CatalogEntrySchema.array(), runOrbSync(["behaviors", "catalog", "--json", ...scope.paths, ...exposure], opts), "behavior/catalog");
|
|
67
|
+
}
|
|
68
|
+
async function behaviorDescribe(value, opts, options = {}) {
|
|
69
|
+
const knobVectors = options.knobVectors === true ? ["--knob-vectors"] : [];
|
|
70
|
+
if ("trait" in value) {
|
|
71
|
+
const run = await runOrb(["behaviors", "describe", "--json", ...knobVectors, formatTraitValueLiteral(value)], opts);
|
|
72
|
+
return parseJson(TraitDescriptionSchema, run, "behavior/describe");
|
|
73
|
+
}
|
|
74
|
+
const spec = "orbital" in value ? `${value.behavior}.orbitals.${value.orbital}` : value.behavior;
|
|
75
|
+
return parseJson(BehaviorDescriptionSchema, await runOrb(["behaviors", "describe", "--json", ...knobVectors, spec], opts), "behavior/describe");
|
|
76
|
+
}
|
|
77
|
+
function booleanOption(op, raw, key) {
|
|
78
|
+
const options = raw ?? {};
|
|
79
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return `${op}: options must be an object`;
|
|
80
|
+
let value;
|
|
81
|
+
for (const [k, v] of Object.entries(options)) {
|
|
82
|
+
if (k !== key) return `${op}: unsupported option "${k}"`;
|
|
83
|
+
if (typeof v !== "boolean") return `${op}: ${key} must be a boolean`;
|
|
84
|
+
value = v;
|
|
85
|
+
}
|
|
86
|
+
return value === void 0 ? {} : { value };
|
|
87
|
+
}
|
|
88
|
+
async function behaviorSource(value, opts) {
|
|
89
|
+
const run = await runOrb(["behaviors", "source", "--json", value.behavior], opts);
|
|
90
|
+
return parseJson(ProgramParser, run, "behavior/source");
|
|
91
|
+
}
|
|
92
|
+
function programRead(text, opts) {
|
|
93
|
+
return withTempFile(
|
|
94
|
+
"program.lolo",
|
|
95
|
+
text,
|
|
96
|
+
async (path) => parseJson(ProgramParser, await runOrb(["emit", "orb", path], opts), "program/read")
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
async function programReadOutcome(text, opts) {
|
|
100
|
+
return withTempFile("program.lolo", text, async (path) => {
|
|
101
|
+
const run = await runOrb(["emit", "orb", path], opts);
|
|
102
|
+
if (run.code !== 0) return fail(`program/read: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
103
|
+
const parsed = ProgramParser.safeParse(JSON.parse(run.stdout));
|
|
104
|
+
if (parsed.success) return { ok: true, result: parsed.data };
|
|
105
|
+
const report = parseJson(ValidateReportSchema, await runOrb(["validate", "--json", "--deny-warnings", path], opts), "program/read");
|
|
106
|
+
const issues = [...report.errors ?? [], ...report.warnings ?? []];
|
|
107
|
+
if (issues.length === 0) return fail(`program/read: not a program: ${parsed.error.message}`);
|
|
108
|
+
return { ok: false, error: `program/read: ${issues.length} validation issue(s): ${issues.map((e) => e.message).join("; ")}`, errors: issues };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function programInspectOutcome(text, raw, opts) {
|
|
112
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return fail("program/read inspect: options must be an object");
|
|
113
|
+
if (typeof raw.path !== "string" || !isAbsolute(raw.path)) return fail("program/read inspect: path must be the original absolute .lolo path");
|
|
114
|
+
if (raw.version !== void 0 && raw.version !== null && (typeof raw.version !== "number" || !Number.isSafeInteger(raw.version) || raw.version < 0)) return fail("program/read inspect: version must be a nonnegative integer");
|
|
115
|
+
const originalPath = raw.path;
|
|
116
|
+
const version = raw.version;
|
|
117
|
+
return withTempFile("program.lolo", text, async (path) => {
|
|
118
|
+
const args = ["emit", "orb", path, "--inspect-source", originalPath];
|
|
119
|
+
if (typeof version === "number") args.push("--document-version", String(version));
|
|
120
|
+
const run = await runOrb(args, opts);
|
|
121
|
+
if (run.code !== 0) return fail(`program/read inspect: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
122
|
+
return { ok: true, result: parseJson(ProgramInspectionSchema, run, "program/read inspect") };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function programPrint(program, opts) {
|
|
126
|
+
return withTempFile("program.orb", JSON.stringify(program), async (path) => {
|
|
127
|
+
const run = await runOrb(["emit", "lolo", path], opts);
|
|
128
|
+
if (run.code !== 0) throw new Error(`program/print: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
129
|
+
return run.stdout;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
var IssueSchema = z.object({
|
|
133
|
+
code: z.string(),
|
|
134
|
+
message: z.string(),
|
|
135
|
+
path: z.string().optional(),
|
|
136
|
+
line: z.number().optional(),
|
|
137
|
+
column: z.number().optional()
|
|
138
|
+
});
|
|
139
|
+
var ValidateReportSchema = z.object({
|
|
140
|
+
valid: z.boolean(),
|
|
141
|
+
errors: z.array(IssueSchema).optional(),
|
|
142
|
+
warnings: z.array(IssueSchema).optional()
|
|
143
|
+
});
|
|
144
|
+
function workspaceAccessError(behavior, opts, context) {
|
|
145
|
+
const host = installedWorkspaceHost();
|
|
146
|
+
if (host === null || !behavior.startsWith("./")) return void 0;
|
|
147
|
+
const under = relative(host.root, resolve(opts.cwd, behavior));
|
|
148
|
+
if (under.startsWith("..") || isAbsolute(under)) return void 0;
|
|
149
|
+
const owner = under.split(sep)[0];
|
|
150
|
+
if (context?.principal !== void 0 && workspaceOwnerSegment(context.principal) === owner) return void 0;
|
|
151
|
+
return `behavior "${behavior}" is in another user's workspace`;
|
|
152
|
+
}
|
|
153
|
+
var PROGRAM_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
154
|
+
async function stampAndValidate(program, opts, dir, caller) {
|
|
155
|
+
if (!PROGRAM_NAME.test(program.name)) {
|
|
156
|
+
return { ok: false, errors: [{ code: "PROGRAM_NAME_INVALID", message: `program name "${program.name}" is not a file name` }] };
|
|
157
|
+
}
|
|
158
|
+
const input = join(dir, `${program.name}.orb`);
|
|
159
|
+
const stamped = join(dir, `${program.name}.stamped.orb`);
|
|
160
|
+
writeFileSync(input, JSON.stringify(program));
|
|
161
|
+
const stampArgs = ["stamp", input, "-o", stamped];
|
|
162
|
+
if (opts.prior !== void 0) {
|
|
163
|
+
const priorPath = join(dir, "prior.orb");
|
|
164
|
+
writeFileSync(priorPath, JSON.stringify(opts.prior));
|
|
165
|
+
stampArgs.push("--prior", priorPath);
|
|
166
|
+
}
|
|
167
|
+
const stamp = await runOrb(stampArgs, opts);
|
|
168
|
+
if (stamp.code !== 0) {
|
|
169
|
+
return { ok: false, errors: [{ code: "PROGRAM_STAMP_FAILED", message: stamp.stderr.trim() }] };
|
|
170
|
+
}
|
|
171
|
+
const validateArgs = ["validate", "--json", "--deny-warnings", ...opts.final === true ? ["--final"] : [], stamped];
|
|
172
|
+
const report = parseJson(ValidateReportSchema, await runOrb(validateArgs, opts), caller);
|
|
173
|
+
const errors = report.errors ?? [];
|
|
174
|
+
const warnings = report.warnings ?? [];
|
|
175
|
+
return { ok: true, stamped, errors, warnings, valid: report.valid && errors.length === 0 && warnings.length === 0 };
|
|
176
|
+
}
|
|
177
|
+
async function programValidate(program, opts) {
|
|
178
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-validate-"));
|
|
179
|
+
try {
|
|
180
|
+
const run = await stampAndValidate(program, opts, dir, "program/validate");
|
|
181
|
+
return run.ok ? { valid: run.valid, errors: run.errors, warnings: run.warnings } : { valid: false, errors: run.errors, warnings: [] };
|
|
182
|
+
} finally {
|
|
183
|
+
rmSync(dir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function programEval(program, opts) {
|
|
187
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-eval-"));
|
|
188
|
+
try {
|
|
189
|
+
const run = await stampAndValidate(program, opts, dir, "program/eval");
|
|
190
|
+
if (!run.ok) return { ok: false, errors: run.errors };
|
|
191
|
+
const stamped = run.stamped;
|
|
192
|
+
const issues = [...run.errors, ...run.warnings];
|
|
193
|
+
if (!run.valid || issues.length > 0) return { ok: false, errors: issues };
|
|
194
|
+
const written = ProgramParser.safeParse(JSON.parse(readFileSync(stamped, "utf-8")));
|
|
195
|
+
if (!written.success) throw new Error(`program/eval: stamped program does not parse: ${written.error.message}`);
|
|
196
|
+
const content = JSON.stringify(written.data, null, 2);
|
|
197
|
+
let behavior;
|
|
198
|
+
if (opts.workspace !== void 0) {
|
|
199
|
+
const host = installedWorkspaceHost();
|
|
200
|
+
if (host === null) return { ok: false, errors: [{ code: "PROGRAM_NO_WORKSPACE_HOST", message: "no workspace host is installed on this host" }] };
|
|
201
|
+
if (opts.workspace.context.principal === void 0) {
|
|
202
|
+
return { ok: false, errors: [{ code: "PROGRAM_WORKSPACE_UNAUTHORIZED", message: "a workspace eval needs a signed-in caller" }] };
|
|
203
|
+
}
|
|
204
|
+
const ws = await host.open(opts.workspace.id, opts.workspace.context);
|
|
205
|
+
const rel = relative(opts.cwd, ws.workDir);
|
|
206
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
207
|
+
return { ok: false, errors: [{ code: "PROGRAM_WORKSPACE_OUTSIDE_PROJECT", message: `workspace ${ws.workDir} is outside the project ${opts.cwd}` }] };
|
|
208
|
+
}
|
|
209
|
+
await ws.writeFile(`orbitals/${program.name}.orb`, content);
|
|
210
|
+
await ws.snapshot(`program/eval ${program.name}`);
|
|
211
|
+
behavior = `./${rel.split(sep).join("/")}/orbitals/${program.name}`;
|
|
212
|
+
} else {
|
|
213
|
+
const target = join(opts.cwd, "orbitals");
|
|
214
|
+
mkdirSync(target, { recursive: true });
|
|
215
|
+
writeFileSync(join(target, `${program.name}.orb`), content);
|
|
216
|
+
behavior = `./orbitals/${program.name}`;
|
|
217
|
+
}
|
|
218
|
+
const traits = written.data.orbitals.flatMap(
|
|
219
|
+
(orbital) => orbital.traits.flatMap((t) => typeof t === "object" && "name" in t && typeof t.name === "string" ? [{ behavior, trait: t.name }] : [])
|
|
220
|
+
);
|
|
221
|
+
return { ok: true, result: { behavior, traits, value: { behavior } } };
|
|
222
|
+
} finally {
|
|
223
|
+
rmSync(dir, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
var LAYOUT_STRATEGIES = ["sidebar", "tabs", "dashboard", "wizard-flow", "single"];
|
|
227
|
+
function importSource(orbital) {
|
|
228
|
+
const ref = orbital.reference?.ref;
|
|
229
|
+
if (ref === void 0) return void 0;
|
|
230
|
+
const [alias, kind, name] = ref.split(".");
|
|
231
|
+
if (kind !== "orbitals" || name === void 0) return void 0;
|
|
232
|
+
const use = (orbital.uses ?? []).find((u) => u.as === alias);
|
|
233
|
+
return use === void 0 ? void 0 : { from: use.from, orbital: name };
|
|
234
|
+
}
|
|
235
|
+
var behaviorName = (from) => from.split("/").pop() ?? from;
|
|
236
|
+
async function programCompose(programs, options, opts) {
|
|
237
|
+
const sources = /* @__PURE__ */ new Set();
|
|
238
|
+
for (const program of programs) {
|
|
239
|
+
for (const orbital of program.orbitals) for (const use of orbital.uses ?? []) sources.add(use.from);
|
|
240
|
+
}
|
|
241
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const from of sources) loaded.set(behaviorName(from), await behaviorSource({ behavior: from }, opts));
|
|
243
|
+
const load = (name) => loaded.get(name) ?? null;
|
|
244
|
+
const byName = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const program of programs) for (const orbital of program.orbitals) byName.set(orbital.name, orbital);
|
|
246
|
+
const catalogOrbitalOf = (name) => {
|
|
247
|
+
const orbital = byName.get(name);
|
|
248
|
+
const source = orbital === void 0 ? void 0 : importSource(orbital);
|
|
249
|
+
return source === void 0 ? void 0 : { organism: behaviorName(source.from), orbital: source.orbital };
|
|
250
|
+
};
|
|
251
|
+
const composed = composeAppFromFiles(programs, {
|
|
252
|
+
appName: options.appName,
|
|
253
|
+
...options.layout !== void 0 ? { layoutStrategy: options.layout } : {},
|
|
254
|
+
organismOf: (name) => catalogOrbitalOf(name)?.organism,
|
|
255
|
+
organismOrderOf: organismOrderResolver(catalogOrbitalOf, load),
|
|
256
|
+
atomTraitOf: atomTraitResolver(load),
|
|
257
|
+
importedOrbitalOf: orbitalImportResolver(load),
|
|
258
|
+
importedProgramOf: (orbital) => {
|
|
259
|
+
const source = importSource(orbital);
|
|
260
|
+
return source === void 0 ? void 0 : load(behaviorName(source.from)) ?? void 0;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return options.theme !== void 0 ? { ...composed.schema, theme: options.theme } : composed.schema;
|
|
264
|
+
}
|
|
265
|
+
async function mountTraitValue(value, opts, context) {
|
|
266
|
+
const denied = workspaceAccessError(value.behavior, opts, context);
|
|
267
|
+
if (denied !== void 0) return { ok: false, error: denied };
|
|
268
|
+
let source;
|
|
269
|
+
try {
|
|
270
|
+
source = await behaviorSource(value, opts);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
273
|
+
}
|
|
274
|
+
const owner = source.orbitals.find(
|
|
275
|
+
(o) => o.traits.some((t) => typeof t === "object" && !("ref" in t) && t.name === value.trait)
|
|
276
|
+
);
|
|
277
|
+
if (owner === void 0) {
|
|
278
|
+
return { ok: false, error: `behavior "${value.behavior}" declares no inline trait "${value.trait}"` };
|
|
279
|
+
}
|
|
280
|
+
const { behavior, trait, config, ...rest } = value;
|
|
281
|
+
const overrides = config !== void 0 ? { ...rest, config: mergeCallSiteConfigOverrides({}, config) } : rest;
|
|
282
|
+
const projectRelative = behavior.startsWith("./");
|
|
283
|
+
const page = (owner.pages ?? []).find((p) => typeof p === "object" && "path" in p && isRoutePage(p) && (p.traits ?? []).some((t) => (typeof t === "string" ? t : "ref" in t ? t.ref : t.name) === trait));
|
|
284
|
+
const wrapper = {
|
|
285
|
+
name: `Mount${trait}`,
|
|
286
|
+
orbitals: [
|
|
287
|
+
{
|
|
288
|
+
name: "Mounted",
|
|
289
|
+
entity: `Bound.orbitals.${owner.name}.entity`,
|
|
290
|
+
reference: {
|
|
291
|
+
ref: `Bound.orbitals.${owner.name}`,
|
|
292
|
+
only: [trait],
|
|
293
|
+
...Object.keys(overrides).length > 0 ? { traits: { [trait]: overrides } } : {}
|
|
294
|
+
},
|
|
295
|
+
traits: [],
|
|
296
|
+
pages: [],
|
|
297
|
+
uses: [{ as: "Bound", from: behavior }]
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
};
|
|
301
|
+
const check = async (path) => {
|
|
302
|
+
const report = parseJson(ValidateReportSchema, await runOrb(["validate", "--json", "--deny-warnings", path], opts), "mount");
|
|
303
|
+
const issues = [...report.errors ?? [], ...report.warnings ?? []];
|
|
304
|
+
if (!report.valid || issues.length > 0) {
|
|
305
|
+
return { ok: false, error: `trait value ${formatTraitValueLiteral(value)} does not validate where it is bound`, errors: issues };
|
|
306
|
+
}
|
|
307
|
+
const schema = parseJson(ProgramParser, await runOrb(["resolve", path], opts), "mount");
|
|
308
|
+
const pagePath = page !== void 0 && typeof page === "object" && "path" in page ? page.path : void 0;
|
|
309
|
+
return { ok: true, mounted: { schema, orbital: "Mounted", trait: `Mounted${trait}`, ...pagePath !== void 0 ? { pagePath } : {} } };
|
|
310
|
+
};
|
|
311
|
+
const text = JSON.stringify(wrapper);
|
|
312
|
+
return projectRelative ? withProjectFile(opts.cwd, "mount.orb", text, check) : withTempFile("mount.orb", text, check);
|
|
313
|
+
}
|
|
314
|
+
var WHICH_LINE = /^(\/\S+\.(?:orb|lolo)) \((.+)\)$/;
|
|
315
|
+
async function behaviorFile(behavior, opts) {
|
|
316
|
+
if (behavior.startsWith("./")) {
|
|
317
|
+
const path = `${resolve(opts.cwd, behavior)}.orb`;
|
|
318
|
+
return existsSync(path) ? path : { error: `no program at ${behavior}` };
|
|
319
|
+
}
|
|
320
|
+
const run = await runOrb(["behaviors", "which", behavior], opts);
|
|
321
|
+
const line = run.stdout.trim().split("\n").pop() ?? "";
|
|
322
|
+
const match = WHICH_LINE.exec(line);
|
|
323
|
+
if (run.code !== 0 || match === null) return { error: `behavior "${behavior}" is not installed${run.stderr.trim() ? `: ${run.stderr.trim()}` : ""}` };
|
|
324
|
+
return match[1];
|
|
325
|
+
}
|
|
326
|
+
async function mountBehaviorValue(value, opts, context) {
|
|
327
|
+
const denied = workspaceAccessError(value.behavior, opts, context);
|
|
328
|
+
if (denied !== void 0) return { ok: false, error: denied };
|
|
329
|
+
const file = await behaviorFile(value.behavior, opts);
|
|
330
|
+
if (typeof file !== "string") return { ok: false, error: file.error };
|
|
331
|
+
const report = parseJson(ValidateReportSchema, await runOrb(["validate", "--json", "--deny-warnings", file], opts), "mount");
|
|
332
|
+
const issues = [...report.errors ?? [], ...report.warnings ?? []];
|
|
333
|
+
if (!report.valid || issues.length > 0) {
|
|
334
|
+
return { ok: false, error: `behavior ${value.behavior} does not validate`, errors: issues };
|
|
335
|
+
}
|
|
336
|
+
const schema = parseJson(ProgramParser, await runOrb(["resolve", file], opts), "mount");
|
|
337
|
+
const firstPage = defaultPage(schema)?.page.path;
|
|
338
|
+
if (firstPage === void 0) return { ok: false, error: `behavior ${value.behavior} has no page to open` };
|
|
339
|
+
return { ok: true, mounted: { schema, firstPage } };
|
|
340
|
+
}
|
|
341
|
+
async function programResolve(path, opts) {
|
|
342
|
+
return parseJson(ProgramParser, await runOrb(["resolve", path], opts), "program/resolve");
|
|
343
|
+
}
|
|
344
|
+
function programResolveSchema(program, opts) {
|
|
345
|
+
return withProjectFile(opts.cwd, "program.orb", JSON.stringify(program), (path) => programResolve(path, opts));
|
|
346
|
+
}
|
|
347
|
+
async function behaviorResolve(value, opts) {
|
|
348
|
+
const file = await behaviorFile(value.behavior, opts);
|
|
349
|
+
if (typeof file !== "string") return file;
|
|
350
|
+
return parseJson(ProgramParser, await runOrb(["resolve", file], opts), "behavior/resolve");
|
|
351
|
+
}
|
|
352
|
+
function installedBehaviorIndex(opts) {
|
|
353
|
+
const out = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const pkg of discoverBehaviorPackages(opts.cwd)) {
|
|
355
|
+
const tier = pkg.name === "@almadar/std" ? "std" : pkg.name === "@almadar-io/behaviors" ? "io" : "unknown";
|
|
356
|
+
const catalog = parseJson(CatalogEntrySchema.array(), runOrbSync(["behaviors", "catalog", "--json", "--no-vectors", pkg.orb.prefix], opts), "behavior/dependencies");
|
|
357
|
+
for (const entry of catalog) {
|
|
358
|
+
const name = entry.behavior.slice(pkg.orb.prefix.length + 1);
|
|
359
|
+
if (!out.has(name)) out.set(name, { tier, prefix: pkg.orb.prefix, ...entry.pattern !== void 0 ? { pattern: entry.pattern } : {} });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
async function behaviorDependencies(program, opts) {
|
|
365
|
+
const index = installedBehaviorIndex(opts);
|
|
366
|
+
const orbs = /* @__PURE__ */ new Map();
|
|
367
|
+
let frontier = [...new Set(importsOf(program, () => "unknown").map((i) => i.behavior))];
|
|
368
|
+
while (frontier.length > 0) {
|
|
369
|
+
const loaded = await Promise.all(
|
|
370
|
+
frontier.map(async (name) => {
|
|
371
|
+
const entry = index.get(name);
|
|
372
|
+
return [name, entry ? await behaviorSource({ behavior: `${entry.prefix}/${name}` }, opts) : null];
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
const next = /* @__PURE__ */ new Set();
|
|
376
|
+
for (const [name, orb] of loaded) {
|
|
377
|
+
orbs.set(name, orb);
|
|
378
|
+
for (const i of orb ? importsOf(orb, () => "unknown") : []) if (!orbs.has(i.behavior)) next.add(i.behavior);
|
|
379
|
+
}
|
|
380
|
+
frontier = [...next].filter((n) => !orbs.has(n));
|
|
381
|
+
}
|
|
382
|
+
return dependencyGraphOf(program, {
|
|
383
|
+
tierOf: (n) => index.get(n)?.tier ?? "unknown",
|
|
384
|
+
orbOf: (n) => orbs.get(n) ?? null,
|
|
385
|
+
patternOf: (n) => index.get(n)?.pattern
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
var StepRequestSchema = z.object({
|
|
389
|
+
orbital: z.string(),
|
|
390
|
+
trait: z.string(),
|
|
391
|
+
from: z.string(),
|
|
392
|
+
event: z.string(),
|
|
393
|
+
payload: EventPayloadSchema,
|
|
394
|
+
entity: EventPayloadSchema.optional(),
|
|
395
|
+
config: EventPayloadSchema.optional(),
|
|
396
|
+
now: z.number().optional(),
|
|
397
|
+
settleMs: z.number().optional(),
|
|
398
|
+
viewer: EventPayloadSchema.nullable().optional()
|
|
399
|
+
});
|
|
400
|
+
var StepOutcomeSchema = z.object({
|
|
401
|
+
orbital: z.string(),
|
|
402
|
+
trait: z.string(),
|
|
403
|
+
event: z.string(),
|
|
404
|
+
executed: z.boolean(),
|
|
405
|
+
previousState: z.string(),
|
|
406
|
+
newState: z.string(),
|
|
407
|
+
emits: z.array(z.object({ event: z.string(), payload: EventPayloadSchema.nullable() })),
|
|
408
|
+
firedArm: z.number().int().nullable(),
|
|
409
|
+
guards: z.array(z.object({ arm: z.number().int(), guard: SExprSchema, passed: z.boolean(), error: z.string().optional() })),
|
|
410
|
+
effects: z.array(z.object({ type: z.string(), args: z.array(SExprSchema), status: z.enum(["executed", "failed", "skipped"]), error: z.string().optional() }))
|
|
411
|
+
});
|
|
412
|
+
var StepReportSchema = z.union([
|
|
413
|
+
z.object({ success: z.literal(true), steps: z.array(z.object({ outcomes: z.array(StepOutcomeSchema), entityAfter: EventPayloadSchema.nullable() })) }),
|
|
414
|
+
z.object({ success: z.literal(false), error: z.string() })
|
|
415
|
+
]);
|
|
416
|
+
function effectsOf(effects) {
|
|
417
|
+
return effects.map((e) => ({ type: e.type, args: e.args, status: e.status, ...e.error !== void 0 ? { error: e.error } : {} }));
|
|
418
|
+
}
|
|
419
|
+
function emittedOf(emits, step) {
|
|
420
|
+
return emits.map((e) => ({ type: e.event, ...e.payload !== null ? { payload: e.payload } : {}, timestamp: step }));
|
|
421
|
+
}
|
|
422
|
+
async function programStep(program, requests, opts, live = false) {
|
|
423
|
+
const steps = requests.map((r) => ({ trait: r.trait, event: r.event, payload: r.payload, from: r.from, ...r.entity !== void 0 ? { entity: r.entity } : {}, ...r.config !== void 0 ? { config: r.config } : {}, ...r.now !== void 0 ? { now: r.now } : {}, ...r.settleMs !== void 0 ? { settleMs: r.settleMs } : {}, ...r.viewer !== void 0 ? { viewer: r.viewer } : {} }));
|
|
424
|
+
return withTempFile("program.orb", JSON.stringify(program), async (path) => {
|
|
425
|
+
const run = await runOrb(["execute", path, "--steps", JSON.stringify(steps), ...live ? ["--live"] : []], opts);
|
|
426
|
+
const report = parseJson(StepReportSchema, run, "program/step");
|
|
427
|
+
if (!report.success) return { error: report.error };
|
|
428
|
+
const results = [];
|
|
429
|
+
for (const [i, request] of requests.entries()) {
|
|
430
|
+
const moved = report.steps[i]?.outcomes ?? [];
|
|
431
|
+
const outcome = moved.find((o) => o.trait === request.trait);
|
|
432
|
+
if (outcome === void 0) return { error: `trait "${request.trait}" did not run` };
|
|
433
|
+
const firedGuarded = outcome.firedArm !== null && outcome.guards.some((g) => g.arm === outcome.firedArm);
|
|
434
|
+
results.push({ ...request, result: {
|
|
435
|
+
trait: outcome.trait,
|
|
436
|
+
event: request.event,
|
|
437
|
+
transitionFired: outcome.executed,
|
|
438
|
+
guard: firedGuarded ? "pass" : outcome.firedArm === null && outcome.guards.length > 0 ? "fail" : "none",
|
|
439
|
+
...outcome.firedArm !== null ? { firedArm: outcome.firedArm } : {},
|
|
440
|
+
state: { before: outcome.previousState, after: outcome.newState },
|
|
441
|
+
effects: effectsOf(outcome.effects),
|
|
442
|
+
emitted: emittedOf(outcome.emits, i),
|
|
443
|
+
guards: outcome.guards.map((g) => ({ arm: g.arm, guard: g.guard, passed: g.passed, trace: [], ...g.error !== void 0 ? { error: g.error } : {} })),
|
|
444
|
+
entityAfter: report.steps[i]?.entityAfter ?? null,
|
|
445
|
+
outcomes: moved.map((o) => ({ orbital: o.orbital, trait: o.trait, event: o.event, from: o.previousState, to: o.newState, arm: o.firedArm, fired: o.executed, effects: effectsOf(o.effects), emitted: emittedOf(o.emits, i) }))
|
|
446
|
+
} });
|
|
447
|
+
}
|
|
448
|
+
return results;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function fail(error) {
|
|
452
|
+
return { ok: false, error };
|
|
453
|
+
}
|
|
454
|
+
function issuesOf(error) {
|
|
455
|
+
return error.issues.map((i) => i.path.length > 0 ? `${i.path.map(String).join(".")}: ${i.message}` : i.message).join("; ");
|
|
456
|
+
}
|
|
457
|
+
function programArg(op, value) {
|
|
458
|
+
const parsed = ProgramParser.safeParse(value);
|
|
459
|
+
return parsed.success ? parsed.data : `${op}: not a program: ${parsed.error.message}`;
|
|
460
|
+
}
|
|
461
|
+
async function runProgramEffect(op, args, opts, context) {
|
|
462
|
+
log.debug("effect", { op, args: JSON.stringify(args) });
|
|
463
|
+
try {
|
|
464
|
+
switch (op) {
|
|
465
|
+
case "behavior/catalog": {
|
|
466
|
+
const scope = CatalogScopeSchema.safeParse(args[0]);
|
|
467
|
+
if (!scope.success) return fail(`behavior/catalog: ${issuesOf(scope.error)}`);
|
|
468
|
+
const vectors = booleanOption(op, args[1], "vectors");
|
|
469
|
+
if (typeof vectors === "string") return fail(vectors);
|
|
470
|
+
return { ok: true, result: await behaviorCatalog(scope.data, opts, vectors.value === void 0 ? {} : { vectors: vectors.value }) };
|
|
471
|
+
}
|
|
472
|
+
case "behavior/describe": {
|
|
473
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
474
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
475
|
+
const knobVectors = booleanOption(op, args[1], "knobVectors");
|
|
476
|
+
if (typeof knobVectors === "string") return fail(knobVectors);
|
|
477
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
478
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
479
|
+
return { ok: true, result: await behaviorDescribe(value.data, opts, knobVectors.value === void 0 ? {} : { knobVectors: knobVectors.value }) };
|
|
480
|
+
}
|
|
481
|
+
case "behavior/source": {
|
|
482
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
483
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
484
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
485
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
486
|
+
return { ok: true, result: await behaviorSource(value.data, opts) };
|
|
487
|
+
}
|
|
488
|
+
case "behavior/resolve": {
|
|
489
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
490
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
491
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
492
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
493
|
+
const resolved = await behaviorResolve(value.data, opts);
|
|
494
|
+
return "error" in resolved ? fail(`${op}: ${resolved.error}`) : { ok: true, result: resolved };
|
|
495
|
+
}
|
|
496
|
+
case "program/step": {
|
|
497
|
+
const program = programArg(op, args[0]);
|
|
498
|
+
if (typeof program === "string") return fail(program);
|
|
499
|
+
const requests = z.array(StepRequestSchema).safeParse(args[1]);
|
|
500
|
+
if (!requests.success) return fail(`${op}: ${issuesOf(requests.error)}`);
|
|
501
|
+
const options = args[2];
|
|
502
|
+
const live = options !== null && typeof options === "object" && !Array.isArray(options) ? options["live"] : void 0;
|
|
503
|
+
if (live !== void 0 && live !== null && typeof live !== "boolean") return fail(`${op}: options.live must be a boolean`);
|
|
504
|
+
const played = await programStep(program, requests.data, opts, live === true);
|
|
505
|
+
return "error" in played ? fail(`${op}: ${played.error}`) : { ok: true, result: played };
|
|
506
|
+
}
|
|
507
|
+
case "behavior/dependencies": {
|
|
508
|
+
const program = programArg(op, args[0]);
|
|
509
|
+
return typeof program === "string" ? fail(program) : { ok: true, result: await behaviorDependencies(program, opts) };
|
|
510
|
+
}
|
|
511
|
+
case "program/read": {
|
|
512
|
+
if (typeof args[0] !== "string") return fail("program/read: expected .lolo text");
|
|
513
|
+
const options = args[1];
|
|
514
|
+
if (options !== null && typeof options === "object" && !Array.isArray(options) && options.inspect === true) return programInspectOutcome(args[0], options, opts);
|
|
515
|
+
return programReadOutcome(args[0], opts);
|
|
516
|
+
}
|
|
517
|
+
case "program/print": {
|
|
518
|
+
const program = programArg(op, args[0]);
|
|
519
|
+
return typeof program === "string" ? fail(program) : { ok: true, result: await programPrint(program, opts) };
|
|
520
|
+
}
|
|
521
|
+
case "program/compose": {
|
|
522
|
+
if (!Array.isArray(args[0])) return fail("program/compose: expected a list of programs");
|
|
523
|
+
const programs = [];
|
|
524
|
+
for (const raw of args[0]) {
|
|
525
|
+
const program = programArg(op, raw);
|
|
526
|
+
if (typeof program === "string") return fail(program);
|
|
527
|
+
programs.push(program);
|
|
528
|
+
}
|
|
529
|
+
const options = args[1];
|
|
530
|
+
if (options === null || options === void 0 || typeof options !== "object" || Array.isArray(options)) {
|
|
531
|
+
return fail("program/compose: options must be an object with an appName");
|
|
532
|
+
}
|
|
533
|
+
let appName;
|
|
534
|
+
let layout;
|
|
535
|
+
let theme;
|
|
536
|
+
for (const [key, value] of Object.entries(options)) {
|
|
537
|
+
if (key === "appName" && typeof value === "string" && value.length > 0) appName = value;
|
|
538
|
+
else if (key === "theme" && typeof value === "string") theme = value.length > 0 ? value : void 0;
|
|
539
|
+
else if (key === "layout") {
|
|
540
|
+
const match = LAYOUT_STRATEGIES.find((l) => l === value);
|
|
541
|
+
if (match === void 0) return fail(`program/compose: layout must be one of ${LAYOUT_STRATEGIES.join(", ")}`);
|
|
542
|
+
layout = match;
|
|
543
|
+
} else return fail(`program/compose: unsupported option "${key}"`);
|
|
544
|
+
}
|
|
545
|
+
if (appName === void 0) return fail("program/compose: options must name the app (appName)");
|
|
546
|
+
return { ok: true, result: await programCompose(programs, { appName, ...layout !== void 0 ? { layout } : {}, ...theme !== void 0 ? { theme } : {} }, opts) };
|
|
547
|
+
}
|
|
548
|
+
case "program/validate": {
|
|
549
|
+
const program = programArg(op, args[0]);
|
|
550
|
+
if (typeof program === "string") return fail(program);
|
|
551
|
+
const options = args[1] ?? {};
|
|
552
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/validate: options must be an object");
|
|
553
|
+
let prior;
|
|
554
|
+
let final;
|
|
555
|
+
for (const [key, value] of Object.entries(options)) {
|
|
556
|
+
if (key === "final") {
|
|
557
|
+
if (typeof value !== "boolean") return fail("program/validate: final must be a boolean");
|
|
558
|
+
final = value;
|
|
559
|
+
} else if (key === "prior") {
|
|
560
|
+
const p = programArg("program/validate prior", value);
|
|
561
|
+
if (typeof p === "string") return fail(p);
|
|
562
|
+
prior = p;
|
|
563
|
+
} else return fail(`program/validate: unsupported option "${key}"`);
|
|
564
|
+
}
|
|
565
|
+
return { ok: true, result: await programValidate(program, { ...opts, ...prior !== void 0 ? { prior } : {}, ...final !== void 0 ? { final } : {} }) };
|
|
566
|
+
}
|
|
567
|
+
case "program/eval": {
|
|
568
|
+
const program = programArg(op, args[0]);
|
|
569
|
+
if (typeof program === "string") return fail(program);
|
|
570
|
+
const options = args[1] ?? {};
|
|
571
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/eval: options must be an object");
|
|
572
|
+
let workspace;
|
|
573
|
+
let prior;
|
|
574
|
+
let final;
|
|
575
|
+
for (const [key, value] of Object.entries(options)) {
|
|
576
|
+
if (key === "workspace") {
|
|
577
|
+
if (typeof value !== "string" || value === "") return fail("program/eval: workspace must be a non-empty id");
|
|
578
|
+
workspace = value;
|
|
579
|
+
} else if (key === "final") {
|
|
580
|
+
if (typeof value !== "boolean") return fail("program/eval: final must be a boolean");
|
|
581
|
+
final = value;
|
|
582
|
+
} else if (key === "prior") {
|
|
583
|
+
const p = programArg("program/eval prior", value);
|
|
584
|
+
if (typeof p === "string") return fail(p);
|
|
585
|
+
prior = p;
|
|
586
|
+
} else return fail(`program/eval: unsupported option "${key}"`);
|
|
587
|
+
}
|
|
588
|
+
const outcome = await programEval(program, { ...opts, ...workspace !== void 0 ? { workspace: { id: workspace, context: context ?? {} } } : {}, ...prior !== void 0 ? { prior } : {}, ...final !== void 0 ? { final } : {} });
|
|
589
|
+
if (outcome.ok) return { ok: true, result: outcome.result };
|
|
590
|
+
return { ok: false, error: `program/eval: ${outcome.errors.length} validation issue(s): ${outcome.errors.map((e) => e.message).join("; ")}`, errors: outcome.errors };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} catch (error) {
|
|
594
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export { behaviorCatalog, behaviorCatalogSync, behaviorDependencies, behaviorDescribe, behaviorResolve, behaviorSource, mountBehaviorValue, mountTraitValue, programCompose, programEval, programPrint, programRead, programResolve, programResolveSchema, programStep, programValidate, runProgramEffect, workspaceAccessError };
|