@danypops/papyrus 0.29.0 → 0.29.1

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.
@@ -5,6 +5,7 @@ import {
5
5
  } from "../../src/constants.ts";
6
6
  import type { Artifact } from "../../src/domain/artifact.ts";
7
7
  import { ruleInjectionPreview } from "./rules.ts";
8
+ import { playbookInjectionPreview } from "./playbook-bridge.ts";
8
9
 
9
10
  export interface ContextPayloadSize {
10
11
  characters: number;
@@ -18,6 +19,7 @@ export interface PapyrusContextInjectionObservation {
18
19
  producerId: string;
19
20
  before: ContextPayloadSize;
20
21
  rules: ContextPayloadSize & { count: number };
22
+ playbooks: ContextPayloadSize & { count: number };
21
23
  tasks: ContextPayloadSize;
22
24
  injected: ContextPayloadSize;
23
25
  after: ContextPayloadSize;
@@ -30,6 +32,7 @@ export interface PapyrusContextInjectionObservation {
30
32
  export interface BuildContextInjectionInput {
31
33
  basePrompt: string;
32
34
  rules: Array<Pick<Artifact, "title" | "body" | "extra">>;
35
+ playbooks: Array<Pick<Artifact, "title" | "extra">>;
33
36
  taskSummary: string | null;
34
37
  observedAt: number;
35
38
  sequence: number;
@@ -46,13 +49,16 @@ function size(value: string): ContextPayloadSize {
46
49
  export function buildContextInjection(input: BuildContextInjectionInput): {
47
50
  prompt: string;
48
51
  ruleBlock: string;
52
+ playbookBlock: string;
49
53
  taskBlock: string;
50
54
  observation: PapyrusContextInjectionObservation;
51
55
  } {
52
56
  const ruleContent = input.rules.map(ruleInjectionPreview).join("\n");
53
57
  const ruleBlock = ruleContent ? `\n\n## Active rules (Papyrus)\n\n${ruleContent}\n` : "";
58
+ const playbookContent = input.playbooks.map(playbookInjectionPreview).join("\n");
59
+ const playbookBlock = playbookContent ? `\n\n## Available playbooks (Papyrus)\n\n${playbookContent}\n` : "";
54
60
  const taskBlock = input.taskSummary ? `\n\n## Open tasks (Papyrus)\n\n${input.taskSummary}\n` : "";
55
- const injected = `${ruleBlock}${taskBlock}`;
61
+ const injected = `${ruleBlock}${playbookBlock}${taskBlock}`;
56
62
  const prompt = `${input.basePrompt}${injected}`;
57
63
  const fingerprint = createHash("sha256").update(injected).digest("hex");
58
64
  const injectedSize = size(injected);
@@ -60,6 +66,7 @@ export function buildContextInjection(input: BuildContextInjectionInput): {
60
66
  return {
61
67
  prompt,
62
68
  ruleBlock,
69
+ playbookBlock,
63
70
  taskBlock,
64
71
  observation: {
65
72
  schema: PAPYRUS_CONTEXT_INJECTION_SCHEMA,
@@ -68,6 +75,7 @@ export function buildContextInjection(input: BuildContextInjectionInput): {
68
75
  producerId: input.producerId,
69
76
  before: size(input.basePrompt),
70
77
  rules: { ...size(ruleBlock), count: input.rules.length },
78
+ playbooks: { ...size(playbookBlock), count: input.playbooks.length },
71
79
  tasks: size(taskBlock),
72
80
  injected: injectedSize,
73
81
  after: afterSize,
@@ -23,7 +23,7 @@ import { formatMetadata } from "./artifact-format.ts";
23
23
  import { callService } from "./service-client.ts";
24
24
  import { registerDomainTools } from "./domain-tools.ts";
25
25
  import { isLiveAskPending } from "./discuss-ask-view.ts";
26
- import { registerPlaybookBridge } from "./playbook-bridge.ts";
26
+ import { PLAYBOOK_BRIDGE_MAX_PLAYBOOKS, registerPlaybookBridge } from "./playbook-bridge.ts";
27
27
  import type { TaskGraph, TaskStatus } from "../../src/task-service.ts";
28
28
  import { ActiveTaskContinuation, automaticPauseReason, shouldResumeFocusOnHumanInput, type ActiveTaskMarker } from "./active-task-continuation.ts";
29
29
  import { buildTaskWidgetProjection, type TaskWidgetProjection } from "./task-widget.ts";
@@ -626,13 +626,15 @@ export default async function (pi: ExtensionAPI) {
626
626
  pi.on("before_agent_start", async (event, ctx) => {
627
627
  try {
628
628
  const sessionId = ctx.sessionManager.getSessionId();
629
- const [rules, summary] = await Promise.all([
629
+ const [rules, playbooks, summary] = await Promise.all([
630
630
  callService<Record<string, unknown>, Array<Pick<Artifact, "title" | "body" | "extra">>>("rules.injectable", { project_root: ctx.cwd, session_id: sessionId }),
631
+ callService<Record<string, unknown>, Array<Pick<Artifact, "title" | "extra">>>("playbooks.list", { status: "active", limit: PLAYBOOK_BRIDGE_MAX_PLAYBOOKS }),
631
632
  callService<Record<string, unknown>, string | null>("tasks.context", { project_root: ctx.cwd, session_id: sessionId }),
632
633
  ]);
633
634
  const injection = buildContextInjection({
634
635
  basePrompt: event.systemPrompt ?? "",
635
636
  rules,
637
+ playbooks,
636
638
  taskSummary: summary,
637
639
  observedAt: Date.now(),
638
640
  sequence: ++contextInjectionSequence,
@@ -21,7 +21,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
21
21
  import type { Artifact } from "../../src/domain/artifact.ts";
22
22
  import { callService } from "./service-client.ts";
23
23
 
24
- const PLAYBOOK_BRIDGE_MAX_PLAYBOOKS = 100;
24
+ export const PLAYBOOK_BRIDGE_MAX_PLAYBOOKS = 100;
25
25
 
26
26
  function slugify(title: string): string {
27
27
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
@@ -37,6 +37,17 @@ export function playbookCommandName(title: string): string {
37
37
  return `playbook:${slugify(title)}`;
38
38
  }
39
39
 
40
+ /**
41
+ * One line per active Playbook for context injection -- the passive, every-turn surfacing that
42
+ * gives the model the same "this exists and might match my task" awareness Pi's own Skill catalog
43
+ * gives real Skills, without a file-based bridge. See buildContextInjection (rules and open tasks
44
+ * already work this way).
45
+ */
46
+ export function playbookInjectionPreview(playbook: Pick<Artifact, "title" | "extra">): string {
47
+ const trigger = typeof playbook.extra["trigger"] === "string" ? playbook.extra["trigger"] : "manual invocation";
48
+ return `• ${playbook.title} (when: ${trigger})`;
49
+ }
50
+
40
51
  /** Exported for direct testing without a real ExtensionAPI: what would be registered right now. */
41
52
  export async function planPlaybookCommandRegistrations(): Promise<Array<{ name: string; id: string; title: string; trigger: string }>> {
42
53
  const playbooks = await activePlaybooks();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
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"],