@danypops/papyrus 0.41.0 → 0.42.1

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.
Files changed (78) hide show
  1. package/README.md +8 -11
  2. package/package.json +2 -2
  3. package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
  4. package/src/adapters/sqlite-artifact-store.ts +7 -5
  5. package/src/adapters/sqlite-discussion-round-store.ts +26 -17
  6. package/src/adapters/sqlite-gate-runner.ts +1 -1
  7. package/src/adapters/sqlite-graph-projection-store.ts +14 -10
  8. package/src/adapters/sqlite-log-store.ts +36 -17
  9. package/src/adapters/sqlite-note-event-store.ts +20 -16
  10. package/src/adapters/sqlite-session-identity-store.ts +13 -7
  11. package/src/adapters/sqlite-task-event-store.ts +29 -21
  12. package/src/adapters/sqlite-task-focus-store.ts +36 -10
  13. package/src/adapters/sqlite-task-lease-store.ts +12 -6
  14. package/src/adapters/sqlite-task-scope-store.ts +25 -14
  15. package/src/artifact-relationship-view.ts +4 -4
  16. package/src/artifact-subtree.ts +4 -2
  17. package/src/authority-registry.ts +2 -1
  18. package/src/cli.ts +794 -354
  19. package/src/client.ts +6 -3
  20. package/src/constants.ts +34 -56
  21. package/src/daemon-state.ts +4 -12
  22. package/src/daemon.ts +31 -9
  23. package/src/db.ts +153 -105
  24. package/src/discussion-service.ts +109 -44
  25. package/src/domain/artifact-event.ts +18 -5
  26. package/src/domain/artifact.ts +3 -1
  27. package/src/domain/blueprint-definition.ts +268 -0
  28. package/src/domain/checklist.ts +20 -17
  29. package/src/domain/discussion.ts +37 -18
  30. package/src/domain/gate.ts +7 -7
  31. package/src/domain/log-entry.ts +1 -1
  32. package/src/domain/note-event.ts +20 -7
  33. package/src/domain/task-event.ts +17 -7
  34. package/src/domain-services.ts +362 -288
  35. package/src/graph-projection-service.ts +34 -8
  36. package/src/id-migration.ts +17 -4
  37. package/src/index.ts +16 -11
  38. package/src/log-service.ts +6 -5
  39. package/src/log.ts +19 -0
  40. package/src/modules/discuss.ts +63 -28
  41. package/src/modules/docs.ts +74 -17
  42. package/src/modules/graph-projection.ts +20 -9
  43. package/src/modules/logs.ts +34 -22
  44. package/src/modules/notes.ts +66 -28
  45. package/src/modules/playbooks.ts +93 -32
  46. package/src/modules/rules.ts +57 -15
  47. package/src/modules/session-identity.ts +6 -2
  48. package/src/modules/tasks.ts +142 -67
  49. package/src/note-service.ts +11 -7
  50. package/src/ops.ts +134 -69
  51. package/src/playbook-definition.ts +124 -39
  52. package/src/playbook-execution.ts +18 -25
  53. package/src/ports/artifact-scope-store.ts +1 -1
  54. package/src/ports/note-event-store.ts +6 -4
  55. package/src/ports/task-event-store.ts +9 -6
  56. package/src/ports/task-focus-store.ts +17 -4
  57. package/src/ports/task-lease-store.ts +9 -4
  58. package/src/ports/task-scope-store.ts +3 -1
  59. package/src/service.ts +148 -110
  60. package/src/session-identity-service.ts +10 -2
  61. package/src/task-context.ts +28 -16
  62. package/src/task-execution.ts +4 -12
  63. package/src/task-graph-view.ts +12 -12
  64. package/src/task-relationship-view.ts +1 -3
  65. package/src/task-service.ts +168 -73
  66. package/src/vehicle/artifact-trash-vehicle.ts +26 -14
  67. package/src/vehicle/artifact-vehicle-shared.ts +32 -13
  68. package/src/vehicle/docs-vehicle.ts +50 -18
  69. package/src/vehicle/notes-vehicle.ts +26 -8
  70. package/src/vehicle/papyrus-vehicle.ts +16 -8
  71. package/src/vehicle/playbooks-vehicle.ts +88 -19
  72. package/src/vehicle/rules-vehicle.ts +58 -21
  73. package/src/vehicle/tasks-vehicle.ts +366 -54
  74. package/src/version.ts +1 -1
  75. package/src/workflow-execution.ts +198 -109
  76. package/src/domain/skill-definition.ts +0 -270
  77. package/src/modules/skills.ts +0 -158
  78. package/src/vehicle/skills-vehicle.ts +0 -194
package/src/service.ts CHANGED
@@ -1,68 +1,77 @@
1
- import { SERVICE_MAX_BODY_BYTES, SQLITE_SCHEMA_VERSION } from "./constants.ts";
2
- import { VERSION } from "./version.ts";
3
- import { migrateDb, openDb, schemaVersion } from "./db.ts";
4
- import { removeArtifactSubtree } from "./artifact-subtree.ts";
1
+ import type { VehicleRegistry } from "@danypops/vehicle-server";
2
+ import { createVehicleHttpApp } from "@danypops/vehicle-server/http";
3
+ import type { Logger } from "@danypops/vehicle-server/logging";
4
+ import { SQLiteArtifactScopeStore } from "./adapters/sqlite-artifact-scope-store.ts";
5
5
  import { SQLiteArtifactStore } from "./adapters/sqlite-artifact-store.ts";
