@coralai/sps-cli 0.90.1 → 0.90.2

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.
Files changed (63) hide show
  1. package/assembly/base.yml +4 -4
  2. package/dist/console/index.d.ts.map +1 -1
  3. package/dist/console/index.js +0 -2
  4. package/dist/console/index.js.map +1 -1
  5. package/dist/core/pathFence.d.ts +14 -14
  6. package/dist/core/pathFence.d.ts.map +1 -1
  7. package/dist/core/pathFence.js +11 -51
  8. package/dist/core/pathFence.js.map +1 -1
  9. package/dist/daemon/sessionDaemon.d.ts +16 -3
  10. package/dist/daemon/sessionDaemon.d.ts.map +1 -1
  11. package/dist/daemon/sessionDaemon.js +16 -7
  12. package/dist/daemon/sessionDaemon.js.map +1 -1
  13. package/dist/seams/workspace/index.d.ts +40 -0
  14. package/dist/seams/workspace/index.d.ts.map +1 -0
  15. package/dist/seams/workspace/index.js +46 -0
  16. package/dist/seams/workspace/index.js.map +1 -0
  17. package/docs/help/02-commands.md +2 -2
  18. package/docs/help/05-api.md +2 -12
  19. package/package.json +2 -2
  20. package/dist/console/routes/storage.d.ts +0 -21
  21. package/dist/console/routes/storage.d.ts.map +0 -1
  22. package/dist/console/routes/storage.js +0 -115
  23. package/dist/console/routes/storage.js.map +0 -1
  24. package/dist/daemon/storageSyncLoop.d.ts +0 -9
  25. package/dist/daemon/storageSyncLoop.d.ts.map +0 -1
  26. package/dist/daemon/storageSyncLoop.js +0 -70
  27. package/dist/daemon/storageSyncLoop.js.map +0 -1
  28. package/dist/providers/storage/FakeStorage.d.ts +0 -16
  29. package/dist/providers/storage/FakeStorage.d.ts.map +0 -1
  30. package/dist/providers/storage/FakeStorage.js +0 -37
  31. package/dist/providers/storage/FakeStorage.js.map +0 -1
  32. package/dist/providers/storage/MinioBackend.d.ts +0 -41
  33. package/dist/providers/storage/MinioBackend.d.ts.map +0 -1
  34. package/dist/providers/storage/MinioBackend.js +0 -85
  35. package/dist/providers/storage/MinioBackend.js.map +0 -1
  36. package/dist/providers/storage/StorageBackend.d.ts +0 -52
  37. package/dist/providers/storage/StorageBackend.d.ts.map +0 -1
  38. package/dist/providers/storage/StorageBackend.js +0 -29
  39. package/dist/providers/storage/StorageBackend.js.map +0 -1
  40. package/dist/providers/storage/keys.d.ts +0 -33
  41. package/dist/providers/storage/keys.d.ts.map +0 -1
  42. package/dist/providers/storage/keys.js +0 -52
  43. package/dist/providers/storage/keys.js.map +0 -1
  44. package/dist/providers/storage/storageConfig.d.ts +0 -46
  45. package/dist/providers/storage/storageConfig.d.ts.map +0 -1
  46. package/dist/providers/storage/storageConfig.js +0 -94
  47. package/dist/providers/storage/storageConfig.js.map +0 -1
  48. package/dist/providers/storage/syncProject.d.ts +0 -67
  49. package/dist/providers/storage/syncProject.d.ts.map +0 -1
  50. package/dist/providers/storage/syncProject.js +0 -136
  51. package/dist/providers/storage/syncProject.js.map +0 -1
  52. package/dist/providers/storage/syncRunner.d.ts +0 -59
  53. package/dist/providers/storage/syncRunner.d.ts.map +0 -1
  54. package/dist/providers/storage/syncRunner.js +0 -119
  55. package/dist/providers/storage/syncRunner.js.map +0 -1
  56. package/dist/providers/storage/syncable.d.ts +0 -47
  57. package/dist/providers/storage/syncable.d.ts.map +0 -1
  58. package/dist/providers/storage/syncable.js +0 -84
  59. package/dist/providers/storage/syncable.js.map +0 -1
  60. package/dist/seams/storage/index.d.ts +0 -53
  61. package/dist/seams/storage/index.d.ts.map +0 -1
  62. package/dist/seams/storage/index.js +0 -69
  63. package/dist/seams/storage/index.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  title: 命令说明
3
3
  generated: true
4
4
  source: sps --help
5
- version: 0.90.1
5
+ version: 0.90.2
6
6
  ---
7
7
 
8
8
  # 命令说明
@@ -11,7 +11,7 @@ version: 0.90.1
11
11
  > 改命令说明请改命令自己的 help 文本,然后跑 `npm run gen:help`。
12
12
  > 门禁 `check:help` 会在两者不一致时报红。
13
13
 
