@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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "project-knowledge",
12
- "version": "5.0.9-rc.12",
12
+ "version": "5.0.9-rc.13",
13
13
  "source": "./plugins/project-knowledge",
14
14
  "description": "Search durable project decisions and implementation history through MCP."
15
15
  }
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 timeoutMs = isPicker ? PICKER_TIMEOUT_MS : PROXY_TIMEOUT_MS;
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.12",
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.12",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-knowledge",
3
- "version": "5.0.9-rc.12",
3
+ "version": "5.0.9-rc.13",
4
4
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
5
5
  "author": {
6
6
  "name": "SanQianX",
@@ -6,7 +6,7 @@
6
6
  "args": [
7
7
  "-y",
8
8
  "--package",
9
- "@sanqianx/project-knowledge@5.0.9-rc.12",
9
+ "@sanqianx/project-knowledge@5.0.9-rc.13",
10
10
  "project-knowledge-mcp"
11
11
  ]
12
12
  }