@danypops/pi-papyrus 0.38.5 → 0.39.0

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.
@@ -540,121 +540,9 @@ export function registerTasksTool(pi: ExtensionAPI): void {
540
540
  });
541
541
  }
542
542
 
543
- // notes.* is registered as a real Vehicle (see ../vehicle-notes-client.ts and index.ts),
544
- // not a hand-rolled pi.registerTool() -- the first domain migrated off the
545
- // action-dispatch mega-tool pattern this file's other tools still use.
546
-
547
- export function registerDocsTool(pi: ExtensionAPI): void {
548
- pi.registerTool({
549
- name: "docs",
550
- label: "Documents",
551
- description: "Document domain tool. ACTIONS: create, list, show, activate, archive, reopen, link, assign_project, update, remove, remove_subtree, restore. project_root is optional at creation (omitted = unscoped); assign_project reassigns it later, or unscopes when project_root is omitted. update changes title/body/labels (at least one required) and is refused for a read-only external projection (e.g. web-spider-ingested Docs) -- capture a correction as a new linked Doc instead. remove moves a Doc to a time-gated trash, excluded from list/query but still directly showable, restorable via restore until the purge deadline; remove_subtree extends this to a whole `contains` subtree in one call. PREFER `name` (the doc's exact title) over `id`, and `target_name` over `target_id` for link -- both are backend implementation details, resolved from name automatically (target_name searches across every kind, since a link target can be a doc, task, rule, or skill). Prefer this over low-level papyrus_* tools for document work.",
552
- parameters: Type.Object({
553
- action: Type.String(),
554
- id: Type.Optional(Type.String()),
555
- name: Type.Optional(Type.String()),
556
- title: Type.Optional(Type.String()),
557
- body: Type.Optional(Type.String()),
558
- subtype: Type.Optional(Type.String()),
559
- status: Type.Optional(Type.String()),
560
- text: Type.Optional(Type.String()),
561
- limit: Type.Optional(Type.Number()),
562
- labels: Type.Optional(Type.Array(Type.String())),
563
- extra: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
564
- template_id: Type.Optional(Type.String()),
565
- relation: Type.Optional(Type.String()),
566
- target_id: Type.Optional(Type.String()),
567
- target_name: Type.Optional(Type.String()),
568
- project_root: Type.Optional(Type.String()),
569
- reason: Type.Optional(Type.String()),
570
- }),
571
- renderCall(args, theme) { return renderPapyrusToolCall("Documents", args, theme); },
572
- renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
573
- async execute(_id, rawParams) {
574
- try {
575
- const params: Record<string, unknown> = { ...rawParams };
576
- const action = params.action;
577
- const scopeRequest = { project_root: params.project_root };
578
- await resolveNameFields(params, [
579
- { nameKey: "name", idKey: "id", listOperation: "docs.list", baseRequest: scopeRequest },
580
- // Kind-agnostic: a link target can be a doc, task, rule, or skill, so this searches every kind rather than only docs.
581
- { nameKey: "target_name", idKey: "target_id", listOperation: "artifact.query", baseRequest: scopeRequest },
582
- ]);
583
- if (action === "create") {
584
- const artifact = await callService<Record<string, unknown>, Artifact>("docs.create", params);
585
- return text(`Created document ${artifactLine(artifact)}`, createArtifactDetails("docs.create", artifact));
586
- }
587
- if (action === "list") {
588
- const rows = await callService<Record<string, unknown>, Artifact[]>("docs.list", params);
589
- return text(rows.length ? artifactLines(rows).join("\n") : "No documents found.", createArtifactListDetails("docs.list", rows));
590
- }
591
- if (action === "show") {
592
- const artifact = await callService<Record<string, unknown>, Artifact>("docs.show", params);
593
- return text(`${artifactLine(artifact)}\n\n${artifact.body}`, createArtifactDetails("docs.show", artifact));
594
- }
595
- const trashResult = await handleArtifactRemoveRestore(action, params);
596
- if (trashResult) return trashResult;
597
- const operations = { activate: "docs.activate", archive: "docs.archive", reopen: "docs.reopen", link: "docs.link", assign_project: "docs.assign_project", update: "docs.update" } as const;
598
- const operation = operations[action as keyof typeof operations];
599
- if (!operation) throw new Error(`unknown docs action: ${action}`);
600
- const artifact = await callService<Record<string, unknown>, Artifact>(operation, params);
601
- return text(artifactLine(artifact), createArtifactDetails(operation, artifact));
602
- } catch (error) {
603
- throw new Error(`docs failed: ${error instanceof Error ? error.message : error}`);
604
- }
605
- },
606
- });
607
- }
608
-
609
- export function registerRulesTool(pi: ExtensionAPI): void {
610
- pi.registerTool({
611
- name: "rules",
612
- label: "Rules",
613
- description: "Rule domain tool. ACTIONS: create, list, show, preview, enable, disable, gate, assign_project, update, remove, remove_subtree, restore. project_root is optional at creation (omitted = unscoped); assign_project reassigns it later, or unscopes when project_root is omitted. Active rules inject into the agent system prompt. update changes title/body/labels (at least one required); body updates still enforce the same combined condition+action+body context-tax bound as creation, and are refused for a read-only external projection. remove moves a Rule to a time-gated trash, excluded from list/query but still directly showable, restorable via restore until the purge deadline; remove_subtree extends this to a whole `contains` subtree in one call. PREFER `name` (the rule's exact title) over `id`, and `task_name` over `task_id` for gate -- both are backend implementation details, resolved from name automatically.",
614
- parameters: Type.Object({
615
- action: Type.String(), id: Type.Optional(Type.String()), name: Type.Optional(Type.String()), title: Type.Optional(Type.String()),
616
- body: Type.Optional(Type.String()), condition: Type.Optional(Type.String()), rule_action: Type.Optional(Type.String()),
617
- severity: Type.Optional(Type.String()), labels: Type.Optional(Type.Array(Type.String())),
618
- extra: Type.Optional(Type.Record(Type.String(), Type.Unknown())), status: Type.Optional(Type.String()),
619
- text: Type.Optional(Type.String()), limit: Type.Optional(Type.Number()), task_id: Type.Optional(Type.String()),
620
- task_name: Type.Optional(Type.String()),
621
- project_root: Type.Optional(Type.String()), reason: Type.Optional(Type.String()),
622
- }),
623
- renderCall(args, theme) { return renderPapyrusToolCall("Rules", args, theme); },
624
- renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
625
- async execute(_id, rawParams, _signal, _onUpdate, ctx) {
626
- try {
627
- const params: Record<string, unknown> = { ...rawParams };
628
- const action = params.action;
629
- await resolveNameFields(params, [
630
- { nameKey: "name", idKey: "id", listOperation: "rules.list", baseRequest: { project_root: params.project_root } },
631
- { nameKey: "task_name", idKey: "task_id", listOperation: "tasks.list", baseRequest: { project_root: params.project_root ?? ctx.cwd } },
632
- ]);
633
- if (action === "create") {
634
- const artifact = await callService<Record<string, unknown>, Artifact>("rules.create", params);
635
- return text(`Created rule ${artifactLine(artifact)}`, createArtifactDetails("rules.create", artifact));
636
- }
637
- if (action === "list") {
638
- const rows = await callService<Record<string, unknown>, Artifact[]>("rules.list", params);
639
- return text(rows.length ? artifactLines(rows).join("\n") : "No rules found.", createArtifactListDetails("rules.list", rows));
640
- }
641
- if (action === "preview") {
642
- const preview = await callService<Record<string, unknown>, string>("rules.preview", params);
643
- return text(preview, createPreviewDetails("rules.preview", "Rule preview", preview));
644
- }
645
- const trashResult = await handleArtifactRemoveRestore(action, params);
646
- if (trashResult) return trashResult;
647
- const operations = { show: "rules.show", enable: "rules.enable", disable: "rules.disable", gate: "rules.gate", assign_project: "rules.assign_project", update: "rules.update" } as const;
648
- const operation = operations[action as keyof typeof operations];
649
- if (!operation) throw new Error(`unknown rules action: ${action}`);
650
- const artifact = await callService<Record<string, unknown>, Artifact>(operation, params);
651
- return text(`${artifactLine(artifact)}${action === "show" ? `\n\n${artifact.body}` : ""}`, createArtifactDetails(operation, artifact));
652
- } catch (error) {
653
- throw new Error(`rules failed: ${error instanceof Error ? error.message : error}`);
654
- }
655
- },
656
- });
657
- }
543
+ // notes.*, rules.*, docs.*, and the shared artifact.* are registered as Vehicles
544
+ // (see ../vehicle-notes-client.ts and @danypops/papyrus's src/vehicle/papyrus-vehicle.ts),
545
+ // not pi.registerTool()s in this file.
658
546
 