6
+ import { SQLiteDiscussionRoundStore } from "./adapters/sqlite-discussion-round-store.ts";
6
7
  import { SQLiteGateRunner } from "./adapters/sqlite-gate-runner.ts";
7
- import { SQLiteArtifactScopeStore } from "./adapters/sqlite-artifact-scope-store.ts";
8
8
  import { SQLiteGraphProjectionStore } from "./adapters/sqlite-graph-projection-store.ts";
9
+ import { SQLiteLogStore } from "./adapters/sqlite-log-store.ts";
10
+ import { SQLiteNoteEventStore } from "./adapters/sqlite-note-event-store.ts";
11
+ import { SQLiteSessionIdentityStore } from "./adapters/sqlite-session-identity-store.ts";
12
+ import { SQLiteTaskEventStore } from "./adapters/sqlite-task-event-store.ts";
9
13
  import { SQLiteTaskFocusStore } from "./adapters/sqlite-task-focus-store.ts";
10
14
  import { SQLiteTaskLeaseStore } from "./adapters/sqlite-task-lease-store.ts";
11
- import { SQLiteTaskEventStore } from "./adapters/sqlite-task-event-store.ts";
12
- import { SQLiteNoteEventStore } from "./adapters/sqlite-note-event-store.ts";
13
15
  import { SQLiteTaskScopeStore } from "./adapters/sqlite-task-scope-store.ts";
14
- import { SQLiteSessionIdentityStore } from "./adapters/sqlite-session-identity-store.ts";
16
+ import { removeArtifactSubtree } from "./artifact-subtree.ts";
17
+ import { type AuthorityClaim, AuthorityRegistry, AuthorizedArtifactWriter } from "./authority-registry.ts";
18
+ import { SERVICE_MAX_BODY_BYTES, SQLITE_SCHEMA_VERSION } from "./constants.ts";
19
+ import { migrateDb, openDb, schemaVersion } from "./db.ts";
20
+ import { Discussions } from "./discussion-service.ts";
15
21
  import type { CreateArtifactInput } from "./domain/artifact.ts";
16
- import { AuthorityRegistry, AuthorizedArtifactWriter, type AuthorityClaim } from "./authority-registry.ts";
17
22
  import type { TaskEventContext } from "./domain/task-event.ts";
18
23
  import type { TaskViewMode } from "./domain/task-scope.ts";
19
- import type { ArtifactStore } from "./ports/artifact-store.ts";
24
+ import { listInjectableRules } from "./domain-services.ts";
25
+ import { Logs } from "./log-service.ts";
26
+ import { OperationRegistry } from "./module-registry.ts";
27
+ import { DISCUSS_OPERATION_NAMES, discussOperations } from "./modules/discuss.ts";
28
+ import { DOCS_OPERATION_NAMES, docsOperations } from "./modules/docs.ts";
29
+ import { GRAPH_PROJECTION_OPERATION_NAMES, graphProjectionOperations } from "./modules/graph-projection.ts";
30
+ import { LOGS_OPERATION_NAMES, logsOperations } from "./modules/logs.ts";
31
+ import { NOTES_OPERATION_NAMES, notesOperations } from "./modules/notes.ts";
32
+ import { PLAYBOOKS_OPERATION_NAMES, playbooksOperations } from "./modules/playbooks.ts";
33
+ import { RULES_OPERATION_NAMES, rulesOperations } from "./modules/rules.ts";
34
+ import { SESSION_IDENTITY_OPERATION_NAMES, sessionIdentityOperations } from "./modules/session-identity.ts";
35
+ import { TASKS_OPERATION_NAMES, tasksOperations } from "./modules/tasks.ts";
36
+ import { NOTE_SUBTYPE, Notes } from "./note-service.ts";
20
37
  import type { ArtifactEventReader } from "./ports/artifact-event-reader.ts";
38
+ import type { ArtifactStore } from "./ports/artifact-store.ts";
21
39
  import type { ArtifactTrashStore } from "./ports/artifact-trash-store.ts";
22
40
  import type { GateRunner } from "./ports/gate-runner.ts";
23
41
  import type { TaskEventStore } from "./ports/task-event-store.ts";
24
42
  import type { TaskScopeStore } from "./ports/task-scope-store.ts";
25
- import { Tasks, type TaskStatus } from "./task-service.ts";
26
- import {
27
- listInjectableRules,
28
- } from "./domain-services.ts";
29
- import { Notes, NOTE_SUBTYPE } from "./note-service.ts";
43
+ import { InvalidSessionSecretError, SessionIdentity } from "./session-identity-service.ts";
44
+ import { type TaskStatus, Tasks } from "./task-service.ts";
30
45
  import { createPapyrusVehicleRegistry } from "./vehicle/papyrus-vehicle.ts";
