@plurnk/plurnk-service 1.23.0 → 1.25.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.
Files changed (215) hide show
  1. package/.env.defaults +17 -15
  2. package/INSTALL.md +96 -20
  3. package/README.md +2 -2
  4. package/SPEC.md +519 -206
  5. package/dist/Paths.d.ts +1 -0
  6. package/dist/Paths.d.ts.map +1 -1
  7. package/dist/Paths.js +1 -0
  8. package/dist/Paths.js.map +1 -1
  9. package/dist/build-info.json +1 -1
  10. package/dist/core/AdministrativeLoop.d.ts +1 -1
  11. package/dist/core/AdministrativeLoop.d.ts.map +1 -1
  12. package/dist/core/AdministrativeLoop.js +5 -5
  13. package/dist/core/AdministrativeLoop.js.map +1 -1
  14. package/dist/core/AdmittedTurnExecutor.d.ts +3 -2
  15. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  16. package/dist/core/AdmittedTurnExecutor.js +14 -1
  17. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  18. package/dist/core/CapabilityResolver.js +1 -1
  19. package/dist/core/CapabilityResolver.js.map +1 -1
  20. package/dist/core/DataStatementRunner.d.ts.map +1 -1
  21. package/dist/core/DataStatementRunner.js +1 -3
  22. package/dist/core/DataStatementRunner.js.map +1 -1
  23. package/dist/core/Dispatcher.d.ts +10 -0
  24. package/dist/core/Dispatcher.d.ts.map +1 -1
  25. package/dist/core/Dispatcher.js +71 -18
  26. package/dist/core/Dispatcher.js.map +1 -1
  27. package/dist/core/Dispatcher.sql +0 -7
  28. package/dist/core/Engine.d.ts +2 -2
  29. package/dist/core/Engine.d.ts.map +1 -1
  30. package/dist/core/ExecutorRegistry.d.ts +16 -4
  31. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  32. package/dist/core/ExecutorRegistry.js +31 -31
  33. package/dist/core/ExecutorRegistry.js.map +1 -1
  34. package/dist/core/FabricatedLog.d.ts +2 -0
  35. package/dist/core/FabricatedLog.d.ts.map +1 -1
  36. package/dist/core/FabricatedLog.js +18 -7
  37. package/dist/core/FabricatedLog.js.map +1 -1
  38. package/dist/core/HostPaths.d.ts +7 -1
  39. package/dist/core/HostPaths.d.ts.map +1 -1
  40. package/dist/core/HostPaths.js +25 -12
  41. package/dist/core/HostPaths.js.map +1 -1
  42. package/dist/core/LogEntryProjection.d.ts +1 -0
  43. package/dist/core/LogEntryProjection.d.ts.map +1 -1
  44. package/dist/core/LogEntryProjection.js +10 -0
  45. package/dist/core/LogEntryProjection.js.map +1 -1
  46. package/dist/core/LoopDriver.d.ts.map +1 -1
  47. package/dist/core/LoopDriver.js +8 -1
  48. package/dist/core/LoopDriver.js.map +1 -1
  49. package/dist/core/LoopLifecycle.d.ts +1 -1
  50. package/dist/core/LoopLifecycle.js +1 -1
  51. package/dist/core/LoopLifecycle.sql +2 -2
  52. package/dist/core/LoopPolicies.d.ts.map +1 -1
  53. package/dist/core/LoopPolicies.js +12 -7
  54. package/dist/core/LoopPolicies.js.map +1 -1
  55. package/dist/core/OperatorConfig.d.ts.map +1 -1
  56. package/dist/core/OperatorConfig.js +97 -51
  57. package/dist/core/OperatorConfig.js.map +1 -1
  58. package/dist/core/PacketBuilder.d.ts +2 -0
  59. package/dist/core/PacketBuilder.d.ts.map +1 -1
  60. package/dist/core/PacketBuilder.js +44 -32
  61. package/dist/core/PacketBuilder.js.map +1 -1
  62. package/dist/core/PacketBuilder.sql +3 -1
  63. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  64. package/dist/core/SchemeRegistry.js +3 -2
  65. package/dist/core/SchemeRegistry.js.map +1 -1
  66. package/dist/core/TurnDispositionHandler.d.ts +1 -1
  67. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  68. package/dist/core/TurnDispositionHandler.js +6 -15
  69. package/dist/core/TurnDispositionHandler.js.map +1 -1
  70. package/dist/core/TurnMaterialization.js +1 -1
  71. package/dist/core/TurnMaterialization.js.map +1 -1
  72. package/dist/core/TurnOps.d.ts +1 -0
  73. package/dist/core/TurnOps.d.ts.map +1 -1
  74. package/dist/core/TurnOps.js +18 -0
  75. package/dist/core/TurnOps.js.map +1 -1
  76. package/dist/core/TurnRunner.d.ts +6 -0
  77. package/dist/core/TurnRunner.d.ts.map +1 -1
  78. package/dist/core/TurnRunner.js +18 -8
  79. package/dist/core/TurnRunner.js.map +1 -1
  80. package/dist/core/TurnSources.sql +0 -15
  81. package/dist/core/env-defaults.d.ts +12 -1
  82. package/dist/core/env-defaults.d.ts.map +1 -1
  83. package/dist/core/env-defaults.js +46 -7
  84. package/dist/core/env-defaults.js.map +1 -1
  85. package/dist/core/file-creation-policy.d.ts.map +1 -1
  86. package/dist/core/file-creation-policy.js +2 -1
  87. package/dist/core/file-creation-policy.js.map +1 -1
  88. package/dist/core/packet-wire.d.ts +10 -2
  89. package/dist/core/packet-wire.d.ts.map +1 -1
  90. package/dist/core/packet-wire.js +87 -69
  91. package/dist/core/packet-wire.js.map +1 -1
  92. package/dist/core/results.d.ts +2 -0
  93. package/dist/core/results.d.ts.map +1 -1
  94. package/dist/core/results.js +7 -0
  95. package/dist/core/results.js.map +1 -1
  96. package/dist/digest/Digest.d.ts.map +1 -1
  97. package/dist/digest/Digest.js +10 -4
  98. package/dist/digest/Digest.js.map +1 -1
  99. package/dist/digest/DigestRender.d.ts.map +1 -1
  100. package/dist/digest/DigestRender.js +51 -22
  101. package/dist/digest/DigestRender.js.map +1 -1
  102. package/dist/digest/DigestRequiem.d.ts.map +1 -1
  103. package/dist/digest/DigestRequiem.js +10 -4
  104. package/dist/digest/DigestRequiem.js.map +1 -1
  105. package/dist/digest/digest-rows.d.ts +9 -1
  106. package/dist/digest/digest-rows.d.ts.map +1 -1
  107. package/dist/digest/digest-rows.js.map +1 -1
  108. package/dist/digest/digest.sql +16 -0
  109. package/dist/observe/init.d.ts +1 -0
  110. package/dist/observe/init.d.ts.map +1 -1
  111. package/dist/observe/init.js +5 -1
  112. package/dist/observe/init.js.map +1 -1
  113. package/dist/schemes/EffectPolicy.d.ts.map +1 -1
  114. package/dist/schemes/EffectPolicy.js +4 -4
  115. package/dist/schemes/EffectPolicy.js.map +1 -1
  116. package/dist/schemes/Exec.d.ts +1 -0
  117. package/dist/schemes/Exec.d.ts.map +1 -1
  118. package/dist/schemes/Exec.js +30 -14
  119. package/dist/schemes/Exec.js.map +1 -1
  120. package/dist/schemes/ExecScheduler.d.ts.map +1 -1
  121. package/dist/schemes/ExecScheduler.js +4 -3
  122. package/dist/schemes/ExecScheduler.js.map +1 -1
  123. package/dist/schemes/ExecScratch.d.ts.map +1 -1
  124. package/dist/schemes/ExecScratch.js +2 -1
  125. package/dist/schemes/ExecScratch.js.map +1 -1
  126. package/dist/schemes/File.d.ts.map +1 -1
  127. package/dist/schemes/File.js +15 -10
  128. package/dist/schemes/File.js.map +1 -1
  129. package/dist/schemes/Log.d.ts.map +1 -1
  130. package/dist/schemes/Log.js +25 -3
  131. package/dist/schemes/Log.js.map +1 -1
  132. package/dist/server/AgentRoots.d.ts +6 -0
  133. package/dist/server/AgentRoots.d.ts.map +1 -0
  134. package/dist/server/AgentRoots.js +25 -0
  135. package/dist/server/AgentRoots.js.map +1 -0
  136. package/dist/server/ConfigurationDiagnostics.d.ts +12 -0
  137. package/dist/server/ConfigurationDiagnostics.d.ts.map +1 -0
  138. package/dist/server/ConfigurationDiagnostics.js +40 -0
  139. package/dist/server/ConfigurationDiagnostics.js.map +1 -0
  140. package/dist/server/Daemon.d.ts +11 -5
  141. package/dist/server/Daemon.d.ts.map +1 -1
  142. package/dist/server/Daemon.js +124 -48
  143. package/dist/server/Daemon.js.map +1 -1
  144. package/dist/server/DaemonModule.d.ts +11 -4
  145. package/dist/server/DaemonModule.d.ts.map +1 -1
  146. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  147. package/dist/server/EnvFunctionality.js +5 -2
  148. package/dist/server/EnvFunctionality.js.map +1 -1
  149. package/dist/server/Functionality.d.ts +6 -0
  150. package/dist/server/Functionality.d.ts.map +1 -1
  151. package/dist/server/Functionality.js +173 -37
  152. package/dist/server/Functionality.js.map +1 -1
  153. package/dist/server/FunctionalityManager.js +6 -6
  154. package/dist/server/FunctionalityManager.js.map +1 -1
  155. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  156. package/dist/server/MembersFunctionality.js +13 -41
  157. package/dist/server/MembersFunctionality.js.map +1 -1
  158. package/dist/server/PluginSources.d.ts +20 -0
  159. package/dist/server/PluginSources.d.ts.map +1 -0
  160. package/dist/server/PluginSources.js +52 -0
  161. package/dist/server/PluginSources.js.map +1 -0
  162. package/dist/server/PlurnkSkill.js +1 -1
  163. package/dist/server/PlurnkSkill.js.map +1 -1
  164. package/dist/server/Retention.d.ts.map +1 -1
  165. package/dist/server/Retention.js +11 -30
  166. package/dist/server/Retention.js.map +1 -1
  167. package/dist/server/ServiceModules.d.ts +1 -0
  168. package/dist/server/ServiceModules.d.ts.map +1 -1
  169. package/dist/server/ServiceModules.js +9 -3
  170. package/dist/server/ServiceModules.js.map +1 -1
  171. package/dist/server/SkillSource.d.ts +37 -0
  172. package/dist/server/SkillSource.d.ts.map +1 -0
  173. package/dist/server/SkillSource.js +267 -0
  174. package/dist/server/SkillSource.js.map +1 -0
  175. package/dist/server/SkillsFunctionality.d.ts +8 -27
  176. package/dist/server/SkillsFunctionality.d.ts.map +1 -1
  177. package/dist/server/SkillsFunctionality.js +218 -270
  178. package/dist/server/SkillsFunctionality.js.map +1 -1
  179. package/dist/server/WorkerModelResolver.d.ts +1 -2
  180. package/dist/server/WorkerModelResolver.d.ts.map +1 -1
  181. package/dist/server/WorkerModelResolver.js +18 -19
  182. package/dist/server/WorkerModelResolver.js.map +1 -1
  183. package/dist/server/WorkspacePlugins.d.ts +25 -0
  184. package/dist/server/WorkspacePlugins.d.ts.map +1 -0
  185. package/dist/server/WorkspacePlugins.js +43 -0
  186. package/dist/server/WorkspacePlugins.js.map +1 -0
  187. package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
  188. package/dist/server/dispatch-as-plurnk.js +3 -1
  189. package/dist/server/dispatch-as-plurnk.js.map +1 -1
  190. package/dist/server/envelope.js +1 -1
  191. package/dist/server/envelope.js.map +1 -1
  192. package/dist/server/loop-model.d.ts.map +1 -1
  193. package/dist/server/loop-model.js +1 -2
  194. package/dist/server/loop-model.js.map +1 -1
  195. package/dist/server/module-discovery.d.ts +6 -0
  196. package/dist/server/module-discovery.d.ts.map +1 -1
  197. package/dist/server/module-discovery.js +46 -24
  198. package/dist/server/module-discovery.js.map +1 -1
  199. package/dist/server/skills-problems.d.ts +8 -0
  200. package/dist/server/skills-problems.d.ts.map +1 -0
  201. package/dist/server/skills-problems.js +18 -0
  202. package/dist/server/skills-problems.js.map +1 -0
  203. package/dist/service.d.ts.map +1 -1
  204. package/dist/service.js +55 -45
  205. package/dist/service.js.map +1 -1
  206. package/docs/env.md +7 -8
  207. package/docs/members.md +4 -3
  208. package/docs/skills.md +45 -22
  209. package/migrations/012_emission.sql +77 -0
  210. package/package.json +38 -36
  211. package/plurnk.service +29 -0
  212. package/dist/core/PreviousEmission.d.ts +0 -14
  213. package/dist/core/PreviousEmission.d.ts.map +0 -1
  214. package/dist/core/PreviousEmission.js +0 -24
  215. package/dist/core/PreviousEmission.js.map +0 -1