659
547
  export function registerPlaybooksTool(pi: ExtensionAPI): void {
660
548
  pi.registerTool({
@@ -950,10 +838,12 @@ export function registerDiscussTool(pi: ExtensionAPI): void {
950
838
  }
951
839
 
952
840
  /** Thin orchestrator: each domain's tool is independently navigable/testable via its own registerXTool function. */
841
+ // docs and rules are no longer registered here -- both migrated onto Vehicle
842
+ // (registerNotesVehicle in vehicle-notes-client.ts, wired at session_start in
843
+ // index.ts), replacing their own pi.registerTool() mega-tools. See
844
+ // @danypops/papyrus's src/vehicle/papyrus-vehicle.ts for the server side.
953
845
  export function registerDomainTools(pi: ExtensionAPI): void {
954
846
  registerTasksTool(pi);
955
- registerDocsTool(pi);
956
- registerRulesTool(pi);
957
847
  registerPlaybooksTool(pi);
958
848
  registerSkillsTool(pi);
959
849
  registerDiscussTool(pi);
@@ -1,39 +1,29 @@
1
1
  /**
2
- * Registers notes.* as a real Vehicle instead of a hand-rolled `pi.registerTool()`
3
- * mega-tool -- see @danypops/papyrus's src/vehicle/notes-vehicle.ts for the
4
- * VehicleRegistry side. Same daemon, same handle file, same Bearer token every
5
- * other Papyrus RPC call already uses (resolveVehicleClientTarget mirrors
6
- * resolvePushChannelTarget's own resolution).
2
+ * Registers every Vehicle-projected domain (notes.*, rules.*, docs.*, artifact.*)
3
+ * as real Pi tools -- see @danypops/papyrus's src/vehicle/papyrus-vehicle.ts.
7
4
  *
8
- * Papyrus's daemon is expected to already be running as an installed service
9
- * (see `packed install-service`), not auto-spawned on first use -- so unlike
10
- * registerVehicleTools' README example, failure here (daemon not started yet,
11
- * stale handle) is tolerated the same silent-degrade way
12
- * subscribeTaskPushChannel already tolerates it, rather than letting a
13
- * daemon-not-running condition abort the rest of extension setup. There is
14
- * no retry-on-later-connect for a tool that was never registered at all --
15
- * Pi has no way to add one after the fact outside the initial registration
16
- * flow.
5
+ * Fails silently on a stale/unreachable daemon handle instead of aborting extension
6
+ * setup: Papyrus's daemon doesn't auto-spawn, and a tool that failed to register
7
+ * here has no later retry path.
17
8
  *
18
- * Resolves the target through service-client.ts's currentVehicleClientTarget()
19
- * (test-injectable, see setVehicleClientTargetResolverForTests), never
20
- * @danypops/papyrus's resolveVehicleClientTarget() directly -- this runs from
21
- * the bare extension entrypoint on every registerPapyrus(api) call, so an
22
- * un-injected default would resolve the real daemonStateDir() in any test
23
- * exercising the full entrypoint, not just ones about notes.
9
+ * Uses service-client.ts's currentVehicleClientTarget() (test-injectable) rather
10
+ * than resolveVehicleClientTarget() directly, so a test exercising the full
11
+ * extension entrypoint doesn't resolve a real daemonStateDir().
24
12
  */
25
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
26
14
  import { RemoteVehicleClient } from "@danypops/vehicle-client/http";
27
15
  import { registerVehicleTools } from "@danypops/vehicle-client-pi";
28
16
  import { currentVehicleClientTarget } from "./service-client.ts";
29
17
 
18
+ const REGISTERED_PERMISSIONS = ["notes:read", "notes:write", "rules:read", "rules:write", "docs:read", "docs:write", "artifact:read", "artifact:write"];
19
+
30
20
  export async function registerNotesVehicle(pi: ExtensionAPI): Promise<void> {
31
21
  const target = currentVehicleClientTarget();
32
22
  if (!target) return;
33
23
  try {
34
24
  const client = new RemoteVehicleClient({ baseUrl: target.baseUrl, token: target.token });
35
25
  await registerVehicleTools(pi, client, {
36
- permissions: ["notes:read", "notes:write"],
26
+ permissions: REGISTERED_PERMISSIONS,
37
27
  principal: { id: "pi-papyrus" },
38
28
  });
39
29
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.38.5",
3
+ "version": "0.39.0",
4
4
  "description": "Pi host extension for Papyrus: native tools, TUI panels, and context injection over the daemon-backed graph store",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
@@ -17,7 +17,7 @@
17
17
  "typebox": "*"
18
18
  },
19
19
  "dependencies": {
20
- "@danypops/papyrus": "^0.38.1",
20
+ "@danypops/papyrus": "^0.39.0",
21
21
  "@danypops/vehicle-core": "^0.1.1",
22
22
  "@danypops/vehicle-server": "^0.1.1",
23
23
  "@danypops/vehicle-client": "^0.1.1",