@danypops/papyrus 0.40.0 → 0.42.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.
@@ -1,19 +1,22 @@
1
1
  /**
2
2
  * playbook-definition.ts — compiles a Playbook's own steps/trigger/tools/arguments, plus its
3
- * `contains` (nested) and `depends_on` (prerequisite) composition tree, into an in-memory
4
- * SkillDefinition. This is the "recycle Papyrus Skills -> Playbooks" half of the redesign:
5
- * rather than a second graph-materialization engine, a Playbook becomes prose that compiles
6
- * down to the exact blueprint shape workflow Skills already use, then hands off to
7
- * workflow-execution.ts's shared materializeWorkflowDefinition for the actual Task creation.
3
+ * `contains` (nested) and `depends_on` (prerequisite) whole-artifact composition tree, into
4
+ * an in-memory BlueprintDefinition. Rather than a second graph-materialization engine, a
5
+ * Playbook becomes a definition that compiles down to the exact Blueprint shape a
6
+ * workflow-definition target already uses, then hands off to workflow-execution.ts's shared
7
+ * materializeWorkflowDefinition for the actual artifact creation.
8
8
  *
9
9
  * One task blueprint per playbook-node root (a container, never itself gated) plus one per
10
10
  * step (chained by sequential dependsOn); `contains`-linked playbooks nest their own root
11
11
  * under the parent root and continue the parent's own step chain ("run as part of this one",
12
12
  * after the parent's own steps); `depends_on`-linked playbooks compile as independent
13
- * subtrees whose tails gate this node's first step ("complete this FIRST"). No SkillCallBlueprint
14
- * indirection is used -- everything is inlined into one flat definition, since a Playbook's
15
- * composition tree is fully known and owned at compile time, unlike a workflow Skill's nested
16
- * pipeline step (which references another SKILL by id, resolved and executed independently).
13
+ * subtrees whose tails gate this node's first step ("complete this FIRST"). That whole-artifact
14
+ * composition tree is fully known and owned at compile time and is always inlined directly
15
+ * (no CallBlueprint indirection). A step-level `call` (one step within a single playbook
16
+ * node, not the composition tree above) is the one place this compiler DOES emit a
17
+ * CallBlueprint -- a finer-grained, in-blueprint nested-run reference resolved and executed
18
+ * independently by workflow-execution.ts, exactly like a workflow-definition target's own
19
+ * nested pipeline step, since its target's own definition is not knowable at this compile time.
17
20
  */
