diff --git a/README.md b/README.md index 928eac0..010eb95 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,49 @@ Results are commit-anchored so any number above can be checked out and re-run. S Shared and synced memory is only useful if it cannot be silently corrupted. Skills merged from external sources (e.g. `skill sync` imports) are **treated as unproven until validated locally**: imported skills carry provenance markers, never enter the `proven` class directly, must pass canary validation on real tasks before promotion, and `anti-pattern` skills are isolated rather than deleted so they can be audited. Promotion is gated by the four-class lifecycle, not by trust in the source. See [docs/team-memory-security.md](docs/team-memory-security.md). +## Install + +Three paths. Use one. Package name: `@roarpeng/graphflow`. + +### Manual VSIX + +Download `graphflow-.vsix` from [GitHub Releases](https://github.com/Roarpeng/GraphFlow/releases) (Open VSX id `roarpeng.graphflow`). Replace `` with the file you downloaded. + +```bash +code --install-extension graphflow-.vsix +cursor --install-extension graphflow-.vsix +``` + +Activation registers MCP and copies the runtime to `~/.graphflow/runtime/`. From this repo, `npm run package:extension` writes `artifacts/graphflow-.vsix` (`vscode-extension` runs `npx @vscode/vsce package --no-dependencies`). + +### npm + +```bash +npm install -g @roarpeng/graphflow +``` + +A global install's postinstall runs `graphflow install`: MCP, skill, and the instruction block on every detected host, plus dangling-entry repair. Check or repeat it with: + +```bash +npx @roarpeng/graphflow@latest doctor +npx @roarpeng/graphflow@latest install +npx @roarpeng/graphflow install --host cursor +``` + +`install` leaves undetected hosts untouched. `--host` takes a registry id (`cursor`, `claude-code`, `codex`, …). + +### Agent prompt + +Paste this to Cursor, Claude Code, Codex, or another coding agent. It only uses the installer above: + +```text +Install @roarpeng/graphflow and configure its MCP server for this agent, then verify it. + +1. Run `npm install -g @roarpeng/graphflow`. The global postinstall runs `graphflow install`, which registers MCP, the skill, and the instruction block for every host it detects and repairs dangling entries. +2. Run `npx @roarpeng/graphflow doctor --json`. If this host is missing, run `npx @roarpeng/graphflow install --host ` with the id from that report (`cursor`, `claude-code`, or `codex`) and run doctor again. +3. It is working when doctor reports ok and this host is not missing. Restart the agent or open a new session so it loads the MCP config. +``` + ## Quick start No API key needed (offline AST indexing + graph compression): diff --git a/README.zh.md b/README.zh.md index d008b1a..074b50c 100644 --- a/README.zh.md +++ b/README.zh.md @@ -12,6 +12,49 @@ GraphFlow 把 **记忆 + hooks + skills** 做成可移植的 MCP 表面(Cursor **一条命令安装承诺**:`npm i -g @roarpeng/graphflow` = 安装 + 注册 + 检测 + 修复;VSIX 激活同样自动完成注册,并把运行时同步到稳定目录 `~/.graphflow/runtime/`,MCP 条目指向稳定路径——IDE 升级删旧扩展目录不再导致悬空。两种安装方式都是装完即用,三平台一致。 +## 安装 + +三条路径,选一条。包名:`@roarpeng/graphflow`。 + +### 手动安装 VSIX + +从 [GitHub Releases](https://github.com/Roarpeng/GraphFlow/releases) 下载 `graphflow-.vsix`(Open VSX 标识 `roarpeng.graphflow`)。把 `` 换成所下载文件的版本。 + +```bash +code --install-extension graphflow-.vsix +cursor --install-extension graphflow-.vsix +``` + +激活时扩展会注册 MCP,并把运行时复制到 `~/.graphflow/runtime/`。在本仓库执行 `npm run package:extension` 会生成 `artifacts/graphflow-.vsix`(`vscode-extension` 里实际运行 `npx @vscode/vsce package --no-dependencies`)。 + +### npm + +```bash +npm install -g @roarpeng/graphflow +``` + +全局安装的 postinstall 会执行 `graphflow install`:向每个检测到的宿主注册 MCP、Skill 和指令块,并修复悬空条目。检查或重跑: + +```bash +npx @roarpeng/graphflow@latest doctor +npx @roarpeng/graphflow@latest install +npx @roarpeng/graphflow install --host cursor +``` + +`install` 不会改写未检测到的宿主。`--host` 接受注册表 id(`cursor`、`claude-code`、`codex` 等)。 + +### 交给编程 Agent 的提示词 + +把下面这段贴给 Cursor、Claude Code、Codex 或其他编程 Agent。它只用上面的安装器: + +```text +Install @roarpeng/graphflow and configure its MCP server for this agent, then verify it. + +1. Run `npm install -g @roarpeng/graphflow`. The global postinstall runs `graphflow install`, which registers MCP, the skill, and the instruction block for every host it detects and repairs dangling entries. +2. Run `npx @roarpeng/graphflow doctor --json`. If this host is missing, run `npx @roarpeng/graphflow install --host ` with the id from that report (`cursor`, `claude-code`, or `codex`) and run doctor again. +3. It is working when doctor reports ok and this host is not missing. Restart the agent or open a new session so it loads the MCP config. +``` + ## 快速开始 ```bash diff --git a/tests/mcp-http-session-ttl.test.ts b/tests/mcp-http-session-ttl.test.ts index ecb8204..15c59e0 100644 --- a/tests/mcp-http-session-ttl.test.ts +++ b/tests/mcp-http-session-ttl.test.ts @@ -30,6 +30,17 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +/** + * JSON mode holds no SSE stream, so the sweeper treats the gap between + * handshake POSTs as idle (`openStreams` stays empty; each request only + * refreshes `lastActivityAt` when it arrives). 80ms was shorter than that + * gap on a loaded Node 20 runner, and the session was deleted inside + * `client.connect`. This override stays far below the 30-minute product + * default; after the handshake the test waits past it with no further requests. + */ +const HANDSHAKE_SAFE_TTL_MS = 3_000; +const SWEEP_INTERVAL_MS = 200; + /** * Opens one stateful session and abandons it WITHOUT the terminating DELETE * (client.close() only aborts local streams in the SDK) — the exact leak the @@ -70,21 +81,21 @@ describe("HTTP stateful session idle TTL", () => { port: 0, stateful: true, enableJsonResponse: true, - sessionTtlMs: 80, - sessionSweepIntervalMs: 25, + sessionTtlMs: HANDSHAKE_SAFE_TTL_MS, + sessionSweepIntervalMs: SWEEP_INTERVAL_MS, }); try { const sessionId = await openAbandonedSession(started); // Silent wait (polling would REFRESH lastActivityAt and defeat the - // sweep by design); one probe at 1s and a final one at 3s give the - // TTL-80ms + sweep-25ms machinery a 30x+ margin under full-suite load. + // sweep by design). The clock starts at the last handshake request, + // so this must land after HANDSHAKE_SAFE_TTL_MS plus a few sweeps. const probe = () => postJson( started.url, { jsonrpc: "2.0", id: "after-ttl", method: "ping" }, { "Mcp-Session-Id": sessionId } ); - await sleep(1_000); + await sleep(HANDSHAKE_SAFE_TTL_MS + SWEEP_INTERVAL_MS * 4); let stale = await probe(); if (stale.status !== 404) { await sleep(2_000); @@ -96,7 +107,7 @@ describe("HTTP stateful session idle TTL", () => { } finally { await started.close(); } - }, 15_000); + }, 20_000); it("keeps sweeping sessions alive while requests keep arriving (activity refresh)", async () => { const started = await startStreamableHttpServer(undefined, { @@ -165,7 +176,7 @@ describe("HTTP stateful session idle TTL", () => { it("GRAPHFLOW_HTTP_SESSION_TTL_MS env overrides the default TTL", async () => { const previous = process.env.GRAPHFLOW_HTTP_SESSION_TTL_MS; - process.env.GRAPHFLOW_HTTP_SESSION_TTL_MS = "80"; + process.env.GRAPHFLOW_HTTP_SESSION_TTL_MS = String(HANDSHAKE_SAFE_TTL_MS); let started: StartedMcpHttpServer | undefined; try { started = await startStreamableHttpServer(undefined, { @@ -173,11 +184,13 @@ describe("HTTP stateful session idle TTL", () => { port: 0, stateful: true, enableJsonResponse: true, - sessionSweepIntervalMs: 25, + sessionSweepIntervalMs: SWEEP_INTERVAL_MS, }); const sessionId = await openAbandonedSession(started); // Silent wait then two probes (polling refreshes activity by design). - await sleep(1_000); + // Same handshake margin as the explicit sessionTtlMs case: the env + // value is the idle TTL, and it must not fire between connect's POSTs. + await sleep(HANDSHAKE_SAFE_TTL_MS + SWEEP_INTERVAL_MS * 4); const probe = () => postJson( started.url, @@ -198,5 +211,5 @@ describe("HTTP stateful session idle TTL", () => { } await started?.close(); } - }, 15_000); + }, 20_000); });