@danypops/papyrus 0.42.1 → 0.42.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/client.ts +40 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.42.1",
3
+ "version": "0.42.2",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
package/src/client.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import { spawn as spawnProcess } from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
- import { connectWithPolicy, spawnDetachedDaemon } from "@danypops/vehicle-client/daemon-client";
3
+ import { connectWithVersionCheck, spawnDetachedDaemon } from "@danypops/vehicle-client/daemon-client";
4
+ import { readPackageVersion } from "@danypops/vehicle-client/version";
4
5
  import { DAEMON_CLIENT_TIMEOUT_MS, DAEMON_DIR_ENV, DAEMON_PROBE_TIMEOUT_MS } from "./constants.ts";
5
6
  import { type DaemonHandle, daemonStateDir, readDaemonHandle } from "./daemon-state.ts";
6
7
  import type { OperationName, SchemaState } from "./service.ts";
7
8
 
9
+ /** Compared against the running daemon's /health-reported version by connectWithVersionCheck below -- a long-lived daemon holds whatever code was loaded at its own start. */
10
+ const PAPYRUS_VERSION = readPackageVersion(new URL("../package.json", import.meta.url), "Papyrus");
11
+
8
12
  export type FetchAdapter = (request: Request) => Promise<Response>;
9
13
 
10
14
  export class PapyrusClient {
@@ -64,6 +68,16 @@ function papyrusCliPath(): string {
64
68
  return fileURLToPath(new URL("cli.ts", import.meta.url));
65
69
  }
66
70
 
71
+ /** connectWithVersionCheck's killStaleProcess callback, factored out for a direct unit test -- a real spawned daemon can't be made to report a mismatched version without a second build. */
72
+ export function killStalePapyrusDaemon(handle: Pick<DaemonHandle, "pid">): void {
73
+ if (handle.pid <= 0) return; // daemon-state.ts's inert "unknown pid" sentinel -- never a real process.
74
+ try {
75
+ process.kill(handle.pid, "SIGTERM");
76
+ } catch {
77
+ // Caller's handle-file poll is the real guarantee, not this call succeeding.
78
+ }
79
+ }
80
+
67
81
  export interface ConnectPapyrusClientOptions {
68
82
  /**
69
83
  * Environment passed to an auto-spawned daemon child. Defaults to the current
@@ -74,6 +88,8 @@ export interface ConnectPapyrusClientOptions {
74
88
  * silently diverge from wherever the child actually starts writing its handle.
75
89
  */
76
90
  env?: Record<string, string | undefined>;
91
+ /** Overrides the version connectWithVersionCheck compares against. Defaults to PAPYRUS_VERSION; test-only -- lets a test force a mismatch against a real daemon without a second build. */
92
+ expectedVersion?: string;
77
93
  }
78
94
 
79
95
  /**
@@ -91,23 +107,30 @@ export async function connectPapyrusClient(
91
107
  dir: string = daemonStateDir(),
92
108
  options: ConnectPapyrusClientOptions = {},
93
109
  ): Promise<PapyrusClient> {
94
- return connectWithPolicy({
95
- readHandle: () => readDaemonHandle(dir) ?? null,
96
- buildClient: probedPapyrusClient,
97
- autoStart: true,
98
- spawn: () => {
99
- spawnDetachedDaemon({
100
- binPath: papyrusCliPath(),
101
- args: ["serve"],
102
- env: { ...(options.env ?? process.env), [DAEMON_DIR_ENV]: dir },
103
- spawn: (command, args, spawnOptions) => {
104
- const child = spawnProcess(command, args, spawnOptions);
105
- child.unref();
106
- },
107
- });
110
+ return connectWithVersionCheck(
111
+ {
112
+ readHandle: () => readDaemonHandle(dir) ?? null,
113
+ buildClient: probedPapyrusClient,
114
+ autoStart: true,
115
+ spawn: () => {
116
+ spawnDetachedDaemon({
117
+ binPath: papyrusCliPath(),
118
+ args: ["serve"],
119
+ env: { ...(options.env ?? process.env), [DAEMON_DIR_ENV]: dir },
120
+ spawn: (command, args, spawnOptions) => {
121
+ const child = spawnProcess(command, args, spawnOptions);
122
+ child.unref();
123
+ },
124
+ });
125
+ },
126
+ fallbackMessage: "Papyrus daemon failed to start automatically; run `papyrus service install` or `papyrus serve` manually.",
127
+ },
128
+ {
129
+ expectedVersion: options.expectedVersion ?? PAPYRUS_VERSION,
130
+ readVersion: async (client) => (await client.health()).version,
131
+ killStaleProcess: killStalePapyrusDaemon,
108
132
  },
109
- fallbackMessage: "Papyrus daemon failed to start automatically; run `papyrus service install` or `papyrus serve` manually.",
110
- });
133
+ );
111
134
  }
112
135
 
113
136
  export interface PushChannelTarget {