@sanqianx/project-knowledge 5.0.9-rc.11 → 5.0.9-rc.13
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +23 -0
- package/_modules/knowledge-engine/README.md +1 -0
- package/_modules/knowledge-engine/package.json +1 -1
- package/_modules/knowledge-engine/src/store.js +4 -6
- package/_site/_test/module-agent-start-proxy-test.js +65 -0
- package/_site/lib/module-bridge.js +14 -2
- package/package.json +1 -1
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/vendor-manifest.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.0.9-rc.13] - 2026-10-11
|
|
4
|
+
|
|
5
|
+
- Give native Agent session creation and input setup a bounded 90-second proxy
|
|
6
|
+
budget. Ordinary API reads/abort/permissions retain 10 seconds; SSE streaming
|
|
7
|
+
and folder-picker contracts are unchanged.
|
|
8
|
+
- Record safe proxy failure categories and deadlines without raw errors or URL
|
|
9
|
+
queries. Preserve MODULE_UNREACHABLE compatibility and add optional details.
|
|
10
|
+
- Reproduce the installed OpenCode cold-start failure with a real delayed HTTP
|
|
11
|
+
server; verify the targeted budget, unrelated routes and credential redaction.
|
|
12
|
+
- Retain rc.12 real acceptance and failed OpenCode results separately; require
|
|
13
|
+
a newly downloaded candidate and cold-client retest before user upgrade.
|
|
14
|
+
|
|
15
|
+
## [5.0.9-rc.12] - 2026-10-11
|
|
16
|
+
|
|
17
|
+
- Vendor engine 0.2.3-rc.6 to address the Windows transient state-file rename
|
|
18
|
+
failure exposed by installed rc.11 fault acceptance. Use exclusive random
|
|
19
|
+
temporary files and bounded sharing retries, retain durable state on failure,
|
|
20
|
+
and never unlink live state to force replacement.
|
|
21
|
+
- Add deterministic sharing-violation, permanent-lock, foreign-temp and
|
|
22
|
+
disk-full regressions; all 39 engine tests pass on Node 22 and Node 24.
|
|
23
|
+
- Preserve the rc.11 failure and require new installed acceptance before any
|
|
24
|
+
existing-user upgrade or stable latest promotion.
|
|
25
|
+
|
|
3
26
|
## [5.0.9-rc.11] - 2026-10-11
|
|
4
27
|
|
|
5
28
|
- Pin vector-hub 1.0.8-rc.3 and vendor engine 0.2.3-rc.5. Raw commit
|
|
@@ -84,6 +84,7 @@ node src/bin.js serve [--port 5790] [--host 127.0.0.1] [--data DIR]
|
|
|
84
84
|
- 已晋升提交的 SHA 或短 SHA 在自然语言查询中仍作为标识符匹配,只返回当前文件的晋升依据;仅在原始会话中提到相同 SHA 不构成知识晋升引用。多个提交标识符可以一起查询。
|
|
85
85
|
- 候选必须有已晋升文件中的引用依据,未确认候选不能进入任务。确认记录原子持久化后写共享记忆文件,派生索引异步更新,索引失败不会撤销有效记忆。
|
|
86
86
|
- 旧格式不读取、不自动迁移。损坏的正式状态不覆盖、不清空。
|
|
87
|
+
- 引擎状态复用受控原子写入:独占随机临时文件、文件同步、Windows 短暂共享冲突有界重试;永久失败保留原状态并清理本次临时文件,绝不先删正式状态。
|
|
87
88
|
|
|
88
89
|
## 验证与尚未验收项
|
|
89
90
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanqianx/ai-coding-knowledge-engine",
|
|
3
|
-
"version": "0.2.3-rc.
|
|
3
|
+
"version": "0.2.3-rc.6",
|
|
4
4
|
"description": "Knowledge engine module for project-knowledge: commit-boundary triggered knowledge analysis/generation (trigger plane) and retrieval for development assistants (retrieval plane).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { validateProjectId } = require('./runtime/contracts');
|
|
7
7
|
const { RuntimeLayout } = require('./runtime/layout');
|
|
8
|
+
const AtomicFile = require('./runtime/atomic-file');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 单文件 JSON 状态层(<data>/engine-state.json):注册项目、分析运行、事件流。
|
|
@@ -56,12 +57,9 @@ class EngineStore {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
save() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
try { fs.writeFileSync(fd, JSON.stringify(this.state, null, 2), 'utf8'); fs.fsyncSync(fd); }
|
|
63
|
-
finally { fs.closeSync(fd); }
|
|
64
|
-
fs.renameSync(tmp, this.file);
|
|
60
|
+
// Windows scanners/readers may briefly hold the destination; never unlink
|
|
61
|
+
// durable state to force a replace, or share a fixed temp name with writers.
|
|
62
|
+
AtomicFile.writeJsonAtomic(this.file, this.state);
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
listProjects() {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const http = require('node:http');
|
|
4
|
+
const { Readable } = require('node:stream');
|
|
5
|
+
const { EventEmitter } = require('node:events');
|
|
6
|
+
const { ModuleBridge } = require('../lib/module-bridge');
|
|
7
|
+
|
|
8
|
+
async function budget(method, pathname) {
|
|
9
|
+
const timers = [], originalTimer = global.setTimeout, originalFetch = global.fetch;
|
|
10
|
+
const req = Readable.from([]); req.method = method; req.headers = {};
|
|
11
|
+
const res = new EventEmitter();
|
|
12
|
+
res.writeHead = () => {}; res.end = () => { res.writableEnded = true; };
|
|
13
|
+
try {
|
|
14
|
+
global.setTimeout = (fn, ms, ...args) => { timers.push(ms); return originalTimer(fn, ms, ...args); };
|
|
15
|
+
global.fetch = async () => new Response('{}', { headers: { 'Content-Type': 'application/json' } });
|
|
16
|
+
await new ModuleBridge().proxy(req, res, 'http://127.0.0.1:5760' + pathname);
|
|
17
|
+
return timers[0];
|
|
18
|
+
} finally { global.setTimeout = originalTimer; global.fetch = originalFetch; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
(async () => {
|
|
22
|
+
const upstream = http.createServer((req, res) => {
|
|
23
|
+
const timer = setTimeout(() => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end('{"accepted":true}'); }, 11000);
|
|
24
|
+
res.on('close', () => clearTimeout(timer));
|
|
25
|
+
});
|
|
26
|
+
await new Promise(resolve => upstream.listen(0, '127.0.0.1', resolve));
|
|
27
|
+
const bridge = new ModuleBridge();
|
|
28
|
+
const target = `http://127.0.0.1:${upstream.address().port}/api/claude-workbench/v1/sessions/cold-client/input`;
|
|
29
|
+
const shell = http.createServer((req, res) => bridge.proxy(req, res, target));
|
|
30
|
+
await new Promise(resolve => shell.listen(0, '127.0.0.1', resolve));
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(`http://127.0.0.1:${shell.address().port}/send`, { method: 'POST', body: '{}' });
|
|
33
|
+
assert.equal(response.status, 200, 'cold native client startup must not be reported as unreachable after ten seconds');
|
|
34
|
+
assert.equal((await response.json()).accepted, true);
|
|
35
|
+
} finally {
|
|
36
|
+
for (const server of [shell, upstream]) { server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }
|
|
37
|
+
}
|
|
38
|
+
assert.equal(await budget('POST', '/api/claude-workbench/v1/sessions'), 90000);
|
|
39
|
+
assert.equal(await budget('POST', '/api/claude-workbench/v1/sessions/cold-client/input?scope=project'), 90000);
|
|
40
|
+
for (const [method, route] of [
|
|
41
|
+
['GET', '/api/claude-workbench/v1/sessions'],
|
|
42
|
+
['POST', '/api/claude-workbench/v1/sessions/cold-client/abort'],
|
|
43
|
+
['POST', '/api/claude-workbench/v1/sessions/cold-client/permission'],
|
|
44
|
+
['POST', '/api/engine/runs'],
|
|
45
|
+
['POST', '/api/sessions/cold-client/input'],
|
|
46
|
+
]) assert.equal(await budget(method, route), 10000, 'unrelated APIs must keep their ordinary timeout');
|
|
47
|
+
assert.equal(await budget('POST', '/api/claude-workbench/v1/system/pick-folder'), 600000);
|
|
48
|
+
const originalFetch = global.fetch;
|
|
49
|
+
let warning, failure;
|
|
50
|
+
const request = Readable.from([]); request.method = 'GET'; request.headers = {};
|
|
51
|
+
const response = new EventEmitter(); response.writeHead = () => {};
|
|
52
|
+
response.end = bytes => { response.writableEnded = true; failure = JSON.parse(bytes); };
|
|
53
|
+
try {
|
|
54
|
+
global.fetch = async () => { throw new Error('upstream leaked credential: secret-proxy-fixture'); };
|
|
55
|
+
const safeBridge = new ModuleBridge({ logger: { warn(...args) { warning = args; } } });
|
|
56
|
+
await safeBridge.proxy(request, response, 'http://127.0.0.1:5760/api/health?apiKey=secret-proxy-fixture');
|
|
57
|
+
assert.equal(failure.error, 'MODULE_UNREACHABLE');
|
|
58
|
+
assert.equal(failure.details.failureKind, 'NETWORK_ERROR');
|
|
59
|
+
assert.equal(failure.details.timeoutMs, 10000);
|
|
60
|
+
assert.equal(warning[0], 'module.proxy.failed');
|
|
61
|
+
assert.equal(warning[2].context.path, '/api/health');
|
|
62
|
+
assert.ok(!JSON.stringify([failure, warning]).includes('secret-proxy-fixture'), 'diagnostics omit queries, raw errors and credentials');
|
|
63
|
+
} finally { global.fetch = originalFetch; }
|
|
64
|
+
console.log('module-agent-start-proxy-test PASS');
|
|
65
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -31,6 +31,9 @@ const { once } = require('events');
|
|
|
31
31
|
const { DomainError } = require('./contracts');
|
|
32
32
|
|
|
33
33
|
const PROXY_TIMEOUT_MS = 10000;
|
|
34
|
+
// Native client startup includes knowledge retrieval and a local server/CLI
|
|
35
|
+
// handshake. It has a bounded setup budget, separate from ordinary reads/SSE.
|
|
36
|
+
const AGENT_START_TIMEOUT_MS = 90000;
|
|
34
37
|
// Registration drives a full embedding pass in vector-hub (remote API); a
|
|
35
38
|
// fresh knowledge folder can easily exceed the interactive proxy budget.
|
|
36
39
|
const REGISTER_TIMEOUT_MS = 60000;
|
|
@@ -131,7 +134,11 @@ class ModuleBridge {
|
|
|
131
134
|
// The native folder pickers hold the request open until the user closes
|
|
132
135
|
// the dialog — they need a human-scale budget, not the API budget.
|
|
133
136
|
const isPicker = /\/(system\/)?pick-folder/.test(target);
|
|
134
|
-
const
|
|
137
|
+
const targetUrl = new URL(target);
|
|
138
|
+
const pathname = targetUrl.pathname;
|
|
139
|
+
const isAgentStart = req.method === 'POST' && /^\/api\/claude-workbench\/v1\/sessions(?:\/[^/]+\/input)?\/?$/.test(pathname);
|
|
140
|
+
const timeoutMs = isPicker ? PICKER_TIMEOUT_MS : isAgentStart ? AGENT_START_TIMEOUT_MS : PROXY_TIMEOUT_MS;
|
|
141
|
+
const startedAt = Date.now();
|
|
135
142
|
let upstream;
|
|
136
143
|
const controller = new AbortController();
|
|
137
144
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -148,8 +155,13 @@ class ModuleBridge {
|
|
|
148
155
|
clearTimeout(timer);
|
|
149
156
|
res.off?.('close', disconnected);
|
|
150
157
|
if (res.destroyed) return;
|
|
158
|
+
const details = { failureKind: controller.signal.aborted ? 'UPSTREAM_TIMEOUT' : 'NETWORK_ERROR', timeoutMs };
|
|
159
|
+
this.logger.warn('module.proxy.failed', '模块请求未完成,请检查连接或客户端启动。', {
|
|
160
|
+
component: 'module-bridge', phase: 'proxy', durationMs: Date.now() - startedAt,
|
|
161
|
+
context: { ...details, method: req.method, path: pathname },
|
|
162
|
+
});
|
|
151
163
|
res.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
152
|
-
res.end(JSON.stringify({ ok: false, error: 'MODULE_UNREACHABLE', target }));
|
|
164
|
+
res.end(JSON.stringify({ ok: false, error: 'MODULE_UNREACHABLE', target: targetUrl.origin + pathname, details }));
|
|
153
165
|
return;
|
|
154
166
|
}
|
|
155
167
|
const contentType = upstream.headers.get('content-type') || 'application/json; charset=utf-8';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanqianx/project-knowledge",
|
|
3
|
-
"version": "5.0.9-rc.
|
|
3
|
+
"version": "5.0.9-rc.13",
|
|
4
4
|
"description": "Knowledge base manager with Git integration, AI-driven analysis, and bilingual (zh-CN/en-US) knowledge output",
|
|
5
5
|
"main": "_site/server.js",
|
|
6
6
|
"scripts": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "project-knowledge",
|
|
4
|
-
"version": "5.0.9-rc.
|
|
4
|
+
"version": "5.0.9-rc.13",
|
|
5
5
|
"description": "Read durable project decisions and implementation history from Project Knowledge.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SanQianX",
|
package/vendor-manifest.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"modules": {
|
|
4
4
|
"knowledge-engine": {
|
|
5
5
|
"name": "@sanqianx/ai-coding-knowledge-engine",
|
|
6
|
-
"version": "0.2.3-rc.
|
|
6
|
+
"version": "0.2.3-rc.6",
|
|
7
7
|
"root": "_modules/knowledge-engine",
|
|
8
8
|
"files": {
|
|
9
|
-
"README.md": "
|
|
10
|
-
"package.json": "
|
|
9
|
+
"README.md": "efcef635df01736a8672219642a76175f3059d23b466f529140d791ec75000d5",
|
|
10
|
+
"package.json": "09e32aacd4ec5c3b596e60f3210f62515c625844e31866261c5596937195db30",
|
|
11
11
|
"src/analysis-contract.js": "fee063967d32eefc955cf0729f7dc8cb31bd690b85e22471c9059494a599c77a",
|
|
12
12
|
"src/analysis-output.js": "95a3c19dcc9ab85d1dba7fee5b9aec80f9c000dfba89da3fd0be25a81e2fb680",
|
|
13
13
|
"src/analysis-worker.js": "a105cabe6e1573b2d986b1e7a5aaf615e2fb53524156fc8e4e2c0a71f2c97e55",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"src/runtime/markdown-knowledge-indexer.js": "4d6b5b955849d274ca0614ef030c94526da7c39ab0874f02c168d0681f5b1406",
|
|
40
40
|
"src/runtime/scanner.js": "d99fa8d74a6fbbaae75ca49f62127e14cf9e48b1404e4b4d597ef39df5441410",
|
|
41
41
|
"src/server.js": "7deff466d28b94ec8975cd87ebd55c59d0aa6b49a8dfa447b552b1d32aa9c4b7",
|
|
42
|
-
"src/store.js": "
|
|
42
|
+
"src/store.js": "7dd1a60fd5338cf91ff8836c0c18f29b101dcaf17d4b9f07d07716799543f577",
|
|
43
43
|
"ui/app.css": "7ad370c269e60a7b5ec907d81a607b2d16bd07b2d7c22a81f102dab51eeb2889",
|
|
44
44
|
"ui/app.js": "33a2ba38f0dafbbb2e30a1eb3fc93476bfcaf528e124e1a9b2edd2fb69221e9e",
|
|
45
45
|
"ui/index.html": "ce3565cfefd4838b11d37a91a906ef26ca5a1834844fe3f7c897b56851a3c418"
|