14
- 当前版本 `0.90.1` 共 **27** 个命令。
14
+ 当前版本 `0.90.2` 共 **27** 个命令。
15
15
 
16
16
  ### `sps agent`
17
17
 
@@ -2,7 +2,7 @@
2
2
  title: 开放 API
3
3
  generated: true
4
4
  source: src/console/routes/*.ts
5
- version: 0.90.1
5
+ version: 0.90.2
6
6
  ---
7
7
 
8
8
  # 开放 API
@@ -10,7 +10,7 @@ version: 0.90.1
10
10
  > ⚠️ **本页由 `scripts/gen-help-api.mjs` 从路由代码扫出,不要手改。**
11
11
  > 门禁 `check:help` 会在代码与本页不一致时报红。
12
12
 
13
- 共 **175** 条路由(其中 110 条尚无说明)。版本 `0.90.1`。
13
+ 共 **170** 条路由(其中 110 条尚无说明)。版本 `0.90.2`。
14
14
 
15
15
  > ℹ️ 口径:这里**逐条列**(同一路径的不同方法算两条)。门禁 `check:api-docs` 报的是
16
16
  > **归一去重后的唯一路径数**,两个数字不相等是正常的 —— 别拿它们互相"对账"。
@@ -299,16 +299,6 @@ version: 0.90.1
299
299
  | `POST` | `/api/skills/sync` | — |
300
300
  | `GET` | `/api/skills/links/:project` | — |
301
301
 
302
- ### /api/storage · storage
303
-
304
- | 方法 | 路径 | 说明 |
305
- |---|---|---|
306
- | `GET` | `/api/storage` | * 当前配置(**脱敏**) |
307
- | `PUT` | `/api/storage` | 整体替换。缺字段即非法 —— 不做"沿用旧值"的合并,理由见模块头。 |
308
- | `GET` | `/api/storage/active` | * 同步路径今天生效吗 —— 给运维/自检用的**一个字段**,免得从三种状态自己推 |
309
- | `GET` | `/api/storage/scope` | * 同步范围声明(**按产品**)。内容由平台写入,sps 只执行 —— * 🔴 这不违反"配置里不放同步什么":那一条禁的是 **sps 把产品知识写死在代码里**, * 而这里是**数据**。反面教材是 coral sidecar 用 |
310
- | `PUT` | `/api/storage/scope` | 整体替换。缺声明的产品走窄兜底(只同步 `assets/`),而**用了兜底这件事会进日志**。 |
311
-
312
302
  ### /api/subagent · subagent
313
303
 
314
304
  | 方法 | 路径 | 说明 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralai/sps-cli",
3
- "version": "0.90.1",
3
+ "version": "0.90.2",
4
4
  "description": "SPS CLI — AI-driven development pipeline orchestrator",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -82,7 +82,7 @@
82
82
  "dependencies": {
83
83
  "@anthropic-ai/claude-agent-sdk": "^0.3.196",
84
84
  "@colbymchenry/codegraph": "^1.2.0",
85
- "@coralai/sps-plugin-api": "^0.12.0",
85
+ "@coralai/sps-plugin-api": "^0.13.0",
86
86
  "@deepseek-ai/cordis": "^4.0.1",
87
87
  "@deepseek-ai/cordis-plugin-include": "^1.0.6",
88
88
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
@@ -1,21 +0,0 @@
1
- /**
2
- * @module console/routes/storage
3
- * @description 对象存储接入的配置面(节点级)。设计见 `object-storage-and-workspace-sync.md` §2.1。
4
- *
5
- * @layer console
6
- *
7
- * 🔴 **读侧永不回显凭据。** `GET` 只回 `redact()` 的形状 —— 与 `GET /:project/agent` 同一条规矩:
8
- * 读者是消费方,不是密钥的所有者。这条不是洁癖:今天刚清掉 11 个世界可读的 pm2 env dump,
9
- * 它们含活 token 且躺了 9 天。**凭据每多经过一个面,就多一个会被写进日志的地方。**
10
- *
11
- * 🔴 **`PUT` 是整体替换,不是合并。** 合并语义会让"删掉一个字段"变成不可能表达的操作
12
- * (与 `PUT /:project/media-providers` 同一条)。
13
- *
14
- * ⚠️ 本路由把"去哪儿"(`/`)与"同步什么"(`/scope`)**分成两个端点**,不是混在一个配置里。
15
- * "同步什么"是**平台写入的数据**(按产品),sps 一个产品名都不硬编码 ——
16
- * 禁的是 sps 把产品知识写死在**代码**里(coral sidecar 用「含 assets/ 目录」猜产品语义,
17
- * 静默漏掉 36 个设计师阶段项目),不是禁它接受声明。
18
- */
19
- import { Hono } from 'hono';
20
- export declare function createStorageRoute(): Hono;
21
- //# sourceMappingURL=storage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/console/routes/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAc5B,wBAAgB,kBAAkB,IAAI,IAAI,CAiGzC"}
@@ -1,115 +0,0 @@
1
- /**
2
- * @module console/routes/storage
3
- * @description 对象存储接入的配置面(节点级)。设计见 `object-storage-and-workspace-sync.md` §2.1。
4
- *
5
- * @layer console
6
- *
7
- * 🔴 **读侧永不回显凭据。** `GET` 只回 `redact()` 的形状 —— 与 `GET /:project/agent` 同一条规矩:
8
- * 读者是消费方,不是密钥的所有者。这条不是洁癖:今天刚清掉 11 个世界可读的 pm2 env dump,
9
- * 它们含活 token 且躺了 9 天。**凭据每多经过一个面,就多一个会被写进日志的地方。**
10
- *
11
- * 🔴 **`PUT` 是整体替换,不是合并。** 合并语义会让"删掉一个字段"变成不可能表达的操作
12
- * (与 `PUT /:project/media-providers` 同一条)。
13
- *
14
- * ⚠️ 本路由把"去哪儿"(`/`)与"同步什么"(`/scope`)**分成两个端点**,不是混在一个配置里。
15
- * "同步什么"是**平台写入的数据**(按产品),sps 一个产品名都不硬编码 ——
16
- * 禁的是 sps 把产品知识写死在**代码**里(coral sidecar 用「含 assets/ 目录」猜产品语义,
17
- * 静默漏掉 36 个设计师阶段项目),不是禁它接受声明。
18
- */
19
- import { Hono } from 'hono';
20
- import { activeStorageConfig, readStorageConfig, redact, writeStorageConfig, } from '../../providers/storage/storageConfig.js';
21
- import { readSyncScope, writeSyncScope, } from '../../providers/storage/syncScope.js';
22
- export function createStorageRoute() {
23
- const app = new Hono();
24
- /**
25
- * 当前配置(**脱敏**)。
26
- *
27
- * 三种状态必须可区分,别合并成一个 `enabled: false`:
28
- * ```
29
- * configured:false 没配过 ← 引导去配
30
- * configured:true enabled:false 配了但关着 ← 有意关掉,别劝
31
- * configured:true enabled:true 在用
32
- * ```
33
- */
34
- app.get('/', (c) => {
35
- const cfg = readStorageConfig();
36
- if (!cfg)
37
- return c.json({ data: { configured: false } });
38
- return c.json({ data: { configured: true, ...redact(cfg) } });
39
- });
40
- /** 整体替换。缺字段即非法 —— 不做"沿用旧值"的合并,理由见模块头。 */
41
- app.put('/', async (c) => {
42
- const body = (await c.req.json().catch(() => null));
43
- const invalid = (detail) => c.json({ type: 'validation', title: 'invalid storage config', status: 422, detail }, 422);
44
- if (!body || typeof body !== 'object' || Array.isArray(body))
45
- return invalid('body must be an object');
46
- for (const k of ['endpoint', 'bucket', 'accessKey', 'secretKey']) {
47
- if (typeof body[k] !== 'string' || !body[k]?.trim())
48
- return invalid(`${k} required (non-empty string)`);
49
- }
50
- // 端点一律当**不透明**:只认 http/https,不解析、不推断是哪家存储。
51
- // 与 `POST /api/templates/register` 的 URL 判据同一句 —— 挡掉 file:// 之类把它变成本地读写的形态。
52
- let parsed;
53
- try {
54
- parsed = new URL(body.endpoint);
55
- }
56
- catch {
57
- return invalid(`endpoint 不是合法 URL:${body.endpoint}`);
58
- }
59
- if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
60
- return invalid(`endpoint 只支持 http/https,收到 ${parsed.protocol}`);
61
- }
62
- const cfg = {
63
- endpoint: body.endpoint.trim(),
64
- bucket: body.bucket.trim(),
65
- accessKey: body.accessKey.trim(),
66
- secretKey: body.secretKey.trim(),
67
- region: typeof body.region === 'string' && body.region.trim() ? body.region.trim() : 'us-east-1',
68
- pathStyle: body.pathStyle !== false,
69
- // 🔴 默认关:没显式给 true 就是关。缺省被解释成"最宽松"是最危险的缺省。
70
- enabled: body.enabled === true,
71
- };
72
- writeStorageConfig(cfg);
73
- return c.json({ data: { configured: true, ...redact(cfg) } });
74
- });
75
- /**
76
- * 同步路径今天生效吗 —— 给运维/自检用的**一个字段**,免得从三种状态自己推。
77
- * 🔴 它回的是"有没有活配置",**不是**"桶连得上":连通性要真发一次请求才知道,
78
- * 那是另一个动作(见 §2.1 任务 2 的验收),别让这个端点看起来像在报连通性。
79
- */
80
- app.get('/active', (c) => c.json({ data: { active: activeStorageConfig() !== null } }));
81
- /**
82
- * 同步范围声明(**按产品**)。内容由平台写入,sps 只执行 ——
83
- * 🔴 这不违反"配置里不放同步什么":那一条禁的是 **sps 把产品知识写死在代码里**,
84
- * 而这里是**数据**。反面教材是 coral sidecar 用「目录下含 assets/」猜产品语义,
85
- * 静默漏掉 36 个设计师阶段项目。
86
- */
87
- app.get('/scope', (c) => {
88
- const scope = readSyncScope();
89
- return c.json({ data: scope ?? { products: {} }, declared: scope !== null });
90
- });
91
- /** 整体替换。缺声明的产品走窄兜底(只同步 `assets/`),而**用了兜底这件事会进日志**。 */
92
- app.put('/scope', async (c) => {
93
- const body = (await c.req.json().catch(() => null));
94
- const invalid = (detail) => c.json({ type: 'validation', title: 'invalid sync scope', status: 422, detail }, 422);
95
- if (!body || typeof body.products !== 'object' || body.products === null || Array.isArray(body.products)) {
96
- return invalid('products must be an object keyed by product name');
97
- }
98
- const products = {};
99
- for (const [name, v] of Object.entries(body.products)) {
100
- const inc = v?.include;
101
- if (!Array.isArray(inc) || !inc.every((x) => typeof x === 'string' && x.trim())) {
102
- return invalid(`products.${name}.include must be a non-empty string[]`);
103
- }
104
- // 通配不支持:带上了什么没人会去 diff(coral 选具名白名单的理由,实测有效)
105
- const star = inc.find((x) => x.includes('*'));
106
- if (star)
107
- return invalid(`不支持通配:products.${name}.include 里的 ${star}`);
108
- products[name] = { include: inc };
109
- }
110
- writeSyncScope({ products });
111
- return c.json({ data: { products } });
112
- });
113
- return app;
114
- }
115
- //# sourceMappingURL=storage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/console/routes/storage.ts"],"names":[],"mappings":"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"}
@@ -1,9 +0,0 @@
1
- export interface StorageSyncLoopHandle {
2
- stop(): void;
3
- }
4
- /**
5
- * 起循环。**返回 handle 而不是 never** —— daemon 关停时要能停掉它,
6
- * 否则进程退不干净(而"退不干净"的表现是重启时端口/socket 还被占着)。
7
- */
8
- export declare function startStorageSyncLoop(log: (line: string) => void): StorageSyncLoopHandle;
9
- //# sourceMappingURL=storageSyncLoop.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"storageSyncLoop.d.ts","sourceRoot":"","sources":["../../src/daemon/storageSyncLoop.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,qBAAqB;IACpC,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,qBAAqB,CAqCvF"}
@@ -1,70 +0,0 @@
1
- /**
2
- * @module daemon/storageSyncLoop
3
- * @description 把工作区 → 桶的对账同步挂进 daemon 的常驻循环。
4
- *
5
- * @layer daemon
6
- *
7
- * 🔴 **为什么在 daemon 而不是 console**:console 是**共享事件循环**,同步要读整棵树、
8
- * 算 md5、缩略图、发网络请求 —— 跑在请求路径里会阻塞 `/api/health` 探针与 SSE 心跳。
9
- * > 判据:**同步在移交件里是对的 —— 它本来跑在 CLI 里;换了宿主之后才变错。**
10
- *
11
- * 🔴 **一轮没跑完不开下一轮。** 用 `setTimeout` 自排,不用 `setInterval`:
12
- * `setInterval` 在一轮变慢时会叠罗汉 —— 两轮并发对同一个前缀列举 + 上传,
13
- * 表现是"同步好像更忙了",实际是互相重复做功,而且**没有任何报错**。
14
- *
15
- * 🔴 **未配置 ⇒ 一行日志都不打。** 未配置是合法状态(sps 是通用工具,别人装它不需要对象存储);
16
- * 每 N 分钟打一次"未配置"会把真正的告警淹掉。
17
- */
18
- import { workspacesRoot } from '../core/workspacePaths.js';
19
- import { activeStorageConfig } from '../providers/storage/storageConfig.js';
20
- import { runSyncPass } from '../providers/storage/syncRunner.js';
21
- /** 轮间隔。与 coral sidecar 同量级(它是分钟级);太密只会重复列举,收益为零。 */
22
- const DEFAULT_INTERVAL_MS = 5 * 60_000;
23
- /** 只同步近 N 天动过的项目。0 = 全部(首轮全量迁移时用)。 */
24
- const DEFAULT_SINCE_DAYS = 30;
25
- /** 单文件上限:显式给,不靠运气。 */
26
- const DEFAULT_MAX_BYTES = 64 * 1024 * 1024;
27
- /**
28
- * 起循环。**返回 handle 而不是 never** —— daemon 关停时要能停掉它,
29
- * 否则进程退不干净(而"退不干净"的表现是重启时端口/socket 还被占着)。
30
- */
31
- export function startStorageSyncLoop(log) {
32
- const intervalMs = Number(process.env.SPS_STORAGE_SYNC_INTERVAL_MS) || DEFAULT_INTERVAL_MS;
33
- const sinceDays = process.env.SPS_STORAGE_SYNC_SINCE_DAYS !== undefined
34
- ? Number(process.env.SPS_STORAGE_SYNC_SINCE_DAYS)
35
- : DEFAULT_SINCE_DAYS;
36
- const maxBytes = Number(process.env.SPS_STORAGE_SYNC_MAX_BYTES) || DEFAULT_MAX_BYTES;
37
- let timer = null;
38
- let stopped = false;
39
- let announced = false;
40
- const tick = async () => {
41
- if (stopped)
42
- return;
43
- try {
44
- if (activeStorageConfig() !== null && !announced) {
45
- announced = true;
46
- log(`[sps-sync] 常驻:根=${workspacesRoot()} 间隔=${intervalMs}ms 范围=近 ${sinceDays || '∞'} 天`);
47
- }
48
- await runSyncPass({ root: workspacesRoot(), sinceDays, maxBytes, log });
49
- }
50
- catch (e) {
51
- // 同步器**不能因为一轮失败就死掉**;但原因要留下。
52
- log(`[sps-sync] 本轮异常:${e instanceof Error ? e.message : String(e)}`);
53
- }
54
- finally {
55
- if (!stopped)
56
- timer = setTimeout(() => void tick(), intervalMs);
57
- }
58
- };
59
- // 起步不立刻跑:daemon 启动那一刻正忙着恢复会话,别抢 IO。
60
- timer = setTimeout(() => void tick(), 10_000);
61
- return {
62
- stop() {
63
- stopped = true;
64
- if (timer)
65
- clearTimeout(timer);
66
- timer = null;
67
- },
68
- };
69
- }
70
- //# sourceMappingURL=storageSyncLoop.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"storageSyncLoop.js","sourceRoot":"","sources":["../../src/daemon/storageSyncLoop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,oDAAoD;AACpD,MAAM,mBAAmB,GAAG,CAAC,GAAG,MAAM,CAAC;AACvC,sCAAsC;AACtC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,sBAAsB;AACtB,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAM3C;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAA2B;IAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,IAAI,mBAAmB,CAAC;IAC3F,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,SAAS;QACrE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;QACjD,CAAC,CAAC,kBAAkB,CAAC;IACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,iBAAiB,CAAC;IAErF,IAAI,KAAK,GAAyC,IAAI,CAAC;IACvD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,IAAI,OAAO;YAAE,OAAO;QACpB,IAAI,CAAC;YACH,IAAI,mBAAmB,EAAE,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjD,SAAS,GAAG,IAAI,CAAC;gBACjB,GAAG,CAAC,mBAAmB,cAAc,EAAE,OAAO,UAAU,WAAW,SAAS,IAAI,GAAG,IAAI,CAAC,CAAC;YAC3F,CAAC;YACD,MAAM,WAAW,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,6BAA6B;YAC7B,GAAG,CAAC,mBAAmB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO;gBAAE,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC;IAEF,oCAAoC;IACpC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IAE9C,OAAO;QACL,IAAI;YACF,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,16 +0,0 @@
1
- import { type StorageBackend, type StorageObject } from './StorageBackend.js';
2
- export declare class FakeStorage implements StorageBackend {
3
- readonly objects: Map<string, {
4
- bytes: Buffer;
5
- contentType?: string;
6
- etag: string;
7
- }>;
8
- putCalls: string[];
9
- /** 下一次 put 到这个 key 时抛错(模拟上传失败)。 */
10
- failNext: string | null;
11
- put(key: string, bytes: Buffer, contentType?: string): Promise<void>;
12
- list(prefix: string): Promise<StorageObject[]>;
13
- /** 没有这个 key ⇒ `null`(与真后端同语义:不抛)。 */
14
- get(key: string): Promise<Buffer | null>;
15
- }
16
- //# sourceMappingURL=FakeStorage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FakeStorage.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/FakeStorage.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAA2B,MAAM,qBAAqB,CAAC;AAEvG,qBAAa,WAAY,YAAW,cAAc;IAChD,QAAQ,CAAC,OAAO;eAA4B,MAAM;sBAAgB,MAAM;cAAQ,MAAM;OAAM;IAC5F,QAAQ,EAAE,MAAM,EAAE,CAAM;IACxB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAMpD,qCAAqC;IAC/B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAG/C"}
@@ -1,37 +0,0 @@
1
- /**
2
- * @module providers/storage/FakeStorage
3
- * @description `StorageBackend` 的内存假实现。**它的存在本身就是"接口中性"的证据** ——
4
- * 一个不认识 minio 的实现能满足整个接口,说明上层没有漏抽象。
5
- *
6
- * @layer providers
7
- *
8
- * ⚠️ 放在 `.test.ts` 之外是因为 lint 禁止从测试文件导出(`noExportsInTest`),
9
- * 而它被两个用例文件共用。**共用的测试替身要有自己的落点**,复制两份才是问题。
10
- */
11
- import { createHash } from 'node:crypto';
12
- import { StorageUnavailableError } from './StorageBackend.js';
13
- export class FakeStorage {
14
- objects = new Map();
15
- putCalls = [];
16
- /** 下一次 put 到这个 key 时抛错(模拟上传失败)。 */
17
- failNext = null;
18
- async put(key, bytes, contentType) {
19
- if (this.failNext === key) {
20
- this.failNext = null;
21
- throw new StorageUnavailableError(`假的上传失败:${key}`);
22
- }
23
- this.putCalls.push(key);
24
- // etag 用 md5:与单段上传的真实语义一致,否则用例会掩盖"分块上传 etag 不是 md5"那个坑
25
- this.objects.set(key, { bytes, contentType, etag: createHash('md5').update(bytes).digest('hex') });
26
- }
27
- async list(prefix) {
28
- return [...this.objects.entries()]
29
- .filter(([k]) => k.startsWith(prefix))
30
- .map(([key, v]) => ({ key, etag: v.etag, size: v.bytes.length }));
31
- }
32
- /** 没有这个 key ⇒ `null`(与真后端同语义:不抛)。 */
33
- async get(key) {
34
- return this.objects.get(key)?.bytes ?? null;
35
- }
36
- }
37
- //# sourceMappingURL=FakeStorage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FakeStorage.js","sourceRoot":"","sources":["../../../src/providers/storage/FakeStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAA2C,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAEvG,MAAM,OAAO,WAAW;IACb,OAAO,GAAG,IAAI,GAAG,EAAiE,CAAC;IAC5F,QAAQ,GAAa,EAAE,CAAC;IACxB,mCAAmC;IACnC,QAAQ,GAAkB,IAAI,CAAC;IAE/B,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,WAAoB;QACxD,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,MAAM,IAAI,uBAAuB,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,uDAAuD;QACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrG,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aACrC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC;IAC9C,CAAC;CACF"}
@@ -1,41 +0,0 @@
1
- /**
2
- * @module providers/storage/MinioBackend
3
- * @description `StorageBackend` 的第一个实现:S3 兼容(MinIO)。
4
- *
5
- * @layer providers
6
- *
7
- * 🔴 **依赖走 `optionalDependencies` + 动态 import。**
8
- * `@coralai/sps-cli` 是公共 npm 包,别人装它不需要对象存储。
9
- * 没装 ⇒ 抛 `StorageUnavailableError` 并**指名说"未安装"**,
10
- * **绝不静默降级**成"这轮没东西同步" —— 那会让"功能没装"伪装成"没有变化"。
11
- *
12
- * ⚠️ 这里是**唯一**允许出现 minio 类型的文件。上层只认 `StorageBackend`。
13
- * 自检:换掉本文件,上层用例应当全绿(`storageBackend.test.ts` 就是拿假实现证明这一点的)。
14
- */
15
- import type { StorageBackend, StorageObject } from './StorageBackend.js';
16
- import type { StorageConfig } from './storageConfig.js';
17
- /**
18
- * `etag` 从 MinIO 回来时**带引号**(`"d41d8…"`),而本地算的 md5 不带。
19
- * 不剥掉的话每一轮对账都判"变了" ⇒ **每轮重传全部文件**,而且完全不报错 ——
20
- * 表现是"同步一直在工作",只是流量和耗时莫名其妙。
21
- */
22
- export declare function normalizeEtag(raw: unknown): string;
23
- export declare class MinioBackend implements StorageBackend {
24
- private readonly cfg;
25
- private client;
26
- constructor(cfg: StorageConfig);
27
- private ensure;
28
- put(key: string, bytes: Buffer, contentType?: string): Promise<void>;
29
- list(prefix: string): Promise<StorageObject[]>;
30
- /**
31
- * 读一个对象。
32
- *
33
- * 🔴 **"没有这个 key" 与 "读不了" 必须分开**:
34
- * 前者返回 `null`(调用方据此走兜底),后者抛 `StorageUnavailableError`。
35
- * 混成一个的表现是"库是空的" —— 而真相可能是**权限不够**,
36
- * 那会让人去补素材而不是去补策略。
37
- * MinIO 的 NoSuchKey 走 code,不看消息文本(消息会随版本变)。
38
- */
39
- get(key: string): Promise<Buffer | null>;
40
- }
41
- //# sourceMappingURL=MinioBackend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MinioBackend.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/MinioBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAexD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAElD;AAED,qBAAa,YAAa,YAAW,cAAc;IAGrC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,MAAM,CAA0B;gBAEX,GAAG,EAAE,aAAa;YAEjC,MAAM;IAwBd,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAapE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAiBpD;;;;;;;;OAQG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAa/C"}
@@ -1,85 +0,0 @@
1
- import { StorageUnavailableError } from './StorageBackend.js';
2
- /**
3
- * `etag` 从 MinIO 回来时**带引号**(`"d41d8…"`),而本地算的 md5 不带。
4
- * 不剥掉的话每一轮对账都判"变了" ⇒ **每轮重传全部文件**,而且完全不报错 ——
5
- * 表现是"同步一直在工作",只是流量和耗时莫名其妙。
6
- */
7
- export function normalizeEtag(raw) {
8
- return String(raw ?? '').replace(/^"|"$/g, '');
9
- }
10
- export class MinioBackend {
11
- cfg;
12
- client = null;
13
- constructor(cfg) {
14
- this.cfg = cfg;
15
- }
16
- async ensure() {
17
- if (this.client)
18
- return this.client;
19
- let mod;
20
- try {
21
- mod = (await import('minio'));
22
- }
23
- catch (cause) {
24
- throw new StorageUnavailableError("对象存储后端未安装:缺可选依赖 'minio'。安装它(npm i minio)或关掉 ~/.coral/storage.json 的 enabled。", cause);
25
- }
26
- const u = new URL(this.cfg.endpoint);
27
- this.client = new mod.Client({
28
- endPoint: u.hostname,
29
- port: u.port ? Number(u.port) : u.protocol === 'https:' ? 443 : 80,
30
- useSSL: u.protocol === 'https:',
31
- accessKey: this.cfg.accessKey,
32
- secretKey: this.cfg.secretKey,
33
- region: this.cfg.region ?? 'us-east-1',
34
- pathStyle: this.cfg.pathStyle ?? true,
35
- });
36
- return this.client;
37
- }
38
- async put(key, bytes, contentType) {
39
- const c = await this.ensure();
40
- // 显式给 size:不给的话客户端会走分块流式上传,而**分块上传的 etag 不是 md5**
41
- // ⇒ 对账判据(etag == 本地 md5)当场失效,且失效方式是"永远判为不同"。
42
- await c.putObject(this.cfg.bucket, key, bytes, bytes.length, contentType ? { 'Content-Type': contentType } : undefined);
43
- }
44
- async list(prefix) {
45
- const c = await this.ensure();
46
- const stream = c.listObjectsV2(this.cfg.bucket, prefix, true);
47
- return await new Promise((resolve, reject) => {
48
- const out = [];
49
- stream.on('data', (o) => {
50
- // 目录占位项没有 name/etag(MinIO 会回"前缀"条目);跳过它们,别当成对象。
51
- if (!o.name)
52
- return;
53
- out.push({ key: o.name, etag: normalizeEtag(o.etag), size: o.size ?? 0 });
54
- });
55
- stream.on('end', () => resolve(out));
56
- stream.on('error', (e) => reject(new StorageUnavailableError(`列举失败:${prefix}`, e)));
57
- });
58
- }
59
- /**
60
- * 读一个对象。
61
- *
62
- * 🔴 **"没有这个 key" 与 "读不了" 必须分开**:
63
- * 前者返回 `null`(调用方据此走兜底),后者抛 `StorageUnavailableError`。
64
- * 混成一个的表现是"库是空的" —— 而真相可能是**权限不够**,
65
- * 那会让人去补素材而不是去补策略。
66
- * MinIO 的 NoSuchKey 走 code,不看消息文本(消息会随版本变)。
67
- */
68
- async get(key) {
69
- const c = await this.ensure();
70
- try {
71
- const stream = await c.getObject(this.cfg.bucket, key);
72
- const chunks = [];
73
- for await (const ch of stream)
74
- chunks.push(ch);
75
- return Buffer.concat(chunks);
76
- }
77
- catch (e) {
78
- const code = e.code;
79
- if (code === 'NoSuchKey' || code === 'NotFound')
80
- return null;
81
- throw new StorageUnavailableError(`读取失败:${key}`, e);
82
- }
83
- }
84
- }
85
- //# sourceMappingURL=MinioBackend.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MinioBackend.js","sourceRoot":"","sources":["../../../src/providers/storage/MinioBackend.ts"],"names":[],"mappings":"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"}
@@ -1,52 +0,0 @@
1
- /**
2
- * @module providers/storage/StorageBackend
3
- * @description 对象存储的**中性接口**。换一个后端(MinIO / S3 / WebDAV / 别的 HTTP 端点),
4
- * 上层几乎不用改 —— 与 `AgentBackend` / `RepoBackend` / `MediaProvider` 同一条自检判据。
5
- *
6
- * @layer providers
7
- *
8
- * 🔴 **接口里不出现任何一家存储的类型或词汇。** 这是"独立性"的全部来源:
9
- * 独立性来自**接口中性**,不来自代码住在哪个包里。
10
- * (§2.1:插件 ≠ 解耦。解耦是接口的性质,插件是分发的性质。)
11
- *
12
- * 🔴 **只有 put 与 list 两个动作,而且没有 delete。**
13
- * · 没有 delete 是**有意的**:同步这条路的不变量是「**不删**」——
14
- * 本地文件没了不等于用户要删桶里那份(工作区可能被重建、节点可能换了)。
15
- * 接口上不给这个动作,比在实现里"记得别调"可靠。
16
- * (同族:授权中心也**不该有 DeleteObject**,理由是 artifacts 未版本化 ⇒ 误删不可恢复。)
17
- * · 没有 `stat` 是因为对账用 `list` 一次拿全前缀更省往返,而且**对账基准必须是桶本身**;
18
- * 逐个 stat 会诱使实现方去缓存"上次看到的",那正是要避免的形态。
19
- */
20
- /** 桶里一个对象的对账信息。**etag 是内容判据**(单段上传时等于 md5),不看时间。 */
21
- export interface StorageObject {
22
- key: string;
23
- etag: string;
24
- size: number;
25
- }
26
- export interface StorageBackend {
27
- /**
28
- * 写一个对象(覆盖同 key)。
29
- *
30
- * 🔴 `contentType` 不是可选的装饰:桶是**直连**的,没有一层服务器可以在返回时补救 ——
31
- * 缺省的 `application/octet-stream` 会让预览图在浏览器里变成一次下载。
32
- * 所以推的时候就得写对;拿不准时**由调用方决定**,后端不猜。
33
- */
34
- put(key: string, bytes: Buffer, contentType?: string): Promise<void>;
35
- /** 列一个前缀下的全部对象(递归)。前缀为空即整桶 —— 调用方自己收窄。 */
36
- list(prefix: string): Promise<StorageObject[]>;
37
- /**
38
- * 读一个对象。
39
- *
40
- * 🔴 **不存在返回 `null`,不抛** —— "有没有"是正常的问题
41
- * (占位兜底就是先问"库里有没有")。而**连不上/没权限要抛**
42
- * `StorageUnavailableError`:那两件事和"这个 key 没有"完全不同,
43
- * 混在一起的表现是"库是空的",而真相是这个 key 读不了。
44
- */
45
- get(key: string): Promise<Buffer | null>;
46
- }
47
- /** 后端不可用时抛这个,**不要静默降级**成"这轮没东西同步"。 */
48
- export declare class StorageUnavailableError extends Error {
49
- readonly cause?: unknown | undefined;
50
- constructor(message: string, cause?: unknown | undefined);
51
- }
52
- //# sourceMappingURL=StorageBackend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"StorageBackend.d.ts","sourceRoot":"","sources":["../../../src/providers/storage/StorageBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE,0CAA0C;IAC1C,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAE/C;;;;;;;OAOG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1C;AAED,sCAAsC;AACtC,qBAAa,uBAAwB,SAAQ,KAAK;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAAzC,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,YAAA;CAItD"}
@@ -1,29 +0,0 @@
1
- /**
2
- * @module providers/storage/StorageBackend
3
- * @description 对象存储的**中性接口**。换一个后端(MinIO / S3 / WebDAV / 别的 HTTP 端点),
4
- * 上层几乎不用改 —— 与 `AgentBackend` / `RepoBackend` / `MediaProvider` 同一条自检判据。
5
- *
6
- * @layer providers
7
- *
8
- * 🔴 **接口里不出现任何一家存储的类型或词汇。** 这是"独立性"的全部来源:
9
- * 独立性来自**接口中性**,不来自代码住在哪个包里。
10
- * (§2.1:插件 ≠ 解耦。解耦是接口的性质,插件是分发的性质。)
11
- *
12
- * 🔴 **只有 put 与 list 两个动作,而且没有 delete。**
13
- * · 没有 delete 是**有意的**:同步这条路的不变量是「**不删**」——
14
- * 本地文件没了不等于用户要删桶里那份(工作区可能被重建、节点可能换了)。
15
- * 接口上不给这个动作,比在实现里"记得别调"可靠。
16
- * (同族:授权中心也**不该有 DeleteObject**,理由是 artifacts 未版本化 ⇒ 误删不可恢复。)
17
- * · 没有 `stat` 是因为对账用 `list` 一次拿全前缀更省往返,而且**对账基准必须是桶本身**;
18
- * 逐个 stat 会诱使实现方去缓存"上次看到的",那正是要避免的形态。
19
- */
20
- /** 后端不可用时抛这个,**不要静默降级**成"这轮没东西同步"。 */
21
- export class StorageUnavailableError extends Error {
22
- cause;
23
- constructor(message, cause) {
24
- super(message);
25
- this.cause = cause;
26
- this.name = 'StorageUnavailableError';
27
- }
28
- }
29
- //# sourceMappingURL=StorageBackend.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"StorageBackend.js","sourceRoot":"","sources":["../../../src/providers/storage/StorageBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiCH,sCAAsC;AACtC,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACV;IAAtC,YAAY,OAAe,EAAW,KAAe;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAU;QAEnD,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF"}