@@ -6,6 +6,7 @@ import Share from "../share/Share.js";
6
6
  import Paths from "../Paths.js";
7
7
  import Engine from "../core/Engine.js";
8
8
  import ExecutorRegistry from "../core/ExecutorRegistry.js";
9
+ import Exec from "../schemes/Exec.js";
9
10
  import SchemeRegistry from "../core/SchemeRegistry.js";
10
11
  import { Mimetypes } from "@plurnk/plurnk-mimetypes";
11
12
  import { RuntimeDeclaration } from "@plurnk/plurnk-execs";
@@ -15,22 +16,27 @@ import EffectPolicy from "../schemes/EffectPolicy.js";
15
16
  import QuestionTool, { questionRuntimeDecl } from "../schemes/QuestionTool.js";
16
17
  import DrainSupervisor from "./DrainSupervisor.js";
17
18
  import Retention, { retentionPolicy } from "./Retention.js";
19
+ import PacketBuilder from "../core/PacketBuilder.js";
18
20
  import { Validator } from "@plurnk/plurnk-contracts";
19
21
  import LogEntry from "./logEntry.js";
20
22
  import Envelope, { projectWorkerRow } from "./envelope.js";
21
23
  import ClientInput from "./client-input.js";
22
24
  import Turn from "../core/Turn.js";
