@papi-ai/server 0.7.108 → 0.7.110

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 (3) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +60 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,6 +43,14 @@ PAPI collects anonymous usage data (tool name, duration, project UUID — no cod
43
43
 
44
44
  PAPI is configured with `PAPI_PROJECT_ID` and `PAPI_DATA_API_KEY` — both are generated by the onboarding wizard at [getpapi.ai](https://getpapi.ai/) and pasted into your `.mcp.json`. If those env vars aren't set, PAPI will fall back to local file storage (md mode) and emit a stderr warning that your cycles aren't visible on the dashboard. To get on the dashboard, sign up at [getpapi.ai](https://getpapi.ai/) and use the config it gives you.
45
45
 
46
+ ### Stdio idle shutdown
47
+
48
+ The stdio server exits cleanly after two minutes without input, including its
49
+ database adapter pool, so abandoned client sessions do not accumulate. Set
50
+ `PAPI_STDIO_IDLE_TIMEOUT_MS=0` to disable this behavior, or provide another
51
+ non-negative millisecond value in the `.mcp.json` environment block. This
52
+ setting applies only to stdio mode; hosted HTTP instances are not idle-killed.
53
+
46
54
  ## License
47
55
 
48
56
  [Elastic License 2.0](https://www.elastic.co/licensing/elastic-license) — free to use, self-host, and modify. Commercial hosting requires a license.
package/dist/index.js CHANGED
@@ -7558,9 +7558,10 @@ function resolveServerName(serverName) {
7558
7558
  const candidate = serverName?.trim();
7559
7559
  return candidate && SAFE_SERVER_NAME.test(candidate) ? candidate : DEFAULT_SERVER_NAME;
7560
7560
  }
7561
- function formatConnectionBanner(identity) {
7561
+ function formatConnectionBanner(identity, health = "connected") {
7562
7562
  const projectIdShort = identity.projectId ? ` (${identity.projectId.slice(0, 8)})` : "";
7563
- return `[papi] Connected \u2014 server: ${resolveServerName(identity.serverName)}, project: ${identity.projectName}${projectIdShort}, adapter: ${identity.adapterType}, v${identity.pkgVersion}`;
7563
+ const lead = health === "degraded" ? "Degraded \u2014 data may be stale" : health === "offline" ? "Offline \u2014 no database connection" : "Connected";
7564
+ return `[papi] ${lead} \u2014 server: ${resolveServerName(identity.serverName)}, project: ${identity.projectName}${projectIdShort}, adapter: ${identity.adapterType}, v${identity.pkgVersion}`;
7564
7565
  }
7565
7566
 
7566
7567
  // src/server.ts
@@ -11034,7 +11035,7 @@ function isBlockerResolved(blocker, ctx) {
11034
11035
  if (decision?.outcome && resolvedOutcomes.has(decision.outcome)) return true;
11035
11036
  if (decision?.resolutionState === "resolved") return true;
11036
11037
  const hasRecentEvent = ctx.decisionEvents.some(
11037
- (e) => idMatches(e.decisionId, blocker.ref) && e.cycle >= blocker.blockedCycle && !PROPOSAL_EVENT_TYPES.includes(e.eventType)
11038
+ (e) => idMatches(e.decisionId, blocker.ref) && e.cycle >= blocker.blockedCycle && !PROPOSAL_EVENT_TYPES.includes(e.eventType) && e.source !== "build_complete"
11038
11039
  );
11039
11040
  return hasRecentEvent;
11040
11041
  }
@@ -32781,23 +32782,23 @@ ${formatRuntimeIdentity(getRuntimeIdentity(config2, serverVersion))}`;
32781
32782
  }
32782
32783
  }
32783
32784
  function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, clientName) {
32784
- if (!shouldWriteClaudeMd(clientName)) return "";
32785
+ const targetFile = shouldWriteClaudeMd(clientName) ? "CLAUDE.md" : "AGENTS.md";
32785
32786
  if (adapterType === "proxy") {
32786
32787
  const additions2 = [];
32787
32788
  if (cycleNumber >= 6) additions2.push(CLAUDE_MD_TIER_1);
32788
32789
  if (cycleNumber >= 21) additions2.push(CLAUDE_MD_TIER_2);
32789
32790
  if (additions2.length === 0) return "";
32790
- collector.add({ path: "CLAUDE.md", content: additions2.join(""), mode: "append" });
32791
+ collector.add({ path: targetFile, content: additions2.join(""), mode: "append" });
32791
32792
  const tierNames2 = [];
32792
32793
  if (cycleNumber >= 6) tierNames2.push("Established (batch building, strategy reviews, AD lifecycle)");
32793
32794
  if (cycleNumber >= 21) tierNames2.push("Mature (idea pipeline, doc registry, advanced patterns)");
32794
32795
  return `
32795
32796
 
32796
- \u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
32797
+ \u{1F4DD} **${targetFile} enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
32797
32798
  }
32798
- const claudeMdPath = join18(projectRoot, "CLAUDE.md");
32799
- if (!existsSync11(claudeMdPath)) return "";
32800
- const content = readFileSync13(claudeMdPath, "utf-8");
32799
+ const targetPath = join18(projectRoot, targetFile);
32800
+ if (!existsSync11(targetPath)) return "";
32801
+ const content = readFileSync13(targetPath, "utf-8");
32801
32802
  const additions = [];
32802
32803
  if (cycleNumber >= 6 && !content.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1)) {
32803
32804
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_1));
@@ -32806,13 +32807,13 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
32806
32807
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_2));
32807
32808
  }
