@danypops/papyrus 0.21.6 → 0.23.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 +12 -6
- package/extension/src/artifact-status-presentation.ts +5 -0
- package/extension/src/discussion-picker.ts +9 -4
- package/extension/src/docs.ts +10 -1
- package/extension/src/domain-tools.ts +88 -16
- package/extension/src/index.ts +8 -1
- package/extension/src/playbook-bridge.ts +90 -0
- package/extension/src/playbooks.ts +62 -0
- package/extension/src/rules.ts +9 -2
- package/extension/src/skills.ts +9 -1
- package/package.json +1 -1
- package/src/authority-registry.ts +1 -1
- package/src/cli.ts +140 -3
- package/src/constants.ts +7 -1
- package/src/db.ts +20 -0
- package/src/domain/artifact.ts +24 -0
- package/src/domain-services.ts +181 -4
- package/src/modules/docs.ts +5 -2
- package/src/modules/playbooks.ts +77 -0
- package/src/modules/rules.ts +5 -2
- package/src/modules/skills.ts +5 -2
- package/src/service.ts +15 -0
package/src/cli.ts
CHANGED
|
@@ -92,6 +92,7 @@ const USAGE = `Usage:
|
|
|
92
92
|
papyrus docs activate|archive|reopen <id> [--json]
|
|
93
93
|
papyrus docs link <id> <relation> <target-id> [--json]
|
|
94
94
|
papyrus docs assign-project <id> [project-root] [--json]
|
|
95
|
+
papyrus docs update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
95
96
|
papyrus rules create --title <title> [--body <body>] [--condition <text>] [--rule-action <text>] [--severity block|warn|info] [--labels-json <json>] [--extra-json <json>] [--project-root <path>] [--json]
|
|
96
97
|
papyrus rules list [--status <status>] [--text <query>] [--limit <count>] [--project-root <path>] [--json]
|
|
97
98
|
papyrus rules show <id> [--json]
|
|
@@ -100,6 +101,7 @@ const USAGE = `Usage:
|
|
|
100
101
|
papyrus rules gate <rule-id> <task-id> [--json]
|
|
101
102
|
papyrus rules injectable [--json]
|
|
102
103
|
papyrus rules assign-project <id> [project-root] [--json]
|
|
104
|
+
papyrus rules update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
103
105
|
papyrus skills run <id> [--arguments-json <json>] [--run-id <id>] [--json]
|
|
104
106
|
papyrus skills create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json>] [--tools-json <json>] [--definition-json <json>] [--labels-json <json>] [--extra-json <json>] [--project-root <path>] [--json]
|
|
105
107
|
papyrus skills create-template --title <title> --target-kind <kind> [--defaults-json <json>] [--required-json <json>] [--body <body>] [--labels-json <json>] [--project-root <path>] [--json]
|
|
@@ -109,6 +111,14 @@ const USAGE = `Usage:
|
|
|
109
111
|
papyrus skills enable|disable <id> [--json]
|
|
110
112
|
papyrus skills instantiate <template-id> [--title <title>] [--body <body>] [--status <status>] [--labels-json <json>] [--extra-json <json>] [--json]
|
|
111
113
|
papyrus skills assign-project <id> [project-root] [--json]
|
|
114
|
+
papyrus skills update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
115
|
+
papyrus playbooks create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json>] [--tools-json <json>] [--labels-json <json>] [--extra-json <json>] [--project-root <path>] [--json]
|
|
116
|
+
papyrus playbooks list [--status <status>] [--text <query>] [--limit <count>] [--project-root <path>] [--json]
|
|
117
|
+
papyrus playbooks show <id> [--json]
|
|
118
|
+
papyrus playbooks invoke <id> [--json]
|
|
119
|
+
papyrus playbooks enable|disable <id> [--json]
|
|
120
|
+
papyrus playbooks assign-project <id> [project-root] [--json]
|
|
121
|
+
papyrus playbooks update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
112
122
|
papyrus notes capture <request> [--title <title>] [--json]
|
|
113
123
|
papyrus notes list [--status <draft|active|archived>] [--text <query>] [--limit <count>] [--json]
|
|
114
124
|
papyrus notes show <id> [--json]
|
|
@@ -466,8 +476,16 @@ export async function runSkillCli(args: string[], client: TaskCliClient, project
|
|
|
466
476
|
human = `Created: ${artifactLabel(artifact)}`;
|
|
467
477
|
break;
|
|
468
478
|
}
|
|
479
|
+
case "update": {
|
|
480
|
+
if (!id || second) throw new Error("skills update requires exactly one skill id");
|
|
481
|
+
if (title === undefined && body === undefined && labels === undefined) throw new Error("skills update requires --title, --body, or --labels-json");
|
|
482
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("skills.update", { id, title, body, labels });
|
|
483
|
+
result = artifact;
|
|
484
|
+
human = `${artifactLabel(artifact)}`;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
469
487
|
default:
|
|
470
|
-
throw new Error("skills action must be run, create, create-template, list, show, invoke, enable, disable, instantiate,
|
|
488
|
+
throw new Error("skills action must be run, create, create-template, list, show, invoke, enable, disable, instantiate, assign-project, or update");
|
|
471
489
|
}
|
|
472
490
|
return json ? JSON.stringify(result) : human;
|
|
473
491
|
}
|
|
@@ -632,8 +650,16 @@ export async function runDocsCli(args: string[], client: TaskCliClient): Promise
|
|
|
632
650
|
human = `Linked ${id} --${second}--> ${third}`;
|
|
633
651
|
break;
|
|
634
652
|
}
|
|
653
|
+
case "update": {
|
|
654
|
+
if (!id || second) throw new Error("docs update requires exactly one document id");
|
|
655
|
+
if (title === undefined && body === undefined && labels === undefined) throw new Error("docs update requires --title, --body, or --labels-json");
|
|
656
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("docs.update", { id, title, body, labels });
|
|
657
|
+
result = artifact;
|
|
658
|
+
human = `${artifactLabel(artifact)}`;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
635
661
|
default:
|
|
636
|
-
throw new Error("docs action must be create, list, show, activate, archive, reopen, link,
|
|
662
|
+
throw new Error("docs action must be create, list, show, activate, archive, reopen, link, assign-project, or update");
|
|
637
663
|
}
|
|
638
664
|
return json ? JSON.stringify(result) : human;
|
|
639
665
|
}
|
|
@@ -736,8 +762,114 @@ export async function runRulesCli(args: string[], client: TaskCliClient, project
|
|
|
736
762
|
human = rows.length === 0 ? "No injectable rules." : rows.map((row) => row.title).join("\n");
|
|
737
763
|
break;
|
|
738
764
|
}
|
|
765
|
+
case "update": {
|
|
766
|
+
if (!id || second) throw new Error("rules update requires exactly one rule id");
|
|
767
|
+
if (title === undefined && body === undefined && labels === undefined) throw new Error("rules update requires --title, --body, or --labels-json");
|
|
768
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("rules.update", { id, title, body, labels });
|
|
769
|
+
result = artifact;
|
|
770
|
+
human = `${artifactLabel(artifact)}`;
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
739
773
|
default:
|
|
740
|
-
throw new Error("rules action must be create, list, show, preview, enable, disable, gate, injectable,
|
|
774
|
+
throw new Error("rules action must be create, list, show, preview, enable, disable, gate, injectable, assign-project, or update");
|
|
775
|
+
}
|
|
776
|
+
return json ? JSON.stringify(result) : human;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export async function runPlaybooksCli(args: string[], client: TaskCliClient): Promise<string> {
|
|
780
|
+
const json = args.includes("--json");
|
|
781
|
+
const positional: string[] = [];
|
|
782
|
+
let title: string | undefined;
|
|
783
|
+
let body: string | undefined;
|
|
784
|
+
let trigger: string | undefined;
|
|
785
|
+
let steps: string[] | undefined;
|
|
786
|
+
let tools: string[] | undefined;
|
|
787
|
+
let labels: string[] | undefined;
|
|
788
|
+
let extra: Record<string, unknown> | undefined;
|
|
789
|
+
let status: string | undefined;
|
|
790
|
+
let text: string | undefined;
|
|
791
|
+
let limit: number | undefined;
|
|
792
|
+
let playbookProjectRoot: string | undefined;
|
|
793
|
+
for (let index = 0; index < args.length; index++) {
|
|
794
|
+
const argument = args[index]!;
|
|
795
|
+
if (argument === "--json") continue;
|
|
796
|
+
if (argument === "--title") { title = args[++index]; if (title === undefined) throw new Error("--title requires a value"); continue; }
|
|
797
|
+
if (argument === "--body") { body = args[++index]; if (body === undefined) throw new Error("--body requires a value"); continue; }
|
|
798
|
+
if (argument === "--trigger") { trigger = args[++index]; if (trigger === undefined) throw new Error("--trigger requires a value"); continue; }
|
|
799
|
+
if (argument === "--steps-json") { steps = parseJsonStringArrayFlag(args[++index], "--steps-json"); continue; }
|
|
800
|
+
if (argument === "--tools-json") { tools = parseJsonStringArrayFlag(args[++index], "--tools-json"); continue; }
|
|
801
|
+
if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
|
|
802
|
+
if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
|
|
803
|
+
if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
|
|
804
|
+
if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
|
|
805
|
+
if (argument === "--project-root") { playbookProjectRoot = args[++index]; if (!playbookProjectRoot) throw new Error("--project-root requires a value"); continue; }
|
|
806
|
+
if (argument === "--limit") {
|
|
807
|
+
const value = args[++index];
|
|
808
|
+
if (!value || Number.isNaN(Number(value))) throw new Error("--limit requires a numeric value");
|
|
809
|
+
limit = Number(value);
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (argument.startsWith("--")) throw new Error(`unknown playbooks option ${argument}`);
|
|
813
|
+
positional.push(argument);
|
|
814
|
+
}
|
|
815
|
+
const [action, id, second] = positional;
|
|
816
|
+
let result: unknown;
|
|
817
|
+
let human: string;
|
|
818
|
+
switch (action) {
|
|
819
|
+
case "create": {
|
|
820
|
+
if (id) throw new Error("playbooks create accepts no positional arguments");
|
|
821
|
+
if (!title) throw new Error("playbooks create requires --title");
|
|
822
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.create", { title, body, trigger, steps, tools, labels, extra, project_root: playbookProjectRoot });
|
|
823
|
+
result = artifact;
|
|
824
|
+
human = `Created playbook: ${artifactLabel(artifact)}`;
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
case "list": {
|
|
828
|
+
if (id) throw new Error("playbooks list accepts no positional arguments");
|
|
829
|
+
const rows = await client.call<Record<string, unknown>, CliArtifact[]>("playbooks.list", { status, text, limit, project_root: playbookProjectRoot });
|
|
830
|
+
result = rows;
|
|
831
|
+
human = rows.length === 0 ? "No playbooks found." : rows.map((row) => artifactLabel(row)).join("\n");
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
case "show": {
|
|
835
|
+
if (!id || second) throw new Error("playbooks show requires exactly one playbook id");
|
|
836
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.show", { id });
|
|
837
|
+
result = artifact;
|
|
838
|
+
human = `${artifactLabel(artifact)}\n\n${artifact.body ?? ""}`;
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
case "invoke": {
|
|
842
|
+
if (!id || second) throw new Error("playbooks invoke requires exactly one playbook id");
|
|
843
|
+
const invocation = await client.call<Record<string, unknown>, string>("playbooks.invoke", { id });
|
|
844
|
+
result = invocation;
|
|
845
|
+
human = invocation;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
case "enable":
|
|
849
|
+
case "disable": {
|
|
850
|
+
if (!id || second) throw new Error(`playbooks ${action} requires exactly one playbook id`);
|
|
851
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>(`playbooks.${action}`, { id });
|
|
852
|
+
result = artifact;
|
|
853
|
+
human = `${artifactLabel(artifact)}`;
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "assign-project": {
|
|
857
|
+
if (!id || second === undefined && positional.length > 2) throw new Error("playbooks assign-project requires <id> [project-root]");
|
|
858
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.assign_project", { id, project_root: second });
|
|
859
|
+
result = artifact;
|
|
860
|
+
human = second ? `Assigned ${id} to ${second}` : `Unscoped ${id}`;
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
case "update": {
|
|
864
|
+
if (!id || second) throw new Error("playbooks update requires exactly one playbook id");
|
|
865
|
+
if (title === undefined && body === undefined && labels === undefined) throw new Error("playbooks update requires --title, --body, or --labels-json");
|
|
866
|
+
const artifact = await client.call<Record<string, unknown>, CliArtifact>("playbooks.update", { id, title, body, labels });
|
|
867
|
+
result = artifact;
|
|
868
|
+
human = `${artifactLabel(artifact)}`;
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
default:
|
|
872
|
+
throw new Error("playbooks action must be create, list, show, invoke, enable, disable, assign-project, or update");
|
|
741
873
|
}
|
|
742
874
|
return json ? JSON.stringify(result) : human;
|
|
743
875
|
}
|
|
@@ -1505,6 +1637,11 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
1505
1637
|
console.log(await runSkillCli(args.slice(1), client));
|
|
1506
1638
|
return;
|
|
1507
1639
|
}
|
|
1640
|
+
if (command === "playbooks") {
|
|
1641
|
+
const client = await connectPapyrusClient();
|
|
1642
|
+
console.log(await runPlaybooksCli(args.slice(1), client));
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1508
1645
|
if (command === "notes") {
|
|
1509
1646
|
const client = await connectPapyrusClient();
|
|
1510
1647
|
console.log(await runNoteCli(args.slice(1), client));
|
package/src/constants.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const DAEMON_PROBE_TIMEOUT_MS = 800;
|
|
|
7
7
|
export const DAEMON_UNIT_NAME = "papyrus.service";
|
|
8
8
|
export const DAEMON_DIR_ENV = "PAPYRUS_DAEMON_DIR";
|
|
9
9
|
export const SQLITE_BUSY_TIMEOUT_MS = 5_000;
|
|
10
|
-
export const SQLITE_SCHEMA_VERSION =
|
|
10
|
+
export const SQLITE_SCHEMA_VERSION = 18;
|
|
11
11
|
export const SERVICE_MAX_BODY_BYTES = 1_048_576;
|
|
12
12
|
|
|
13
13
|
export const WAL_CHECKPOINT_INTERVAL_MS = 60_000;
|
|
@@ -99,6 +99,7 @@ export const SKILL_MAX_RENDERED_BYTES = 1_048_576;
|
|
|
99
99
|
*/
|
|
100
100
|
export const SKILL_INVOCATION_MAX_LINKED_ARTIFACTS = 20;
|
|
101
101
|
export const SKILL_INVOCATION_MAX_CALL_DEPTH = 4;
|
|
102
|
+
export const PLAYBOOK_INVOCATION_MAX_LINKED_ARTIFACTS = 20;
|
|
102
103
|
|
|
103
104
|
/**
|
|
104
105
|
* At the core, a workflow Skill creates Tasks and begins a pipeline -- an Ansible playbook or
|
|
@@ -119,6 +120,11 @@ export const TASK_TITLE_MAX_LENGTH = 500;
|
|
|
119
120
|
export const TASK_BODY_MAX_LENGTH = 100_000;
|
|
120
121
|
export const TASK_LABEL_MAX_COUNT = 64;
|
|
121
122
|
export const TASK_LABEL_MAX_LENGTH = 128;
|
|
123
|
+
/** Mutable Doc/Rule/Skill content bounds -- same numbers as Task's, since these are the same kind of freeform content at the same scale. Rules also enforce their own stricter RULE_TEXT_HARD_LIMIT_CHARACTERS on top of this. */
|
|
124
|
+
export const ARTIFACT_TITLE_MAX_LENGTH = 500;
|
|
125
|
+
export const ARTIFACT_BODY_MAX_LENGTH = 100_000;
|
|
126
|
+
export const ARTIFACT_LABEL_MAX_COUNT = 64;
|
|
127
|
+
export const ARTIFACT_LABEL_MAX_LENGTH = 128;
|
|
122
128
|
/** Append-only Task chronology query and evidence bounds. */
|
|
123
129
|
export const TASK_HISTORY_DEFAULT_LIMIT = 25;
|
|
124
130
|
export const TASK_HISTORY_MAX_LIMIT = 100;
|
package/src/db.ts
CHANGED
|
@@ -253,6 +253,7 @@ INSERT OR IGNORE INTO kinds VALUES ('doc','Knowledge — what we know (specs, de
|
|
|
253
253
|
INSERT OR IGNORE INTO kinds VALUES ('task','Work — what we are doing (objectives, steps, checklists)');
|
|
254
254
|
INSERT OR IGNORE INTO kinds VALUES ('rule','Governance — when doing X, follow Y');
|
|
255
255
|
INSERT OR IGNORE INTO kinds VALUES ('skill','Parameterized workflow bundle — inputs and templates load tasks, rules, and docs');
|
|
256
|
+
INSERT OR IGNORE INTO kinds VALUES ('playbook','Reusable procedure — a trigger and an ordered list of steps an agent reads and follows, not a mechanically instantiated blueprint');
|
|
256
257
|
INSERT OR IGNORE INTO statuses VALUES ('draft','doc');
|
|
257
258
|
INSERT OR IGNORE INTO statuses VALUES ('active','doc');
|
|
258
259
|
INSERT OR IGNORE INTO statuses VALUES ('archived','doc');
|
|
@@ -266,6 +267,8 @@ INSERT OR IGNORE INTO statuses VALUES ('active','rule');
|
|
|
266
267
|
INSERT OR IGNORE INTO statuses VALUES ('deprecated','rule');
|
|
267
268
|
INSERT OR IGNORE INTO statuses VALUES ('active','skill');
|
|
268
269
|
INSERT OR IGNORE INTO statuses VALUES ('deprecated','skill');
|
|
270
|
+
INSERT OR IGNORE INTO statuses VALUES ('active','playbook');
|
|
271
|
+
INSERT OR IGNORE INTO statuses VALUES ('deprecated','playbook');
|
|
269
272
|
INSERT OR IGNORE INTO relation_names VALUES ('references','Source material (doc→doc, doc→task, doc→rule)');
|
|
270
273
|
INSERT OR IGNORE INTO relation_names VALUES ('implements','This work satisfies that (task→doc, task→rule)');
|
|
271
274
|
INSERT OR IGNORE INTO relation_names VALUES ('follows','This work obeys that (task→rule, task→skill)');
|
|
@@ -504,6 +507,23 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
504
507
|
`);
|
|
505
508
|
},
|
|
506
509
|
},
|
|
510
|
+
{
|
|
511
|
+
version: 18,
|
|
512
|
+
name: "playbook-kind",
|
|
513
|
+
// Playbooks (trigger/steps/tools guidance an agent reads and follows) were a subtype-less
|
|
514
|
+
// shape squeezed into the "skill" kind alongside artifact-templates and workflow blueprints
|
|
515
|
+
// -- fundamentally different mechanisms (mechanical multi-artifact instantiation) from a flat
|
|
516
|
+
// step list. Split into its own kind; only rows with no subtype move -- artifact-template and
|
|
517
|
+
// workflow rows stay exactly where they are.
|
|
518
|
+
up: (db) => {
|
|
519
|
+
db.exec(`
|
|
520
|
+
INSERT OR IGNORE INTO kinds VALUES ('playbook','Reusable procedure — a trigger and an ordered list of steps an agent reads and follows, not a mechanically instantiated blueprint');
|
|
521
|
+
INSERT OR IGNORE INTO statuses VALUES ('active','playbook');
|
|
522
|
+
INSERT OR IGNORE INTO statuses VALUES ('deprecated','playbook');
|
|
523
|
+
UPDATE artifacts SET kind = 'playbook' WHERE kind = 'skill' AND (subtype IS NULL OR subtype = '');
|
|
524
|
+
`);
|
|
525
|
+
},
|
|
526
|
+
},
|
|
507
527
|
];
|
|
508
528
|
|
|
509
529
|
/**
|
package/src/domain/artifact.ts
CHANGED
|
@@ -67,3 +67,27 @@ export interface RelationshipQuery {
|
|
|
67
67
|
artifactIds?: string[];
|
|
68
68
|
limit?: number;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A label of the form "source:<system>" marks an artifact as ingested/projected from an
|
|
73
|
+
* external, non-Papyrus system (e.g. web-spider's own "source:web-spider" convention on the
|
|
74
|
+
* Docs it creates) -- content Papyrus does not own and cannot safely rewrite without silently
|
|
75
|
+
* diverging from the true source. Editing one directly would look like a correction but really
|
|
76
|
+
* just be a local fork nobody re-syncs.
|
|
77
|
+
*/
|
|
78
|
+
export const EXTERNAL_SOURCE_LABEL_PREFIX = "source:";
|
|
79
|
+
|
|
80
|
+
/** The external system name from a "source:<system>" label, or undefined if this artifact has no such label (i.e. it's Papyrus-native content). */
|
|
81
|
+
export function externalSourceOf(artifact: Pick<Artifact, "labels">): string | undefined {
|
|
82
|
+
const label = artifact.labels.find((entry) => entry.startsWith(EXTERNAL_SOURCE_LABEL_PREFIX));
|
|
83
|
+
return label === undefined ? undefined : label.slice(EXTERNAL_SOURCE_LABEL_PREFIX.length) || undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Throws if the artifact is a read-only external projection; a caller must never silently rewrite content it doesn't own the source of. */
|
|
87
|
+
export function requireLocallyOwnedContent(artifact: Artifact): Artifact {
|
|
88
|
+
const system = externalSourceOf(artifact);
|
|
89
|
+
if (system !== undefined) {
|
|
90
|
+
throw new Error(`"${artifact.title}" is a read-only projection from ${system}; edit it there, or capture a correction as a new linked Doc, until a write-back capability is integrated`);
|
|
91
|
+
}
|
|
92
|
+
return artifact;
|
|
93
|
+
}
|
package/src/domain-services.ts
CHANGED
|
@@ -1,17 +1,52 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ARTIFACT_BODY_MAX_LENGTH,
|
|
3
|
+
ARTIFACT_LABEL_MAX_COUNT,
|
|
4
|
+
ARTIFACT_LABEL_MAX_LENGTH,
|
|
2
5
|
ARTIFACT_SCOPE_MAX_ARTIFACTS,
|
|
6
|
+
ARTIFACT_TITLE_MAX_LENGTH,
|
|
7
|
+
PLAYBOOK_INVOCATION_MAX_LINKED_ARTIFACTS,
|
|
3
8
|
RULE_TEXT_HARD_LIMIT_CHARACTERS,
|
|
4
9
|
SKILL_INVOCATION_MAX_CALL_DEPTH,
|
|
5
10
|
SKILL_INVOCATION_MAX_LINKED_ARTIFACTS,
|
|
6
11
|
} from "./constants.ts";
|
|
7
|
-
import type
|
|
12
|
+
import { requireLocallyOwnedContent, type Artifact, type CreateArtifactInput } from "./domain/artifact.ts";
|
|
8
13
|
import type { ArtifactEventContext } from "./domain/artifact-event.ts";
|
|
9
14
|
import { normalizeProjectRoot } from "./domain/task-scope.ts";
|
|
10
15
|
import { validateSkillDefinition } from "./domain/skill-definition.ts";
|
|
11
16
|
import type { ArtifactStore } from "./ports/artifact-store.ts";
|
|
12
17
|
import type { ArtifactScopeStore } from "./ports/artifact-scope-store.ts";
|
|
13
18
|
import { NOTE_SUBTYPE } from "./note-service.ts";
|
|
14
|
-
import type
|
|
19
|
+
import { type ArtifactAction, type AuthorityRegistry } from "./authority-registry.ts";
|
|
20
|
+
|
|
21
|
+
export interface UpdateContentInput {
|
|
22
|
+
title?: string;
|
|
23
|
+
body?: string;
|
|
24
|
+
labels?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function requireContentUpdateFields(input: UpdateContentInput): void {
|
|
28
|
+
if (input.title === undefined && input.body === undefined && input.labels === undefined) {
|
|
29
|
+
throw new Error("update requires title, body, or labels");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertTitleBounds(title: string | undefined): void {
|
|
34
|
+
if (title !== undefined && (title.trim().length === 0 || title.length > ARTIFACT_TITLE_MAX_LENGTH)) {
|
|
35
|
+
throw new Error(`title must be between 1 and ${ARTIFACT_TITLE_MAX_LENGTH} characters`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertBodyBounds(body: string | undefined): void {
|
|
40
|
+
if (body !== undefined && body.length > ARTIFACT_BODY_MAX_LENGTH) throw new Error(`body cannot exceed ${ARTIFACT_BODY_MAX_LENGTH} characters`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function assertLabelsBounds(labels: string[] | undefined): void {
|
|
44
|
+
if (labels === undefined) return;
|
|
45
|
+
if (labels.length > ARTIFACT_LABEL_MAX_COUNT) throw new Error(`labels cannot exceed ${ARTIFACT_LABEL_MAX_COUNT} entries`);
|
|
46
|
+
if (labels.some((label) => label.length === 0 || label.length > ARTIFACT_LABEL_MAX_LENGTH)) {
|
|
47
|
+
throw new Error(`each label must be between 1 and ${ARTIFACT_LABEL_MAX_LENGTH} characters`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
15
50
|
|
|
16
51
|
export interface ListFilter {
|
|
17
52
|
status?: string;
|
|
@@ -83,8 +118,8 @@ function templateSubtype(artifacts: ArtifactStore, templateId: string | undefine
|
|
|
83
118
|
return typeof subtype === "string" ? subtype : undefined;
|
|
84
119
|
}
|
|
85
120
|
|
|
86
|
-
function requireMutableDocument(document: Artifact, authority: AuthorityRegistry): Artifact {
|
|
87
|
-
authority.requireArtifactAllowed(document.kind, document.subtype,
|
|
121
|
+
function requireMutableDocument(document: Artifact, authority: AuthorityRegistry, action: ArtifactAction = "status"): Artifact {
|
|
122
|
+
authority.requireArtifactAllowed(document.kind, document.subtype, action, "docs");
|
|
88
123
|
return document;
|
|
89
124
|
}
|
|
90
125
|
|
|
@@ -99,6 +134,8 @@ export interface CreateDocumentInput {
|
|
|
99
134
|
projectRoot?: string;
|
|
100
135
|
}
|
|
101
136
|
|
|
137
|
+
export type UpdateDocumentInput = UpdateContentInput;
|
|
138
|
+
|
|
102
139
|
export type DocumentTransition = "activate" | "archive" | "reopen";
|
|
103
140
|
export type DocumentRelation = "references" | "documents" | "supersedes" | "relates_to" | "contains" | "part_of";
|
|
104
141
|
|
|
@@ -157,6 +194,23 @@ export function transitionDocument(artifacts: ArtifactStore, id: string, action:
|
|
|
157
194
|
return artifacts.setStatus(id, transition.to, context)!;
|
|
158
195
|
}
|
|
159
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Docs are immutable-by-convention only in the sense that no path existed to change them --
|
|
199
|
+
* this is that path. A read-only external projection (see requireLocallyOwnedContent) still
|
|
200
|
+
* refuses, on purpose: rewriting it here would silently fork from whatever system actually
|
|
201
|
+
* owns it (e.g. web-spider's ingested pages), with nothing to ever reconcile the two again.
|
|
202
|
+
*/
|
|
203
|
+
export function updateDocument(artifacts: ArtifactStore, id: string, input: UpdateDocumentInput, authority: AuthorityRegistry, context?: ArtifactEventContext): Artifact {
|
|
204
|
+
requireContentUpdateFields(input);
|
|
205
|
+
assertTitleBounds(input.title);
|
|
206
|
+
assertBodyBounds(input.body);
|
|
207
|
+
assertLabelsBounds(input.labels);
|
|
208
|
+
const document = requireLocallyOwnedContent(requireMutableDocument(requireDocument(artifacts, id), authority, "update"));
|
|
209
|
+
const updated = artifacts.updateContent(id, input, context);
|
|
210
|
+
if (!updated) throw new Error(`document "${id}" not found`);
|
|
211
|
+
return updated;
|
|
212
|
+
}
|
|
213
|
+
|
|
160
214
|
export function linkDocument(artifacts: ArtifactStore, id: string, relation: DocumentRelation, targetId: string, authority: AuthorityRegistry, context?: ArtifactEventContext): Artifact {
|
|
161
215
|
requireMutableDocument(requireDocument(artifacts, id), authority);
|
|
162
216
|
const target = artifacts.get(targetId);
|
|
@@ -258,6 +312,24 @@ export function transitionRule(artifacts: ArtifactStore, id: string, action: Rul
|
|
|
258
312
|
return artifacts.setStatus(id, target, context)!;
|
|
259
313
|
}
|
|
260
314
|
|
|
315
|
+
export type UpdateRuleInput = UpdateContentInput;
|
|
316
|
+
|
|
317
|
+
/** A Rule's body update stays under the same combined condition+action+body ceiling as creation -- a permanent per-turn injection cost doesn't get looser just because it's an edit, not a create. */
|
|
318
|
+
export function updateRule(artifacts: ArtifactStore, id: string, input: UpdateRuleInput, context?: ArtifactEventContext): Artifact {
|
|
319
|
+
requireContentUpdateFields(input);
|
|
320
|
+
assertTitleBounds(input.title);
|
|
321
|
+
assertLabelsBounds(input.labels);
|
|
322
|
+
const rule = requireLocallyOwnedContent(requireKind(artifacts, id, "rule"));
|
|
323
|
+
if (input.body !== undefined) {
|
|
324
|
+
const condition = typeof rule.extra["condition"] === "string" ? rule.extra["condition"] : undefined;
|
|
325
|
+
const action = typeof rule.extra["action"] === "string" ? rule.extra["action"] : undefined;
|
|
326
|
+
assertRuleTextWithinBounds(condition, action, input.body);
|
|
327
|
+
}
|
|
328
|
+
const updated = artifacts.updateContent(id, input, context);
|
|
329
|
+
if (!updated) throw new Error(`rule "${id}" not found`);
|
|
330
|
+
return updated;
|
|
331
|
+
}
|
|
332
|
+
|
|
261
333
|
export function gateTaskWithRule(artifacts: ArtifactStore, ruleId: string, taskId: string, context?: ArtifactEventContext): Artifact {
|
|
262
334
|
requireKind(artifacts, ruleId, "rule");
|
|
263
335
|
requireKind(artifacts, taskId, "task");
|
|
@@ -353,6 +425,19 @@ export function showSkill(artifacts: ArtifactStore, id: string): Artifact {
|
|
|
353
425
|
return artifacts.get(id, { tree: true })!;
|
|
354
426
|
}
|
|
355
427
|
|
|
428
|
+
export type UpdateSkillInput = UpdateContentInput;
|
|
429
|
+
|
|
430
|
+
export function updateSkill(artifacts: ArtifactStore, id: string, input: UpdateSkillInput, context?: ArtifactEventContext): Artifact {
|
|
431
|
+
requireContentUpdateFields(input);
|
|
432
|
+
assertTitleBounds(input.title);
|
|
433
|
+
assertBodyBounds(input.body);
|
|
434
|
+
assertLabelsBounds(input.labels);
|
|
435
|
+
const skill = requireLocallyOwnedContent(requireKind(artifacts, id, "skill"));
|
|
436
|
+
const updated = artifacts.updateContent(skill.id, input, context);
|
|
437
|
+
if (!updated) throw new Error(`skill "${id}" not found`);
|
|
438
|
+
return updated;
|
|
439
|
+
}
|
|
440
|
+
|
|
356
441
|
function skillInvocationBody(skill: Artifact): string {
|
|
357
442
|
if (skill.subtype === "artifact-template") {
|
|
358
443
|
return `Create an artifact using Papyrus template "${skill.title}".\ntemplate_id: ${skill.id}\nAsk for or infer all required template fields, then call the skills domain tool instantiate action.`;
|
|
@@ -430,3 +515,95 @@ export function transitionSkill(artifacts: ArtifactStore, id: string, action: Sk
|
|
|
430
515
|
if (skill.status !== expected) throw new Error(`cannot ${action} skill from ${skill.status}`);
|
|
431
516
|
return artifacts.setStatus(id, target, context)!;
|
|
432
517
|
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Playbooks: a trigger and an ordered list of steps an agent reads and follows -- a completely
|
|
521
|
+
* different beast from Skills, not a subtype of one. A Skill (artifact-template or workflow) is
|
|
522
|
+
* mechanically instantiated into other artifacts; a Playbook is never instantiated, it's read
|
|
523
|
+
* and followed, and it never composes other Playbooks the way a Skill can call another Skill.
|
|
524
|
+
*/
|
|
525
|
+
export interface CreatePlaybookInput {
|
|
526
|
+
title: string;
|
|
527
|
+
body?: string;
|
|
528
|
+
trigger?: string;
|
|
529
|
+
steps?: string[];
|
|
530
|
+
tools?: string[];
|
|
531
|
+
labels?: string[];
|
|
532
|
+
extra?: Record<string, unknown>;
|
|
533
|
+
projectRoot?: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export type PlaybookTransition = "enable" | "disable";
|
|
537
|
+
export type UpdatePlaybookInput = UpdateContentInput;
|
|
538
|
+
|
|
539
|
+
export function createPlaybook(artifacts: ArtifactStore, scopes: ArtifactScopeStore, input: CreatePlaybookInput, context?: ArtifactEventContext): Artifact {
|
|
540
|
+
const projectRoot = input.projectRoot === undefined ? undefined : normalizeProjectRoot(input.projectRoot);
|
|
541
|
+
const playbook = artifacts.create({
|
|
542
|
+
kind: "playbook",
|
|
543
|
+
status: "active", // explicit; see createDocument for why defaultStatusFor is not trusted here
|
|
544
|
+
title: input.title,
|
|
545
|
+
body: input.body,
|
|
546
|
+
labels: input.labels,
|
|
547
|
+
extra: {
|
|
548
|
+
...(input.extra ?? {}),
|
|
549
|
+
...(input.trigger ? { trigger: input.trigger } : {}),
|
|
550
|
+
...(input.steps ? { steps: input.steps } : {}),
|
|
551
|
+
...(input.tools ? { tools: input.tools } : {}),
|
|
552
|
+
},
|
|
553
|
+
}, context);
|
|
554
|
+
scopes.assign(playbook.id, projectRoot, projectRoot === undefined ? "unscoped" : "explicit");
|
|
555
|
+
return playbook;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function listPlaybooks(artifacts: ArtifactStore, scopes: ArtifactScopeStore, filter: ListFilter): Artifact[] {
|
|
559
|
+
return listScoped(artifacts, scopes, "playbook", filter);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export function assignPlaybookProject(artifacts: ArtifactStore, scopes: ArtifactScopeStore, id: string, projectRoot: string | undefined): Artifact {
|
|
563
|
+
return assignArtifactProject(artifacts, scopes, id, "playbook", projectRoot);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function showPlaybook(artifacts: ArtifactStore, id: string): Artifact {
|
|
567
|
+
requireKind(artifacts, id, "playbook");
|
|
568
|
+
return artifacts.get(id, { tree: true })!;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export function updatePlaybook(artifacts: ArtifactStore, id: string, input: UpdatePlaybookInput, context?: ArtifactEventContext): Artifact {
|
|
572
|
+
requireContentUpdateFields(input);
|
|
573
|
+
assertTitleBounds(input.title);
|
|
574
|
+
assertBodyBounds(input.body);
|
|
575
|
+
assertLabelsBounds(input.labels);
|
|
576
|
+
const playbook = requireLocallyOwnedContent(requireKind(artifacts, id, "playbook"));
|
|
577
|
+
const updated = artifacts.updateContent(playbook.id, input, context);
|
|
578
|
+
if (!updated) throw new Error(`playbook "${id}" not found`);
|
|
579
|
+
return updated;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function transitionPlaybook(artifacts: ArtifactStore, id: string, action: PlaybookTransition, context?: ArtifactEventContext): Artifact {
|
|
583
|
+
const playbook = requireKind(artifacts, id, "playbook");
|
|
584
|
+
const expected = action === "enable" ? "deprecated" : "active";
|
|
585
|
+
const target = action === "enable" ? "active" : "deprecated";
|
|
586
|
+
if (playbook.status !== expected) throw new Error(`cannot ${action} playbook from ${playbook.status}`);
|
|
587
|
+
return artifacts.setStatus(id, target, context)!;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Renders trigger/steps/tools into readable guidance, plus any real linked artifacts. No nested playbook-calls-playbook composition -- a Playbook is a flat procedure, not a composable bundle. */
|
|
591
|
+
export function playbookInvocation(artifacts: ArtifactStore, id: string): string {
|
|
592
|
+
const playbook = requireKind(artifacts, id, "playbook");
|
|
593
|
+
const trigger = typeof playbook.extra["trigger"] === "string" ? playbook.extra["trigger"] : "manual invocation";
|
|
594
|
+
const steps = Array.isArray(playbook.extra["steps"]) ? playbook.extra["steps"].filter((step): step is string => typeof step === "string") : [];
|
|
595
|
+
const tools = Array.isArray(playbook.extra["tools"]) ? playbook.extra["tools"].filter((tool): tool is string => typeof tool === "string") : [];
|
|
596
|
+
const sections = [[
|
|
597
|
+
`Apply Papyrus playbook "${playbook.title}" (${playbook.id}).`,
|
|
598
|
+
`Trigger: ${trigger}`,
|
|
599
|
+
...(playbook.body ? [`Context: ${playbook.body}`] : []),
|
|
600
|
+
...(steps.length ? ["Steps:", ...steps.map((step, index) => `${index + 1}. ${step}`)] : []),
|
|
601
|
+
...(tools.length ? [`Tools: ${tools.join(", ")}`] : []),
|
|
602
|
+
].join("\n")];
|
|
603
|
+
const edges = artifacts.relationships({ artifactIds: [id] }).filter((edge) => edge.from === id).slice(0, PLAYBOOK_INVOCATION_MAX_LINKED_ARTIFACTS);
|
|
604
|
+
const linkedLines = edges
|
|
605
|
+
.map((edge) => { const target = artifacts.get(edge.to); return target ? `- ${edge.relation} ${target.kind} "${target.title}" (${target.id})` : undefined; })
|
|
606
|
+
.filter((line): line is string => line !== undefined);
|
|
607
|
+
if (linkedLines.length > 0) sections.push(["Linked context (query Papyrus for full detail before proceeding):", ...linkedLines].join("\n"));
|
|
608
|
+
return sections.join("\n\n");
|
|
609
|
+
}
|
package/src/modules/docs.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* ArtifactStore-based with no other module's concrete class dependency.
|
|
8
8
|
*/
|
|
9
9
|
import type { AuthorityRegistry } from "../authority-registry.ts";
|
|
10
|
-
import { assignDocumentProject, createDocument, linkDocument, listDocuments, showDocument, transitionDocument, type DocumentRelation } from "../domain-services.ts";
|
|
10
|
+
import { assignDocumentProject, createDocument, linkDocument, listDocuments, showDocument, transitionDocument, updateDocument, type DocumentRelation } from "../domain-services.ts";
|
|
11
11
|
import type { OperationDefinition } from "../module-registry.ts";
|
|
12
12
|
import type { ArtifactScopeStore } from "../ports/artifact-scope-store.ts";
|
|
13
13
|
import type { ArtifactStore } from "../ports/artifact-store.ts";
|
|
@@ -52,7 +52,7 @@ const artifactFilter = (input: OperationInput) => ({
|
|
|
52
52
|
/** Registers every docs.* operation against the shared ArtifactStore port. Behavior is unchanged from the prior inline handlers in src/service.ts. */
|
|
53
53
|
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
|
|
54
54
|
export const DOCS_OPERATION_NAMES = [
|
|
55
|
-
"docs.create", "docs.list", "docs.show", "docs.activate", "docs.archive", "docs.reopen", "docs.link", "docs.assign_project",
|
|
55
|
+
"docs.create", "docs.list", "docs.show", "docs.activate", "docs.archive", "docs.reopen", "docs.link", "docs.assign_project", "docs.update",
|
|
56
56
|
] as const;
|
|
57
57
|
|
|
58
58
|
export function docsOperations(artifacts: ArtifactStore, scopes: ArtifactScopeStore, authority: AuthorityRegistry): OperationDefinition[] {
|
|
@@ -73,5 +73,8 @@ export function docsOperations(artifacts: ArtifactStore, scopes: ArtifactScopeSt
|
|
|
73
73
|
define("docs.reopen", (input: OperationInput) => transitionDocument(artifacts, string(input, "id"), "reopen", authority, eventContext(input))),
|
|
74
74
|
define("docs.link", (input: OperationInput) => linkDocument(artifacts, string(input, "id"), string(input, "relation") as DocumentRelation, string(input, "target_id"), authority, eventContext(input))),
|
|
75
75
|
define("docs.assign_project", (input: OperationInput) => assignDocumentProject(artifacts, scopes, string(input, "id"), optionalString(input, "project_root"))),
|
|
76
|
+
define("docs.update", (input: OperationInput) => updateDocument(artifacts, string(input, "id"), {
|
|
77
|
+
title: optionalString(input, "title"), body: optionalString(input, "body"), labels: input["labels"] as string[] | undefined,
|
|
78
|
+
}, authority, eventContext(input))),
|
|
76
79
|
];
|
|
77
80
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* modules/playbooks.ts — Playbooks as a Papyrus-native registered module.
|
|
3
|
+
*
|
|
4
|
+
* A Playbook (trigger + ordered steps an agent reads and follows) is a completely different
|
|
5
|
+
* beast from a Skill (a mechanically instantiated artifact-template or workflow blueprint) --
|
|
6
|
+
* its own kind, not a subtype squeezed into "skill". See domain-services.ts's Playbook section
|
|
7
|
+
* for the full rationale.
|
|
8
|
+
*/
|
|
9
|
+
import { assignPlaybookProject, createPlaybook, listPlaybooks, playbookInvocation, showPlaybook, transitionPlaybook, updatePlaybook } from "../domain-services.ts";
|
|
10
|
+
import type { OperationDefinition } from "../module-registry.ts";
|
|
11
|
+
import type { ArtifactScopeStore } from "../ports/artifact-scope-store.ts";
|
|
12
|
+
import type { ArtifactStore } from "../ports/artifact-store.ts";
|
|
13
|
+
|
|
14
|
+
const MODULE_ID = "playbooks";
|
|
15
|
+
|
|
16
|
+
type OperationInput = Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
function string(input: OperationInput, key: string): string {
|
|
19
|
+
const value = input[key];
|
|
20
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${key} is required`);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function optionalString(input: OperationInput, key: string): string | undefined {
|
|
25
|
+
const value = input[key];
|
|
26
|
+
if (value === undefined) return undefined;
|
|
27
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function optionalNumber(input: OperationInput, key: string): number | undefined {
|
|
32
|
+
const value = input[key];
|
|
33
|
+
if (value === undefined) return undefined;
|
|
34
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const eventContext = (input: OperationInput) => ({
|
|
39
|
+
actor: optionalString(input, "actor"),
|
|
40
|
+
source: optionalString(input, "source"),
|
|
41
|
+
sessionId: optionalString(input, "session_id") ?? optionalString(input, "sessionId"),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const artifactFilter = (input: OperationInput) => ({
|
|
45
|
+
status: optionalString(input, "status"),
|
|
46
|
+
text: optionalString(input, "text"),
|
|
47
|
+
limit: optionalNumber(input, "limit"),
|
|
48
|
+
projectRoot: optionalString(input, "project_root"),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
|
|
52
|
+
export const PLAYBOOKS_OPERATION_NAMES = [
|
|
53
|
+
"playbooks.create", "playbooks.list", "playbooks.show", "playbooks.invoke", "playbooks.enable", "playbooks.disable", "playbooks.assign_project", "playbooks.update",
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
56
|
+
export function playbooksOperations(artifacts: ArtifactStore, scopes: ArtifactScopeStore): OperationDefinition[] {
|
|
57
|
+
const define = <Input, Output>(name: string, execute: (input: Input) => Output): OperationDefinition<Input, Output> => ({
|
|
58
|
+
name, moduleId: MODULE_ID, execute,
|
|
59
|
+
});
|
|
60
|
+
return [
|
|
61
|
+
define("playbooks.create", (input: OperationInput) => createPlaybook(artifacts, scopes, {
|
|
62
|
+
title: string(input, "title"), body: optionalString(input, "body"), trigger: optionalString(input, "trigger"),
|
|
63
|
+
steps: input["steps"] as string[] | undefined, tools: input["tools"] as string[] | undefined,
|
|
64
|
+
labels: input["labels"] as string[] | undefined, extra: input["extra"] as Record<string, unknown> | undefined,
|
|
65
|
+
projectRoot: optionalString(input, "project_root"),
|
|
66
|
+
}, eventContext(input))),
|
|
67
|
+
define("playbooks.list", (input: OperationInput) => listPlaybooks(artifacts, scopes, artifactFilter(input))),
|
|
68
|
+
define("playbooks.show", (input: OperationInput) => showPlaybook(artifacts, string(input, "id"))),
|
|
69
|
+
define("playbooks.invoke", (input: OperationInput) => playbookInvocation(artifacts, string(input, "id"))),
|
|
70
|
+
define("playbooks.enable", (input: OperationInput) => transitionPlaybook(artifacts, string(input, "id"), "enable", eventContext(input))),
|
|
71
|
+
define("playbooks.disable", (input: OperationInput) => transitionPlaybook(artifacts, string(input, "id"), "disable", eventContext(input))),
|
|
72
|
+
define("playbooks.assign_project", (input: OperationInput) => assignPlaybookProject(artifacts, scopes, string(input, "id"), optionalString(input, "project_root"))),
|
|
73
|
+
define("playbooks.update", (input: OperationInput) => updatePlaybook(artifacts, string(input, "id"), {
|
|
74
|
+
title: optionalString(input, "title"), body: optionalString(input, "body"), labels: input["labels"] as string[] | undefined,
|
|
75
|
+
}, eventContext(input))),
|
|
76
|
+
];
|
|
77
|
+
}
|