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