/** * pi-papyrus — native Pi extension for the Papyrus graph store. * * Tools: papyrus_query/graph/show (low-level), plus one native tool per domain (docs/rules/playbooks/tasks/discuss/notes). * Command: /tasks (interactive task panel). * Widget: persistent task status above editor (rpiv-todo pattern). * Injection: active rules + open tasks appended to system prompt every turn. * "Are we there yet?" — the agent sees its open work items. */ import { randomUUID } from "node:crypto"; import { CONTEXT_DEFAULT_RESERVE_TOKENS, CONTEXT_HUB_CONTRIBUTION_CHANNEL, CONTEXT_HUB_CONTRIBUTION_SCHEMA } from "@danypops/jittor"; import { type Artifact, type GateResult, NOTE_LIST_MAX_LIMIT, NOTE_WIDGET_OPEN_LIMIT, NOTE_WIDGET_POLL_INTERVAL_MS, NOTE_WIDGET_VISIBLE_ROWS, PAPYRUS_CONTEXT_INJECTION_CHANNEL, PAPYRUS_VEHICLE_NAME, TASK_DRIVER_MAX_TURNS, TASK_DRIVER_MAX_UNCHANGED_TURNS, TASK_WIDGET_POLL_INTERVAL_MS, TASK_WIDGET_VISIBLE_ROWS, type TaskGraph, type TaskStatus, WIDGET_CARD_ROTATION_INTERVAL_MS, } from "@danypops/papyrus"; import type { PushChannelClient } from "@danypops/vehicle-client/daemon-client"; import { vehicleWidgetOwner } from "@danypops/vehicle-client-pi/widget-header"; import type { ExtensionAPI, ExtensionContext, ExtensionUIContext, Theme } from "@earendil-works/pi-coding-agent"; import { truncateToWidth } from "@earendil-works/pi-tui"; import { AutoRotatingWindow, renderCardRow, type WidgetSection } from "malevich-tui-components"; import { Type } from "typebox"; import { formatMetadata } from "./artifact/artifact-format.ts"; import { BoundedPoll } from "./bounded-poll.ts"; import { buildActivationContext } from "./context/activation-context.ts"; import { buildTaskItemTree, computeContextBudget } from "./context/context-budget.ts"; import { PAPYRUS_CONTEXT_HUB_PRODUCER_NAME, papyrusContextSegment } from "./context/context-hub-contribution.ts"; import { buildContextInjection } from "./context/context-injection-telemetry.ts"; import { ensureTypingCourtesyTracking, isLiveAskPending } from "./discuss/discuss-ask-view.ts"; import { resolveNameFields } from "./domain-tools.ts"; import { buildNoteWidgetSection, type NoteWidgetRow } from "./note/note-widget.ts"; import { PLAYBOOK_BRIDGE_MAX_PLAYBOOKS, registerPlaybookBridge } from "./playbook/playbook-bridge.ts"; import { callService, callServicePassive, subscribeTaskPushChannel } from "./service-client.ts"; import { cacheSessionSecret, forgetSessionSecret, sessionSecretField } from "./session-identity.ts"; import { ActiveTaskContinuation, type ActiveTaskMarker, automaticPauseReason, shouldResumeFocusOnHumanInput, } from "./task/active-task-continuation.ts"; import { emitTaskFocusEvent, extractDeclaredEffort, setTaskFocusEventBus } from "./task/task-focus-events.ts"; import { TASK_STATUS_PRESENTATION, taskTreeConnector } from "./task/task-presentation.ts"; import { buildTaskWidgetProjection, type TaskWidgetProjection } from "./task/task-widget.ts"; import { measure as artifactCardMeasure } from "./tool-rendering/artifact-card.ts"; import { renderPapyrusToolCall, renderPapyrusToolResult } from "./tool-rendering/index.ts"; import { createArtifactDetails, createArtifactListDetails, createGraphDetails, createModelContent, createPreviewDetails, } from "./tool-rendering/render-model.ts"; import { registerNotesVehicle } from "./tools/vehicle-notes-client.ts"; function text(value: string, details: unknown = {}) { const modelContent = createModelContent(value); return { content: [{ type: "text" as const, text: modelContent.text }], details }; } function artifactTextLabel(artifact: Artifact): string { return `[${artifact.kind}|${artifact.status}] ${artifact.title}`; } function artifactTextLines(artifacts: readonly Artifact[]): string[] { const titleCounts = new Map(); for (const artifact of artifacts) titleCounts.set(artifact.title, (titleCounts.get(artifact.title) ?? 0) + 1); return artifacts.map((artifact) => titleCounts.get(artifact.title)! > 1 ? `${artifactTextLabel(artifact)} (${artifact.id})` : artifactTextLabel(artifact), ); } /** Resolves graph protocol ids into model-facing names; equal titles retain ids only to disambiguate. */ async function artifactNamesById(ids: readonly string[]): Promise> { const uniqueIds = [...new Set(ids)]; const artifacts = ( await Promise.all(uniqueIds.map((id) => callService, Artifact | null>("artifact.show", { id }))) ).filter((artifact): artifact is Artifact => artifact !== null); const titleCounts = new Map(); for (const artifact of artifacts) titleCounts.set(artifact.title, (titleCounts.get(artifact.title) ?? 0) + 1); return new Map( artifacts.map((artifact) => [ artifact.id, titleCounts.get(artifact.title)! > 1 ? `${artifact.title} (${artifact.id})` : artifact.title, ]), ); } // --------------------------------------------------------------------------- // Task + Notes widget (one shared aboveEditor CardRow grid -- PapyrusWidgetGroup below // tiles both overlays' own sections as bordered cards, instead of each registering // its own separate "Papyrus ·