@danypops/pi-papyrus 0.57.4 → 0.57.6

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.
@@ -0,0 +1,43 @@
1
+ import { existsSync } from "node:fs";
2
+ import { extname, join } from "node:path";
3
+
4
+ export interface ActivationContextInput {
5
+ languages?: string[];
6
+ file_extensions?: string[];
7
+ session_capabilities?: string[];
8
+ }
9
+
10
+ const LANGUAGE_MARKERS: ReadonlyArray<{ language: string; files: string[] }> = [
11
+ { language: "typescript", files: ["tsconfig.json"] },
12
+ { language: "javascript", files: ["package.json"] },
13
+ { language: "rust", files: ["Cargo.toml"] },
14
+ { language: "go", files: ["go.mod"] },
15
+ { language: "python", files: ["pyproject.toml", "requirements.txt"] },
16
+ { language: "java", files: ["pom.xml", "build.gradle", "build.gradle.kts"] },
17
+ { language: "kotlin", files: ["build.gradle.kts"] },
18
+ { language: "swift", files: ["Package.swift"] },
19
+ { language: "zig", files: ["build.zig"] },
20
+ ];
21
+
22
+ const PATH_TOKEN = /(?:^|[\s"'`(])([^\s"'`()]+\.[A-Za-z0-9]{1,10})(?=$|[\s"'`),:])/g;
23
+ const MAX_EXTENSIONS = 32;
24
+ const MAX_CAPABILITIES = 32;
25
+
26
+ /** Derives only bounded, deterministic turn signals. It never executes project code or interprets free-form predicates. */
27
+ export function buildActivationContext(cwd: string, prompt: string, selectedTools: readonly string[] = []): ActivationContextInput {
28
+ const languages = LANGUAGE_MARKERS.filter((entry) => entry.files.some((file) => existsSync(join(cwd, file))))
29
+ .map((entry) => entry.language)
30
+ .sort();
31
+ const extensions = new Set<string>();
32
+ for (const match of prompt.matchAll(PATH_TOKEN)) {
33
+ const extension = extname(match[1]!).toLowerCase();
34
+ if (extension) extensions.add(extension);
35
+ if (extensions.size >= MAX_EXTENSIONS) break;
36
+ }
37
+ const capabilities = [...new Set(selectedTools.filter((tool) => tool.length > 0))].sort().slice(0, MAX_CAPABILITIES);
38
+ return {
39
+ ...(languages.length > 0 ? { languages } : {}),
40
+ ...(extensions.size > 0 ? { file_extensions: [...extensions].sort() } : {}),
41
+ ...(capabilities.length > 0 ? { session_capabilities: capabilities } : {}),
42
+ };
43
+ }
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import type { ContextSegmentItem } from "@danypops/jittor";
4
4
  import { type Artifact, CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN, CONTEXT_TREE_MAX_NODES, type TaskGraph } from "@danypops/papyrus";
5
+ import { playbookInjectionPreview } from "../playbook/playbook-bridge.ts";
5
6
  import { ruleInjectionPreview } from "../rules/rules.ts";
6
7
  import { discoverSkillDirectories, type SkillCatalogFootprint, scanSkillCatalogFootprint } from "./skill-catalog-footprint.ts";
7
8
 
@@ -27,6 +28,10 @@ export interface ContextBudget {
27
28
  totalCharacters: number;
28
29
  totalEstimatedTokens: number;
29
30
  };
31
+ playbooks: {
32
+ entries: Array<{ title: string; estimatedTokens: number }>;
33
+ totalEstimatedTokens: number;
34
+ };
30
35
  skills: SkillCatalogFootprint;
31
36
  totalEstimatedTokens: number;
32
37
  }
@@ -61,12 +66,26 @@ export function computeContextBudget(
61
66
  rules: ReadonlyArray<Pick<Artifact, "id" | "title" | "body" | "extra">>,
62
67
  cwd: string,
63
68
  homeDirectory: string = homedir(),
69
+ playbooks: ReadonlyArray<Pick<Artifact, "title" | "extra">> = [],
64
70
  ): ContextBudget {
65
71
  const settingsSkills = readSettingsSkillPaths(`${homeDirectory}/.pi/agent/settings.json`);
66
72
  const directories = discoverSkillDirectories(homeDirectory, cwd, settingsSkills);
67
73
  const skills = scanSkillCatalogFootprint(directories);
68
74
  const ruleBudget = computeRuleBudget(rules);
69
- return { rules: ruleBudget, skills, totalEstimatedTokens: ruleBudget.totalEstimatedTokens + skills.totalEstimatedTokens };
75
+ const playbookEntries = playbooks.map((playbook) => ({
76
+ title: playbook.title,
77
+ estimatedTokens: Math.ceil(playbookInjectionPreview(playbook).length / CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN),
78
+ }));
79
+ const playbookBudget = {
80
+ entries: playbookEntries,
81
+ totalEstimatedTokens: playbookEntries.reduce((sum, entry) => sum + entry.estimatedTokens, 0),
82
+ };
83
+ return {
84
+ rules: ruleBudget,
85
+ playbooks: playbookBudget,
86
+ skills,
87
+ totalEstimatedTokens: ruleBudget.totalEstimatedTokens + playbookBudget.totalEstimatedTokens + skills.totalEstimatedTokens,
88
+ };
70
89
  }
71
90
 
72
91
  /** Sums a possibly-nested item tree's tokens recursively -- every node's own contribution, not just top-level items. */
@@ -17,6 +17,7 @@ export function papyrusContextSegment(
17
17
  ruleBudget: ContextBudget["rules"],
18
18
  taskItems: ContextSegmentItem[],
19
19
  skills: SkillCatalogFootprint,
20
+ playbookBudget?: ContextBudget["playbooks"],
20
21
  ): ContextSegment {
21
22
  const items: ContextSegmentItem[] = [];
22
23
  if (ruleBudget.entries.length > 0) {
@@ -26,6 +27,13 @@ export function papyrusContextSegment(
26
27
  children: ruleBudget.entries.map((entry) => ({ label: entry.title, estimatedTokens: entry.estimatedTokens })),
27
28
  });
28
29
  }
30
+ if ((playbookBudget?.entries.length ?? 0) > 0) {
31
+ items.push({
32
+ label: "Available Playbooks",
33
+ estimatedTokens: playbookBudget!.totalEstimatedTokens,
34
+ children: playbookBudget!.entries.map((entry) => ({ label: entry.title, estimatedTokens: entry.estimatedTokens })),
35
+ });
36
+ }
29
37
  if (taskItems.length > 0) {
30
38
  items.push({ label: "Open Tasks", estimatedTokens: sumItemTree(taskItems), children: taskItems });
31
39
  }
@@ -38,7 +46,7 @@ export function papyrusContextSegment(
38
46
  }
39
47
  return {
40
48
  key: "papyrus",
41
- label: "Papyrus (Rules, Tasks, Skills)",
49
+ label: "Papyrus (Rules, Playbooks, Tasks, Skills)",
42
50
  estimatedTokens: items.reduce((sum, item) => sum + item.estimatedTokens, 0),
43
51
  confidence: "exact-cooperative",
44
52
  ...(items.length > 0 ? { items } : {}),
@@ -1,5 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
- import { type Artifact, CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN, PAPYRUS_CONTEXT_INJECTION_SCHEMA } from "@danypops/papyrus";
2
+ import {
3
+ type Artifact,
4
+ activationConfig,
5
+ CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN,
6
+ PAPYRUS_CONTEXT_INJECTION_MAX_TOKENS,
7
+ PAPYRUS_CONTEXT_INJECTION_SCHEMA,
8
+ } from "@danypops/papyrus";
3
9
  import { playbookInjectionPreview } from "../playbook/playbook-bridge.ts";
4
10
  import { ruleInjectionPreview } from "../rules/rules.ts";
5
11
 
@@ -14,8 +20,8 @@ export interface PapyrusContextInjectionObservation {
14
20
  sequence: number;
15
21
  producerId: string;
16
22
  before: ContextPayloadSize;
17
- rules: ContextPayloadSize & { count: number };
18
- playbooks: ContextPayloadSize & { count: number };
23
+ rules: ContextPayloadSize & { count: number; omitted: number };
24
+ playbooks: ContextPayloadSize & { count: number; omitted: number };
19
25
  tasks: ContextPayloadSize;
20
26
  injected: ContextPayloadSize;
21
27
  after: ContextPayloadSize;
@@ -34,9 +40,75 @@ export interface BuildContextInjectionInput {
34
40
  sequence: number;
35
41
  producerId: string;
36
42
  previousFingerprint?: string;
43
+ maxEstimatedTokens?: number;
37
44
  }
38
45
 
39
46
  const encoder = new TextEncoder();
47
+ const RULE_HEADER = "\n\n## Active rules (Papyrus)\n\n";
48
+ const PLAYBOOK_HEADER = "\n\n## Available playbooks (Papyrus)\n\n";
49
+ const TASK_HEADER = "\n\n## Open tasks (Papyrus)\n\n";
50
+
51
+ function ruleText(rule: Pick<Artifact, "title" | "body" | "extra">): string {
52
+ const config = activationConfig(rule.extra, "full");
53
+ const condition = typeof rule.extra.condition === "string" ? ` (when: ${rule.extra.condition})` : "";
54
+ if (config.injection === "catalog") return `• ${rule.title}${condition}`;
55
+ return ruleInjectionPreview(rule);
56
+ }
57
+
58
+ interface InjectionCandidate {
59
+ kind: "rule" | "playbook";
60
+ text: string;
61
+ priority: number;
62
+ stableKey: string;
63
+ index: number;
64
+ }
65
+
66
+ function selectWithinBudget(
67
+ input: BuildContextInjectionInput,
68
+ taskBlock: string,
69
+ ): { ruleIndexes: Set<number>; playbookIndexes: Set<number> } {
70
+ const maxCharacters = (input.maxEstimatedTokens ?? PAPYRUS_CONTEXT_INJECTION_MAX_TOKENS) * CONTEXT_ESTIMATE_CHARACTERS_PER_TOKEN;
71
+ let used = taskBlock.length;
72
+ let hasRule = false;
73
+ let hasPlaybook = false;
74
+ const candidates: InjectionCandidate[] = [
75
+ ...input.rules.map((rule, index) => ({
76
+ kind: "rule" as const,
77
+ text: ruleText(rule),
78
+ priority: activationConfig(rule.extra, "full").priority,
79
+ stableKey: `${rule.title}\u0000${index}`,
80
+ index,
81
+ })),
82
+ ...input.playbooks.map((playbook, index) => ({
83
+ kind: "playbook" as const,
84
+ text: playbookInjectionPreview(playbook),
85
+ priority: activationConfig(playbook.extra, "catalog").priority,
86
+ stableKey: `${playbook.title}\u0000${index}`,
87
+ index,
88
+ })),
89
+ ].filter((candidate) => {
90
+ const source = candidate.kind === "rule" ? input.rules[candidate.index] : input.playbooks[candidate.index];
91
+ return activationConfig(source!.extra, candidate.kind === "rule" ? "full" : "catalog").injection !== "on-demand";
92
+ });
93
+ candidates.sort((left, right) => right.priority - left.priority || left.stableKey.localeCompare(right.stableKey));
94
+ const ruleIndexes = new Set<number>();
95
+ const playbookIndexes = new Set<number>();
96
+ for (const candidate of candidates) {
97
+ const firstOfKind = candidate.kind === "rule" ? !hasRule : !hasPlaybook;
98
+ const overhead = firstOfKind ? (candidate.kind === "rule" ? RULE_HEADER.length + 1 : PLAYBOOK_HEADER.length + 1) : 1;
99
+ const cost = candidate.text.length + overhead;
100
+ if (used + cost > maxCharacters) continue;
101
+ used += cost;
102
+ if (candidate.kind === "rule") {
103
+ ruleIndexes.add(candidate.index);
104
+ hasRule = true;
105
+ } else {
106
+ playbookIndexes.add(candidate.index);
107
+ hasPlaybook = true;
108
+ }
109
+ }
110
+ return { ruleIndexes, playbookIndexes };
111
+ }
40
112
 
41
113
  function size(value: string): ContextPayloadSize {
42
114
  return { characters: value.length, bytes: encoder.encode(value).byteLength };
@@ -49,11 +121,14 @@ export function buildContextInjection(input: BuildContextInjectionInput): {
49
121
  taskBlock: string;
50
122
  observation: PapyrusContextInjectionObservation;
51
123
  } {
52
- const ruleContent = input.rules.map(ruleInjectionPreview).join("\n");
53
- const ruleBlock = ruleContent ? `\n\n## Active rules (Papyrus)\n\n${ruleContent}\n` : "";
54
- const playbookContent = input.playbooks.map(playbookInjectionPreview).join("\n");
55
- const playbookBlock = playbookContent ? `\n\n## Available playbooks (Papyrus)\n\n${playbookContent}\n` : "";
56
- const taskBlock = input.taskSummary ? `\n\n## Open tasks (Papyrus)\n\n${input.taskSummary}\n` : "";
124
+ const taskBlock = input.taskSummary ? `${TASK_HEADER}${input.taskSummary}\n` : "";
125
+ const selected = selectWithinBudget(input, taskBlock);
126
+ const selectedRules = input.rules.filter((_rule, index) => selected.ruleIndexes.has(index));
127
+ const selectedPlaybooks = input.playbooks.filter((_playbook, index) => selected.playbookIndexes.has(index));
128
+ const ruleContent = selectedRules.map(ruleText).join("\n");
129
+ const ruleBlock = ruleContent ? `${RULE_HEADER}${ruleContent}\n` : "";
130
+ const playbookContent = selectedPlaybooks.map(playbookInjectionPreview).join("\n");
131
+ const playbookBlock = playbookContent ? `${PLAYBOOK_HEADER}${playbookContent}\n` : "";
57
132
  const injected = `${ruleBlock}${playbookBlock}${taskBlock}`;
58
133
  const prompt = `${input.basePrompt}${injected}`;
59
134
  const fingerprint = createHash("sha256").update(injected).digest("hex");
@@ -70,8 +145,12 @@ export function buildContextInjection(input: BuildContextInjectionInput): {
70
145
  sequence: input.sequence,
71
146
  producerId: input.producerId,
72
147
  before: size(input.basePrompt),
73
- rules: { ...size(ruleBlock), count: input.rules.length },
74
- playbooks: { ...size(playbookBlock), count: input.playbooks.length },
148
+ rules: { ...size(ruleBlock), count: selectedRules.length, omitted: input.rules.length - selectedRules.length },
149
+ playbooks: {
150
+ ...size(playbookBlock),
151
+ count: selectedPlaybooks.length,
152
+ omitted: input.playbooks.length - selectedPlaybooks.length,
153
+ },
75
154
  tasks: size(taskBlock),
76
155
  injected: injectedSize,
77
156
  after: afterSize,
@@ -34,6 +34,7 @@ import { AutoRotatingWindow, renderCardRow, type WidgetSection } from "malevich-
34
34
  import { Type } from "typebox";
35
35
  import { formatMetadata } from "./artifact/artifact-format.ts";
36
36
  import { BoundedPoll } from "./bounded-poll.ts";
37
+ import { buildActivationContext } from "./context/activation-context.ts";
37
38
  import { buildTaskItemTree, computeContextBudget } from "./context/context-budget.ts";
38
39
  import { PAPYRUS_CONTEXT_HUB_PRODUCER_NAME, papyrusContextSegment } from "./context/context-hub-contribution.ts";
39
40
  import { buildContextInjection } from "./context/context-injection-telemetry.ts";
@@ -917,15 +918,20 @@ export default async function (pi: ExtensionAPI) {
917
918
  let result: { systemPrompt: string } | undefined;
918
919
  try {
919
920
  const sessionId = ctx.sessionManager.getSessionId();
921
+ const activationContext = buildActivationContext(ctx.cwd, event.prompt, event.systemPromptOptions.selectedTools);
920
922
  const [rules, playbooks, summary, taskGraph] = await Promise.all([
921
923
  callService<Record<string, unknown>, Array<Pick<Artifact, "id" | "title" | "body" | "extra">>>("rules.injectable", {
922
924
  project_root: ctx.cwd,
923
925
  session_id: sessionId,
926
+ activation_context: activationContext,
924
927
  }),
925
928
  callService<Record<string, unknown>, Array<Pick<Artifact, "title" | "extra">>>("playbooks.list", {
926
929
  status: "active",
927
930
  project_root: ctx.cwd,
928
931
  applicable: true,
932
+ activated: true,
933
+ activation_context: activationContext,
934
+ session_id: sessionId,
929
935
  limit: PLAYBOOK_BRIDGE_MAX_PLAYBOOKS,
930
936
  }),
931
937
  callService<Record<string, unknown>, string | null>("tasks.context", {
@@ -951,13 +957,13 @@ export default async function (pi: ExtensionAPI) {
951
957
  // Context Hub contribution is best-effort observability for /context -- its own failure
952
958
  // must never block this turn's actual rules/tasks injection above.
953
959
  try {
954
- const { rules: ruleBudget, skills } = computeContextBudget(rules, ctx.cwd);
960
+ const { rules: ruleBudget, playbooks: playbookBudget, skills } = computeContextBudget(rules, ctx.cwd, undefined, playbooks);
955
961
  pi.events.emit(CONTEXT_HUB_CONTRIBUTION_CHANNEL, {
956
962
  schema: CONTEXT_HUB_CONTRIBUTION_SCHEMA,
957
963
  observedAt: Date.now(),
958
964
  sequence: ++contextHubContributionSequence,
959
965
  producerName: PAPYRUS_CONTEXT_HUB_PRODUCER_NAME,
960
- segment: papyrusContextSegment(ruleBudget, buildTaskItemTree(taskGraph), skills),
966
+ segment: papyrusContextSegment(ruleBudget, buildTaskItemTree(taskGraph), skills, playbookBudget),
961
967
  });
962
968
  } catch {
963
969
  // Malformed/unreachable daemon data for this turn's contribution -- drop it silently.
@@ -20,6 +20,7 @@
20
20
 
21
21
  import type { Artifact } from "@danypops/papyrus";
22
22
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
23
+ import { buildActivationContext } from "../context/activation-context.ts";
23
24
  import { callService } from "../service-client.ts";
24
25
 
25
26
  export const PLAYBOOK_BRIDGE_MAX_PLAYBOOKS = 100;
@@ -33,8 +34,19 @@ function slugify(title: string): string {
33
34
  return slug.length > 0 ? slug : "playbook";
34
35
  }
35
36
 
36
- async function activePlaybooks(): Promise<Artifact[]> {
37
- return callService<Record<string, unknown>, Artifact[]>("playbooks.list", { status: "active", limit: PLAYBOOK_BRIDGE_MAX_PLAYBOOKS });
37
+ async function activePlaybooks(projectRoot?: string, capabilities: readonly string[] = []): Promise<Artifact[]> {
38
+ return callService<Record<string, unknown>, Artifact[]>("playbooks.list", {
39
+ status: "active",
40
+ limit: PLAYBOOK_BRIDGE_MAX_PLAYBOOKS,
41
+ ...(projectRoot === undefined
42
+ ? {}
43
+ : {
44
+ project_root: projectRoot,
45
+ applicable: true,
46
+ activated: true,
47
+ activation_context: buildActivationContext(projectRoot, "", capabilities),
48
+ }),
49
+ });
38
50
  }
39
51
 
40
52
  /** Exported for direct testing without a real ExtensionAPI. */
@@ -54,8 +66,11 @@ export function playbookInjectionPreview(playbook: Pick<Artifact, "title" | "ext
54
66
  }
55
67
 
56
68
  /** Exported for direct testing without a real ExtensionAPI: what would be registered right now. */
57
- export async function planPlaybookCommandRegistrations(): Promise<Array<{ name: string; id: string; title: string; trigger: string }>> {
58
- const playbooks = await activePlaybooks();
69
+ export async function planPlaybookCommandRegistrations(
70
+ projectRoot?: string,
71
+ capabilities: readonly string[] = [],
72
+ ): Promise<Array<{ name: string; id: string; title: string; trigger: string }>> {
73
+ const playbooks = await activePlaybooks(projectRoot, capabilities);
59
74
  const usedNames = new Set<string>();
60
75
  return playbooks.map((playbook) => {
61
76
  let name = playbookCommandName(playbook.title);
@@ -67,14 +82,21 @@ export async function planPlaybookCommandRegistrations(): Promise<Array<{ name:
67
82
  }
68
83
 
69
84
  export function registerPlaybookBridge(pi: ExtensionAPI): void {
70
- const refresh = async () => {
85
+ const refresh = async (projectRoot?: string) => {
71
86
  try {
72
- const registrations = await planPlaybookCommandRegistrations();
87
+ const registrations = await planPlaybookCommandRegistrations(projectRoot, pi.getActiveTools?.() ?? []);
73
88
  for (const { name, id, title, trigger } of registrations) {
74
89
  pi.registerCommand(name, {
75
90
  description: trigger,
76
91
  handler: async (_args, ctx) => {
77
92
  try {
93
+ if (ctx.cwd) {
94
+ const currentlyActive = await activePlaybooks(ctx.cwd, pi.getActiveTools?.() ?? []);
95
+ if (!currentlyActive.some((playbook) => playbook.id === id)) {
96
+ ctx.ui.notify(`"${title}" is not enabled for this project context`, "error");
97
+ return;
98
+ }
99
+ }
78
100
  // Re-fetched live, not captured at registration time: a lingering stale
79
101
  // command (renamed or disabled since, since registerCommand can't be
80
102
  // unregistered) must fail cleanly, never run deleted/stale content.
@@ -103,8 +125,8 @@ export function registerPlaybookBridge(pi: ExtensionAPI): void {
103
125
  // "no new/updated playbook commands this cycle", not a broken session start.
104
126
  }
105
127
  };
106
- pi.on("resources_discover", async () => {
107
- await refresh();
128
+ pi.on("resources_discover", async (event) => {
129
+ await refresh(event?.cwd);
108
130
  return {};
109
131
  });
110
132
  }
@@ -39,7 +39,7 @@ import { emitTaskFocusEvent } from "../task/task-focus-events.ts";
39
39
  import { recordRenderDiagnostic, shapeFingerprint } from "./render-diagnostics.ts";
40
40
  import { papyrusVehiclePresentations, papyrusVehicleRenderers } from "./vehicle-artifact-renderers.ts";
41
41
 
42
- const REGISTERED_PERMISSIONS = [
42
+ export const PAPYRUS_VEHICLE_PERMISSIONS = [
43
43
  "notes:read",
44
44
  "notes:write",
45
45
  "rules:read",
@@ -54,7 +54,9 @@ const REGISTERED_PERMISSIONS = [
54
54
  "discuss:write",
55
55
  "artifact:read",
56
56
  "artifact:write",
57
- ];
57
+ "binders:read",
58
+ "binders:write",
59
+ ] as const;
58
60
 
59
61
  /** Task Focus's own internal write needs a real, per-session secret -- see below. Every other tasks.* operation reads session_id purely for read-scoping and needs no secret. */
60
62
  const FOCUS_MUTATION_OPERATIONS = new Set(["tasks.focus", "tasks.pause", "tasks.unpause", "tasks.clear_focus"]);
@@ -148,7 +150,7 @@ export function registerNotesVehicle(pi: ExtensionAPI): Promise<RegisteredPiVehi
148
150
  recordRenderDiagnostic({ event: "vehicle-ready", kind: event.kind, ...("attempt" in event ? { attempt: event.attempt } : {}) });
149
151
  notifyReadyEvent(event);
150
152
  },
151
- permissions: REGISTERED_PERMISSIONS,
153
+ permissions: PAPYRUS_VEHICLE_PERMISSIONS,
152
154
  principal: { id: "pi-papyrus" },
153
155
  renderers: papyrusVehicleRenderers,
154
156
  // papyrusVehiclePresentations projects a bounded, versioned PapyrusToolDetails DTO
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.57.4",
3
+ "version": "0.57.6",
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"],
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@danypops/jittor": "^0.19.2",
22
- "@danypops/papyrus": "^0.60.2",
22
+ "@danypops/papyrus": "^0.60.5",
23
23
  "@danypops/vehicle-client": "^0.10.3",
24
24
  "@danypops/vehicle-core": "^0.18.5",
25
25
  "@danypops/vehicle-server": "^0.25.2",