@danypops/papyrus 0.11.3 → 0.12.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.
- package/README.md +16 -2
- package/extension/src/active-task-continuation.ts +6 -0
- package/extension/src/domain-tools.ts +108 -52
- package/extension/src/index.ts +90 -37
- package/extension/src/notes.ts +14 -1
- package/extension/src/task-focus-events.ts +57 -0
- package/extension/src/tasks.ts +51 -15
- package/extension/src/tool-rendering/artifact-card.ts +117 -0
- package/extension/src/tool-rendering/artifact-list.ts +179 -0
- package/extension/src/tool-rendering/index.ts +107 -0
- package/extension/src/tool-rendering/render-model.ts +406 -0
- package/package.json +4 -2
- package/src/adapters/in-memory-conversation-journal-store.ts +48 -0
- package/src/adapters/sqlite-artifact-scope-store.ts +36 -0
- package/src/adapters/sqlite-artifact-store.ts +20 -11
- package/src/adapters/sqlite-discourse-store.ts +325 -0
- package/src/adapters/sqlite-graph-projection-store.ts +41 -0
- package/src/adapters/sqlite-task-focus-store.ts +34 -15
- package/src/authority-registry.ts +115 -0
- package/src/cli.ts +904 -124
- package/src/constants.ts +38 -5
- package/src/conversation-journal-service.ts +87 -0
- package/src/db.ts +336 -8
- package/src/domain/artifact-event.ts +99 -0
- package/src/domain/conversation-journal.ts +168 -0
- package/src/domain/discourse-store.ts +142 -0
- package/src/domain/graph-projection.ts +74 -0
- package/src/domain/task-event.ts +4 -0
- package/src/domain-services.ts +133 -38
- package/src/graph-projection-service.ts +103 -0
- package/src/id-migration.ts +200 -0
- package/src/module-registry.ts +53 -0
- package/src/modules/docs.ts +77 -0
- package/src/modules/graph-projection.ts +82 -0
- package/src/modules/notes.ts +76 -0
- package/src/modules/rules.ts +81 -0
- package/src/modules/skills.ts +113 -0
- package/src/modules/tasks.ts +164 -0
- package/src/ops.ts +142 -15
- package/src/ports/artifact-scope-store.ts +20 -0
- package/src/ports/artifact-store.ts +10 -5
- package/src/ports/conversation-journal-store.ts +17 -0
- package/src/ports/graph-projection-store.ts +15 -0
- package/src/ports/task-focus-store.ts +62 -20
- package/src/service.ts +218 -223
- package/src/task-service.ts +70 -38
package/README.md
CHANGED
|
@@ -56,6 +56,20 @@ It blocks every Papyrus push whose destination is not `DanyPops/papyrus`, includ
|
|
|
56
56
|
|
|
57
57
|
The daemon uses WAL, foreign keys, a bounded busy timeout, versioned migrations, periodic passive checkpoints, and periodic `PRAGMA optimize`. Keep the database on a local filesystem; SQLite WAL does not support network filesystems.
|
|
58
58
|
|
|
59
|
+
### Context Mesh persistence model
|
|
60
|
+
|
|
61
|
+
`artifacts` is the shared graph-identity supertype, not a second copy of every application's database. `edges` references that single identity table at both endpoints, preserving foreign-key integrity for cross-domain links. Domain extension tables exist only where application invariants require indexed relational state: Task chronology/focus/scope and Discourse posts/events/session cursors/projection checkpoints. This is a class-table/table-per-type variant with explicit child-to-parent foreign keys; Papyrus does not use SQLite table inheritance or orphan-prone `(target_type, target_id)` links.
|
|
62
|
+
|
|
63
|
+
The owning application remains the mutation authority. Discourse commits its extension rows and `context-thread`/`context-message` Doc projections atomically through `discourse.store`; generic artifact, document, Skill-template, lifecycle, and graph-link operations reject those owned subtypes and the `reply_to`/`discusses` relations. SQLite triggers additionally verify that each extension row references the expected Doc subtype. Domain tables are canonical for domain invariants; graph bodies and metadata are read-oriented projections committed in the same transaction.
|
|
64
|
+
|
|
65
|
+
The authenticated CLI exposes the same operation for diagnostics and adapter parity:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
papyrus discourse store read_thread --store-id team-forum \
|
|
69
|
+
--input-json '{"forumId":"engineering","topicId":"reviews","threadId":"mesh","limit":25}' \
|
|
70
|
+
--json
|
|
71
|
+
```
|
|
72
|
+
|
|
59
73
|
## Schema protocol (enforceable)
|
|
60
74
|
|
|
61
75
|
Papyrus enforces four artifact kinds:
|
|
@@ -217,10 +231,10 @@ packed install npm:@danypops/papyrus
|
|
|
217
231
|
~/.pi/agent/npm/node_modules/.bin/papyrus service install
|
|
218
232
|
```
|
|
219
233
|
|
|
220
|
-
Existing databases are never migrated on daemon boot. After upgrading to
|
|
234
|
+
Existing databases are never migrated on daemon boot. After upgrading to a newer schema, run the authenticated CLI migration explicitly. Older databases receive prerequisite schemas—including Task continuation and Context Mesh extensions—in one transaction. Existing Tasks are deliberately marked **unscoped**: Papyrus does not guess ownership from titles, labels, historical cwd, or repository names. They remain visible in **All projects** until explicitly assigned with `papyrus tasks assign-project <task-id> [project-root]`:
|
|
221
235
|
|
|
222
236
|
```bash
|
|
223
|
-
~/.pi/agent/npm/node_modules/.bin/papyrus migrate
|
|
237
|
+
~/.pi/agent/npm/node_modules/.bin/papyrus migrate schema
|
|
224
238
|
```
|
|
225
239
|
|
|
226
240
|
Until that command succeeds, health reports `migrationRequired` and normal domain operations are rejected with actionable guidance. Migration is not exposed as a Pi tool or MCP action. New empty databases bootstrap directly at the current schema.
|
|
@@ -41,6 +41,8 @@ function continuationPrompt(task: ActiveTaskMarker): string {
|
|
|
41
41
|
return [
|
|
42
42
|
"Continue the active Papyrus Task now; do not hand off merely because the previous Pi run settled.",
|
|
43
43
|
"Reconcile its lifecycle, take the next concrete action, use tools, submit it for review when implementation effort is ready, and run gates plus checklist review before completion.",
|
|
44
|
+
"Do not shrink the task's scope to whatever fits in this turn, and do not treat a status update or summary as a substitute for doing the work or as proof of completion.",
|
|
45
|
+
"If something blocks progress, do not reject or pause on the first obstacle -- only after it genuinely recurs, and only when the task truly cannot proceed without external input.",
|
|
44
46
|
`Active task: ${task.id}: ${task.title.slice(0, TITLE_LIMIT)}`,
|
|
45
47
|
].join("\n");
|
|
46
48
|
}
|
|
@@ -97,6 +99,10 @@ export class ActiveTaskContinuation {
|
|
|
97
99
|
this.queued = false;
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
onCompaction(): void {
|
|
103
|
+
this.queued = false;
|
|
104
|
+
}
|
|
105
|
+
|
|
100
106
|
onHumanInput(): void {
|
|
101
107
|
this.resetProgress();
|
|
102
108
|
}
|
|
@@ -7,11 +7,23 @@ import type { TaskExecutionPlan } from "../../src/task-execution.ts";
|
|
|
7
7
|
import type { TaskHistoryPage } from "../../src/domain/task-event.ts";
|
|
8
8
|
import type { TaskCompletion, TaskGraph } from "../../src/task-service.ts";
|
|
9
9
|
import type { SkillWorkflowRunResult } from "../../src/skill-execution.ts";
|
|
10
|
+
import { emitTaskFocusEvent } from "./task-focus-events.ts";
|
|
10
11
|
import { NOTE_DISPOSITIONS } from "../../src/note-service.ts";
|
|
11
12
|
import { callService } from "./service-client.ts";
|
|
13
|
+
import { renderPapyrusToolCall, renderPapyrusToolResult } from "./tool-rendering/index.ts";
|
|
14
|
+
import {
|
|
15
|
+
createArtifactDetails,
|
|
16
|
+
createArtifactListDetails,
|
|
17
|
+
createGateRunDetails,
|
|
18
|
+
createGraphDetails,
|
|
19
|
+
createInvocationDetails,
|
|
20
|
+
createModelContent,
|
|
21
|
+
createPreviewDetails,
|
|
22
|
+
} from "./tool-rendering/render-model.ts";
|
|
12
23
|
|
|
13
|
-
function text(message: string, details:
|
|
14
|
-
|
|
24
|
+
function text(message: string, details: unknown = {}) {
|
|
25
|
+
const modelContent = createModelContent(message);
|
|
26
|
+
return { content: [{ type: "text" as const, text: modelContent.text }], details };
|
|
15
27
|
}
|
|
16
28
|
|
|
17
29
|
function artifactLine(artifact: Artifact): string {
|
|
@@ -32,7 +44,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
32
44
|
pi.registerTool({
|
|
33
45
|
name: "tasks",
|
|
34
46
|
label: "Tasks",
|
|
35
|
-
description: "Task domain tool. ACTIONS: create, update, list, show, history, scope, set_scope, assign_project, graph, plan, active, focused, focus, pause, unpause, clear_focus, start, submit, complete, reject, retry, cancel, run_gates, set_checklist, depend, contain. Lifecycle is todo → in-progress → review → done, with review failure → rejected and retry → in-progress; canceled is terminal. update can recover a Task accidentally created terminal by setting status=todo with a reason, but cannot rewrite legitimate lifecycle history. Active focus is independent and identifies the one task auto-drive continues. Completion runs gates and checklist-proof review, then focuses one deterministic ready successor without claiming effort. Dependency cycles are rejected. Prefer this over low-level papyrus_* tools for task work.",
|
|
47
|
+
description: "Task domain tool. ACTIONS: create, update, list, show, history, scope, set_scope, assign_project, graph, plan, active, focused, focus, pause, unpause, clear_focus, start, submit, complete, reject, retry, cancel, run_gates, set_checklist, depend, undepend, contain, uncontain. Lifecycle is todo → in-progress → review → done, with review failure → rejected and retry → in-progress; canceled is terminal. update can recover a Task accidentally created terminal by setting status=todo with a reason, but cannot rewrite legitimate lifecycle history. Active focus is independent and identifies the one task auto-drive continues. Completion runs gates and checklist-proof review, then focuses one deterministic ready successor without claiming effort. Dependency cycles are rejected. undepend/uncontain are idempotent for an already-absent relationship and never start, complete, or focus work merely because an edge disappeared; uncontain removes both contains and part_of edges atomically. Prefer this over low-level papyrus_* tools for task work.",
|
|
36
48
|
parameters: Type.Object({
|
|
37
49
|
action: Type.String(),
|
|
38
50
|
id: Type.Optional(Type.String()),
|
|
@@ -58,53 +70,72 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
58
70
|
scope: Type.Optional(Type.Union([Type.Literal("project"), Type.Literal("graph"), Type.Literal("all")])),
|
|
59
71
|
root_task_id: Type.Optional(Type.String()),
|
|
60
72
|
}),
|
|
73
|
+
renderCall(args, theme) { return renderPapyrusToolCall("Tasks", args, theme); },
|
|
74
|
+
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
61
75
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
62
76
|
try {
|
|
63
77
|
const action = params.action;
|
|
64
|
-
|
|
78
|
+
// Defaults to this Pi session's own id so Focus reads/writes are isolated per agent
|
|
79
|
+
// without depending on the model to know or supply its own session identity.
|
|
80
|
+
const request = { ...params, project_root: params.project_root ?? ctx.cwd, actor: "agent", source: "pi-tool", session_id: params.session_id ?? ctx.sessionManager.getSessionId() };
|
|
65
81
|
if (action === "create") {
|
|
66
82
|
const artifact = await callService<Record<string, unknown>, Artifact>("tasks.create", request);
|
|
67
|
-
return text(`Created task ${artifactLine(artifact)}`,
|
|
83
|
+
return text(`Created task ${artifactLine(artifact)}`, createArtifactDetails("tasks.create", artifact));
|
|
68
84
|
}
|
|
69
85
|
if (action === "list") {
|
|
70
86
|
const rows = await callService<Record<string, unknown>, Artifact[]>("tasks.list", request);
|
|
71
|
-
return text(rows.length ? rows.map(artifactLine).join("\n") : "No tasks found.",
|
|
87
|
+
return text(rows.length ? rows.map(artifactLine).join("\n") : "No tasks found.", createArtifactListDetails("tasks.list", rows));
|
|
72
88
|
}
|
|
73
89
|
if (action === "show") {
|
|
74
90
|
const artifact = await callService<Record<string, unknown>, Artifact>("tasks.show", params);
|
|
75
|
-
return text(`${artifactLine(artifact)}\n\n${artifact.body}`,
|
|
91
|
+
return text(`${artifactLine(artifact)}\n\n${artifact.body}`, createArtifactDetails("tasks.show", artifact));
|
|
76
92
|
}
|
|
77
93
|
if (action === "history") {
|
|
78
94
|
const page = await callService<Record<string, unknown>, TaskHistoryPage>("tasks.history", request);
|
|
79
95
|
const lines = page.events.map((event) => `${event.occurredAt} ${event.type} ${event.fromStatus ?? "∅"} → ${event.toStatus ?? "∅"} · ${event.actor}/${event.source}${event.reason ? ` · ${event.reason}` : ""}`);
|
|
80
|
-
|
|
96
|
+
const output = lines.join("\n") || "No recorded history for this task.";
|
|
97
|
+
return text(output, createPreviewDetails("tasks.history", "Task history", output));
|
|
81
98
|
}
|
|
82
99
|
if (action === "scope") {
|
|
83
100
|
const selection = await callService<Record<string, unknown>, import("../../src/domain/task-scope.ts").TaskViewSelection>("tasks.scope", request);
|
|
84
|
-
return text(`Task scope: ${selection.label}`,
|
|
101
|
+
return text(`Task scope: ${selection.label}`, createPreviewDetails("tasks.scope", "Task scope", selection.label));
|
|
85
102
|
}
|
|
86
103
|
if (action === "active") {
|
|
87
104
|
const artifact = await callService<Record<string, unknown>, Artifact | null>("tasks.active", request);
|
|
88
|
-
return
|
|
105
|
+
return artifact
|
|
106
|
+
? text(`Active: ${artifactLine(artifact)}`, createArtifactDetails("tasks.active", artifact))
|
|
107
|
+
: text("No active task.", createPreviewDetails("tasks.active", "Active task", "No active task."));
|
|
89
108
|
}
|
|
90
109
|
if (action === "focused") {
|
|
91
110
|
const focus = await callService<Record<string, unknown>, { artifact: Artifact; status: string } | null>("tasks.focused", request);
|
|
92
|
-
return
|
|
111
|
+
return focus
|
|
112
|
+
? text(`Focused (${focus.status}): ${artifactLine(focus.artifact)}`, createArtifactDetails("tasks.focused", focus.artifact))
|
|
113
|
+
: text("No focused task.", createPreviewDetails("tasks.focused", "Focused task", "No focused task."));
|
|
93
114
|
}
|
|
94
115
|
if (action === "pause" || action === "unpause") {
|
|
95
116
|
const operation = action === "pause" ? "tasks.pause" : "tasks.unpause";
|
|
96
117
|
const focus = await callService<Record<string, unknown>, { artifact: Artifact; status: string }>(operation, request);
|
|
97
|
-
|
|
118
|
+
emitTaskFocusEvent({ taskId: focus.artifact.id, sessionId: request.session_id as string, status: action === "pause" ? "paused" : "unpaused" });
|
|
119
|
+
return text(`Focused (${focus.status}): ${artifactLine(focus.artifact)}`, createArtifactDetails(operation, focus.artifact));
|
|
98
120
|
}
|
|
99
121
|
if (action === "clear_focus") {
|
|
100
122
|
const result = await callService<Record<string, unknown>, { cleared: boolean }>("tasks.clear_focus", request);
|
|
101
|
-
|
|
123
|
+
if (result.cleared) emitTaskFocusEvent({ taskId: null, sessionId: request.session_id as string, status: "cleared" });
|
|
124
|
+
const output = result.cleared ? "Task focus cleared." : "No focused task.";
|
|
125
|
+
return text(output, createPreviewDetails("tasks.clear_focus", "Task focus", output));
|
|
102
126
|
}
|
|
103
127
|
if (action === "graph") {
|
|
104
128
|
const graph = await callService<Record<string, unknown>, TaskGraph>("tasks.graph", request);
|
|
105
129
|
const dependencies = graph.nodes.reduce((count, node) => count + node.dependencyIds.length, 0);
|
|
106
130
|
const containment = graph.nodes.reduce((count, node) => count + node.childIds.length, 0);
|
|
107
|
-
|
|
131
|
+
const edges = graph.nodes.flatMap((node) => [
|
|
132
|
+
...node.dependencyIds.map((dependencyId) => ({ from: node.task.id, relation: "depends_on", to: dependencyId })),
|
|
133
|
+
...node.childIds.map((childId) => ({ from: node.task.id, relation: "contains", to: childId })),
|
|
134
|
+
]);
|
|
135
|
+
return text(
|
|
136
|
+
`Task graph: ${graph.nodes.length} nodes, ${graph.rootIds.length} roots, ${dependencies} dependencies, ${containment} containment edges.`,
|
|
137
|
+
createGraphDetails("tasks.graph", graph.nodes.map((node) => node.task), edges),
|
|
138
|
+
);
|
|
108
139
|
}
|
|
109
140
|
if (action === "plan") {
|
|
110
141
|
const plan = await callService<Record<string, unknown>, TaskExecutionPlan>("tasks.plan", request);
|
|
@@ -117,11 +148,12 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
117
148
|
}),
|
|
118
149
|
]);
|
|
119
150
|
if (plan.cycleIds.length > 0) lines.push(`Invalid cycle: ${plan.cycleIds.join(", ")}`);
|
|
120
|
-
|
|
151
|
+
const output = lines.join("\n") || "No tasks in execution plan.";
|
|
152
|
+
return text(output, createPreviewDetails("tasks.plan", "Task execution plan", output));
|
|
121
153
|
}
|
|
122
154
|
if (action === "set_checklist") {
|
|
123
155
|
const artifact = await callService<Record<string, unknown>, Artifact>("tasks.set_checklist", params);
|
|
124
|
-
return text(`Updated checklist: ${artifactLine(artifact)}`,
|
|
156
|
+
return text(`Updated checklist: ${artifactLine(artifact)}`, createArtifactDetails("tasks.set_checklist", artifact));
|
|
125
157
|
}
|
|
126
158
|
if (action === "complete") {
|
|
127
159
|
const result = await callService<Record<string, unknown>, TaskCompletion>("tasks.complete", request);
|
|
@@ -131,11 +163,17 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
131
163
|
const blocked = result.blocked.length > 0
|
|
132
164
|
? `\nBlocked: ${result.blocked.map((entry) => `${artifactLine(entry.artifact)} waits for ${entry.dependencyIds.join(", ")}`).join("; ")}`
|
|
133
165
|
: "";
|
|
134
|
-
|
|
166
|
+
const output = `${result.completed ? "Completed" : "Rejected"}: ${artifactLine(result.artifact)}${focused}${blocked}${checklist ? `\n${checklist}` : ""}${gates ? `\n${gates}` : ""}`;
|
|
167
|
+
return text(output, createPreviewDetails("tasks.complete", "Task completion", output));
|
|
135
168
|
}
|
|
136
169
|
if (action === "run_gates") {
|
|
137
170
|
const gates = await callService<Record<string, unknown>, GateResult[]>("tasks.run_gates", request);
|
|
138
|
-
return text(
|
|
171
|
+
return text(
|
|
172
|
+
gates.map((gate) => `${gate.passed ? "✓" : "✗"} ${gate.gate.type}: ${gate.gate.target} — ${gate.output}`).join("\n") || "No gates configured.",
|
|
173
|
+
createGateRunDetails("tasks.run_gates", params.id ?? "", gates.map((gate) => ({
|
|
174
|
+
passed: gate.passed, type: gate.gate.type, target: gate.gate.target, output: gate.output,
|
|
175
|
+
}))),
|
|
176
|
+
);
|
|
139
177
|
}
|
|
140
178
|
const operations = {
|
|
141
179
|
focus: "tasks.focus",
|
|
@@ -148,14 +186,17 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
148
186
|
set_scope: "tasks.set_scope",
|
|
149
187
|
assign_project: "tasks.assign_project",
|
|
150
188
|
depend: "tasks.depend",
|
|
189
|
+
undepend: "tasks.undepend",
|
|
151
190
|
contain: "tasks.contain",
|
|
191
|
+
uncontain: "tasks.uncontain",
|
|
152
192
|
} as const;
|
|
153
193
|
const operation = operations[action as keyof typeof operations];
|
|
154
|
-
if (!operation)
|
|
194
|
+
if (!operation) throw new Error(`unknown tasks action: ${action}`);
|
|
155
195
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, request);
|
|
156
|
-
|
|
196
|
+
if (operation === "tasks.focus") emitTaskFocusEvent({ taskId: artifact.id, sessionId: request.session_id as string, status: "focused" });
|
|
197
|
+
return text(artifactLine(artifact), createArtifactDetails(operation, artifact));
|
|
157
198
|
} catch (error) {
|
|
158
|
-
|
|
199
|
+
throw new Error(`tasks failed: ${error instanceof Error ? error.message : error}`);
|
|
159
200
|
}
|
|
160
201
|
},
|
|
161
202
|
});
|
|
@@ -178,29 +219,31 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
178
219
|
session_id: Type.Optional(Type.String()),
|
|
179
220
|
project_root: Type.Optional(Type.String()),
|
|
180
221
|
}),
|
|
222
|
+
renderCall(args, theme) { return renderPapyrusToolCall("Notes", args, theme); },
|
|
223
|
+
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
181
224
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
182
225
|
try {
|
|
183
226
|
const action = params.action;
|
|
184
227
|
const request = { ...params, project_root: params.project_root ?? ctx.cwd, actor: "agent", source: "notes-tool" };
|
|
185
228
|
if (action === "capture") {
|
|
186
229
|
const artifact = await callService<Record<string, unknown>, Artifact>("notes.capture", request);
|
|
187
|
-
return text(`Captured note ${artifactLine(artifact)}`,
|
|
230
|
+
return text(`Captured note ${artifactLine(artifact)}`, createArtifactDetails("notes.capture", artifact));
|
|
188
231
|
}
|
|
189
232
|
if (action === "list") {
|
|
190
233
|
const rows = await callService<Record<string, unknown>, Artifact[]>("notes.list", request);
|
|
191
|
-
return text(rows.length ? rows.map(artifactLine).join("\n") : "No open notes.",
|
|
234
|
+
return text(rows.length ? rows.map(artifactLine).join("\n") : "No open notes.", createArtifactListDetails("notes.list", rows));
|
|
192
235
|
}
|
|
193
236
|
if (action === "show") {
|
|
194
237
|
const artifact = await callService<Record<string, unknown>, Artifact>("notes.show", request);
|
|
195
|
-
return text(`${artifactLine(artifact)}\n\n${artifact.body}`,
|
|
238
|
+
return text(`${artifactLine(artifact)}\n\n${artifact.body}`, createArtifactDetails("notes.show", artifact));
|
|
196
239
|
}
|
|
197
240
|
const operations = { consume: "notes.consume", promote: "notes.promote", archive: "notes.archive" } as const;
|
|
198
241
|
const operation = operations[action as keyof typeof operations];
|
|
199
|
-
if (!operation)
|
|
242
|
+
if (!operation) throw new Error(`unknown notes action: ${action}`);
|
|
200
243
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, request);
|
|
201
|
-
return text(`${action}: ${artifactLine(artifact)}`,
|
|
244
|
+
return text(`${action}: ${artifactLine(artifact)}`, createArtifactDetails(operation, artifact));
|
|
202
245
|
} catch (error) {
|
|
203
|
-
|
|
246
|
+
throw new Error(`notes failed: ${error instanceof Error ? error.message : error}`);
|
|
204
247
|
}
|
|
205
248
|
},
|
|
206
249
|
});
|
|
@@ -208,7 +251,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
208
251
|
pi.registerTool({
|
|
209
252
|
name: "docs",
|
|
210
253
|
label: "Documents",
|
|
211
|
-
description: "Document domain tool. ACTIONS: create, list, show, activate, archive, reopen, link. Prefer this over low-level papyrus_* tools for document work.",
|
|
254
|
+
description: "Document domain tool. ACTIONS: create, list, show, activate, archive, reopen, link, assign_project. project_root is optional at creation (omitted = unscoped); assign_project reassigns it later, or unscopes when project_root is omitted. Prefer this over low-level papyrus_* tools for document work.",
|
|
212
255
|
parameters: Type.Object({
|
|
213
256
|
action: Type.String(),
|
|
214
257
|
id: Type.Optional(Type.String()),
|
|
@@ -223,29 +266,32 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
223
266
|
template_id: Type.Optional(Type.String()),
|
|
224
267
|
relation: Type.Optional(Type.String()),
|
|
225
268
|
target_id: Type.Optional(Type.String()),
|
|
269
|
+
project_root: Type.Optional(Type.String()),
|
|
226
270
|
}),
|
|
271
|
+
renderCall(args, theme) { return renderPapyrusToolCall("Documents", args, theme); },
|
|
272
|
+
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
227
273
|
async execute(_id, params) {
|
|
228
274
|
try {
|
|
229
275
|
const action = params.action;
|
|
230
276
|
if (action === "create") {
|
|
231
277
|
const artifact = await callService<Record<string, unknown>, Artifact>("docs.create", params);
|
|
232
|
-
return text(`Created document ${artifactLine(artifact)}`,
|
|
278
|
+
return text(`Created document ${artifactLine(artifact)}`, createArtifactDetails("docs.create", artifact));
|
|
233
279
|
}
|
|
234
280
|
if (action === "list") {
|
|
235
281
|
const rows = await callService<Record<string, unknown>, Artifact[]>("docs.list", params);
|
|
236
|
-
return text(rows.length ? rows.map(artifactLine).join("\n") : "No documents found.",
|
|
282
|
+
return text(rows.length ? rows.map(artifactLine).join("\n") : "No documents found.", createArtifactListDetails("docs.list", rows));
|
|
237
283
|
}
|
|
238
284
|
if (action === "show") {
|
|
239
285
|
const artifact = await callService<Record<string, unknown>, Artifact>("docs.show", params);
|
|
240
|
-
return text(`${artifactLine(artifact)}\n\n${artifact.body}`,
|
|
286
|
+
return text(`${artifactLine(artifact)}\n\n${artifact.body}`, createArtifactDetails("docs.show", artifact));
|
|
241
287
|
}
|
|
242
|
-
const operations = { activate: "docs.activate", archive: "docs.archive", reopen: "docs.reopen", link: "docs.link" } as const;
|
|
288
|
+
const operations = { activate: "docs.activate", archive: "docs.archive", reopen: "docs.reopen", link: "docs.link", assign_project: "docs.assign_project" } as const;
|
|
243
289
|
const operation = operations[action as keyof typeof operations];
|
|
244
|
-
if (!operation)
|
|
290
|
+
if (!operation) throw new Error(`unknown docs action: ${action}`);
|
|
245
291
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, params);
|
|
246
|
-
return text(artifactLine(artifact),
|
|
292
|
+
return text(artifactLine(artifact), createArtifactDetails(operation, artifact));
|
|
247
293
|
} catch (error) {
|
|
248
|
-
|
|
294
|
+
throw new Error(`docs failed: ${error instanceof Error ? error.message : error}`);
|
|
249
295
|
}
|
|
250
296
|
},
|
|
251
297
|
});
|
|
@@ -253,36 +299,39 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
253
299
|
pi.registerTool({
|
|
254
300
|
name: "rules",
|
|
255
301
|
label: "Rules",
|
|
256
|
-
description: "Rule domain tool. ACTIONS: create, list, show, preview, enable, disable, gate. Active rules inject into the agent system prompt.",
|
|
302
|
+
description: "Rule domain tool. ACTIONS: create, list, show, preview, enable, disable, gate, assign_project. 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.",
|
|
257
303
|
parameters: Type.Object({
|
|
258
304
|
action: Type.String(), id: Type.Optional(Type.String()), title: Type.Optional(Type.String()),
|
|
259
305
|
body: Type.Optional(Type.String()), condition: Type.Optional(Type.String()), rule_action: Type.Optional(Type.String()),
|
|
260
306
|
severity: Type.Optional(Type.String()), labels: Type.Optional(Type.Array(Type.String())),
|
|
261
307
|
extra: Type.Optional(Type.Record(Type.String(), Type.Unknown())), status: Type.Optional(Type.String()),
|
|
262
308
|
text: Type.Optional(Type.String()), limit: Type.Optional(Type.Number()), task_id: Type.Optional(Type.String()),
|
|
309
|
+
project_root: Type.Optional(Type.String()),
|
|
263
310
|
}),
|
|
311
|
+
renderCall(args, theme) { return renderPapyrusToolCall("Rules", args, theme); },
|
|
312
|
+
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
264
313
|
async execute(_id, params) {
|
|
265
314
|
try {
|
|
266
315
|
const action = params.action;
|
|
267
316
|
if (action === "create") {
|
|
268
317
|
const artifact = await callService<Record<string, unknown>, Artifact>("rules.create", params);
|
|
269
|
-
return text(`Created rule ${artifactLine(artifact)}`,
|
|
318
|
+
return text(`Created rule ${artifactLine(artifact)}`, createArtifactDetails("rules.create", artifact));
|
|
270
319
|
}
|
|
271
320
|
if (action === "list") {
|
|
272
321
|
const rows = await callService<Record<string, unknown>, Artifact[]>("rules.list", params);
|
|
273
|
-
return text(rows.length ? rows.map(artifactLine).join("\n") : "No rules found.",
|
|
322
|
+
return text(rows.length ? rows.map(artifactLine).join("\n") : "No rules found.", createArtifactListDetails("rules.list", rows));
|
|
274
323
|
}
|
|
275
324
|
if (action === "preview") {
|
|
276
325
|
const preview = await callService<Record<string, unknown>, string>("rules.preview", params);
|
|
277
|
-
return text(preview,
|
|
326
|
+
return text(preview, createPreviewDetails("rules.preview", "Rule preview", preview));
|
|
278
327
|
}
|
|
279
|
-
const operations = { show: "rules.show", enable: "rules.enable", disable: "rules.disable", gate: "rules.gate" } as const;
|
|
328
|
+
const operations = { show: "rules.show", enable: "rules.enable", disable: "rules.disable", gate: "rules.gate", assign_project: "rules.assign_project" } as const;
|
|
280
329
|
const operation = operations[action as keyof typeof operations];
|
|
281
|
-
if (!operation)
|
|
330
|
+
if (!operation) throw new Error(`unknown rules action: ${action}`);
|
|
282
331
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, params);
|
|
283
|
-
return text(`${artifactLine(artifact)}${action === "show" ? `\n\n${artifact.body}` : ""}`,
|
|
332
|
+
return text(`${artifactLine(artifact)}${action === "show" ? `\n\n${artifact.body}` : ""}`, createArtifactDetails(operation, artifact));
|
|
284
333
|
} catch (error) {
|
|
285
|
-
|
|
334
|
+
throw new Error(`rules failed: ${error instanceof Error ? error.message : error}`);
|
|
286
335
|
}
|
|
287
336
|
},
|
|
288
337
|
});
|
|
@@ -290,7 +339,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
290
339
|
pi.registerTool({
|
|
291
340
|
name: "skills",
|
|
292
341
|
label: "Skills",
|
|
293
|
-
description: "Papyrus Skill workflow and compatibility-template domain tool. Papyrus Skills are parameterized Task/Rule/Doc bundles, distinct from prompt-only skills. ACTIONS: create, create_template, list, show, invoke, run, enable, disable, instantiate. run validates arguments and atomically creates one scoped workflow run.",
|
|
342
|
+
description: "Papyrus Skill workflow and compatibility-template domain tool. Papyrus Skills are parameterized Task/Rule/Doc bundles, distinct from prompt-only skills. ACTIONS: create, create_template, list, show, invoke, run, enable, disable, instantiate, assign_project. run validates arguments and atomically creates one scoped workflow run. project_root is optional at creation (omitted = unscoped) for create/create_template; assign_project reassigns it later, or unscopes when project_root is omitted.",
|
|
294
343
|
parameters: Type.Object({
|
|
295
344
|
action: Type.String(), id: Type.Optional(Type.String()), title: Type.Optional(Type.String()),
|
|
296
345
|
body: Type.Optional(Type.String()), trigger: Type.Optional(Type.String()), steps: Type.Optional(Type.Array(Type.String())),
|
|
@@ -303,6 +352,8 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
303
352
|
required: Type.Optional(Type.Array(Type.String())), kind: Type.Optional(Type.String()), subtype: Type.Optional(Type.String()),
|
|
304
353
|
project_root: Type.Optional(Type.String()),
|
|
305
354
|
}),
|
|
355
|
+
renderCall(args, theme) { return renderPapyrusToolCall("Skills", args, theme); },
|
|
356
|
+
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
306
357
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
307
358
|
try {
|
|
308
359
|
const action = params.action;
|
|
@@ -310,15 +361,15 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
310
361
|
if (action === "create" || action === "create_template") {
|
|
311
362
|
const operation = action === "create" ? "skills.create" : "skills.create_template";
|
|
312
363
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, params);
|
|
313
|
-
return text(`Created skill ${artifactLine(artifact)}`,
|
|
364
|
+
return text(`Created skill ${artifactLine(artifact)}`, createArtifactDetails(operation, artifact));
|
|
314
365
|
}
|
|
315
366
|
if (action === "list") {
|
|
316
367
|
const rows = await callService<Record<string, unknown>, Artifact[]>("skills.list", params);
|
|
317
|
-
return text(rows.length ? rows.map(artifactLine).join("\n") : "No skills found.",
|
|
368
|
+
return text(rows.length ? rows.map(artifactLine).join("\n") : "No skills found.", createArtifactListDetails("skills.list", rows));
|
|
318
369
|
}
|
|
319
370
|
if (action === "invoke") {
|
|
320
371
|
const invocation = await callService<Record<string, unknown>, string>("skills.invoke", params);
|
|
321
|
-
return text(invocation,
|
|
372
|
+
return text(invocation, createPreviewDetails("skills.invoke", "Skill invocation", invocation));
|
|
322
373
|
}
|
|
323
374
|
if (action === "run") {
|
|
324
375
|
const run = await callService<Record<string, unknown>, SkillWorkflowRunResult>("skills.run", request);
|
|
@@ -329,15 +380,20 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
329
380
|
`Context docs: ${run.created.docs.join(", ") || "none"}.`,
|
|
330
381
|
`Scoped rules: ${run.created.rules.join(", ") || "none"}.`,
|
|
331
382
|
...(execution ? ["Execution:", execution] : []),
|
|
332
|
-
].join("\n"),
|
|
383
|
+
].join("\n"), createInvocationDetails("skills.run", run.runId, {
|
|
384
|
+
tasks: run.created.tasks,
|
|
385
|
+
docs: run.created.docs,
|
|
386
|
+
rules: run.created.rules,
|
|
387
|
+
roots: run.rootTaskIds,
|
|
388
|
+
}));
|
|
333
389
|
}
|
|
334
|
-
const operations = { show: "skills.show", enable: "skills.enable", disable: "skills.disable", instantiate: "skills.instantiate" } as const;
|
|
390
|
+
const operations = { show: "skills.show", enable: "skills.enable", disable: "skills.disable", instantiate: "skills.instantiate", assign_project: "skills.assign_project" } as const;
|
|
335
391
|
const operation = operations[action as keyof typeof operations];
|
|
336
|
-
if (!operation)
|
|
392
|
+
if (!operation) throw new Error(`unknown skills action: ${action}`);
|
|
337
393
|
const artifact = await callService<Record<string, unknown>, Artifact>(operation, action === "instantiate" ? request : params);
|
|
338
|
-
return text(`${artifactLine(artifact)}${action === "show" ? `\n\n${artifact.body}` : ""}`,
|
|
394
|
+
return text(`${artifactLine(artifact)}${action === "show" ? `\n\n${artifact.body}` : ""}`, createArtifactDetails(operation, artifact));
|
|
339
395
|
} catch (error) {
|
|
340
|
-
|
|
396
|
+
throw new Error(`skills failed: ${error instanceof Error ? error.message : error}`);
|
|
341
397
|
}
|
|
342
398
|
},
|
|
343
399
|
});
|