31
- import type { VehicleRegistry } from "@danypops/vehicle-server";
32
- import { createVehicleHttpApp } from "@danypops/vehicle-server/http";
33
- import { Logs } from "./log-service.ts";
34
- import { SQLiteLogStore } from "./adapters/sqlite-log-store.ts";
35
- import { SessionIdentity, InvalidSessionSecretError } from "./session-identity-service.ts";
36
- import { OperationRegistry } from "./module-registry.ts";
37
- import { docsOperations, DOCS_OPERATION_NAMES } from "./modules/docs.ts";
38
- import { graphProjectionOperations, GRAPH_PROJECTION_OPERATION_NAMES } from "./modules/graph-projection.ts";
39
- import { logsOperations, LOGS_OPERATION_NAMES } from "./modules/logs.ts";
40
- import { notesOperations, NOTES_OPERATION_NAMES } from "./modules/notes.ts";
41
- import { rulesOperations, RULES_OPERATION_NAMES } from "./modules/rules.ts";
42
- import { instantiateSkillOrTemplate, skillsOperations, SKILLS_OPERATION_NAMES } from "./modules/skills.ts";
43
- import { playbooksOperations, PLAYBOOKS_OPERATION_NAMES } from "./modules/playbooks.ts";
44
- import { sessionIdentityOperations, SESSION_IDENTITY_OPERATION_NAMES } from "./modules/session-identity.ts";
45
- import { discussOperations, DISCUSS_OPERATION_NAMES } from "./modules/discuss.ts";
46
- import { tasksOperations, TASKS_OPERATION_NAMES } from "./modules/tasks.ts";
47
- import { Discussions } from "./discussion-service.ts";
48
- import { SQLiteDiscussionRoundStore } from "./adapters/sqlite-discussion-round-store.ts";
46
+ import { VERSION } from "./version.ts";
49
47
 
50
48
  /**
51
49
  * Operations with no registered module: the generic, cross-cutting kernel surface
52
50
  * (artifact create/query/show, graph link/unlink/tree/status/history, gates run --
53
51
  * no domain owns creation/linking/traversal for every kind, the same way system.migrate
54
- * has no owning module) and two permanent composition-root exceptions (rules.injectable
55
- * needs tasks.active(); skills.instantiate branches into tasks.create()) -- see
56
- * src/modules/rules.ts and src/modules/skills.ts's module comments. Discourse's own
52
+ * has no owning module) and one permanent composition-root exception (rules.injectable
53
+ * needs tasks.active()) -- see src/modules/rules.ts's own module comment. Discourse's own
57
54
  * Papyrus-embedded storage (discourse.store) was removed entirely -- zero real callers
58
55
  * were ever confirmed against it; Discourse's real home is the standalone
59
56
  * @danypops/discourse package plus host adapters.
60
57
  */
61
58
  const COMPOSITION_ROOT_OPERATION_NAMES = [
62
- "system.migrate", "artifact.create", "artifact.query", "artifact.show",
63
- "artifact.remove", "artifact.remove_subtree", "artifact.restore", "artifact.trash_status", "artifact.trash_list",
64
- "graph.link", "graph.unlink", "graph.tree", "graph.status", "graph.history", "gates.run",
65
- "rules.injectable", "skills.instantiate",
59
+ "system.migrate",
60
+ "artifact.create",
61
+ "artifact.query",
62
+ "artifact.show",
63
+ "artifact.remove",
64
+ "artifact.remove_subtree",
65
+ "artifact.restore",
66
+ "artifact.trash_status",
67
+ "artifact.trash_list",
68
+ "graph.link",
69
+ "graph.unlink",
70
+ "graph.tree",
71
+ "graph.status",
72
+ "graph.history",
73
+ "gates.run",
74
+ "rules.injectable",
66
75
  ] as const;
67
76
 
68
77
  /**
@@ -80,7 +89,6 @@ export const EXPECTED_OPERATION_NAMES = [
80
89
  ...DOCS_OPERATION_NAMES,
81
90
  ...NOTES_OPERATION_NAMES,
82
91
  ...RULES_OPERATION_NAMES,
83
- ...SKILLS_OPERATION_NAMES,
84
92
  ...PLAYBOOKS_OPERATION_NAMES,
85
93
  ...GRAPH_PROJECTION_OPERATION_NAMES,
86
94
  ...LOGS_OPERATION_NAMES,
@@ -88,7 +96,7 @@ export const EXPECTED_OPERATION_NAMES = [
88
96
  ...DISCUSS_OPERATION_NAMES,
89
97
  ] as const;
90
98
 
91
- export type OperationName = typeof EXPECTED_OPERATION_NAMES[number];
99
+ export type OperationName = (typeof EXPECTED_OPERATION_NAMES)[number];
92
100
  type OperationInput = Record<string, unknown>;
93
101
  type OperationHandler = (input: OperationInput) => unknown;
94
102
 
@@ -110,7 +118,7 @@ function optionalString(input: OperationInput, key: string): string | undefined
110
118
  return value;
111
119
  }
112
120
 
113
- function optionalStringArray(input: OperationInput, key: string): string[] | undefined {
121
+ function _optionalStringArray(input: OperationInput, key: string): string[] | undefined {
114
122
  const value = input[key];
115
123
  if (value === undefined) return undefined;
116
124
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${key} must be an array of strings`);
@@ -131,9 +139,9 @@ function normalizeCreateInput(input: OperationInput): CreateArtifactInput {
131
139
 
132
140
  function templateSubtype(artifacts: ArtifactStore, templateId: string | undefined): string | undefined {
133
141
  if (!templateId) return undefined;
134
- const defaults = artifacts.get(templateId)?.extra["defaults"];
142
+ const defaults = artifacts.get(templateId)?.extra.defaults;
135
143
  if (typeof defaults !== "object" || defaults === null || Array.isArray(defaults)) return undefined;
136
- const subtype = (defaults as Record<string, unknown>)["subtype"];
144
+ const subtype = (defaults as Record<string, unknown>).subtype;
137
145
  return typeof subtype === "string" ? subtype : undefined;
138
146
  }
139
147
 
@@ -170,11 +178,11 @@ const tasksAuthorityClaim: AuthorityClaim = {
170
178
 
171
179
  /**
172
180
  * The same status-bypass protection Tasks and Notes already have, extended to every other kind
173
- * with its own validated transition set (Doc's draft/active/archived, Rule/Skill/Playbook's
181
+ * with its own validated transition set (Doc's draft/active/archived, Rule/Playbook's
174
182
  * active/deprecated) -- graph.status previously let a caller jump straight to any status string,
175
183
  * skipping e.g. Doc's draft-must-go-through-active-before-archived rule entirely.
176
184
  */