32808
32809
  if (additions.length === 0) return "";
32809
- writeFileSync7(claudeMdPath, content + additions.join(""), "utf-8");
32810
+ writeFileSync7(targetPath, content + additions.join(""), "utf-8");
32810
32811
  const tierNames = [];
32811
32812
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1))) tierNames.push("Established (batch building, strategy reviews, AD lifecycle)");
32812
32813
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T2))) tierNames.push("Mature (idea pipeline, doc registry, advanced patterns)");
32813
32814
  return `
32814
32815
 
32815
- \u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
32816
+ \u{1F4DD} **${targetFile} enriched** \u2014 added ${tierNames.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
32816
32817
  }
32817
32818
 
32818
32819
  // src/tools/hierarchy.ts
@@ -36707,6 +36708,43 @@ function shouldCheckForUpdate(version, selfHostFlag) {
36707
36708
  return version !== "unknown" && !isSelfHostedDeployment(selfHostFlag);
36708
36709
  }
36709
36710
 
36711
+ // src/lib/stdio-idle-timeout.ts
36712
+ var DEFAULT_STDIO_IDLE_TIMEOUT_MS = 12e4;
36713
+ function parseStdioIdleTimeoutMs(raw = process.env["PAPI_STDIO_IDLE_TIMEOUT_MS"]) {
36714
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_STDIO_IDLE_TIMEOUT_MS;
36715
+ const timeoutMs = Number(raw);
36716
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 0) {
36717
+ throw new Error(
36718
+ `PAPI_STDIO_IDLE_TIMEOUT_MS must be a non-negative integer in milliseconds; received ${JSON.stringify(raw)}`
36719
+ );
36720
+ }
36721
+ return timeoutMs;
36722
+ }
36723
+ function startStdioIdleTimeout(input, timeoutMs, onTimeout) {
36724
+ if (timeoutMs === 0) return () => void 0;
36725
+ let timer2;
36726
+ let stopped = false;
36727
+ const stop = () => {
36728
+ if (stopped) return;
36729
+ stopped = true;
36730
+ if (timer2 !== void 0) clearTimeout(timer2);
36731
+ input.off("data", refresh);
36732
+ };
36733
+ const refresh = () => {
36734
+ if (stopped) return;
36735
+ if (timer2 !== void 0) clearTimeout(timer2);
36736
+ timer2 = setTimeout(() => {
36737
+ if (stopped) return;
36738
+ stopped = true;
36739
+ input.off("data", refresh);
36740
+ onTimeout();
36741
+ }, timeoutMs);
36742
+ };
36743
+ input.on("data", refresh);
36744
+ refresh();
36745
+ return stop;
36746
+ }
36747
+
36710
36748
  // src/index.ts
36711
36749
  init_dist();
36712
36750
  var __dirname = dirname7(fileURLToPath5(import.meta.url));
@@ -36808,7 +36846,7 @@ async function gracefulShutdown(signal) {
36808
36846
  } catch (err) {
36809
36847
  console.error(`[papi] Adapter close failed during ${signal}: ${err instanceof Error ? err.message : String(err)}`);
36810
36848
  }
36811
- process.exit(signal === "SIGTERM" || signal === "SIGINT" ? 0 : 1);
36849
+ process.exit(signal === "SIGTERM" || signal === "SIGINT" || signal === "idle-timeout" ? 0 : 1);
36812
36850
  }
36813
36851
  process.on("SIGTERM", () => {
36814
36852
  void gracefulShutdown("SIGTERM");
@@ -36922,13 +36960,22 @@ if (isHttpMode && httpPort !== void 0) {
36922
36960
  } catch {
36923
36961
  }
36924
36962
  const transport = new StdioServerTransport();
36963
+ let stopStdioIdleTimeout = () => void 0;
36964
+ transport.onclose = () => stopStdioIdleTimeout();
36925
36965
  await server.connect(transport);
36966
+ stopStdioIdleTimeout = startStdioIdleTimeout(
36967
+ process.stdin,
36968
+ parseStdioIdleTimeoutMs(),
36969
+ () => {
36970
+ void gracefulShutdown("idle-timeout");
36971
+ }
36972
+ );
36926
36973
  process.stderr.write(`${formatConnectionBanner({
36927
36974
  serverName: config.serverName,
36928
36975
  projectName: basename2(config.projectRoot),
36929
36976
  projectId: config.projectId ?? process.env.PAPI_PROJECT_ID,
36930
36977
  adapterType: config.adapterType,
36931
36978
  pkgVersion
36932
- })}
36979
+ }, getConnectionStatus())}
36933
36980
  `);
36934
36981
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.108",
3
+ "version": "0.7.110",
4
4
  "description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
5
5
  "license": "Elastic-2.0",
6
6
  "mcpName": "io.github.getpapi/papi",