@deepexi/datasense-cli 2.0.4 → 3.0.1
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/README.md +88 -88
- package/dist/connector-child.cjs +350 -0
- package/dist/datasense-connector-3.0.1.tgz +0 -0
- package/dist/ontos.js +11 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
# ontos
|
|
2
|
-
|
|
3
|
-
Ontos 平台命令行工具。npm 包名为 `@deepexi/datasense-cli`,终端命令为 `ontos`。
|
|
4
|
-
|
|
5
|
-
需要 Node.js ≥22.12.0 与 npm。
|
|
6
|
-
|
|
7
|
-
## 安装
|
|
8
|
-
|
|
9
|
-
向目标环境安装,不要直接 `npm install -g @deepexi/datasense-cli@latest`:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
curl -fsSL https://your-host/api/cli/install.sh | sh -s -- https://your-host
|
|
13
|
-
ontos auth host set https://your-host
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Windows 使用 `/api/cli/install.ps1`。安装脚本会按该 host 的兼容清单安装对应版本。
|
|
17
|
-
|
|
18
|
-
升级:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
ontos update # 按当前 host 兼容范围升级
|
|
22
|
-
ontos update --check # 只查看,不安装
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
查看 CLI 版本:`ontos -v` / `ontos --version`。子命令里的 `--version <n>` 是该命令自己的版本参数,例如 `kb cat`。
|
|
26
|
-
|
|
27
|
-
## 快速开始
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
ontos auth host set https://your-host
|
|
31
|
-
ontos auth login
|
|
32
|
-
ontos project list
|
|
33
|
-
ontos project set <name>
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`project set` 会在当前目录写入 `ontos.yaml`。
|
|
37
|
-
|
|
38
|
-
## 常用命令
|
|
39
|
-
|
|
40
|
-
| 命令 | 说明 |
|
|
41
|
-
| :--- | :--- |
|
|
42
|
-
| `auth` | 登录、退出、查看会话;`host get` / `host set` |
|
|
43
|
-
| `project` | 项目列表、落地与成员 |
|
|
44
|
-
| `update` | 按当前 host 升级 CLI |
|
|
45
|
-
| `skill list` / `skill install` / `skill context` / `skill prune` | 列出、安装技能存根,运行时取技能内容,清理已下线存根 |
|
|
46
|
-
| `query` | 只读查询 |
|
|
47
|
-
| `search` | 检索对象 |
|
|
48
|
-
| `execute` | 执行动作 |
|
|
49
|
-
| `compute` | 执行已发布函数 |
|
|
50
|
-
| `kb` | 知识库读取:`ls` / `cat` / `fetch` / `history` / `search` |
|
|
51
|
-
| `simulation` | 场景推演 |
|
|
52
|
-
| `admin` | 平台管理(租户、环境、资源、同步、校验等),详见 `ontos admin --help` |
|
|
53
|
-
|
|
54
|
-
完整命令与参数以 `ontos --help` 和各子命令 `--help` 为准。
|
|
55
|
-
|
|
56
|
-
## 技能
|
|
57
|
-
|
|
58
|
-
`ontos skill install <skill...>` 只在当前目录写入技能存根(一份 `SKILL.md`:触发描述、所属项目,以及「先运行 `ontos skill context <name>`」)。默认同时写入 `.opencode/skills`、`.agents/skills`、`.claude/skills`;`--agent` 选择子集,`--global` 写到各宿主的用户目录。能力内容不落盘:agent 每次用 `ontos skill context <name>` 按当前身份、环境和快照取得;`--ref <path>` 取单篇参考。平台升级和项目发布后不用重装。`skill context` 与 `kb cat` 默认一次输出全文并带行号:首行给行号范围与补读命令,末行是 `[end …]`;空行不带行号,单行超过 500 字截断。宿主截断输出时,agent 从行号断档或缺失的末行发现,用 `--offset <行> --limit <行数>` 补读。需要分段时用 `--limit`,或设 `ONTOS_PAGE_LINES` / `ONTOS_PAGE_CHARS`(每段非空行数 / 正文字符数,缺省的一项取 240 / 26000)。脚本要不带行号的原文,用 `--raw` 或把输出重定向到文件。技能删了用 `ontos skill prune` 清掉本地存根。
|
|
59
|
-
|
|
60
|
-
Grok 和 Codex 在仓库未被信任时不加载项目级技能,把仓库标为信任后才会看到存根。OpenCode v1 不热加载技能文件,存根改写之后要新开一个会话,description 才会更新;`ontos skill context` 取到的内容不受影响。
|
|
61
|
-
|
|
62
|
-
## 输出
|
|
63
|
-
|
|
64
|
-
默认输出 Markdown 表格。脚本与编排器请显式使用 `--format json`(或 `ONTOS_FORMAT=json`)。
|
|
65
|
-
|
|
66
|
-
失败时错误 JSON 写在 stderr,含稳定的 `code` 字段;不要解析 `message` 文本。退出码:`0` 成功,`2` 用法/配置/输入错误,`1` 运行时或远端失败。
|
|
67
|
-
|
|
68
|
-
## 项目清单
|
|
69
|
-
|
|
70
|
-
每个项目目录一份 `ontos.yaml`:
|
|
71
|
-
|
|
72
|
-
```yaml
|
|
73
|
-
tenant: acme
|
|
74
|
-
project: sales
|
|
75
|
-
specDir: ./spec
|
|
76
|
-
sync:
|
|
77
|
-
domains: [ontology]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
ontos --cwd ./sales project list
|
|
82
|
-
ontos --cwd ./sales admin sync diff
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
- `--cwd` 指定工作目录;`--manifest` 显式指定清单文件。
|
|
86
|
-
- 自动发现从当前目录向上查找,止于最近 Git 根或用户主目录。
|
|
87
|
-
- 切换已绑定项目使用 `--cwd` / `--manifest`。覆盖已有绑定需 `ontos project set --rebind`。
|
|
88
|
-
- 清单不存放登录凭据。更换 `auth host set` 后需要重新登录。
|
|
1
|
+
# ontos
|
|
2
|
+
|
|
3
|
+
Ontos 平台命令行工具。npm 包名为 `@deepexi/datasense-cli`,终端命令为 `ontos`。
|
|
4
|
+
|
|
5
|
+
需要 Node.js ≥22.12.0 与 npm。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
向目标环境安装,不要直接 `npm install -g @deepexi/datasense-cli@latest`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -fsSL https://your-host/api/cli/install.sh | sh -s -- https://your-host
|
|
13
|
+
ontos auth host set https://your-host
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Windows 使用 `/api/cli/install.ps1`。安装脚本会按该 host 的兼容清单安装对应版本。
|
|
17
|
+
|
|
18
|
+
升级:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
ontos update # 按当前 host 兼容范围升级
|
|
22
|
+
ontos update --check # 只查看,不安装
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
查看 CLI 版本:`ontos -v` / `ontos --version`。子命令里的 `--version <n>` 是该命令自己的版本参数,例如 `kb cat`。
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ontos auth host set https://your-host
|
|
31
|
+
ontos auth login
|
|
32
|
+
ontos project list
|
|
33
|
+
ontos project set <name>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`project set` 会在当前目录写入 `ontos.yaml`。
|
|
37
|
+
|
|
38
|
+
## 常用命令
|
|
39
|
+
|
|
40
|
+
| 命令 | 说明 |
|
|
41
|
+
| :--- | :--- |
|
|
42
|
+
| `auth` | 登录、退出、查看会话;`host get` / `host set` |
|
|
43
|
+
| `project` | 项目列表、落地与成员 |
|
|
44
|
+
| `update` | 按当前 host 升级 CLI |
|
|
45
|
+
| `skill list` / `skill install` / `skill context` / `skill prune` | 列出、安装技能存根,运行时取技能内容,清理已下线存根 |
|
|
46
|
+
| `query` | 只读查询 |
|
|
47
|
+
| `search` | 检索对象 |
|
|
48
|
+
| `execute` | 执行动作 |
|
|
49
|
+
| `compute` | 执行已发布函数 |
|
|
50
|
+
| `kb` | 知识库读取:`ls` / `cat` / `fetch` / `history` / `search` |
|
|
51
|
+
| `simulation` | 场景推演 |
|
|
52
|
+
| `admin` | 平台管理(租户、环境、资源、同步、校验等),详见 `ontos admin --help` |
|
|
53
|
+
|
|
54
|
+
完整命令与参数以 `ontos --help` 和各子命令 `--help` 为准。
|
|
55
|
+
|
|
56
|
+
## 技能
|
|
57
|
+
|
|
58
|
+
`ontos skill install <skill...>` 只在当前目录写入技能存根(一份 `SKILL.md`:触发描述、所属项目,以及「先运行 `ontos skill context <name>`」)。默认同时写入 `.opencode/skills`、`.agents/skills`、`.claude/skills`;`--agent` 选择子集,`--global` 写到各宿主的用户目录。能力内容不落盘:agent 每次用 `ontos skill context <name>` 按当前身份、环境和快照取得;`--ref <path>` 取单篇参考。平台升级和项目发布后不用重装。`skill context` 与 `kb cat` 默认一次输出全文并带行号:首行给行号范围与补读命令,末行是 `[end …]`;空行不带行号,单行超过 500 字截断。宿主截断输出时,agent 从行号断档或缺失的末行发现,用 `--offset <行> --limit <行数>` 补读。需要分段时用 `--limit`,或设 `ONTOS_PAGE_LINES` / `ONTOS_PAGE_CHARS`(每段非空行数 / 正文字符数,缺省的一项取 240 / 26000)。脚本要不带行号的原文,用 `--raw` 或把输出重定向到文件。技能删了用 `ontos skill prune` 清掉本地存根。
|
|
59
|
+
|
|
60
|
+
Grok 和 Codex 在仓库未被信任时不加载项目级技能,把仓库标为信任后才会看到存根。OpenCode v1 不热加载技能文件,存根改写之后要新开一个会话,description 才会更新;`ontos skill context` 取到的内容不受影响。
|
|
61
|
+
|
|
62
|
+
## 输出
|
|
63
|
+
|
|
64
|
+
默认输出 Markdown 表格。脚本与编排器请显式使用 `--format json`(或 `ONTOS_FORMAT=json`)。
|
|
65
|
+
|
|
66
|
+
失败时错误 JSON 写在 stderr,含稳定的 `code` 字段;不要解析 `message` 文本。退出码:`0` 成功,`2` 用法/配置/输入错误,`1` 运行时或远端失败。
|
|
67
|
+
|
|
68
|
+
## 项目清单
|
|
69
|
+
|
|
70
|
+
每个项目目录一份 `ontos.yaml`:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
tenant: acme
|
|
74
|
+
project: sales
|
|
75
|
+
specDir: ./spec
|
|
76
|
+
sync:
|
|
77
|
+
domains: [ontology]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ontos --cwd ./sales project list
|
|
82
|
+
ontos --cwd ./sales admin sync diff
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `--cwd` 指定工作目录;`--manifest` 显式指定清单文件。
|
|
86
|
+
- 自动发现从当前目录向上查找,止于最近 Git 根或用户主目录。
|
|
87
|
+
- 切换已绑定项目使用 `--cwd` / `--manifest`。覆盖已有绑定需 `ontos project set --rebind`。
|
|
88
|
+
- 清单不存放登录凭据。更换 `auth host set` 后需要重新登录。
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
function __accessProp(key) {
|
|
7
|
+
return this[key];
|
|
8
|
+
}
|
|
9
|
+
var __toESMCache_node;
|
|
10
|
+
var __toESMCache_esm;
|
|
11
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
12
|
+
var canCache = mod != null && typeof mod === "object";
|
|
13
|
+
if (canCache) {
|
|
14
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
15
|
+
var cached = cache.get(mod);
|
|
16
|
+
if (cached)
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
19
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
20
|
+
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
21
|
+
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/runtime/session.ts
|
|
35
|
+
var import_node_vm = __toESM(require("node:vm"), 1);
|
|
36
|
+
|
|
37
|
+
// src/runtime/protocol.ts
|
|
38
|
+
var HOST_METHODS = Object.freeze([
|
|
39
|
+
"definition",
|
|
40
|
+
"declaration",
|
|
41
|
+
"http",
|
|
42
|
+
"db.query",
|
|
43
|
+
"db.next",
|
|
44
|
+
"db.close",
|
|
45
|
+
"crypto",
|
|
46
|
+
"cache.get",
|
|
47
|
+
"cache.set",
|
|
48
|
+
"secret.reveal",
|
|
49
|
+
"emit"
|
|
50
|
+
]);
|
|
51
|
+
var GUEST_FAILURE_CODES = Object.freeze([
|
|
52
|
+
"CONNECTOR_LOAD_FAILED",
|
|
53
|
+
"CONFIG_INVALID",
|
|
54
|
+
"SCHEMA_ERROR",
|
|
55
|
+
"FATAL_ERROR",
|
|
56
|
+
"AUTH_ERROR",
|
|
57
|
+
"TRANSIENT_ERROR",
|
|
58
|
+
"PUSH_UNKNOWN",
|
|
59
|
+
"CANCELED"
|
|
60
|
+
]);
|
|
61
|
+
var HTTP_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
62
|
+
var BODY_ENCODINGS = new Set(["utf8", "base64"]);
|
|
63
|
+
var DIGEST_ENCODINGS = new Set(["hex", "base64"]);
|
|
64
|
+
var HMAC_ALGORITHMS = new Set(["sha1", "sha256", "sha512"]);
|
|
65
|
+
var HASH_ALGORITHMS = new Set(["md5", "sha1", "sha256", "sha512"]);
|
|
66
|
+
var LOG_LEVELS = new Set(["info", "warn", "error"]);
|
|
67
|
+
var SYNC_MODES = new Set(["full", "incremental"]);
|
|
68
|
+
var SNAPSHOTS = new Set(["complete", "partial"]);
|
|
69
|
+
var HOST_METHOD_SET = new Set(HOST_METHODS);
|
|
70
|
+
var FAILURE_CODE_SET = new Set(GUEST_FAILURE_CODES);
|
|
71
|
+
function isRecord(value) {
|
|
72
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
73
|
+
}
|
|
74
|
+
function isStringArray(value) {
|
|
75
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
76
|
+
}
|
|
77
|
+
function isStringRecord(value) {
|
|
78
|
+
return isRecord(value) && Object.values(value).every((item) => typeof item === "string");
|
|
79
|
+
}
|
|
80
|
+
function isId(value) {
|
|
81
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
82
|
+
}
|
|
83
|
+
function parsePushRequest(value) {
|
|
84
|
+
if (!isRecord(value))
|
|
85
|
+
return null;
|
|
86
|
+
const { requestId, push, receivedAt, headers, body } = value;
|
|
87
|
+
if (typeof requestId !== "string" || typeof push !== "string" || typeof receivedAt !== "string" || !isStringRecord(headers) || typeof body !== "string")
|
|
88
|
+
return null;
|
|
89
|
+
return { requestId, push, receivedAt, headers: { ...headers }, body };
|
|
90
|
+
}
|
|
91
|
+
function parseJob(value) {
|
|
92
|
+
if (!isRecord(value) || !isRecord(value.config) || !isStringArray(value.secretFields))
|
|
93
|
+
return null;
|
|
94
|
+
const config = value.config;
|
|
95
|
+
const secretFields = [...value.secretFields];
|
|
96
|
+
if (value.kind === "push") {
|
|
97
|
+
if (!Array.isArray(value.requests))
|
|
98
|
+
return null;
|
|
99
|
+
const requests = [];
|
|
100
|
+
for (const item of value.requests) {
|
|
101
|
+
const request = parsePushRequest(item);
|
|
102
|
+
if (!request)
|
|
103
|
+
return null;
|
|
104
|
+
requests.push(request);
|
|
105
|
+
}
|
|
106
|
+
return { kind: "push", config, secretFields, requests };
|
|
107
|
+
}
|
|
108
|
+
if (value.kind === "discover")
|
|
109
|
+
return { kind: "discover", config, secretFields };
|
|
110
|
+
if (value.kind === "check")
|
|
111
|
+
return { kind: "check", config, secretFields };
|
|
112
|
+
if (value.kind === "pull") {
|
|
113
|
+
const { stream, mode, streamConfig, tables, discoverFirst } = value;
|
|
114
|
+
if (typeof stream !== "string" || typeof mode !== "string" || !SYNC_MODES.has(mode) || !isRecord(streamConfig) || !isStringArray(tables))
|
|
115
|
+
return null;
|
|
116
|
+
if (discoverFirst !== undefined && typeof discoverFirst !== "boolean")
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
kind: "pull",
|
|
120
|
+
config,
|
|
121
|
+
secretFields,
|
|
122
|
+
stream,
|
|
123
|
+
mode,
|
|
124
|
+
state: value.state,
|
|
125
|
+
streamConfig,
|
|
126
|
+
tables: [...tables],
|
|
127
|
+
...discoverFirst === true ? { discoverFirst: true } : {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function parseHostError(value) {
|
|
133
|
+
if (!isRecord(value) || typeof value.code !== "string" || typeof value.message !== "string")
|
|
134
|
+
return null;
|
|
135
|
+
const error = { code: value.code, message: value.message };
|
|
136
|
+
if (value.status !== undefined) {
|
|
137
|
+
if (typeof value.status !== "number" || !Number.isInteger(value.status))
|
|
138
|
+
return null;
|
|
139
|
+
error.status = value.status;
|
|
140
|
+
}
|
|
141
|
+
if (value.retryAfterMs !== undefined) {
|
|
142
|
+
if (typeof value.retryAfterMs !== "number" || !Number.isFinite(value.retryAfterMs) || value.retryAfterMs < 0)
|
|
143
|
+
return null;
|
|
144
|
+
error.retryAfterMs = value.retryAfterMs;
|
|
145
|
+
}
|
|
146
|
+
return error;
|
|
147
|
+
}
|
|
148
|
+
function parseParentMessage(value) {
|
|
149
|
+
if (!isRecord(value))
|
|
150
|
+
return null;
|
|
151
|
+
switch (value.type) {
|
|
152
|
+
case "execute": {
|
|
153
|
+
const { guest, code, expect } = value;
|
|
154
|
+
if (typeof guest !== "string" || typeof code !== "string")
|
|
155
|
+
return null;
|
|
156
|
+
if (!isRecord(expect) || typeof expect.name !== "string" || typeof expect.version !== "string")
|
|
157
|
+
return null;
|
|
158
|
+
const job = parseJob(value.job);
|
|
159
|
+
return job ? { type: "execute", guest, code, expect: { name: expect.name, version: expect.version }, job } : null;
|
|
160
|
+
}
|
|
161
|
+
case "reply": {
|
|
162
|
+
if (!isId(value.id))
|
|
163
|
+
return null;
|
|
164
|
+
if (value.ok === true)
|
|
165
|
+
return { type: "reply", id: value.id, ok: true, value: value.value };
|
|
166
|
+
if (value.ok !== false)
|
|
167
|
+
return null;
|
|
168
|
+
const error = parseHostError(value.error);
|
|
169
|
+
return error ? { type: "reply", id: value.id, ok: false, error } : null;
|
|
170
|
+
}
|
|
171
|
+
case "cancel":
|
|
172
|
+
return { type: "cancel" };
|
|
173
|
+
default:
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/runtime/session.ts
|
|
179
|
+
var GUEST_FILENAME = "ontos-guest.js";
|
|
180
|
+
var CONNECTOR_FILENAME = "connector.js";
|
|
181
|
+
function isRecord2(value) {
|
|
182
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
183
|
+
}
|
|
184
|
+
function guestErrorMessage(error) {
|
|
185
|
+
try {
|
|
186
|
+
if (error !== null && typeof error === "object") {
|
|
187
|
+
const message = error.message;
|
|
188
|
+
if (typeof message === "string")
|
|
189
|
+
return message;
|
|
190
|
+
}
|
|
191
|
+
} catch {}
|
|
192
|
+
return "unknown error";
|
|
193
|
+
}
|
|
194
|
+
function bootGuest(context, guestSource) {
|
|
195
|
+
const wrapped = `(() => { const module = { exports: {} }; const exports = module.exports; ${guestSource}
|
|
196
|
+
; return module.exports; })()`;
|
|
197
|
+
const exported = new import_node_vm.default.Script(wrapped, { filename: GUEST_FILENAME }).runInContext(context);
|
|
198
|
+
const boot = exported.bootGuest;
|
|
199
|
+
if (typeof boot !== "function")
|
|
200
|
+
throw new Error("guest runtime does not export bootGuest");
|
|
201
|
+
const api = boot();
|
|
202
|
+
const { start, handle, poll } = api;
|
|
203
|
+
if (typeof start !== "function" || typeof handle !== "function" || typeof poll !== "function")
|
|
204
|
+
throw new Error("guest runtime returned an incomplete API");
|
|
205
|
+
return { start, handle, poll };
|
|
206
|
+
}
|
|
207
|
+
function bootInVm(guestSource, code) {
|
|
208
|
+
const context = import_node_vm.default.createContext(Object.create(null), { codeGeneration: { strings: false, wasm: false } });
|
|
209
|
+
let guest;
|
|
210
|
+
try {
|
|
211
|
+
guest = bootGuest(context, guestSource);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return { ok: false, code: "CONNECTOR_LOAD_FAILED", message: `connector runtime failed to start: ${guestErrorMessage(error)}` };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const factory = new import_node_vm.default.Script(`(function (module, exports, require) {${code}
|
|
217
|
+
})`, { filename: CONNECTOR_FILENAME }).runInContext(context);
|
|
218
|
+
return { ok: true, guest, factory };
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return { ok: false, code: "CONNECTOR_LOAD_FAILED", message: `connector code failed to compile: ${guestErrorMessage(error)}` };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function createGuestSession(send) {
|
|
224
|
+
return createSession(send, bootInVm);
|
|
225
|
+
}
|
|
226
|
+
function createSession(send, boot) {
|
|
227
|
+
let guest = null;
|
|
228
|
+
let started = false;
|
|
229
|
+
let done = false;
|
|
230
|
+
let timer;
|
|
231
|
+
let timerAt = Number.POSITIVE_INFINITY;
|
|
232
|
+
const stop = () => {
|
|
233
|
+
done = true;
|
|
234
|
+
if (timer !== undefined)
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
timer = undefined;
|
|
237
|
+
};
|
|
238
|
+
const fail = (code, message) => {
|
|
239
|
+
if (done)
|
|
240
|
+
return;
|
|
241
|
+
stop();
|
|
242
|
+
send(JSON.stringify({ type: "failure", code, message, logs: [] }));
|
|
243
|
+
};
|
|
244
|
+
const schedule = (delay) => {
|
|
245
|
+
if (done)
|
|
246
|
+
return;
|
|
247
|
+
const at = Date.now() + delay;
|
|
248
|
+
if (timer !== undefined && timerAt <= at)
|
|
249
|
+
return;
|
|
250
|
+
if (timer !== undefined)
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
timerAt = at;
|
|
253
|
+
timer = setTimeout(pump, delay);
|
|
254
|
+
};
|
|
255
|
+
function pump() {
|
|
256
|
+
timer = undefined;
|
|
257
|
+
timerAt = Number.POSITIVE_INFINITY;
|
|
258
|
+
if (done || !guest)
|
|
259
|
+
return;
|
|
260
|
+
let text;
|
|
261
|
+
try {
|
|
262
|
+
text = guest.poll();
|
|
263
|
+
} catch (error) {
|
|
264
|
+
fail("FATAL_ERROR", `connector runtime failed: ${guestErrorMessage(error)}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
let parsed;
|
|
268
|
+
try {
|
|
269
|
+
parsed = typeof text === "string" ? JSON.parse(text) : null;
|
|
270
|
+
} catch {
|
|
271
|
+
parsed = null;
|
|
272
|
+
}
|
|
273
|
+
if (!isRecord2(parsed) || !Array.isArray(parsed.out)) {
|
|
274
|
+
fail("FATAL_ERROR", "connector runtime returned a malformed poll result");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
for (const message of parsed.out) {
|
|
278
|
+
if (!isRecord2(message))
|
|
279
|
+
continue;
|
|
280
|
+
send(JSON.stringify(message));
|
|
281
|
+
if (message.type === "result" || message.type === "failure") {
|
|
282
|
+
stop();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (parsed.out.length > 0 || parsed.fired === true)
|
|
287
|
+
schedule(0);
|
|
288
|
+
else if (typeof parsed.wake === "number" && parsed.wake >= 0)
|
|
289
|
+
schedule(parsed.wake);
|
|
290
|
+
}
|
|
291
|
+
const execute = (guestSource, code, payload) => {
|
|
292
|
+
const booted = boot(guestSource, code);
|
|
293
|
+
if (!booted.ok) {
|
|
294
|
+
fail(booted.code, booted.message);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
guest = booted.guest;
|
|
298
|
+
try {
|
|
299
|
+
guest.start(payload, booted.factory);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
fail("FATAL_ERROR", `connector runtime failed: ${guestErrorMessage(error)}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
schedule(0);
|
|
305
|
+
};
|
|
306
|
+
return {
|
|
307
|
+
handle(json) {
|
|
308
|
+
if (done || typeof json !== "string")
|
|
309
|
+
return;
|
|
310
|
+
let raw;
|
|
311
|
+
try {
|
|
312
|
+
raw = JSON.parse(json);
|
|
313
|
+
} catch {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const message = parseParentMessage(raw);
|
|
317
|
+
if (!message)
|
|
318
|
+
return;
|
|
319
|
+
if (message.type === "execute") {
|
|
320
|
+
if (started)
|
|
321
|
+
return;
|
|
322
|
+
started = true;
|
|
323
|
+
execute(message.guest, message.code, JSON.stringify({ expect: message.expect, job: message.job }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (!guest)
|
|
327
|
+
return;
|
|
328
|
+
try {
|
|
329
|
+
guest.handle(JSON.stringify(message));
|
|
330
|
+
} catch (error) {
|
|
331
|
+
fail("FATAL_ERROR", `connector runtime failed: ${guestErrorMessage(error)}`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
schedule(0);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/testing/child.ts
|
|
340
|
+
var session = createGuestSession((json) => {
|
|
341
|
+
process.send?.(json);
|
|
342
|
+
});
|
|
343
|
+
process.on("message", (raw) => {
|
|
344
|
+
if (typeof raw === "string")
|
|
345
|
+
session.handle(raw);
|
|
346
|
+
});
|
|
347
|
+
process.on("disconnect", () => {
|
|
348
|
+
process.exit(0);
|
|
349
|
+
});
|
|
350
|
+
process.send?.(JSON.stringify({ type: "ready" }));
|
|
Binary file
|