@flowy-agent-store/sdk 0.1.0-beta.7 → 0.1.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -3
- package/dist/index.cjs +131 -2
- package/dist/index.d.cts +114 -3
- package/dist/index.d.mts +114 -3
- package/dist/index.mjs +130 -4
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -15,7 +15,17 @@ const items = await harness.store.search("software");
|
|
|
15
15
|
// 安装:导入 + 注册,并等到组件真的可用(默认 waitForReady: true)
|
|
16
16
|
const outcome = await harness.store.install(items[0]);
|
|
17
17
|
if (outcome.readyIssue === "authorization_required") {
|
|
18
|
-
|
|
18
|
+
const id = outcome.readyComponentId!;
|
|
19
|
+
// 失败有两条通道,别只看一条:浏览器**之前**的失败由 authStart 同步回
|
|
20
|
+
// state:"error";**之后**的失败(换 token 被拒、回调超时、被限流)只会出现在
|
|
21
|
+
// authStatus 的 error 里,state 一直停在 not_authenticated。
|
|
22
|
+
// waitForAuth 把第二条通道包成一次调用,并在结束时说清原因。
|
|
23
|
+
const started = await harness.connectors.authStart(id);
|
|
24
|
+
if (started.state === "error") throw new Error(started.error ?? "authorization failed to start");
|
|
25
|
+
const auth = await harness.connectors.waitForAuth(id);
|
|
26
|
+
if (auth.state !== "authenticated") {
|
|
27
|
+
throw new Error(auth.state === "error" ? auth.error : "timed out in the browser");
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
// 禁用 / 启用:连接器改 mcp_servers.enabled,专家/专家团改 Preset.enabled
|
|
@@ -29,14 +39,53 @@ if (!released.ok) console.warn(released.components.filter((c) => !c.ok));
|
|
|
29
39
|
await harness.close();
|
|
30
40
|
```
|
|
31
41
|
|
|
42
|
+
## 导出专家 / 专家团并写到目录(`exportAgent` / `exportTeam` / `materializePack`)
|
|
43
|
+
|
|
44
|
+
`agents.export` / `teams.export` 只给**定义**(`ExpertPack`),技能是**引用**(`{name, id}`,没有字节)。
|
|
45
|
+
下面三个函数把「定义 + 技能字节」一次**写成一个目录**(`docs/agent-store/35`;它把 doc `32` §6.5 的
|
|
46
|
+
11 行配方提升成了 API):
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { exportTeam } from "@flowy-agent-store/sdk";
|
|
50
|
+
|
|
51
|
+
// 多 agent 的专家团:递归展开 + 整包失败(成员缺一即失败并点名),leader 在首位
|
|
52
|
+
const result = await exportTeam(harness, teamId, "./frontend-backend-experts");
|
|
53
|
+
// 单 agent 专家用 exportAgent(harness, agentId, dir)
|
|
54
|
+
// 已有 pack(比如缓存/网络传来的)用 materializePack(harness, pack, dir) 只写目录
|
|
55
|
+
|
|
56
|
+
result.pack; // ExpertPack —— 内存里也拿得到,不落盘也能用
|
|
57
|
+
result.writtenSkills; // 实际写入的技能名(去重后)
|
|
58
|
+
result.danglingSkills; // 声明了但本机取不到的技能:{ id, error },如实上报,不静默跳过
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
写出的布局:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
<dir>/
|
|
65
|
+
expert-pack.json # 线上 pack 逐字节
|
|
66
|
+
persona.md # 仅 agent 形态(团没有自己的 persona)
|
|
67
|
+
members/<id>/persona.md # 仅 team 形态,每个成员一个
|
|
68
|
+
skills/<name>/… # 团包级技能与成员声明合并去重后的技能字节
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
团的技能有**两个来源**,`materializePack` 把两者合并后按 id 去重:顶层 `pack.skills`(该团**包级**声明、
|
|
72
|
+
装机时已物化到托管技能根目录的技能)与 `team.members[].skills`(各成员 frontmatter 的 `skills:` 声明)。
|
|
73
|
+
官方团大多**只在包级声明**技能(例如 `stock-partner-team`:包级 3 个、7 名成员零声明),所以两者都要算进来。
|
|
74
|
+
|
|
75
|
+
错误语义:wire 导出失败(`agent_not_installed` / `agent_disabled` / `policy_denied` /
|
|
76
|
+
`version_mismatch` / `response_too_large`…)**先于任何写盘**,不留半成品目录;`skill/files`
|
|
77
|
+
失败进 `danglingSkills` 并继续;`skill/file` 在列文件成功后失败则**抛出**——那是宿主 I/O 错误,
|
|
78
|
+
吞掉会造出「列了文件却缺内容」的残目录。
|
|
79
|
+
|
|
32
80
|
**`launchHarness` 解析出的对象就是那个 client**(`docs/agent-store/31` §5 方案 B):`harness.store` / `harness.conversations` / `harness.models` 直接可用,没有 `.client` 一跳;子进程是 `harness.server`(`readiness` / `dataDir` / `exited`),握手响应是 `harness.handshake`。
|
|
33
81
|
|
|
34
82
|
五个动词都在 SDK 里可用,但**它们不是 `@flowy-agent-store/sdk` 自己实现的**:本包只负责进程与传输,返回的对象就是一个 `@flowy-agent-store/client` 的 `AppServerClient`(sdk 另挂 `server` / `handshake` / `close()`),其中 `store` 子客户端把 `install` / `uninstall` / `setEnabled` 编排成一条状态机(`search → install → … → uninstall`)。这样分层是刻意的——见 `docs/agent-store/12-sdk-packaging.md` §2 的职责划分。
|
|
35
83
|
|
|
36
84
|
几条使用上的事实(都是实测结论,不是约定):
|
|
37
85
|
|
|
38
|
-
- `store.install` 默认**等到可用**才 resolve。技能拷完即可用,连接器不然:它注册出来是 **disabled** 的(安装器的既定默认),所以就绪检查会先把它 enable,再跑一次探针——只轮询状态永远到不了 `connected
|
|
86
|
+
- `store.install` 默认**等到可用**才 resolve。技能拷完即可用,连接器不然:它注册出来是 **disabled** 的(安装器的既定默认),所以就绪检查会先把它 enable,再跑一次探针——只轮询状态永远到不了 `connected`,因为状态派生自上一次探针。探针**有节奏**:首轮(以及之后每 `readyProbeMs`,默认 5s)真连一次,其余轮次只读状态。这是刻意的:一次探针会解析已存 token(临近过期就刷新,401 再刷新一次),按 400ms 的轮询间隔去探针,30 秒预算里能对连接器及其授权服务器打出约 10 次连接 / 最多约 20 次 token 请求,而没有任何人点过按钮。
|
|
39
87
|
- 就绪超时**不会丢掉安装结果**:返回成功的安装 + `ready: false` / `readyIssue: "ready_timeout"`。需要 OAuth 的连接器立刻返回 `authorization_required`,不把超时预算烧光。
|
|
88
|
+
- `connectors.waitForAuth(id)` 是「等授权完成」的写法:它轮询 `authStatus`,一拿到 `error` 就立刻带原因返回(`{state:"error", error}`),成功返回 `{state:"authenticated"}`,预算用完返回 `{state:"timeout"}`;只有状态读本身失败才 reject。手写 `while (state !== "authenticated")` 循环的典型结局是「它就是没变成 authenticated」——而原因(换 token 被拒、回调超时、`slow_down` 限流)早就躺在它每次读到的 `error` 里。
|
|
40
89
|
- 没有「更新」动词。`store.checkUpdates()` 只报告哪些条目有新版,`store.updateHint(item)` 告诉你该怎么做——当前是 `"uninstall_reinstall"`:卸载后重装,安装路径会按版本漂移重新导入,取到市场当前版本。
|
|
41
90
|
- 卸载是**可重入**的:产物已不在算成功。部分失败时那些组件保持已安装、`ok: false` 且点名到组件,重试有意义。
|
|
42
91
|
- `store.setEnabled(item, false)` 对技能只翻一个目录层标记,返回 `code: "skill_disable_flag_only"`——技能语料没有启用状态,要让技能离开运行时只能 `uninstall`。这个 code 会原样透出,不会被吞。
|
|
@@ -64,4 +113,8 @@ await harness.close();
|
|
|
64
113
|
- 二进制定位:`bin` 参数 → `AGENT_STORE_BIN` → 平台 runtime 包的 `vendor/` → `PATH`;找不到直接报错(**不下载**,见 P2)。
|
|
65
114
|
- 就绪行 `protocol_version` 与 SDK 不一致时杀掉子进程并报错(含两端版本)。该值是**契约指纹**,不是版本号:任何 wire 变更都会 bump。
|
|
66
115
|
|
|
67
|
-
不在本包范围内(`07-typescript-sdk.md` §非目标):`config/*` 与 `skill/*`
|
|
116
|
+
不在本包范围内(`07-typescript-sdk.md` §非目标):`config/*` 与 `skill/*` **写**面。它们的**类型**在
|
|
117
|
+
`@flowy-agent-store/protocol`(wire 上存在的方法就有形状),但**没有** typed 方法——因为这些面跟着
|
|
118
|
+
宿主自己的设置文件与技能目录走,是最易变的部分。**技能的读面是例外**:`skill/files` / `skill/file`
|
|
119
|
+
有 typed 方法(`harness.skills.*`),且被上面的导出函数消费。这是可发现性边界,不是权限边界:
|
|
120
|
+
`harness.transport` 是公开的,服务端按磁盘归属判定可写性。要用就直连 `transport.request`,并自担变动。
|
package/dist/index.cjs
CHANGED
|
@@ -3,8 +3,8 @@ let _flowy_agent_store_client = require("@flowy-agent-store/client");
|
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
let node_module = require("node:module");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
|
-
let node_child_process = require("node:child_process");
|
|
7
6
|
let node_fs_promises = require("node:fs/promises");
|
|
7
|
+
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_os = require("node:os");
|
|
9
9
|
let node_readline = require("node:readline");
|
|
10
10
|
let _flowy_agent_store_protocol = require("@flowy-agent-store/protocol");
|
|
@@ -66,6 +66,109 @@ function findOnPath(name) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
69
|
+
//#region src/export.ts
|
|
70
|
+
/**
|
|
71
|
+
* Export an expert (single agent) or an expert **team** together with its
|
|
72
|
+
* skills, written out as a directory (doc `35`).
|
|
73
|
+
*
|
|
74
|
+
* The wire face (`agent/export` / `team/export`, docs/agent-store/32) carries
|
|
75
|
+
* skills **by reference** — `{name, id}`, never bytes — so "export the team's
|
|
76
|
+
* skills" means: walk the pack, fetch every referenced skill through
|
|
77
|
+
* `skill/files` + `skill/file`, and write the whole thing to a directory the
|
|
78
|
+
* *caller* owns. Doc `32` §6.5 originally shipped this as an 11-line recipe in
|
|
79
|
+
* prose; doc `35` promotes it into these three functions (and records why the
|
|
80
|
+
* earlier decision against an SDK helper was reversed).
|
|
81
|
+
*
|
|
82
|
+
* Everything here runs in the caller's process. The host never writes a byte:
|
|
83
|
+
* there is no server-side directory-writing endpoint by design (doc `32` §2
|
|
84
|
+
* item 7), which is why this file is allowed to import `node:fs` — the same
|
|
85
|
+
* privilege `spawn.ts` already has — while `@flowy-agent-store/client` must
|
|
86
|
+
* never gain it.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Write an in-hand pack to `dir` as a real directory (doc `35` §5 layout).
|
|
90
|
+
* "Materialize" here means exactly that: the in-memory pack object plus the
|
|
91
|
+
* skill bytes it references become files on disk; the pack data itself is
|
|
92
|
+
* unchanged.
|
|
93
|
+
*
|
|
94
|
+
* ```text
|
|
95
|
+
* <dir>/expert-pack.json # the wire pack, byte-for-byte (JSON.stringify(_, 2))
|
|
96
|
+
* <dir>/persona.md # kind === "agent" only: pack.persona.instructions
|
|
97
|
+
* <dir>/members/<id>/persona.md # kind === "team" only, one per member
|
|
98
|
+
* <dir>/skills/<name>/… # every referenced skill's files, deduplicated by id
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* Error semantics (doc `35` §6): the pack is fetched **before** anything is
|
|
102
|
+
* written, so a failed wire export leaves no directory behind; a `skill/files`
|
|
103
|
+
* rejection lands in {@link ExportResult.danglingSkills} and the loop
|
|
104
|
+
* continues; a `skill/file` rejection after a successful listing **throws** —
|
|
105
|
+
* that is a host I/O failure, and swallowing it would produce a directory that
|
|
106
|
+
* lists files it does not contain. No rollback on mid-write failure.
|
|
107
|
+
*/
|
|
108
|
+
async function materializePack(deps, pack, dir) {
|
|
109
|
+
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
110
|
+
await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "expert-pack.json"), JSON.stringify(pack, null, 2));
|
|
111
|
+
if (pack.kind === "agent") await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, "persona.md"), pack.persona.instructions);
|
|
112
|
+
for (const member of pack.team?.members ?? []) {
|
|
113
|
+
const target = (0, node_path.join)(dir, "members", member.id, "persona.md");
|
|
114
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(target), { recursive: true });
|
|
115
|
+
await (0, node_fs_promises.writeFile)(target, member.persona.instructions);
|
|
116
|
+
}
|
|
117
|
+
const refs = [...pack.skills, ...(pack.team?.members ?? []).flatMap((m) => m.skills)];
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
119
|
+
const writtenSkills = [];
|
|
120
|
+
const danglingSkills = [];
|
|
121
|
+
for (const ref of refs) {
|
|
122
|
+
if (seen.has(ref.id)) continue;
|
|
123
|
+
seen.add(ref.id);
|
|
124
|
+
let files;
|
|
125
|
+
try {
|
|
126
|
+
files = (await deps.skills.files(ref.id)).files;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
danglingSkills.push({
|
|
129
|
+
id: ref.id,
|
|
130
|
+
error: String(error)
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const target = (0, node_path.join)(dir, "skills", ref.name, file.path);
|
|
136
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(target), { recursive: true });
|
|
137
|
+
await (0, node_fs_promises.writeFile)(target, await deps.skills.readFile(ref.id, file.path));
|
|
138
|
+
}
|
|
139
|
+
writtenSkills.push(ref.name);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
pack,
|
|
143
|
+
dir,
|
|
144
|
+
writtenSkills,
|
|
145
|
+
danglingSkills
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Export one single-agent expert and write it out as a directory.
|
|
150
|
+
*
|
|
151
|
+
* Same preconditions as the wire method: the expert must be installed and
|
|
152
|
+
* enabled, or the host answers `agent_not_installed` / `agent_disabled` /
|
|
153
|
+
* `preset_disabled`; a host whose `[expert_export]` table refuses the id
|
|
154
|
+
* answers `policy_denied` (the capability bit only reports the seam). Check
|
|
155
|
+
* `capabilities.expert_export` for the seam, not for this id.
|
|
156
|
+
*/
|
|
157
|
+
async function exportAgent(deps, agentId, dir) {
|
|
158
|
+
return materializePack(deps, await deps.agents.export(agentId), dir);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Export one expert team and write it out as a directory, leader first.
|
|
162
|
+
*
|
|
163
|
+
* All-or-nothing stays on the server: if any member is uninstalled, disabled,
|
|
164
|
+
* or refused by policy, the whole call fails and nothing is written. A pack
|
|
165
|
+
* over 1 MiB fails with `response_too_large` rather than being truncated.
|
|
166
|
+
* `teamVersion` is an optional pin; a mismatch is `version_mismatch`.
|
|
167
|
+
*/
|
|
168
|
+
async function exportTeam(deps, teamId, dir, teamVersion) {
|
|
169
|
+
return materializePack(deps, await deps.teams.export(teamId, teamVersion), dir);
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
69
172
|
//#region src/readiness.ts
|
|
70
173
|
/** Parse one stdout line; `null` when it is not the readiness line. */
|
|
71
174
|
function parseReadinessLine(line) {
|
|
@@ -107,11 +210,30 @@ const KILL_GRACE_MS = 2e3;
|
|
|
107
210
|
function assertProtocolCompatible(runtimeVersion) {
|
|
108
211
|
if (runtimeVersion !== _flowy_agent_store_protocol.APP_SERVER_PROTOCOL_VERSION) throw new Error(`protocol version mismatch: runtime speaks ${runtimeVersion}, SDK expects ${_flowy_agent_store_protocol.APP_SERVER_PROTOCOL_VERSION}`);
|
|
109
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The host-policy environment the typed options above expand to.
|
|
215
|
+
*
|
|
216
|
+
* The spawned host cannot be handed backend CLI flags: `apps/agent-store` parses
|
|
217
|
+
* its own argv (`--host` / `--port` / `--data-dir` / `--no-open`) and has no
|
|
218
|
+
* `--agent-store-config`, so a flag would make clap exit before the readiness
|
|
219
|
+
* line. The parent environment is therefore the only channel a launcher has —
|
|
220
|
+
* the same conclusion `NOMI_LOG_LEVEL` already documents for the log filter.
|
|
221
|
+
*/
|
|
222
|
+
function hostPolicyEnv(options) {
|
|
223
|
+
const env = {};
|
|
224
|
+
if (options.marketDownload !== void 0) env["AGENT_STORE_MARKET_DOWNLOAD"] = options.marketDownload;
|
|
225
|
+
if (options.configPath !== void 0) env["AGENT_STORE_CONFIG"] = options.configPath;
|
|
226
|
+
return env;
|
|
227
|
+
}
|
|
110
228
|
async function spawnAppServer(options = {}) {
|
|
111
229
|
const bin = resolveAppServerBin(options.bin);
|
|
112
230
|
const ownedDir = options.dataDir === void 0;
|
|
113
231
|
const dataDir = ownedDir ? await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "agent-store-sdk-")) : options.dataDir;
|
|
114
232
|
const port = options.port ?? 0;
|
|
233
|
+
const policyEnv = {
|
|
234
|
+
...hostPolicyEnv(options),
|
|
235
|
+
...options.env
|
|
236
|
+
};
|
|
115
237
|
const { child, exited } = spawnRuntimeChild(bin, [
|
|
116
238
|
"--host",
|
|
117
239
|
"127.0.0.1",
|
|
@@ -121,7 +243,11 @@ async function spawnAppServer(options = {}) {
|
|
|
121
243
|
dataDir,
|
|
122
244
|
"--no-open",
|
|
123
245
|
...options.extraArgs ?? []
|
|
124
|
-
],
|
|
246
|
+
], {
|
|
247
|
+
...Object.keys(policyEnv).length > 0 ? { env: policyEnv } : {},
|
|
248
|
+
cwd: options.cwd,
|
|
249
|
+
onExit: options.onExit
|
|
250
|
+
});
|
|
125
251
|
const stderrTail = [];
|
|
126
252
|
child.stderr?.on("data", (chunk) => {
|
|
127
253
|
for (const line of String(chunk).split(/\r?\n/)) {
|
|
@@ -311,7 +437,10 @@ async function launchHarness(options) {
|
|
|
311
437
|
}
|
|
312
438
|
//#endregion
|
|
313
439
|
exports.assertProtocolCompatible = assertProtocolCompatible;
|
|
440
|
+
exports.exportAgent = exportAgent;
|
|
441
|
+
exports.exportTeam = exportTeam;
|
|
314
442
|
exports.launchHarness = launchHarness;
|
|
443
|
+
exports.materializePack = materializePack;
|
|
315
444
|
exports.parseReadinessLine = parseReadinessLine;
|
|
316
445
|
exports.resolveAppServerBin = resolveAppServerBin;
|
|
317
446
|
exports.spawnAppServer = spawnAppServer;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
import { AppServerClient } from "@flowy-agent-store/client";
|
|
2
|
-
import { ClientCapabilities, ClientInfo, InitializeResult } from "@flowy-agent-store/protocol";
|
|
2
|
+
import { ClientCapabilities, ClientInfo, ExpertPack, InitializeResult } from "@flowy-agent-store/protocol";
|
|
3
3
|
//#region src/bin.d.ts
|
|
4
4
|
export declare function resolveAppServerBin(explicit?: string): string;
|
|
5
5
|
//#endregion
|
|
6
|
+
//#region src/export.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* The three wire faces this module needs, narrowed from {@link Harness} on
|
|
9
|
+
* purpose: unit tests hand a fake, and the dependency surface stays explicit
|
|
10
|
+
* instead of quietly reaching `store` / `conversations`. A real harness
|
|
11
|
+
* satisfies this structurally (`Harness` extends `AppServerClient`, which owns
|
|
12
|
+
* `agents` / `teams` / `skills`).
|
|
13
|
+
*/
|
|
14
|
+
interface ExportDeps {
|
|
15
|
+
agents: {
|
|
16
|
+
export(agentId: string): Promise<ExpertPack>;
|
|
17
|
+
};
|
|
18
|
+
teams: {
|
|
19
|
+
export(teamId: string, teamVersion?: string): Promise<ExpertPack>;
|
|
20
|
+
};
|
|
21
|
+
skills: {
|
|
22
|
+
files(skillId: string): Promise<{
|
|
23
|
+
files: {
|
|
24
|
+
path: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
27
|
+
readFile(skillId: string, path: string): Promise<Uint8Array>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface ExportResult {
|
|
31
|
+
/** The pack is also returned in memory: a consumer that never touches the disk needs no second call. */
|
|
32
|
+
pack: ExpertPack;
|
|
33
|
+
dir: string;
|
|
34
|
+
/** Skill names actually written (deduplicated). */
|
|
35
|
+
writtenSkills: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Skills the pack **declares** but this host cannot resolve (`skill/files`
|
|
38
|
+
* rejected them). Reported honestly, never skipped in silence: the pack
|
|
39
|
+
* states declarations, resolvability is a host fact the consumer has to
|
|
40
|
+
* judge (doc `32` §5 R8, live criterion EX-008).
|
|
41
|
+
*/
|
|
42
|
+
danglingSkills: {
|
|
43
|
+
id: string;
|
|
44
|
+
error: string;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write an in-hand pack to `dir` as a real directory (doc `35` §5 layout).
|
|
49
|
+
* "Materialize" here means exactly that: the in-memory pack object plus the
|
|
50
|
+
* skill bytes it references become files on disk; the pack data itself is
|
|
51
|
+
* unchanged.
|
|
52
|
+
*
|
|
53
|
+
* ```text
|
|
54
|
+
* <dir>/expert-pack.json # the wire pack, byte-for-byte (JSON.stringify(_, 2))
|
|
55
|
+
* <dir>/persona.md # kind === "agent" only: pack.persona.instructions
|
|
56
|
+
* <dir>/members/<id>/persona.md # kind === "team" only, one per member
|
|
57
|
+
* <dir>/skills/<name>/… # every referenced skill's files, deduplicated by id
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Error semantics (doc `35` §6): the pack is fetched **before** anything is
|
|
61
|
+
* written, so a failed wire export leaves no directory behind; a `skill/files`
|
|
62
|
+
* rejection lands in {@link ExportResult.danglingSkills} and the loop
|
|
63
|
+
* continues; a `skill/file` rejection after a successful listing **throws** —
|
|
64
|
+
* that is a host I/O failure, and swallowing it would produce a directory that
|
|
65
|
+
* lists files it does not contain. No rollback on mid-write failure.
|
|
66
|
+
*/
|
|
67
|
+
export declare function materializePack(deps: ExportDeps, pack: ExpertPack, dir: string): Promise<ExportResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Export one single-agent expert and write it out as a directory.
|
|
70
|
+
*
|
|
71
|
+
* Same preconditions as the wire method: the expert must be installed and
|
|
72
|
+
* enabled, or the host answers `agent_not_installed` / `agent_disabled` /
|
|
73
|
+
* `preset_disabled`; a host whose `[expert_export]` table refuses the id
|
|
74
|
+
* answers `policy_denied` (the capability bit only reports the seam). Check
|
|
75
|
+
* `capabilities.expert_export` for the seam, not for this id.
|
|
76
|
+
*/
|
|
77
|
+
export declare function exportAgent(deps: ExportDeps, agentId: string, dir: string): Promise<ExportResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Export one expert team and write it out as a directory, leader first.
|
|
80
|
+
*
|
|
81
|
+
* All-or-nothing stays on the server: if any member is uninstalled, disabled,
|
|
82
|
+
* or refused by policy, the whole call fails and nothing is written. A pack
|
|
83
|
+
* over 1 MiB fails with `response_too_large` rather than being truncated.
|
|
84
|
+
* `teamVersion` is an optional pin; a mismatch is `version_mismatch`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function exportTeam(deps: ExportDeps, teamId: string, dir: string, teamVersion?: string): Promise<ExportResult>;
|
|
87
|
+
//#endregion
|
|
6
88
|
//#region src/readiness.d.ts
|
|
7
89
|
/**
|
|
8
90
|
* Readiness protocol (docs/agent-store/12 P0-1).
|
|
@@ -34,9 +116,38 @@ interface SpawnOptions {
|
|
|
34
116
|
port?: number;
|
|
35
117
|
/** Extra CLI args appended after the managed ones. */
|
|
36
118
|
extraArgs?: string[];
|
|
119
|
+
/**
|
|
120
|
+
* What the spawned host does with its default marketplace sources at boot
|
|
121
|
+
* (doc `37` §3.1). Sent as `AGENT_STORE_MARKET_DOWNLOAD`.
|
|
122
|
+
*
|
|
123
|
+
* - `"lazy"` — register the declared sources and download nothing; the
|
|
124
|
+
* archives wait for an explicit `market/refresh` / `refreshMarketplace()`;
|
|
125
|
+
* - `"none"` — register nothing at all, so `store/list` starts empty;
|
|
126
|
+
* - `"eager"` — fetch every declared source during boot.
|
|
127
|
+
*
|
|
128
|
+
* Omitted = the host's own policy (`download_on_start` per source, default
|
|
129
|
+
* `true`, so a declared source *is* downloaded unless it says otherwise).
|
|
130
|
+
* This is a process-scoped override: nothing is written to the user's config.
|
|
131
|
+
*/
|
|
132
|
+
marketDownload?: "eager" | "lazy" | "none";
|
|
133
|
+
/**
|
|
134
|
+
* Config file for this child only (doc `37` §3.1). Sent as
|
|
135
|
+
* `AGENT_STORE_CONFIG`.
|
|
136
|
+
*
|
|
137
|
+
* Without it the host reads the per-user `~/.agent-store/config.toml` — which
|
|
138
|
+
* a spawned, SDK-owned host should not have to share. The data directory is
|
|
139
|
+
* independent of this (`dataDir`, always explicit).
|
|
140
|
+
*/
|
|
141
|
+
configPath?: string;
|
|
37
142
|
/** How long to wait for the readiness line. Defaults to 120s (cold DB init). */
|
|
38
143
|
readyTimeoutMs?: number;
|
|
39
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Extra environment variables, merged over the parent's `process.env`.
|
|
146
|
+
*
|
|
147
|
+
* An entry here **wins over the typed options above** (`marketDownload`,
|
|
148
|
+
* `configPath`): the raw map is the caller's most specific statement, and
|
|
149
|
+
* nothing this factory derives should silently overwrite it.
|
|
150
|
+
*/
|
|
40
151
|
env?: Record<string, string | undefined>;
|
|
41
152
|
/** Working directory for the child. Defaults to the parent's cwd. */
|
|
42
153
|
cwd?: string;
|
|
@@ -114,4 +225,4 @@ export interface Harness extends AppServerClient {
|
|
|
114
225
|
*/
|
|
115
226
|
export declare function launchHarness(options: HarnessOptions): Promise<Harness>;
|
|
116
227
|
//#endregion
|
|
117
|
-
export type { ReadinessInfo, SpawnExitInfo, SpawnOptions, SpawnedServer };
|
|
228
|
+
export type { ExportDeps, ExportResult, ReadinessInfo, SpawnExitInfo, SpawnOptions, SpawnedServer };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
import { AppServerClient } from "@flowy-agent-store/client";
|
|
2
|
-
import { ClientCapabilities, ClientInfo, InitializeResult } from "@flowy-agent-store/protocol";
|
|
2
|
+
import { ClientCapabilities, ClientInfo, ExpertPack, InitializeResult } from "@flowy-agent-store/protocol";
|
|
3
3
|
//#region src/bin.d.ts
|
|
4
4
|
export declare function resolveAppServerBin(explicit?: string): string;
|
|
5
5
|
//#endregion
|
|
6
|
+
//#region src/export.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* The three wire faces this module needs, narrowed from {@link Harness} on
|
|
9
|
+
* purpose: unit tests hand a fake, and the dependency surface stays explicit
|
|
10
|
+
* instead of quietly reaching `store` / `conversations`. A real harness
|
|
11
|
+
* satisfies this structurally (`Harness` extends `AppServerClient`, which owns
|
|
12
|
+
* `agents` / `teams` / `skills`).
|
|
13
|
+
*/
|
|
14
|
+
interface ExportDeps {
|
|
15
|
+
agents: {
|
|
16
|
+
export(agentId: string): Promise<ExpertPack>;
|
|
17
|
+
};
|
|
18
|
+
teams: {
|
|
19
|
+
export(teamId: string, teamVersion?: string): Promise<ExpertPack>;
|
|
20
|
+
};
|
|
21
|
+
skills: {
|
|
22
|
+
files(skillId: string): Promise<{
|
|
23
|
+
files: {
|
|
24
|
+
path: string;
|
|
25
|
+
}[];
|
|
26
|
+
}>;
|
|
27
|
+
readFile(skillId: string, path: string): Promise<Uint8Array>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface ExportResult {
|
|
31
|
+
/** The pack is also returned in memory: a consumer that never touches the disk needs no second call. */
|
|
32
|
+
pack: ExpertPack;
|
|
33
|
+
dir: string;
|
|
34
|
+
/** Skill names actually written (deduplicated). */
|
|
35
|
+
writtenSkills: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Skills the pack **declares** but this host cannot resolve (`skill/files`
|
|
38
|
+
* rejected them). Reported honestly, never skipped in silence: the pack
|
|
39
|
+
* states declarations, resolvability is a host fact the consumer has to
|
|
40
|
+
* judge (doc `32` §5 R8, live criterion EX-008).
|
|
41
|
+
*/
|
|
42
|
+
danglingSkills: {
|
|
43
|
+
id: string;
|
|
44
|
+
error: string;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write an in-hand pack to `dir` as a real directory (doc `35` §5 layout).
|
|
49
|
+
* "Materialize" here means exactly that: the in-memory pack object plus the
|
|
50
|
+
* skill bytes it references become files on disk; the pack data itself is
|
|
51
|
+
* unchanged.
|
|
52
|
+
*
|
|
53
|
+
* ```text
|
|
54
|
+
* <dir>/expert-pack.json # the wire pack, byte-for-byte (JSON.stringify(_, 2))
|
|
55
|
+
* <dir>/persona.md # kind === "agent" only: pack.persona.instructions
|
|
56
|
+
* <dir>/members/<id>/persona.md # kind === "team" only, one per member
|
|
57
|
+
* <dir>/skills/<name>/… # every referenced skill's files, deduplicated by id
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Error semantics (doc `35` §6): the pack is fetched **before** anything is
|
|
61
|
+
* written, so a failed wire export leaves no directory behind; a `skill/files`
|
|
62
|
+
* rejection lands in {@link ExportResult.danglingSkills} and the loop
|
|
63
|
+
* continues; a `skill/file` rejection after a successful listing **throws** —
|
|
64
|
+
* that is a host I/O failure, and swallowing it would produce a directory that
|
|
65
|
+
* lists files it does not contain. No rollback on mid-write failure.
|
|
66
|
+
*/
|
|
67
|
+
export declare function materializePack(deps: ExportDeps, pack: ExpertPack, dir: string): Promise<ExportResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Export one single-agent expert and write it out as a directory.
|
|
70
|
+
*
|
|
71
|
+
* Same preconditions as the wire method: the expert must be installed and
|
|
72
|
+
* enabled, or the host answers `agent_not_installed` / `agent_disabled` /
|
|
73
|
+
* `preset_disabled`; a host whose `[expert_export]` table refuses the id
|
|
74
|
+
* answers `policy_denied` (the capability bit only reports the seam). Check
|
|
75
|
+
* `capabilities.expert_export` for the seam, not for this id.
|
|
76
|
+
*/
|
|
77
|
+
export declare function exportAgent(deps: ExportDeps, agentId: string, dir: string): Promise<ExportResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Export one expert team and write it out as a directory, leader first.
|
|
80
|
+
*
|
|
81
|
+
* All-or-nothing stays on the server: if any member is uninstalled, disabled,
|
|
82
|
+
* or refused by policy, the whole call fails and nothing is written. A pack
|
|
83
|
+
* over 1 MiB fails with `response_too_large` rather than being truncated.
|
|
84
|
+
* `teamVersion` is an optional pin; a mismatch is `version_mismatch`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function exportTeam(deps: ExportDeps, teamId: string, dir: string, teamVersion?: string): Promise<ExportResult>;
|
|
87
|
+
//#endregion
|
|
6
88
|
//#region src/readiness.d.ts
|
|
7
89
|
/**
|
|
8
90
|
* Readiness protocol (docs/agent-store/12 P0-1).
|
|
@@ -34,9 +116,38 @@ interface SpawnOptions {
|
|
|
34
116
|
port?: number;
|
|
35
117
|
/** Extra CLI args appended after the managed ones. */
|
|
36
118
|
extraArgs?: string[];
|
|
119
|
+
/**
|
|
120
|
+
* What the spawned host does with its default marketplace sources at boot
|
|
121
|
+
* (doc `37` §3.1). Sent as `AGENT_STORE_MARKET_DOWNLOAD`.
|
|
122
|
+
*
|
|
123
|
+
* - `"lazy"` — register the declared sources and download nothing; the
|
|
124
|
+
* archives wait for an explicit `market/refresh` / `refreshMarketplace()`;
|
|
125
|
+
* - `"none"` — register nothing at all, so `store/list` starts empty;
|
|
126
|
+
* - `"eager"` — fetch every declared source during boot.
|
|
127
|
+
*
|
|
128
|
+
* Omitted = the host's own policy (`download_on_start` per source, default
|
|
129
|
+
* `true`, so a declared source *is* downloaded unless it says otherwise).
|
|
130
|
+
* This is a process-scoped override: nothing is written to the user's config.
|
|
131
|
+
*/
|
|
132
|
+
marketDownload?: "eager" | "lazy" | "none";
|
|
133
|
+
/**
|
|
134
|
+
* Config file for this child only (doc `37` §3.1). Sent as
|
|
135
|
+
* `AGENT_STORE_CONFIG`.
|
|
136
|
+
*
|
|
137
|
+
* Without it the host reads the per-user `~/.agent-store/config.toml` — which
|
|
138
|
+
* a spawned, SDK-owned host should not have to share. The data directory is
|
|
139
|
+
* independent of this (`dataDir`, always explicit).
|
|
140
|
+
*/
|
|
141
|
+
configPath?: string;
|
|
37
142
|
/** How long to wait for the readiness line. Defaults to 120s (cold DB init). */
|
|
38
143
|
readyTimeoutMs?: number;
|
|
39
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Extra environment variables, merged over the parent's `process.env`.
|
|
146
|
+
*
|
|
147
|
+
* An entry here **wins over the typed options above** (`marketDownload`,
|
|
148
|
+
* `configPath`): the raw map is the caller's most specific statement, and
|
|
149
|
+
* nothing this factory derives should silently overwrite it.
|
|
150
|
+
*/
|
|
40
151
|
env?: Record<string, string | undefined>;
|
|
41
152
|
/** Working directory for the child. Defaults to the parent's cwd. */
|
|
42
153
|
cwd?: string;
|
|
@@ -114,4 +225,4 @@ export interface Harness extends AppServerClient {
|
|
|
114
225
|
*/
|
|
115
226
|
export declare function launchHarness(options: HarnessOptions): Promise<Harness>;
|
|
116
227
|
//#endregion
|
|
117
|
-
export type { ReadinessInfo, SpawnExitInfo, SpawnOptions, SpawnedServer };
|
|
228
|
+
export type { ExportDeps, ExportResult, ReadinessInfo, SpawnExitInfo, SpawnOptions, SpawnedServer };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { AppServerClient, WebSocketTransport, isLoopbackUrl } from "@flowy-agent-store/client";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { delimiter, join } from "node:path";
|
|
4
|
+
import { delimiter, dirname, join } from "node:path";
|
|
5
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { APP_SERVER_PROTOCOL_VERSION } from "@flowy-agent-store/protocol";
|
|
@@ -65,6 +65,109 @@ function findOnPath(name) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
//#endregion
|
|
68
|
+
//#region src/export.ts
|
|
69
|
+
/**
|
|
70
|
+
* Export an expert (single agent) or an expert **team** together with its
|
|
71
|
+
* skills, written out as a directory (doc `35`).
|
|
72
|
+
*
|
|
73
|
+
* The wire face (`agent/export` / `team/export`, docs/agent-store/32) carries
|
|
74
|
+
* skills **by reference** — `{name, id}`, never bytes — so "export the team's
|
|
75
|
+
* skills" means: walk the pack, fetch every referenced skill through
|
|
76
|
+
* `skill/files` + `skill/file`, and write the whole thing to a directory the
|
|
77
|
+
* *caller* owns. Doc `32` §6.5 originally shipped this as an 11-line recipe in
|
|
78
|
+
* prose; doc `35` promotes it into these three functions (and records why the
|
|
79
|
+
* earlier decision against an SDK helper was reversed).
|
|
80
|
+
*
|
|
81
|
+
* Everything here runs in the caller's process. The host never writes a byte:
|
|
82
|
+
* there is no server-side directory-writing endpoint by design (doc `32` §2
|
|
83
|
+
* item 7), which is why this file is allowed to import `node:fs` — the same
|
|
84
|
+
* privilege `spawn.ts` already has — while `@flowy-agent-store/client` must
|
|
85
|
+
* never gain it.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Write an in-hand pack to `dir` as a real directory (doc `35` §5 layout).
|
|
89
|
+
* "Materialize" here means exactly that: the in-memory pack object plus the
|
|
90
|
+
* skill bytes it references become files on disk; the pack data itself is
|
|
91
|
+
* unchanged.
|
|
92
|
+
*
|
|
93
|
+
* ```text
|
|
94
|
+
* <dir>/expert-pack.json # the wire pack, byte-for-byte (JSON.stringify(_, 2))
|
|
95
|
+
* <dir>/persona.md # kind === "agent" only: pack.persona.instructions
|
|
96
|
+
* <dir>/members/<id>/persona.md # kind === "team" only, one per member
|
|
97
|
+
* <dir>/skills/<name>/… # every referenced skill's files, deduplicated by id
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* Error semantics (doc `35` §6): the pack is fetched **before** anything is
|
|
101
|
+
* written, so a failed wire export leaves no directory behind; a `skill/files`
|
|
102
|
+
* rejection lands in {@link ExportResult.danglingSkills} and the loop
|
|
103
|
+
* continues; a `skill/file` rejection after a successful listing **throws** —
|
|
104
|
+
* that is a host I/O failure, and swallowing it would produce a directory that
|
|
105
|
+
* lists files it does not contain. No rollback on mid-write failure.
|
|
106
|
+
*/
|
|
107
|
+
async function materializePack(deps, pack, dir) {
|
|
108
|
+
await mkdir(dir, { recursive: true });
|
|
109
|
+
await writeFile(join(dir, "expert-pack.json"), JSON.stringify(pack, null, 2));
|
|
110
|
+
if (pack.kind === "agent") await writeFile(join(dir, "persona.md"), pack.persona.instructions);
|
|
111
|
+
for (const member of pack.team?.members ?? []) {
|
|
112
|
+
const target = join(dir, "members", member.id, "persona.md");
|
|
113
|
+
await mkdir(dirname(target), { recursive: true });
|
|
114
|
+
await writeFile(target, member.persona.instructions);
|
|
115
|
+
}
|
|
116
|
+
const refs = [...pack.skills, ...(pack.team?.members ?? []).flatMap((m) => m.skills)];
|
|
117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
118
|
+
const writtenSkills = [];
|
|
119
|
+
const danglingSkills = [];
|
|
120
|
+
for (const ref of refs) {
|
|
121
|
+
if (seen.has(ref.id)) continue;
|
|
122
|
+
seen.add(ref.id);
|
|
123
|
+
let files;
|
|
124
|
+
try {
|
|
125
|
+
files = (await deps.skills.files(ref.id)).files;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
danglingSkills.push({
|
|
128
|
+
id: ref.id,
|
|
129
|
+
error: String(error)
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const target = join(dir, "skills", ref.name, file.path);
|
|
135
|
+
await mkdir(dirname(target), { recursive: true });
|
|
136
|
+
await writeFile(target, await deps.skills.readFile(ref.id, file.path));
|
|
137
|
+
}
|
|
138
|
+
writtenSkills.push(ref.name);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
pack,
|
|
142
|
+
dir,
|
|
143
|
+
writtenSkills,
|
|
144
|
+
danglingSkills
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Export one single-agent expert and write it out as a directory.
|
|
149
|
+
*
|
|
150
|
+
* Same preconditions as the wire method: the expert must be installed and
|
|
151
|
+
* enabled, or the host answers `agent_not_installed` / `agent_disabled` /
|
|
152
|
+
* `preset_disabled`; a host whose `[expert_export]` table refuses the id
|
|
153
|
+
* answers `policy_denied` (the capability bit only reports the seam). Check
|
|
154
|
+
* `capabilities.expert_export` for the seam, not for this id.
|
|
155
|
+
*/
|
|
156
|
+
async function exportAgent(deps, agentId, dir) {
|
|
157
|
+
return materializePack(deps, await deps.agents.export(agentId), dir);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Export one expert team and write it out as a directory, leader first.
|
|
161
|
+
*
|
|
162
|
+
* All-or-nothing stays on the server: if any member is uninstalled, disabled,
|
|
163
|
+
* or refused by policy, the whole call fails and nothing is written. A pack
|
|
164
|
+
* over 1 MiB fails with `response_too_large` rather than being truncated.
|
|
165
|
+
* `teamVersion` is an optional pin; a mismatch is `version_mismatch`.
|
|
166
|
+
*/
|
|
167
|
+
async function exportTeam(deps, teamId, dir, teamVersion) {
|
|
168
|
+
return materializePack(deps, await deps.teams.export(teamId, teamVersion), dir);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
68
171
|
//#region src/readiness.ts
|
|
69
172
|
/** Parse one stdout line; `null` when it is not the readiness line. */
|
|
70
173
|
function parseReadinessLine(line) {
|
|
@@ -106,11 +209,30 @@ const KILL_GRACE_MS = 2e3;
|
|
|
106
209
|
function assertProtocolCompatible(runtimeVersion) {
|
|
107
210
|
if (runtimeVersion !== APP_SERVER_PROTOCOL_VERSION) throw new Error(`protocol version mismatch: runtime speaks ${runtimeVersion}, SDK expects ${APP_SERVER_PROTOCOL_VERSION}`);
|
|
108
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* The host-policy environment the typed options above expand to.
|
|
214
|
+
*
|
|
215
|
+
* The spawned host cannot be handed backend CLI flags: `apps/agent-store` parses
|
|
216
|
+
* its own argv (`--host` / `--port` / `--data-dir` / `--no-open`) and has no
|
|
217
|
+
* `--agent-store-config`, so a flag would make clap exit before the readiness
|
|
218
|
+
* line. The parent environment is therefore the only channel a launcher has —
|
|
219
|
+
* the same conclusion `NOMI_LOG_LEVEL` already documents for the log filter.
|
|
220
|
+
*/
|
|
221
|
+
function hostPolicyEnv(options) {
|
|
222
|
+
const env = {};
|
|
223
|
+
if (options.marketDownload !== void 0) env["AGENT_STORE_MARKET_DOWNLOAD"] = options.marketDownload;
|
|
224
|
+
if (options.configPath !== void 0) env["AGENT_STORE_CONFIG"] = options.configPath;
|
|
225
|
+
return env;
|
|
226
|
+
}
|
|
109
227
|
async function spawnAppServer(options = {}) {
|
|
110
228
|
const bin = resolveAppServerBin(options.bin);
|
|
111
229
|
const ownedDir = options.dataDir === void 0;
|
|
112
230
|
const dataDir = ownedDir ? await mkdtemp(join(tmpdir(), "agent-store-sdk-")) : options.dataDir;
|
|
113
231
|
const port = options.port ?? 0;
|
|
232
|
+
const policyEnv = {
|
|
233
|
+
...hostPolicyEnv(options),
|
|
234
|
+
...options.env
|
|
235
|
+
};
|
|
114
236
|
const { child, exited } = spawnRuntimeChild(bin, [
|
|
115
237
|
"--host",
|
|
116
238
|
"127.0.0.1",
|
|
@@ -120,7 +242,11 @@ async function spawnAppServer(options = {}) {
|
|
|
120
242
|
dataDir,
|
|
121
243
|
"--no-open",
|
|
122
244
|
...options.extraArgs ?? []
|
|
123
|
-
],
|
|
245
|
+
], {
|
|
246
|
+
...Object.keys(policyEnv).length > 0 ? { env: policyEnv } : {},
|
|
247
|
+
cwd: options.cwd,
|
|
248
|
+
onExit: options.onExit
|
|
249
|
+
});
|
|
124
250
|
const stderrTail = [];
|
|
125
251
|
child.stderr?.on("data", (chunk) => {
|
|
126
252
|
for (const line of String(chunk).split(/\r?\n/)) {
|
|
@@ -309,4 +435,4 @@ async function launchHarness(options) {
|
|
|
309
435
|
}
|
|
310
436
|
}
|
|
311
437
|
//#endregion
|
|
312
|
-
export { assertProtocolCompatible, launchHarness, parseReadinessLine, resolveAppServerBin, spawnAppServer };
|
|
438
|
+
export { assertProtocolCompatible, exportAgent, exportTeam, launchHarness, materializePack, parseReadinessLine, resolveAppServerBin, spawnAppServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowy-agent-store/sdk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.9",
|
|
4
4
|
"description": "Node.js host for the Agent Store App Server: spawn the runtime binary and connect over loopback (docs/agent-store/12 P1-3)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@flowy-agent-store/client": "0.1.0-beta.
|
|
29
|
-
"@flowy-agent-store/protocol": "0.1.0-beta.
|
|
28
|
+
"@flowy-agent-store/client": "0.1.0-beta.9",
|
|
29
|
+
"@flowy-agent-store/protocol": "0.1.0-beta.9"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsdown",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"module": "./dist/index.mjs",
|
|
41
41
|
"types": "./dist/index.d.mts",
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"@flowy-agent-store/runtime-win32-x64": "0.1.0-beta.
|
|
44
|
-
"@flowy-agent-store/runtime-linux-x64": "0.1.0-beta.
|
|
45
|
-
"@flowy-agent-store/runtime-linux-arm64": "0.1.0-beta.
|
|
46
|
-
"@flowy-agent-store/runtime-darwin-x64": "0.1.0-beta.
|
|
47
|
-
"@flowy-agent-store/runtime-darwin-arm64": "0.1.0-beta.
|
|
43
|
+
"@flowy-agent-store/runtime-win32-x64": "0.1.0-beta.9",
|
|
44
|
+
"@flowy-agent-store/runtime-linux-x64": "0.1.0-beta.9",
|
|
45
|
+
"@flowy-agent-store/runtime-linux-arm64": "0.1.0-beta.9",
|
|
46
|
+
"@flowy-agent-store/runtime-darwin-x64": "0.1.0-beta.9",
|
|
47
|
+
"@flowy-agent-store/runtime-darwin-arm64": "0.1.0-beta.9"
|
|
48
48
|
}
|
|
49
49
|
}
|