@danypops/papyrus 0.38.2 → 0.38.4
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/package.json +2 -1
- package/src/client.ts +54 -5
- package/src/daemon-state.ts +12 -4
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/papyrus",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.4",
|
|
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"],
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"files": ["src", "README.md"],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@danypops/vehicle-core": "^0.1.1",
|
|
38
|
+
"@danypops/vehicle-client": "^0.1.1",
|
|
38
39
|
"@danypops/vehicle-server": "^0.3.2"
|
|
39
40
|
}
|
|
40
41
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { spawn as spawnProcess } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { connectWithPolicy, spawnDetachedDaemon } from "@danypops/vehicle-client/daemon-client";
|
|
4
|
+
import { DAEMON_CLIENT_TIMEOUT_MS, DAEMON_DIR_ENV, DAEMON_PROBE_TIMEOUT_MS } from "./constants.ts";
|
|
5
|
+
import { daemonStateDir, readDaemonHandle, type DaemonHandle } from "./daemon-state.ts";
|
|
3
6
|
import type { OperationName, SchemaState } from "./service.ts";
|
|
4
7
|
|
|
5
8
|
export type FetchAdapter = (request: Request) => Promise<Response>;
|
|
@@ -46,9 +49,7 @@ export class PapyrusClient {
|
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
const handle = readDaemonHandle(dir);
|
|
51
|
-
if (!handle) throw new Error("Papyrus daemon is not running; install/start papyrus.service");
|
|
52
|
+
async function probedPapyrusClient(handle: DaemonHandle): Promise<PapyrusClient> {
|
|
52
53
|
const probe = new PapyrusClient(handle.baseUrl, handle.token, (request) => fetch(request), DAEMON_PROBE_TIMEOUT_MS);
|
|
53
54
|
try {
|
|
54
55
|
await probe.health();
|
|
@@ -58,6 +59,54 @@ export async function connectPapyrusClient(dir: string = daemonStateDir()): Prom
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** packages/papyrus/src/cli.ts, resolved relative to this file's own installed location, not require.resolve('@danypops/papyrus') -- this module IS that package, no cross-package lookup needed. */
|
|
63
|
+
function papyrusCliPath(): string {
|
|
64
|
+
return fileURLToPath(new URL("cli.ts", import.meta.url));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ConnectPapyrusClientOptions {
|
|
68
|
+
/**
|
|
69
|
+
* Environment passed to an auto-spawned daemon child. Defaults to the current
|
|
70
|
+
* process.env. Always carries DAEMON_DIR_ENV=dir so the spawned child computes
|
|
71
|
+
* the exact same state directory this call itself reads/polls -- without this,
|
|
72
|
+
* a caller-supplied `dir` (every real test; production always uses the default
|
|
73
|
+
* daemonStateDir(), which the child would derive identically on its own) would
|
|
74
|
+
* silently diverge from wherever the child actually starts writing its handle.
|
|
75
|
+
*/
|
|
76
|
+
env?: Record<string, string | undefined>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transparently starts the daemon first if it is not already running -- matches
|
|
81
|
+
* every other daemon-backed ecosystem package that opted into auto-start (see
|
|
82
|
+
* @danypops/vehicle-client's connectWithPolicy doc comment: web-spider opts in,
|
|
83
|
+
* lector/pi-packed fail closed by design). Papyrus previously failed closed with
|
|
84
|
+
* "install/start papyrus.service", requiring a human to separately discover and
|
|
85
|
+
* run `papyrus service install` before the very first tool call could succeed.
|
|
86
|
+
* A handle file that exists but points at a dead/unreachable daemon is a distinct
|
|
87
|
+
* failure (stale, not "never started") and is NOT auto-recovered here -- it still
|
|
88
|
+
* throws its own actionable "restart manually" error, unchanged from before.
|
|
89
|
+
*/
|
|
90
|
+
export async function connectPapyrusClient(dir: string = daemonStateDir(), options: ConnectPapyrusClientOptions = {}): Promise<PapyrusClient> {
|
|
91
|
+
return connectWithPolicy({
|
|
92
|
+
readHandle: () => readDaemonHandle(dir) ?? null,
|
|
93
|
+
buildClient: probedPapyrusClient,
|
|
94
|
+
autoStart: true,
|
|
95
|
+
spawn: () => {
|
|
96
|
+
spawnDetachedDaemon({
|
|
97
|
+
binPath: papyrusCliPath(),
|
|
98
|
+
args: ["serve"],
|
|
99
|
+
env: { ...(options.env ?? process.env), [DAEMON_DIR_ENV]: dir },
|
|
100
|
+
spawn: (command, args, spawnOptions) => {
|
|
101
|
+
const child = spawnProcess(command, args, spawnOptions);
|
|
102
|
+
child.unref();
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
fallbackMessage: "Papyrus daemon failed to start automatically; run `papyrus service install` or `papyrus serve` manually.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
export interface PushChannelTarget {
|
|
62
111
|
/** ws:// URL for the daemon's push-invalidation channel (see push-channel.ts in vehicle-server). */
|
|
63
112
|
url: string;
|
package/src/daemon-state.ts
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
export interface DaemonHandle {
|
|
13
13
|
baseUrl: string;
|
|
14
14
|
token: string;
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
pid: number;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export function daemonStateDir(
|
|
@@ -38,9 +41,9 @@ export function loadOrCreateToken(dir: string): string {
|
|
|
38
41
|
return token;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
export function writeDaemonPort(dir: string, port: number): void {
|
|
44
|
+
export function writeDaemonPort(dir: string, port: number, pid: number = process.pid): void {
|
|
42
45
|
mkdirSync(dir, { recursive: true });
|
|
43
|
-
writeFileSync(join(dir, DAEMON_PORT_FILE), `${port}\n`, { mode: 0o600 });
|
|
46
|
+
writeFileSync(join(dir, DAEMON_PORT_FILE), `${port}\n${pid}\n`, { mode: 0o600 });
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export function clearDaemonPort(dir: string): void {
|
|
@@ -50,9 +53,14 @@ export function clearDaemonPort(dir: string): void {
|
|
|
50
53
|
export function readDaemonHandle(dir: string): DaemonHandle | undefined {
|
|
51
54
|
try {
|
|
52
55
|
const token = readFileSync(join(dir, DAEMON_TOKEN_FILE), "utf8").trim();
|
|
53
|
-
const
|
|
56
|
+
const lines = readFileSync(join(dir, DAEMON_PORT_FILE), "utf8").trim().split("\n");
|
|
57
|
+
const port = Number(lines[0]);
|
|
58
|
+
// pid is absent for a handle written before this field existed -- 0 is a safe
|
|
59
|
+
// "unknown" sentinel (never a real pid), not a crash. Nothing currently reads
|
|
60
|
+
// pid to make a kill/staleness decision, so a stale 0 is inert, not unsafe.
|
|
61
|
+
const pid = lines[1] ? Number(lines[1]) : 0;
|
|
54
62
|
if (!token || !Number.isInteger(port) || port < 1 || port > 65_535) return undefined;
|
|
55
|
-
return { baseUrl: `http://${DAEMON_HOST}:${port}`, token };
|
|
63
|
+
return { baseUrl: `http://${DAEMON_HOST}:${port}`, token, host: DAEMON_HOST, port, pid };
|
|
56
64
|
} catch {
|
|
57
65
|
return undefined;
|
|
58
66
|
}
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type { DiscussionAndRounds } from "./discussion-service.ts";
|
|
|
24
24
|
export { NOTE_DISPOSITIONS } from "./note-service.ts";
|
|
25
25
|
export type { OperationName, SchemaState } from "./service.ts";
|
|
26
26
|
export type { WorkflowRunResult } from "./workflow-execution.ts";
|
|
27
|
+
export type { PlaybookInvocationResult, PlaybookMissingArguments } from "./playbook-execution.ts";
|
|
27
28
|
export { projectArtifactRelationships } from "./artifact-relationship-view.ts";
|
|
28
29
|
export { taskContext } from "./task-context.ts";
|
|
29
30
|
export { projectTaskExecution, type TaskExecutionPlan, type TaskExecutionState } from "./task-execution.ts";
|