@danypops/pi-papyrus 0.45.3 → 0.45.5

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.
@@ -1,5 +1,6 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
- import { type Component, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
+ import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
3
+ import { buildDetailLines, type DetailField, type DetailSection } from "malevich-tui-components";
3
4
  import type { ArtifactToolDetails } from "./render-model.ts";
4
5
 
5
6
  const KIND_GLYPHS: Readonly<Record<string, string>> = {
@@ -92,33 +93,55 @@ export class ArtifactCard implements Component {
92
93
  this.invalidate();
93
94
  }
94
95
 
96
+ /** Every field as a labeled `Label: value` line (buildDetailLines), matching the same
97
+ * convention pi-tickets' issue-detail-view.ts already uses -- not bare stacked values. */
98
+ private fields(): DetailField[] {
99
+ const artifact = this.details.artifact;
100
+ const status = this.theme.fg(statusColor(artifact.status), `${statusGlyph(artifact.status)} ${artifact.status}`);
101
+ return [
102
+ { label: "Title", value: artifact.title },
103
+ { label: "Kind", value: `${kindGlyph(artifact.kind)} ${artifact.kind}` },
104
+ { label: "Status", value: status },
105
+ ...(this.expanded ? [{ label: "ID", value: artifact.id }] : []),
106
+ ...(this.expanded && artifact.subtype ? [{ label: "Subtype", value: artifact.subtype }] : []),
107
+ ...(this.expanded && artifact.labels.length > 0 ? [{ label: "Labels", value: artifact.labels.join(", ") }] : []),
108
+ ];
109
+ }
110
+
111
+ private sections(): DetailSection[] {
112
+ if (!this.expanded) return [];
113
+ const sections: DetailSection[] = [];
114
+ const artifact = this.details.artifact;
115
+ if (artifact.body) sections.push({ heading: "Body:", body: artifact.body });
116
+ if (this.details.completeness.truncated) {
117
+ sections.push({ lines: [`[truncated ${this.details.completeness.omitted} characters]`] });
118
+ }
119
+ return sections;
120
+ }
121
+
95
122
  render(width: number): string[] {
96
123
  const safeWidth = Math.max(1, width);
97
124
  if (this.cachedLines && this.cachedWidth === safeWidth) return this.cachedLines;
98
125
 
99
- const artifact = this.details.artifact;
100
- const status = `${statusGlyph(artifact.status)} ${artifact.status}`;
101
- const header = [
102
- this.theme.fg("toolTitle", this.theme.bold(`${kindGlyph(artifact.kind)} ${artifact.kind.toUpperCase()}`)),
103
- ...(this.expanded ? [this.theme.fg("accent", artifact.id)] : []),
104
- this.theme.fg(statusColor(artifact.status), status),
105
- ].join(" ");
106
- const lines = [truncateToWidth(header, safeWidth)];
107
- lines.push(truncateToWidth(this.theme.fg("text", artifact.title), safeWidth));
126
+ const theme = this.theme;
127
+ const lines = buildDetailLines(safeWidth, {
128
+ fields: this.fields(),
129
+ sections: this.sections(),
130
+ alignFields: true,
131
+ theme: {
132
+ field: (s) => theme.fg("text", s),
133
+ heading: (s) => theme.fg("toolTitle", theme.bold(s)),
134
+ byline: (s) => theme.fg("muted", s),
135
+ body: (s) => theme.fg("text", s),
136
+ line: (s) => theme.fg("warning", s),
137
+ },
138
+ });
108
139
 
109
140
  if (this.details.focus) {
110
- lines.push(truncateToWidth(focusLine(this.details.focus, this.theme), safeWidth));
141
+ lines.push(truncateToWidth(focusLine(this.details.focus, theme), safeWidth));
111
142
  }
112
-
113
- if (this.expanded) {
114
- const metadata = [artifact.subtype, ...artifact.labels].filter(Boolean).join(" · ");
115
- if (metadata) lines.push(truncateToWidth(this.theme.fg("muted", metadata), safeWidth));
116
- if (artifact.body) lines.push(...wrapTextWithAnsi(artifact.body, safeWidth));
117
- if (this.details.completeness.truncated) {
118
- lines.push(truncateToWidth(this.theme.fg("warning", `[truncated ${this.details.completeness.omitted} characters]`), safeWidth));
119
- }
120
- } else if (artifact.body || artifact.labels.length > 0) {
121
- lines.push(truncateToWidth(this.theme.fg("dim", expandHint()), safeWidth));
143
+ if (!this.expanded && (this.details.artifact.body || this.details.artifact.labels.length > 0)) {
144
+ lines.push(truncateToWidth(theme.fg("dim", expandHint()), safeWidth));
122
145
  }
123
146
 
124
147
  this.cachedWidth = safeWidth;
@@ -1,3 +1,4 @@
1
+ import { pickIdentityArgument } from "@danypops/vehicle-client-pi/vehicle-render";
1
2
  import type { AgentToolResult, Theme, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
2
3
  import { type Component, Text } from "@earendil-works/pi-tui";
3
4
  import { ArtifactCard } from "./artifact-card.ts";
@@ -6,25 +7,18 @@ import { type PapyrusToolDetails, parsePapyrusToolDetails } from "./render-model
6
7
 
7
8
  const CALL_VALUE_MAX_CHARACTERS = 80;
8
9
 
10
+ /** name/title before id: a caller that already knows the name shouldn't have the raw id echoed back at it. */
11
+ const IDENTITY_ARG_KEYS = ["name", "title", "id", "text", "query", "kind", "template_id"];
12
+
9
13
  export interface PapyrusToolRenderContext {
10
14
  lastComponent: Component | undefined;
11
15
  isError: boolean;
12
16
  }
13
17
 
14
- function primaryArgument(args: Record<string, unknown>): string | undefined {
15
- // name/title before id: a caller that already knows the name shouldn't have the raw id echoed
16
- // back at it; id only surfaces here when it's genuinely the only identifying argument given.
17
- for (const key of ["name", "title", "id", "text", "query", "kind", "template_id"]) {
18
- const value = args[key];
19
- if (typeof value === "string" && value.trim()) return value.slice(0, CALL_VALUE_MAX_CHARACTERS);
20
- }
21
- return undefined;
22
- }
23
-
24
18
  /** Compact native call header that never echoes bodies or structured payloads. */
25
19
  export function renderPapyrusToolCall(label: string, args: Record<string, unknown>, theme: Theme): Component {
26
20
  const action = typeof args.action === "string" ? args.action : "call";
27
- const primary = primaryArgument(args);
21
+ const primary = pickIdentityArgument(args, IDENTITY_ARG_KEYS, CALL_VALUE_MAX_CHARACTERS);
28
22
  const text = [
29
23
  theme.fg("toolTitle", theme.bold(label)),
30
24
  theme.fg("muted", action),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.45.3",
3
+ "version": "0.45.5",
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"],
@@ -20,11 +20,11 @@
20
20
  "@danypops/jittor": "^0.14.0",
21
21
  "@danypops/papyrus": "^0.44.0",
22
22
  "@danypops/vehicle-client": "^0.5.0",
23
- "@danypops/vehicle-client-pi": "^0.16.1",
23
+ "@danypops/vehicle-client-pi": "^0.16.7",
24
24
  "@danypops/vehicle-core": "^0.10.0",
25
25
  "@danypops/vehicle-server": "^0.13.0",
26
26
  "beautiful-mermaid": "1.1.3",
27
- "malevich-tui-components": "^0.20.3"
27
+ "malevich-tui-components": "^0.21.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@danypops/pi-tui-harness": "^0.0.2",