@danypops/papyrus 0.44.9 → 0.44.11
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/package.json +3 -2
- package/src/artifact/artifact.ts +23 -0
- package/src/cli/artifact-command.ts +210 -0
- package/src/cli/discuss-command.ts +256 -0
- package/src/cli/docs-command.ts +192 -0
- package/src/cli/gates-command.ts +46 -0
- package/src/cli/graph-command.ts +171 -0
- package/src/cli/graph-projection-command.ts +79 -0
- package/src/cli/log-command.ts +101 -0
- package/src/cli/migration-command.ts +40 -0
- package/src/cli/note-command.ts +186 -0
- package/src/cli/playbooks-command.ts +313 -0
- package/src/cli/rules-command.ts +220 -0
- package/src/cli/session-identity-command.ts +58 -0
- package/src/cli/shared.ts +5 -0
- package/src/cli/stricli-run.ts +30 -0
- package/src/cli/task-command.ts +892 -0
- package/src/cli.ts +30 -1912
- package/src/handlers/discuss.ts +22 -20
- package/src/handlers/docs.ts +4 -30
- package/src/handlers/notes.ts +2 -29
- package/src/handlers/playbooks.ts +27 -92
- package/src/handlers/rules.ts +4 -30
- package/src/handlers/shared.ts +92 -1
- package/src/handlers/tasks.ts +31 -118
- package/src/modules/discuss.ts +5 -26
- package/src/modules/docs.ts +6 -23
- package/src/modules/graph-projection.ts +1 -8
- package/src/modules/logs.ts +1 -22
- package/src/modules/notes.ts +1 -22
- package/src/modules/operation-input.ts +42 -0
- package/src/modules/playbooks.ts +6 -23
- package/src/modules/rules.ts +6 -23
- package/src/modules/session-identity.ts +1 -15
- package/src/modules/tasks.ts +5 -33
- package/src/service.ts +1 -28
package/src/handlers/discuss.ts
CHANGED
|
@@ -13,17 +13,23 @@
|
|
|
13
13
|
* (service.ts's moduleRegistry) stays registered unchanged for pi-papyrus's
|
|
14
14
|
* own /discuss TUI, which never went through the retired mega-tool.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import type { VehicleContentBlock } from "@danypops/vehicle-core";
|
|
17
17
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
18
18
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
19
19
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
20
20
|
import type { DiscussionAndRounds, Discussions } from "../discussion/discussion-service.ts";
|
|
21
21
|
import { DISCUSSION_SUBTYPE, type DiscussionRound } from "../domain/discussion.ts";
|
|
22
22
|
import { discussOperations } from "../modules/discuss.ts";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
createOperationDefiner,
|
|
25
|
+
numberProp,
|
|
26
|
+
type OperationSchemaProperties,
|
|
27
|
+
resolveArtifactIdWidened,
|
|
28
|
+
stringProp,
|
|
29
|
+
validationError,
|
|
30
|
+
} from "./shared.ts";
|
|
24
31
|
|
|
25
32
|
const OWNER = "discuss";
|
|
26
|
-
const LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
27
33
|
const arrayProp = { type: "array" } as const;
|
|
28
34
|
/** Purely a client-side hint (see vehicle-client-pi's interactiveFollowUps) -- never read server-side, but must still be declared or the schema's additionalProperties:false rejects it outright. */
|
|
29
35
|
const boolProp = { type: "boolean" } as const;
|
|
@@ -115,34 +121,30 @@ const optionsUnionSchema = { type: "array" } as const;
|
|
|
115
121
|
export function registerDiscussVehicleOperations(registry: VehicleRegistry, discussions: Discussions, artifacts: ArtifactStore): void {
|
|
116
122
|
const moduleOperations = new Map(discussOperations(discussions).map((op) => [op.name, op]));
|
|
117
123
|
const call = <Output>(name: string, input: Record<string, unknown>): Output => moduleOperations.get(name)!.execute(input) as Output;
|
|
124
|
+
const baseDefine = createOperationDefiner(registry, OWNER, "discuss", ["discuss:read", "discuss:write"], call);
|
|
118
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Defensively shallow-copies context.input before handing it to resolve() -- unlike every
|
|
128
|
+
* other domain's resolve, discuss's own (normalizeOptions, defaultActorToAgent) mutate their
|
|
129
|
+
* argument in place, and context.input must never be the object that mutation lands on.
|
|
130
|
+
*/
|
|
119
131
|
const define = (
|
|
120
132
|
action: string,
|
|
121
133
|
description: string,
|
|
122
134
|
effect: "read" | "local-write",
|
|
123
|
-
properties:
|
|
135
|
+
properties: OperationSchemaProperties,
|
|
124
136
|
required: readonly string[],
|
|
125
137
|
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
126
138
|
wrap: (raw: unknown, resolvedInput: Record<string, unknown>) => unknown = (raw) => raw,
|
|
127
139
|
): void => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
version: 1,
|
|
140
|
+
baseDefine(
|
|
141
|
+
action,
|
|
131
142
|
description,
|
|
132
|
-
input: looseObjectSchema(properties, required),
|
|
133
|
-
output: passthroughOutput,
|
|
134
|
-
permissions: ["discuss:read", "discuss:write"],
|
|
135
143
|
effect,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
OWNER,
|
|
141
|
-
bindVehicleOperation(operation, () => async (context) => {
|
|
142
|
-
const resolvedInput = resolve({ ...(context.input as Record<string, unknown>) });
|
|
143
|
-
const raw = call(`discuss.${action}`, resolvedInput);
|
|
144
|
-
return wrap(raw, resolvedInput);
|
|
145
|
-
}),
|
|
144
|
+
properties,
|
|
145
|
+
required,
|
|
146
|
+
(input) => resolve({ ...input }),
|
|
147
|
+
(resolvedInput) => wrap(call(`discuss.${action}`, resolvedInput), resolvedInput),
|
|
146
148
|
);
|
|
147
149
|
};
|
|
148
150
|
|
package/src/handlers/docs.ts
CHANGED
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
* Wraps modules/docs.ts's operation definitions. remove/restore/remove_subtree
|
|
4
4
|
* are not duplicated here -- see ./artifact-trash-vehicle.ts.
|
|
5
5
|
*/
|
|
6
|
-
import { bindVehicleOperation, defineVehicleOperation } from "@danypops/vehicle-core";
|
|
7
6
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
8
7
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
9
8
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
10
9
|
import type { AuthorityRegistry } from "../authority-registry.ts";
|
|
11
10
|
import { listDocuments } from "../domain-services.ts";
|
|
12
11
|
import { docsOperations } from "../modules/docs.ts";
|
|
13
|
-
import {
|
|
12
|
+
import { booleanProp, createOperationDefiner, numberProp, resolveArtifactIdWidened, stringProp, validationError } from "./shared.ts";
|
|
14
13
|
|
|
15
14
|
const OWNER = "docs";
|
|
16
|
-
const LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
17
15
|
|
|
18
16
|
function resolveDocId(
|
|
19
17
|
artifacts: ArtifactStore,
|
|
@@ -47,31 +45,7 @@ export function registerDocsVehicleOperations(
|
|
|
47
45
|
): void {
|
|
48
46
|
const moduleOperations = new Map(docsOperations(artifacts, scopes, authority).map((op) => [op.name, op]));
|
|
49
47
|
const call = (name: string, input: Record<string, unknown>): unknown => moduleOperations.get(name)!.execute(input);
|
|
50
|
-
|
|
51
|
-
const define = (
|
|
52
|
-
action: string,
|
|
53
|
-
description: string,
|
|
54
|
-
effect: "read" | "local-write",
|
|
55
|
-
properties: Record<string, { type: string; enum?: readonly string[] }>,
|
|
56
|
-
required: readonly string[],
|
|
57
|
-
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
58
|
-
): void => {
|
|
59
|
-
const operation = defineVehicleOperation({
|
|
60
|
-
name: `docs.${action}`,
|
|
61
|
-
version: 1,
|
|
62
|
-
description,
|
|
63
|
-
input: looseObjectSchema(properties, required),
|
|
64
|
-
output: passthroughOutput,
|
|
65
|
-
permissions: ["docs:read", "docs:write"],
|
|
66
|
-
effect,
|
|
67
|
-
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
68
|
-
limits: LIMITS,
|
|
69
|
-
});
|
|
70
|
-
registry.register(
|
|
71
|
-
OWNER,
|
|
72
|
-
bindVehicleOperation(operation, () => async (context) => call(`docs.${action}`, resolve(context.input))),
|
|
73
|
-
);
|
|
74
|
-
};
|
|
48
|
+
const define = createOperationDefiner(registry, OWNER, "docs", ["docs:read", "docs:write"], call);
|
|
75
49
|
|
|
76
50
|
define(
|
|
77
51
|
"create",
|
|
@@ -92,9 +66,9 @@ export function registerDocsVehicleOperations(
|
|
|
92
66
|
|
|
93
67
|
define(
|
|
94
68
|
"list",
|
|
95
|
-
"Lists Docs matching an optional status/text filter, scoped to project_root when given.",
|
|
69
|
+
"Lists Docs matching an optional status/text filter, scoped to project_root when given. Returns a lean summary (no body) by default -- pass full: true for the complete artifact.",
|
|
96
70
|
"read",
|
|
97
|
-
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp },
|
|
71
|
+
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp, full: booleanProp },
|
|
98
72
|
[],
|
|
99
73
|
(input) => input,
|
|
100
74
|
);
|
package/src/handlers/notes.ts
CHANGED
|
@@ -7,17 +7,14 @@
|
|
|
7
7
|
* input parsing. Resolves `name`/`target_name` to `id`/`target_id` server-side, in
|
|
8
8
|
* the same call -- avoids a separate round trip per name before the real call.
|
|
9
9
|
*/
|
|
10
|
-
import { bindVehicleOperation, defineVehicleOperation } from "@danypops/vehicle-core";
|
|
11
10
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
12
11
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
13
12
|
import { notesOperations } from "../modules/notes.ts";
|
|
14
13
|
import { NOTE_DISPOSITIONS, type Notes } from "../note/note-service.ts";
|
|
15
|
-
import {
|
|
14
|
+
import { createOperationDefiner, numberProp, resolveArtifactIdWidened, stringProp, validationError } from "./shared.ts";
|
|
16
15
|
|
|
17
16
|
const OWNER = "notes";
|
|
18
17
|
|
|
19
|
-
const LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
20
|
-
|
|
21
18
|
/** Resolves a note's id from either an explicit id or its title within projectRoot. */
|
|
22
19
|
function resolveNoteId(artifacts: ArtifactStore, notes: Notes, projectRoot: string, id: unknown, name: unknown): string {
|
|
23
20
|
if (typeof id === "string" && id.length > 0) return id;
|
|
@@ -43,31 +40,7 @@ function resolveArtifactId(artifacts: ArtifactStore, id: unknown, name: unknown)
|
|
|
43
40
|
export function registerNotesVehicleOperations(registry: VehicleRegistry, notes: Notes, artifacts: ArtifactStore): void {
|
|
44
41
|
const moduleOperations = new Map(notesOperations(notes).map((op) => [op.name, op]));
|
|
45
42
|
const call = (name: string, input: Record<string, unknown>): unknown => moduleOperations.get(name)!.execute(input);
|
|
46
|
-
|
|
47
|
-
const define = (
|
|
48
|
-
action: string,
|
|
49
|
-
description: string,
|
|
50
|
-
effect: "read" | "local-write",
|
|
51
|
-
properties: Record<string, { type: string; enum?: readonly string[] }>,
|
|
52
|
-
required: readonly string[],
|
|
53
|
-
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
54
|
-
): void => {
|
|
55
|
-
const operation = defineVehicleOperation({
|
|
56
|
-
name: `notes.${action}`,
|
|
57
|
-
version: 1,
|
|
58
|
-
description,
|
|
59
|
-
input: looseObjectSchema(properties, required),
|
|
60
|
-
output: passthroughOutput,
|
|
61
|
-
permissions: ["notes:read", "notes:write"],
|
|
62
|
-
effect,
|
|
63
|
-
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
64
|
-
limits: LIMITS,
|
|
65
|
-
});
|
|
66
|
-
registry.register(
|
|
67
|
-
OWNER,
|
|
68
|
-
bindVehicleOperation(operation, () => async (context) => call(`notes.${action}`, resolve(context.input))),
|
|
69
|
-
);
|
|
70
|
-
};
|
|
43
|
+
const define = createOperationDefiner(registry, OWNER, "notes", ["notes:read", "notes:write"], call);
|
|
71
44
|
|
|
72
45
|
define(
|
|
73
46
|
"capture",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* WithVehicleContent) built from the same execution-DAG summary pi-papyrus's hand-rolled
|
|
19
19
|
* tool used to build client-side.
|
|
20
20
|
*/
|
|
21
|
-
import { bindVehicleOperation, defineVehicleOperation, type VehicleOperationContext } from "@danypops/vehicle-core";
|
|
22
21
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
23
22
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
24
23
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
@@ -30,21 +29,21 @@ import type { TaskEventStore } from "../stores/task-event-store.ts";
|
|
|
30
29
|
import type { TaskScopeStore } from "../stores/task-scope-store.ts";
|
|
31
30
|
import type { Tasks } from "../task/task-service.ts";
|
|
32
31
|
import {
|
|
32
|
+
booleanProp,
|
|
33
33
|
buildWorkflowRunContent,
|
|
34
34
|
classifyPlaybookComposition,
|
|
35
35
|
classifySessionAuthorization,
|
|
36
36
|
classifyTaskExecutionBounds,
|
|
37
|
-
|
|
37
|
+
createOperationDefiner,
|
|
38
|
+
definePairedMutation,
|
|
38
39
|
normalizeJsonEncodedField,
|
|
39
40
|
numberProp,
|
|
40
|
-
passthroughOutput,
|
|
41
41
|
resolveArtifactIdWidened,
|
|
42
42
|
stringProp,
|
|
43
43
|
validationError,
|
|
44
44
|
} from "./shared.ts";
|
|
45
45
|
|
|
46
46
|
const OWNER = "playbooks";
|
|
47
|
-
const LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
48
47
|
|
|
49
48
|
export interface PlaybooksVehicleDeps {
|
|
50
49
|
artifacts: ArtifactStore;
|
|
@@ -80,36 +79,7 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
80
79
|
classifySessionAuthorization(() =>
|
|
81
80
|
classifyTaskExecutionBounds(() => classifyPlaybookComposition(() => moduleOperations.get(name)!.execute(input))),
|
|
82
81
|
);
|
|
83
|
-
|
|
84
|
-
const define = (
|
|
85
|
-
action: string,
|
|
86
|
-
description: string,
|
|
87
|
-
effect: "read" | "local-write",
|
|
88
|
-
properties: Record<string, { type: string; enum?: readonly string[] }>,
|
|
89
|
-
required: readonly string[],
|
|
90
|
-
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
91
|
-
execute?: (input: Record<string, unknown>, context: VehicleOperationContext<Record<string, unknown>>) => unknown,
|
|
92
|
-
): void => {
|
|
93
|
-
const operation = defineVehicleOperation({
|
|
94
|
-
name: `playbooks.${action}`,
|
|
95
|
-
version: 1,
|
|
96
|
-
description,
|
|
97
|
-
input: looseObjectSchema(properties, required),
|
|
98
|
-
output: passthroughOutput,
|
|
99
|
-
permissions: ["playbooks:read", "playbooks:write"],
|
|
100
|
-
effect,
|
|
101
|
-
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
102
|
-
limits: LIMITS,
|
|
103
|
-
});
|
|
104
|
-
registry.register(
|
|
105
|
-
OWNER,
|
|
106
|
-
bindVehicleOperation(
|
|
107
|
-
operation,
|
|
108
|
-
() => async (context) =>
|
|
109
|
-
(execute ?? ((input: Record<string, unknown>) => call(`playbooks.${action}`, input)))(resolve(context.input), context),
|
|
110
|
-
),
|
|
111
|
-
);
|
|
112
|
-
};
|
|
82
|
+
const define = createOperationDefiner(registry, OWNER, "playbooks", ["playbooks:read", "playbooks:write"], call);
|
|
113
83
|
|
|
114
84
|
define(
|
|
115
85
|
"create",
|
|
@@ -138,9 +108,9 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
138
108
|
|
|
139
109
|
define(
|
|
140
110
|
"list",
|
|
141
|
-
"Lists Playbooks matching an optional status/text filter, scoped to project_root when given.",
|
|
111
|
+
"Lists Playbooks matching an optional status/text filter, scoped to project_root when given. Returns a lean summary (no body/steps) by default -- pass full: true for the complete artifact.",
|
|
142
112
|
"read",
|
|
143
|
-
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp },
|
|
113
|
+
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp, full: booleanProp },
|
|
144
114
|
[],
|
|
145
115
|
(input) => input,
|
|
146
116
|
);
|
|
@@ -242,10 +212,13 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
242
212
|
(input) => ({ ...input, id: resolvePlaybookId(artifacts, artifactScopes, input.id, input.name) }),
|
|
243
213
|
);
|
|
244
214
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
215
|
+
const resolvePlaybookIdField = (input: Record<string, unknown>, idProp: string, nameProp: string): string =>
|
|
216
|
+
resolvePlaybookId(artifacts, artifactScopes, input[idProp], input[nameProp]);
|
|
217
|
+
|
|
218
|
+
definePairedMutation(
|
|
219
|
+
define,
|
|
220
|
+
{ idProp: "parent_id", nameProp: "parent_name" },
|
|
221
|
+
{ idProp: "child_id", nameProp: "child_name" },
|
|
249
222
|
{
|
|
250
223
|
parent_id: stringProp,
|
|
251
224
|
parent_name: stringProp,
|
|
@@ -256,38 +229,19 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
256
229
|
session_id: stringProp,
|
|
257
230
|
},
|
|
258
231
|
[],
|
|
259
|
-
|
|
260
|
-
...input,
|
|
261
|
-
parent_id: resolvePlaybookId(artifacts, artifactScopes, input.parent_id, input.parent_name),
|
|
262
|
-
child_id: resolvePlaybookId(artifacts, artifactScopes, input.child_id, input.child_name),
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
define(
|
|
267
|
-
"uncontain",
|
|
268
|
-
"Removes a parent/child Playbook nesting. Idempotent -- a no-op if the edge is already absent.",
|
|
269
|
-
"local-write",
|
|
232
|
+
resolvePlaybookIdField,
|
|
270
233
|
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
child_name: stringProp,
|
|
275
|
-
actor: stringProp,
|
|
276
|
-
source: stringProp,
|
|
277
|
-
session_id: stringProp,
|
|
234
|
+
action: "contain",
|
|
235
|
+
description:
|
|
236
|
+
"Nests a child Playbook inside a parent -- the child's steps run AFTER the parent's own. Prefer parent_name/child_name over parent_id/child_id -- resolved server-side.",
|
|
278
237
|
},
|
|
279
|
-
|
|
280
|
-
(input) => ({
|
|
281
|
-
...input,
|
|
282
|
-
parent_id: resolvePlaybookId(artifacts, artifactScopes, input.parent_id, input.parent_name),
|
|
283
|
-
child_id: resolvePlaybookId(artifacts, artifactScopes, input.child_id, input.child_name),
|
|
284
|
-
}),
|
|
238
|
+
{ action: "uncontain", description: "Removes a parent/child Playbook nesting. Idempotent -- a no-op if the edge is already absent." },
|
|
285
239
|
);
|
|
286
240
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"
|
|
241
|
+
definePairedMutation(
|
|
242
|
+
define,
|
|
243
|
+
{ idProp: "id", nameProp: "name" },
|
|
244
|
+
{ idProp: "dependency_id", nameProp: "dependency_name" },
|
|
291
245
|
{
|
|
292
246
|
id: stringProp,
|
|
293
247
|
name: stringProp,
|
|
@@ -298,31 +252,12 @@ export function registerPlaybooksVehicleOperations(registry: VehicleRegistry, de
|
|
|
298
252
|
session_id: stringProp,
|
|
299
253
|
},
|
|
300
254
|
[],
|
|
301
|
-
|
|
302
|
-
...input,
|
|
303
|
-
id: resolvePlaybookId(artifacts, artifactScopes, input.id, input.name),
|
|
304
|
-
dependency_id: resolvePlaybookId(artifacts, artifactScopes, input.dependency_id, input.dependency_name),
|
|
305
|
-
}),
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
define(
|
|
309
|
-
"undepend",
|
|
310
|
-
"Removes a Playbook dependency. Idempotent -- a no-op if the edge is already absent.",
|
|
311
|
-
"local-write",
|
|
255
|
+
resolvePlaybookIdField,
|
|
312
256
|
{
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
dependency_name: stringProp,
|
|
317
|
-
actor: stringProp,
|
|
318
|
-
source: stringProp,
|
|
319
|
-
session_id: stringProp,
|
|
257
|
+
action: "depend",
|
|
258
|
+
description:
|
|
259
|
+
"Chains a prerequisite Playbook before another -- it must fully complete FIRST. Prefer dependency_name over dependency_id.",
|
|
320
260
|
},
|
|
321
|
-
|
|
322
|
-
(input) => ({
|
|
323
|
-
...input,
|
|
324
|
-
id: resolvePlaybookId(artifacts, artifactScopes, input.id, input.name),
|
|
325
|
-
dependency_id: resolvePlaybookId(artifacts, artifactScopes, input.dependency_id, input.dependency_name),
|
|
326
|
-
}),
|
|
261
|
+
{ action: "undepend", description: "Removes a Playbook dependency. Idempotent -- a no-op if the edge is already absent." },
|
|
327
262
|
);
|
|
328
263
|
}
|
package/src/handlers/rules.ts
CHANGED
|
@@ -4,16 +4,14 @@
|
|
|
4
4
|
* composition-root-only concern, absent here too). remove/restore/remove_subtree
|
|
5
5
|
* are not duplicated here -- see ./artifact-trash-vehicle.ts.
|
|
6
6
|
*/
|
|
7
|
-
import { bindVehicleOperation, defineVehicleOperation } from "@danypops/vehicle-core";
|
|
8
7
|
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
9
8
|
import type { ArtifactScopeStore } from "../artifact/artifact-scope-store.ts";
|
|
10
9
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
11
10
|
import { listRules } from "../domain-services.ts";
|
|
12
11
|
import { rulesOperations } from "../modules/rules.ts";
|
|
13
|
-
import {
|
|
12
|
+
import { booleanProp, createOperationDefiner, numberProp, resolveArtifactIdWidened, stringProp, validationError } from "./shared.ts";
|
|
14
13
|
|
|
15
14
|
const OWNER = "rules";
|
|
16
|
-
const LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
17
15
|
|
|
18
16
|
/** Resolves a rule's id from either an explicit id or its title, widened past project_root when unscoped-vs-scoped search finds nothing. */
|
|
19
17
|
function resolveRuleId(
|
|
@@ -47,31 +45,7 @@ function resolveTaskId(artifacts: ArtifactStore, _projectRoot: string | undefine
|
|
|
47
45
|
export function registerRulesVehicleOperations(registry: VehicleRegistry, artifacts: ArtifactStore, scopes: ArtifactScopeStore): void {
|
|
48
46
|
const moduleOperations = new Map(rulesOperations(artifacts, scopes).map((op) => [op.name, op]));
|
|
49
47
|
const call = (name: string, input: Record<string, unknown>): unknown => moduleOperations.get(name)!.execute(input);
|
|
50
|
-
|
|
51
|
-
const define = (
|
|
52
|
-
action: string,
|
|
53
|
-
description: string,
|
|
54
|
-
effect: "read" | "local-write",
|
|
55
|
-
properties: Record<string, { type: string; enum?: readonly string[] }>,
|
|
56
|
-
required: readonly string[],
|
|
57
|
-
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
58
|
-
): void => {
|
|
59
|
-
const operation = defineVehicleOperation({
|
|
60
|
-
name: `rules.${action}`,
|
|
61
|
-
version: 1,
|
|
62
|
-
description,
|
|
63
|
-
input: looseObjectSchema(properties, required),
|
|
64
|
-
output: passthroughOutput,
|
|
65
|
-
permissions: ["rules:read", "rules:write"],
|
|
66
|
-
effect,
|
|
67
|
-
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
68
|
-
limits: LIMITS,
|
|
69
|
-
});
|
|
70
|
-
registry.register(
|
|
71
|
-
OWNER,
|
|
72
|
-
bindVehicleOperation(operation, () => async (context) => call(`rules.${action}`, resolve(context.input))),
|
|
73
|
-
);
|
|
74
|
-
};
|
|
48
|
+
const define = createOperationDefiner(registry, OWNER, "rules", ["rules:read", "rules:write"], call);
|
|
75
49
|
|
|
76
50
|
define(
|
|
77
51
|
"create",
|
|
@@ -96,9 +70,9 @@ export function registerRulesVehicleOperations(registry: VehicleRegistry, artifa
|
|
|
96
70
|
|
|
97
71
|
define(
|
|
98
72
|
"list",
|
|
99
|
-
"Lists Rules matching an optional status/text filter, scoped to project_root when given.",
|
|
73
|
+
"Lists Rules matching an optional status/text filter, scoped to project_root when given. Returns a lean summary (no condition/action/body) by default -- pass full: true for the complete artifact.",
|
|
100
74
|
"read",
|
|
101
|
-
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp },
|
|
75
|
+
{ status: stringProp, text: stringProp, limit: numberProp, project_root: stringProp, full: booleanProp },
|
|
102
76
|
[],
|
|
103
77
|
(input) => input,
|
|
104
78
|
);
|
package/src/handlers/shared.ts
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
* VehicleRegistry projection (notes-vehicle.ts, rules-vehicle.ts, docs-vehicle.ts,
|
|
4
4
|
* artifact-trash-vehicle.ts).
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
bindVehicleOperation,
|
|
8
|
+
defineVehicleOperation,
|
|
9
|
+
defineVehicleSchema,
|
|
10
|
+
type VehicleContentBlock,
|
|
11
|
+
VehicleError,
|
|
12
|
+
type VehicleOperationContext,
|
|
13
|
+
type VehicleSchemaCodec,
|
|
14
|
+
} from "@danypops/vehicle-core";
|
|
15
|
+
import type { VehicleRegistry } from "@danypops/vehicle-server";
|
|
7
16
|
import type { Artifact } from "../artifact/artifact.ts";
|
|
8
17
|
import type { ArtifactStore } from "../artifact/artifact-store.ts";
|
|
9
18
|
import { PlaybookCompositionError } from "../playbook/playbook-definition.ts";
|
|
@@ -48,6 +57,7 @@ export const passthroughOutput: VehicleSchemaCodec<unknown> = defineVehicleSchem
|
|
|
48
57
|
|
|
49
58
|
export const stringProp = { type: "string" } as const;
|
|
50
59
|
export const numberProp = { type: "number" } as const;
|
|
60
|
+
export const booleanProp = { type: "boolean" } as const;
|
|
51
61
|
|
|
52
62
|
/**
|
|
53
63
|
* A plain `throw new Error(...)` inside any resolve()/execute() step here is caught by
|
|
@@ -223,3 +233,84 @@ export function buildWorkflowRunContent(
|
|
|
223
233
|
].join("\n");
|
|
224
234
|
return { type: "text", text };
|
|
225
235
|
}
|
|
236
|
+
|
|
237
|
+
export type OperationSchemaProperties = Record<string, { type: string; enum?: readonly string[] }>;
|
|
238
|
+
|
|
239
|
+
export type DefineOperation = (
|
|
240
|
+
action: string,
|
|
241
|
+
description: string,
|
|
242
|
+
effect: "read" | "local-write",
|
|
243
|
+
properties: OperationSchemaProperties,
|
|
244
|
+
required: readonly string[],
|
|
245
|
+
resolve: (input: Record<string, unknown>) => Record<string, unknown>,
|
|
246
|
+
execute?: (input: Record<string, unknown>, context: VehicleOperationContext<Record<string, unknown>>) => unknown,
|
|
247
|
+
) => void;
|
|
248
|
+
|
|
249
|
+
const STANDARD_OPERATION_LIMITS = { defaultTimeoutMs: 5_000, maxTimeoutMs: 30_000, maxRequestBytes: 65_536, maxResponseBytes: 262_144 };
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Every *-vehicle.ts handler wires up the identical defineVehicleOperation +
|
|
253
|
+
* bindVehicleOperation + registry.register triple per action, differing only in
|
|
254
|
+
* owner/domain-prefix/permissions and (for tasks/playbooks) a real execute() override
|
|
255
|
+
* in place of the default "call the wrapped module operation" behavior. One factory,
|
|
256
|
+
* called once per domain, replaces that repetition.
|
|
257
|
+
*/
|
|
258
|
+
export function createOperationDefiner(
|
|
259
|
+
registry: VehicleRegistry,
|
|
260
|
+
owner: string,
|
|
261
|
+
domain: string,
|
|
262
|
+
permissions: readonly [string, string],
|
|
263
|
+
defaultCall: (name: string, input: Record<string, unknown>) => unknown,
|
|
264
|
+
): DefineOperation {
|
|
265
|
+
return (action, description, effect, properties, required, resolve, execute) => {
|
|
266
|
+
const operation = defineVehicleOperation({
|
|
267
|
+
name: `${domain}.${action}`,
|
|
268
|
+
version: 1,
|
|
269
|
+
description,
|
|
270
|
+
input: looseObjectSchema(properties, required),
|
|
271
|
+
output: passthroughOutput,
|
|
272
|
+
permissions: [...permissions],
|
|
273
|
+
effect,
|
|
274
|
+
idempotency: { mode: effect === "read" ? "safe" : "unsafe" },
|
|
275
|
+
limits: STANDARD_OPERATION_LIMITS,
|
|
276
|
+
});
|
|
277
|
+
registry.register(
|
|
278
|
+
owner,
|
|
279
|
+
bindVehicleOperation(
|
|
280
|
+
operation,
|
|
281
|
+
() => async (context) =>
|
|
282
|
+
(execute ?? ((input: Record<string, unknown>) => defaultCall(`${domain}.${action}`, input)))(resolve(context.input), context),
|
|
283
|
+
),
|
|
284
|
+
);
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface PairedMutationFieldSpec {
|
|
289
|
+
idProp: string;
|
|
290
|
+
nameProp: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* depend/undepend and contain/uncontain (tasks-vehicle.ts, playbooks-vehicle.ts) share
|
|
295
|
+
* one shape: two id-or-name fields resolved the same way for both the add and the
|
|
296
|
+
* remove action, differing only in action name/description. One call replaces two
|
|
297
|
+
* near-identical define() invocations.
|
|
298
|
+
*/
|
|
299
|
+
export function definePairedMutation(
|
|
300
|
+
define: DefineOperation,
|
|
301
|
+
first: PairedMutationFieldSpec,
|
|
302
|
+
second: PairedMutationFieldSpec,
|
|
303
|
+
properties: OperationSchemaProperties,
|
|
304
|
+
required: readonly string[],
|
|
305
|
+
resolveId: (input: Record<string, unknown>, idProp: string, nameProp: string) => string,
|
|
306
|
+
add: { action: string; description: string },
|
|
307
|
+
remove: { action: string; description: string },
|
|
308
|
+
): void {
|
|
309
|
+
const resolve = (input: Record<string, unknown>): Record<string, unknown> => ({
|
|
310
|
+
...input,
|
|
311
|
+
[first.idProp]: resolveId(input, first.idProp, first.nameProp),
|
|
312
|
+
[second.idProp]: resolveId(input, second.idProp, second.nameProp),
|
|
313
|
+
});
|
|
314
|
+
define(add.action, add.description, "local-write", properties, required, resolve);
|
|
315
|
+
define(remove.action, remove.description, "local-write", properties, required, resolve);
|
|
316
|
+
}
|