@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.
- package/README.md +9 -12
- package/package.json +1 -1
- package/src/artifact-relationship-view.ts +1 -1
- package/src/cli.ts +12 -178
- package/src/constants.ts +19 -52
- package/src/db.ts +34 -7
- package/src/domain/artifact-event.ts +1 -1
- package/src/domain/blueprint-definition.ts +273 -0
- package/src/domain-services.ts +147 -204
- package/src/modules/logs.ts +1 -1
- package/src/modules/playbooks.ts +9 -7
- package/src/ops.ts +3 -1
- package/src/playbook-definition.ts +75 -29
- package/src/playbook-execution.ts +7 -24
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/service.ts +5 -21
- package/src/task-service.ts +1 -1
- package/src/vehicle/artifact-trash-vehicle.ts +1 -1
- package/src/vehicle/artifact-vehicle-shared.ts +4 -6
- package/src/vehicle/docs-vehicle.ts +2 -2
- package/src/vehicle/notes-vehicle.ts +2 -2
- package/src/vehicle/papyrus-vehicle.ts +6 -7
- package/src/vehicle/playbooks-vehicle.ts +1 -1
- package/src/vehicle/tasks-vehicle.ts +407 -0
- package/src/workflow-execution.ts +139 -69
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- package/src/vehicle/skills-vehicle.ts +0 -194
|
@@ -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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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").
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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 {
|
|
25
|
-
import {
|
|
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:
|
|
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):
|
|
60
|
-
return Array.isArray(playbook.extra["steps"]) ? playbook.extra["steps"]
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 =
|
|
223
|
+
const definition = validateBlueprintDefinition({
|
|
178
224
|
version: 1,
|
|
179
225
|
inputs: ctx.inputs,
|
|
180
|
-
blueprints: { docs:
|
|
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
|
|
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 {
|
|
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,
|
|
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?:
|
|
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
|
-
|
|
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/
|
|
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
|
|
55
|
-
* needs tasks.active()
|
|
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",
|
|
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/
|
|
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" | "
|
|
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);
|
package/src/task-service.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.*,
|
|
5
|
-
*
|
|
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
|
|
9
|
-
*
|
|
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 {
|
|
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 --
|
|
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"],
|