@alisio/core 0.1.0-alpha.20 → 0.1.0-alpha.22
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/agents/active.d.ts +19 -0
- package/dist/agents/active.js +41 -0
- package/dist/agents/capabilities.d.ts +26 -0
- package/dist/agents/capabilities.js +120 -0
- package/dist/agents/create-agent-skill.d.ts +11 -0
- package/dist/agents/create-agent-skill.js +60 -0
- package/dist/agents/definitions.d.ts +88 -0
- package/dist/agents/definitions.js +404 -0
- package/dist/agents/draft.d.ts +41 -0
- package/dist/agents/draft.js +133 -0
- package/dist/agents/service.d.ts +57 -0
- package/dist/agents/service.js +86 -0
- package/dist/agents/templates.d.ts +8 -0
- package/dist/agents/templates.js +102 -0
- package/dist/analysis/capabilities.d.ts +48 -0
- package/dist/analysis/capabilities.js +90 -0
- package/dist/analysis/data/csv.d.ts +46 -0
- package/dist/analysis/data/csv.js +240 -0
- package/dist/analysis/data/datasets.d.ts +132 -0
- package/dist/analysis/data/datasets.js +441 -0
- package/dist/analysis/data/describe.d.ts +27 -0
- package/dist/analysis/data/describe.js +103 -0
- package/dist/analysis/data/engine-client.d.ts +58 -0
- package/dist/analysis/data/engine-client.js +304 -0
- package/dist/analysis/data/engine-entry.d.ts +1 -0
- package/dist/analysis/data/engine-entry.js +59 -0
- package/dist/analysis/data/engine-source.d.ts +4 -0
- package/dist/analysis/data/engine-source.js +6 -0
- package/dist/analysis/data/engine-types.d.ts +86 -0
- package/dist/analysis/data/engine-types.js +7 -0
- package/dist/analysis/data/infer.d.ts +42 -0
- package/dist/analysis/data/infer.js +118 -0
- package/dist/analysis/data/ingest-worker.d.ts +17 -0
- package/dist/analysis/data/ingest-worker.js +365 -0
- package/dist/analysis/data/json.d.ts +19 -0
- package/dist/analysis/data/json.js +44 -0
- package/dist/analysis/data/query-worker.d.ts +12 -0
- package/dist/analysis/data/query-worker.js +75 -0
- package/dist/analysis/data/rows.d.ts +39 -0
- package/dist/analysis/data/rows.js +120 -0
- package/dist/analysis/data/sql-guard.d.ts +20 -0
- package/dist/analysis/data/sql-guard.js +129 -0
- package/dist/analysis/data/stats.d.ts +18 -0
- package/dist/analysis/data/stats.js +77 -0
- package/dist/analysis/install-hints.d.ts +33 -0
- package/dist/analysis/install-hints.js +144 -0
- package/dist/analysis/janitor.d.ts +60 -0
- package/dist/analysis/janitor.js +264 -0
- package/dist/analysis/jobs.d.ts +94 -0
- package/dist/analysis/jobs.js +165 -0
- package/dist/analysis/oci.d.ts +129 -0
- package/dist/analysis/oci.js +330 -0
- package/dist/analysis/python/sources.d.ts +2 -0
- package/dist/analysis/python/sources.js +10 -0
- package/dist/analysis/requirements.d.ts +32 -0
- package/dist/analysis/requirements.js +73 -0
- package/dist/analysis/rerun.d.ts +40 -0
- package/dist/analysis/rerun.js +136 -0
- package/dist/analysis/runtime-manager.d.ts +146 -0
- package/dist/analysis/runtime-manager.js +472 -0
- package/dist/analysis/status.d.ts +21 -0
- package/dist/analysis/status.js +52 -0
- package/dist/application.d.ts +48 -0
- package/dist/application.js +228 -0
- package/dist/artifacts/kinds.d.ts +35 -0
- package/dist/artifacts/kinds.js +177 -0
- package/dist/artifacts/publisher.d.ts +7 -0
- package/dist/artifacts/publisher.js +29 -0
- package/dist/artifacts/store.d.ts +119 -0
- package/dist/artifacts/store.js +581 -0
- package/dist/commands/builtins.js +13 -0
- package/dist/commands/catalog.js +13 -1
- package/dist/config.d.ts +102 -0
- package/dist/config.js +167 -6
- package/dist/core/contracts.d.ts +69 -1
- package/dist/core/runner.d.ts +34 -4
- package/dist/core/runner.js +388 -130
- package/dist/index.d.ts +30 -5
- package/dist/index.js +29 -4
- package/dist/mcp/rich.js +4 -0
- package/dist/plugins/host.js +14 -6
- package/dist/runtime/blobs.d.ts +22 -0
- package/dist/runtime/blobs.js +84 -1
- package/dist/runtime/ids.d.ts +8 -0
- package/dist/runtime/ids.js +32 -0
- package/dist/runtime/paths.d.ts +5 -0
- package/dist/runtime/paths.js +6 -0
- package/dist/runtime/process.d.ts +12 -0
- package/dist/runtime/process.js +64 -4
- package/dist/runtime/sqlite.d.ts +2 -0
- package/dist/runtime/sqlite.js +18 -0
- package/dist/runtime/store.d.ts +5 -0
- package/dist/runtime/store.js +75 -0
- package/dist/runtime/zip.d.ts +24 -0
- package/dist/runtime/zip.js +126 -0
- package/dist/sessions/children.js +4 -0
- package/dist/tools/analysis.d.ts +37 -0
- package/dist/tools/analysis.js +572 -0
- package/dist/tools/artifacts.d.ts +14 -0
- package/dist/tools/artifacts.js +143 -0
- package/dist/tools/data.d.ts +7 -0
- package/dist/tools/data.js +160 -0
- package/dist/tools/execute.js +6 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
/** @alisio/core public API: embed the Alisio agent programmatically. */
|
|
2
2
|
export * from "@alisio/sdk";
|
|
3
|
-
export { activeAgentCatalog, agentCatalogFromState, agentPickerItems, agentRunOptions, BUILTIN_AGENTS, DEFAULT_AGENT_ID, mainAgentFromRecord, resolveActiveAgent, } from "./agents/active.js";
|
|
3
|
+
export { AGENT_COMMAND_PREFIX, activeAgentCatalog, agentCatalogFromState, agentCommandDescriptors, agentCommandName, agentIdFromCommand, agentPickerItems, agentRunOptions, BUILTIN_AGENTS, DEFAULT_AGENT_ID, mainAgentFromRecord, resolveActiveAgent, } from "./agents/active.js";
|
|
4
|
+
export { agentModelCapabilities, agentModelLabels, agentModelOptions, DEFAULT_EFFORT_LEVELS, fitAgentSettings, } from "./agents/capabilities.js";
|
|
5
|
+
export { AGENT_AUTHORING_SKILL_NAMES, CREATE_AGENT_SKILL, } from "./agents/create-agent-skill.js";
|
|
6
|
+
export { AGENT_ID_PATTERN, AGENT_LIMITS, AGENT_REASONING_SUMMARIES, AGENT_TEXT_FORMATS, AGENT_VERBOSITIES, AgentNotFoundError, AgentScopeStore, agentIdFromName, agentScopeDir, parseAgentFile, RESERVED_AGENT_IDS, slugify, validateAgentDefinitionInput, } from "./agents/definitions.js";
|
|
7
|
+
export { AGENT_DRAFT_INSTRUCTIONS, AGENT_DRAFT_MAX_DESCRIPTION, agentAuthoringGuidance, agentDraftRequest, generateAgentDraft, parseAgentDraft, } from "./agents/draft.js";
|
|
8
|
+
export { AgentDefinitionService, mergeAgentScopes, } from "./agents/service.js";
|
|
9
|
+
export { AGENT_TEMPLATES, agentTemplate } from "./agents/templates.js";
|
|
10
|
+
export { CapabilityGrants, toGrantWire, } from "./analysis/capabilities.js";
|
|
11
|
+
export { CsvParser, decodeTextBytes, detectDelimiter } from "./analysis/data/csv.js";
|
|
12
|
+
export { DatasetService, DEFAULT_DATASET_LIMITS, datasetFormat, PageParamError, toDatasetRef, XLSX_REMEDY, } from "./analysis/data/datasets.js";
|
|
13
|
+
export { DataEngine, DataError, } from "./analysis/data/engine-client.js";
|
|
14
|
+
export { guardSql } from "./analysis/data/sql-guard.js";
|
|
15
|
+
export { detectHintHost, installHintsMarkdown, installHintsText, parseOsRelease, pythonInstallHints, } from "./analysis/install-hints.js";
|
|
16
|
+
export { AnalysisJanitor, SWEEP_INTERVAL_MS, } from "./analysis/janitor.js";
|
|
17
|
+
export { AnalysisJobs, } from "./analysis/jobs.js";
|
|
18
|
+
export { containerName, OciRuntime, ociRunArgs, parseDigests, } from "./analysis/oci.js";
|
|
19
|
+
export { AnalysisRerun, } from "./analysis/rerun.js";
|
|
20
|
+
export { AnalysisRuntimeManager, ExtrasInstallError, installPreview, setupCommand, venvInterpreter, } from "./analysis/runtime-manager.js";
|
|
21
|
+
export { analysisStatus } from "./analysis/status.js";
|
|
4
22
|
export { createApplication, enabledBuiltins, } from "./application.js";
|
|
23
|
+
export { classifyArtifact, extensionOf, fileType as artifactFileType, isPreviewable, KIND_LABELS, viewerContentType, } from "./artifacts/kinds.js";
|
|
24
|
+
export { createArtifactPublisher } from "./artifacts/publisher.js";
|
|
25
|
+
export { ArtifactRejected, ArtifactStore, readHead as readArtifactHead, slugify as artifactSlug, toRef, } from "./artifacts/store.js";
|
|
5
26
|
export { BUILTIN_COMMANDS, CommandCatalog, } from "./commands/catalog.js";
|
|
6
|
-
export { configFile, configHome, configSchema, isSettableSettingKey, loadConfig, loadConfigWithProvenance, overridesSavedProviderProfile, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, settableSettings, stateHome, } from "./config.js";
|
|
27
|
+
export { configFile, configHome, configSchema, isSettableSettingKey, loadConfig, loadConfigWithProvenance, OCI_IMAGE_PATTERN, overridesSavedProviderProfile, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, settableSettings, stateHome, } from "./config.js";
|
|
7
28
|
export { checkpointInstructions, completeText, effectiveContextBudget, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, renderCheckpoint, serializeForSummary, shouldCompact, shouldCompactContext, summarize, } from "./core/compaction.js";
|
|
8
29
|
export { ToolRegistry } from "./core/registry.js";
|
|
9
30
|
export { AgentRunner, defaultTokenBudget, } from "./core/runner.js";
|
|
@@ -19,16 +40,20 @@ export { ProjectContext, } from "./resources/context.js";
|
|
|
19
40
|
export { expandSlashPrompt, loadPromptTemplates, parsePromptTemplate, promptSources, renderPromptTemplate, splitArguments, } from "./resources/prompts.js";
|
|
20
41
|
export { Skills, skillRoots, } from "./resources/skills.js";
|
|
21
42
|
export { PathAccess, } from "./runtime/access.js";
|
|
22
|
-
export { BlobStore } from "./runtime/blobs.js";
|
|
43
|
+
export { BlobStore, BlobTooLarge } from "./runtime/blobs.js";
|
|
23
44
|
export { clipLines, unifiedPatch } from "./runtime/diff.js";
|
|
24
45
|
export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.js";
|
|
46
|
+
export { newId, ulid } from "./runtime/ids.js";
|
|
25
47
|
export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.js";
|
|
26
|
-
export { findWorkspace, inside, outsideRootsMessage, safePath } from "./runtime/paths.js";
|
|
48
|
+
export { findWorkspace, inside, outsideRootsMessage, safePath, workspaceKey, } from "./runtime/paths.js";
|
|
27
49
|
export { isMissingCommand, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.js";
|
|
28
50
|
export { isSqliteExperimentalWarning, openDatabase } from "./runtime/sqlite.js";
|
|
29
51
|
export { SQLiteStore } from "./runtime/store.js";
|
|
52
|
+
export { crc32, writeZip } from "./runtime/zip.js";
|
|
30
53
|
export { ChildSessions } from "./sessions/children.js";
|
|
31
54
|
export { formatSideQuestion, SIDE_QUESTION_DESCRIPTION, SIDE_QUESTION_HISTORY_LIMIT, SIDE_QUESTION_INSTRUCTIONS, SIDE_QUESTION_MAX_CHARS, SIDE_QUESTION_USAGE, SIDE_QUESTIONS_NAMESPACE, SideQuestions, sideTranscript, } from "./sessions/side-questions.js";
|
|
32
55
|
export { composeSideBySide, DefaultAlisioMascot, DefaultStartupScreen, infoSection, mascotSection, padEnd, pluginsSection, renderStartup, sanitizeLine, shortenPath, startupTips, stripAnsi, tipsSection, titleSection, truncate, visibleWidth, welcomeSection, } from "./startup/index.js";
|
|
56
|
+
export { formatBytes, NOT_SANDBOXED } from "./tools/analysis.js";
|
|
57
|
+
export { ARTIFACT_READ_MAX, exportedPaths } from "./tools/artifacts.js";
|
|
33
58
|
export { hash, objectSchema, registerStandard } from "./tools/standard.js";
|
|
34
59
|
export { getTrust, hashProjectConfig, hasProjectResources, listTrust, resolveTrust, revokeTrust, setTrust, trustStorePath, } from "./trust.js";
|
package/dist/mcp/rich.js
CHANGED
|
@@ -227,6 +227,10 @@ export function renderUiBlockText(block) {
|
|
|
227
227
|
block.title ? `[progress: ${block.title}]` : "[progress]",
|
|
228
228
|
...block.steps.map((step) => `- [${step.status}] ${step.label}${step.detail ? ` (${step.detail})` : ""}`),
|
|
229
229
|
].join("\n");
|
|
230
|
+
case "artifact": {
|
|
231
|
+
const a = block.artifact;
|
|
232
|
+
return `[artifact: ${a.fileName} · ${a.kind} · ${a.bytes} bytes · ${a.id}${a.partial ? " · partial" : ""}${a.status !== "ready" ? ` · ${a.status}` : ""}]`;
|
|
233
|
+
}
|
|
230
234
|
default: {
|
|
231
235
|
// A kind this build does not know (e.g. persisted by a newer Alisio): keep it readable.
|
|
232
236
|
const kind = String(block.kind ?? "unknown");
|
package/dist/plugins/host.js
CHANGED
|
@@ -203,12 +203,20 @@ export class PluginHost {
|
|
|
203
203
|
register: (tool) => {
|
|
204
204
|
const registered = builtin
|
|
205
205
|
? tool
|
|
206
|
-
: {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
: (() => {
|
|
207
|
+
// Only built-ins may claim the always-allowed internal effect, a capability
|
|
208
|
+
// (finer than the effect) or the artifact publisher (decision D12, v1).
|
|
209
|
+
const { capability: _capability, ...rest } = tool;
|
|
210
|
+
return {
|
|
211
|
+
...rest,
|
|
212
|
+
name: `${pluginPrefix(plugin.id)}_${tool.name}`,
|
|
213
|
+
...(tool.effect === "internal" ? { effect: "external" } : {}),
|
|
214
|
+
execute: (input, context) => {
|
|
215
|
+
const { artifacts: _artifacts, approveInstall: _install, ...narrowed } = context;
|
|
216
|
+
return tool.execute(input, narrowed);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
})();
|
|
212
220
|
const unregister = this.registry.register(registered);
|
|
213
221
|
this.toolOwners.set(registered.name, plugin.id);
|
|
214
222
|
return track(() => {
|
package/dist/runtime/blobs.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Attachment, BlobRef, SqlDatabase } from "@alisio/sdk";
|
|
2
|
+
/** A streamed blob exceeded its size limit. */
|
|
3
|
+
export declare class BlobTooLarge extends Error {
|
|
4
|
+
readonly limit: number;
|
|
5
|
+
constructor(limit: number);
|
|
6
|
+
}
|
|
2
7
|
export interface BlobStoreOptions {
|
|
3
8
|
/** Directory that holds `sha256/…` (by default `<state home>/blobs`). */
|
|
4
9
|
root: string;
|
|
@@ -17,6 +22,23 @@ export declare class BlobStore {
|
|
|
17
22
|
width?: number;
|
|
18
23
|
height?: number;
|
|
19
24
|
}): BlobRef;
|
|
25
|
+
/**
|
|
26
|
+
* Stores a byte stream (an upload too large to hold in memory, e.g. a dataset) once per content
|
|
27
|
+
* hash, hashing while it writes. Fails with `BlobTooLarge` as soon as `maxBytes` is exceeded and
|
|
28
|
+
* leaves nothing behind. The stored type is the declared one; images stay the only blobs that
|
|
29
|
+
* `attachment()` and the blob route serve.
|
|
30
|
+
*/
|
|
31
|
+
putStream(stream: AsyncIterable<Uint8Array>, mimeType: string, options: {
|
|
32
|
+
maxBytes: number;
|
|
33
|
+
}): Promise<BlobRef>;
|
|
34
|
+
/**
|
|
35
|
+
* Retention: deletes the bytes and the row of a blob that is not an image (an uploaded dataset
|
|
36
|
+
* original). Images are referenced by messages and are never removed here. Returns the freed
|
|
37
|
+
* bytes (0 when the blob is unknown, an image or already gone).
|
|
38
|
+
*/
|
|
39
|
+
removeUpload(hash: string): number;
|
|
40
|
+
/** Hashes of non-image blobs created before `cutoff` (epoch ms): uploaded dataset originals. */
|
|
41
|
+
uploadsBefore(cutoff: number): string[];
|
|
20
42
|
get(hash: string): {
|
|
21
43
|
bytes: Buffer;
|
|
22
44
|
mimeType: string;
|
package/dist/runtime/blobs.js
CHANGED
|
@@ -8,9 +8,18 @@
|
|
|
8
8
|
* Persisted messages therefore still carry base64 (as legacy inline attachments do).
|
|
9
9
|
*/
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
11
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { pipeline } from "node:stream/promises";
|
|
13
14
|
const HASH = /^[0-9a-f]{64}$/;
|
|
15
|
+
/** A streamed blob exceeded its size limit. */
|
|
16
|
+
export class BlobTooLarge extends Error {
|
|
17
|
+
limit;
|
|
18
|
+
constructor(limit) {
|
|
19
|
+
super(`The upload exceeds ${limit} bytes`);
|
|
20
|
+
this.limit = limit;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
export class BlobStore {
|
|
15
24
|
options;
|
|
16
25
|
constructor(options) {
|
|
@@ -61,6 +70,80 @@ export class BlobStore {
|
|
|
61
70
|
...(row?.height != null ? { height: Number(row.height) } : {}),
|
|
62
71
|
};
|
|
63
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Stores a byte stream (an upload too large to hold in memory, e.g. a dataset) once per content
|
|
75
|
+
* hash, hashing while it writes. Fails with `BlobTooLarge` as soon as `maxBytes` is exceeded and
|
|
76
|
+
* leaves nothing behind. The stored type is the declared one; images stay the only blobs that
|
|
77
|
+
* `attachment()` and the blob route serve.
|
|
78
|
+
*/
|
|
79
|
+
async putStream(stream, mimeType, options) {
|
|
80
|
+
const mime = mimeType.trim();
|
|
81
|
+
if (!mime || mime.length > 255)
|
|
82
|
+
throw new Error("A blob needs a MIME type");
|
|
83
|
+
const dir = join(this.options.root, "sha256");
|
|
84
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
85
|
+
const temp = join(dir, `.tmp-${randomUUID()}`);
|
|
86
|
+
const hash = createHash("sha256");
|
|
87
|
+
let size = 0;
|
|
88
|
+
try {
|
|
89
|
+
await pipeline(stream, async function* (source) {
|
|
90
|
+
for await (const chunk of source) {
|
|
91
|
+
size += chunk.byteLength;
|
|
92
|
+
if (size > options.maxBytes)
|
|
93
|
+
throw new BlobTooLarge(options.maxBytes);
|
|
94
|
+
hash.update(chunk);
|
|
95
|
+
yield chunk;
|
|
96
|
+
}
|
|
97
|
+
}, createWriteStream(temp, { mode: 0o600, flags: "wx" }));
|
|
98
|
+
const digest = hash.digest("hex");
|
|
99
|
+
const file = this.path(digest);
|
|
100
|
+
const fileDir = join(dir, digest.slice(0, 2));
|
|
101
|
+
mkdirSync(fileDir, { recursive: true, mode: 0o700 });
|
|
102
|
+
chmodSync(fileDir, 0o700);
|
|
103
|
+
if (existsSync(file))
|
|
104
|
+
rmSync(temp, { force: true });
|
|
105
|
+
else
|
|
106
|
+
renameSync(temp, file);
|
|
107
|
+
this.options.db
|
|
108
|
+
.prepare("INSERT OR IGNORE INTO blobs(hash,mime,size,width,height,created_at) VALUES(?,?,?,?,?,?)")
|
|
109
|
+
.run(digest, mime, statSync(file).size, null, null, Date.now());
|
|
110
|
+
const row = this.row(digest);
|
|
111
|
+
return { hash: digest, mimeType: row?.mime ?? mime, bytes: statSync(file).size };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
rmSync(temp, { force: true });
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Retention: deletes the bytes and the row of a blob that is not an image (an uploaded dataset
|
|
120
|
+
* original). Images are referenced by messages and are never removed here. Returns the freed
|
|
121
|
+
* bytes (0 when the blob is unknown, an image or already gone).
|
|
122
|
+
*/
|
|
123
|
+
removeUpload(hash) {
|
|
124
|
+
if (!HASH.test(hash))
|
|
125
|
+
return 0;
|
|
126
|
+
const row = this.row(hash);
|
|
127
|
+
if (row?.mime.startsWith("image/"))
|
|
128
|
+
return 0;
|
|
129
|
+
const file = this.path(hash);
|
|
130
|
+
let size = 0;
|
|
131
|
+
try {
|
|
132
|
+
size = statSync(file).size;
|
|
133
|
+
rmSync(file, { force: true });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* Already gone. */
|
|
137
|
+
}
|
|
138
|
+
this.options.db.prepare("DELETE FROM blobs WHERE hash=?").run(hash);
|
|
139
|
+
return size;
|
|
140
|
+
}
|
|
141
|
+
/** Hashes of non-image blobs created before `cutoff` (epoch ms): uploaded dataset originals. */
|
|
142
|
+
uploadsBefore(cutoff) {
|
|
143
|
+
return this.options.db
|
|
144
|
+
.prepare("SELECT hash FROM blobs WHERE mime NOT LIKE 'image/%' AND created_at<?")
|
|
145
|
+
.all(cutoff).map((r) => r.hash);
|
|
146
|
+
}
|
|
64
147
|
get(hash) {
|
|
65
148
|
if (!HASH.test(hash))
|
|
66
149
|
return undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A ULID (26 characters, lexicographically sortable by creation time): 48 bits of milliseconds
|
|
3
|
+
* and 80 random bits. No dependency and no monotonic counter; two ids created in the same
|
|
4
|
+
* millisecond sort arbitrarily, which is fine for row ids and folder names.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ulid(now?: number): string;
|
|
7
|
+
/** A prefixed id such as `art_01J…` or `exec_01J…`. */
|
|
8
|
+
export declare const newId: (prefix: string, now?: number) => string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/** Crockford base32 (no I, L, O, U), the ULID alphabet. */
|
|
3
|
+
const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
4
|
+
/**
|
|
5
|
+
* A ULID (26 characters, lexicographically sortable by creation time): 48 bits of milliseconds
|
|
6
|
+
* and 80 random bits. No dependency and no monotonic counter; two ids created in the same
|
|
7
|
+
* millisecond sort arbitrarily, which is fine for row ids and folder names.
|
|
8
|
+
*/
|
|
9
|
+
export function ulid(now = Date.now()) {
|
|
10
|
+
let time = "";
|
|
11
|
+
let t = Math.max(0, Math.floor(now));
|
|
12
|
+
for (let i = 0; i < 10; i++) {
|
|
13
|
+
time = ALPHABET[t % 32] + time;
|
|
14
|
+
t = Math.floor(t / 32);
|
|
15
|
+
}
|
|
16
|
+
const bytes = randomBytes(10);
|
|
17
|
+
let random = "";
|
|
18
|
+
// 80 bits → 16 characters of 5 bits each.
|
|
19
|
+
let buffer = 0, bits = 0;
|
|
20
|
+
for (const byte of bytes) {
|
|
21
|
+
buffer = (buffer << 8) | byte;
|
|
22
|
+
bits += 8;
|
|
23
|
+
while (bits >= 5) {
|
|
24
|
+
bits -= 5;
|
|
25
|
+
random += ALPHABET[(buffer >> bits) & 31];
|
|
26
|
+
}
|
|
27
|
+
buffer &= (1 << bits) - 1;
|
|
28
|
+
}
|
|
29
|
+
return time + random;
|
|
30
|
+
}
|
|
31
|
+
/** A prefixed id such as `art_01J…` or `exec_01J…`. */
|
|
32
|
+
export const newId = (prefix, now) => `${prefix}_${ulid(now)}`;
|
package/dist/runtime/paths.d.ts
CHANGED
|
@@ -8,3 +8,8 @@ export declare function outsideRootsMessage(target: string, reason?: string): st
|
|
|
8
8
|
/** Mediated path policy; not an OS sandbox and not race-proof against hostile concurrent processes. */
|
|
9
9
|
export declare function safePath(root: string, path: string): Promise<string>;
|
|
10
10
|
export declare function findWorkspace(cwd: string): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Opaque, stable key of a workspace path: the first 16 hex characters of its sha256. Core names
|
|
13
|
+
* per-workspace folders with it and the server exposes it as the web `workspaceId`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const workspaceKey: (path: string) => string;
|
package/dist/runtime/paths.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { lstat, realpath } from "node:fs/promises";
|
|
2
3
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
4
|
export function inside(root, path) {
|
|
@@ -50,3 +51,8 @@ export async function findWorkspace(cwd) {
|
|
|
50
51
|
path = dirname(path);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Opaque, stable key of a workspace path: the first 16 hex characters of its sha256. Core names
|
|
56
|
+
* per-workspace folders with it and the server exposes it as the web `workspaceId`.
|
|
57
|
+
*/
|
|
58
|
+
export const workspaceKey = (path) => createHash("sha256").update(path).digest("hex").slice(0, 16);
|
|
@@ -22,4 +22,16 @@ export declare function runProcess(command: string, args: string[], options: {
|
|
|
22
22
|
onData?: (chunk: string) => void;
|
|
23
23
|
/** On cancellation: SIGTERM the process group, then SIGKILL after this grace (default 5s). */
|
|
24
24
|
killGraceMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* What happens when a stream exceeds `maxBytes`: `kill` (default) stops the process; `truncate`
|
|
27
|
+
* lets it run and keeps only the LAST `maxBytes` of each stream (the result is `truncated`).
|
|
28
|
+
*/
|
|
29
|
+
onOverflow?: "kill" | "truncate";
|
|
30
|
+
/** Full stdout/stderr copies (up to `maxLogBytes` each), independent of `maxBytes`. */
|
|
31
|
+
logFiles?: {
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
};
|
|
35
|
+
/** Cap of each log file (default 10 MiB); a marker line notes the cut. */
|
|
36
|
+
maxLogBytes?: number;
|
|
25
37
|
}): Promise<ProcessResult>;
|
package/dist/runtime/process.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { createWriteStream } from "node:fs";
|
|
2
3
|
/**
|
|
3
4
|
* Detect a spawn failure caused by a missing executable (`ENOENT`), so tools can explain how to
|
|
4
5
|
* install the dependency instead of surfacing the raw OS error.
|
|
@@ -76,19 +77,77 @@ export async function runProcess(command, args, options) {
|
|
|
76
77
|
terminate();
|
|
77
78
|
let size = 0, truncated = false;
|
|
78
79
|
const max = options.maxBytes ?? 32_000;
|
|
79
|
-
const
|
|
80
|
+
const tailMode = options.onOverflow === "truncate";
|
|
81
|
+
const maxLog = options.maxLogBytes ?? 10 * 1024 * 1024;
|
|
82
|
+
const logs = [];
|
|
83
|
+
const logger = (path) => {
|
|
84
|
+
if (!path)
|
|
85
|
+
return undefined;
|
|
86
|
+
const out = createWriteStream(path, { flags: "w" });
|
|
87
|
+
out.on("error", () => {
|
|
88
|
+
/* A log write failure never fails the process run. */
|
|
89
|
+
});
|
|
90
|
+
logs.push(out);
|
|
91
|
+
let written = 0, cut = false;
|
|
92
|
+
return (chunk) => {
|
|
93
|
+
if (cut)
|
|
94
|
+
return;
|
|
95
|
+
const room = maxLog - written;
|
|
96
|
+
if (chunk.byteLength > room) {
|
|
97
|
+
out.write(chunk.subarray(0, Math.max(0, room)));
|
|
98
|
+
out.write(Buffer.from("\n[log truncated]\n"));
|
|
99
|
+
cut = true;
|
|
100
|
+
written = maxLog;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
out.write(chunk);
|
|
104
|
+
written += chunk.byteLength;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
const consume = (stream, logPath) => new Promise((resolveText, reject) => {
|
|
80
108
|
const decoder = new TextDecoder();
|
|
109
|
+
const log = logger(logPath);
|
|
81
110
|
let text = "", done = false;
|
|
111
|
+
// Tail mode keeps raw buffers and decodes once at the end (a cut may split a character).
|
|
112
|
+
const tail = [];
|
|
113
|
+
let tailBytes = 0;
|
|
82
114
|
const finish = () => {
|
|
83
115
|
if (done)
|
|
84
116
|
return;
|
|
85
117
|
done = true;
|
|
86
|
-
|
|
118
|
+
if (tailMode) {
|
|
119
|
+
const joined = Buffer.concat(tail);
|
|
120
|
+
text = new TextDecoder().decode(joined).replace(/^\uFFFD+/, "");
|
|
121
|
+
}
|
|
122
|
+
else
|
|
123
|
+
text += decoder.decode();
|
|
87
124
|
resolveText(text);
|
|
88
125
|
};
|
|
89
126
|
stream.on("data", (value) => {
|
|
90
127
|
if (done)
|
|
91
128
|
return;
|
|
129
|
+
log?.(value);
|
|
130
|
+
if (tailMode) {
|
|
131
|
+
const live = decoder.decode(value, { stream: true });
|
|
132
|
+
if (live)
|
|
133
|
+
options.onData?.(live);
|
|
134
|
+
tail.push(value);
|
|
135
|
+
tailBytes += value.byteLength;
|
|
136
|
+
while (tailBytes > max && tail.length) {
|
|
137
|
+
const first = tail[0];
|
|
138
|
+
const excess = tailBytes - max;
|
|
139
|
+
if (first.byteLength <= excess) {
|
|
140
|
+
tail.shift();
|
|
141
|
+
tailBytes -= first.byteLength;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
tail[0] = first.subarray(excess);
|
|
145
|
+
tailBytes -= excess;
|
|
146
|
+
}
|
|
147
|
+
truncated = true;
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
92
151
|
const remaining = Math.max(0, max - size), slice = value.subarray(0, remaining);
|
|
93
152
|
size += slice.byteLength;
|
|
94
153
|
const chunk = decoder.decode(slice, { stream: true });
|
|
@@ -107,8 +166,8 @@ export async function runProcess(command, args, options) {
|
|
|
107
166
|
});
|
|
108
167
|
try {
|
|
109
168
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
110
|
-
consume(child.stdout),
|
|
111
|
-
consume(child.stderr),
|
|
169
|
+
consume(child.stdout, options.logFiles?.stdout),
|
|
170
|
+
consume(child.stderr, options.logFiles?.stderr),
|
|
112
171
|
exited,
|
|
113
172
|
]);
|
|
114
173
|
signal.throwIfAborted();
|
|
@@ -118,5 +177,6 @@ export async function runProcess(command, args, options) {
|
|
|
118
177
|
signal.removeEventListener("abort", terminate);
|
|
119
178
|
clearTimeout(graceTimer);
|
|
120
179
|
kill();
|
|
180
|
+
await Promise.all(logs.map((out) => new Promise((resolveLog) => out.end(() => resolveLog()))));
|
|
121
181
|
}
|
|
122
182
|
}
|
package/dist/runtime/sqlite.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { SqlDatabase } from "@alisio/sdk";
|
|
2
2
|
export declare function isSqliteExperimentalWarning(message: string, type?: string): boolean;
|
|
3
|
+
/** Opens an existing database file read-only (no directory or file is created). */
|
|
4
|
+
export declare function openReadOnlyDatabase(path: string): SqlDatabase;
|
|
3
5
|
export declare function openDatabase(path: string): SqlDatabase;
|
package/dist/runtime/sqlite.js
CHANGED
|
@@ -34,6 +34,24 @@ function sqlite() {
|
|
|
34
34
|
return module;
|
|
35
35
|
}
|
|
36
36
|
const plain = (row) => row === undefined || row === null ? undefined : { ...row };
|
|
37
|
+
/** Opens an existing database file read-only (no directory or file is created). */
|
|
38
|
+
export function openReadOnlyDatabase(path) {
|
|
39
|
+
const db = new (sqlite().DatabaseSync)(path, { readOnly: true });
|
|
40
|
+
db.exec("PRAGMA query_only=ON;");
|
|
41
|
+
return {
|
|
42
|
+
exec: (sql) => db.exec(sql),
|
|
43
|
+
prepare(sql) {
|
|
44
|
+
const raw = db.prepare(sql);
|
|
45
|
+
return {
|
|
46
|
+
run: (...params) => raw.run(...params),
|
|
47
|
+
get: (...params) => plain(raw.get(...params)),
|
|
48
|
+
all: (...params) => raw.all(...params).map((r) => ({ ...r })),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
transaction: (fn) => fn(),
|
|
52
|
+
close: () => db.close(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
37
55
|
export function openDatabase(path) {
|
|
38
56
|
const memory = path === ":memory:";
|
|
39
57
|
const fresh = !memory && !existsSync(path);
|
package/dist/runtime/store.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export declare class SQLiteStore implements SessionStore {
|
|
|
5
5
|
constructor(path: string);
|
|
6
6
|
/** Forward-only, idempotent migrations. Existing databases keep all rows. */
|
|
7
7
|
private migrate;
|
|
8
|
+
/**
|
|
9
|
+
* The root of a session (itself for roots), following `parent_id`. Unknown ids are their own
|
|
10
|
+
* root, so callers never throw on a session another process has not written yet.
|
|
11
|
+
*/
|
|
12
|
+
rootOf(id: string): string;
|
|
8
13
|
create(workspace: string, provider: string, model: string): Session;
|
|
9
14
|
private row;
|
|
10
15
|
get(id: string): Session;
|
package/dist/runtime/store.js
CHANGED
|
@@ -112,6 +112,81 @@ export class SQLiteStore {
|
|
|
112
112
|
this.db.exec("ALTER TABLE workspaces ADD COLUMN archived_at INTEGER");
|
|
113
113
|
this.db.exec("INSERT OR IGNORE INTO schema_migrations VALUES(5)");
|
|
114
114
|
});
|
|
115
|
+
if (!has(6))
|
|
116
|
+
this.db.transaction(() => {
|
|
117
|
+
// v6 (additive): Python analysis executions, published artifacts, persisted capability
|
|
118
|
+
// grants (audit included) and tabular datasets (used from the data phase on).
|
|
119
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS analysis_executions(
|
|
120
|
+
id TEXT PRIMARY KEY,
|
|
121
|
+
session TEXT NOT NULL,
|
|
122
|
+
root_session TEXT NOT NULL,
|
|
123
|
+
workspace TEXT NOT NULL,
|
|
124
|
+
run_id TEXT, call_id TEXT,
|
|
125
|
+
runtime TEXT NOT NULL CHECK(runtime IN ('managed','oci')),
|
|
126
|
+
status TEXT NOT NULL CHECK(status IN ('running','completed','failed','cancelled','timed_out')),
|
|
127
|
+
script_sha256 TEXT NOT NULL,
|
|
128
|
+
exit_code INTEGER, error TEXT,
|
|
129
|
+
rel_dir TEXT NOT NULL,
|
|
130
|
+
rerun_of TEXT,
|
|
131
|
+
created_at INTEGER NOT NULL, ended_at INTEGER);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS analysis_executions_session
|
|
133
|
+
ON analysis_executions(root_session, created_at);
|
|
134
|
+
CREATE TABLE IF NOT EXISTS artifacts(
|
|
135
|
+
id TEXT PRIMARY KEY,
|
|
136
|
+
session TEXT NOT NULL, root_session TEXT NOT NULL, workspace TEXT NOT NULL,
|
|
137
|
+
run_id TEXT, call_id TEXT,
|
|
138
|
+
execution_id TEXT REFERENCES analysis_executions(id),
|
|
139
|
+
source_path TEXT,
|
|
140
|
+
title TEXT NOT NULL, file_name TEXT NOT NULL,
|
|
141
|
+
kind TEXT NOT NULL, mime TEXT NOT NULL,
|
|
142
|
+
bytes INTEGER NOT NULL, file_count INTEGER NOT NULL DEFAULT 1,
|
|
143
|
+
sha256 TEXT NOT NULL, entry TEXT,
|
|
144
|
+
rel_dir TEXT NOT NULL,
|
|
145
|
+
status TEXT NOT NULL CHECK(status IN ('ready','deleted','expired')),
|
|
146
|
+
provenance TEXT NOT NULL,
|
|
147
|
+
created_at INTEGER NOT NULL, deleted_at INTEGER,
|
|
148
|
+
UNIQUE(execution_id, source_path));
|
|
149
|
+
CREATE INDEX IF NOT EXISTS artifacts_session ON artifacts(root_session, created_at);
|
|
150
|
+
CREATE TABLE IF NOT EXISTS capability_grants(
|
|
151
|
+
id TEXT PRIMARY KEY,
|
|
152
|
+
capability TEXT NOT NULL,
|
|
153
|
+
session TEXT NOT NULL,
|
|
154
|
+
workspace TEXT NOT NULL,
|
|
155
|
+
scope TEXT NOT NULL CHECK(scope IN ('once','session')),
|
|
156
|
+
decision TEXT NOT NULL CHECK(decision IN ('allow','deny')),
|
|
157
|
+
source TEXT NOT NULL CHECK(source IN ('tui','web','flag','headless-grant')),
|
|
158
|
+
call_id TEXT, run_id TEXT, correlation_id TEXT,
|
|
159
|
+
created_at INTEGER NOT NULL,
|
|
160
|
+
revoked_at INTEGER, revoked_by TEXT);
|
|
161
|
+
CREATE INDEX IF NOT EXISTS capability_grants_lookup
|
|
162
|
+
ON capability_grants(capability, session, scope, decision, revoked_at);
|
|
163
|
+
CREATE TABLE IF NOT EXISTS datasets(
|
|
164
|
+
id TEXT PRIMARY KEY,
|
|
165
|
+
session TEXT NOT NULL, root_session TEXT NOT NULL,
|
|
166
|
+
name TEXT NOT NULL, format TEXT NOT NULL,
|
|
167
|
+
sha256 TEXT NOT NULL, bytes INTEGER NOT NULL,
|
|
168
|
+
blob_hash TEXT,
|
|
169
|
+
source_path TEXT,
|
|
170
|
+
db_rel_path TEXT NOT NULL,
|
|
171
|
+
ingest_version INTEGER NOT NULL,
|
|
172
|
+
sheets TEXT NOT NULL,
|
|
173
|
+
created_at INTEGER NOT NULL);
|
|
174
|
+
INSERT OR IGNORE INTO schema_migrations VALUES(6);`);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The root of a session (itself for roots), following `parent_id`. Unknown ids are their own
|
|
179
|
+
* root, so callers never throw on a session another process has not written yet.
|
|
180
|
+
*/
|
|
181
|
+
rootOf(id) {
|
|
182
|
+
let current = id;
|
|
183
|
+
for (let depth = 0; depth < 32; depth++) {
|
|
184
|
+
const row = this.db.prepare("SELECT parent_id FROM sessions WHERE id=?").get(current);
|
|
185
|
+
if (!row?.parent_id)
|
|
186
|
+
return current;
|
|
187
|
+
current = row.parent_id;
|
|
188
|
+
}
|
|
189
|
+
return current;
|
|
115
190
|
}
|
|
116
191
|
create(workspace, provider, model) {
|
|
117
192
|
const id = crypto.randomUUID();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ZipEntry = {
|
|
2
|
+
name: string;
|
|
3
|
+
source: string;
|
|
4
|
+
} | {
|
|
5
|
+
name: string;
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
/** CRC-32 (IEEE) of `bytes`, continuing from `crc`. */
|
|
9
|
+
export declare function crc32(bytes: Uint8Array, crc?: number): number;
|
|
10
|
+
/** Something to write the archive to (an HTTP response, a file stream…), honoring backpressure. */
|
|
11
|
+
export interface ZipSink {
|
|
12
|
+
write(chunk: Buffer): boolean;
|
|
13
|
+
once(event: "drain", listener: () => void): unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Writes `entries` as a ZIP archive to `out`, streaming file contents. Entry names use `/` and
|
|
17
|
+
* must be relative; the caller validates them. Throws when the archive would need ZIP64.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeZip(entries: ZipEntry[], out: ZipSink, options?: {
|
|
20
|
+
now?: Date;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
bytes: number;
|
|
24
|
+
}>;
|