@danypops/papyrus 0.33.2 → 0.33.3
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.
|
@@ -164,7 +164,7 @@ export function normalizeJsonEncodedField(params: Record<string, unknown>, key:
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/** Resolves every {nameKey -> idKey} pair present and not already satisfied by an explicit id, in place. */
|
|
167
|
-
async function resolveNameFields(
|
|
167
|
+
export async function resolveNameFields(
|
|
168
168
|
params: Record<string, unknown>,
|
|
169
169
|
fields: ReadonlyArray<{ nameKey: string; idKey: string; listOperation: OperationName; baseRequest: Record<string, unknown> }>,
|
|
170
170
|
): Promise<void> {
|
|
@@ -474,7 +474,7 @@ export function registerNotesTool(pi: ExtensionAPI): void {
|
|
|
474
474
|
pi.registerTool({
|
|
475
475
|
name: "notes",
|
|
476
476
|
label: "Notes",
|
|
477
|
-
description: "Deferred human-intent inbox. ACTIONS: capture, list, show, consume, promote, archive. Capture stores a request without creating work. Consume marks it considered. To promote, first create the resulting Task, Doc, Rule, or Skill through its domain tool, then link it with target_id. Archive requires an explicit disposition. PREFER `name` (the note's exact title) over `id` for show/consume/promote/archive --
|
|
477
|
+
description: "Deferred human-intent inbox. ACTIONS: capture, list, show, consume, promote, archive. Capture stores a request without creating work. Consume marks it considered. To promote, first create the resulting Task, Doc, Rule, or Skill through its domain tool, then link it with target_id (or target_name). Archive requires an explicit disposition. PREFER `name` (the note's exact title) over `id` for show/consume/promote/archive, and `target_name` over `target_id` for promote -- all are backend implementation details, resolved from name automatically (target_name searches across every kind, since a promotion target can be a task, doc, rule, or skill).",
|
|
478
478
|
parameters: Type.Object({
|
|
479
479
|
action: Type.String(),
|
|
480
480
|
id: Type.Optional(Type.String()),
|
|
@@ -485,6 +485,7 @@ export function registerNotesTool(pi: ExtensionAPI): void {
|
|
|
485
485
|
text: Type.Optional(Type.String()),
|
|
486
486
|
limit: Type.Optional(Type.Number()),
|
|
487
487
|
target_id: Type.Optional(Type.String()),
|
|
488
|
+
target_name: Type.Optional(Type.String()),
|
|
488
489
|
disposition: Type.Optional(Type.Union(NOTE_DISPOSITIONS.map((value) => Type.Literal(value)))),
|
|
489
490
|
reason: Type.Optional(Type.String()),
|
|
490
491
|
session_id: Type.Optional(Type.String()),
|
|
@@ -497,7 +498,11 @@ export function registerNotesTool(pi: ExtensionAPI): void {
|
|
|
497
498
|
const params: Record<string, unknown> = { ...rawParams };
|
|
498
499
|
const action = params.action;
|
|
499
500
|
const baseRequest = { project_root: params.project_root ?? ctx.cwd, actor: "agent", source: "notes-tool" };
|
|
500
|
-
await resolveNameFields(params, [
|
|
501
|
+
await resolveNameFields(params, [
|
|
502
|
+
{ nameKey: "name", idKey: "id", listOperation: "notes.list", baseRequest },
|
|
503
|
+
// Kind-agnostic: a promotion target can be a task, doc, rule, or skill, so this searches every kind rather than only notes.
|
|
504
|
+
{ nameKey: "target_name", idKey: "target_id", listOperation: "artifact.query", baseRequest },
|
|
505
|
+
]);
|
|
501
506
|
const request = { ...params, ...baseRequest };
|
|
502
507
|
if (action === "capture") {
|
|
503
508
|
const artifact = await callService<Record<string, unknown>, Artifact>("notes.capture", request);
|
package/extension/src/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { Artifact } from "../../src/domain/artifact.ts";
|
|
|
22
22
|
import type { GateResult } from "../../src/domain/gate.ts";
|
|
23
23
|
import { formatMetadata } from "./artifact-format.ts";
|
|
24
24
|
import { callService } from "./service-client.ts";
|
|
25
|
-
import { registerDomainTools } from "./domain-tools.ts";
|
|
25
|
+
import { registerDomainTools, resolveNameFields } from "./domain-tools.ts";
|
|
26
26
|
import { ensureTypingCourtesyTracking, isLiveAskPending } from "./discuss-ask-view.ts";
|
|
27
27
|
import { PLAYBOOK_BRIDGE_MAX_PLAYBOOKS, registerPlaybookBridge } from "./playbook-bridge.ts";
|
|
28
28
|
import type { TaskGraph, TaskStatus } from "../../src/task-service.ts";
|
|
@@ -331,12 +331,15 @@ export default async function (pi: ExtensionAPI) {
|
|
|
331
331
|
"ACTIONS: link (from+relation+to), unlink (from+relation+to — idempotent, no error if already absent; for Task depends_on/contains prefer the tasks tool's undepend/uncontain), " +
|
|
332
332
|
"tree (id → bounded BFS subgraph), " +
|
|
333
333
|
"history (who did what, when — requires id, actor, or session_id). " +
|
|
334
|
-
"status (id+status) exists at the protocol level but is refused for every kind with its own lifecycle (Doc/Rule/Skill/Playbook/Task/Note all reject it) -- use that kind's own domain tool for status changes (docs.activate, rules.enable, tasks.start, etc), never this."
|
|
334
|
+
"status (id+status) exists at the protocol level but is refused for every kind with its own lifecycle (Doc/Rule/Skill/Playbook/Task/Note all reject it) -- use that kind's own domain tool for status changes (docs.activate, rules.enable, tasks.start, etc), never this. " +
|
|
335
|
+
"PREFER `from_name`/`to_name` over `from`/`to` for link/unlink -- both are backend implementation details, resolved from name automatically, searching across every kind since either end of an edge can be any artifact.",
|
|
335
336
|
parameters: Type.Object({
|
|
336
337
|
action: Type.String({ description: "link | unlink | tree | status | history" }),
|
|
337
338
|
from: Type.Optional(Type.String()),
|
|
339
|
+
from_name: Type.Optional(Type.String()),
|
|
338
340
|
relation: Type.Optional(Type.String()),
|
|
339
341
|
to: Type.Optional(Type.String()),
|
|
342
|
+
to_name: Type.Optional(Type.String()),
|
|
340
343
|
id: Type.Optional(Type.String()),
|
|
341
344
|
status: Type.Optional(Type.String()),
|
|
342
345
|
depth: Type.Optional(Type.Number({ description: "tree traversal depth; bounded by a hard ceiling" })),
|
|
@@ -348,18 +351,26 @@ export default async function (pi: ExtensionAPI) {
|
|
|
348
351
|
}),
|
|
349
352
|
renderCall(args, theme) { return renderPapyrusToolCall("Artifact graph", args, theme); },
|
|
350
353
|
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
351
|
-
async execute(_id,
|
|
354
|
+
async execute(_id, rawParams, _signal, _onUpdate, _ctx) {
|
|
352
355
|
try {
|
|
356
|
+
const params: Record<string, unknown> = { ...rawParams };
|
|
357
|
+
if (params.action === "link" || params.action === "unlink") {
|
|
358
|
+
// Kind-agnostic: either end of an edge can be a task, doc, rule, skill, or playbook.
|
|
359
|
+
await resolveNameFields(params, [
|
|
360
|
+
{ nameKey: "from_name", idKey: "from", listOperation: "artifact.query", baseRequest: {} },
|
|
361
|
+
{ nameKey: "to_name", idKey: "to", listOperation: "artifact.query", baseRequest: {} },
|
|
362
|
+
]);
|
|
363
|
+
}
|
|
353
364
|
if (params.action === "link") {
|
|
354
|
-
await callService("graph.link", { from: params.from
|
|
355
|
-
const names = await artifactNamesById([params.from
|
|
356
|
-
const output = `Linked "${names.get(params.from
|
|
365
|
+
await callService("graph.link", { from: params.from as string, relation: params.relation as string, to: params.to as string });
|
|
366
|
+
const names = await artifactNamesById([params.from as string, params.to as string]);
|
|
367
|
+
const output = `Linked "${names.get(params.from as string) ?? "unknown artifact"}" --${params.relation}--> "${names.get(params.to as string) ?? "unknown artifact"}"`;
|
|
357
368
|
return text(output, createPreviewDetails("graph.link", "Artifact relationship", output));
|
|
358
369
|
}
|
|
359
370
|
if (params.action === "unlink") {
|
|
360
|
-
const result = await callService<Record<string, unknown>, { removed: boolean }>("graph.unlink", { from: params.from
|
|
361
|
-
const names = await artifactNamesById([params.from
|
|
362
|
-
const relationship = `"${names.get(params.from
|
|
371
|
+
const result = await callService<Record<string, unknown>, { removed: boolean }>("graph.unlink", { from: params.from as string, relation: params.relation as string, to: params.to as string });
|
|
372
|
+
const names = await artifactNamesById([params.from as string, params.to as string]);
|
|
373
|
+
const relationship = `"${names.get(params.from as string) ?? "unknown artifact"}" --${params.relation}--> "${names.get(params.to as string) ?? "unknown artifact"}"`;
|
|
363
374
|
const output = result.removed ? `Unlinked ${relationship}` : `No such relationship: ${relationship}`;
|
|
364
375
|
return text(output, createPreviewDetails("graph.unlink", "Artifact relationship", output));
|
|
365
376
|
}
|
package/package.json
CHANGED