23
25
  import SkillsFunctionality from "./SkillsFunctionality.js";
26
+ import WorkspacePlugins from "./WorkspacePlugins.js";
27
+ import { agentRootScopes, configurationDirectories } from "./AgentRoots.js";
28
+ import ExecEnv from "../schemes/exec-env.js";
24
29
  import PlurnkSkill from "./PlurnkSkill.js";
25
30
  import Skill from "../schemes/Skill.js";
26
- import MembersFunctionality from "./MembersFunctionality.js";
31
+ import MembersFunctionality, { modelScope, serviceMembers } from "./MembersFunctionality.js";
32
+ import FileCreationPolicy from "../core/file-creation-policy.js";
27
33
  import EnvFunctionality, { ENV_OWNER } from "./EnvFunctionality.js";
28
34
  import HostPaths from "../core/HostPaths.js";
29
35
  import WorkspaceStorage from "./WorkspaceStorage.js";
30
36
  import Fork from "../core/fork.js";
31
37
  import WorkerControlAddress from "../core/WorkerControlAddress.js";
32
38
  import LoopLifecycle from "../core/LoopLifecycle.js";
33
- import { Knob } from "@plurnk/plurnk-meta";
39
+ import { ConfigurationError, Knob } from "@plurnk/plurnk-meta";
34
40
  import LoopPolicies from "../core/LoopPolicies.js";
