@almadar/shell-node 0.0.0-stage → 1.0.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 +473 -0
- package/dist/OrbitalServerRuntime.js +3 -0
- package/dist/chunk-3PNZYEZQ.js +844 -0
- package/dist/chunk-7QE32QQR.js +523 -0
- package/dist/chunk-7QG7JGKQ.js +18 -0
- package/dist/chunk-BWMLDDCL.js +21 -0
- package/dist/chunk-EEYGL7AB.js +53 -0
- package/dist/chunk-VQVAHUAX.js +183 -0
- package/dist/chunk-YLFK5OJN.js +2225 -0
- package/dist/createProgramHandlers.d.ts +28 -0
- package/dist/createProgramHandlers.js +6 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +217 -0
- package/dist/kernel-loader-3JR6LJV5.js +1 -0
- package/dist/kernel-workspace-host-cT9JGJur.d.ts +130 -0
- package/dist/kernelWorkspace.d.ts +3 -0
- package/dist/kernelWorkspace.js +4 -0
- package/dist/program-6DB6LK5B.js +2 -0
- package/package.json +64 -3
- package/README.md +0 -3
|
@@ -0,0 +1,523 @@
|
|
|
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 { decodeProveReport, decodeOsGrantsReport, decodeValidationJsonOutput, isStepRefusal, decodeStepRefusal, decodePlayedStep } from '@almadar/core/kernel-wire';
|
|
10
|
+
import { resolveOrbBinary, discoverBehaviorPackages } from '@almadar/core/behavior-package';
|
|
11
|
+
import { EventPayloadSchema, CatalogEntrySchema, formatTraitValueLiteral, BehaviorDescriptionSchema, importsOf, dependencyGraphOf, DescribeTargetSchema, CatalogScopeSchema, parseProgram, ProgramInspectionSchema, TraitDescriptionSchema } from '@almadar/core';
|
|
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 decodeJson(decode, run, what) {
|
|
48
|
+
if (run.code !== 0) throw new Error(`${what}: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
49
|
+
try {
|
|
50
|
+
return decode(JSON.parse(run.stdout));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`${what}: unexpected orb output: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function issueOf(finding) {
|
|
56
|
+
return { code: finding.code, message: finding.message, path: finding.path, ...finding.line !== void 0 ? { line: finding.line } : {}, ...finding.column !== void 0 ? { column: finding.column } : {} };
|
|
57
|
+
}
|
|
58
|
+
function withTempFile(name, content, use) {
|
|
59
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-program-"));
|
|
60
|
+
const path = join(dir, name);
|
|
61
|
+
writeFileSync(path, content);
|
|
62
|
+
return use(path).finally(() => rmSync(dir, { recursive: true, force: true }));
|
|
63
|
+
}
|
|
64
|
+
function withProjectFile(cwd, name, content, use) {
|
|
65
|
+
const path = join(cwd, `.${randomUUID()}-${name}`);
|
|
66
|
+
writeFileSync(path, content);
|
|
67
|
+
return use(path).finally(() => rmSync(path, { force: true }));
|
|
68
|
+
}
|
|
69
|
+
async function behaviorCatalog(scope, opts, options = {}) {
|
|
70
|
+
const exposure = (scope.exposure ?? []).flatMap((e) => ["--exposure", e]);
|
|
71
|
+
const noTraits = options.traits === false ? ["--no-traits"] : [];
|
|
72
|
+
const run = await runOrb(["behaviors", "catalog", "--json", ...noTraits, ...scope.paths, ...exposure], opts);
|
|
73
|
+
return parseJson(CatalogEntrySchema.array(), run, "behavior/catalog");
|
|
74
|
+
}
|
|
75
|
+
function behaviorCatalogSync(scope, opts) {
|
|
76
|
+
const exposure = (scope.exposure ?? []).flatMap((e) => ["--exposure", e]);
|
|
77
|
+
return parseJson(CatalogEntrySchema.array(), runOrbSync(["behaviors", "catalog", "--json", ...scope.paths, ...exposure], opts), "behavior/catalog");
|
|
78
|
+
}
|
|
79
|
+
async function behaviorDescribe(value, opts) {
|
|
80
|
+
if ("trait" in value) {
|
|
81
|
+
const run = await runOrb(["behaviors", "describe", "--json", formatTraitValueLiteral(value)], opts);
|
|
82
|
+
return parseJson(TraitDescriptionSchema, run, "behavior/describe");
|
|
83
|
+
}
|
|
84
|
+
const spec = "orbital" in value ? `${value.behavior}.orbitals.${value.orbital}` : value.behavior;
|
|
85
|
+
return parseJson(BehaviorDescriptionSchema, await runOrb(["behaviors", "describe", "--json", spec], opts), "behavior/describe");
|
|
86
|
+
}
|
|
87
|
+
function booleanOptions(op, raw, keys) {
|
|
88
|
+
const options = raw ?? {};
|
|
89
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return `${op}: options must be an object`;
|
|
90
|
+
const out = {};
|
|
91
|
+
for (const [k, v] of Object.entries(options)) {
|
|
92
|
+
const key = keys.find((known) => known === k);
|
|
93
|
+
if (key === void 0) return `${op}: unsupported option "${k}"`;
|
|
94
|
+
if (typeof v !== "boolean") return `${op}: ${k} must be a boolean`;
|
|
95
|
+
out[key] = v;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
async function behaviorSource(value, opts) {
|
|
100
|
+
const run = await runOrb(["behaviors", "source", "--json", value.behavior], opts);
|
|
101
|
+
return parseJson(ProgramParser, run, "behavior/source");
|
|
102
|
+
}
|
|
103
|
+
function programRead(text, opts) {
|
|
104
|
+
return withTempFile(
|
|
105
|
+
"program.lolo",
|
|
106
|
+
text,
|
|
107
|
+
async (path) => parseJson(ProgramParser, await runOrb(["emit", "orb", path], opts), "program/read")
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async function programReadOutcome(text, opts) {
|
|
111
|
+
return withTempFile("program.lolo", text, async (path) => {
|
|
112
|
+
const run = await runOrb(["emit", "orb", path], opts);
|
|
113
|
+
if (run.code !== 0) return fail(`program/read: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
114
|
+
const parsed = ProgramParser.safeParse(JSON.parse(run.stdout));
|
|
115
|
+
if (parsed.success) return { ok: true, result: parsed.data };
|
|
116
|
+
const report = decodeJson(decodeValidationJsonOutput, await runOrb(["validate", "--json", "--deny-warnings", path], opts), "program/read");
|
|
117
|
+
const issues = [...report.errors ?? [], ...report.warnings ?? []].map(issueOf);
|
|
118
|
+
if (issues.length === 0) return fail(`program/read: not a program: ${parsed.error.message}`);
|
|
119
|
+
return { ok: false, error: `program/read: ${issues.length} validation issue(s): ${issues.map((e) => e.message).join("; ")}`, errors: issues };
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function programInspectOutcome(text, raw, opts) {
|
|
123
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return fail("program/read inspect: options must be an object");
|
|
124
|
+
if (typeof raw.path !== "string" || !isAbsolute(raw.path)) return fail("program/read inspect: path must be the original absolute .lolo path");
|
|
125
|
+
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");
|
|
126
|
+
const originalPath = raw.path;
|
|
127
|
+
const version = raw.version;
|
|
128
|
+
return withTempFile("program.lolo", text, async (path) => {
|
|
129
|
+
const args = ["emit", "orb", path, "--inspect-source", originalPath];
|
|
130
|
+
if (typeof version === "number") args.push("--document-version", String(version));
|
|
131
|
+
const run = await runOrb(args, opts);
|
|
132
|
+
if (run.code !== 0) return fail(`program/read inspect: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
133
|
+
return { ok: true, result: parseJson(ProgramInspectionSchema, run, "program/read inspect") };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function programPrint(program, opts) {
|
|
137
|
+
return withTempFile("program.orb", JSON.stringify(program), async (path) => {
|
|
138
|
+
const run = await runOrb(["emit", "lolo", path], opts);
|
|
139
|
+
if (run.code !== 0) throw new Error(`program/print: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
140
|
+
return run.stdout;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function workspaceAccessError(behavior, opts, context) {
|
|
144
|
+
const host = installedWorkspaceHost();
|
|
145
|
+
if (host === null || !behavior.startsWith("./")) return void 0;
|
|
146
|
+
const under = relative(host.root, resolve(opts.cwd, behavior));
|
|
147
|
+
if (under.startsWith("..") || isAbsolute(under)) return void 0;
|
|
148
|
+
const owner = under.split(sep)[0];
|
|
149
|
+
if (context?.principal !== void 0 && workspaceOwnerSegment(context.principal) === owner) return void 0;
|
|
150
|
+
return `behavior "${behavior}" is in another user's workspace`;
|
|
151
|
+
}
|
|
152
|
+
var PROGRAM_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
153
|
+
async function stampAndValidate(program, opts, dir, caller) {
|
|
154
|
+
if (!PROGRAM_NAME.test(program.name)) {
|
|
155
|
+
return { ok: false, errors: [{ code: "PROGRAM_NAME_INVALID", message: `program name "${program.name}" is not a file name` }] };
|
|
156
|
+
}
|
|
157
|
+
const input = join(dir, `${program.name}.orb`);
|
|
158
|
+
const stamped = join(dir, `${program.name}.stamped.orb`);
|
|
159
|
+
writeFileSync(input, JSON.stringify(program));
|
|
160
|
+
const stampArgs = ["stamp", input, "-o", stamped];
|
|
161
|
+
if (opts.prior !== void 0) {
|
|
162
|
+
const priorPath = join(dir, "prior.orb");
|
|
163
|
+
writeFileSync(priorPath, JSON.stringify(opts.prior));
|
|
164
|
+
stampArgs.push("--prior", priorPath);
|
|
165
|
+
}
|
|
166
|
+
const stamp = await runOrb(stampArgs, opts);
|
|
167
|
+
if (stamp.code !== 0) {
|
|
168
|
+
return { ok: false, errors: [{ code: "PROGRAM_STAMP_FAILED", message: stamp.stderr.trim() }] };
|
|
169
|
+
}
|
|
170
|
+
const validateArgs = ["validate", "--json", "--deny-warnings", ...opts.final === true ? ["--final"] : [], stamped];
|
|
171
|
+
const report = decodeJson(decodeValidationJsonOutput, await runOrb(validateArgs, opts), caller);
|
|
172
|
+
const errors = (report.errors ?? []).map(issueOf);
|
|
173
|
+
const warnings = (report.warnings ?? []).map(issueOf);
|
|
174
|
+
return { ok: true, stamped, errors, warnings, valid: report.valid && errors.length === 0 && warnings.length === 0 };
|
|
175
|
+
}
|
|
176
|
+
async function programValidate(program, opts) {
|
|
177
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-validate-"));
|
|
178
|
+
try {
|
|
179
|
+
const run = await stampAndValidate(program, opts, dir, "program/validate");
|
|
180
|
+
return run.ok ? { valid: run.valid, errors: run.errors, warnings: run.warnings } : { valid: false, errors: run.errors, warnings: [] };
|
|
181
|
+
} finally {
|
|
182
|
+
rmSync(dir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function programProve(target, opts) {
|
|
186
|
+
if ("path" in target) return decodeJson(decodeProveReport, await runOrb(["prove", target.path], opts), "program/prove");
|
|
187
|
+
if (!PROGRAM_NAME.test(target.program.name)) throw new Error(`program/prove: program name "${target.program.name}" is not a file name`);
|
|
188
|
+
return withTempFile(`${target.program.name}.orb`, JSON.stringify(target.program), async (file) => decodeJson(decodeProveReport, await runOrb(["prove", file], opts), "program/prove"));
|
|
189
|
+
}
|
|
190
|
+
async function programOsGrants(target, opts) {
|
|
191
|
+
const flags = opts.final === true ? ["--final"] : [];
|
|
192
|
+
if ("path" in target) return decodeJson(decodeOsGrantsReport, await runOrb(["os-grants", target.path, ...flags], opts), "program/os-grants");
|
|
193
|
+
if (!PROGRAM_NAME.test(target.program.name)) throw new Error(`program/os-grants: program name "${target.program.name}" is not a file name`);
|
|
194
|
+
return withTempFile(`${target.program.name}.orb`, JSON.stringify(target.program), async (file) => decodeJson(decodeOsGrantsReport, await runOrb(["os-grants", file, ...flags], opts), "program/os-grants"));
|
|
195
|
+
}
|
|
196
|
+
async function programEval(program, opts) {
|
|
197
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-eval-"));
|
|
198
|
+
try {
|
|
199
|
+
const run = await stampAndValidate(program, opts, dir, "program/eval");
|
|
200
|
+
if (!run.ok) return { ok: false, errors: run.errors };
|
|
201
|
+
const stamped = run.stamped;
|
|
202
|
+
const issues = [...run.errors, ...run.warnings];
|
|
203
|
+
if (!run.valid || issues.length > 0) return { ok: false, errors: issues };
|
|
204
|
+
const written = ProgramParser.safeParse(JSON.parse(readFileSync(stamped, "utf-8")));
|
|
205
|
+
if (!written.success) throw new Error(`program/eval: stamped program does not parse: ${written.error.message}`);
|
|
206
|
+
const content = JSON.stringify(written.data, null, 2);
|
|
207
|
+
let behavior;
|
|
208
|
+
if (opts.workspace !== void 0) {
|
|
209
|
+
const host = installedWorkspaceHost();
|
|
210
|
+
if (host === null) return { ok: false, errors: [{ code: "PROGRAM_NO_WORKSPACE_HOST", message: "no workspace host is installed on this host" }] };
|
|
211
|
+
if (opts.workspace.context.principal === void 0) {
|
|
212
|
+
return { ok: false, errors: [{ code: "PROGRAM_WORKSPACE_UNAUTHORIZED", message: "a workspace eval needs a signed-in caller" }] };
|
|
213
|
+
}
|
|
214
|
+
const ws = await host.open(opts.workspace.id, opts.workspace.context);
|
|
215
|
+
const rel = relative(opts.cwd, ws.workDir);
|
|
216
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
217
|
+
return { ok: false, errors: [{ code: "PROGRAM_WORKSPACE_OUTSIDE_PROJECT", message: `workspace ${ws.workDir} is outside the project ${opts.cwd}` }] };
|
|
218
|
+
}
|
|
219
|
+
await ws.writeFile(`orbitals/${program.name}.orb`, content);
|
|
220
|
+
await ws.snapshot(`program/eval ${program.name}`);
|
|
221
|
+
behavior = `./${rel.split(sep).join("/")}/orbitals/${program.name}`;
|
|
222
|
+
} else {
|
|
223
|
+
const target = join(opts.cwd, "orbitals");
|
|
224
|
+
mkdirSync(target, { recursive: true });
|
|
225
|
+
writeFileSync(join(target, `${program.name}.orb`), content);
|
|
226
|
+
behavior = `./orbitals/${program.name}`;
|
|
227
|
+
}
|
|
228
|
+
const traits = written.data.orbitals.flatMap(
|
|
229
|
+
(orbital) => orbital.traits.flatMap((t) => typeof t === "object" && "name" in t && typeof t.name === "string" ? [{ behavior, trait: t.name }] : [])
|
|
230
|
+
);
|
|
231
|
+
return { ok: true, result: { behavior, traits, value: { behavior } } };
|
|
232
|
+
} finally {
|
|
233
|
+
rmSync(dir, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
var LAYOUT_STRATEGIES = ["sidebar", "tabs", "dashboard", "wizard-flow", "single"];
|
|
237
|
+
async function programCompose(programs, options, opts) {
|
|
238
|
+
const dir = mkdtempSync(join(tmpdir(), "almadar-compose-"));
|
|
239
|
+
try {
|
|
240
|
+
const files = programs.map((program, index) => {
|
|
241
|
+
const file = join(dir, `${index}.orb`);
|
|
242
|
+
writeFileSync(file, JSON.stringify(program));
|
|
243
|
+
return file;
|
|
244
|
+
});
|
|
245
|
+
const flags = [
|
|
246
|
+
"--app-name",
|
|
247
|
+
options.appName,
|
|
248
|
+
...options.layout !== void 0 ? ["--layout", options.layout] : [],
|
|
249
|
+
...options.theme !== void 0 ? ["--theme", options.theme] : []
|
|
250
|
+
];
|
|
251
|
+
return parseJson(ProgramParser, await runOrb(["compose", ...flags, ...files], opts), "program/compose");
|
|
252
|
+
} finally {
|
|
253
|
+
rmSync(dir, { recursive: true, force: true });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
var WHICH_LINE = /^(\/\S+\.(?:orb|lolo)) \((.+)\)$/;
|
|
257
|
+
async function behaviorFile(behavior, opts) {
|
|
258
|
+
if (behavior.startsWith("./")) {
|
|
259
|
+
const path = `${resolve(opts.cwd, behavior)}.orb`;
|
|
260
|
+
return existsSync(path) ? path : { error: `no program at ${behavior}` };
|
|
261
|
+
}
|
|
262
|
+
const run = await runOrb(["behaviors", "which", behavior], opts);
|
|
263
|
+
const line = run.stdout.trim().split("\n").pop() ?? "";
|
|
264
|
+
const match = WHICH_LINE.exec(line);
|
|
265
|
+
if (run.code !== 0 || match === null) return { error: `behavior "${behavior}" is not installed${run.stderr.trim() ? `: ${run.stderr.trim()}` : ""}` };
|
|
266
|
+
return match[1];
|
|
267
|
+
}
|
|
268
|
+
async function programResolve(path, opts) {
|
|
269
|
+
return parseJson(ProgramParser, await runOrb(["resolve", path], opts), "program/resolve");
|
|
270
|
+
}
|
|
271
|
+
function programResolveSchema(program, opts) {
|
|
272
|
+
return withProjectFile(opts.cwd, "program.orb", JSON.stringify(program), (path) => programResolve(path, opts));
|
|
273
|
+
}
|
|
274
|
+
async function behaviorResolve(value, opts) {
|
|
275
|
+
const file = await behaviorFile(value.behavior, opts);
|
|
276
|
+
if (typeof file !== "string") return file;
|
|
277
|
+
return parseJson(ProgramParser, await runOrb(["resolve", file], opts), "behavior/resolve");
|
|
278
|
+
}
|
|
279
|
+
function installedBehaviorIndex(opts) {
|
|
280
|
+
const out = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const pkg of discoverBehaviorPackages(opts.cwd)) {
|
|
282
|
+
const tier = pkg.name === "@almadar/std" ? "std" : pkg.name === "@almadar-io/behaviors" ? "io" : "unknown";
|
|
283
|
+
const catalog = parseJson(CatalogEntrySchema.array(), runOrbSync(["behaviors", "catalog", "--json", "--no-vectors", pkg.orb.prefix], opts), "behavior/dependencies");
|
|
284
|
+
for (const entry of catalog) {
|
|
285
|
+
const name = entry.behavior.slice(pkg.orb.prefix.length + 1);
|
|
286
|
+
if (!out.has(name)) out.set(name, { tier, prefix: pkg.orb.prefix, ...entry.pattern !== void 0 ? { pattern: entry.pattern } : {} });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
async function behaviorDependencies(program, opts) {
|
|
292
|
+
const index = installedBehaviorIndex(opts);
|
|
293
|
+
const orbs = /* @__PURE__ */ new Map();
|
|
294
|
+
let frontier = [...new Set(importsOf(program, () => "unknown").map((i) => i.behavior))];
|
|
295
|
+
while (frontier.length > 0) {
|
|
296
|
+
const loaded = await Promise.all(
|
|
297
|
+
frontier.map(async (name) => {
|
|
298
|
+
const entry = index.get(name);
|
|
299
|
+
return [name, entry ? await behaviorSource({ behavior: `${entry.prefix}/${name}` }, opts) : null];
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
const next = /* @__PURE__ */ new Set();
|
|
303
|
+
for (const [name, orb] of loaded) {
|
|
304
|
+
orbs.set(name, orb);
|
|
305
|
+
for (const i of orb ? importsOf(orb, () => "unknown") : []) if (!orbs.has(i.behavior)) next.add(i.behavior);
|
|
306
|
+
}
|
|
307
|
+
frontier = [...next].filter((n) => !orbs.has(n));
|
|
308
|
+
}
|
|
309
|
+
return dependencyGraphOf(program, {
|
|
310
|
+
tierOf: (n) => index.get(n)?.tier ?? "unknown",
|
|
311
|
+
orbOf: (n) => orbs.get(n) ?? null,
|
|
312
|
+
patternOf: (n) => index.get(n)?.pattern
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
var StepRequestSchema = z.object({
|
|
316
|
+
orbital: z.string(),
|
|
317
|
+
trait: z.string(),
|
|
318
|
+
from: z.string(),
|
|
319
|
+
event: z.string(),
|
|
320
|
+
payload: EventPayloadSchema,
|
|
321
|
+
entity: EventPayloadSchema.optional(),
|
|
322
|
+
config: EventPayloadSchema.optional(),
|
|
323
|
+
now: z.number().optional(),
|
|
324
|
+
settleMs: z.number().optional(),
|
|
325
|
+
identity: z.object({ subject: z.string(), claims: EventPayloadSchema }).optional()
|
|
326
|
+
});
|
|
327
|
+
function decodePlayedSteps(printed) {
|
|
328
|
+
if (!Array.isArray(printed)) throw new Error("program/step: expected an array of played steps");
|
|
329
|
+
return printed.map((step) => decodePlayedStep(step));
|
|
330
|
+
}
|
|
331
|
+
function decodePlayedOutput(run) {
|
|
332
|
+
if (run.code !== 0) throw new Error(`program/step: orb exited ${run.code}: ${run.stderr.trim()}`);
|
|
333
|
+
try {
|
|
334
|
+
const printed = JSON.parse(run.stdout);
|
|
335
|
+
return isStepRefusal(printed) ? decodeStepRefusal(printed) : decodePlayedSteps(printed);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
throw new Error(`program/step: unexpected orb output: ${error instanceof Error ? error.message : String(error)}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function programStep(program, requests, opts, live = false) {
|
|
341
|
+
const steps = requests.map((r) => ({ orbital: r.orbital, 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.identity !== void 0 ? { identity: r.identity } : {} }));
|
|
342
|
+
return withTempFile("program.orb", JSON.stringify(program), async (path) => {
|
|
343
|
+
const run = await runOrb(["execute", path, "--steps", JSON.stringify(steps), "--played", ...live ? ["--live"] : []], opts);
|
|
344
|
+
return decodePlayedOutput(run);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function fail(error) {
|
|
348
|
+
return { ok: false, error };
|
|
349
|
+
}
|
|
350
|
+
function issuesOf(error) {
|
|
351
|
+
return error.issues.map((i) => i.path.length > 0 ? `${i.path.map(String).join(".")}: ${i.message}` : i.message).join("; ");
|
|
352
|
+
}
|
|
353
|
+
function programArg(op, value) {
|
|
354
|
+
const parsed = ProgramParser.safeParse(value);
|
|
355
|
+
return parsed.success ? parsed.data : `${op}: not a program: ${parsed.error.message}`;
|
|
356
|
+
}
|
|
357
|
+
async function runProgramEffect(op, args, opts, context) {
|
|
358
|
+
log.debug("effect", { op, args: JSON.stringify(args) });
|
|
359
|
+
try {
|
|
360
|
+
switch (op) {
|
|
361
|
+
case "behavior/catalog": {
|
|
362
|
+
const scope = CatalogScopeSchema.safeParse(args[0]);
|
|
363
|
+
if (!scope.success) return fail(`behavior/catalog: ${issuesOf(scope.error)}`);
|
|
364
|
+
const options = booleanOptions(op, args[1], ["traits"]);
|
|
365
|
+
if (typeof options === "string") return fail(options);
|
|
366
|
+
return { ok: true, result: await behaviorCatalog(scope.data, opts, options.traits === void 0 ? {} : { traits: options.traits }) };
|
|
367
|
+
}
|
|
368
|
+
case "behavior/describe": {
|
|
369
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
370
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
371
|
+
const none = booleanOptions(op, args[1], []);
|
|
372
|
+
if (typeof none === "string") return fail(none);
|
|
373
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
374
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
375
|
+
return { ok: true, result: await behaviorDescribe(value.data, opts) };
|
|
376
|
+
}
|
|
377
|
+
case "behavior/source": {
|
|
378
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
379
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
380
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
381
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
382
|
+
return { ok: true, result: await behaviorSource(value.data, opts) };
|
|
383
|
+
}
|
|
384
|
+
case "behavior/resolve": {
|
|
385
|
+
const value = DescribeTargetSchema.safeParse(args[0]);
|
|
386
|
+
if (!value.success) return fail(`${op}: ${issuesOf(value.error)}`);
|
|
387
|
+
const denied = workspaceAccessError(value.data.behavior, opts, context);
|
|
388
|
+
if (denied !== void 0) return fail(`${op}: ${denied}`);
|
|
389
|
+
const resolved = await behaviorResolve(value.data, opts);
|
|
390
|
+
return "error" in resolved ? fail(`${op}: ${resolved.error}`) : { ok: true, result: resolved };
|
|
391
|
+
}
|
|
392
|
+
case "program/step": {
|
|
393
|
+
const program = programArg(op, args[0]);
|
|
394
|
+
if (typeof program === "string") return fail(program);
|
|
395
|
+
const requests = z.array(StepRequestSchema).safeParse(args[1]);
|
|
396
|
+
if (!requests.success) return fail(`${op}: ${issuesOf(requests.error)}`);
|
|
397
|
+
const options = args[2];
|
|
398
|
+
const live = options !== null && typeof options === "object" && !Array.isArray(options) ? options["live"] : void 0;
|
|
399
|
+
if (live !== void 0 && live !== null && typeof live !== "boolean") return fail(`${op}: options.live must be a boolean`);
|
|
400
|
+
const played = await programStep(program, requests.data, opts, live === true);
|
|
401
|
+
return "error" in played ? fail(`${op}: ${played.error}`) : { ok: true, result: played };
|
|
402
|
+
}
|
|
403
|
+
case "behavior/dependencies": {
|
|
404
|
+
const program = programArg(op, args[0]);
|
|
405
|
+
return typeof program === "string" ? fail(program) : { ok: true, result: await behaviorDependencies(program, opts) };
|
|
406
|
+
}
|
|
407
|
+
case "program/read": {
|
|
408
|
+
if (typeof args[0] !== "string") return fail("program/read: expected .lolo text");
|
|
409
|
+
const options = args[1];
|
|
410
|
+
if (options !== null && typeof options === "object" && !Array.isArray(options) && options.inspect === true) return programInspectOutcome(args[0], options, opts);
|
|
411
|
+
return programReadOutcome(args[0], opts);
|
|
412
|
+
}
|
|
413
|
+
case "program/print": {
|
|
414
|
+
const program = programArg(op, args[0]);
|
|
415
|
+
return typeof program === "string" ? fail(program) : { ok: true, result: await programPrint(program, opts) };
|
|
416
|
+
}
|
|
417
|
+
case "program/compose": {
|
|
418
|
+
if (!Array.isArray(args[0])) return fail("program/compose: expected a list of programs");
|
|
419
|
+
const programs = [];
|
|
420
|
+
for (const raw of args[0]) {
|
|
421
|
+
const program = programArg(op, raw);
|
|
422
|
+
if (typeof program === "string") return fail(program);
|
|
423
|
+
programs.push(program);
|
|
424
|
+
}
|
|
425
|
+
const options = args[1];
|
|
426
|
+
if (options === null || options === void 0 || typeof options !== "object" || Array.isArray(options)) {
|
|
427
|
+
return fail("program/compose: options must be an object with an appName");
|
|
428
|
+
}
|
|
429
|
+
let appName;
|
|
430
|
+
let layout;
|
|
431
|
+
let theme;
|
|
432
|
+
for (const [key, value] of Object.entries(options)) {
|
|
433
|
+
if (key === "appName" && typeof value === "string" && value.length > 0) appName = value;
|
|
434
|
+
else if (key === "theme" && typeof value === "string") theme = value.length > 0 ? value : void 0;
|
|
435
|
+
else if (key === "layout") {
|
|
436
|
+
const match = LAYOUT_STRATEGIES.find((l) => l === value);
|
|
437
|
+
if (match === void 0) return fail(`program/compose: layout must be one of ${LAYOUT_STRATEGIES.join(", ")}`);
|
|
438
|
+
layout = match;
|
|
439
|
+
} else return fail(`program/compose: unsupported option "${key}"`);
|
|
440
|
+
}
|
|
441
|
+
if (appName === void 0) return fail("program/compose: options must name the app (appName)");
|
|
442
|
+
return { ok: true, result: await programCompose(programs, { appName, ...layout !== void 0 ? { layout } : {}, ...theme !== void 0 ? { theme } : {} }, opts) };
|
|
443
|
+
}
|
|
444
|
+
case "program/validate": {
|
|
445
|
+
const program = programArg(op, args[0]);
|
|
446
|
+
if (typeof program === "string") return fail(program);
|
|
447
|
+
const options = args[1] ?? {};
|
|
448
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/validate: options must be an object");
|
|
449
|
+
let prior;
|
|
450
|
+
let final;
|
|
451
|
+
for (const [key, value] of Object.entries(options)) {
|
|
452
|
+
if (key === "final") {
|
|
453
|
+
if (typeof value !== "boolean") return fail("program/validate: final must be a boolean");
|
|
454
|
+
final = value;
|
|
455
|
+
} else if (key === "prior") {
|
|
456
|
+
const p = programArg("program/validate prior", value);
|
|
457
|
+
if (typeof p === "string") return fail(p);
|
|
458
|
+
prior = p;
|
|
459
|
+
} else return fail(`program/validate: unsupported option "${key}"`);
|
|
460
|
+
}
|
|
461
|
+
return { ok: true, result: await programValidate(program, { ...opts, ...prior !== void 0 ? { prior } : {}, ...final !== void 0 ? { final } : {} }) };
|
|
462
|
+
}
|
|
463
|
+
case "program/prove": {
|
|
464
|
+
const options = args[1] ?? {};
|
|
465
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/prove: options must be an object");
|
|
466
|
+
for (const key of Object.keys(options)) if (key !== "path") return fail(`program/prove: unsupported option "${key}"`);
|
|
467
|
+
const path = options["path"];
|
|
468
|
+
if (path !== void 0) {
|
|
469
|
+
if (typeof path !== "string" || !isAbsolute(path)) return fail("program/prove: path must be an absolute path");
|
|
470
|
+
return { ok: true, result: await programProve({ path }, opts) };
|
|
471
|
+
}
|
|
472
|
+
const program = programArg(op, args[0]);
|
|
473
|
+
if (typeof program === "string") return fail(program);
|
|
474
|
+
return { ok: true, result: await programProve({ program }, opts) };
|
|
475
|
+
}
|
|
476
|
+
case "program/os-grants": {
|
|
477
|
+
const options = args[1] ?? {};
|
|
478
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/os-grants: options must be an object");
|
|
479
|
+
for (const key of Object.keys(options)) if (key !== "path" && key !== "final") return fail(`program/os-grants: unsupported option "${key}"`);
|
|
480
|
+
const final = options["final"];
|
|
481
|
+
if (final !== void 0 && typeof final !== "boolean") return fail("program/os-grants: final must be a boolean");
|
|
482
|
+
const pass = final === true ? { final } : {};
|
|
483
|
+
const path = options["path"];
|
|
484
|
+
if (path !== void 0) {
|
|
485
|
+
if (typeof path !== "string" || !isAbsolute(path)) return fail("program/os-grants: path must be an absolute path");
|
|
486
|
+
return { ok: true, result: await programOsGrants({ path }, { ...opts, ...pass }) };
|
|
487
|
+
}
|
|
488
|
+
const program = programArg(op, args[0]);
|
|
489
|
+
if (typeof program === "string") return fail(program);
|
|
490
|
+
return { ok: true, result: await programOsGrants({ program }, { ...opts, ...pass }) };
|
|
491
|
+
}
|
|
492
|
+
case "program/eval": {
|
|
493
|
+
const program = programArg(op, args[0]);
|
|
494
|
+
if (typeof program === "string") return fail(program);
|
|
495
|
+
const options = args[1] ?? {};
|
|
496
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) return fail("program/eval: options must be an object");
|
|
497
|
+
let workspace;
|
|
498
|
+
let prior;
|
|
499
|
+
let final;
|
|
500
|
+
for (const [key, value] of Object.entries(options)) {
|
|
501
|
+
if (key === "workspace") {
|
|
502
|
+
if (typeof value !== "string" || value === "") return fail("program/eval: workspace must be a non-empty id");
|
|
503
|
+
workspace = value;
|
|
504
|
+
} else if (key === "final") {
|
|
505
|
+
if (typeof value !== "boolean") return fail("program/eval: final must be a boolean");
|
|
506
|
+
final = value;
|
|
507
|
+
} else if (key === "prior") {
|
|
508
|
+
const p = programArg("program/eval prior", value);
|
|
509
|
+
if (typeof p === "string") return fail(p);
|
|
510
|
+
prior = p;
|
|
511
|
+
} else return fail(`program/eval: unsupported option "${key}"`);
|
|
512
|
+
}
|
|
513
|
+
const outcome = await programEval(program, { ...opts, ...workspace !== void 0 ? { workspace: { id: workspace, context: context ?? {} } } : {}, ...prior !== void 0 ? { prior } : {}, ...final !== void 0 ? { final } : {} });
|
|
514
|
+
if (outcome.ok) return { ok: true, result: outcome.result };
|
|
515
|
+
return { ok: false, error: `program/eval: ${outcome.errors.length} validation issue(s): ${outcome.errors.map((e) => e.message).join("; ")}`, errors: outcome.errors };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch (error) {
|
|
519
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export { behaviorCatalog, behaviorCatalogSync, behaviorDependencies, behaviorDescribe, behaviorResolve, behaviorSource, programCompose, programEval, programOsGrants, programPrint, programProve, programRead, programResolve, programResolveSchema, programStep, programValidate, runProgramEffect, workspaceAccessError };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { workspacesRoot, kernelWorkspaceHost, workspaceBucketFromEnv } from './chunk-YLFK5OJN.js';
|
|
2
|
+
import { runProgramEffect } from './chunk-7QE32QQR.js';
|
|
3
|
+
import { installedWorkspaceHost, installWorkspaceHost } from './chunk-BWMLDDCL.js';
|
|
4
|
+
|
|
5
|
+
// src/program/createProgramHandlers.ts
|
|
6
|
+
var bucketOf = () => {
|
|
7
|
+
const bucket = workspaceBucketFromEnv(process.env);
|
|
8
|
+
return bucket === void 0 ? {} : { bucket };
|
|
9
|
+
};
|
|
10
|
+
function createProgramHandlers(host) {
|
|
11
|
+
const opts = host.orbBin === void 0 ? { cwd: host.cwd } : { cwd: host.cwd, orbBin: host.orbBin };
|
|
12
|
+
if (installedWorkspaceHost()?.root !== workspacesRoot(host.cwd)) {
|
|
13
|
+
installWorkspaceHost(kernelWorkspaceHost({ projectRoot: host.cwd, ...bucketOf(), ...host.orbBin !== void 0 ? { orb: host.orbBin } : {} }));
|
|
14
|
+
}
|
|
15
|
+
return { programEffect: (op, args, context) => runProgramEffect(op, args, opts, context) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { createProgramHandlers };
|
|
@@ -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,53 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { installKernelEvaluator } from '@almadar/core';
|
|
6
|
+
import { readLanguageBundle } from '@almadar/core/language-data';
|
|
7
|
+
import * as installedModule from '@almadar/kernel';
|
|
8
|
+
import { NativeMemoryPersistenceAdapter } from '@almadar/shell-runtime';
|
|
9
|
+
|
|
10
|
+
// src/kernel-loader.ts
|
|
11
|
+
function hasMemoryStore(kernel) {
|
|
12
|
+
return "MemoryStore" in kernel;
|
|
13
|
+
}
|
|
14
|
+
function nativeMemoryOf(kernel) {
|
|
15
|
+
if (!hasMemoryStore(kernel)) throw new Error("this kernel module exports no MemoryStore; install a kernel build that carries it");
|
|
16
|
+
return new NativeMemoryPersistenceAdapter(new kernel.MemoryStore());
|
|
17
|
+
}
|
|
18
|
+
var initialised = /* @__PURE__ */ new WeakSet();
|
|
19
|
+
function initOnce(loaded, wasm, languageFrom) {
|
|
20
|
+
if (initialised.has(loaded)) return loaded;
|
|
21
|
+
if (wasm !== void 0) loaded.initSync({ module: wasm });
|
|
22
|
+
loaded.installLanguage(JSON.stringify(readLanguageBundle(languageFrom)));
|
|
23
|
+
installKernelEvaluator(loaded);
|
|
24
|
+
initialised.add(loaded);
|
|
25
|
+
return loaded;
|
|
26
|
+
}
|
|
27
|
+
async function loadInstalledKernel(languageFrom) {
|
|
28
|
+
return initOnce(installedModule, readFileSync(createRequire(import.meta.url).resolve("@almadar/kernel/kernel.wasm")), languageFrom);
|
|
29
|
+
}
|
|
30
|
+
async function loadKernelModule(kernelDir, options) {
|
|
31
|
+
const loaded = await import(pathToFileURL(join(kernelDir, "pkg/orbital_wasm.js")).href);
|
|
32
|
+
return initOnce(loaded, initialised.has(loaded) ? void 0 : readFileSync(join(kernelDir, "pkg/orbital_wasm_bg.wasm")), options.languageFrom);
|
|
33
|
+
}
|
|
34
|
+
var NATIVE_PLATFORMS = {
|
|
35
|
+
"darwin-x64": "darwin-x64",
|
|
36
|
+
"darwin-arm64": "darwin-arm64",
|
|
37
|
+
"linux-x64": "linux-x64",
|
|
38
|
+
"linux-arm64": "linux-arm64",
|
|
39
|
+
"win32-x64": "windows-x64"
|
|
40
|
+
};
|
|
41
|
+
function resolveNativeAddon(from) {
|
|
42
|
+
const named = process.env.ALMADAR_KERNEL_NODE;
|
|
43
|
+
if (named !== void 0 && named !== "") return named;
|
|
44
|
+
const platform = NATIVE_PLATFORMS[`${process.platform}-${process.arch}`];
|
|
45
|
+
if (platform === void 0) throw new Error(`no native kernel add-on is shipped for ${process.platform}-${process.arch}`);
|
|
46
|
+
return createRequire(join(from, "resolve.js")).resolve(`@almadar/orb-${platform}/kernel.node`);
|
|
47
|
+
}
|
|
48
|
+
async function loadNativeKernelModule(addonPath, options) {
|
|
49
|
+
const loaded = createRequire(import.meta.url)(addonPath);
|
|
50
|
+
return initOnce(loaded, void 0, options.languageFrom);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { loadInstalledKernel, loadKernelModule, loadNativeKernelModule, nativeMemoryOf, resolveNativeAddon };
|