177
- function lifecycleAuthorityClaim(owner: "docs" | "rules" | "skills" | "playbooks", kind: string): AuthorityClaim {
185
+ function lifecycleAuthorityClaim(owner: "docs" | "rules" | "playbooks", kind: string): AuthorityClaim {
178
186
  return {
179
187
  owner,
180
188
  matchesArtifact: (candidateKind, subtype) => candidateKind === kind && !(kind === "doc" && subtype === NOTE_SUBTYPE),
@@ -190,7 +198,6 @@ export function createAuthorityRegistry(): AuthorityRegistry {
190
198
  tasksAuthorityClaim,
191
199
  lifecycleAuthorityClaim("docs", "doc"),
192
200
  lifecycleAuthorityClaim("rules", "rule"),
193
- lifecycleAuthorityClaim("skills", "skill"),
194
201
  lifecycleAuthorityClaim("playbooks", "playbook"),
195
202
  ]);
196
203
  return authority;
@@ -229,9 +236,9 @@ function handlers(
229
236
  artifacts: ArtifactStore & ArtifactTrashStore & ArtifactEventReader,
230
237
  gates: GateRunner,
231
238
  tasks: Tasks,
232
- notes: Notes,
233
- events: TaskEventStore,
234
- scopes: TaskScopeStore,
239
+ _notes: Notes,
240
+ _events: TaskEventStore,
241
+ _scopes: TaskScopeStore,
235
242
  migrate: () => unknown,
236
243
  moduleRegistry: OperationRegistry,
237
244
  authority: AuthorityRegistry,
@@ -241,7 +248,10 @@ function handlers(
241
248
  // these six entries stay in this completeness-checked table only as a thin forward so
242
249
  // `Record<OperationName, OperationHandler>` still guarantees every operation has an entry
243
250
  // at compile time. The actual notes.* logic now lives in the module, not here.
244
- const forwardToModule = (name: OperationName): OperationHandler => (input) => moduleRegistry.get(name)!.execute(input);
251
+ const forwardToModule =
252
+ (name: OperationName): OperationHandler =>
253
+ (input) =>
254
+ moduleRegistry.get(name)!.execute(input);
245
255
  const eventContext = (input: OperationInput): TaskEventContext => ({
246
256
  actor: optionalString(input, "actor"),
247
257
  source: optionalString(input, "source"),
@@ -268,30 +278,41 @@ function handlers(
268
278
  "system.migrate": () => migrate(),
269
279
  "artifact.create": (input) => {
270
280
  const normalized = normalizeCreateInput(input);
271
- authority.requireArtifactAllowed(normalized.kind, normalized.subtype ?? templateSubtype(artifacts, normalized.templateId), "create", GENERIC_CALLER);
281
+ authority.requireArtifactAllowed(
282
+ normalized.kind,
283
+ normalized.subtype ?? templateSubtype(artifacts, normalized.templateId),
284
+ "create",
285
+ GENERIC_CALLER,
286
+ );
272
287
  authority.requireArtifactAllowed(normalized.kind, normalized.subtype, "create", GENERIC_CALLER);
273
288
  if (normalized.kind !== "task") return artifacts.create(normalized);
274
- return tasks.create({
275
- id: normalized.id,
276
- title: string(input, "title"),
277
- body: normalized.body,
278
- subtype: normalized.subtype,
279
- status: normalized.status as TaskStatus | undefined,
280
- labels: normalized.labels,
281
- extra: normalized.extra,
282
- templateId: normalized.templateId,
283
- projectRoot: string(input, "project_root"),
284
- projectSource: "cwd",
285
- }, eventContextFor(input, "artifact-api"));
289
+ return tasks.create(
290
+ {
291
+ id: normalized.id,
292
+ title: string(input, "title"),
293
+ body: normalized.body,
294
+ subtype: normalized.subtype,
295
+ status: normalized.status as TaskStatus | undefined,
296
+ labels: normalized.labels,
297
+ extra: normalized.extra,
298
+ templateId: normalized.templateId,
299
+ projectRoot: string(input, "project_root"),
300
+ projectSource: "cwd",
301
+ },
302
+ eventContextFor(input, "artifact-api"),
303
+ );
286
304
  },
287
305
  "artifact.query": (input) => artifacts.query(input),
288
- "artifact.show": (input) => artifacts.get(string(input, "id"), {
289
- tree: input["tree"] === true,
290
- depth: optionalNumber(input, "depth"),
291
- maxNodes: optionalNumber(input, "max_nodes") ?? optionalNumber(input, "maxNodes"),
292
- }),
293
- "artifact.remove": (input) => artifacts.trash(string(input, "id"), { reason: optionalString(input, "reason"), context: eventContext(input) }),
294
- "artifact.remove_subtree": (input) => removeArtifactSubtree(artifacts, string(input, "id"), { reason: optionalString(input, "reason"), context: eventContext(input) }),
306
+ "artifact.show": (input) =>
307
+ artifacts.get(string(input, "id"), {
308
+ tree: input.tree === true,
309
+ depth: optionalNumber(input, "depth"),
310
+ maxNodes: optionalNumber(input, "max_nodes") ?? optionalNumber(input, "maxNodes"),
311
+ }),
312
+ "artifact.remove": (input) =>
313
+ artifacts.trash(string(input, "id"), { reason: optionalString(input, "reason"), context: eventContext(input) }),
314
+ "artifact.remove_subtree": (input) =>
315
+ removeArtifactSubtree(artifacts, string(input, "id"), { reason: optionalString(input, "reason"), context: eventContext(input) }),
295
316
  "artifact.restore": (input) => artifacts.restore(string(input, "id"), eventContext(input)),
296
317
  "artifact.trash_status": (input) => artifacts.trashStatus(string(input, "id")),
297
318
  "artifact.trash_list": () => artifacts.listTrash(),
@@ -314,7 +335,11 @@ function handlers(
314
335
  genericWriter.checkLink({ from, relation, to });
315
336
  let removed: boolean;
316
337
  if (relation === "depends_on" && artifacts.get(from)?.kind === "task" && artifacts.get(to)?.kind === "task") {
317
- const before = tasks.graph().nodes.find((node) => node.task.id === from)?.dependencyIds.includes(to) ?? false;
338
+ const before =
339
+ tasks
340
+ .graph()
341
+ .nodes.find((node) => node.task.id === from)
342
+ ?.dependencyIds.includes(to) ?? false;
318
343
  tasks.undepend(from, to, eventContext(input));
319
344
  removed = before;
320
345
  } else {
@@ -322,29 +347,29 @@ function handlers(
322
347
  }
323
348
  return { removed };
324
349
  },
325
- "graph.tree": (input) => artifacts.get(string(input, "id"), {
326
- tree: true,
327
- depth: optionalNumber(input, "depth"),
328
- maxNodes: optionalNumber(input, "max_nodes") ?? optionalNumber(input, "maxNodes"),
329
- }),
350
+ "graph.tree": (input) =>
351
+ artifacts.get(string(input, "id"), {
352
+ tree: true,
353
+ depth: optionalNumber(input, "depth"),
354
+ maxNodes: optionalNumber(input, "max_nodes") ?? optionalNumber(input, "maxNodes"),
355
+ }),
330
356
  "graph.status": (input) => genericWriter.setStatus(string(input, "id"), string(input, "status"), eventContext(input)),
331
- "graph.history": (input) => artifacts.events({
332
- artifactId: optionalString(input, "id"),
333
- actor: optionalString(input, "actor"),
334
- sessionId: optionalString(input, "session_id") ?? optionalString(input, "sessionId"),
335
- since: optionalString(input, "since"),
336
- limit: optionalNumber(input, "limit"),
337
- cursor: optionalNumber(input, "cursor"),
338
- direction: optionalString(input, "direction") as "asc" | "desc" | undefined,
339
- }),
357
+ "graph.history": (input) =>
358
+ artifacts.events({
359
+ artifactId: optionalString(input, "id"),
360
+ actor: optionalString(input, "actor"),
361
+ sessionId: optionalString(input, "session_id") ?? optionalString(input, "sessionId"),
362
+ since: optionalString(input, "since"),
363
+ limit: optionalNumber(input, "limit"),
364
+ cursor: optionalNumber(input, "cursor"),
365
+ direction: optionalString(input, "direction") as "asc" | "desc" | undefined,
366
+ }),
340
367
  "gates.run": (input) => {
341
368
  const id = string(input, "id");
342
- return artifacts.get(id)?.kind === "task"
343
- ? tasks.runGates(id, eventContextFor(input, "gates-api"))
344
- : gates.runAsync(id);
369
+ return artifacts.get(id)?.kind === "task" ? tasks.runGates(id, eventContextFor(input, "gates-api")) : gates.runAsync(id);
345
370
  },
346
- "rules.injectable": (input) => listInjectableRules(artifacts, tasks.active(taskFilter(input))?.id)
347
- .map(({ id, title, body, extra }) => ({ id, title, body, extra })),
371
+ "rules.injectable": (input) =>
372
+ listInjectableRules(artifacts, tasks.active(taskFilter(input))?.id).map(({ id, title, body, extra }) => ({ id, title, body, extra })),
348
373
  "tasks.create": forwardToModule("tasks.create"),
349
374
  "tasks.update": forwardToModule("tasks.update"),
350
375
  "tasks.list": forwardToModule("tasks.list"),
@@ -408,16 +433,6 @@ function handlers(
408
433
  "rules.gate": forwardToModule("rules.gate"),
409
434
  "rules.assign_project": forwardToModule("rules.assign_project"),
410
435
  "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
436
  "playbooks.create": forwardToModule("playbooks.create"),
422
437
  "playbooks.list": forwardToModule("playbooks.list"),
423
438
  "playbooks.show": forwardToModule("playbooks.show"),
@@ -431,7 +446,6 @@ function handlers(
431
446
  "playbooks.uncontain": forwardToModule("playbooks.uncontain"),
432
447
  "playbooks.depend": forwardToModule("playbooks.depend"),
433
448
  "playbooks.undepend": forwardToModule("playbooks.undepend"),
434
- "skills.instantiate": (input) => instantiateSkillOrTemplate({ artifacts, tasks, authority }, input, eventContextFor(input, "template-instantiation")),
435
449
  "graph_projection.apply": forwardToModule("graph_projection.apply"),
436
450
  "graph_projection.checkpoint": forwardToModule("graph_projection.checkpoint"),
437
451
  "logs.append": forwardToModule("logs.append"),
@@ -468,7 +482,16 @@ export function createPapyrusService(path: string): PapyrusService {
468
482
  const sessionIdentity = new SessionIdentity(new SQLiteSessionIdentityStore(db));
469
483
  const discussions = new Discussions(artifacts, new SQLiteDiscussionRoundStore(db));
470
484
  const authority = createAuthorityRegistry();
471
- const vehicle = createPapyrusVehicleRegistry({ artifacts, scopes: artifactScopes, authority, notes, events, taskScopes: scopes, tasks, sessionIdentity });
485
+ const vehicle = createPapyrusVehicleRegistry({
486
+ artifacts,
487
+ scopes: artifactScopes,
488
+ authority,
489
+ notes,
490
+ events,
491
+ taskScopes: scopes,
492
+ tasks,
493
+ sessionIdentity,
494
+ });
472
495
  const moduleRegistry = new OperationRegistry();
473
496
  moduleRegistry.registerAll(notesOperations(notes));
474
497
  moduleRegistry.registerAll(logsOperations(logs));
@@ -477,7 +500,6 @@ export function createPapyrusService(path: string): PapyrusService {
477
500
  moduleRegistry.registerAll(tasksOperations(tasks, artifacts, sessionIdentity));
478
501
  moduleRegistry.registerAll(docsOperations(artifacts, artifactScopes, authority));
479
502
  moduleRegistry.registerAll(rulesOperations(artifacts, artifactScopes));
480
- moduleRegistry.registerAll(skillsOperations({ artifacts, events, scopes, artifactScopes, authority }));
481
503
  moduleRegistry.registerAll(playbooksOperations({ artifacts, events, scopes, artifactScopes, tasks, sessionIdentity }));
482
504
  moduleRegistry.registerAll(graphProjectionOperations(artifacts, projections, authority));
483
505
  const registry = handlers(artifacts, gates, tasks, notes, events, scopes, () => migrateDb(db), moduleRegistry, authority);
@@ -497,8 +519,12 @@ export function createPapyrusService(path: string): PapyrusService {
497
519
  }
498
520
  return handler(input);
499
521
  },
500
- checkpoint: () => { db.exec("PRAGMA wal_checkpoint(PASSIVE)"); },
501
- optimize: () => { db.exec("PRAGMA optimize"); },
522
+ checkpoint: () => {
523
+ db.exec("PRAGMA wal_checkpoint(PASSIVE)");
524
+ },
525
+ optimize: () => {
526
+ db.exec("PRAGMA optimize");
527
+ },
502
528
  reapStaleFocus: () => tasks.reapStaleFocus(),
503
529
  purgeDueTrash: () => artifacts.purgeDueTrash(),
504
530
  close: () => {
@@ -533,7 +559,10 @@ async function readOperationBody(request: Request): Promise<{ op?: unknown; inpu
533
559
  }
534
560
  const bytes = new Uint8Array(size);
535
561
  let offset = 0;
536
- for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
562
+ for (const chunk of chunks) {
563
+ bytes.set(chunk, offset);
564
+ offset += chunk.byteLength;
565
+ }
537
566
  return JSON.parse(new TextDecoder().decode(bytes)) as { op?: unknown; input?: unknown };
538
567
  }
539
568
 
@@ -547,9 +576,11 @@ export function createApp(deps: {
547
576
  * (daemon.ts) wires this to a PushChannel; tests and other embedders can ignore it.
548
577
  */
549
578
  onOperationExecuted?: (operation: string, input: OperationInput) => void;
579
+ /** Defaults to a no-op (createVehicleHttpApp's own default) -- daemon.ts wires vehicleLogger() so a failed invocation is actually logged, not silently discarded. */
580
+ logger?: Logger;
550
581
  }): { fetch(request: Request): Promise<Response> } {
551
582
  // Same Bearer token, daemon, and port as the rest of this API -- see ./vehicle/papyrus-vehicle.ts.
552
- const vehicleApp = createVehicleHttpApp({ registry: deps.service.vehicle, token: deps.token });
583
+ const vehicleApp = createVehicleHttpApp({ registry: deps.service.vehicle, token: deps.token, logger: deps.logger });
553
584
  return {
554
585
  async fetch(request: Request): Promise<Response> {
555
586
  if (request.headers.get("authorization") !== `Bearer ${deps.token}`) {
@@ -575,7 +606,14 @@ export function createApp(deps: {
575
606
  deps.onOperationExecuted?.(body.op, input as OperationInput);
576
607
  return json({ result });
577
608
  } catch (error) {
578
- const status = error instanceof PayloadTooLargeError ? 413 : error instanceof UnknownOperationError ? 404 : error instanceof InvalidSessionSecretError ? 403 : 400;
609
+ const status =
610
+ error instanceof PayloadTooLargeError
611
+ ? 413
612
+ : error instanceof UnknownOperationError
613
+ ? 404
614
+ : error instanceof InvalidSessionSecretError
615
+ ? 403
616
+ : 400;
579
617
  return json({ error: error instanceof Error ? error.message : String(error) }, { status });
580
618
  }
581
619
  }
@@ -1,4 +1,9 @@
1
- import { isSessionRegistered, registerSessionIdentity, releaseSessionIdentity, verifySessionSecret } from "@danypops/vehicle-server/session-identity";
1
+ import {
2
+ isSessionRegistered,
3
+ registerSessionIdentity,
4
+ releaseSessionIdentity,
5
+ verifySessionSecret,
6
+ } from "@danypops/vehicle-server/session-identity";
2
7
  import { assertValidSessionId } from "./domain/session-identity.ts";
3
8
  import type { SessionIdentityStore } from "./ports/session-identity-store.ts";
4
9
 
@@ -50,6 +55,9 @@ export class SessionIdentity {
50
55
  assertAuthorized(sessionId: string | undefined, secret: string | undefined): void {
51
56
  if (sessionId === undefined) return;
52
57
  if (!this.isRegistered(sessionId)) return;
53
- if (!this.verify(sessionId, secret)) throw new InvalidSessionSecretError(`session "${sessionId}" is registered; a valid session_secret is required to mutate its Task Focus`);
58
+ if (!this.verify(sessionId, secret))
59
+ throw new InvalidSessionSecretError(
60
+ `session "${sessionId}" is registered; a valid session_secret is required to mutate its Task Focus`,
61
+ );
54
62
  }
55
63
  }
@@ -1,11 +1,7 @@
1
+ import { TASK_CONTEXT_CURRENT_LIMIT, TASK_CONTEXT_REJECTED_LIMIT, TASK_RECONCILIATION_INSTRUCTION } from "./constants.ts";
1
2
  import type { Artifact } from "./domain/artifact.ts";
2
3
  import { DISCUSSION_SUBTYPE, readDiscussionExtra } from "./domain/discussion.ts";
3
4
  import type { ArtifactStore } from "./ports/artifact-store.ts";
4
- import {
5
- TASK_CONTEXT_CURRENT_LIMIT,
6
- TASK_CONTEXT_REJECTED_LIMIT,
7
- TASK_RECONCILIATION_INSTRUCTION,
8
- } from "./constants.ts";
9
5
 
10
6
  interface Gate {
11
7
  type?: unknown;
@@ -14,8 +10,8 @@ interface Gate {
14
10
  }
15
11
 
16
12
  function gatesFrom(task: Artifact): Gate[] {
17
- const gates = task.extra["gates"];
18
- return Array.isArray(gates) ? gates as Gate[] : [];
13
+ const gates = task.extra.gates;
14
+ return Array.isArray(gates) ? (gates as Gate[]) : [];
19
15
  }
20
16
 
21
17
  function renderGate(gate: Gate): string {
@@ -45,14 +41,22 @@ function inScope(taskId: string, activeTaskId: string | undefined, taskIds: Set<
45
41
  return taskIds === undefined || taskIds.has(taskId) || taskId === activeTaskId;
46
42
  }
47
43
 
48
- function deferredBlockingDiscussions(artifacts: ArtifactStore, activeTaskId: string | undefined, taskIds: Set<string> | undefined): string[] {
49
- const discussions = artifacts.query({ kind: "task", subtype: DISCUSSION_SUBTYPE })
50
- .filter((discussion) => {
51
- try { return readDiscussionExtra(discussion.extra).state === "deferred"; } catch { return false; }
52
- });
44
+ function deferredBlockingDiscussions(
45
+ artifacts: ArtifactStore,
46
+ activeTaskId: string | undefined,
47
+ taskIds: Set<string> | undefined,
48
+ ): string[] {
49
+ const discussions = artifacts.query({ kind: "task", subtype: DISCUSSION_SUBTYPE }).filter((discussion) => {
50
+ try {
51
+ return readDiscussionExtra(discussion.extra).state === "deferred";
52
+ } catch {
53
+ return false;
54
+ }
55
+ });
53
56
  if (discussions.length === 0) return [];
54
57
 
55
- const blocks = artifacts.relationships({ artifactIds: discussions.map((discussion) => discussion.id) })
58
+ const blocks = artifacts
59
+ .relationships({ artifactIds: discussions.map((discussion) => discussion.id) })
56
60
  .filter((edge) => edge.relation === "blocks");
57
61
  const lines: string[] = [];
58
62
  for (const discussion of discussions) {
@@ -76,8 +80,14 @@ export type TaskContextVerbosity = "summary" | "full";
76
80
  * exists and that the full plan is one explicit call away -- avoiding repeating the same
77
81
  * unchanged prose every single turn for a task that can persist across dozens of turns.
78
82
  */
79
- export function taskContext(artifacts: ArtifactStore, activeTaskId?: string, taskIds?: Set<string>, verbosity: TaskContextVerbosity = "full"): string | null {
80
- const tasks = artifacts.query({ kind: "task", excludeSubtype: DISCUSSION_SUBTYPE })
83
+ export function taskContext(
84
+ artifacts: ArtifactStore,
85
+ activeTaskId?: string,
86
+ taskIds?: Set<string>,
87
+ verbosity: TaskContextVerbosity = "full",
88
+ ): string | null {
89
+ const tasks = artifacts
90
+ .query({ kind: "task", excludeSubtype: DISCUSSION_SUBTYPE })
81
91
  .filter((task) => taskIds === undefined || taskIds.has(task.id))
82
92
  .sort((left, right) => left.updated_at.localeCompare(right.updated_at));
83
93
  const open = tasks.filter((task) => task.status !== "done" && task.status !== "canceled");
@@ -86,7 +96,9 @@ export function taskContext(artifacts: ArtifactStore, activeTaskId?: string, tas
86
96
 
87
97
  const done = tasks.length - open.length;
88
98
  const active = activeTaskId ? open.find((task) => task.id === activeTaskId) : undefined;
89
- const current = active ? [active] : open.filter((task) => task.status === "in-progress" || task.status === "review").slice(0, TASK_CONTEXT_CURRENT_LIMIT);
99
+ const current = active
100
+ ? [active]
101
+ : open.filter((task) => task.status === "in-progress" || task.status === "review").slice(0, TASK_CONTEXT_CURRENT_LIMIT);
90
102
  const next = open.find((task) => task.status === "todo");
91
103
  const rejected = open.filter((task) => task.status === "rejected").slice(0, TASK_CONTEXT_REJECTED_LIMIT);
92
104
  const lines = tasks.length > 0 ? [`Progress: ${done}/${tasks.length} done`] : [];
@@ -1,16 +1,7 @@
1
1
  import { TASK_EXECUTION_MAX_DEGREE, TASK_EXECUTION_MAX_EDGES, TASK_EXECUTION_MAX_NODES } from "./constants.ts";
2
2
  import type { TaskGraph } from "./task-service.ts";
3
3
 
4
- export type TaskExecutionState =
5
- | "todo"
6
- | "in-progress"
7
- | "review"
8
- | "rejected"
9
- | "done"
10
- | "canceled"
11
- | "ready"
12
- | "blocked"
13
- | "invalid";
4
+ export type TaskExecutionState = "todo" | "in-progress" | "review" | "rejected" | "done" | "canceled" | "ready" | "blocked" | "invalid";
14
5
 
15
6
  export interface TaskExecutionNode {
16
7
  id: string;
@@ -56,8 +47,9 @@ function assertBounds(graph: TaskGraph): void {
56
47
  /** Build deterministic topological layers ordered by creation time and task ID. */
57
48
  export function projectTaskExecution(graph: TaskGraph): TaskExecutionPlan {
58
49
  assertBounds(graph);
59
- const orderedNodes = [...graph.nodes].sort((left, right) =>
60
- left.task.created_at.localeCompare(right.task.created_at) || left.task.id.localeCompare(right.task.id));
50
+ const orderedNodes = [...graph.nodes].sort(
51
+ (left, right) => left.task.created_at.localeCompare(right.task.created_at) || left.task.id.localeCompare(right.task.id),
52
+ );
61
53
  const byId = new Map(orderedNodes.map((node) => [node.task.id, node]));
62
54
  const order = new Map(orderedNodes.map((node, index) => [node.task.id, index]));
63
55
  const successors = new Map(orderedNodes.map((node) => [node.task.id, [] as string[]]));
@@ -36,18 +36,18 @@ export function projectTaskGraph(graph: TaskGraph, view: TaskGraphView): Display
36
36
  }
37
37
  }
38
38
 
39
- const connected = view === "execution"
40
- ? new Set(graph.nodes.map((node) => node.task.id))
41
- : new Set(edges.flatMap((edge) => [edge.from, edge.to]));
42
- const nodes = view === "execution"
43
- ? projectTaskExecution(graph).nodes.map((node) => ({
44
- id: node.id,
45
- label: `${node.active ? "▶ " : ""}${EXECUTION_GLYPHS[node.state]} ${node.title} · ${node.layer === null ? "no layer" : `layer ${node.layer + 1}`} · ${node.state}`,
46
- status: node.state,
47
- }))
48
- : graph.nodes
49
- .filter((node) => connected.has(node.task.id))
50
- .map((node) => ({ id: node.task.id, label: node.task.title, status: node.task.status }));
39
+ const connected =
40
+ view === "execution" ? new Set(graph.nodes.map((node) => node.task.id)) : new Set(edges.flatMap((edge) => [edge.from, edge.to]));
41
+ const nodes =
42
+ view === "execution"
43
+ ? projectTaskExecution(graph).nodes.map((node) => ({
44
+ id: node.id,
45
+ label: `${node.active ? "▶ " : ""}${EXECUTION_GLYPHS[node.state]} ${node.title} · ${node.layer === null ? "no layer" : `layer ${node.layer + 1}`} · ${node.state}`,
46
+ status: node.state,
47
+ }))
48
+ : graph.nodes
49
+ .filter((node) => connected.has(node.task.id))
50
+ .map((node) => ({ id: node.task.id, label: node.task.title, status: node.task.status }));
51
51
  return {
52
52
  direction: "TD",
53
53
  nodes,
@@ -32,9 +32,7 @@ export function projectTaskRelationships(task: Artifact, graph?: TaskGraph): Dis
32
32
  }
33
33
  const nodes: DisplayGraphNode[] = [...nodeIds].map((id) => {
34
34
  const artifact = taskNodes.get(id);
35
- return artifact
36
- ? { id, label: artifact.title, status: artifact.status }
37
- : { id, label: fallbackLabel(id) };
35
+ return artifact ? { id, label: artifact.title, status: artifact.status } : { id, label: fallbackLabel(id) };
38
36
  });
39
37
  return { direction: "LR", nodes, edges };
40
38
  }