35
41
  import LoopPolicyReader from "../core/LoopPolicyReader.js";
36
42
  import { contentWeight } from "../core/content-weight.js";
@@ -46,6 +52,7 @@ import { listModelCatalog } from "./model-catalog.js";
46
52
  import { daemonFailure, modelRouteLabel } from "./daemon-results.js";
47
53
  import WorkerModelResolver from "./WorkerModelResolver.js";
48
54
  import ClientReads from "./ClientReads.js";
55
+ import ConfigurationDiagnostics from "./ConfigurationDiagnostics.js";
49
56
  const clientActionFailure = (error) => {
50
57
  if (error instanceof OperationFailureError)
51
58
  return error.result;
@@ -56,8 +63,24 @@ const clientActionFailure = (error) => {
56
63
  });
57
64
  };
58
65
  export default class Daemon {
66
+ static validateConfiguration() {
67
+ FileCreationPolicy.serviceScope();
68
+ EffectPolicy.validateConfiguration();
69
+ LoopPolicies.validateConfiguration();
70
+ retentionPolicy();
71
+ PacketBuilder.validateConfiguration();
72
+ Exec.validateConfiguration();
73
+ ExecutorRegistry.validateConfiguration();
74
+ }
75
+ static validateWorkspaceConfiguration() {
76
+ SkillsFunctionality.validateConfiguration();
77
+ agentRootScopes();
78
+ serviceMembers();
79
+ modelScope();
80
+ }
59
81
  #db;
60
82
  #dbPath;
83
+ #hostPaths;
61
84
  #engine;
62
85
  #workspaceGate;
63
86
  #lifecycle;
@@ -73,13 +96,14 @@ export default class Daemon {
73
96
  #discoveryCwd;
74
97
  #started = false; // {§module-lifecycle}: one discovery/module boot; no listener
75
98
  #modules = [];
76
- #moduleClosers = [];
99
+ #moduleLifetimes = [];
77
100
  #moduleActions = new Map();
78
101
  #residency;
79
102
  #functionality;
80
103
  // {§retention-policy} — the operator's retention, run on a cadence and at shutdown.
81
- #retention;
104
+ #retention = null;
82
105
  #skills;
106
+ #plugins;
83
107
  #members;
84
108
  // {§methods-event-subscribe} — the broadcast's in-process event source. A transport
85
109
  // module (plurnk-agui) subscribes and fans out to its OWN clients; core emits, never owns
@@ -88,10 +112,13 @@ export default class Daemon {
88
112
  #workerModels;
89
113
  #reads;
90
114
  #storage;
91
- constructor({ db, schemes, mimetypes, provider, nodeModulesPath, hostPaths = new HostPaths(), skills, http = null, dbPath }) {
115
+ #configuration;
116
+ constructor({ db, schemes, mimetypes, provider, nodeModulesPath, hostPaths = new HostPaths(), http = null, dbPath, configuration = new ConfigurationDiagnostics() }) {
92
117
  this.#db = db;
118
+ this.#hostPaths = hostPaths;
93
119
  this.#dbPath = dbPath;
94
120
  this.#http = http;
121
+ this.#configuration = configuration;
95
122
  this.#storage = new WorkspaceStorage(db, hostPaths);
96
123
  this.#lifecycle = new LoopLifecycle(db);
97
124
  this.#schemes = schemes ?? new SchemeRegistry();
@@ -111,7 +138,7 @@ export default class Daemon {
111
138
  defaultMimetype: "text/markdown",
112
139
  discoverOptions: { cwd: this.#discoveryCwd }
113
140
  });
114
- const bootSpec = resolveActiveRoute();
141
+ const bootSpec = this.#provider === null ? null : resolveActiveRoute();
115
142
  if (this.#provider !== null && bootSpec !== null) {
116
143
  ProviderInstantiate.registerInstance(this.#provider, bootSpec);
117
144
  }
@@ -140,13 +167,16 @@ export default class Daemon {
140
167
  readWorkerModuleState: (workerId, owner) => this.#residency.readWorkerModuleState(workerId, owner),
141
168
  replaceWorkerModuleState: (workerId, owner, state) => this.#residency.replaceWorkerModuleState(workerId, owner, state),
142
169
  mutateWorkspace: (workspaceId, owner, caller, run) => this.#residency.exclusively(workspaceId, owner, caller === "operation" ? "wait" : "try", run),
143
- retainWorkspace: (workspaceId) => this.#residency.retain(workspaceId)
170
+ retainWorkspace: (workspaceId) => this.#residency.retain(workspaceId),
171
+ preparationChanged: (workspaceId, preparation) => this.#broadcast({ workspaceId }, "workspace/preparation", { workspaceId, preparation })
144
172
  });
145
173
  // {§skills-functionality} — Core's own family: standard Agent Skills.
174
+ this.#plugins = new WorkspacePlugins({ db, hostPaths, nodeModules: this.#nodeModulesPath });
146
175
  this.#skills = new SkillsFunctionality({
147
176
  db,
177
+ plugins: this.#plugins,
178
+ storage: this.#storage,
148
179
  hostPaths,
149
- ...skills,
150
180
  provided: async () => {
151
181
  const tree = await PlurnkSkill.load(this.#nodeModulesPath);
152
182
  return new Map([[tree.document.name, tree]]);
@@ -238,11 +268,12 @@ export default class Daemon {
238
268
  return { action, loopId };
239
269
  },
240
270
  acquireWorkspaceTurn: async (workspaceId, workerId, signal) => this.#workspaceGate.acquireTurn(workspaceId, workerId, signal),
241
- // {§skills-hotload} — filesystem installers operate out of band.
271
+ // {§functionality-hotload} — skills and plugins change out of band.
242
272
  // Republish under the workspace turn gate before packet assembly so
243
273
  // the first subsequent model turn sees their exact result.
244
274
  workspaceTurnStarting: async ({ workspaceId }) => {
245
- await this.#skills.refreshIfChanged({ workspaceId });
275
+ await this.#functionality.refreshChanged({ workspaceId });
276
+ return [...this.configurationNotices(), ...this.#functionality.configurationNotices(workspaceId), ...this.#plugins.notices(workspaceId)];
246
277
  },
247
278
  // worker:// KILL (terminate) — cancel the addressed worker subtree and
248
279
  // tear down its held streams before the operation completes.
@@ -252,7 +283,6 @@ export default class Daemon {
252
283
  this.#broadcast({ workspaceId }, "loop/packet", payload);
253
284
  }
254
285
  });
255
- this.#retention = new Retention(db, retentionPolicy());
256
286
  this.#drains = new DrainSupervisor({
257
287
  db,
258
288
  lifecycle: this.#lifecycle,
@@ -323,7 +353,7 @@ export default class Daemon {
323
353
  const { workspaceId, ...interaction } = event;
324
354
  this.#broadcast({ workspaceId }, "loop/interaction", interaction);
325
355
  });
326
- this.#workerModels = new WorkerModelResolver({ db: this.#db, provider: this.#provider });
356
+ this.#workerModels = new WorkerModelResolver({ db: this.#db });
327
357
  this.#reads = new ClientReads({ db: this.#db, engine: this.#engine, workspaceGate: this.#workspaceGate, residency: this.#residency });
328
358
  }
329
359
  // {§methods-event-subscribe}. A transport module subscribes to the daemon's in-process
@@ -870,10 +900,19 @@ export default class Daemon {
870
900
  // {§methods} — the module's render surface beyond the journal. Thin delegations
871
901
  // into core's envelope / membership / provider machinery; the module fans the results into its own views.
872
902
  listProviders() {
873
- const active = resolveActiveRoute();
903
+ const active = process.env.PLURNK_MODEL?.toLowerCase();
904
+ let aliases;
905
+ try {
906
+ aliases = parseAliasesFromEnv();
907
+ }
908
+ catch (cause) {
909
+ if (!(cause instanceof ConfigurationError))
910
+ throw cause;
911
+ throw new OperationFailureError(Results.configurationFailure(cause), { cause });
912
+ }
874
913
  return {
875
- aliases: parseAliasesFromEnv().map((a) => {
876
- const isActive = active !== null && active.alias === a.alias;
914
+ aliases: aliases.map((a) => {
915
+ const isActive = active === a.alias;
877
916
  return {
878
917
  alias: a.alias, provider: a.provider, model: a.model, active: isActive,
879
918
  inputCapacity: isActive && this.#provider !== null ? this.#provider.inputCapacity : null
@@ -988,6 +1027,9 @@ export default class Daemon {
988
1027
  const checkedWorkerId = workerId === undefined ? null : ClientInput.assertId("workspace.prompts", "workerId", workerId);
989
1028
  return Envelope.listPromptsForWorkspace(this.#db, checkedWorkspaceId, checkedLimit ?? Knob.integer("PLURNK_SERVICE_PROMPTS_PAGE", 1), checkedWorkerId);
990
1029
  }
1030
+ workspacePreparationStatus(workspaceId) {
1031
+ return this.#functionality.preparationStatus(ClientInput.assertId("workspace preparation", "workspaceId", workspaceId));
1032
+ }
991
1033
  workspaceDerivationStatus(workspaceId) {
992
1034
  return this.#engine.workspaceDerivationStatus(ClientInput.assertId("workspace.derivation", "workspaceId", workspaceId));
993
1035
  }
@@ -1142,6 +1184,12 @@ export default class Daemon {
1142
1184
  }
1143
1185
  if (typeof handler !== "function")
1144
1186
  throw new Error(`module action '${name}' has no handler`);
1187
+ if (registration.residency !== "required" && registration.residency !== "none") {
1188
+ throw new Error(`module action '${name}' must declare its residency requirement`);
1189
+ }
1190
+ if (scope === "worldless" && registration.residency !== "none") {
1191
+ throw new Error(`worldless module action '${name}' cannot require workspace residency`);
1192
+ }
1145
1193
  if (this.#moduleActions.has(name))
1146
1194
  throw new Error(`module action '${name}' is already registered`);
1147
1195
  this.#moduleActions.set(name, registration);
@@ -1164,20 +1212,22 @@ export default class Daemon {
1164
1212
  throw new Error(`module action '${name}' requires ${registration.scope} context, not ${context.scope}`);
1165
1213
  }
1166
1214
  let releaseCapabilities;
1167
- if (context.scope === "workspace") {
1168
- releaseCapabilities = await this.#residency.acquire(context.workspaceId);
1169
- }
1170
- else if (context.scope === "worker") {
1215
+ if (context.scope === "worker") {
1171
1216
  const workspaceId = ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
1172
1217
  const workerId = ClientInput.assertId(`module action '${name}'`, "workerId", context.workerId);
1173
1218
  const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
1174
1219
  if (worker === undefined || worker.workspace_id !== workspaceId) {
1175
1220
  throw daemonFailure("daemon:module-action", "worker-not-found", 404, `Worker ${workerId} does not exist in workspace ${workspaceId}.`, { workspaceId, workerId, retryable: false });
1176
1221
  }
1177
- releaseCapabilities = await this.#residency.acquire(workspaceId);
1222
+ }
1223
+ if (registration.residency === "required" && context.scope !== "worldless") {
1224
+ releaseCapabilities = await this.#residency.acquire(context.workspaceId);
1225
+ }
1226
+ else if (context.scope === "workspace") {
1227
+ await this.#residency.identity(context.workspaceId);
1178
1228
  }
1179
1229
  try {
1180
- if (context.scope === "worker") {
1230
+ if (registration.residency === "required" && context.scope === "worker") {
1181
1231
  const release = await this.#workspaceGate.acquireTurn(context.workspaceId, context.workerId);
1182
1232
  try {
1183
1233
  await this.#residency.reconcile(context.workspaceId);
@@ -1206,6 +1256,18 @@ export default class Daemon {
1206
1256
  workspaceStateDirectory(workspaceId, namespaceOwner) {
1207
1257
  return this.#storage.directory(workspaceId, namespaceOwner);
1208
1258
  }
1259
+ async workspaceConfigurationDirectories(workspaceId) {
1260
+ const workspace = await this.#db.envelope_get_workspace.get({ id: workspaceId });
1261
+ return configurationDirectories(this.#hostPaths, workspace?.project_root ?? null);
1262
+ }
1263
+ // {§agent-plugins-hosting}
1264
+ readWorkspacePlugins(workspaceId) {
1265
+ return this.#plugins.read(workspaceId);
1266
+ }
1267
+ // {§mcp-launch-environment}
1268
+ operatorEnvironment() {
1269
+ return ExecEnv.withoutOwnSecrets();
1270
+ }
1209
1271
  async readWorkerEnvironment(workspaceId, workerId) {
1210
1272
  await this.#assertWorkerOwned(workspaceId, workerId);
1211
1273
  const shared = await EnvFunctionality.workspace(this.#db, workspaceId);
@@ -1220,6 +1282,7 @@ export default class Daemon {
1220
1282
  await this.#residency.replace(replacement, options);
1221
1283
  }
1222
1284
  get engine() { return this.#engine; }
1285
+ configurationNotices() { return this.#configuration.notices(); }
1223
1286
  get provider() { return this.#provider; }
1224
1287
  get schemes() { return this.#schemes; }
1225
1288
  get mimetypes() { return this.#mimetypes; }
@@ -1231,14 +1294,22 @@ export default class Daemon {
1231
1294
  async start() {
1232
1295
  if (this.#started)
1233
1296
  throw new Error("daemon already started");
1297
+ await this.#configuration.capture("file-creation", () => FileCreationPolicy.serviceScope());
1298
+ await this.#configuration.capture("effect-policy", () => EffectPolicy.validateConfiguration());
1299
+ await this.#configuration.capture("loop-policy", () => LoopPolicies.validateConfiguration());
1300
+ await this.#configuration.capture("packet", () => PacketBuilder.validateConfiguration());
1301
+ await this.#configuration.capture("execution", () => Exec.validateConfiguration());
1302
+ this.#retention = await this.#configuration.capture("retention", () => new Retention(this.#db, retentionPolicy()));
1234
1303
  this.#started = true;
1235
1304
  // {§db-space-reclamation} — the file is brought to the policy's auto-vacuum mode before any work.
1236
- const storage = await this.#retention.prepareStorage();
1237
- if (storage.converted) {
1238
- console.error(`database: converted to auto_vacuum=${this.#retention.policy.autoVacuum} (${storage.pagesBefore} → ${storage.pagesAfter} pages)`);
1305
+ if (this.#retention !== null) {
1306
+ const storage = await this.#retention.prepareStorage();
1307
+ if (storage.converted) {
1308
+ console.error(`database: converted to auto_vacuum=${this.#retention.policy.autoVacuum} (${storage.pagesBefore} → ${storage.pagesAfter} pages)`);
1309
+ }
1239
1310
  }
1240
1311
  this.#drains.start();
1241
- this.#retention.start((cause) => { console.error("retention pass failed:", cause instanceof Error ? cause.message : String(cause)); });
1312
+ this.#retention?.start((cause) => { console.error("retention pass failed:", cause instanceof Error ? cause.message : String(cause)); });
1242
1313
  // Mimetypes owns its own discovery scan over @plurnk/plurnk-mimetypes-*
1243
1314
  // packages; pre-warm it so first index render doesn't pay the cost.
1244
1315
  await this.#mimetypes.ready();
@@ -1246,9 +1317,11 @@ export default class Daemon {
1246
1317
  console.warn(`mimetype discovery: '${name}' is discovered but untrusted (PLURNK_PLUGINS_TRUSTED_ONLY); not registered`);
1247
1318
  }
1248
1319
  // Discover + probe the installed executor siblings, then hand the
1249
- // registry to the engine for exec dispatch ({§exec-registry-resolves}). The
1250
- // shell is the default runtime, so its executor must boot usable.
1251
- const executors = await ExecutorRegistry.build({ defaultRuntime: "sh", cwd: this.#discoveryCwd });
1320
+ // registry to the engine for exec dispatch ({§exec-registry-resolves}).
1321
+ const executors = await ExecutorRegistry.build({ cwd: this.#discoveryCwd });
1322
+ for (const { runtime, error } of executors.configurationErrors()) {
1323
+ this.#configuration.record(`executor:${runtime}`, error);
1324
+ }
1252
1325
  this.#engine.setExecutors(executors);
1253
1326
  this.#engine.setFunctionalityDocuments((workspaceId) => this.#functionality.documents(workspaceId));
1254
1327
  // {§question-tool} — the native request-user-input runtime, process-wide;
@@ -1266,9 +1339,6 @@ export default class Daemon {
1266
1339
  detail: "in-process"
1267
1340
  }
1268
1341
  }]);
1269
- // {§effect-policy-tunable} — invalid operator policy fails boot, not the first execution.
1270
- EffectPolicy.validateConfiguration();
1271
- LoopPolicies.validateConfiguration();
1272
1342
  // {§exec} — mint a scheme per runtime tag so exec output entries address by tag
1273
1343
  // authority (sh:///l/t/s). The "exec" scheme stays for execution dispatch.
1274
1344
  this.#schemes.registerRuntimeSchemes(executors);
@@ -1279,7 +1349,10 @@ export default class Daemon {
1279
1349
  // {§module-discovery} — third-party daemon-module composition: trusted
1280
1350
  // packages declaring `plurnk.kind: "module"` register beside the
1281
1351
  // service's explicit composition before any module setup runs.
1282
- const discoveredModules = await discoverDaemonModules({ cwd: this.#discoveryCwd });
1352
+ const discoveredModules = await discoverDaemonModules({ cwd: this.#discoveryCwd, hostPaths: this.#hostPaths });
1353
+ for (const cause of discoveredModules.configurationErrors)
1354
+ this.#configuration.record("native-plugins", cause);
1355
+ this.#configuration.pluginReports(discoveredModules.reports);
1283
1356
  for (const packageName of discoveredModules.skipped) {
1284
1357
  console.warn(`module discovery: '${packageName}' is discovered but untrusted (PLURNK_PLUGINS_TRUSTED_ONLY); not registered`);
1285
1358
  }
@@ -1288,8 +1361,8 @@ export default class Daemon {
1288
1361
  }
1289
1362
  const setupSeam = this;
1290
1363
  for (const module of this.#modules) {
1291
- if (module.close !== undefined)
1292
- this.#moduleClosers.push(module);
1364
+ if (module.stop !== undefined || module.close !== undefined)
1365
+ this.#moduleLifetimes.push(module);
1293
1366
  await module.setup?.(setupSeam);
1294
1367
  }
1295
1368
  await this.#schemes.ready();
@@ -1300,8 +1373,8 @@ export default class Daemon {
1300
1373
  // lifecycle recovery are complete. None opens a listener of its own.
1301
1374
  for (const module of this.#modules) {
1302
1375
  const started = await module.start?.(this);
1303
- if (started !== undefined && !this.#moduleClosers.includes(started)) {
1304
- this.#moduleClosers.push(started);
1376
+ if (started !== undefined && !this.#moduleLifetimes.includes(started)) {
1377
+ this.#moduleLifetimes.push(started);
1305
1378
  }
1306
1379
  }
1307
1380
  }
@@ -1364,7 +1437,7 @@ export default class Daemon {
1364
1437
  // already between queue claim and activation must observe its own abort,
1365
1438
  // not misclassify orderly shutdown as a capability failure.
1366
1439
  const derivationAbort = new DOMException("daemon stopping", "AbortError");
1367
- this.#retention.stop();
1440
+ this.#retention?.stop();
1368
1441
  this.#engine.cancelAllProposals("daemon_stopping");
1369
1442
  this.#engine.cancelDerivations(derivationAbort);
1370
1443
  this.#drains.beginStop("daemon_stopping");
@@ -1386,10 +1459,15 @@ export default class Daemon {
1386
1459
  // Stop accepting external work immediately, but do not await listener
1387
1460
  // closure before cancelling active workers: an SSE connection may itself be
1388
1461
  // waiting for the worker cancellation that follows.
1389
- const moduleClose = Promise.allSettled(this.#moduleClosers
1390
- .toReversed()
1391
- .map((module) => Promise.resolve().then(() => module.close())));
1392
- this.#moduleClosers = [];
1462
+ const modules = this.#moduleLifetimes.toReversed();
1463
+ this.#moduleLifetimes = [];
1464
+ const modulePhase = async (phase) => {
1465
+ const results = await Promise.allSettled(modules.map((module) => Promise.resolve().then(() => module[phase]?.())));
1466
+ const errors = results.filter((result) => result.status === "rejected").map(({ reason }) => reason);
1467
+ if (errors.length > 0)
1468
+ throw new AggregateError(errors, `module ${phase} failed`);
1469
+ };
1470
+ const moduleStop = settle("modules stop", () => modulePhase("stop"));
1393
1471
  // Drain order: (1) tell the supervisor to abort worker scopes so
1394
1472
  // strike paths don't keep going, (2) await its active drains
1395
1473
  // to completion, (3) drain streaming schemes' background work
@@ -1411,11 +1489,7 @@ export default class Daemon {
1411
1489
  // A boundary publication queued behind the last turn settles before
1412
1490
  // modules close and before the DB goes away ({§functionality-publication}).
1413
1491
  const functionalityResult = await settle("functionality publications", () => this.#functionality.settle());
1414
- const moduleResult = await settle("modules close", async () => {
1415
- const results = await moduleClose;
1416
- if (results.some((r) => r.status === "rejected"))
1417
- throw new AggregateError(results.filter((r) => r.status === "rejected").map((r) => r.reason), "module close failed");
1418
- });
1492
+ const moduleStopResult = await moduleStop;
1419
1493
  const streamingResult = await settle("streaming schemes idle", () => this.#drainStreamingSchemes());
1420
1494
  const derivationResult = await settle("derivation drain", () => this.#engine.drainDerivations(derivationAbort));
1421
1495
  const mimetypeResult = this.#ownsMimetypes
@@ -1426,13 +1500,15 @@ export default class Daemon {
1426
1500
  // conclusion notifications. Join the supervisor-owned async tails only
1427
1501
  // after those producers settle, before the caller may close SQLite.
1428
1502
  const wakeResult = await settle("drains idle (wake)", () => this.#drains.idle());
1503
+ const moduleCloseResult = await settle("modules close", () => modulePhase("close"));
1429
1504
  // {§db-maintenance-optimize} — the last database step before the caller closes SQLite:
1430
1505
  // planner statistics refreshed on the writer, bounded by SQLite's own analysis limit.
1431
1506
  // {§retention-policy} — the operator's retention runs once more before the statistics.
1432
- const collectResult = await settle("retention", () => this.#retention.run());
1507
+ const collectResult = await settle("retention", async () => this.#retention?.run());
1433
1508
  const optimizeResult = await settle("database optimize", () => this.#db.maintenance_optimize.run({}));
1434
1509
  const closeErrors = [
1435
- moduleResult,
1510
+ moduleStopResult,
1511
+ moduleCloseResult,
1436
1512
  functionalityResult,
1437
1513
  drainResult,
1438
1514
  streamingResult,