18
21
  import {
19
22
  PLAYBOOK_INVOCATION_MAX_CALL_DEPTH,
@@ -21,9 +24,9 @@ import {
21
24
  PLAYBOOK_INVOCATION_MAX_LINKED_ARTIFACTS,
22
25
  } from "./constants.ts";
23
26
  import type { Artifact } from "./domain/artifact.ts";
24
- import type { SkillDefinition, SkillInputDefinition, SkillTaskBlueprint } from "./domain/skill-definition.ts";
25
- import { validateSkillDefinition } from "./domain/skill-definition.ts";
26
- import type { PlaybookArgument } from "./domain-services.ts";
27
+ import type { CallBlueprint, BlueprintDefinition, DocBlueprint, BlueprintInputDefinition, RuleBlueprint, TaskBlueprint } from "./domain/blueprint-definition.ts";
28
+ import { validateBlueprintDefinition } from "./domain/blueprint-definition.ts";
29
+ import type { PlaybookArgument, PlaybookStep } from "./domain-services.ts";
27
30
  import type { ArtifactStore } from "./ports/artifact-store.ts";
28
31
 
29
32
  /** A non-composing edge touching a playbook node, to be mirrored onto that node's generated root task once real task ids exist -- e.g. a Rule `gates` this playbook, or this playbook `references`/`documents` a Doc. Direction is preserved exactly: `from`/`to` name whichever side is NOT the playbook, and `ownerIsFrom` says which side the playbook (now the generated root task) occupies. */
@@ -36,7 +39,7 @@ export interface PlaybookExternalLink {
36
39
  }
37
40
 
38
41
  export interface CompiledPlaybook {
39
- definition: SkillDefinition;
42
+ definition: BlueprintDefinition;
40
43
  /** The very first real leaf task in the whole tree's reading order -- what a caller should focus once materialized. */
41
44
  entryRef: string;
42
45
  externalLinks: PlaybookExternalLink[];
@@ -56,8 +59,8 @@ function requirePlaybook(artifacts: ArtifactStore, id: string): Artifact {
56
59
  return playbook;
57
60
  }
58
61
 
59
- function stepsOf(playbook: Artifact): string[] {
60
- return Array.isArray(playbook.extra["steps"]) ? playbook.extra["steps"].filter((step): step is string => typeof step === "string") : [];
62
+ function stepsOf(playbook: Artifact): PlaybookStep[] {
63
+ return Array.isArray(playbook.extra["steps"]) ? (playbook.extra["steps"] as PlaybookStep[]) : [];
61
64
  }
62
65
 
63
66
  function toolsOf(playbook: Artifact): string[] {
@@ -87,8 +90,11 @@ function stepTitle(step: string): string {
87
90
  }
88
91
 
89
92
  interface CompileContext {
90
- tasks: SkillTaskBlueprint[];
91
- inputs: Record<string, SkillInputDefinition>;
93
+ docs: DocBlueprint[];
94
+ rules: RuleBlueprint[];
95
+ tasks: TaskBlueprint[];
96
+ skills: CallBlueprint[];
97
+ inputs: Record<string, BlueprintInputDefinition>;
92
98
  externalLinks: PlaybookExternalLink[];
93
99
  refCounter: { n: number };
94
100
  }
@@ -101,9 +107,18 @@ interface CompileNodeResult {
101
107
  tailRef: string;
102
108
  }
103
109
 
104
- function mergeArgument(inputs: Record<string, SkillInputDefinition>, argument: PlaybookArgument): void {
110
+ /** A composition tree can declare the same argument name from more than one node (e.g. two prerequisite playbooks both need a `target`); required OR-accumulates the same way it always did, but the type must agree everywhere -- silently picking one node's type over another's would compile a definition whose placeholder substitution disagrees with what one of the two authors actually declared. */
111
+ function mergeArgument(inputs: Record<string, BlueprintInputDefinition>, argument: PlaybookArgument): void {
105
112
  const existing = inputs[argument.name];
106
- inputs[argument.name] = { type: "string", required: (existing?.required ?? false) || argument.required };
113
+ if (existing && existing.type !== argument.type) {
114
+ throw new Error(`playbook composition declares conflicting types for argument "${argument.name}" (${existing.type} vs ${argument.type})`);
115
+ }
116
+ inputs[argument.name] = {
117
+ type: argument.type,
118
+ required: (existing?.required ?? false) || argument.required,
119
+ ...(argument.enum ? { enum: argument.enum } : {}),
120
+ ...(argument.default !== undefined ? { default: argument.default } : {}),
121
+ };
107
122
  }
108
123
 
109
124
  function compileNode(
@@ -123,7 +138,7 @@ function compileNode(
123
138
  for (const argument of argumentsOf(playbook)) mergeArgument(ctx.inputs, argument);
124
139
 
125
140
  const rootRef = `pb${ctx.refCounter.n++}`;
126
- const rootBlueprint: SkillTaskBlueprint = { ref: rootRef, title: playbook.title, body: rootTaskBody(playbook), ...(parentRef ? { parent: parentRef } : {}) };
141
+ const rootBlueprint: TaskBlueprint = { ref: rootRef, title: playbook.title, body: rootTaskBody(playbook), ...(parentRef ? { parent: parentRef } : {}) };
127
142
  ctx.tasks.push(rootBlueprint);
128
143
  if (ctx.tasks.length > PLAYBOOK_INVOCATION_MAX_CREATED_TASKS) throw new Error(`playbook invocation exceeds ${PLAYBOOK_INVOCATION_MAX_CREATED_TASKS} tasks`);
129
144
 
@@ -148,15 +163,46 @@ function compileNode(
148
163
  if (headRef === undefined) headRef = result.headRef;
149
164
  }
150
165
 
166
+ // Doc/rule steps are not gated tasks (DocBlueprint/RuleBlueprint have no dependsOn/parent of
167
+ // their own -- workflow-execution.ts always creates them unconditionally alongside the run)
168
+ // -- they do not touch cursorPrecedingRefs/headRef/tailRef at all. A task or call step DOES
169
+ // occupy a position in the sequential chain, exactly as a plain-string step always did.
151
170
  let cursorPrecedingRefs = [...incomingPrecedingRefs, ...prerequisiteTailRefs];
152
171
  let tailRef = rootRef;
153
172
  for (const [index, step] of stepsOf(playbook).entries()) {
154
- const stepRef = `${rootRef}-s${index}`;
155
- ctx.tasks.push({ ref: stepRef, title: stepTitle(step), body: step, parent: rootRef, dependsOn: cursorPrecedingRefs });
156
- if (ctx.tasks.length > PLAYBOOK_INVOCATION_MAX_CREATED_TASKS) throw new Error(`playbook invocation exceeds ${PLAYBOOK_INVOCATION_MAX_CREATED_TASKS} tasks`);
157
- if (headRef === undefined) headRef = stepRef;
158
- cursorPrecedingRefs = [stepRef];
159
- tailRef = stepRef;
173
+ if (typeof step === "string" || step.kind === "task") {
174
+ const body = typeof step === "string" ? step : step.body;
175
+ const title = typeof step === "string" ? stepTitle(step) : (step.title ?? stepTitle(body));
176
+ const stepRef = `${rootRef}-s${index}`;
177
+ ctx.tasks.push({ ref: stepRef, title, body, parent: rootRef, dependsOn: cursorPrecedingRefs });
178
+ if (ctx.tasks.length > PLAYBOOK_INVOCATION_MAX_CREATED_TASKS) throw new Error(`playbook invocation exceeds ${PLAYBOOK_INVOCATION_MAX_CREATED_TASKS} tasks`);
179
+ if (headRef === undefined) headRef = stepRef;
180
+ cursorPrecedingRefs = [stepRef];
181
+ tailRef = stepRef;
182
+ } else if (step.kind === "doc") {
183
+ const stepRef = `${rootRef}-d${index}`;
184
+ ctx.docs.push({ ref: stepRef, title: step.title, ...(step.body ? { body: step.body } : {}), ...(step.subtype ? { subtype: step.subtype } : {}), ...(step.labels ? { labels: step.labels } : {}) });
185
+ } else if (step.kind === "rule") {
186
+ const stepRef = `${rootRef}-r${index}`;
187
+ ctx.rules.push({
188
+ ref: stepRef,
189
+ title: step.title,
190
+ ...(step.body ? { body: step.body } : {}),
191
+ ...(step.condition ? { condition: step.condition } : {}),
192
+ ...(step.action ? { action: step.action } : {}),
193
+ ...(step.severity ? { severity: step.severity } : {}),
194
+ ...(step.labels ? { labels: step.labels } : {}),
195
+ });
196
+ } else {
197
+ // kind === "call": nests another Playbook's (or a workflow-definition target's) run as
198
+ // a pipeline step -- shares the same dependsOn chain as a task step, resolved
199
+ // polymorphically at execution time by workflow-execution.ts based on the target's kind.
200
+ const stepRef = `${rootRef}-c${index}`;
201
+ ctx.skills.push({ ref: stepRef, title: step.title, targetId: step.playbookId, ...(step.arguments ? { arguments: step.arguments } : {}), parent: rootRef, dependsOn: cursorPrecedingRefs });
202
+ if (headRef === undefined) headRef = stepRef;
203
+ cursorPrecedingRefs = [stepRef];
204
+ tailRef = stepRef;
205
+ }
160
206
  }
161
207
 
162
208
  for (const nestedId of nestedIds) {
@@ -172,12 +218,12 @@ function compileNode(
172
218
 
173
219
  /** Pure and read-only: creates no artifacts. Cycle/depth-bounded exactly like playbookInvocation's own traversal, but a composition cycle here is a hard error (real Tasks would be created, unlike a text render degrading to a marker). */
174
220
  export function compilePlaybookDefinition(artifacts: ArtifactStore, playbookId: string): CompiledPlaybook {
175
- const ctx: CompileContext = { tasks: [], inputs: {}, externalLinks: [], refCounter: { n: 0 } };
221
+ const ctx: CompileContext = { docs: [], rules: [], tasks: [], skills: [], inputs: {}, externalLinks: [], refCounter: { n: 0 } };
176
222
  const { headRef } = compileNode(artifacts, playbookId, ctx, new Set(), 0, undefined, []);
177
- const definition = validateSkillDefinition({
223
+ const definition = validateBlueprintDefinition({
178
224
  version: 1,
179
225
  inputs: ctx.inputs,
180
- blueprints: { docs: [], rules: [], tasks: ctx.tasks, skills: [] },
226
+ blueprints: { docs: ctx.docs, rules: ctx.rules, tasks: ctx.tasks, skills: ctx.skills },
181
227
  links: [],
182
228
  });
183
229
  return { definition, entryRef: headRef, externalLinks: ctx.externalLinks };
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * playbook-execution.ts — playbooks.invoke's real implementation: compile the Playbook's
3
- * composition tree (playbook-definition.ts) into a SkillDefinition, materialize it through
3
+ * composition tree (playbook-definition.ts) into a BlueprintDefinition, materialize it through
4
4
  * workflow-execution.ts's shared engine, mirror any pre-existing Rule/Doc links onto the
5
5
  * generated root task, and report which task to focus. No text is rendered here -- every
6
6
  * step is its own Task, and only the currently-focused one is ever surfaced to an agent
7
7
  * (via the existing Task Focus system-prompt pointer), which is what actually avoids the
8
8
  * old text-dump problem: one page at a time, not the whole book at once.
9
9
  */
10
- import type { SkillArgumentValue } from "./domain/skill-definition.ts";
10
+ import type { BlueprintArgumentValue } from "./domain/blueprint-definition.ts";
11
11
  import { compilePlaybookDefinition } from "./playbook-definition.ts";
12
12
  import type { ArtifactStore } from "./ports/artifact-store.ts";
13
13
  import { requireAtomicArtifactStore } from "./ports/atomic-artifact-store.ts";
14
- import { materializeWorkflowDefinition, type WorkflowRunHistory } from "./workflow-execution.ts";
14
+ import { applyPlaybookExternalLinks, materializeWorkflowDefinition, resolveRefToTaskId, type WorkflowRunHistory } from "./workflow-execution.ts";
15
15
  import type { TaskExecutionPlan } from "./task-execution.ts";
16
16
 
17
17
  export interface InvokePlaybookInput {
@@ -22,7 +22,7 @@ export interface InvokePlaybookInput {
22
22
  export interface PlaybookInvocationResult {
23
23
  playbookId: string;
24
24
  runId: string;
25
- arguments: Record<string, SkillArgumentValue>;
25
+ arguments: Record<string, BlueprintArgumentValue>;
26
26
  created: { docs: string[]; rules: string[]; tasks: string[] };
27
27
  rootTaskIds: string[];
28
28
  /** The one task to focus -- the first real leaf in the whole composition tree's reading order (deepest prerequisite's own first step, or this playbook's own first step, or its first nested child's, or the container task itself when there is nothing else). */
@@ -36,24 +36,12 @@ export interface PlaybookMissingArguments {
36
36
  missingArguments: string[];
37
37
  }
38
38
 
39
- function missingRequiredInputs(inputs: Record<string, { required?: boolean; default?: SkillArgumentValue }>, provided: Record<string, unknown>): string[] {
39
+ function missingRequiredInputs(inputs: Record<string, { required?: boolean; default?: BlueprintArgumentValue }>, provided: Record<string, unknown>): string[] {
40
40
  return Object.entries(inputs)
41
41
  .filter(([name, input]) => input.required && provided[name] === undefined && input.default === undefined)
42
42
  .map(([name]) => name);
43
43
  }
44
44
 
45
- /** Reads each newly-created task's own playbookRun.ref tag back off the store -- avoids threading an extra ref-to-id map out of materializeWorkflowDefinition's existing, already-stable return shape. */
46
- function resolveRefToTaskId(artifacts: ArtifactStore, taskIds: string[]): Map<string, string> {
47
- const map = new Map<string, string>();
48
- for (const taskId of taskIds) {
49
- const lineage = artifacts.get(taskId)?.extra["playbookRun"];
50
- if (typeof lineage !== "object" || lineage === null || Array.isArray(lineage)) continue;
51
- const ref = (lineage as Record<string, unknown>)["ref"];
52
- if (typeof ref === "string") map.set(ref, taskId);
53
- }
54
- return map;
55
- }
56
-
57
45
  export function invokePlaybook(
58
46
  artifacts: ArtifactStore,
59
47
  playbookId: string,
@@ -75,13 +63,8 @@ export function invokePlaybook(
75
63
  new Set(),
76
64
  0,
77
65
  );
78
- const refToTaskId = resolveRefToTaskId(artifacts, result.created.tasks);
79
- for (const link of compiled.externalLinks) {
80
- const taskId = refToTaskId.get(link.rootRef);
81
- if (!taskId) continue; // defensive -- every rootRef this compiler emits is always materialized
82
- if (link.ownerIsFrom) artifacts.link({ from: taskId, relation: link.relation, to: link.otherArtifactId });
83
- else artifacts.link({ from: link.otherArtifactId, relation: link.relation, to: taskId });
84
- }
66
+ const refToTaskId = resolveRefToTaskId(artifacts, result.created.tasks, "playbookRun");
67
+ applyPlaybookExternalLinks(artifacts, compiled.externalLinks, refToTaskId);
85
68
  return {
86
69
  playbookId,
87
70
  runId: result.runId,
@@ -1,7 +1,7 @@
1
1
  import type { TaskScopeSource } from "../domain/task-scope.ts";
2
2
 
3
3
  /**
4
- * Project scoping for Docs/Rules/Skills, mirroring TaskScopeStore's shape (task_scopes) but
4
+ * Project scoping for Docs/Rules/Playbooks, mirroring TaskScopeStore's shape (task_scopes) but
5
5
  * kept as its own table/port rather than folding non-Task kinds into Task-named
6
6
  * infrastructure. TaskScopeSource ("cwd" | "explicit" | "unscoped") is already kind-agnostic
7
7
  * and reused as-is -- no reason to redefine the same three values under a new name.
package/src/service.ts CHANGED
@@ -39,7 +39,6 @@ import { graphProjectionOperations, GRAPH_PROJECTION_OPERATION_NAMES } from "./m
39
39
  import { logsOperations, LOGS_OPERATION_NAMES } from "./modules/logs.ts";
40
40
  import { notesOperations, NOTES_OPERATION_NAMES } from "./modules/notes.ts";
41
41
  import { rulesOperations, RULES_OPERATION_NAMES } from "./modules/rules.ts";
42
- import { instantiateSkillOrTemplate, skillsOperations, SKILLS_OPERATION_NAMES } from "./modules/skills.ts";
43
42
  import { playbooksOperations, PLAYBOOKS_OPERATION_NAMES } from "./modules/playbooks.ts";
44
43
  import { sessionIdentityOperations, SESSION_IDENTITY_OPERATION_NAMES } from "./modules/session-identity.ts";
45
44
  import { discussOperations, DISCUSS_OPERATION_NAMES } from "./modules/discuss.ts";
@@ -51,9 +50,8 @@ import { SQLiteDiscussionRoundStore } from "./adapters/sqlite-discussion-round-s
51
50
  * Operations with no registered module: the generic, cross-cutting kernel surface
52
51
  * (artifact create/query/show, graph link/unlink/tree/status/history, gates run --
53
52
  * no domain owns creation/linking/traversal for every kind, the same way system.migrate
54
- * has no owning module) and two permanent composition-root exceptions (rules.injectable
55
- * needs tasks.active(); skills.instantiate branches into tasks.create()) -- see
56
- * src/modules/rules.ts and src/modules/skills.ts's module comments. Discourse's own
53
+ * has no owning module) and one permanent composition-root exception (rules.injectable
54
+ * needs tasks.active()) -- see src/modules/rules.ts's own module comment. Discourse's own
57
55
  * Papyrus-embedded storage (discourse.store) was removed entirely -- zero real callers
58
56
  * were ever confirmed against it; Discourse's real home is the standalone
59
57
  * @danypops/discourse package plus host adapters.
@@ -62,7 +60,7 @@ const COMPOSITION_ROOT_OPERATION_NAMES = [
62
60
  "system.migrate", "artifact.create", "artifact.query", "artifact.show",
63
61
  "artifact.remove", "artifact.remove_subtree", "artifact.restore", "artifact.trash_status", "artifact.trash_list",
64
62
  "graph.link", "graph.unlink", "graph.tree", "graph.status", "graph.history", "gates.run",
65
- "rules.injectable", "skills.instantiate",
63
+ "rules.injectable",
66
64
  ] as const;
67
65
 
68
66
  /**
@@ -80,7 +78,6 @@ export const EXPECTED_OPERATION_NAMES = [
80
78
  ...DOCS_OPERATION_NAMES,
81
79
  ...NOTES_OPERATION_NAMES,
82
80
  ...RULES_OPERATION_NAMES,
83
- ...SKILLS_OPERATION_NAMES,
84
81
  ...PLAYBOOKS_OPERATION_NAMES,
85
82
  ...GRAPH_PROJECTION_OPERATION_NAMES,
86
83
  ...LOGS_OPERATION_NAMES,
@@ -170,11 +167,11 @@ const tasksAuthorityClaim: AuthorityClaim = {
170
167
 
171
168
  /**
172
169
  * The same status-bypass protection Tasks and Notes already have, extended to every other kind
173
- * with its own validated transition set (Doc's draft/active/archived, Rule/Skill/Playbook's
170
+ * with its own validated transition set (Doc's draft/active/archived, Rule/Playbook's
174
171
  * active/deprecated) -- graph.status previously let a caller jump straight to any status string,
175
172
  * skipping e.g. Doc's draft-must-go-through-active-before-archived rule entirely.
176
173
  */
177
- function lifecycleAuthorityClaim(owner: "docs" | "rules" | "skills" | "playbooks", kind: string): AuthorityClaim {
174
+ function lifecycleAuthorityClaim(owner: "docs" | "rules" | "playbooks", kind: string): AuthorityClaim {
178
175
  return {
179
176
  owner,
180
177
  matchesArtifact: (candidateKind, subtype) => candidateKind === kind && !(kind === "doc" && subtype === NOTE_SUBTYPE),
@@ -190,7 +187,6 @@ export function createAuthorityRegistry(): AuthorityRegistry {
190
187
  tasksAuthorityClaim,
191
188
  lifecycleAuthorityClaim("docs", "doc"),
192
189
  lifecycleAuthorityClaim("rules", "rule"),
193
- lifecycleAuthorityClaim("skills", "skill"),
194
190
  lifecycleAuthorityClaim("playbooks", "playbook"),
195
191
  ]);
196
192
  return authority;
@@ -408,16 +404,6 @@ function handlers(
408
404
  "rules.gate": forwardToModule("rules.gate"),
409
405
  "rules.assign_project": forwardToModule("rules.assign_project"),
410
406
  "rules.update": forwardToModule("rules.update"),
411
- "skills.create": forwardToModule("skills.create"),
412
- "skills.create_template": forwardToModule("skills.create_template"),
413
- "skills.list": forwardToModule("skills.list"),
414
- "skills.show": forwardToModule("skills.show"),
415
- "skills.invoke": forwardToModule("skills.invoke"),
416
- "skills.run": forwardToModule("skills.run"),
417
- "skills.enable": forwardToModule("skills.enable"),
418
- "skills.disable": forwardToModule("skills.disable"),
419
- "skills.assign_project": forwardToModule("skills.assign_project"),
420
- "skills.update": forwardToModule("skills.update"),
421
407
  "playbooks.create": forwardToModule("playbooks.create"),
422
408
  "playbooks.list": forwardToModule("playbooks.list"),
423
409
  "playbooks.show": forwardToModule("playbooks.show"),
@@ -431,7 +417,6 @@ function handlers(
431
417
  "playbooks.uncontain": forwardToModule("playbooks.uncontain"),
432
418
  "playbooks.depend": forwardToModule("playbooks.depend"),
433
419
  "playbooks.undepend": forwardToModule("playbooks.undepend"),
434
- "skills.instantiate": (input) => instantiateSkillOrTemplate({ artifacts, tasks, authority }, input, eventContextFor(input, "template-instantiation")),
435
420
  "graph_projection.apply": forwardToModule("graph_projection.apply"),
436
421
  "graph_projection.checkpoint": forwardToModule("graph_projection.checkpoint"),
437
422
  "logs.append": forwardToModule("logs.append"),
@@ -477,7 +462,6 @@ export function createPapyrusService(path: string): PapyrusService {
477
462
  moduleRegistry.registerAll(tasksOperations(tasks, artifacts, sessionIdentity));
478
463
  moduleRegistry.registerAll(docsOperations(artifacts, artifactScopes, authority));
479
464
  moduleRegistry.registerAll(rulesOperations(artifacts, artifactScopes));
480
- moduleRegistry.registerAll(skillsOperations({ artifacts, events, scopes, artifactScopes, authority }));
481
465
  moduleRegistry.registerAll(playbooksOperations({ artifacts, events, scopes, artifactScopes, tasks, sessionIdentity }));
482
466
  moduleRegistry.registerAll(graphProjectionOperations(artifacts, projections, authority));
483
467
  const registry = handlers(artifacts, gates, tasks, notes, events, scopes, () => migrateDb(db), moduleRegistry, authority);
@@ -462,7 +462,7 @@ export class Tasks {
462
462
 
463
463
  /**
464
464
  * Cancels a task and every task in its containment subtree (`contains` edges, transitively) --
465
- * a whole materialized playbook/skill run can be torn down in one call instead of enumerating
465
+ * a whole materialized playbook run can be torn down in one call instead of enumerating
466
466
  * every task id by hand. A task already in a terminal state (done/canceled) is skipped, not
467
467
  * treated as an error, matching how a mixed-status subtree is the normal case (some steps
468
468
  * genuinely finished before the rest needed to be abandoned). Does not follow `depends_on` --
@@ -55,7 +55,7 @@ export function registerArtifactTrashOperations(registry: VehicleRegistry, artif
55
55
 
56
56
  define(
57
57
  "show",
58
- "Shows any artifact (doc, task, rule, skill, playbook) by id, regardless of kind.",
58
+ "Shows any artifact (doc, task, rule, playbook) by id, regardless of kind.",
59
59
  "read",
60
60
  { id: stringProp, tree: { type: "boolean" } as unknown as { type: string }, depth: numberProp, max_nodes: numberProp },
61
61
  ["id"],
@@ -98,12 +98,10 @@ export interface WorkflowRunNarrativeInput {
98
98
  }
99
99
 
100
100
  /**
101
- * Shared between skills.run and playbooks.invoke's Vehicle operations -- both produce the
102
- * same shaped narrative (ready roots, context docs, scoped rules, an execution tree), only
103
- * the headline and whether an "entry task focused" line is present differ. Builds the model-
104
- * facing `content` text directly, so the model reads a summary instead of the raw execution
105
- * DAG -- the same shape pi-papyrus's own hand-rolled skills/playbooks tools built client-side,
106
- * now built once here where the run result is actually produced.
101
+ * Builds the model-facing `content` text for a workflow run result (ready roots, context docs,
102
+ * scoped rules, an execution tree) directly, so the model reads a summary instead of the raw
103
+ * execution DAG -- the same shape pi-papyrus's own hand-rolled playbooks tool built
104
+ * client-side, now built once here where the run result is actually produced.
107
105
  */
108
106
  export function buildWorkflowRunContent(artifacts: ArtifactStore, headline: string, input: WorkflowRunNarrativeInput, extraLines: readonly string[] = []): VehicleContentBlock {
109
107
  const nodeById = new Map(input.execution.nodes.map((node) => [node.id, node]));
@@ -25,7 +25,7 @@ function resolveDocId(artifacts: ArtifactStore, scopes: ArtifactScopeStore, proj
25
25
  );
26
26
  }
27
27
 
28
- /** Cross-kind resolution for a link target -- can be a doc, task, rule, or skill. Unscoped, matching the exact behavior of the artifact.query-backed resolution it replaces. */
28
+ /** Cross-kind resolution for a link target -- can be a doc, task, rule, or playbook. Unscoped, matching the exact behavior of the artifact.query-backed resolution it replaces. */
29
29
  function resolveTargetId(artifacts: ArtifactStore, id: unknown, name: unknown): string {
30
30
  if (typeof id === "string" && id.length > 0) return id;
31
31
  if (typeof name !== "string" || name.length === 0) throw new Error("target_id or target_name is required");
@@ -114,7 +114,7 @@ export function registerDocsVehicleOperations(registry: VehicleRegistry, artifac
114
114
 
115
115
  define(
116
116
  "link",
117
- "Links a Doc to another artifact via a typed relation. Prefer target_name over target_id -- resolved server-side, searching every kind since a link target can be a doc, task, rule, or skill.",
117
+ "Links a Doc to another artifact via a typed relation. Prefer target_name over target_id -- resolved server-side, searching every kind since a link target can be a doc, task, rule, or playbook.",
118
118
  "local-write",
119
119
  { id: stringProp, name: stringProp, relation: { type: "string", enum: ["references", "documents", "supersedes", "relates_to", "contains", "part_of"] }, target_id: stringProp, target_name: stringProp, project_root: stringProp },
120
120
  ["relation"],
@@ -25,7 +25,7 @@ function resolveNoteId(notes: Notes, projectRoot: string, id: unknown, name: unk
25
25
  return resolveArtifactIdWidened(name, () => notes.list({ projectRoot, text: name }));
26
26
  }
27
27
 
28
- /** Cross-kind equivalent for a promotion target -- a target can be a task, doc, rule, or skill, not just a note. Unscoped by project, matching the exact behavior of the artifact.query-backed resolution it replaces. */
28
+ /** Cross-kind equivalent for a promotion target -- a target can be a task, doc, rule, or playbook, not just a note. Unscoped by project, matching the exact behavior of the artifact.query-backed resolution it replaces. */
29
29
  function resolveArtifactId(artifacts: ArtifactStore, id: unknown, name: unknown): string {
30
30
  if (typeof id === "string" && id.length > 0) return id;
31
31
  if (typeof name !== "string" || name.length === 0) throw new Error("target_id or target_name is required");
@@ -113,7 +113,7 @@ export function registerNotesVehicleOperations(registry: VehicleRegistry, notes:
113
113
 
114
114
  define(
115
115
  "promote",
116
- "Links a note to the Task, Doc, Rule, or Skill it was promoted into, then archives it.",
116
+ "Links a note to the Task, Doc, Rule, or Playbook it was promoted into, then archives it.",
117
117
  "local-write",
118
118
  {
119
119
  id: stringProp,
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Composition root for every domain projected onto Vehicle -- one VehicleRegistry,
3
3
  * one HTTP mount (see service.ts's createApp). Operation names are already globally
4
- * unique via their own dotted prefix (notes.*, rules.*, docs.*, skills.*, playbooks.*,
5
- * artifact.*), so merging costs nothing and avoids a separate registry/mount/client
6
- * per domain.
4
+ * unique via their own dotted prefix (notes.*, rules.*, docs.*, playbooks.*, artifact.*),
5
+ * so merging costs nothing and avoids a separate registry/mount/client per domain.
7
6
  *
8
- * discuss and tasks still register via pi-papyrus's own pi.registerTool() in
9
- * domain-tools.ts, not here -- see the papyrus Vehicle migration task for why.
7
+ * discuss still registers via pi-papyrus's own pi.registerTool() in domain-tools.ts,
8
+ * not here -- see the papyrus Vehicle migration task for why.
10
9
  */
11
10
  import { VehicleRegistry } from "@danypops/vehicle-server";
12
11
  import type { AuthorityRegistry } from "../authority-registry.ts";
@@ -23,7 +22,7 @@ import { registerDocsVehicleOperations } from "./docs-vehicle.ts";
23
22
  import { registerNotesVehicleOperations } from "./notes-vehicle.ts";
24
23
  import { registerPlaybooksVehicleOperations } from "./playbooks-vehicle.ts";
25
24
  import { registerRulesVehicleOperations } from "./rules-vehicle.ts";
26
- import { registerSkillsVehicleOperations } from "./skills-vehicle.ts";
25
+ import { registerTasksVehicleOperations } from "./tasks-vehicle.ts";
27
26
 
28
27
  export interface PapyrusVehicleDeps {
29
28
  artifacts: ArtifactStore & ArtifactTrashStore;
@@ -41,8 +40,8 @@ export function createPapyrusVehicleRegistry(deps: PapyrusVehicleDeps): VehicleR
41
40
  registerNotesVehicleOperations(registry, deps.notes, deps.artifacts);
42
41
  registerRulesVehicleOperations(registry, deps.artifacts, deps.scopes);
43
42
  registerDocsVehicleOperations(registry, deps.artifacts, deps.scopes, deps.authority);
44
- registerSkillsVehicleOperations(registry, { artifacts: deps.artifacts, events: deps.events, scopes: deps.taskScopes, artifactScopes: deps.scopes, authority: deps.authority, tasks: deps.tasks });
45
43
  registerPlaybooksVehicleOperations(registry, { artifacts: deps.artifacts, events: deps.events, scopes: deps.taskScopes, artifactScopes: deps.scopes, tasks: deps.tasks, sessionIdentity: deps.sessionIdentity });
44
+ registerTasksVehicleOperations(registry, { tasks: deps.tasks, artifacts: deps.artifacts, sessionIdentity: deps.sessionIdentity });
46
45
  registerArtifactTrashOperations(registry, deps.artifacts);
47
46
  return registry;
48
47
  }
@@ -80,7 +80,7 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
80
80
 
81
81
  define(
82
82
  "create",
83
- "Creates a Playbook -- prose: a trigger and an ordered list of steps. `arguments` declares named inputs: [{name, description?, required?}] (required defaults true), referenced in step text as {{name}}. project_root is optional (omitted = unscoped).",
83
+ "Creates a Playbook -- a trigger and an ordered list of steps. Each step is either a plain prose string (a task), or a structured object: {kind:'doc',title,body?,subtype?,labels?} creates a Doc, {kind:'rule',title,body?,condition?,action?,severity?,labels?} creates a Rule, {kind:'call',title,playbookId,arguments?} nests another Playbook's own run as a pipeline step gated in the same sequence, {kind:'task',title?,body} is an explicit task step. `arguments` declares named inputs: [{name, description?, required?, type?('string'|'number'|'boolean', default 'string'), enum?, default?}] (required defaults true), referenced in step text/call arguments as {{name}}. project_root is optional (omitted = unscoped).",
84
84
  "local-write",
85
85
  { title: stringProp, body: stringProp, trigger: stringProp, steps: { type: "array" }, tools: { type: "array" }, arguments: { type: "array" }, labels: { type: "array" }, extra: { type: "object" }, project_root: stringProp, actor: stringProp, source: stringProp, session_id: stringProp },
86
86
  ["title"],