@sanqianx/project-knowledge 5.0.9-rc.12 → 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 +12 -0
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
3
15
|
## [5.0.9-rc.12] - 2026-10-11
|
|
4
16
|
|
|
5
17
|
- Vendor engine 0.2.3-rc.6 to address the Windows transient state-file rename
|
|
@@ -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",
|